From 20226e620477b113ea23f9ec79410221972329c5 Mon Sep 17 00:00:00 2001 From: Marcel <34648785+MarcelMWS@users.noreply.github.com> Date: Thu, 15 Nov 2018 10:02:25 +0100 Subject: [PATCH] update fork (#1) * Back to 50 initially bonded * Add query deposits cmds * Update test * Update PENDING.md * Back to on-operation=false, update PENDING.md * Remove unnecessary change, clarify amt in sim_test.go * Cleanup, additional item in PENDING.md * Update PENDING.md Co-Authored-By: alessio * Update client/keys/utils.go Co-Authored-By: alessio * update depositer addr * Address @rigelrozanski comments * Make linter happy * Update PENDING.md * Remove shorthand * Make "multi" const * Store last total power as sdk.Int, not sdk.Dec * Merge PR #2553: Renamed msg.Name() and msg.Type() to msg.Type() and msg.Route() * Fix stupid mistake * s/number/weight/ * LastValidatorPower is also an Int * Merge PR #2462: Add swagger-ui for gov, stake and slashing * 'make format' * Move PENDING to CHANGELOG * Linkify changelog * Fix db write perm * Additional cleanup * Remove logs from prior debugging * Merge PR #2599 from cosmos/jae/dist_refactor * Rename Pool -> DelRewards; PoolCommission -> ValCommision * FeePool.Pool -> FeePool.ValPool * WithdrawalHeight->DelPoolWithdrawalHeight * OnValidatorBeginUnbonding * Caught the bug's tail * Update vi.FeePoolWithdrawalHeight upon bonding * Fix staking slashUnbondingDelegation bug; fixes simulator failure #9 * Merge PR #2597: Add distribution accum invariants * PENDING.md => CHANGELOG.md * Manually linkify * Manually fix some links * Docs fixes in progress while running through the release process * More docs fixes * '--voter' is no longer required * Rectify validator setup documentation * Merge PR #2596: Cmds for validator unbondings and redelegations * Make simulation use a transition matrix for block size This enables simulating periods of high load, and periods of low to no load. (low load because future ops will still terminate in that time frame) * address bez's comments * fix flags in docs, closes #2530 * Merge PR #2616: Block redelegations to the same validator * Merge PR #2623: Speedup simulator by switching to goleveldb Due to requiring app.Commit() at the moment, golevel db is significantly faster than a memdb * fix block offsets in printing simulation block number * Merge PR #2644: Simulation: Print last block when there is an error There was an off by one error in the log printing function previously * Merge PR #2642: Add todo diagrams * Various sign command improvements - Exit with error if the user is attempting to sign with a key whose address is not among those who are expected to sign the transaction. - Add --print-signature-only to output only the generated signature. * Check sanity of signatures and report errors when run with --print-sigs * Improve errors reporting * Improve online docs * Refresh PENDING.md * Find better name for --print-signature-only * Fix integration tests * Validate --name * Fix integration tests * s/--print-sigs/--validate-signatures/ * s/--sig-only/--signature-only/ * Docs updated * Update PENDING.md * Rename append, it's go builtin * Set success = false when it fails * Apply suggestions from bez * Nest switches * Fix rebase * Document what --validate-signatures does * perform minor doc and function cleanup * move typedef * Merge PR #2614: Configurable Bech32 prefix for SDK users * Merge PR #2643: AppendTag function usage error. append elements do not work * simulation: Make validator choice use validator set This also had to change the default seed, since with the previous one it actually got into a state where there were no validators left bonded, lol. This also changes Unbond msgs from failing with almost 100% probability to now only failing with 33% probability. Thus more of the state machine is getting tested! * Update changelog * Merge PR #2657: Fix config.js * Merge PR #2589: Update Vesting Spec * Merge PR #2656: Revert read-only leveldb database * Revert read-only leveldb database Waiting on a fix for syndtr/goleveldb#240. * Update client/keys/utils.go * Include DNS alt name in certificate Closes: #2664 * Gaialite signal handling is broken, repair it * Merge PR #2665: simulation: Remove header from Invariant This got introduced recently, but wasn't actually needed, hence the reversion * Merge PR #2653: Add benchmark for get and set account * Fix test * Refactor TrapSignal * Fix lint * enforcing @jaekwon mergemaster * added querier redelegation * added validatorDelegations querier endpoint * LCD and CLI * cli fixes * removed redelegation stuff * address other comments * rebased * addressed comments * Make the simulator create the new comission rate sensibly * Update to TM v0.26.0 - Part I (#2679) * Update to TM v0.26.0 * Bez/tm0.26 update pt 2 redux (#2684) * Update to TM v0.26.0 * Update TODOs * Proof and verification updates * Fix linting * Fix key path creation * Temporarily fix tendermint revision to make tests pass * Fix merge conflict bug; Update PENDING * New genesis workflow (#2602) New genesis workflow: * `gaiad init` is now used to generate an empty `genesis.json`. * Genesis accounts need to be populated manually before running `gaiad collect-gentxs`. * This should support starfish too, see #2615 for more info. * Closes: #2596 #2615 * Validate validator address and address against respective account ex ante * Fix local testnet failures * New genesis tests * Run make format * Add --pubkey flag * gaiad collect-gentxs takes no args * Simulation improvements (logging fix, random genesis parameters) (#2617) * Print out initial update on every block * Randomize simulation parameters * Randomize initial liveness weightings * Randomize genesis parameters * fixed power store invariant * IterateValidatorsBonded -> IterateBondedValidatorsByPower * WriteValidators uses IterateLastValidators rather than IterateBondedValidatorsByPower * fixed democoin interface Closes #2556 Closes #2396 Via #2671: closes #2669 closes #2670 closes #2620 Offshoot issues: #2618 #2619 #2620 #2661 * Fix simulation bugs; Incorprates #2676 from Sunny (#2677) * Fix simulation bugs; Incorprates #2676 from Sunny * Address review feedback; Update PENDING * 'make format' * Revert "enforcing @jaekwon mergemaster" This reverts commit 15c209324f56e99f1bf1b323a3057ec2d27fad8e. * Update x/stake/client/rest/query.go Co-Authored-By: sunnya97 * addressed fede's comment * Switch gov proposal-queues to use iterators (#2638) * switched gov proposals queue to use iterators * update gov spec * update proposal.Equal * Amino api change * switched proposalID to uint64 * renamed Gov Procedures to Params * s/ActiveProposalQueueProposalKey/KeyActiveProposalQueueProposal/g * numLatestProposals -> Limit * fixed staking invariant breakage because of gov deposits * Send deposits to DepositedCoinsAccAddr or BurnedDepositCoinsAccAddr * Add general merkle absence proof (also for empty substores) (#2685) * Fix coins.IsLT() impl (#2686) * Fix coins.IsLT() impl * Fix coin.IsLT() impl * Coins.IsLT -> Coins.IsAllLT etc * Update testnet to use canonical genesis time (#2692) * Update testnet to use canonical genesis time * Fix linting in genesis test * Do not allow nil values to be set in CacheKVStore (#2708) * Do not allow nil values to be set in CacheKVStore * Makefile OS compatibility update * Merge PR #2714: Add commission data to MsgCreateValidator signature bytes * PENDING => CHANGELOG * Linkify changelog * Cleanup bank keeper * whitespacing * rand utile ... * moving stuff around a bit, trying to get rid of types * reorganize more * rename ambig naming of queueOperations * minimizing indentation * fix some duplicate to get passing * Address style comments * Reorganize CLI command structure. Fixes #2575 * Fix missing flags issue * Address linting issues * Fix gobash CLI testing * Fix typo * Cross-compiling get_tools Makefile added * operations functions * assertAllInvarients changes, Operation reorg * mock tendermint * util cleanup * event stats object, more general cleanup * compiling * pending * Removed comment from Makefile as per bez's request * val comments * Address PR comments * Update cmd/gaia/cmd/gaiacli/main.go Co-Authored-By: jackzampolin * PENDING * Fix state export/import, add to CI (#2690) * Update slashing import/export * More slashing.WriteGenesis * Add test import/export to CI * Store equality comparison. * Fix validator bond intra-tx counter * Set timeslices for unbonding validators * WriteGenesis => ExportGenesis * Delete validators from unbonding queue when re-bonded * Hook for validator deletion, fix staking genesis tests * Merge 0.26.0 back to develop (#2718) * PENDING => CHANGELOG * Linkify changelog * Merge PR #2716: Temporarily disable gaia lite insecure mode * TODO: need to update CHANGELOG w/ import-export PR #2690 * Update CHANGELOG/PENDING for straggling PR #2690 * Add small utility to add account to genesis.json after gaiad init * Update CHANGELOG.md * s/WriteGenesisFile/ExportGenesisFile/ * Update PENDING.md * Add --chain-id to testnet command * Address remaining comments from #2690 * Update PENDING.md * add back in PeriodicInvariant * Linter fix * Fix TimeoutCommit (#2743) * Fix TimeoutCommit to 5 seconds instead of whatever it was before which was too short. * Gaia-9000: Update to TM 0.26.1-rc2 (#2753) * Update to tm 0.26.1-rc2 to fix prometheus issue and node disconnect issue. * Gaia-9000: Update to TM 0.26.1-rc3 -- pex SeedMode fix * fix typo I think it might be a spelling mistake * Slight distribution spec cleanup * More cleanup * use defer * Use correct Bech32 prefix for show-address command (#2746) * Use consensus address bech32 prefix * Update show-address CLI description * Generate random moniker when missing * Update moniker prefix * Require moniker instead of generating a random one * update to tendermint v0.26.1 * Fix test coverage * Correctly set return code * Fix date to be cross platform * Merge PR #2752: Don't hardcode bondable denom * R4R: Fix unbonding command flow (#2727) * Fix required flag * Fix redelegation command * Add pending entry * update swagger.yaml * use newQuery...Params * Link to issue * Fix DiffKVStore * Address PR review * Working on stake import/export * Only apply validator set updates on initial genesis * Clarify comment * Fix failing test * add back in CLI command after rebase * Fix CLI tests * update to amino 0.14.1 * pending * R4R: Query Gov Params (#2576) * gov query params * Merge PR #2744: Fix Makefile targets dependencies * Fix Makefile targets dependencies * Remove unnecessary build deps from install targets * Create a rule for each tool * Don't dep test_lint on tools * Update docs/spec/distribution/overview.md Co-Authored-By: alexanderbez * Update docs/spec/distribution/overview.md Co-Authored-By: alexanderbez * Update docs/spec/distribution/overview.md Co-Authored-By: alexanderbez * Update overview.md * Documentation Structure Change and Cleanup (#2808) * Update docs/sdk/clients.md * organize ADR directory like tendermint * docs: move spec-proposals into spec/ * remove lotion, moved to website repo * move getting-started to cosmos-hub, and voyager to website * docs: move lite/ into clients/lite/ * move introduction/ content to website repo * move resources/ content to website repo * mv sdk/clients.md to clients/clients.md * mv validators to cosmos-hub/validators * move deprecated sdk/ content to _attic * sdk/modules.md is duplicate with modules/README.md * consolidate remianing sdk/ files into a single sdk.md * move examples/ to docs/examples/ * mv docs/cosmos-hub to docs/gaia * Add keys/accounts section to localnet docs * Bring back banner (#2814) * Build docs in CircleCI (#2810) * error checking the API call * added docs build trigger to circleci job * Update contributing.md with new merge policy (#2789) * Update contribuiting.md with new merge policy * deleted obsolete file (#2817) --- .circleci/config.yml | 44 +- .gitignore | 5 + CHANGELOG.md | 408 ++++- CONTRIBUTING.md | 50 +- Gopkg.lock | 109 +- Gopkg.toml | 66 +- Makefile | 102 +- PENDING.md | 180 +- README.md | 19 +- baseapp/baseapp.go | 11 +- baseapp/baseapp_test.go | 50 +- baseapp/query_test.go | 2 +- client/config.go | 20 +- client/context/broadcast.go | 8 +- client/context/context.go | 7 +- client/context/query.go | 65 +- client/input.go | 14 +- client/keys/add.go | 8 +- client/keys/delete.go | 16 +- client/keys/mnemonic.go | 78 + client/keys/new.go | 188 ++ client/keys/root.go | 2 + client/keys/show.go | 74 +- client/keys/update.go | 17 +- client/keys/utils.go | 41 +- client/keys/utils_test.go | 39 + client/lcd/certificates.go | 3 +- client/lcd/certificates_test.go | 2 +- client/lcd/lcd_test.go | 367 ++-- client/lcd/root.go | 46 +- client/lcd/swagger-ui/swagger.yaml | 1622 ++++++++++++++--- client/lcd/test_helpers.go | 144 +- client/lcd/version.go | 6 +- client/rpc/block.go | 10 +- client/rpc/root.go | 11 +- client/rpc/status.go | 9 +- client/rpc/validators.go | 10 +- client/tx/query.go | 8 +- client/tx/root.go | 9 - client/tx/search.go | 13 +- client/utils/rest.go | 30 +- client/utils/utils.go | 12 +- client/utils/utils_test.go | 4 +- cmd/cosmos-sdk-cli/cmd/init.go | 2 +- cmd/gaia/app/app.go | 215 ++- cmd/gaia/app/app_test.go | 2 + cmd/gaia/app/genesis.go | 369 ++-- cmd/gaia/app/genesis_test.go | 74 +- cmd/gaia/app/sim_test.go | 233 ++- cmd/gaia/app/test_utils.go | 78 - cmd/gaia/cli_test/cli_test.go | 320 +++- cmd/gaia/cmd/gaiacli/main.go | 110 +- cmd/gaia/cmd/gaiacli/query.go | 83 + cmd/gaia/cmd/gaiacli/tx.go | 83 + cmd/gaia/cmd/gaiad/main.go | 13 +- cmd/gaia/cmd/gaiadebug/hack.go | 12 +- cmd/gaia/cmd/gaiadebug/main.go | 9 +- cmd/gaia/init/collect.go | 118 ++ cmd/gaia/init/genesis_accts.go | 66 + cmd/gaia/init/gentx.go | 145 ++ cmd/gaia/init/init.go | 324 +--- cmd/gaia/init/init_test.go | 59 +- cmd/gaia/init/testnet.go | 307 +++- cmd/gaia/init/utils.go | 112 ++ crypto/keys/bcrypt/base64.go | 35 - crypto/keys/bcrypt/bcrypt.go | 297 --- crypto/keys/bip39/wordcodec.go | 66 - crypto/keys/bip39/wordcodec_test.go | 15 - crypto/keys/hd/fundraiser_test.go | 3 +- crypto/keys/hd/hdpath.go | 94 +- crypto/keys/hd/hdpath_test.go | 68 +- crypto/keys/keybase.go | 65 +- crypto/keys/keybase_test.go | 9 +- crypto/keys/keyerror/errors.go | 81 + crypto/keys/{ => mintkey}/mintkey.go | 64 +- crypto/keys/types.go | 15 +- docs/.vuepress/config.js | 101 +- docs/DOCS_README.md | 32 +- docs/PRIORITIES.md | 38 +- docs/README.md | 38 +- docs/{ => _attic}/sdk/core/app1.md | 6 +- docs/{ => _attic}/sdk/core/app2.md | 8 +- docs/{ => _attic}/sdk/core/app3.md | 46 +- docs/{ => _attic}/sdk/core/app4.md | 0 docs/{ => _attic}/sdk/core/app5.md | 0 docs/{ => _attic}/sdk/core/examples/app1.go | 5 +- docs/{ => _attic}/sdk/core/examples/app2.go | 7 +- .../sdk/core/examples/app2_test.go | 4 +- docs/{ => _attic}/sdk/core/examples/app3.go | 6 +- docs/{ => _attic}/sdk/core/examples/app4.go | 14 +- .../sdk/core/examples/app4_test.go | 0 docs/{ => _attic}/sdk/core/intro.md | 0 docs/{ => _attic}/sdk/core/multistore.md | 0 .../{ => _attic}/sdk/sdk-by-examples/intro.md | 0 .../simple-governance/app-init.md | 0 .../simple-governance/bridging-it-all.md | 6 +- .../simple-governance/intro.md | 0 .../running-the-application.md | 0 .../simple-governance/setup-and-design.md | 0 .../simple-governance/simple-gov-module.md | 0 docs/architecture/adr-002-docs-structure.md | 94 + docs/architecture/decision-records/README.md | 22 - .../adr-001-message-counter.md | 54 - .../decision-records/adr-template.md | 32 - docs/clients/README.md | 18 + docs/clients/cli.md | 7 +- .../lite}/getting_started.md | 35 +- docs/{light => clients/lite}/pics/C2H.png | Bin docs/{light => clients/lite}/pics/H2C.png | Bin docs/{light => clients/lite}/pics/MA.png | Bin .../{light => clients/lite}/pics/absence1.png | Bin .../{light => clients/lite}/pics/absence2.png | Bin .../{light => clients/lite}/pics/absence3.png | Bin .../lite}/pics/architecture.png | Bin .../lite}/pics/changeProcess.png | Bin .../lite}/pics/commitValidation.png | Bin .../lite}/pics/create-account.png | Bin docs/{light => clients/lite}/pics/deposit.png | Bin .../lite}/pics/existProof.png | Bin .../lite}/pics/high-level.png | Bin .../lite}/pics/light-client-architecture.png | Bin .../lite}/pics/loadbalanceDiagram.png | Bin .../lite}/pics/simpleMerkleTree.png | Bin .../lite}/pics/substoreProof.png | Bin .../lite}/pics/transfer-tokens.png | Bin .../{light => clients/lite}/pics/transfer.png | Bin .../lite}/pics/trustPropagate.png | Bin .../lite}/pics/updateValidatorToHeight.png | Bin .../lite}/pics/validatorSetChange.png | Bin .../{light => clients/lite}/pics/withdraw.png | Bin docs/clients/lite/readme.md | 86 + docs/{light => clients/lite}/specification.md | 161 +- docs/clients/node.md | 45 - docs/clients/rest.md | 6 - docs/clients/service-providers.md | 33 +- docs/{graphics => }/cosmos-docs.jpg | Bin docs/{graphics => }/cosmos-sdk-image.png | Bin {examples => docs/examples}/README.md | 0 .../examples}/basecoin/app/app.go | 23 +- .../examples}/basecoin/app/app_test.go | 6 +- .../examples}/basecoin/cli_test/cli_test.go | 13 +- .../examples}/basecoin/cmd/basecli/main.go | 22 +- docs/examples/basecoin/cmd/basecoind/main.go | 133 ++ .../examples}/basecoin/types/account.go | 2 +- .../examples}/democoin/app/app.go | 35 +- .../examples}/democoin/app/app_test.go | 8 +- .../examples}/democoin/cli_test/cli_test.go | 14 +- .../examples}/democoin/cmd/democli/main.go | 36 +- docs/examples/democoin/cmd/democoind/main.go | 174 ++ .../examples}/democoin/mock/validator.go | 14 +- .../examples}/democoin/types/account.go | 4 +- .../democoin/x/assoc/validator_set.go | 0 .../democoin/x/assoc/validator_set_test.go | 2 +- .../examples}/democoin/x/cool/app_test.go | 4 +- .../democoin/x/cool/client/cli/tx.go | 2 +- .../examples}/democoin/x/cool/codec.go | 0 .../examples}/democoin/x/cool/errors.go | 0 .../examples}/democoin/x/cool/handler.go | 2 +- .../examples}/democoin/x/cool/keeper.go | 6 +- .../examples}/democoin/x/cool/keeper_test.go | 4 +- .../examples}/democoin/x/cool/types.go | 8 +- .../examples}/democoin/x/oracle/README.md | 2 +- .../examples}/democoin/x/oracle/errors.go | 0 .../examples}/democoin/x/oracle/handler.go | 0 .../examples}/democoin/x/oracle/keeper.go | 4 +- .../democoin/x/oracle/keeper_keys.go | 4 +- .../democoin/x/oracle/oracle_test.go | 10 +- .../examples}/democoin/x/oracle/types.go | 2 +- .../examples}/democoin/x/pow/app_test.go | 4 +- .../examples}/democoin/x/pow/client/cli/tx.go | 2 +- .../examples}/democoin/x/pow/codec.go | 0 .../examples}/democoin/x/pow/errors.go | 0 .../examples}/democoin/x/pow/handler.go | 2 +- .../examples}/democoin/x/pow/handler_test.go | 2 +- .../examples}/democoin/x/pow/keeper.go | 4 +- .../examples}/democoin/x/pow/keeper_test.go | 4 +- .../examples}/democoin/x/pow/mine.go | 0 .../examples}/democoin/x/pow/types.go | 4 +- .../examples}/democoin/x/pow/types_test.go | 2 +- .../x/simplestake/client/cli/commands.go | 2 +- .../examples}/democoin/x/simplestake/codec.go | 0 .../democoin/x/simplestake/errors.go | 0 .../democoin/x/simplestake/handler.go | 0 .../democoin/x/simplestake/keeper.go | 6 +- .../democoin/x/simplestake/keeper_test.go | 12 +- .../examples}/democoin/x/simplestake/msgs.go | 8 +- .../democoin/x/simplestake/msgs_test.go | 0 .../examples}/democoin/x/simplestake/types.go | 0 .../examples}/democoin/x/sketchy/handler.go | 0 {examples => docs/examples}/kvstore/kvstore | Bin {examples => docs/examples}/kvstore/main.go | 0 {examples => docs/examples}/kvstore/tx.go | 4 +- docs/gaia/README.md | 19 + docs/{sdk/clients.md => gaia/gaiacli.md} | 247 ++- .../{getting-started => gaia}/installation.md | 18 +- .../{getting-started => gaia}/join-testnet.md | 36 +- docs/{clients => gaia}/keys.md | 0 docs/{clients => gaia}/ledger.md | 0 docs/{getting-started => gaia}/networks.md | 26 +- docs/{ => gaia}/validators/overview.md | 0 docs/{ => gaia}/validators/security.md | 0 docs/{ => gaia}/validators/validator-faq.md | 0 docs/{ => gaia}/validators/validator-setup.md | 38 +- docs/getting-started/voyager.md | 9 - docs/intro/README.md | 43 + docs/intro/ocap.md | 102 ++ docs/intro/sdk-app-architecture.md | 62 + docs/introduction/cosmos-hub.md | 32 - docs/introduction/tendermint-cosmos.md | 27 - docs/introduction/tendermint.md | 47 - docs/introduction/what-is-cosmos.md | 7 - docs/light/api.md | 961 ---------- docs/light/load_balancer.md | 203 --- docs/light/readme.md | 101 - docs/light/todo.md | 16 - docs/lotion/overview.md | 54 - docs/reference/baseapp.md | 14 + docs/reference/store/README.md | 0 docs/resources/delegator-faq.md | 69 - docs/resources/faq.md | 95 - docs/resources/whitepaper-ko.md | 756 -------- docs/resources/whitepaper-pt.md | 1482 --------------- docs/resources/whitepaper-zh-CN.md | 1013 ---------- docs/resources/whitepaper.md | 1590 ---------------- docs/sdk/cosmos-sdk-cli.md | 34 - docs/sdk/modules.md | 19 - docs/sdk/overview.md | 205 --- docs/spec/README.md | 43 +- docs/spec/auth/vesting.md | 516 ++++-- docs/spec/distribution/end_block.md | 10 +- docs/spec/distribution/hooks.md | 2 +- docs/spec/distribution/overview.md | 68 +- docs/spec/distribution/state.md | 14 +- docs/spec/distribution/transactions.md | 69 +- docs/spec/governance/README.md | 4 +- docs/spec/governance/state.md | 100 +- docs/spec/governance/transactions.md | 92 +- .../{ => spec}/ics/ics-030-signed-messages.md | 0 docs/spec/mint/begin_block.md | 28 + docs/spec/mint/state.md | 31 + docs/spec/params/README.md | 20 + docs/spec/params/keeper.md | 17 + docs/spec/params/subspace.md | 76 + docs/spec/slashing/begin-block.md | 22 +- docs/spec/slashing/hooks.md | 11 + docs/spec/slashing/state.md | 12 +- docs/spec/slashing/transactions.md | 4 - .../f1-fee-distribution/f1_fee_distr.pdf | Bin 0 -> 160589 bytes .../f1-fee-distribution/f1_fee_distr.tex | 135 ++ docs/spec/staking/end_block.md | 21 +- docs/spec/staking/hooks.md | 8 +- docs/spec/staking/state.md | 9 - docs/spec/staking/transactions.md | 5 +- examples/basecoin/cmd/basecoind/main.go | 57 - examples/democoin/cmd/democoind/main.go | 88 - scripts/Makefile | 54 + scripts/get_tools.sh | 49 - scripts/import-export-sim.sh | 54 + scripts/multisim.sh | 5 +- server/config/config.go | 13 - server/init.go | 90 +- server/mock/app.go | 21 +- server/mock/app_test.go | 3 +- server/mock/tx.go | 4 +- server/start.go | 12 +- server/test_helpers.go | 2 + server/tm_cmds.go | 8 +- server/util.go | 22 +- store/cachekvstore.go | 7 + store/iavlstore.go | 39 +- store/iavlstore_test.go | 8 +- store/list.go | 112 ++ store/list_test.go | 75 + store/multistoreproof.go | 161 +- store/multistoreproof_test.go | 249 ++- store/prefixstore_test.go | 4 - store/queue.go | 88 + store/queue_test.go | 102 ++ store/rootmultistore.go | 42 +- store/rootmultistore_test.go | 4 +- tests/gobash.go | 8 +- types/address.go | 36 +- types/address_test.go | 43 + types/coin.go | 56 +- types/coin_test.go | 73 +- types/config.go | 105 ++ types/context.go | 10 +- types/context_test.go | 7 +- types/decimal.go | 75 +- types/decimal_test.go | 58 +- types/errors.go | 30 +- types/errors_test.go | 26 + types/int.go | 8 + types/lib/linear.go | 254 --- types/lib/linear_test.go | 169 -- types/stake.go | 24 +- types/store.go | 41 +- types/tx_msg.go | 8 +- types/utils.go | 28 + types/utils_test.go | 25 +- x/auth/account_test.go | 6 +- x/auth/ante.go | 33 +- x/auth/ante_test.go | 84 +- x/auth/client/cli/sign.go | 90 +- x/auth/client/rest/query.go | 9 +- x/auth/client/rest/sign.go | 9 +- x/auth/client/txbuilder/txbuilder.go | 5 +- x/auth/client/txbuilder/txbuilder_test.go | 5 +- x/auth/context.go | 2 +- x/auth/feekeeper.go | 14 +- x/auth/feekeeper_test.go | 4 +- x/auth/genesis.go | 33 + x/auth/{mapper.go => keeper.go} | 46 +- x/auth/{mapper_test.go => keeper_test.go} | 97 +- x/auth/simulation/fake.go | 62 + x/auth/stdtx.go | 3 +- x/bank/app_test.go | 6 +- x/bank/bench_test.go | 2 +- x/bank/client/cli/broadcast.go | 3 +- x/bank/client/cli/sendtx.go | 4 +- x/bank/client/rest/broadcast.go | 3 +- x/bank/client/rest/sendtx.go | 2 +- x/bank/handler.go | 2 +- x/bank/keeper.go | 114 +- x/bank/keeper_test.go | 28 +- x/bank/msgs.go | 8 +- x/bank/msgs_test.go | 8 +- x/bank/simulation/invariants.go | 4 +- x/bank/simulation/msgs.go | 8 +- x/bank/simulation/sim_test.go | 2 +- x/distribution/abci_app.go | 39 + x/distribution/alias.go | 79 + x/distribution/client/cli/tx.go | 114 ++ x/distribution/genesis.go | 40 + x/distribution/handler.go | 90 + x/distribution/keeper/allocation.go | 48 + x/distribution/keeper/allocation_test.go | 110 ++ x/distribution/keeper/delegation.go | 200 ++ x/distribution/keeper/delegation_test.go | 253 +++ x/distribution/keeper/genesis.go | 50 + x/distribution/keeper/hooks.go | 128 ++ x/distribution/keeper/keeper.go | 157 ++ x/distribution/keeper/keeper_test.go | 37 + x/distribution/keeper/key.go | 55 + x/distribution/keeper/test_common.go | 181 ++ x/distribution/keeper/validator.go | 100 + x/distribution/keeper/validator_test.go | 192 ++ x/distribution/simulation/invariants.go | 50 + x/distribution/simulation/msgs.go | 122 ++ x/distribution/tags/tags.go | 17 + x/distribution/types/dec_coin.go | 43 +- x/distribution/types/delegator_info.go | 72 +- x/distribution/types/delegator_info_test.go | 44 +- x/distribution/types/errors.go | 11 +- x/distribution/types/fee_pool.go | 43 +- x/distribution/types/fee_pool_test.go | 15 +- x/distribution/types/genesis.go | 48 +- x/distribution/types/keepers.go | 2 + x/distribution/types/msg.go | 18 +- .../types/{test_utils.go => test_common.go} | 6 +- x/distribution/types/total_accum.go | 40 + x/distribution/types/total_accum_test.go | 19 + x/distribution/types/validator_info.go | 135 +- x/distribution/types/validator_info_test.go | 63 +- x/gov/client/cli/tx.go | 85 +- x/gov/client/rest/rest.go | 165 +- x/gov/client/utils.go | 44 + x/gov/depositsvotes.go | 4 +- x/gov/endblocker_test.go | 164 +- x/gov/errors.go | 12 +- x/gov/genesis.go | 94 +- x/gov/handler.go | 81 +- x/gov/keeper.go | 261 ++- x/gov/keeper_keys.go | 54 +- x/gov/keeper_test.go | 84 +- x/gov/msgs.go | 32 +- x/gov/msgs_test.go | 25 +- x/gov/{procedures.go => params.go} | 12 +- x/gov/proposals.go | 44 +- x/gov/{queryable.go => querier.go} | 98 +- x/gov/querier_test.go | 306 ++++ x/gov/simulation/msgs.go | 45 +- x/gov/simulation/sim_test.go | 10 +- x/gov/tally.go | 12 +- x/gov/tally_test.go | 25 +- x/gov/test_common.go | 6 +- x/ibc/app_test.go | 4 +- x/ibc/client/cli/relay.go | 6 +- x/ibc/handler.go | 2 +- x/ibc/ibc_test.go | 4 +- x/ibc/mapper.go | 8 +- x/ibc/types.go | 8 +- x/ibc/types_test.go | 4 +- x/mint/abci_app.go | 26 + x/mint/expected_keepers.go | 17 + x/mint/genesis.go | 55 + x/mint/keeper.go | 85 + x/mint/minter.go | 72 + x/mint/minter_test.go | 53 + x/mint/params.go | 44 + x/mock/app.go | 73 +- x/mock/app_test.go | 10 +- x/mock/simulation/account.go | 51 + x/mock/simulation/constants.go | 31 - x/mock/simulation/doc.go | 32 +- x/mock/simulation/event_stats.go | 30 + x/mock/simulation/invariants.go | 30 + x/mock/simulation/mock_tendermint.go | 201 ++ x/mock/simulation/operation.go | 112 ++ x/mock/simulation/params.go | 69 + x/mock/simulation/rand_util.go | 64 + x/mock/simulation/random_simulate_blocks.go | 426 ----- x/mock/simulation/simulate.go | 330 ++++ x/mock/simulation/transition_matrix.go | 18 +- x/mock/simulation/types.go | 86 - x/mock/simulation/util.go | 213 +-- x/mock/test_utils.go | 2 +- x/params/doc.go | 35 +- x/params/keeper_test.go | 99 +- x/params/subspace/subspace.go | 37 +- x/params/subspace/table.go | 39 +- x/params/subspace/table_test.go | 35 + x/slashing/app_test.go | 14 +- x/slashing/client/cli/query.go | 2 +- x/slashing/client/rest/query.go | 29 +- x/slashing/client/rest/tx.go | 17 +- x/slashing/genesis.go | 75 +- x/slashing/handler.go | 6 +- x/slashing/handler_test.go | 6 +- x/slashing/hooks.go | 57 +- x/slashing/hooks_test.go | 6 +- x/slashing/keeper.go | 72 +- x/slashing/keeper_test.go | 171 +- x/slashing/keys.go | 26 +- x/slashing/msg.go | 6 +- x/slashing/signing_info.go | 73 +- x/slashing/signing_info_test.go | 16 +- x/slashing/simulation/invariants.go | 4 +- x/slashing/slashing_period.go | 20 +- x/slashing/test_common.go | 16 +- x/slashing/tick.go | 2 +- x/slashing/tick_test.go | 7 +- x/stake/app_test.go | 36 +- x/stake/client/cli/flags.go | 26 +- x/stake/client/cli/query.go | 108 +- x/stake/client/cli/tx.go | 64 +- x/stake/client/rest/query.go | 358 +--- x/stake/client/rest/tx.go | 36 +- x/stake/client/rest/utils.go | 91 + x/stake/genesis.go | 109 +- x/stake/genesis_test.go | 33 +- x/stake/handler.go | 57 +- x/stake/handler_test.go | 229 ++- x/stake/keeper/delegation.go | 101 +- x/stake/keeper/delegation_test.go | 201 +- x/stake/keeper/hooks.go | 26 +- x/stake/keeper/keeper.go | 80 +- x/stake/keeper/key.go | 64 +- x/stake/keeper/key_test.go | 20 +- x/stake/keeper/params.go | 28 - x/stake/keeper/sdk_types.go | 43 +- x/stake/keeper/slash.go | 17 +- x/stake/keeper/slash_test.go | 26 +- x/stake/keeper/test_common.go | 39 +- x/stake/keeper/val_state_change.go | 104 +- x/stake/keeper/validator.go | 131 +- x/stake/keeper/validator_test.go | 120 +- x/stake/querier/queryable.go | 192 +- x/stake/querier/queryable_test.go | 232 ++- x/stake/simulation/invariants.go | 88 +- x/stake/simulation/msgs.go | 85 +- x/stake/simulation/sim_test.go | 14 +- x/stake/stake.go | 48 +- x/stake/test_common.go | 62 + x/stake/types/commission.go | 2 +- x/stake/types/delegation.go | 37 +- x/stake/types/errors.go | 14 + x/stake/types/genesis.go | 24 +- x/stake/types/inflation_test.go | 145 -- x/stake/types/msg.go | 25 +- x/stake/types/msg_test.go | 6 +- x/stake/types/params.go | 47 +- x/stake/types/pool.go | 76 +- x/stake/types/validator.go | 28 +- x/stake/types/validator_test.go | 13 +- 485 files changed, 16922 insertions(+), 14447 deletions(-) create mode 100644 client/keys/mnemonic.go create mode 100644 client/keys/new.go create mode 100644 client/keys/utils_test.go delete mode 100644 cmd/gaia/app/test_utils.go create mode 100644 cmd/gaia/cmd/gaiacli/query.go create mode 100644 cmd/gaia/cmd/gaiacli/tx.go create mode 100644 cmd/gaia/init/collect.go create mode 100644 cmd/gaia/init/genesis_accts.go create mode 100644 cmd/gaia/init/gentx.go create mode 100644 cmd/gaia/init/utils.go delete mode 100644 crypto/keys/bcrypt/base64.go delete mode 100644 crypto/keys/bcrypt/bcrypt.go delete mode 100644 crypto/keys/bip39/wordcodec.go delete mode 100644 crypto/keys/bip39/wordcodec_test.go create mode 100644 crypto/keys/keyerror/errors.go rename crypto/keys/{ => mintkey}/mintkey.go (76%) rename docs/{ => _attic}/sdk/core/app1.md (99%) rename docs/{ => _attic}/sdk/core/app2.md (97%) rename docs/{ => _attic}/sdk/core/app3.md (91%) rename docs/{ => _attic}/sdk/core/app4.md (100%) rename docs/{ => _attic}/sdk/core/app5.md (100%) rename docs/{ => _attic}/sdk/core/examples/app1.go (98%) rename docs/{ => _attic}/sdk/core/examples/app2.go (97%) rename docs/{ => _attic}/sdk/core/examples/app2_test.go (92%) rename docs/{ => _attic}/sdk/core/examples/app3.go (90%) rename docs/{ => _attic}/sdk/core/examples/app4.go (85%) rename docs/{ => _attic}/sdk/core/examples/app4_test.go (100%) rename docs/{ => _attic}/sdk/core/intro.md (100%) rename docs/{ => _attic}/sdk/core/multistore.md (100%) rename docs/{ => _attic}/sdk/sdk-by-examples/intro.md (100%) rename docs/{ => _attic}/sdk/sdk-by-examples/simple-governance/app-init.md (100%) rename docs/{ => _attic}/sdk/sdk-by-examples/simple-governance/bridging-it-all.md (98%) rename docs/{ => _attic}/sdk/sdk-by-examples/simple-governance/intro.md (100%) rename docs/{ => _attic}/sdk/sdk-by-examples/simple-governance/running-the-application.md (100%) rename docs/{ => _attic}/sdk/sdk-by-examples/simple-governance/setup-and-design.md (100%) rename docs/{ => _attic}/sdk/sdk-by-examples/simple-governance/simple-gov-module.md (100%) create mode 100644 docs/architecture/adr-002-docs-structure.md delete mode 100644 docs/architecture/decision-records/README.md delete mode 100644 docs/architecture/decision-records/adr-001-message-counter.md delete mode 100644 docs/architecture/decision-records/adr-template.md create mode 100644 docs/clients/README.md rename docs/{light => clients/lite}/getting_started.md (56%) rename docs/{light => clients/lite}/pics/C2H.png (100%) rename docs/{light => clients/lite}/pics/H2C.png (100%) rename docs/{light => clients/lite}/pics/MA.png (100%) rename docs/{light => clients/lite}/pics/absence1.png (100%) rename docs/{light => clients/lite}/pics/absence2.png (100%) rename docs/{light => clients/lite}/pics/absence3.png (100%) rename docs/{light => clients/lite}/pics/architecture.png (100%) rename docs/{light => clients/lite}/pics/changeProcess.png (100%) rename docs/{light => clients/lite}/pics/commitValidation.png (100%) rename docs/{light => clients/lite}/pics/create-account.png (100%) rename docs/{light => clients/lite}/pics/deposit.png (100%) rename docs/{light => clients/lite}/pics/existProof.png (100%) rename docs/{light => clients/lite}/pics/high-level.png (100%) rename docs/{light => clients/lite}/pics/light-client-architecture.png (100%) rename docs/{light => clients/lite}/pics/loadbalanceDiagram.png (100%) rename docs/{light => clients/lite}/pics/simpleMerkleTree.png (100%) rename docs/{light => clients/lite}/pics/substoreProof.png (100%) rename docs/{light => clients/lite}/pics/transfer-tokens.png (100%) rename docs/{light => clients/lite}/pics/transfer.png (100%) rename docs/{light => clients/lite}/pics/trustPropagate.png (100%) rename docs/{light => clients/lite}/pics/updateValidatorToHeight.png (100%) rename docs/{light => clients/lite}/pics/validatorSetChange.png (100%) rename docs/{light => clients/lite}/pics/withdraw.png (100%) create mode 100644 docs/clients/lite/readme.md rename docs/{light => clients/lite}/specification.md (64%) delete mode 100644 docs/clients/node.md delete mode 100644 docs/clients/rest.md rename docs/{graphics => }/cosmos-docs.jpg (100%) rename docs/{graphics => }/cosmos-sdk-image.png (100%) rename {examples => docs/examples}/README.md (100%) rename {examples => docs/examples}/basecoin/app/app.go (90%) rename {examples => docs/examples}/basecoin/app/app_test.go (92%) rename {examples => docs/examples}/basecoin/cli_test/cli_test.go (67%) rename {examples => docs/examples}/basecoin/cmd/basecli/main.go (84%) create mode 100644 docs/examples/basecoin/cmd/basecoind/main.go rename {examples => docs/examples}/basecoin/types/account.go (97%) rename {examples => docs/examples}/democoin/app/app.go (85%) rename {examples => docs/examples}/democoin/app/app_test.go (88%) rename {examples => docs/examples}/democoin/cli_test/cli_test.go (67%) rename {examples => docs/examples}/democoin/cmd/democli/main.go (68%) create mode 100644 docs/examples/democoin/cmd/democoind/main.go rename {examples => docs/examples}/democoin/mock/validator.go (87%) rename {examples => docs/examples}/democoin/types/account.go (94%) rename {examples => docs/examples}/democoin/x/assoc/validator_set.go (100%) rename {examples => docs/examples}/democoin/x/assoc/validator_set_test.go (97%) rename {examples => docs/examples}/democoin/x/cool/app_test.go (96%) rename {examples => docs/examples}/democoin/x/cool/client/cli/tx.go (96%) rename {examples => docs/examples}/democoin/x/cool/codec.go (100%) rename {examples => docs/examples}/democoin/x/cool/errors.go (100%) rename {examples => docs/examples}/democoin/x/cool/handler.go (95%) rename {examples => docs/examples}/democoin/x/cool/keeper.go (90%) rename {examples => docs/examples}/democoin/x/cool/keeper_test.go (92%) rename {examples => docs/examples}/democoin/x/cool/types.go (91%) rename {examples => docs/examples}/democoin/x/oracle/README.md (89%) rename {examples => docs/examples}/democoin/x/oracle/errors.go (100%) rename {examples => docs/examples}/democoin/x/oracle/handler.go (100%) rename {examples => docs/examples}/democoin/x/oracle/keeper.go (95%) rename {examples => docs/examples}/democoin/x/oracle/keeper_keys.go (85%) rename {examples => docs/examples}/democoin/x/oracle/oracle_test.go (96%) rename {examples => docs/examples}/democoin/x/oracle/types.go (97%) rename {examples => docs/examples}/democoin/x/pow/app_test.go (95%) rename {examples => docs/examples}/democoin/x/pow/client/cli/tx.go (95%) rename {examples => docs/examples}/democoin/x/pow/codec.go (100%) rename {examples => docs/examples}/democoin/x/pow/errors.go (100%) rename {examples => docs/examples}/democoin/x/pow/handler.go (92%) rename {examples => docs/examples}/democoin/x/pow/handler_test.go (96%) rename {examples => docs/examples}/democoin/x/pow/keeper.go (97%) rename {examples => docs/examples}/democoin/x/pow/keeper_test.go (93%) rename {examples => docs/examples}/democoin/x/pow/mine.go (100%) rename {examples => docs/examples}/democoin/x/pow/types.go (94%) rename {examples => docs/examples}/democoin/x/pow/types_test.go (98%) rename {examples => docs/examples}/democoin/x/simplestake/client/cli/commands.go (97%) rename {examples => docs/examples}/democoin/x/simplestake/codec.go (100%) rename {examples => docs/examples}/democoin/x/simplestake/errors.go (100%) rename {examples => docs/examples}/democoin/x/simplestake/handler.go (100%) rename {examples => docs/examples}/democoin/x/simplestake/keeper.go (95%) rename {examples => docs/examples}/democoin/x/simplestake/keeper_test.go (89%) rename {examples => docs/examples}/democoin/x/simplestake/msgs.go (87%) rename {examples => docs/examples}/democoin/x/simplestake/msgs_test.go (100%) rename {examples => docs/examples}/democoin/x/simplestake/types.go (100%) rename {examples => docs/examples}/democoin/x/sketchy/handler.go (100%) rename {examples => docs/examples}/kvstore/kvstore (100%) rename {examples => docs/examples}/kvstore/main.go (100%) rename {examples => docs/examples}/kvstore/tx.go (97%) create mode 100644 docs/gaia/README.md rename docs/{sdk/clients.md => gaia/gaiacli.md} (72%) rename docs/{getting-started => gaia}/installation.md (51%) rename docs/{getting-started => gaia}/join-testnet.md (72%) rename docs/{clients => gaia}/keys.md (100%) rename docs/{clients => gaia}/ledger.md (100%) rename docs/{getting-started => gaia}/networks.md (89%) rename docs/{ => gaia}/validators/overview.md (100%) rename docs/{ => gaia}/validators/security.md (100%) rename docs/{ => gaia}/validators/validator-faq.md (100%) rename docs/{ => gaia}/validators/validator-setup.md (81%) delete mode 100644 docs/getting-started/voyager.md create mode 100644 docs/intro/README.md create mode 100644 docs/intro/ocap.md create mode 100644 docs/intro/sdk-app-architecture.md delete mode 100644 docs/introduction/cosmos-hub.md delete mode 100644 docs/introduction/tendermint-cosmos.md delete mode 100644 docs/introduction/tendermint.md delete mode 100644 docs/introduction/what-is-cosmos.md delete mode 100644 docs/light/api.md delete mode 100644 docs/light/load_balancer.md delete mode 100644 docs/light/readme.md delete mode 100644 docs/light/todo.md delete mode 100644 docs/lotion/overview.md create mode 100644 docs/reference/baseapp.md create mode 100644 docs/reference/store/README.md delete mode 100644 docs/resources/delegator-faq.md delete mode 100644 docs/resources/faq.md delete mode 100644 docs/resources/whitepaper-ko.md delete mode 100644 docs/resources/whitepaper-pt.md delete mode 100644 docs/resources/whitepaper-zh-CN.md delete mode 100644 docs/resources/whitepaper.md delete mode 100644 docs/sdk/cosmos-sdk-cli.md delete mode 100644 docs/sdk/modules.md delete mode 100644 docs/sdk/overview.md rename docs/{ => spec}/ics/ics-030-signed-messages.md (100%) create mode 100644 docs/spec/mint/begin_block.md create mode 100644 docs/spec/mint/state.md create mode 100644 docs/spec/params/README.md create mode 100644 docs/spec/params/keeper.md create mode 100644 docs/spec/params/subspace.md create mode 100644 docs/spec/spec-proposals/f1-fee-distribution/f1_fee_distr.pdf create mode 100644 docs/spec/spec-proposals/f1-fee-distribution/f1_fee_distr.tex delete mode 100644 examples/basecoin/cmd/basecoind/main.go delete mode 100644 examples/democoin/cmd/democoind/main.go create mode 100644 scripts/Makefile delete mode 100755 scripts/get_tools.sh create mode 100755 scripts/import-export-sim.sh create mode 100644 store/list.go create mode 100644 store/list_test.go create mode 100644 store/queue.go create mode 100644 store/queue_test.go create mode 100644 types/config.go delete mode 100644 types/lib/linear.go delete mode 100644 types/lib/linear_test.go create mode 100644 x/auth/genesis.go rename x/auth/{mapper.go => keeper.go} (71%) rename x/auth/{mapper_test.go => keeper_test.go} (55%) create mode 100644 x/auth/simulation/fake.go create mode 100644 x/distribution/abci_app.go create mode 100644 x/distribution/alias.go create mode 100644 x/distribution/client/cli/tx.go create mode 100644 x/distribution/genesis.go create mode 100644 x/distribution/handler.go create mode 100644 x/distribution/keeper/allocation.go create mode 100644 x/distribution/keeper/allocation_test.go create mode 100644 x/distribution/keeper/delegation.go create mode 100644 x/distribution/keeper/delegation_test.go create mode 100644 x/distribution/keeper/genesis.go create mode 100644 x/distribution/keeper/hooks.go create mode 100644 x/distribution/keeper/keeper.go create mode 100644 x/distribution/keeper/keeper_test.go create mode 100644 x/distribution/keeper/key.go create mode 100644 x/distribution/keeper/test_common.go create mode 100644 x/distribution/keeper/validator.go create mode 100644 x/distribution/keeper/validator_test.go create mode 100644 x/distribution/simulation/invariants.go create mode 100644 x/distribution/simulation/msgs.go create mode 100644 x/distribution/tags/tags.go rename x/distribution/types/{test_utils.go => test_common.go} (82%) create mode 100644 x/distribution/types/total_accum.go create mode 100644 x/distribution/types/total_accum_test.go create mode 100644 x/gov/client/utils.go rename x/gov/{procedures.go => params.go} (78%) rename x/gov/{queryable.go => querier.go} (60%) create mode 100644 x/gov/querier_test.go create mode 100644 x/mint/abci_app.go create mode 100644 x/mint/expected_keepers.go create mode 100644 x/mint/genesis.go create mode 100644 x/mint/keeper.go create mode 100644 x/mint/minter.go create mode 100644 x/mint/minter_test.go create mode 100644 x/mint/params.go create mode 100644 x/mock/simulation/account.go delete mode 100644 x/mock/simulation/constants.go create mode 100644 x/mock/simulation/event_stats.go create mode 100644 x/mock/simulation/invariants.go create mode 100644 x/mock/simulation/mock_tendermint.go create mode 100644 x/mock/simulation/operation.go create mode 100644 x/mock/simulation/params.go create mode 100644 x/mock/simulation/rand_util.go delete mode 100644 x/mock/simulation/random_simulate_blocks.go create mode 100644 x/mock/simulation/simulate.go delete mode 100644 x/mock/simulation/types.go create mode 100644 x/params/subspace/table_test.go create mode 100644 x/stake/test_common.go delete mode 100644 x/stake/types/inflation_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 879df07f02a9..6be110cf27b6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,6 +7,13 @@ defaults: &defaults environment: GOBIN: /tmp/workspace/bin +docs_update: &docs_deploy + working_directory: ~/repo + docker: + - image: tendermint/docs_deployment + environment: + AWS_REGION: us-east-1 + jobs: setup_dependencies: @@ -137,6 +144,24 @@ jobs: export PATH="$GOBIN:$PATH" make test_sim_gaia_fast + test_sim_gaia_import_export: + <<: *defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - run: + name: dependencies + command: | + export PATH="$GOBIN:$PATH" + make get_vendor_deps + - run: + name: Test Gaia import/export simulation + command: | + export PATH="$GOBIN:$PATH" + make test_sim_gaia_import_export + test_sim_gaia_multi_seed: <<: *defaults parallelism: 1 @@ -237,12 +262,26 @@ jobs: make build-linux make localnet-start ./scripts/localnet-blocks-test.sh 40 5 10 localhost - + deploy_docs: + <<: *docs_deploy + steps: + - checkout + - run: + name: Trigger website build + command: | + export LAST_COMMIT=`git rev-parse HEAD` + export DOCS_COMMIT=`git log -1 --format=format:%H --full-diff docs` + if [[ $DOCS_COMMIT == $LAST_COMMIT ]]; then + chamber exec cosmos-sdk -- start_website_build + else + echo "No changes to docs detected" + fi workflows: version: 2 test-suite: jobs: + - deploy_docs - setup_dependencies - lint: requires: @@ -259,6 +298,9 @@ workflows: - test_sim_gaia_fast: requires: - setup_dependencies + - test_sim_gaia_import_export: + requires: + - setup_dependencies - test_sim_gaia_multi_seed: requires: - setup_dependencies diff --git a/.gitignore b/.gitignore index dec322b7bafd..cbc5e5e22cde 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,8 @@ vagrant # Graphviz dependency-graph.png + +# Latex +*.aux +*.out +*.synctex.gz diff --git a/CHANGELOG.md b/CHANGELOG.md index bb5d1e639a15..50316b4a7299 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,309 @@ # Changelog +## 0.26.0 + +BREAKING CHANGES + +* Gaia + * [gaiad init] [\#2602](https://github.com/cosmos/cosmos-sdk/issues/2602) New genesis workflow + +* SDK + * [simulation] [\#2665](https://github.com/cosmos/cosmos-sdk/issues/2665) only argument to simulation.Invariant is now app + +* Tendermint + * Upgrade to version 0.26.0 + +FEATURES + +* Gaia CLI (`gaiacli`) + * [cli] [\#2569](https://github.com/cosmos/cosmos-sdk/pull/2569) Add commands to query validator unbondings and redelegations + * [cli] [\#2569](https://github.com/cosmos/cosmos-sdk/pull/2569) Add commands to query validator unbondings and redelegations + * [cli] [\#2524](https://github.com/cosmos/cosmos-sdk/issues/2524) Add support offline mode to `gaiacli tx sign`. Lookups are not performed if the flag `--offline` is on. + * [cli] [\#2558](https://github.com/cosmos/cosmos-sdk/issues/2558) Rename --print-sigs to --validate-signatures. It now performs a complete set of sanity checks and reports to the user. Also added --print-signature-only to print the signature only, not the whole transaction. + * [cli] [\#2704](https://github.com/cosmos/cosmos-sdk/pull/2704) New add-genesis-account convenience command to populate genesis.json with genesis accounts. + +* SDK + * [\#1336](https://github.com/cosmos/cosmos-sdk/issues/1336) Mechanism for SDK Users to configure their own Bech32 prefixes instead of using the default cosmos prefixes. + +IMPROVEMENTS + +* Gaia + * [\#2637](https://github.com/cosmos/cosmos-sdk/issues/2637) [x/gov] Switched inactive and active proposal queues to an iterator based queue + +* SDK + * [\#2573](https://github.com/cosmos/cosmos-sdk/issues/2573) [x/distribution] add accum invariance + * [\#2556](https://github.com/cosmos/cosmos-sdk/issues/2556) [x/mock/simulation] Fix debugging output + * [\#2396](https://github.com/cosmos/cosmos-sdk/issues/2396) [x/mock/simulation] Change parameters to get more slashes + * [\#2617](https://github.com/cosmos/cosmos-sdk/issues/2617) [x/mock/simulation] Randomize all genesis parameters + * [\#2669](https://github.com/cosmos/cosmos-sdk/issues/2669) [x/stake] Added invarant check to make sure validator's power aligns with its spot in the power store. + * [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) [x/mock/simulation] Use a transition matrix for block size + * [\#2660](https://github.com/cosmos/cosmos-sdk/issues/2660) [x/mock/simulation] Staking transactions get tested far more frequently + * [\#2610](https://github.com/cosmos/cosmos-sdk/issues/2610) [x/stake] Block redelegation to and from the same validator + * [\#2652](https://github.com/cosmos/cosmos-sdk/issues/2652) [x/auth] Add benchmark for get and set account + * [\#2685](https://github.com/cosmos/cosmos-sdk/issues/2685) [store] Add general merkle absence proof (also for empty substores) + * [\#2708](https://github.com/cosmos/cosmos-sdk/issues/2708) [store] Disallow setting nil values + +BUG FIXES + +* Gaia + * [\#2670](https://github.com/cosmos/cosmos-sdk/issues/2670) [x/stake] fixed incorrect `IterateBondedValidators` and split into two functions: `IterateBondedValidators` and `IterateLastBlockConsValidators` + * [\#2691](https://github.com/cosmos/cosmos-sdk/issues/2691) Fix local testnet creation by using a single canonical genesis time + * [\#2648](https://github.com/cosmos/cosmos-sdk/issues/2648) [gaiad] Fix `gaiad export` / `gaiad import` consistency, test in CI + +* SDK + * [\#2625](https://github.com/cosmos/cosmos-sdk/issues/2625) [x/gov] fix AppendTag function usage error + * [\#2677](https://github.com/cosmos/cosmos-sdk/issues/2677) [x/stake, x/distribution] various staking/distribution fixes as found by the simulator + * [\#2674](https://github.com/cosmos/cosmos-sdk/issues/2674) [types] Fix coin.IsLT() impl, coins.IsLT() impl, and renamed coins.Is\* to coins.IsAll\* (see [\#2686](https://github.com/cosmos/cosmos-sdk/issues/2686)) + * [\#2711](https://github.com/cosmos/cosmos-sdk/issues/2711) [x/stake] Add commission data to `MsgCreateValidator` signature bytes. + * Temporarily disable insecure mode for Gaia Lite + +## 0.25.0 + +*October 24th, 2018* + +BREAKING CHANGES + +* Gaia REST API (`gaiacli advanced rest-server`) + * [x/stake] Validator.Owner renamed to Validator.Operator + * [\#595](https://github.com/cosmos/cosmos-sdk/issues/595) Connections to the REST server are now secured using Transport Layer Security by default. The --insecure flag is provided to switch back to insecure HTTP. + * [gaia-lite] [\#2258](https://github.com/cosmos/cosmos-sdk/issues/2258) Split `GET stake/delegators/{delegatorAddr}` into `GET stake/delegators/{delegatorAddr}/delegations`, `GET stake/delegators/{delegatorAddr}/unbonding_delegations` and `GET stake/delegators/{delegatorAddr}/redelegations` + +* Gaia CLI (`gaiacli`) + * [x/stake] Validator.Owner renamed to Validator.Operator + * [cli] unsafe_reset_all, show_validator, and show_node_id have been renamed to unsafe-reset-all, show-validator, and show-node-id + * [cli] [\#1983](https://github.com/cosmos/cosmos-sdk/issues/1983) --print-response now defaults to true in commands that create and send a transaction + * [cli] [\#1983](https://github.com/cosmos/cosmos-sdk/issues/1983) you can now pass --pubkey or --address to gaiacli keys show to return a plaintext representation of the key's address or public key for use with other commands + * [cli] [\#2061](https://github.com/cosmos/cosmos-sdk/issues/2061) changed proposalID in governance REST endpoints to proposal-id + * [cli] [\#2014](https://github.com/cosmos/cosmos-sdk/issues/2014) `gaiacli advanced` no longer exists - to access `ibc`, `rest-server`, and `validator-set` commands use `gaiacli ibc`, `gaiacli rest-server`, and `gaiacli tendermint`, respectively + * [makefile] `get_vendor_deps` no longer updates lock file it just updates vendor directory. Use `update_vendor_deps` to update the lock file. [#2152](https://github.com/cosmos/cosmos-sdk/pull/2152) + * [cli] [\#2221](https://github.com/cosmos/cosmos-sdk/issues/2221) All commands that + utilize a validator's operator address must now use the new Bech32 prefix, + `cosmosvaloper`. + * [cli] [\#2190](https://github.com/cosmos/cosmos-sdk/issues/2190) `gaiacli init --gen-txs` is now `gaiacli init --with-txs` to reduce confusion + * [cli] [\#2073](https://github.com/cosmos/cosmos-sdk/issues/2073) --from can now be either an address or a key name + * [cli] [\#1184](https://github.com/cosmos/cosmos-sdk/issues/1184) Subcommands reorganisation, see [\#2390](https://github.com/cosmos/cosmos-sdk/pull/2390) for a comprehensive list of changes. + * [cli] [\#2524](https://github.com/cosmos/cosmos-sdk/issues/2524) Add support offline mode to `gaiacli tx sign`. Lookups are not performed if the flag `--offline` is on. + * [cli] [\#2570](https://github.com/cosmos/cosmos-sdk/pull/2570) Add commands to query deposits on proposals + +* Gaia + * Make the transient store key use a distinct store key. [#2013](https://github.com/cosmos/cosmos-sdk/pull/2013) + * [x/stake] [\#1901](https://github.com/cosmos/cosmos-sdk/issues/1901) Validator type's Owner field renamed to Operator; Validator's GetOwner() renamed accordingly to comply with the SDK's Validator interface. + * [docs] [#2001](https://github.com/cosmos/cosmos-sdk/pull/2001) Update slashing spec for slashing period + * [x/stake, x/slashing] [#1305](https://github.com/cosmos/cosmos-sdk/issues/1305) - Rename "revoked" to "jailed" + * [x/stake] [#1676] Revoked and jailed validators put into the unbonding state + * [x/stake] [#1877] Redelegations/unbonding-delegation from unbonding validator have reduced time + * [x/slashing] [\#1789](https://github.com/cosmos/cosmos-sdk/issues/1789) Slashing changes for Tendermint validator set offset (NextValSet) + * [x/stake] [\#2040](https://github.com/cosmos/cosmos-sdk/issues/2040) Validator + operator type has now changed to `sdk.ValAddress` + * [x/stake] [\#2221](https://github.com/cosmos/cosmos-sdk/issues/2221) New + Bech32 prefixes have been introduced for a validator's consensus address and + public key: `cosmosvalcons` and `cosmosvalconspub` respectively. Also, existing Bech32 prefixes have been + renamed for accounts and validator operators: + * `cosmosaccaddr` / `cosmosaccpub` => `cosmos` / `cosmospub` + * `cosmosvaladdr` / `cosmosvalpub` => `cosmosvaloper` / `cosmosvaloperpub` + * [x/stake] [#1013] TendermintUpdates now uses transient store + * [x/stake] [\#2435](https://github.com/cosmos/cosmos-sdk/issues/2435) Remove empty bytes from the ValidatorPowerRank store key + * [x/gov] [\#2195](https://github.com/cosmos/cosmos-sdk/issues/2195) Governance uses BFT Time + * [x/gov] [\#2256](https://github.com/cosmos/cosmos-sdk/issues/2256) Removed slashing for governance non-voting validators + * [simulation] [\#2162](https://github.com/cosmos/cosmos-sdk/issues/2162) Added back correct supply invariants + * [x/slashing] [\#2430](https://github.com/cosmos/cosmos-sdk/issues/2430) Simulate more slashes, check if validator is jailed before jailing + * [x/stake] [\#2393](https://github.com/cosmos/cosmos-sdk/issues/2393) Removed `CompleteUnbonding` and `CompleteRedelegation` Msg types, and instead added unbonding/redelegation queues to endblocker + * [x/mock/simulation] [\#2501](https://github.com/cosmos/cosmos-sdk/issues/2501) Simulate transactions & invariants for fee distribution, and fix bugs discovered in the process + * [x/auth] Simulate random fee payments + * [cmd/gaia/app] Simulate non-zero inflation + * [x/stake] Call hooks correctly in several cases related to delegation/validator updates + * [x/stake] Check full supply invariants, including yet-to-be-withdrawn fees + * [x/stake] Remove no-longer-in-use store key + * [x/slashing] Call hooks correctly when a validator is slashed + * [x/slashing] Truncate withdrawals (unbonding, redelegation) and burn change + * [x/mock/simulation] Ensure the simulation cannot set a proposer address of nil + * [x/mock/simulation] Add more event logs on begin block / end block for clarity + * [x/mock/simulation] Correctly set validator power in abci.RequestBeginBlock + * [x/minting] Correctly call stake keeper to track inflated supply + * [x/distribution] Sanity check for nonexistent rewards + * [x/distribution] Truncate withdrawals and return change to the community pool + * [x/distribution] Add sanity checks for incorrect accum / total accum relations + * [x/distribution] Correctly calculate total power using Tendermint updates + * [x/distribution] Simulate withdrawal transactions + * [x/distribution] Fix a bug where the fee pool was not correctly tracked on WithdrawDelegatorRewardsAll + * [x/stake] [\#1673](https://github.com/cosmos/cosmos-sdk/issues/1673) Validators are no longer deleted until they can no longer possibly be slashed + * [\#1890](https://github.com/cosmos/cosmos-sdk/issues/1890) Start chain with initial state + sequence of transactions + * [cli] Rename `gaiad init gentx` to `gaiad gentx`. + * [cli] Add `--skip-genesis` flag to `gaiad init` to prevent `genesis.json` generation. + * Drop `GenesisTx` in favor of a signed `StdTx` with only one `MsgCreateValidator` message. + * [cli] Port `gaiad init` and `gaiad testnet` to work with `StdTx` genesis transactions. + * [cli] Add `--moniker` flag to `gaiad init` to override moniker when generating `genesis.json` - i.e. it takes effect when running with the `--with-txs` flag, it is ignored otherwise. + +* SDK + * [core] [\#2219](https://github.com/cosmos/cosmos-sdk/issues/2219) Update to Tendermint 0.24.0 + * Validator set updates delayed by one block + * BFT timestamp that can safely be used by applications + * Fixed maximum block size enforcement + * [core] [\#1807](https://github.com/cosmos/cosmos-sdk/issues/1807) Switch from use of rational to decimal + * [types] [\#1901](https://github.com/cosmos/cosmos-sdk/issues/1901) Validator interface's GetOwner() renamed to GetOperator() + * [x/slashing] [#2122](https://github.com/cosmos/cosmos-sdk/pull/2122) - Implement slashing period + * [types] [\#2119](https://github.com/cosmos/cosmos-sdk/issues/2119) Parsed error messages and ABCI log errors to make them more human readable. + * [types] [\#2407](https://github.com/cosmos/cosmos-sdk/issues/2407) MulInt method added to big decimal in order to improve efficiency of slashing + * [simulation] Rename TestAndRunTx to Operation [#2153](https://github.com/cosmos/cosmos-sdk/pull/2153) + * [simulation] Remove log and testing.TB from Operation and Invariants, in favor of using errors [\#2282](https://github.com/cosmos/cosmos-sdk/issues/2282) + * [simulation] Remove usage of keys and addrs in the types, in favor of simulation.Account [\#2384](https://github.com/cosmos/cosmos-sdk/issues/2384) + * [tools] Removed gocyclo [#2211](https://github.com/cosmos/cosmos-sdk/issues/2211) + * [baseapp] Remove `SetTxDecoder` in favor of requiring the decoder be set in baseapp initialization. [#1441](https://github.com/cosmos/cosmos-sdk/issues/1441) + * [baseapp] [\#1921](https://github.com/cosmos/cosmos-sdk/issues/1921) Add minimumFees field to BaseApp. + * [store] Change storeInfo within the root multistore to use tmhash instead of ripemd160 [\#2308](https://github.com/cosmos/cosmos-sdk/issues/2308) + * [codec] [\#2324](https://github.com/cosmos/cosmos-sdk/issues/2324) All referrences to wire have been renamed to codec. Additionally, wire.NewCodec is now codec.New(). + * [types] [\#2343](https://github.com/cosmos/cosmos-sdk/issues/2343) Make sdk.Msg have a names field, to facilitate automatic tagging. + * [baseapp] [\#2366](https://github.com/cosmos/cosmos-sdk/issues/2366) Automatically add action tags to all messages + * [x/auth] [\#2377](https://github.com/cosmos/cosmos-sdk/issues/2377) auth.StdSignMsg -> txbuilder.StdSignMsg + * [x/staking] [\#2244](https://github.com/cosmos/cosmos-sdk/issues/2244) staking now holds a consensus-address-index instead of a consensus-pubkey-index + * [x/staking] [\#2236](https://github.com/cosmos/cosmos-sdk/issues/2236) more distribution hooks for distribution + * [x/stake] [\#2394](https://github.com/cosmos/cosmos-sdk/issues/2394) Split up UpdateValidator into distinct state transitions applied only in EndBlock + * [x/slashing] [\#2480](https://github.com/cosmos/cosmos-sdk/issues/2480) Fix signing info handling bugs & faulty slashing + * [x/stake] [\#2412](https://github.com/cosmos/cosmos-sdk/issues/2412) Added an unbonding validator queue to EndBlock to automatically update validator.Status when finished Unbonding + * [x/stake] [\#2500](https://github.com/cosmos/cosmos-sdk/issues/2500) Block conflicting redelegations until we add an index + * [x/params] Global Paramstore refactored + * [types] [\#2506](https://github.com/cosmos/cosmos-sdk/issues/2506) sdk.Dec MarshalJSON now marshals as a normal Decimal, with 10 digits of decimal precision + * [x/stake] [\#2508](https://github.com/cosmos/cosmos-sdk/issues/2508) Utilize Tendermint power for validator power key + * [x/stake] [\#2531](https://github.com/cosmos/cosmos-sdk/issues/2531) Remove all inflation logic + * [x/mint] [\#2531](https://github.com/cosmos/cosmos-sdk/issues/2531) Add minting module and inflation logic + * [x/auth] [\#2540](https://github.com/cosmos/cosmos-sdk/issues/2540) Rename `AccountMapper` to `AccountKeeper`. + * [types] [\#2456](https://github.com/cosmos/cosmos-sdk/issues/2456) Renamed msg.Name() and msg.Type() to msg.Type() and msg.Route() respectively + +* Tendermint + * Update tendermint version from v0.23.0 to v0.25.0, notable changes + * Mempool now won't build too large blocks, or too computationally expensive blocks + * Maximum tx sizes and gas are now removed, and are implicitly the blocks maximums + * ABCI validators no longer send the pubkey. The pubkey is only sent in validator updates + * Validator set changes are now delayed by one block + * Block header now includes the next validator sets hash + * BFT time is implemented + * Secp256k1 signature format has changed + * There is now a threshold multisig format + * See the [tendermint changelog](https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md) for other changes. + +FEATURES + +* Gaia REST API (`gaiacli advanced rest-server`) + * [gaia-lite] Endpoints to query staking pool and params + * [gaia-lite] [\#2110](https://github.com/cosmos/cosmos-sdk/issues/2110) Add support for `simulate=true` requests query argument to endpoints that send txs to run simulations of transactions + * [gaia-lite] [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add support for `generate_only=true` query argument to generate offline unsigned transactions + * [gaia-lite] [\#1953](https://github.com/cosmos/cosmos-sdk/issues/1953) Add /sign endpoint to sign transactions generated with `generate_only=true`. + * [gaia-lite] [\#1954](https://github.com/cosmos/cosmos-sdk/issues/1954) Add /broadcast endpoint to broadcast transactions signed by the /sign endpoint. + * [gaia-lite] [\#2113](https://github.com/cosmos/cosmos-sdk/issues/2113) Rename `/accounts/{address}/send` to `/bank/accounts/{address}/transfers`, rename `/accounts/{address}` to `/auth/accounts/{address}`, replace `proposal-id` with `proposalId` in all gov endpoints + * [gaia-lite] [\#2478](https://github.com/cosmos/cosmos-sdk/issues/2478) Add query gov proposal's deposits endpoint + * [gaia-lite] [\#2477](https://github.com/cosmos/cosmos-sdk/issues/2477) Add query validator's outgoing redelegations and unbonding delegations endpoints + +* Gaia CLI (`gaiacli`) + * [cli] Cmds to query staking pool and params + * [gov][cli] [\#2062](https://github.com/cosmos/cosmos-sdk/issues/2062) added `--proposal` flag to `submit-proposal` that allows a JSON file containing a proposal to be passed in + * [\#2040](https://github.com/cosmos/cosmos-sdk/issues/2040) Add `--bech` to `gaiacli keys show` and respective REST endpoint to + provide desired Bech32 prefix encoding + * [cli] [\#2047](https://github.com/cosmos/cosmos-sdk/issues/2047) [\#2306](https://github.com/cosmos/cosmos-sdk/pull/2306) Passing --gas=simulate triggers a simulation of the tx before the actual execution. + The gas estimate obtained via the simulation will be used as gas limit in the actual execution. + * [cli] [\#2047](https://github.com/cosmos/cosmos-sdk/issues/2047) The --gas-adjustment flag can be used to adjust the estimate obtained via the simulation triggered by --gas=simulate. + * [cli] [\#2110](https://github.com/cosmos/cosmos-sdk/issues/2110) Add --dry-run flag to perform a simulation of a transaction without broadcasting it. The --gas flag is ignored as gas would be automatically estimated. + * [cli] [\#2204](https://github.com/cosmos/cosmos-sdk/issues/2204) Support generating and broadcasting messages with multiple signatures via command line: + * [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add --generate-only flag to build an unsigned transaction and write it to STDOUT. + * [\#1953](https://github.com/cosmos/cosmos-sdk/issues/1953) New `sign` command to sign transactions generated with the --generate-only flag. + * [\#1954](https://github.com/cosmos/cosmos-sdk/issues/1954) New `broadcast` command to broadcast transactions generated offline and signed with the `sign` command. + * [cli] [\#2220](https://github.com/cosmos/cosmos-sdk/issues/2220) Add `gaiacli config` feature to interactively create CLI config files to reduce the number of required flags + * [stake][cli] [\#1672](https://github.com/cosmos/cosmos-sdk/issues/1672) Introduced + new commission flags for validator commands `create-validator` and `edit-validator`. + * [stake][cli] [\#1890](https://github.com/cosmos/cosmos-sdk/issues/1890) Add `--genesis-format` flag to `gaiacli tx create-validator` to produce transactions in genesis-friendly format. + * [cli][\#2554](https://github.com/cosmos/cosmos-sdk/issues/2554) Make `gaiacli keys show` multisig ready. + +* Gaia + * [cli] [\#2170](https://github.com/cosmos/cosmos-sdk/issues/2170) added ability to show the node's address via `gaiad tendermint show-address` + * [simulation] [\#2313](https://github.com/cosmos/cosmos-sdk/issues/2313) Reworked `make test_sim_gaia_slow` to `make test_sim_gaia_full`, now simulates from multiple starting seeds in parallel + * [cli] [\#1921] (https://github.com/cosmos/cosmos-sdk/issues/1921) + * New configuration file `gaiad.toml` is now created to host Gaia-specific configuration. + * New --minimum_fees/minimum_fees flag/config option to set a minimum fee. + +* SDK + * [querier] added custom querier functionality, so ABCI query requests can be handled by keepers + * [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) allow operations to specify future operations + * [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) Add benchmarking capabilities, with makefile commands "test_sim_gaia_benchmark, test_sim_gaia_profile" + * [simulation] [\#2349](https://github.com/cosmos/cosmos-sdk/issues/2349) Add time-based future scheduled operations to simulator + * [x/auth] [\#2376](https://github.com/cosmos/cosmos-sdk/issues/2376) Remove FeePayer() from StdTx + * [x/stake] [\#1672](https://github.com/cosmos/cosmos-sdk/issues/1672) Implement + basis for the validator commission model. + * [x/auth] Support account removal in the account mapper. + + +IMPROVEMENTS +* [tools] Improved terraform and ansible scripts for infrastructure deployment +* [tools] Added ansible script to enable process core dumps + +* Gaia REST API (`gaiacli advanced rest-server`) + * [x/stake] [\#2000](https://github.com/cosmos/cosmos-sdk/issues/2000) Added tests for new staking endpoints + * [gaia-lite] [\#2445](https://github.com/cosmos/cosmos-sdk/issues/2445) Standarized REST error responses + * [gaia-lite] Added example to Swagger specification for /keys/seed. + * [x/stake] Refactor REST utils + +* Gaia CLI (`gaiacli`) + * [cli] [\#2060](https://github.com/cosmos/cosmos-sdk/issues/2060) removed `--select` from `block` command + * [cli] [\#2128](https://github.com/cosmos/cosmos-sdk/issues/2128) fixed segfault when exporting directly after `gaiad init` + * [cli] [\#1255](https://github.com/cosmos/cosmos-sdk/issues/1255) open KeyBase in read-only mode + for query-purpose CLI commands + * [docs] Added commands for querying governance deposits, votes and tally + +* Gaia + * [x/stake] [#2023](https://github.com/cosmos/cosmos-sdk/pull/2023) Terminate iteration loop in `UpdateBondedValidators` and `UpdateBondedValidatorsFull` when the first revoked validator is encountered and perform a sanity check. + * [x/auth] Signature verification's gas cost now accounts for pubkey type. [#2046](https://github.com/tendermint/tendermint/pull/2046) + * [x/stake] [x/slashing] Ensure delegation invariants to jailed validators [#1883](https://github.com/cosmos/cosmos-sdk/issues/1883). + * [x/stake] Improve speed of GetValidator, which was shown to be a performance bottleneck. [#2046](https://github.com/tendermint/tendermint/pull/2200) + * [x/stake] [\#2435](https://github.com/cosmos/cosmos-sdk/issues/2435) Improve memory efficiency of getting the various store keys + * [genesis] [\#2229](https://github.com/cosmos/cosmos-sdk/issues/2229) Ensure that there are no duplicate accounts or validators in the genesis state. + * [genesis] [\#2450](https://github.com/cosmos/cosmos-sdk/issues/2450) Validate staking genesis parameters. + * Add SDK validation to `config.toml` (namely disabling `create_empty_blocks`) [\#1571](https://github.com/cosmos/cosmos-sdk/issues/1571) + * [\#1941](https://github.com/cosmos/cosmos-sdk/issues/1941)(https://github.com/cosmos/cosmos-sdk/issues/1941) Version is now inferred via `git describe --tags`. + * [x/distribution] [\#1671](https://github.com/cosmos/cosmos-sdk/issues/1671) add distribution types and tests + +* SDK + * [tools] Make get_vendor_deps deletes `.vendor-new` directories, in case scratch files are present. + * [spec] Added simple piggy bank distribution spec + * [cli] [\#1632](https://github.com/cosmos/cosmos-sdk/issues/1632) Add integration tests to ensure `basecoind init && basecoind` start sequences run successfully for both `democoin` and `basecoin` examples. + * [store] Speedup IAVL iteration, and consequently everything that requires IAVL iteration. [#2143](https://github.com/cosmos/cosmos-sdk/issues/2143) + * [store] [\#1952](https://github.com/cosmos/cosmos-sdk/issues/1952), [\#2281](https://github.com/cosmos/cosmos-sdk/issues/2281) Update IAVL dependency to v0.11.0 + * [simulation] Make timestamps randomized [#2153](https://github.com/cosmos/cosmos-sdk/pull/2153) + * [simulation] Make logs not just pure strings, speeding it up by a large factor at greater block heights [\#2282](https://github.com/cosmos/cosmos-sdk/issues/2282) + * [simulation] Add a concept of weighting the operations [\#2303](https://github.com/cosmos/cosmos-sdk/issues/2303) + * [simulation] Logs get written to file if large, and also get printed on panics [\#2285](https://github.com/cosmos/cosmos-sdk/issues/2285) + * [simulation] Bank simulations now makes testing auth configurable [\#2425](https://github.com/cosmos/cosmos-sdk/issues/2425) + * [gaiad] [\#1992](https://github.com/cosmos/cosmos-sdk/issues/1992) Add optional flag to `gaiad testnet` to make config directory of daemon (default `gaiad`) and cli (default `gaiacli`) configurable + * [x/stake] Add stake `Queriers` for Gaia-lite endpoints. This increases the staking endpoints performance by reusing the staking `keeper` logic for queries. [#2249](https://github.com/cosmos/cosmos-sdk/pull/2149) + * [store] [\#2017](https://github.com/cosmos/cosmos-sdk/issues/2017) Refactor + gas iterator gas consumption to only consume gas for iterator creation and `Next` + calls which includes dynamic consumption of value length. + * [types/decimal] [\#2378](https://github.com/cosmos/cosmos-sdk/issues/2378) - Added truncate functionality to decimal + * [client] [\#1184](https://github.com/cosmos/cosmos-sdk/issues/1184) Remove unused `client/tx/sign.go`. + * [tools] [\#2464](https://github.com/cosmos/cosmos-sdk/issues/2464) Lock binary dependencies to a specific version + * #2573 [x/distribution] add accum invariance + +BUG FIXES + +* Gaia CLI (`gaiacli`) + * [cli] [\#1997](https://github.com/cosmos/cosmos-sdk/issues/1997) Handle panics gracefully when `gaiacli stake {delegation,unbond}` fail to unmarshal delegation. + * [cli] [\#2265](https://github.com/cosmos/cosmos-sdk/issues/2265) Fix JSON formatting of the `gaiacli send` command. + * [cli] [\#2547](https://github.com/cosmos/cosmos-sdk/issues/2547) Mark --to and --amount as required flags for `gaiacli tx send`. + +* Gaia + * [x/stake] Return correct Tendermint validator update set on `EndBlocker` by not + including non previously bonded validators that have zero power. [#2189](https://github.com/cosmos/cosmos-sdk/issues/2189) + * [docs] Fixed light client section links + +* SDK + * [\#1988](https://github.com/cosmos/cosmos-sdk/issues/1988) Make us compile on OpenBSD (disable ledger) [#1988] (https://github.com/cosmos/cosmos-sdk/issues/1988) + * [\#2105](https://github.com/cosmos/cosmos-sdk/issues/2105) Fix DB Iterator leak, which may leak a go routine. + * [ledger] [\#2064](https://github.com/cosmos/cosmos-sdk/issues/2064) Fix inability to sign and send transactions via the LCD by + loading a Ledger device at runtime. + * [\#2158](https://github.com/cosmos/cosmos-sdk/issues/2158) Fix non-deterministic ordering of validator iteration when slashing in `gov EndBlocker` + * [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) Make simulation stop on SIGTERM + * [\#2388](https://github.com/cosmos/cosmos-sdk/issues/2388) Remove dependency on deprecated tendermint/tmlibs repository. + * [\#2416](https://github.com/cosmos/cosmos-sdk/issues/2416) Refactored `InitializeTestLCD` to properly include proposing validator in genesis state. + * #2573 [x/distribution] accum invariance bugfix + * #2573 [x/slashing] unbonding-delegation slashing invariance bugfix + ## 0.24.2 *August 22nd, 2018* @@ -7,7 +311,7 @@ BUG FIXES * Tendermint - - Fix unbounded consensus WAL growth + - Fix unbounded consensus WAL growth ## 0.24.1 @@ -25,36 +329,36 @@ BUG FIXES BREAKING CHANGES * Gaia REST API (`gaiacli advanced rest-server`) - - [x/stake] \#1880 More REST-ful endpoints (large refactor) - - [x/slashing] \#1866 `/slashing/signing_info` takes cosmosvalpub instead of cosmosvaladdr + - [x/stake] [\#1880](https://github.com/cosmos/cosmos-sdk/issues/1880) More REST-ful endpoints (large refactor) + - [x/slashing] [\#1866](https://github.com/cosmos/cosmos-sdk/issues/1866) `/slashing/signing_info` takes cosmosvalpub instead of cosmosvaladdr - use time.Time instead of int64 for time. See Tendermint v0.23.0 - Signatures are no longer Amino encoded with prefixes (just encoded as raw bytes) - see Tendermint v0.23.0 * Gaia CLI (`gaiacli`) - [x/stake] change `--keybase-sig` to `--identity` - - [x/stake] \#1828 Force user to specify amount on create-validator command by removing default + - [x/stake] [\#1828](https://github.com/cosmos/cosmos-sdk/issues/1828) Force user to specify amount on create-validator command by removing default - [x/gov] Change `--proposalID` to `--proposal-id` - - [x/stake, x/gov] \#1606 Use `--from` instead of adhoc flags like `--address-validator` + - [x/stake, x/gov] [\#1606](https://github.com/cosmos/cosmos-sdk/issues/1606) Use `--from` instead of adhoc flags like `--address-validator` and `--proposer` to indicate the sender address. - - \#1551 Remove `--name` completely + - [\#1551](https://github.com/cosmos/cosmos-sdk/issues/1551) Remove `--name` completely - Genesis/key creation (`gaiad init`) now supports user-provided key passwords * Gaia - [x/stake] Inflation doesn't use rationals in calculation (performance boost) - [x/stake] Persist a map from `addr->pubkey` in the state since BeginBlock doesn't provide pubkeys. - - [x/gov] \#1781 Added tags sub-package, changed tags to use dash-case - - [x/gov] \#1688 Governance parameters are now stored in globalparams store - - [x/gov] \#1859 Slash validators who do not vote on a proposal - - [x/gov] \#1914 added TallyResult type that gets stored in Proposal after tallying is finished + - [x/gov] [\#1781](https://github.com/cosmos/cosmos-sdk/issues/1781) Added tags sub-package, changed tags to use dash-case + - [x/gov] [\#1688](https://github.com/cosmos/cosmos-sdk/issues/1688) Governance parameters are now stored in globalparams store + - [x/gov] [\#1859](https://github.com/cosmos/cosmos-sdk/issues/1859) Slash validators who do not vote on a proposal + - [x/gov] [\#1914](https://github.com/cosmos/cosmos-sdk/issues/1914) added TallyResult type that gets stored in Proposal after tallying is finished * SDK - [baseapp] Msgs are no longer run on CheckTx, removed `ctx.IsCheckTx()` - [baseapp] NewBaseApp constructor takes sdk.TxDecoder as argument instead of wire.Codec - [types] sdk.NewCoin takes sdk.Int, sdk.NewInt64Coin takes int64 - [x/auth] Default TxDecoder can be found in `x/auth` rather than baseapp - - [client] \#1551: Refactored `CoreContext` to `TxContext` and `QueryContext` + - [client] [\#1551](https://github.com/cosmos/cosmos-sdk/issues/1551): Refactored `CoreContext` to `TxContext` and `QueryContext` - Removed all tx related fields and logic (building & signing) to separate structure `TxContext` in `x/auth/client/context` @@ -74,7 +378,7 @@ FEATURES * Gaia CLI (`gaiacli`) - [x/gov] added `query-proposals` command. Can filter by `depositer`, `voter`, and `status` - - [x/stake] \#2043 Added staking query cli cmds for unbonding-delegations and redelegations + - [x/stake] [\#2043](https://github.com/cosmos/cosmos-sdk/issues/2043) Added staking query cli cmds for unbonding-delegations and redelegations * Gaia - [networks] Added ansible scripts to upgrade seed nodes on a network @@ -87,7 +391,7 @@ FEATURES - Simulates Tendermint's algorithm for validator set updates - Simulates validator signing/downtime with a Markov chain, and occaisional double-signatures - Includes simulated operations & invariants for staking, slashing, governance, and bank modules - - [store] \#1481 Add transient store + - [store] [\#1481](https://github.com/cosmos/cosmos-sdk/issues/1481) Add transient store - [baseapp] Initialize validator set on ResponseInitChain - [baseapp] added BaseApp.Seal - ability to seal baseapp parameters once they've been set - [cosmos-sdk-cli] New `cosmos-sdk-cli` tool to quickly initialize a new @@ -97,41 +401,41 @@ FEATURES IMPROVEMENTS * Gaia - - [spec] \#967 Inflation and distribution specs drastically improved - - [x/gov] \#1773 Votes on a proposal can now be queried + - [spec] [\#967](https://github.com/cosmos/cosmos-sdk/issues/967) Inflation and distribution specs drastically improved + - [x/gov] [\#1773](https://github.com/cosmos/cosmos-sdk/issues/1773) Votes on a proposal can now be queried - [x/gov] Initial governance parameters can now be set in the genesis file - - [x/stake] \#1815 Sped up the processing of `EditValidator` txs. - - [config] \#1930 Transactions indexer indexes all tags by default. + - [x/stake] [\#1815](https://github.com/cosmos/cosmos-sdk/issues/1815) Sped up the processing of `EditValidator` txs. + - [config] [\#1930](https://github.com/cosmos/cosmos-sdk/issues/1930) Transactions indexer indexes all tags by default. - [ci] [#2057](https://github.com/cosmos/cosmos-sdk/pull/2057) Run `make localnet-start` on every commit and ensure network reaches at least 10 blocks * SDK - - [baseapp] \#1587 Allow any alphanumeric character in route + - [baseapp] [\#1587](https://github.com/cosmos/cosmos-sdk/issues/1587) Allow any alphanumeric character in route - [baseapp] Allow any alphanumeric character in route - [tools] Remove `rm -rf vendor/` from `make get_vendor_deps` - [x/auth] Recover ErrorOutOfGas panic in order to set sdk.Result attributes correctly - - [x/auth] \#2376 No longer runs any signature in a multi-msg, if any account/sequence number is wrong. - - [x/auth] \#2376 No longer charge gas for subtracting fees + - [x/auth] [\#2376](https://github.com/cosmos/cosmos-sdk/issues/2376) No longer runs any signature in a multi-msg, if any account/sequence number is wrong. + - [x/auth] [\#2376](https://github.com/cosmos/cosmos-sdk/issues/2376) No longer charge gas for subtracting fees - [x/bank] Unit tests are now table-driven - [tests] Add tests to example apps in docs - [tests] Fixes ansible scripts to work with AWS too - - [tests] \#1806 CLI tests are now behind the build flag 'cli_test', so go test works on a new repo + - [tests] [\#1806](https://github.com/cosmos/cosmos-sdk/issues/1806) CLI tests are now behind the build flag 'cli_test', so go test works on a new repo BUG FIXES * Gaia CLI (`gaiacli`) - - \#1766 Fixes bad example for keybase identity - - [x/stake] \#2021 Fixed repeated CLI commands in staking + - [\#1766](https://github.com/cosmos/cosmos-sdk/issues/1766) Fixes bad example for keybase identity + - [x/stake] [\#2021](https://github.com/cosmos/cosmos-sdk/issues/2021) Fixed repeated CLI commands in staking * Gaia - [x/stake] [#2077](https://github.com/cosmos/cosmos-sdk/pull/2077) Fixed invalid cliff power comparison - - \#1804 Fixes gen-tx genesis generation logic temporarily until upstream updates - - \#1799 Fix `gaiad export` - - \#1839 Fixed bug where intra-tx counter wasn't set correctly for genesis validators - - [x/stake] \#1858 Fixed bug where the cliff validator was not updated correctly - - [tests] \#1675 Fix non-deterministic `test_cover` - - [tests] \#1551 Fixed invalid LCD test JSON payload in `doIBCTransfer` + - [\#1804](https://github.com/cosmos/cosmos-sdk/issues/1804) Fixes gen-tx genesis generation logic temporarily until upstream updates + - [\#1799](https://github.com/cosmos/cosmos-sdk/issues/1799) Fix `gaiad export` + - [\#1839](https://github.com/cosmos/cosmos-sdk/issues/1839) Fixed bug where intra-tx counter wasn't set correctly for genesis validators + - [x/stake] [\#1858](https://github.com/cosmos/cosmos-sdk/issues/1858) Fixed bug where the cliff validator was not updated correctly + - [tests] [\#1675](https://github.com/cosmos/cosmos-sdk/issues/1675) Fix non-deterministic `test_cover` + - [tests] [\#1551](https://github.com/cosmos/cosmos-sdk/issues/1551) Fixed invalid LCD test JSON payload in `doIBCTransfer` - [basecoin] Fixes coin transaction failure and account query [discussion](https://forum.cosmos.network/t/unmarshalbinarybare-expected-to-read-prefix-bytes-75fbfab8-since-it-is-registered-concrete-but-got-0a141dfa/664/6) - - [x/gov] \#1757 Fix VoteOption conversion to String + - [x/gov] [\#1757](https://github.com/cosmos/cosmos-sdk/issues/1757) Fix VoteOption conversion to String * [x/stake] [#2083] Fix broken invariant of bonded validator power decrease ## 0.23.1 @@ -159,9 +463,9 @@ IMPROVEMENTS BUG FIXES * [tendermint] Update to v0.22.6 - Fixes some security vulnerabilities reported in the [Bug Bounty](https://hackerone.com/tendermint) -* \#1797 Fix off-by-one error in slashing for downtime -* \#1787 Fixed bug where Tally fails due to revoked/unbonding validator -* \#1666 Add intra-tx counter to the genesis validators +* [\#1797](https://github.com/cosmos/cosmos-sdk/issues/1797) Fix off-by-one error in slashing for downtime +* [\#1787](https://github.com/cosmos/cosmos-sdk/issues/1787) Fixed bug where Tally fails due to revoked/unbonding validator +* [\#1666](https://github.com/cosmos/cosmos-sdk/issues/1666) Add intra-tx counter to the genesis validators ## 0.22.0 @@ -207,8 +511,8 @@ IMPROVEMENTS * [store] Pruning strategy configurable with pruning flag on gaiad start BUG FIXES -* \#1630 - redelegation nolonger removes tokens from the delegator liquid account -* [keys] \#1629 - updating password no longer asks for a new password when the first entered password was incorrect +* [\#1630](https://github.com/cosmos/cosmos-sdk/issues/1630) - redelegation nolonger removes tokens from the delegator liquid account +* [keys] [\#1629](https://github.com/cosmos/cosmos-sdk/issues/1629) - updating password no longer asks for a new password when the first entered password was incorrect * [lcd] importing an account would create a random account * [server] 'gaiad init' command family now writes provided name as the moniker in `config.toml` * [build] Added Ledger build support via `LEDGER_ENABLED=true|false` @@ -324,9 +628,9 @@ IMPROVEMENTS * [docs] Added commands for governance CLI on testnet README BUG FIXES -* [x/slashing] \#1510 Unrevoked validators cannot un-revoke themselves -* [x/stake] \#1513 Validators slashed to zero power are unbonded and removed from the store -* [x/stake] \#1567 Validators decreased in power but not unbonded are now updated in Tendermint +* [x/slashing] [\#1510](https://github.com/cosmos/cosmos-sdk/issues/1510) Unrevoked validators cannot un-revoke themselves +* [x/stake] [\#1513](https://github.com/cosmos/cosmos-sdk/issues/1513) Validators slashed to zero power are unbonded and removed from the store +* [x/stake] [\#1567](https://github.com/cosmos/cosmos-sdk/issues/1567) Validators decreased in power but not unbonded are now updated in Tendermint * [x/stake] error strings lower case * [x/stake] pool loose tokens now accounts for unbonding and unbonding tokens not associated with any validator * [x/stake] fix revoke bytes ordering (was putting revoked candidates at the top of the list) @@ -336,20 +640,20 @@ BUG FIXES * Retry on HTTP request failure in CLI tests, add option to retry tests in Makefile * Fixed bug where chain ID wasn't passed properly in x/bank REST handler, removed Viper hack from ante handler * Fixed bug where `democli account` didn't decode the account data correctly -* \#872 - recovery phrases no longer all end in `abandon` -* \#887 - limit the size of rationals that can be passed in from user input -* \#1052 - Make all now works -* \#1258 - printing big.rat's can no longer overflow int64 -* \#1259 - fix bug where certain tests that could have a nil pointer in defer -* \#1343 - fixed unnecessary parallelism in CI -* \#1353 - CLI: Show pool shares fractions in human-readable format -* \#1367 - set ChainID in InitChain -* \#1461 - CLI tests now no longer reset your local environment data -* \#1505 - `gaiacli stake validator` no longer panics if validator doesn't exist -* \#1565 - fix cliff validator persisting when validator set shrinks from max -* \#1287 - prevent zero power validators at genesis +* [\#872](https://github.com/cosmos/cosmos-sdk/issues/872) - recovery phrases no longer all end in `abandon` +* [\#887](https://github.com/cosmos/cosmos-sdk/issues/887) - limit the size of rationals that can be passed in from user input +* [\#1052](https://github.com/cosmos/cosmos-sdk/issues/1052) - Make all now works +* [\#1258](https://github.com/cosmos/cosmos-sdk/issues/1258) - printing big.rat's can no longer overflow int64 +* [\#1259](https://github.com/cosmos/cosmos-sdk/issues/1259) - fix bug where certain tests that could have a nil pointer in defer +* [\#1343](https://github.com/cosmos/cosmos-sdk/issues/1343) - fixed unnecessary parallelism in CI +* [\#1353](https://github.com/cosmos/cosmos-sdk/issues/1353) - CLI: Show pool shares fractions in human-readable format +* [\#1367](https://github.com/cosmos/cosmos-sdk/issues/1367) - set ChainID in InitChain +* [\#1461](https://github.com/cosmos/cosmos-sdk/issues/1461) - CLI tests now no longer reset your local environment data +* [\#1505](https://github.com/cosmos/cosmos-sdk/issues/1505) - `gaiacli stake validator` no longer panics if validator doesn't exist +* [\#1565](https://github.com/cosmos/cosmos-sdk/issues/1565) - fix cliff validator persisting when validator set shrinks from max +* [\#1287](https://github.com/cosmos/cosmos-sdk/issues/1287) - prevent zero power validators at genesis * [x/stake] fix bug when unbonding/redelegating using `--shares-percent` -* \#1010 - two validators can't bond with the same pubkey anymore +* [\#1010](https://github.com/cosmos/cosmos-sdk/issues/1010) - two validators can't bond with the same pubkey anymore ## 0.19.0 @@ -507,7 +811,7 @@ Update to Tendermint v0.19.4 (fixes a consensus bug and improves logging) BREAKING CHANGES -* [stake] MarshalJSON -> MarshalBinary +* [stake] MarshalJSON -> MarshalBinaryLengthPrefixed * Queries against the store must be prefixed with the path "/store" FEATURES diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3a7dca210edf..96804e28c8e8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,40 +1,40 @@ # Contributing Thank you for considering making contributions to Cosmos-SDK and related -repositories! +repositories! Contributing to this repo can mean many things such as participated in discussion or proposing code changes. To ensure a smooth workflow for all contributors, the general procedure for contributing has been established: 1. either [open](https://github.com/cosmos/cosmos-sdk/issues/new/choose) or - [find](https://github.com/cosmos/cosmos-sdk/issues) an issue you'd like to help with, - 2. participate in thoughtful discussion on that issue, + [find](https://github.com/cosmos/cosmos-sdk/issues) an issue you'd like to help with, + 2. participate in thoughtful discussion on that issue, 3. if you would then like to contribute code: - 1. if a the issue is a proposal, ensure that the proposal has been accepted, + 1. if a the issue is a proposal, ensure that the proposal has been accepted, 2. ensure that nobody else has already begun working on this issue, if they have - make sure to contact them to collaborate, + make sure to contact them to collaborate, 3. if nobody has been assigned the issue and you would like to work on it make a comment on the issue to inform the community of your intentions - to begin work, + to begin work, 4. follow standard github best practices: fork the repo, branch from the - tip of `develop`, make some commits, and submit a PR to `develop`, + tip of `develop`, make some commits, and submit a PR to `develop`, 5. include `WIP:` in the PR-title to and submit your PR early, even if it's incomplete, this indicates to the community you're working on something and allows them to provide comments early in the development process. When the code is complete it can be marked as ready-for-review by replacing `WIP:` with - `R4R:` in the PR-title. + `R4R:` in the PR-title. -Note that for very small or blatantly obvious problems (such as typos) it is +Note that for very small or blatantly obvious problems (such as typos) it is not required to an open issue to submit a PR, but be aware that for more complex problems/features, if a PR is opened before an adequate design discussion has -taken place in a github issue, that PR runs a high likelihood of being rejected. +taken place in a github issue, that PR runs a high likelihood of being rejected. Take a peek at our [coding repo](https://github.com/tendermint/coding) for overall information on repository workflow and standards. Note, we use `make get_dev_tools` and `make update_dev_tools` for installing the linting tools. -Other notes: +Other notes: - Looking for a good place to start contributing? How about checking out some [good first issues](https://github.com/cosmos/cosmos-sdk/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) @@ -44,9 +44,33 @@ Other notes: ## Pull Requests -To accommodate review process we suggest that PRs are categorically broken up. +To accommodate review process we suggest that PRs are categorically broken up. Ideally each PR addresses only a single issue. Additionally, as much as possible -code refactoring and cleanup should be submitted as a separate PRs from bugfixes/feature-additions. +code refactoring and cleanup should be submitted as a separate PRs from bugfixes/feature-additions. + +### Process for reviewing PRs + +All PRs require two Reviews before merge (except docs changes, or variable name-changes which only require one). When reviewing PRs please use the following review explanations: + +- `LGTM` without an explicit approval means that the changes look good, but you haven't pulled down the code, run tests locally and thoroughly reviewed it. +- `Approval` through the GH UI means that you understand the code, documentation/spec is updated in the right places, you have pulled down and tested the code locally. In addition: + - You must also think through anything which ought to be included but is not + - You must think through whether any added code could be partially combined (DRYed) with existing code + - You must think through any potential security issues or incentive-compatibility flaws introduced by the changes + - Naming must be consistent with conventions and the rest of the codebase + - Code must live in a reasonable location, considering dependency structures (e.g. not importing testing modules in production code, or including example code modules in production code). + - if you approve of the PR, you are responsible for fixing any of the issues mentioned here and more +- If you sat down with the PR submitter and did a pairing review please note that in the `Approval`, or your PR comments. +- If you are only making "surface level" reviews, submit any notes as `Comments` without adding a review. + +### Updating Documentation + +If you open a PR on the Cosmos SDK, it is mandatory to update the relevant documentation in /docs. + +* 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 the core of the CLI or Light-client (not specifically to module's CLI/Rest), please modify the docs/clients folder. ## Forking diff --git a/Gopkg.lock b/Gopkg.lock index 169893877a54..bcc306edb951 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -10,6 +10,7 @@ revision = "48b08affede2cea076a3cf13b2e3f72ed262b743" [[projects]] + branch = "master" digest = "1:09a7f74eb6bb3c0f14d8926610c87f569c5cff68e978d30e9a3540aeb626fdf0" name = "github.com/bartekn/go-bip39" packages = ["."] @@ -38,7 +39,7 @@ name = "github.com/btcsuite/btcd" packages = ["btcec"] pruneopts = "UT" - revision = "2a560b2036bee5e3679ec2133eb6520b2f195213" + revision = "67e573d211ace594f1366b4ce9d39726c4b19bd0" [[projects]] digest = "1:386de157f7d19259a7f9c81f26ce011223ce0f090353c1152ffdf730d7d10ac2" @@ -48,19 +49,19 @@ revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" [[projects]] - digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" - name = "github.com/davecgh/go-spew" - packages = ["spew"] + digest = "1:e8a3550c8786316675ff54ad6f09d265d129c9d986919af7f541afba50d87ce2" + name = "github.com/cosmos/go-bip39" + packages = ["."] pruneopts = "UT" - revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" - version = "v1.1.1" + revision = "52158e4697b87de16ed390e1bdaf813e581008fa" [[projects]] - digest = "1:c7644c73a3d23741fdba8a99b1464e021a224b7e205be497271a8003a15ca41b" - name = "github.com/ebuchman/fail-test" - packages = ["."] + digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39" + name = "github.com/davecgh/go-spew" + packages = ["spew"] pruneopts = "UT" - revision = "95f809107225be108efcf10a3509e4ea6ceef3c4" + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" [[projects]] digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd" @@ -238,12 +239,12 @@ version = "v1.0.0" [[projects]] - digest = "1:e32dfc6abff6a3633ef4d9a1022fd707c8ef26f1e1e8f855dc58dc415ce7c8f3" + digest = "1:53bc4cd4914cd7cd52139990d5170d6dc99067ae31c56530621b18b35fc30318" name = "github.com/mitchellh/mapstructure" packages = ["."] pruneopts = "UT" - revision = "fe40af7a9c397fa3ddba203c38a5042c5d0475ad" - version = "v1.1.1" + revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe" + version = "v1.1.2" [[projects]] digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e" @@ -289,7 +290,7 @@ [[projects]] branch = "master" - digest = "1:63b68062b8968092eb86bedc4e68894bd096ea6b24920faca8b9dcf451f54bb5" + digest = "1:db712fde5d12d6cdbdf14b777f0c230f4ff5ab0be8e35b239fc319953ed577a4" name = "github.com/prometheus/common" packages = [ "expfmt", @@ -297,11 +298,11 @@ "model", ] pruneopts = "UT" - revision = "c7de2306084e37d54b8be01f3541a8464345e9a5" + revision = "0b1957f9d949dfa3084171a6ec5642b38055276a" [[projects]] branch = "master" - digest = "1:ef1dd9945e58ee9b635273d28c0ef3fa3742a7dedc038ebe207fd63e6ce000ef" + digest = "1:ef74914912f99c79434d9c09658274678bc85080ebe3ab32bec3940ebce5e1fc" name = "github.com/prometheus/procfs" packages = [ ".", @@ -310,7 +311,7 @@ "xfs", ] pruneopts = "UT" - revision = "418d78d0b9a7b7de3a6bbc8a23def624cc977bb2" + revision = "185b4288413d2a0dd0806f78c90dde719829e5ae" [[projects]] digest = "1:ea0700160aca4ef099f4e06686a665a87691f4248dddd40796925eda2e46bd64" @@ -339,12 +340,12 @@ version = "v1.1.2" [[projects]] - digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f" + digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc" name = "github.com/spf13/cast" packages = ["."] pruneopts = "UT" - revision = "8965335b8c7107321228e3e3702cab9832751bac" - version = "v1.2.0" + revision = "8c9545af88b134710ab1cd196795e7f2388358d7" + version = "v1.3.0" [[projects]] digest = "1:7ffc0983035bc7e297da3688d9fe19d60a420e9c38bef23f845c53788ed6a05e" @@ -363,12 +364,12 @@ version = "v1.0.0" [[projects]] - digest = "1:dab83a1bbc7ad3d7a6ba1a1cc1760f25ac38cdf7d96a5cdd55cd915a4f5ceaf9" + digest = "1:c1b1102241e7f645bc8e0c22ae352e8f0dc6484b6cb4d132fa9f24174e0119e2" name = "github.com/spf13/pflag" packages = ["."] pruneopts = "UT" - revision = "9a97c102cda95a86cec2345a6f09f55a939babf5" - version = "v1.0.2" + revision = "298182f68c66c05229eb03ac171abe6e309ee79a" + version = "v1.0.3" [[projects]] digest = "1:f8e1a678a2571e265f4bf91a3e5e32aa6b1474a55cb0ea849750cc177b664d96" @@ -390,8 +391,7 @@ version = "v1.2.1" [[projects]] - branch = "master" - digest = "1:f2ffd421680b0a3f7887501b3c6974bcf19217ecd301d0e2c9b681940ec363d5" + digest = "1:b3cfb8d82b1601a846417c3f31c03a7961862cb2c98dcf0959c473843e6d9a2b" name = "github.com/syndtr/goleveldb" packages = [ "leveldb", @@ -408,7 +408,7 @@ "leveldb/util", ] pruneopts = "UT" - revision = "ae2bd5eed72d46b28834ec3f60db3a3ebedd8dbd" + revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445" [[projects]] digest = "1:605b6546f3f43745695298ec2d342d3e952b6d91cdf9f349bea9315f677d759f" @@ -418,35 +418,23 @@ revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df" [[projects]] - branch = "master" - digest = "1:087aaa7920e5d0bf79586feb57ce01c35c830396ab4392798112e8aae8c47722" - name = "github.com/tendermint/ed25519" - packages = [ - ".", - "edwards25519", - "extra25519", - ] - pruneopts = "UT" - revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057" - -[[projects]] - digest = "1:2c971a45c89ca2ccc735af50919cdee05fbdc54d4bf50625073693300e31ead8" + digest = "1:ad9c4c1a4e7875330b1f62906f2830f043a23edb5db997e3a5ac5d3e6eadf80a" name = "github.com/tendermint/go-amino" packages = ["."] pruneopts = "UT" - revision = "faa6e731944e2b7b6a46ad202902851e8ce85bee" - version = "v0.12.0" + revision = "dc14acf9ef15f85828bfbc561ed9dd9d2a284885" + version = "v0.14.1" [[projects]] - digest = "1:53397098d6acb7613358683cc84ae59281a60c6033f0bff62fa8d3f279c6c430" + digest = "1:9f8c4c93658315a795ffd3e0c943d39f78067dd8382b8d7bcfaf6686b92f3978" name = "github.com/tendermint/iavl" packages = ["."] pruneopts = "UT" - revision = "3acc91fb8811db2c5409a855ae1f8e441fe98e2d" - version = "v0.11.0" + revision = "fa74114f764f9827c4ad5573f990ed25bf8c4bac" + version = "v0.11.1" [[projects]] - digest = "1:a69eebd15b05045ffdb10a984e001fadc5666f74383de3d2a9ee5862ee99cfdc" + digest = "1:ba2ba7d6a0853472bdb7a64c4f9c1d5f9cba0eb7aac0b024654104387bf5eb57" name = "github.com/tendermint/tendermint" packages = [ "abci/client", @@ -479,6 +467,7 @@ "libs/db", "libs/errors", "libs/events", + "libs/fail", "libs/flowrate", "libs/log", "libs/pubsub", @@ -499,7 +488,6 @@ "rpc/core", "rpc/core/types", "rpc/grpc", - "rpc/lib", "rpc/lib/client", "rpc/lib/server", "rpc/lib/types", @@ -512,8 +500,8 @@ "version", ] pruneopts = "UT" - revision = "0c9c3292c918617624f6f3fbcd95eceade18bcd5" - version = "v0.25.0" + revision = "80d0a362500fea2dd089258319075a54e5d40a2d" + version = "v0.26.1" [[projects]] digest = "1:7886f86064faff6f8d08a3eb0e8c773648ff5a2e27730831e2bfbf07467f6666" @@ -524,13 +512,15 @@ version = "v0.1.0" [[projects]] - branch = "master" - digest = "1:27507554c6d4f060d8d700c31c624a43d3a92baa634e178ddc044bdf7d13b44a" + digest = "1:6f6dc6060c4e9ba73cf28aa88f12a69a030d3d19d518ef8e931879eaa099628d" name = "golang.org/x/crypto" packages = [ + "bcrypt", "blowfish", "chacha20poly1305", "curve25519", + "ed25519", + "ed25519/internal/edwards25519", "hkdf", "internal/chacha20", "internal/subtle", @@ -544,7 +534,8 @@ "salsa20/salsa", ] pruneopts = "UT" - revision = "e3636079e1a4c1f337f212cc5cd2aca108f6c900" + revision = "3764759f34a542a3aef74d6b02e35be7ab893bba" + source = "https://github.com/tendermint/crypto" [[projects]] digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1" @@ -563,15 +554,14 @@ revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f" [[projects]] - branch = "master" - digest = "1:8bc8ecef1d63576cfab4d08b44a1f255dd67e5b019b7a44837d62380f266a91c" + digest = "1:4bd75b1a219bc590b05c976bbebf47f4e993314ebb5c7cbf2efe05a09a184d54" name = "golang.org/x/sys" packages = [ "cpu", "unix", ] pruneopts = "UT" - revision = "e4b3c5e9061176387e7cea65e4dc5853801f3fb7" + revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf" [[projects]] digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" @@ -597,12 +587,11 @@ version = "v0.3.0" [[projects]] - branch = "master" - digest = "1:1e6b0176e8c5dd8ff551af65c76f8b73a99bcf4d812cedff1b91711b7df4804c" + digest = "1:077c1c599507b3b3e9156d17d36e1e61928ee9b53a5b420f10f28ebd4a0b275c" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] pruneopts = "UT" - revision = "c7e5094acea1ca1b899e2259d80a6b0f882f81f8" + revision = "383e8b2c3b9e36c4076b235b32537292176bae20" [[projects]] digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74" @@ -653,6 +642,7 @@ "github.com/bartekn/go-bip39", "github.com/bgentry/speakeasy", "github.com/btcsuite/btcd/btcec", + "github.com/cosmos/go-bip39", "github.com/golang/protobuf/proto", "github.com/gorilla/mux", "github.com/mattn/go-isatty", @@ -665,6 +655,7 @@ "github.com/spf13/viper", "github.com/stretchr/testify/assert", "github.com/stretchr/testify/require", + "github.com/syndtr/goleveldb/leveldb/opt", "github.com/tendermint/go-amino", "github.com/tendermint/iavl", "github.com/tendermint/tendermint/abci/server", @@ -676,6 +667,7 @@ "github.com/tendermint/tendermint/crypto/ed25519", "github.com/tendermint/tendermint/crypto/encoding/amino", "github.com/tendermint/tendermint/crypto/merkle", + "github.com/tendermint/tendermint/crypto/multisig", "github.com/tendermint/tendermint/crypto/secp256k1", "github.com/tendermint/tendermint/crypto/tmhash", "github.com/tendermint/tendermint/crypto/xsalsa20symmetric", @@ -697,9 +689,10 @@ "github.com/tendermint/tendermint/rpc/lib/client", "github.com/tendermint/tendermint/rpc/lib/server", "github.com/tendermint/tendermint/types", + "github.com/tendermint/tendermint/types/time", "github.com/tendermint/tendermint/version", "github.com/zondax/ledger-goclient", - "golang.org/x/crypto/blowfish", + "golang.org/x/crypto/bcrypt", ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index b63234f4603d..f8ffcca0543d 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -1,24 +1,3 @@ -# Gopkg.toml example -# -# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - [[constraint]] name = "github.com/bgentry/speakeasy" version = "~0.1.0" @@ -49,32 +28,59 @@ [[override]] name = "github.com/tendermint/go-amino" - version = "=v0.12.0" + version = "v0.14.1" [[override]] name = "github.com/tendermint/iavl" - version = "=v0.11.0" + version = "=v0.11.1" [[override]] name = "github.com/tendermint/tendermint" - version = "=0.25.0" + version = "v0.26.1" + +## deps without releases: + +[[override]] + name = "golang.org/x/crypto" + source = "https://github.com/tendermint/crypto" + revision = "3764759f34a542a3aef74d6b02e35be7ab893bba" [[constraint]] - name = "github.com/bartekn/go-bip39" - revision = "a05967ea095d81c8fe4833776774cfaff8e5036c" + name = "github.com/cosmos/go-bip39" + revision = "52158e4697b87de16ed390e1bdaf813e581008fa" [[constraint]] name = "github.com/zondax/ledger-goclient" version = "=v0.1.0" +## transitive deps, with releases: + +[[override]] + name = "github.com/davecgh/go-spew" + version = "=v1.1.0" + [[constraint]] name = "github.com/rakyll/statik" version = "=v0.1.4" -[prune] - go-tests = true - unused-packages = true - [[constraint]] name = "github.com/mitchellh/go-homedir" version = "1.0.0" + +## transitive deps, without releases: + +[[override]] + name = "github.com/syndtr/goleveldb" + revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445" + +[[override]] + name = "golang.org/x/sys" + revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf" + +[[override]] + name = "google.golang.org/genproto" + revision = "383e8b2c3b9e36c4076b235b32537292176bae20" + +[prune] + go-tests = true + unused-packages = true diff --git a/Makefile b/Makefile index ec02f1402ef0..35e5b50eff1f 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,14 @@ PACKAGES_NOSIMULATION=$(shell go list ./... | grep -v '/simulation') PACKAGES_SIMTEST=$(shell go list ./... | grep '/simulation') -VERSION := $(shell git describe --tags --long | sed 's/v\(.*\)/\1/') -BUILD_TAGS = netgo ledger +VERSION := $(subst v,,$(shell git describe --tags --long)) +BUILD_TAGS = netgo BUILD_FLAGS = -tags "${BUILD_TAGS}" -ldflags "-X github.com/cosmos/cosmos-sdk/version.Version=${VERSION}" -GCC := $(shell command -v gcc 2> /dev/null) LEDGER_ENABLED ?= true -UNAME_S := $(shell uname -s) 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 ######################################## @@ -20,23 +19,30 @@ ci: get_tools get_vendor_deps install test_cover test_lint test ######################################## ### Build/Install -check-ledger: ifeq ($(LEDGER_ENABLED),true) - ifeq ($(UNAME_S),OpenBSD) - $(info "OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988)") -TMP_BUILD_TAGS := $(BUILD_TAGS) -BUILD_TAGS = $(filter-out ledger, $(TMP_BUILD_TAGS)) - else - ifndef GCC - $(error "gcc not installed for ledger support, please install or set LEDGER_ENABLED to false in the Makefile") - endif - endif -else -TMP_BUILD_TAGS := $(BUILD_TAGS) -BUILD_TAGS = $(filter-out ledger, $(TMP_BUILD_TAGS)) + ifeq ($(OS),Windows_NT) + GCCEXE = $(shell where gcc.exe 2> NUL) + ifeq ($(GCCEXE),) + $(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false) + else + BUILD_TAGS += ledger + endif + else + UNAME_S = $(shell uname -s) + ifeq ($(UNAME_S),OpenBSD) + $(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988)) + else + GCC = $(shell command -v gcc 2> /dev/null) + ifeq ($(GCC),) + $(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false) + else + BUILD_TAGS += ledger + endif + endif + endif endif -build: check-ledger update_gaia_lite_docs +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 @@ -60,15 +66,15 @@ endif build_examples: ifeq ($(OS),Windows_NT) - go build $(BUILD_FLAGS) -o build/basecoind.exe ./examples/basecoin/cmd/basecoind - go build $(BUILD_FLAGS) -o build/basecli.exe ./examples/basecoin/cmd/basecli - go build $(BUILD_FLAGS) -o build/democoind.exe ./examples/democoin/cmd/democoind - go build $(BUILD_FLAGS) -o build/democli.exe ./examples/democoin/cmd/democli + 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 ./examples/basecoin/cmd/basecoind - go build $(BUILD_FLAGS) -o build/basecli ./examples/basecoin/cmd/basecli - go build $(BUILD_FLAGS) -o build/democoind ./examples/democoin/cmd/democoind - go build $(BUILD_FLAGS) -o build/democli ./examples/democoin/cmd/democli + 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 @@ -76,10 +82,10 @@ install: check-ledger update_gaia_lite_docs go install $(BUILD_FLAGS) ./cmd/gaia/cmd/gaiacli install_examples: - go install $(BUILD_FLAGS) ./examples/basecoin/cmd/basecoind - go install $(BUILD_FLAGS) ./examples/basecoin/cmd/basecli - go install $(BUILD_FLAGS) ./examples/democoin/cmd/democoind - go install $(BUILD_FLAGS) ./examples/democoin/cmd/democli + 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 @@ -101,33 +107,36 @@ check_tools: update_tools: @echo "--> Updating tools to correct version" - ./scripts/get_tools.sh + $(MAKE) -C scripts get_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_tools: +get_tools: $(GOBIN)/dep $(GOBIN)/gometalinter $(GOBIN)/statik @echo "--> Installing tools" - ./scripts/get_tools.sh + $(MAKE) -C scripts get_tools + +$(GOBIN)/%: + $(MAKE) -C scripts $(subst $(GOBIN)/,,$(@)) -get_dev_tools: +get_dev_tools: get_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 -get_vendor_deps: +get_vendor_deps: get_tools @echo "--> Generating vendor directory via dep ensure" @rm -rf .vendor-new @dep ensure -v -vendor-only -update_vendor_deps: +update_vendor_deps: get_tools @echo "--> Running dep ensure" @rm -rf .vendor-new @dep ensure -v -draw_deps: +draw_deps: get_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 @@ -150,8 +159,8 @@ test_cli: @go test -count 1 -p 1 `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/examples/basecoin/cli_test` -tags=cli_test - @go test -count 1 -p 1 `go list github.com/cosmos/cosmos-sdk/examples/democoin/cli_test` -tags=cli_test + @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) @@ -169,13 +178,17 @@ test_sim_gaia_nondeterminism: test_sim_gaia_fast: @echo "Running quick Gaia simulation. This may take several minutes..." - @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=400 -SimulationBlockSize=200 -SimulationCommit=true -v -timeout 24h + @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=500 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=10 -v -timeout 24h + +test_sim_gaia_import_export: + @echo "Running Gaia import/export simulation. This may take several minutes..." + @bash scripts/import-export-sim.sh 50 test_sim_gaia_multi_seed: @echo "Running multi-seed Gaia simulation. This may take awhile!" - @bash scripts/multisim.sh 10 + @bash scripts/multisim.sh 25 -SIM_NUM_BLOCKS ?= 210 +SIM_NUM_BLOCKS ?= 500 SIM_BLOCK_SIZE ?= 200 SIM_COMMIT ?= true test_sim_gaia_benchmark: @@ -247,7 +260,8 @@ localnet-stop: # 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_tools get_dev_tools get_vendor_deps draw_deps test test_cli test_unit \ +check_tools check_dev_tools get_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 test_sim_gaia_multi_seed update_tools update_dev_tools +format check-ledger test_sim_gaia_nondeterminism test_sim_modules test_sim_gaia_fast \ +test_sim_gaia_multi_seed test_sim_gaia_import_export update_tools update_dev_tools diff --git a/PENDING.md b/PENDING.md index 0b4e63ff3797..f699ef4636e5 100644 --- a/PENDING.md +++ b/PENDING.md @@ -3,198 +3,72 @@ BREAKING CHANGES * Gaia REST API (`gaiacli advanced rest-server`) - * [x/stake] Validator.Owner renamed to Validator.Operator - * [\#595](https://github.com/cosmos/cosmos-sdk/issues/595) Connections to the REST server are now secured using Transport Layer Security by default. The --insecure flag is provided to switch back to insecure HTTP. * Gaia CLI (`gaiacli`) - * [x/stake] Validator.Owner renamed to Validator.Operator - * [cli] unsafe_reset_all, show_validator, and show_node_id have been renamed to unsafe-reset-all, show-validator, and show-node-id - * [cli] [\#1983](https://github.com/cosmos/cosmos-sdk/issues/1983) --print-response now defaults to true in commands that create and send a transaction - * [cli] [\#1983](https://github.com/cosmos/cosmos-sdk/issues/1983) you can now pass --pubkey or --address to gaiacli keys show to return a plaintext representation of the key's address or public key for use with other commands - * [cli] [\#2061](https://github.com/cosmos/cosmos-sdk/issues/2061) changed proposalID in governance REST endpoints to proposal-id - * [cli] [\#2014](https://github.com/cosmos/cosmos-sdk/issues/2014) `gaiacli advanced` no longer exists - to access `ibc`, `rest-server`, and `validator-set` commands use `gaiacli ibc`, `gaiacli rest-server`, and `gaiacli tendermint`, respectively - * [makefile] `get_vendor_deps` no longer updates lock file it just updates vendor directory. Use `update_vendor_deps` to update the lock file. [#2152](https://github.com/cosmos/cosmos-sdk/pull/2152) - * [cli] [\#2221](https://github.com/cosmos/cosmos-sdk/issues/2221) All commands that - utilize a validator's operator address must now use the new Bech32 prefix, - `cosmosvaloper`. - * [cli] [\#2190](https://github.com/cosmos/cosmos-sdk/issues/2190) `gaiacli init --gen-txs` is now `gaiacli init --with-txs` to reduce confusion - * [cli] \#2073 --from can now be either an address or a key name - * [cli] [\#1184](https://github.com/cosmos/cosmos-sdk/issues/1184) Subcommands reorganisation, see [\#2390](https://github.com/cosmos/cosmos-sdk/pull/2390) for a comprehensive list of changes. + * [cli] [\#2728](https://github.com/cosmos/cosmos-sdk/pull/2728) Seperate `tx` and `query` subcommands by module + * [cli] [\#2727](https://github.com/cosmos/cosmos-sdk/pull/2727) Fix unbonding command flow + * [cli] [\#2786](https://github.com/cosmos/cosmos-sdk/pull/2786) Fix redelegation command flow * Gaia - * Make the transient store key use a distinct store key. [#2013](https://github.com/cosmos/cosmos-sdk/pull/2013) - * [x/stake] [\#1901](https://github.com/cosmos/cosmos-sdk/issues/1901) Validator type's Owner field renamed to Operator; Validator's GetOwner() renamed accordingly to comply with the SDK's Validator interface. - * [docs] [#2001](https://github.com/cosmos/cosmos-sdk/pull/2001) Update slashing spec for slashing period - * [x/stake, x/slashing] [#1305](https://github.com/cosmos/cosmos-sdk/issues/1305) - Rename "revoked" to "jailed" - * [x/stake] [#1676] Revoked and jailed validators put into the unbonding state - * [x/stake] [#1877] Redelegations/unbonding-delegation from unbonding validator have reduced time - * [x/slashing] \#1789 Slashing changes for Tendermint validator set offset (NextValSet) - * [x/stake] [\#2040](https://github.com/cosmos/cosmos-sdk/issues/2040) Validator - operator type has now changed to `sdk.ValAddress` - * [x/stake] [\#2221](https://github.com/cosmos/cosmos-sdk/issues/2221) New - Bech32 prefixes have been introduced for a validator's consensus address and - public key: `cosmosvalcons` and `cosmosvalconspub` respectively. Also, existing Bech32 prefixes have been - renamed for accounts and validator operators: - * `cosmosaccaddr` / `cosmosaccpub` => `cosmos` / `cosmospub` - * `cosmosvaladdr` / `cosmosvalpub` => `cosmosvaloper` / `cosmosvaloperpub` - * [x/stake] [#1013] TendermintUpdates now uses transient store - * [x/stake] \#2435 Remove empty bytes from the ValidatorPowerRank store key - * [x/gov] [#2195] Governance uses BFT Time - * [x/gov] \#2256 Removed slashing for governance non-voting validators - * [simulation] \#2162 Added back correct supply invariants - * [x/slashing] \#2430 Simulate more slashes, check if validator is jailed before jailing - * [x/stake] \#2393 Removed `CompleteUnbonding` and `CompleteRedelegation` Msg types, and instead added unbonding/redelegation queues to endblocker - + * SDK - * [core] \#2219 Update to Tendermint 0.24.0 - * Validator set updates delayed by one block - * BFT timestamp that can safely be used by applications - * Fixed maximum block size enforcement - * [core] [\#1807](https://github.com/cosmos/cosmos-sdk/issues/1807) Switch from use of rational to decimal - * [types] [\#1901](https://github.com/cosmos/cosmos-sdk/issues/1901) Validator interface's GetOwner() renamed to GetOperator() - * [x/slashing] [#2122](https://github.com/cosmos/cosmos-sdk/pull/2122) - Implement slashing period - * [types] [\#2119](https://github.com/cosmos/cosmos-sdk/issues/2119) Parsed error messages and ABCI log errors to make them more human readable. - * [types] \#2407 MulInt method added to big decimal in order to improve efficiency of slashing - * [simulation] Rename TestAndRunTx to Operation [#2153](https://github.com/cosmos/cosmos-sdk/pull/2153) - * [simulation] Remove log and testing.TB from Operation and Invariants, in favor of using errors \#2282 - * [simulation] Remove usage of keys and addrs in the types, in favor of simulation.Account \#2384 - * [tools] Removed gocyclo [#2211](https://github.com/cosmos/cosmos-sdk/issues/2211) - * [baseapp] Remove `SetTxDecoder` in favor of requiring the decoder be set in baseapp initialization. [#1441](https://github.com/cosmos/cosmos-sdk/issues/1441) - * [baseapp] [\#1921](https://github.com/cosmos/cosmos-sdk/issues/1921) Add minimumFees field to BaseApp. - * [store] Change storeInfo within the root multistore to use tmhash instead of ripemd160 \#2308 - * [codec] \#2324 All referrences to wire have been renamed to codec. Additionally, wire.NewCodec is now codec.New(). - * [types] \#2343 Make sdk.Msg have a names field, to facilitate automatic tagging. - * [baseapp] \#2366 Automatically add action tags to all messages - * [x/auth] \#2377 auth.StdSignMsg -> txbuilder.StdSignMsg - * [x/staking] \#2244 staking now holds a consensus-address-index instead of a consensus-pubkey-index - * [x/staking] \#2236 more distribution hooks for distribution - * [x/stake] \#2394 Split up UpdateValidator into distinct state transitions applied only in EndBlock + * [\#2752](https://github.com/cosmos/cosmos-sdk/pull/2752) Don't hardcode bondable denom. * Tendermint - * Update tendermint version from v0.23.0 to v0.25.0, notable changes - * Mempool now won't build too large blocks, or too computationally expensive blocks - * Maximum tx sizes and gas are now removed, and are implicitly the blocks maximums - * ABCI validators no longer send the pubkey. The pubkey is only sent in validator updates - * Validator set changes are now delayed by one block - * Block header now includes the next validator sets hash - * BFT time is implemented - * Secp256k1 signature format has changed - * There is now a threshold multisig format - * See the [tendermint changelog](https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md) for other changes. + FEATURES * Gaia REST API (`gaiacli advanced rest-server`) - * [gaia-lite] Endpoints to query staking pool and params - * [gaia-lite] [\#2110](https://github.com/cosmos/cosmos-sdk/issues/2110) Add support for `simulate=true` requests query argument to endpoints that send txs to run simulations of transactions - * [gaia-lite] [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add support for `generate_only=true` query argument to generate offline unsigned transactions - * [gaia-lite] [\#1953](https://github.com/cosmos/cosmos-sdk/issues/1953) Add /sign endpoint to sign transactions generated with `generate_only=true`. - * [gaia-lite] [\#1954](https://github.com/cosmos/cosmos-sdk/issues/1954) Add /broadcast endpoint to broadcast transactions signed by the /sign endpoint. - * [gaia-lite] [\#2113](https://github.com/cosmos/cosmos-sdk/issues/2113) Rename `/accounts/{address}/send` to `/bank/accounts/{address}/transfers`, rename `/accounts/{address}` to `/auth/accounts/{address}` + * [gov] [\#2479](https://github.com/cosmos/cosmos-sdk/issues/2479) Added governance parameter + query REST endpoints. * Gaia CLI (`gaiacli`) - * [cli] Cmds to query staking pool and params - * [gov][cli] #2062 added `--proposal` flag to `submit-proposal` that allows a JSON file containing a proposal to be passed in - * [\#2040](https://github.com/cosmos/cosmos-sdk/issues/2040) Add `--bech` to `gaiacli keys show` and respective REST endpoint to - provide desired Bech32 prefix encoding - * [cli] [\#2047](https://github.com/cosmos/cosmos-sdk/issues/2047) [\#2306](https://github.com/cosmos/cosmos-sdk/pull/2306) Passing --gas=simulate triggers a simulation of the tx before the actual execution. - The gas estimate obtained via the simulation will be used as gas limit in the actual execution. - * [cli] [\#2047](https://github.com/cosmos/cosmos-sdk/issues/2047) The --gas-adjustment flag can be used to adjust the estimate obtained via the simulation triggered by --gas=simulate. - * [cli] [\#2110](https://github.com/cosmos/cosmos-sdk/issues/2110) Add --dry-run flag to perform a simulation of a transaction without broadcasting it. The --gas flag is ignored as gas would be automatically estimated. - * [cli] [\#2204](https://github.com/cosmos/cosmos-sdk/issues/2204) Support generating and broadcasting messages with multiple signatures via command line: - * [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add --generate-only flag to build an unsigned transaction and write it to STDOUT. - * [\#1953](https://github.com/cosmos/cosmos-sdk/issues/1953) New `sign` command to sign transactions generated with the --generate-only flag. - * [\#1954](https://github.com/cosmos/cosmos-sdk/issues/1954) New `broadcast` command to broadcast transactions generated offline and signed with the `sign` command. - * [cli] \#2220 Add `gaiacli config` feature to interactively create CLI config files to reduce the number of required flags - * [stake][cli] [\#1672](https://github.com/cosmos/cosmos-sdk/issues/1672) Introduced - new commission flags for validator commands `create-validator` and `edit-validator`. - + * [gov][cli] [\#2479](https://github.com/cosmos/cosmos-sdk/issues/2479) Added governance + parameter query commands. + * [stake][cli] [\#2027] Add CLI query command for getting all delegations to a specific validator. + * Gaia - * [cli] #2170 added ability to show the node's address via `gaiad tendermint show-address` - * [simulation] #2313 Reworked `make test_sim_gaia_slow` to `make test_sim_gaia_full`, now simulates from multiple starting seeds in parallel - * [cli] [\#1921] (https://github.com/cosmos/cosmos-sdk/issues/1921) - * New configuration file `gaiad.toml` is now created to host Gaia-specific configuration. - * New --minimum_fees/minimum_fees flag/config option to set a minimum fee. + * [x/gov] [#2479](https://github.com/cosmos/cosmos-sdk/issues/2479) Implemented querier + for getting governance parameters. * SDK - * [querier] added custom querier functionality, so ABCI query requests can be handled by keepers - * [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) allow operations to specify future operations - * [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) Add benchmarking capabilities, with makefile commands "test_sim_gaia_benchmark, test_sim_gaia_profile" - * [simulation] [\#2349](https://github.com/cosmos/cosmos-sdk/issues/2349) Add time-based future scheduled operations to simulator - * [x/auth] \#2376 Remove FeePayer() from StdTx - * [x/stake] [\#1672](https://github.com/cosmos/cosmos-sdk/issues/1672) Implement - basis for the validator commission model. - * [x/auth] Support account removal in the account mapper. + * [simulator] \#2682 MsgEditValidator now looks at the validator's max rate, thus it now succeeds a significant portion of the time * Tendermint IMPROVEMENTS -* [tools] Improved terraform and ansible scripts for infrastructure deployment -* [tools] Added ansible script to enable process core dumps * Gaia REST API (`gaiacli advanced rest-server`) - * [x/stake] [\#2000](https://github.com/cosmos/cosmos-sdk/issues/2000) Added tests for new staking endpoints * Gaia CLI (`gaiacli`) - * [cli] #2060 removed `--select` from `block` command - * [cli] #2128 fixed segfault when exporting directly after `gaiad init` + * [\#2749](https://github.com/cosmos/cosmos-sdk/pull/2749) Add --chain-id flag to gaiad testnet * Gaia - * [x/stake] [#2023](https://github.com/cosmos/cosmos-sdk/pull/2023) Terminate iteration loop in `UpdateBondedValidators` and `UpdateBondedValidatorsFull` when the first revoked validator is encountered and perform a sanity check. - * [x/auth] Signature verification's gas cost now accounts for pubkey type. [#2046](https://github.com/tendermint/tendermint/pull/2046) - * [x/stake] [x/slashing] Ensure delegation invariants to jailed validators [#1883](https://github.com/cosmos/cosmos-sdk/issues/1883). - * [x/stake] Improve speed of GetValidator, which was shown to be a performance bottleneck. [#2046](https://github.com/tendermint/tendermint/pull/2200) - * [x/stake] \#2435 Improve memory efficiency of getting the various store keys - * [genesis] \#2229 Ensure that there are no duplicate accounts or validators in the genesis state. - * [genesis] \#2450 Validate staking genesis parameters. - * Add SDK validation to `config.toml` (namely disabling `create_empty_blocks`) \#1571 - * \#1941(https://github.com/cosmos/cosmos-sdk/issues/1941) Version is now inferred via `git describe --tags`. - * [x/distribution] \#1671 add distribution types and tests - + - #2773 Require moniker to be provided on `gaiad init`. + - #2672 [Makefile] Updated for better Windows compatibility and ledger support logic, get_tools was rewritten as a cross-compatible Makefile. + - [#110](https://github.com/tendermint/devops/issues/110) Updated CircleCI job to trigger website build when cosmos docs are updated. * SDK - * [tools] Make get_vendor_deps deletes `.vendor-new` directories, in case scratch files are present. - * [spec] Added simple piggy bank distribution spec - * [cli] [\#1632](https://github.com/cosmos/cosmos-sdk/issues/1632) Add integration tests to ensure `basecoind init && basecoind` start sequences run successfully for both `democoin` and `basecoin` examples. - * [store] Speedup IAVL iteration, and consequently everything that requires IAVL iteration. [#2143](https://github.com/cosmos/cosmos-sdk/issues/2143) - * [store] \#1952, \#2281 Update IAVL dependency to v0.11.0 - * [simulation] Make timestamps randomized [#2153](https://github.com/cosmos/cosmos-sdk/pull/2153) - * [simulation] Make logs not just pure strings, speeding it up by a large factor at greater block heights \#2282 - * [simulation] Add a concept of weighting the operations \#2303 - * [simulation] Logs get written to file if large, and also get printed on panics \#2285 - * [simulation] Bank simulations now makes testing auth configurable \#2425 - * [gaiad] \#1992 Add optional flag to `gaiad testnet` to make config directory of daemon (default `gaiad`) and cli (default `gaiacli`) configurable - * [x/stake] Add stake `Queriers` for Gaia-lite endpoints. This increases the staking endpoints performance by reusing the staking `keeper` logic for queries. [#2249](https://github.com/cosmos/cosmos-sdk/pull/2149) - * [store] [\#2017](https://github.com/cosmos/cosmos-sdk/issues/2017) Refactor - gas iterator gas consumption to only consume gas for iterator creation and `Next` - calls which includes dynamic consumption of value length. - * [types/decimal] \#2378 - Added truncate functionality to decimal - * [client] [\#1184](https://github.com/cosmos/cosmos-sdk/issues/1184) Remove unused `client/tx/sign.go`. - * [tools] \#2464 Lock binary dependencies to a specific version + - [x/mock/simulation] [\#2720] major cleanup, introduction of helper objects, reorganization * Tendermint + - #2796 Update to go-amino 0.14.1 + BUG FIXES * Gaia REST API (`gaiacli advanced rest-server`) * Gaia CLI (`gaiacli`) - * [cli] [\#1997](https://github.com/cosmos/cosmos-sdk/issues/1997) Handle panics gracefully when `gaiacli stake {delegation,unbond}` fail to unmarshal delegation. - * [cli] [\#2265](https://github.com/cosmos/cosmos-sdk/issues/2265) Fix JSON formatting of the `gaiacli send` command. * Gaia - * [x/stake] Return correct Tendermint validator update set on `EndBlocker` by not - including non previously bonded validators that have zero power. [#2189](https://github.com/cosmos/cosmos-sdk/issues/2189) + * [\#2723] Use `cosmosvalcons` Bech32 prefix in `tendermint show-address` + * [\#2742](https://github.com/cosmos/cosmos-sdk/issues/2742) Fix time format of TimeoutCommit override * SDK - * [\#1988](https://github.com/cosmos/cosmos-sdk/issues/1988) Make us compile on OpenBSD (disable ledger) [#1988] (https://github.com/cosmos/cosmos-sdk/issues/1988) - * [\#2105](https://github.com/cosmos/cosmos-sdk/issues/2105) Fix DB Iterator leak, which may leak a go routine. - * [ledger] [\#2064](https://github.com/cosmos/cosmos-sdk/issues/2064) Fix inability to sign and send transactions via the LCD by - loading a Ledger device at runtime. - * [\#2158](https://github.com/cosmos/cosmos-sdk/issues/2158) Fix non-deterministic ordering of validator iteration when slashing in `gov EndBlocker` - * [simulation] \#1924 Make simulation stop on SIGTERM - * [\#2388](https://github.com/cosmos/cosmos-sdk/issues/2388) Remove dependency on deprecated tendermint/tmlibs repository. - * [\#2416](https://github.com/cosmos/cosmos-sdk/issues/2416) Refactored - `InitializeTestLCD` to properly include proposing validator in genesis state. + + - \#2733 [x/gov, x/mock/simulation] Fix governance simulation, update x/gov import/export * Tendermint + * [\#2797](https://github.com/tendermint/tendermint/pull/2797) AddressBook requires addresses to have IDs; Do not crap out immediately after sending pex addrs in seed mode diff --git a/README.md b/README.md index 94b2b19da848..3540dbe3d958 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Cosmos SDK -![banner](docs/graphics/cosmos-sdk-image.png) +![banner](docs/cosmos-sdk-image.png) [![version](https://img.shields.io/github/tag/cosmos/cosmos-sdk.svg)](https://github.com/cosmos/cosmos-sdk/releases/latest) [![CircleCI](https://circleci.com/gh/cosmos/cosmos-sdk/tree/master.svg?style=shield)](https://circleci.com/gh/cosmos/cosmos-sdk/tree/master) @@ -17,27 +17,26 @@ It is being used to build `Gaia`, the first implementation of the [Cosmos Hub](h **WARNING**: The SDK has mostly stabilized, but we are still making some breaking changes. -**Note**: Requires [Go 1.10+](https://golang.org/dl/) +**Note**: Requires [Go 1.11+](https://golang.org/dl/) ## Gaia Testnet +To install the binaries, read the [install instructions](./docs/gaia/installation.md) + To join the latest testnet, follow -[the guide](./docs/getting-started/join-testnet.md). +[the guide](./docs/gaia/join-testnet.md). For status updates and genesis files, see the [testnets repo](https://github.com/cosmos/testnets). -## Install - -See the -[install instructions](./docs/getting-started/installation.md). ## Quick Start -See the [Cosmos Docs](https://cosmos.network/docs/) +To learn how the SDK works from a high-level perspective, go to the [SDK Intro](./docs/intro/README.md). + +If you want to get started quickly and learn how to build on top of the SDK, please follow the [SDK Application Tutorial](https://github.com/cosmos/sdk-application-tutorial). You can also fork the tutorial's repo to get started building your own Cosmos SDK application. -- [Getting started with the SDK](./docs/sdk/core/intro.md) -- [SDK Examples](/examples) +For more, please go to the [Cosmos SDK Docs](./docs/README.md) ## Disambiguation diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index b390e38417af..827536d21562 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -337,7 +337,7 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abc } // Encode with json - value := codec.Cdc.MustMarshalBinary(result) + value := codec.Cdc.MustMarshalBinaryLengthPrefixed(result) return abci.ResponseQuery{ Code: uint32(sdk.ABCICodeOK), Value: value, @@ -394,6 +394,7 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res ctx := sdk.NewContext(app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger). WithMinimumFees(app.minimumFees) + // 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 resBytes, err := querier(ctx, path[2:], req) @@ -528,10 +529,10 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re var code sdk.ABCICodeType for msgIdx, msg := range msgs { // Match route. - msgType := msg.Type() - handler := app.router.Route(msgType) + msgRoute := msg.Route() + handler := app.router.Route(msgRoute) if handler == nil { - return sdk.ErrUnknownRequest("Unrecognized Msg type: " + msgType).Result() + return sdk.ErrUnknownRequest("Unrecognized Msg type: " + msgRoute).Result() } var msgResult sdk.Result @@ -539,7 +540,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re if mode != runTxModeCheck { msgResult = handler(ctx, msg) } - msgResult.Tags = append(msgResult.Tags, sdk.MakeTag("action", []byte(msg.Name()))) + msgResult.Tags = append(msgResult.Tags, sdk.MakeTag("action", []byte(msg.Type()))) // NOTE: GasWanted is determined by ante handler and // GasUsed by the GasMeter diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 0137a3f7d7be..9f6414214bbb 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -290,8 +290,8 @@ type txTest struct { func (tx txTest) GetMsgs() []sdk.Msg { return tx.Msgs } const ( - typeMsgCounter = "msgCounter" - typeMsgCounter2 = "msgCounter2" + routeMsgCounter = "msgCounter" + routeMsgCounter2 = "msgCounter2" ) // ValidateBasic() fails on negative counters. @@ -301,8 +301,8 @@ type msgCounter struct { } // Implements Msg -func (msg msgCounter) Type() string { return typeMsgCounter } -func (msg msgCounter) Name() string { return "counter1" } +func (msg msgCounter) Route() string { return routeMsgCounter } +func (msg msgCounter) Type() string { return "counter1" } func (msg msgCounter) GetSignBytes() []byte { return nil } func (msg msgCounter) GetSigners() []sdk.AccAddress { return nil } func (msg msgCounter) ValidateBasic() sdk.Error { @@ -325,14 +325,14 @@ type msgNoRoute struct { msgCounter } -func (tx msgNoRoute) Type() string { return "noroute" } +func (tx msgNoRoute) Route() string { return "noroute" } // a msg we dont know how to decode type msgNoDecode struct { msgCounter } -func (tx msgNoDecode) Type() string { return typeMsgCounter } +func (tx msgNoDecode) Route() string { return routeMsgCounter } // Another counter msg. Duplicate of msgCounter type msgCounter2 struct { @@ -340,8 +340,8 @@ type msgCounter2 struct { } // Implements Msg -func (msg msgCounter2) Type() string { return typeMsgCounter2 } -func (msg msgCounter2) Name() string { return "counter2" } +func (msg msgCounter2) Route() string { return routeMsgCounter2 } +func (msg msgCounter2) Type() string { return "counter2" } func (msg msgCounter2) GetSignBytes() []byte { return nil } func (msg msgCounter2) GetSigners() []sdk.AccAddress { return nil } func (msg msgCounter2) ValidateBasic() sdk.Error { @@ -358,7 +358,7 @@ func testTxDecoder(cdc *codec.Codec) sdk.TxDecoder { if len(txBytes) == 0 { return nil, sdk.ErrTxDecode("txBytes are empty") } - err := cdc.UnmarshalBinary(txBytes, &tx) + err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) if err != nil { return nil, sdk.ErrTxDecode("").TraceSDK(err.Error()) } @@ -440,7 +440,7 @@ func TestCheckTx(t *testing.T) { anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, counterKey)) } routerOpt := func(bapp *BaseApp) { // TODO: can remove this once CheckTx doesnt process msgs. - bapp.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { return sdk.Result{} }) + bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { return sdk.Result{} }) } app := setupBaseApp(t, anteOpt, routerOpt) @@ -455,7 +455,7 @@ func TestCheckTx(t *testing.T) { for i := int64(0); i < nTxs; i++ { tx := newTxCounter(i, 0) - txBytes, err := codec.MarshalBinary(tx) + txBytes, err := codec.MarshalBinaryLengthPrefixed(tx) require.NoError(t, err) r := app.CheckTx(txBytes) assert.True(t, r.IsOK(), fmt.Sprintf("%v", r)) @@ -486,7 +486,9 @@ func TestDeliverTx(t *testing.T) { // test increments in the handler deliverKey := []byte("deliver-key") - routerOpt := func(bapp *BaseApp) { bapp.Router().AddRoute(typeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey)) } + routerOpt := func(bapp *BaseApp) { + bapp.Router().AddRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey)) + } app := setupBaseApp(t, anteOpt, routerOpt) @@ -501,7 +503,7 @@ func TestDeliverTx(t *testing.T) { for i := 0; i < txPerHeight; i++ { counter := int64(blockN*txPerHeight + i) tx := newTxCounter(counter, counter) - txBytes, err := codec.MarshalBinary(tx) + txBytes, err := codec.MarshalBinaryLengthPrefixed(tx) require.NoError(t, err) res := app.DeliverTx(txBytes) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) @@ -527,8 +529,8 @@ func TestMultiMsgDeliverTx(t *testing.T) { deliverKey := []byte("deliver-key") deliverKey2 := []byte("deliver-key2") routerOpt := func(bapp *BaseApp) { - bapp.Router().AddRoute(typeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey)) - bapp.Router().AddRoute(typeMsgCounter2, handlerMsgCounter(t, capKey1, deliverKey2)) + bapp.Router().AddRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey)) + bapp.Router().AddRoute(routeMsgCounter2, handlerMsgCounter(t, capKey1, deliverKey2)) } app := setupBaseApp(t, anteOpt, routerOpt) @@ -538,11 +540,11 @@ func TestMultiMsgDeliverTx(t *testing.T) { registerTestCodec(codec) // run a multi-msg tx - // with all msgs the same type + // with all msgs the same route { app.BeginBlock(abci.RequestBeginBlock{}) tx := newTxCounter(0, 0, 1, 2) - txBytes, err := codec.MarshalBinary(tx) + txBytes, err := codec.MarshalBinaryLengthPrefixed(tx) require.NoError(t, err) res := app.DeliverTx(txBytes) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) @@ -563,7 +565,7 @@ func TestMultiMsgDeliverTx(t *testing.T) { tx := newTxCounter(1, 3) tx.Msgs = append(tx.Msgs, msgCounter2{0}) tx.Msgs = append(tx.Msgs, msgCounter2{1}) - txBytes, err := codec.MarshalBinary(tx) + txBytes, err := codec.MarshalBinaryLengthPrefixed(tx) require.NoError(t, err) res := app.DeliverTx(txBytes) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) @@ -604,7 +606,7 @@ func TestSimulateTx(t *testing.T) { } routerOpt := func(bapp *BaseApp) { - bapp.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { ctx.GasMeter().ConsumeGas(gasConsumed, "test") return sdk.Result{GasUsed: ctx.GasMeter().GasConsumed()} }) @@ -636,7 +638,7 @@ func TestSimulateTx(t *testing.T) { require.Equal(t, gasConsumed, result.GasUsed) // simulate by calling Query with encoded tx - txBytes, err := cdc.MarshalBinary(tx) + txBytes, err := cdc.MarshalBinaryLengthPrefixed(tx) require.Nil(t, err) query := abci.RequestQuery{ Path: "/app/simulate", @@ -646,7 +648,7 @@ func TestSimulateTx(t *testing.T) { require.True(t, queryResult.IsOK(), queryResult.Log) var res sdk.Result - codec.Cdc.MustUnmarshalBinary(queryResult.Value, &res) + codec.Cdc.MustUnmarshalBinaryLengthPrefixed(queryResult.Value, &res) require.Nil(t, err, "Result unmarshalling failed") require.True(t, res.IsOK(), res.Log) require.Equal(t, gasConsumed, res.GasUsed, res.Log) @@ -666,7 +668,7 @@ func TestRunInvalidTransaction(t *testing.T) { }) } routerOpt := func(bapp *BaseApp) { - bapp.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) + bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) } app := setupBaseApp(t, anteOpt, routerOpt) @@ -727,7 +729,7 @@ func TestRunInvalidTransaction(t *testing.T) { registerTestCodec(newCdc) newCdc.RegisterConcrete(&msgNoDecode{}, "cosmos-sdk/baseapp/msgNoDecode", nil) - txBytes, err := newCdc.MarshalBinary(tx) + txBytes, err := newCdc.MarshalBinaryLengthPrefixed(tx) require.NoError(t, err) res := app.DeliverTx(txBytes) require.EqualValues(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeTxDecode), res.Code) @@ -771,7 +773,7 @@ func TestTxGasLimits(t *testing.T) { } routerOpt := func(bapp *BaseApp) { - bapp.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { count := msg.(msgCounter).Counter ctx.GasMeter().ConsumeGas(count, "counter-handler") return sdk.Result{} diff --git a/baseapp/query_test.go b/baseapp/query_test.go index 579874e21745..d9d4001eb437 100644 --- a/baseapp/query_test.go +++ b/baseapp/query_test.go @@ -21,7 +21,7 @@ func TestQuery(t *testing.T) { } routerOpt := func(bapp *BaseApp) { - bapp.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { store := ctx.KVStore(capKey1) store.Set(key, value) return sdk.Result{} diff --git a/client/config.go b/client/config.go index a1d38a016ff2..b7661063877b 100644 --- a/client/config.go +++ b/client/config.go @@ -3,20 +3,20 @@ package client import ( "bufio" "fmt" + "io/ioutil" + "os" + "path" + "github.com/cosmos/cosmos-sdk/types" "github.com/mitchellh/go-homedir" "github.com/pelletier/go-toml" "github.com/spf13/cobra" - "io/ioutil" - "os" - "path" ) type cliConfig struct { Home string `toml:"home"` ChainID string `toml:"chain_id"` TrustNode bool `toml:"trust_node"` - Encoding string `toml:"encoding"` Output string `toml:"output"` Node string `toml:"node"` Trace bool `toml:"trace"` @@ -41,23 +41,24 @@ func runConfigCmd(cmd *cobra.Command, args []string) error { } stdin := BufferStdin() + gaiaCLIHome, err := handleGaiaCLIHome(home, stdin) if err != nil { return err } + node, err := handleNode(stdin) if err != nil { return err } + trustNode, err := handleTrustNode(stdin) if err != nil { return err } - encoding := "btc" - output := "text" - var chainID string - chainID, err = types.DefaultChainID() + chainID, err := types.DefaultChainID() + if err != nil { fmt.Println("Couldn't populate ChainID, so using an empty one.") } @@ -66,8 +67,7 @@ func runConfigCmd(cmd *cobra.Command, args []string) error { Home: gaiaCLIHome, ChainID: chainID, TrustNode: trustNode, - Encoding: encoding, - Output: output, + Output: "text", Node: node, Trace: false, } diff --git a/client/context/broadcast.go b/client/context/broadcast.go index b15b2a8e32c4..9f88ce7b9892 100644 --- a/client/context/broadcast.go +++ b/client/context/broadcast.go @@ -55,15 +55,11 @@ func (ctx CLIContext) BroadcastTxAndAwaitCommit(tx []byte) (*ctypes.ResultBroadc } if !res.CheckTx.IsOK() { - return res, errors.Errorf("checkTx failed: (%d) %s", - res.CheckTx.Code, - res.CheckTx.Log) + return res, errors.Errorf(res.CheckTx.Log) } if !res.DeliverTx.IsOK() { - return res, errors.Errorf("deliverTx failed: (%d) %s", - res.DeliverTx.Code, - res.DeliverTx.Log) + return res, errors.Errorf(res.DeliverTx.Log) } return res, err diff --git a/client/context/context.go b/client/context/context.go index ce36c37a8a9d..9108b3d0bba5 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -121,8 +121,13 @@ func createVerifier() tmlite.Verifier { fmt.Printf("Must specify these options: %s when --trust-node is false\n", errMsg.String()) os.Exit(1) } + node := rpcclient.NewHTTP(nodeURI, "/websocket") - verifier, err := tmliteProxy.NewVerifier(chainID, filepath.Join(home, ".gaialite"), node, log.NewNopLogger()) + cacheSize := 10 // TODO: determine appropriate cache size + verifier, err := tmliteProxy.NewVerifier( + chainID, filepath.Join(home, ".gaialite"), + node, log.NewNopLogger(), cacheSize, + ) if err != nil { fmt.Printf("Create verifier failed: %s\n", err.Error()) diff --git a/client/context/query.go b/client/context/query.go index e4e48819047d..572a1377874b 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -10,9 +10,9 @@ import ( "strings" - "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/merkle" cmn "github.com/tendermint/tendermint/libs/common" tmliteErr "github.com/tendermint/tendermint/lite/errors" tmliteProxy "github.com/tendermint/tendermint/lite/proxy" @@ -54,7 +54,7 @@ func (ctx CLIContext) QuerySubspace(subspace []byte, storeName string) (res []sd return res, err } - ctx.Codec.MustUnmarshalBinary(resRaw, &res) + ctx.Codec.MustUnmarshalBinaryLengthPrefixed(resRaw, &res) return } @@ -157,8 +157,8 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro } opts := rpcclient.ABCIQueryOptions{ - Height: ctx.Height, - Trusted: ctx.TrustNode, + Height: ctx.Height, + Prove: !ctx.TrustNode, } result, err := node.ABCIQueryWithOptions(path, key, opts) @@ -168,7 +168,7 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro resp := result.Response if !resp.IsOK() { - return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) + return res, errors.Errorf(resp.Log) } // data from trusted node or subspace query doesn't need verification @@ -198,7 +198,7 @@ func (ctx CLIContext) Verify(height int64) (tmtypes.SignedHeader, error) { } // verifyProof perform response proof verification. -func (ctx CLIContext) verifyProof(_ string, resp abci.ResponseQuery) error { +func (ctx CLIContext) verifyProof(queryPath string, resp abci.ResponseQuery) error { if ctx.Verifier == nil { return fmt.Errorf("missing valid certifier to verify data from distrusted node") } @@ -209,25 +209,22 @@ func (ctx CLIContext) verifyProof(_ string, resp abci.ResponseQuery) error { return err } - var multiStoreProof store.MultiStoreProof - cdc := codec.New() + // TODO: Instead of reconstructing, stash on CLIContext field? + prt := store.DefaultProofRuntime() - err = cdc.UnmarshalBinary(resp.Proof, &multiStoreProof) + // TODO: Better convention for path? + storeName, err := parseQueryStorePath(queryPath) if err != nil { - return errors.Wrap(err, "failed to unmarshalBinary rangeProof") + return err } - // verify the substore commit hash against trusted appHash - substoreCommitHash, err := store.VerifyMultiStoreCommitInfo( - multiStoreProof.StoreName, multiStoreProof.StoreInfos, commit.Header.AppHash, - ) - if err != nil { - return errors.Wrap(err, "failed in verifying the proof against appHash") - } + kp := merkle.KeyPath{} + kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL) + kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL) - err = store.VerifyRangeProof(resp.Key, resp.Value, substoreCommitHash, &multiStoreProof.RangeProof) + err = prt.VerifyValue(resp.Proof, commit.Header.AppHash, kp.String(), resp.Value) if err != nil { - return errors.Wrap(err, "failed in the range proof verification") + return errors.Wrap(err, "failed to prove merkle proof") } return nil @@ -241,20 +238,40 @@ func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([ } // isQueryStoreWithProof expects a format like /// -// queryType can be app or store. +// queryType must be "store" and subpath must be "key" to require a proof. func isQueryStoreWithProof(path string) bool { if !strings.HasPrefix(path, "/") { return false } paths := strings.SplitN(path[1:], "/", 3) - if len(paths) != 3 { + switch { + case len(paths) != 3: return false - } - - if store.RequireProof("/" + paths[2]) { + case paths[0] != "store": + return false + case store.RequireProof("/" + paths[2]): return true } return false } + +// parseQueryStorePath expects a format like /store//key. +func parseQueryStorePath(path string) (storeName string, err error) { + if !strings.HasPrefix(path, "/") { + return "", errors.New("expected path to start with /") + } + + paths := strings.SplitN(path[1:], "/", 3) + switch { + case len(paths) != 3: + return "", errors.New("expected format like /store//key") + case paths[0] != "store": + return "", errors.New("expected format like /store//key") + case paths[2] != "key": + return "", errors.New("expected format like /store//key") + } + + return paths[1], nil +} diff --git a/client/input.go b/client/input.go index a456f1b92a3f..46c838e2e4e4 100644 --- a/client/input.go +++ b/client/input.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/bgentry/speakeasy" - isatty "github.com/mattn/go-isatty" + "github.com/mattn/go-isatty" "github.com/pkg/errors" ) @@ -44,13 +44,8 @@ func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) { // GetSeed will request a seed phrase from stdin and trims off // leading/trailing spaces -func GetSeed(prompt string, buf *bufio.Reader) (seed string, err error) { - if inputIsTty() { - fmt.Println(prompt) - } - seed, err = readLineFromBuf(buf) - seed = strings.TrimSpace(seed) - return +func GetSeed(prompt string, buf *bufio.Reader) (string, error) { + return GetString(prompt, buf) } // GetCheckPassword will prompt for a password twice to verify they @@ -133,5 +128,6 @@ func readLineFromBuf(buf *bufio.Reader) (string, error) { // PrintPrefixed prints a string with > prefixed for use in prompts. func PrintPrefixed(msg string) { - fmt.Printf("> %s\n", msg) + msg = fmt.Sprintf("> %s\n", msg) + fmt.Fprint(os.Stderr, msg) } diff --git a/client/keys/add.go b/client/keys/add.go index 21a372e2f752..6b6e381174ec 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -63,7 +63,7 @@ func runAddCmd(cmd *cobra.Command, args []string) error { return errMissingName() } name = args[0] - kb, err = GetKeyBase() + kb, err = GetKeyBaseWithWritePerm() if err != nil { return err } @@ -174,7 +174,7 @@ func AddNewKeyRequestHandler(indent bool) http.HandlerFunc { var kb keys.Keybase var m NewKeyBody - kb, err := GetKeyBase() + kb, err := GetKeyBaseWithWritePerm() if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) @@ -262,6 +262,8 @@ func SeedRequestHandler(w http.ResponseWriter, r *http.Request) { algo := keys.SigningAlgo(algoType) seed := getSeed(algo) + + w.Header().Set("Content-Type", "application/json") w.Write([]byte(seed)) } @@ -309,7 +311,7 @@ func RecoverRequestHandler(indent bool) http.HandlerFunc { return } - kb, err := GetKeyBase() + kb, err := GetKeyBaseWithWritePerm() if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) diff --git a/client/keys/delete.go b/client/keys/delete.go index 23fc41ffd6ae..406843663d38 100644 --- a/client/keys/delete.go +++ b/client/keys/delete.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/crypto/keys" + keyerror "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -25,7 +26,7 @@ func deleteKeyCommand() *cobra.Command { func runDeleteCmd(cmd *cobra.Command, args []string) error { name := args[0] - kb, err := GetKeyBase() + kb, err := GetKeyBaseWithWritePerm() if err != nil { return err } @@ -73,16 +74,23 @@ func DeleteKeyRequestHandler(w http.ResponseWriter, r *http.Request) { return } - kb, err = GetKeyBase() + kb, err = GetKeyBaseWithWritePerm() if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } - // TODO handle error if key is not available or pass is wrong err = kb.Delete(name, m.Password) - if err != nil { + if keyerror.IsErrKeyNotFound(err) { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte(err.Error())) + return + } else if keyerror.IsErrWrongPassword(err) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(err.Error())) + return + } else if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return diff --git a/client/keys/mnemonic.go b/client/keys/mnemonic.go new file mode 100644 index 000000000000..33270a087414 --- /dev/null +++ b/client/keys/mnemonic.go @@ -0,0 +1,78 @@ +package keys + +import ( + "crypto/sha256" + "fmt" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/spf13/cobra" + + bip39 "github.com/bartekn/go-bip39" +) + +const ( + flagUserEntropy = "unsafe-entropy" + + mnemonicEntropySize = 256 +) + +func mnemonicKeyCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "mnemonic", + Short: "Compute the bip39 mnemonic for some input entropy", + Long: "Create a bip39 mnemonic, sometimes called a seed phrase, by reading from the system entropy. To pass your own entropy, use --unsafe-entropy", + RunE: runMnemonicCmd, + } + cmd.Flags().Bool(flagUserEntropy, false, "Prompt the user to supply their own entropy, instead of relying on the system") + return cmd +} + +func runMnemonicCmd(cmd *cobra.Command, args []string) error { + flags := cmd.Flags() + + userEntropy, _ := flags.GetBool(flagUserEntropy) + + var entropySeed []byte + + if userEntropy { + // prompt the user to enter some entropy + buf := client.BufferStdin() + inputEntropy, err := client.GetString("> WARNING: Generate at least 256-bits of entropy and enter the results here:", buf) + if err != nil { + return err + } + if len(inputEntropy) < 43 { + return fmt.Errorf("256-bits is 43 characters in Base-64, and 100 in Base-6. You entered %v, and probably want more", len(inputEntropy)) + } + conf, err := client.GetConfirmation( + fmt.Sprintf("> Input length: %d", len(inputEntropy)), + buf) + if err != nil { + return err + } + if !conf { + return nil + } + + // hash input entropy to get entropy seed + hashedEntropy := sha256.Sum256([]byte(inputEntropy)) + entropySeed = hashedEntropy[:] + printStep() + } else { + // read entropy seed straight from crypto.Rand + var err error + entropySeed, err = bip39.NewEntropy(mnemonicEntropySize) + if err != nil { + return err + } + } + + mnemonic, err := bip39.NewMnemonic(entropySeed[:]) + if err != nil { + return err + } + + fmt.Println(mnemonic) + + return nil +} diff --git a/client/keys/new.go b/client/keys/new.go new file mode 100644 index 000000000000..e72a958a928a --- /dev/null +++ b/client/keys/new.go @@ -0,0 +1,188 @@ +package keys + +import ( + "fmt" + + "github.com/bartekn/go-bip39" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" +) + +const ( + flagNewDefault = "default" + flagBIP44Path = "bip44-path" +) + +func newKeyCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "new", + Short: "Interactive command to derive a new private key, encrypt it, and save to disk", + Long: `Derive a new private key using an interactive command that will prompt you for each input. +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 encrypted with the given password. The only input that is required is the encryption password.`, + Args: cobra.ExactArgs(1), + RunE: runNewCmd, + } + cmd.Flags().Bool(flagNewDefault, false, "Skip the prompts and just use the default values for everything") + 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") + return cmd +} + +/* +input + - bip39 mnemonic + - bip39 passphrase + - bip44 path + - local encryption password +output + - armor encrypted private key (saved to file) +*/ +func runNewCmd(cmd *cobra.Command, args []string) error { + name := args[0] + kb, err := GetKeyBaseWithWritePerm() + if err != nil { + return err + } + + buf := client.BufferStdin() + + _, err = kb.Get(name) + if err == nil { + // account exists, ask for user confirmation + if response, err := client.GetConfirmation( + fmt.Sprintf("> override the existing name %s", name), buf); err != nil || !response { + return err + } + } + + flags := cmd.Flags() + useDefaults, _ := flags.GetBool(flagNewDefault) + bipFlag := flags.Lookup(flagBIP44Path) + + bip44Params, err := getBIP44ParamsAndPath(bipFlag.Value.String(), bipFlag.Changed || useDefaults) + if err != nil { + return err + } + + // If we're using ledger, only thing we need is the path. So generate key and + // we're done. + if viper.GetBool(client.FlagUseLedger) { + algo := keys.Secp256k1 + path := bip44Params.DerivationPath() // ccrypto.DerivationPath{44, 118, account, 0, index} + + info, err := kb.CreateLedger(name, path, algo) + if err != nil { + return err + } + + printCreate(info, "") + return nil + } + + var mnemonic string + + if !useDefaults { + mnemonic, err = client.GetString("Enter your bip39 mnemonic, or hit enter to generate one.", buf) + if err != nil { + return err + } + } + + if len(mnemonic) == 0 { + // read entropy seed straight from crypto.Rand and convert to mnemonic + entropySeed, err := bip39.NewEntropy(mnemonicEntropySize) + if err != nil { + return err + } + + mnemonic, err = bip39.NewMnemonic(entropySeed[:]) + if err != nil { + return err + } + } + + // get bip39 passphrase + var bip39Passphrase string + if !useDefaults { + printStep() + printPrefixed("Enter your bip39 passphrase. This is combined with the mnemonic to derive the seed") + + bip39Passphrase, err = client.GetString("Most users should just hit enter to use the default, \"\"", buf) + if err != nil { + return err + } + + // if they use one, make them re-enter it + if len(bip39Passphrase) != 0 { + p2, err := client.GetString("Repeat the passphrase:", buf) + if err != nil { + return err + } + + if bip39Passphrase != p2 { + return errors.New("passphrases don't match") + } + } + } + + printStep() + + // get the encryption password + encryptPassword, err := client.GetCheckPassword( + "> Enter a passphrase to encrypt your key to disk:", + "> Repeat the passphrase:", buf) + if err != nil { + return err + } + + info, err := kb.Derive(name, mnemonic, bip39Passphrase, encryptPassword, *bip44Params) + if err != nil { + return err + } + + _ = info + return nil +} + +func getBIP44ParamsAndPath(path string, flagSet bool) (*hd.BIP44Params, error) { + buf := client.BufferStdin() + bip44Path := path + + // if it wasn't set in the flag, give it a chance to overide interactively + if !flagSet { + var err error + + printStep() + + bip44Path, err = client.GetString(fmt.Sprintf("Enter your bip44 path. Default is %s\n", path), buf) + if err != nil { + return nil, err + } + + if len(bip44Path) == 0 { + bip44Path = path + } + } + + bip44params, err := hd.NewParamsFromPath(bip44Path) + if err != nil { + return nil, err + } + + return bip44params, nil +} + +func printPrefixed(msg string) { + fmt.Printf("> %s\n", msg) +} + +func printStep() { + printPrefixed("-------------------------------------") +} diff --git a/client/keys/root.go b/client/keys/root.go index a7a7d2e6fc60..b10cd2b550c1 100644 --- a/client/keys/root.go +++ b/client/keys/root.go @@ -19,6 +19,8 @@ func Commands() *cobra.Command { needs to sign with a private key.`, } cmd.AddCommand( + mnemonicKeyCommand(), + newKeyCommand(), addKeyCommand(), listKeysCmd, showKeysCmd(), diff --git a/client/keys/show.go b/client/keys/show.go index b567daf12435..3b574544b5ac 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -2,12 +2,17 @@ package keys import ( "fmt" + "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/tendermint/tendermint/crypto" "net/http" + "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/tendermint/tendermint/crypto/multisig" "github.com/tendermint/tendermint/libs/cli" ) @@ -18,30 +23,68 @@ const ( FlagPublicKey = "pubkey" // FlagBechPrefix defines a desired Bech32 prefix encoding for a key. FlagBechPrefix = "bech" + + flagMultiSigThreshold = "multisig-threshold" + defaultMultiSigKeyName = "multi" ) +var _ keys.Info = (*multiSigKey)(nil) + +type multiSigKey struct { + name string + key crypto.PubKey +} + +func (m multiSigKey) GetName() string { return m.name } +func (m multiSigKey) GetType() keys.KeyType { return keys.TypeLocal } +func (m multiSigKey) GetPubKey() crypto.PubKey { return m.key } +func (m multiSigKey) GetAddress() sdk.AccAddress { return sdk.AccAddress(m.key.Address()) } + func showKeysCmd() *cobra.Command { cmd := &cobra.Command{ Use: "show [name]", Short: "Show key info for the given name", Long: `Return public details of one local key.`, - Args: cobra.ExactArgs(1), + Args: cobra.MinimumNArgs(1), RunE: runShowCmd, } cmd.Flags().String(FlagBechPrefix, "acc", "The Bech32 prefix encoding for a key (acc|val|cons)") cmd.Flags().Bool(FlagAddress, false, "output the address only (overrides --output)") cmd.Flags().Bool(FlagPublicKey, false, "output the public key only (overrides --output)") + cmd.Flags().Uint(flagMultiSigThreshold, 1, "K out of N required signatures") return cmd } -func runShowCmd(cmd *cobra.Command, args []string) error { - name := args[0] +func runShowCmd(cmd *cobra.Command, args []string) (err error) { + var info keys.Info - info, err := GetKeyInfo(name) - if err != nil { - return err + if len(args) == 1 { + info, err = GetKeyInfo(args[0]) + if err != nil { + return err + } + } else { + pks := make([]crypto.PubKey, len(args)) + for i, keyName := range args { + info, err := GetKeyInfo(keyName) + if err != nil { + return err + } + pks[i] = info.GetPubKey() + } + + multisigThreshold := viper.GetInt(flagMultiSigThreshold) + err = validateMultisigThreshold(multisigThreshold, len(args)) + if err != nil { + return err + } + multikey := multisig.NewPubKeyMultisigThreshold(multisigThreshold, pks) + info = multiSigKey{ + name: defaultMultiSigKeyName, + key: multikey, + } } isShowAddr := viper.GetBool(FlagAddress) @@ -73,6 +116,17 @@ func runShowCmd(cmd *cobra.Command, args []string) error { return nil } +func validateMultisigThreshold(k, nKeys int) error { + if k <= 0 { + return fmt.Errorf("threshold must be a positive integer") + } + if nKeys < k { + return fmt.Errorf( + "threshold k of n multisignature: %d < %d", nKeys, k) + } + return nil +} + func getBechKeyOut(bechPrefix string) (bechKeyOutFn, error) { switch bechPrefix { case "acc": @@ -108,12 +162,14 @@ func GetKeyRequestHandler(indent bool) http.HandlerFunc { } info, err := GetKeyInfo(name) - // TODO: check for the error if key actually does not exist, instead of - // assuming this as the reason - if err != nil { + if keyerror.IsErrKeyNotFound(err) { w.WriteHeader(http.StatusNotFound) w.Write([]byte(err.Error())) return + } else if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return } keyOutput, err := bechKeyOut(info) diff --git a/client/keys/update.go b/client/keys/update.go index 18a18be58828..2489bce128bc 100644 --- a/client/keys/update.go +++ b/client/keys/update.go @@ -9,6 +9,7 @@ import ( keys "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/gorilla/mux" + "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" "github.com/spf13/cobra" ) @@ -26,7 +27,7 @@ func runUpdateCmd(cmd *cobra.Command, args []string) error { name := args[0] buf := client.BufferStdin() - kb, err := GetKeyBase() + kb, err := GetKeyBaseWithWritePerm() if err != nil { return err } @@ -74,7 +75,7 @@ func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) { return } - kb, err = GetKeyBase() + kb, err = GetKeyBaseWithWritePerm() if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) @@ -83,13 +84,21 @@ func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) { getNewpass := func() (string, error) { return m.NewPassword, nil } - // TODO check if account exists and if password is correct err = kb.Update(name, m.OldPassword, getNewpass) - if err != nil { + if keyerror.IsErrKeyNotFound(err) { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte(err.Error())) + return + } else if keyerror.IsErrWrongPassword(err) { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(err.Error())) return + } else if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return } + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) } diff --git a/client/keys/utils.go b/client/keys/utils.go index 4ca8fc8f4279..742b512d3da7 100644 --- a/client/keys/utils.go +++ b/client/keys/utils.go @@ -2,6 +2,7 @@ package keys import ( "fmt" + "github.com/syndtr/goleveldb/leveldb/opt" "path/filepath" "github.com/spf13/viper" @@ -25,14 +26,6 @@ var keybase keys.Keybase type bechKeyOutFn func(keyInfo keys.Info) (KeyOutput, error) -// TODO make keybase take a database not load from the directory - -// initialize a keybase based on the configuration -func GetKeyBase() (keys.Keybase, error) { - rootDir := viper.GetString(cli.HomeFlag) - return GetKeyBaseFromDir(rootDir) -} - // GetKeyInfo returns key info for a given name. An error is returned if the // keybase cannot be retrieved or getting the info fails. func GetKeyInfo(name string) (keys.Info, error) { @@ -82,10 +75,38 @@ func ReadPassphraseFromStdin(name string) (string, error) { return passphrase, nil } -// initialize a keybase based on the configuration +// TODO make keybase take a database not load from the directory + +// GetKeyBase initializes a read-only KeyBase based on the configuration. +func GetKeyBase() (keys.Keybase, error) { + rootDir := viper.GetString(cli.HomeFlag) + return GetKeyBaseFromDir(rootDir) +} + +// GetKeyBaseWithWritePerm initialize a keybase based on the configuration with write permissions. +func GetKeyBaseWithWritePerm() (keys.Keybase, error) { + rootDir := viper.GetString(cli.HomeFlag) + return GetKeyBaseFromDirWithWritePerm(rootDir) +} + +// GetKeyBaseFromDirWithWritePerm initializes a keybase at a particular dir with write permissions. +func GetKeyBaseFromDirWithWritePerm(rootDir string) (keys.Keybase, error) { + return getKeyBaseFromDirWithOpts(rootDir, nil) +} + +// GetKeyBaseFromDir initializes a read-only keybase at a particular dir. func GetKeyBaseFromDir(rootDir string) (keys.Keybase, error) { + // Disabled because of the inability to create a new keys database directory + // in the instance of when ReadOnly is set to true. + // + // ref: syndtr/goleveldb#240 + // return getKeyBaseFromDirWithOpts(rootDir, &opt.Options{ReadOnly: true}) + return getKeyBaseFromDirWithOpts(rootDir, nil) +} + +func getKeyBaseFromDirWithOpts(rootDir string, o *opt.Options) (keys.Keybase, error) { if keybase == nil { - db, err := dbm.NewGoLevelDB(KeyDBName, filepath.Join(rootDir, "keys")) + db, err := dbm.NewGoLevelDBWithOpts(KeyDBName, filepath.Join(rootDir, "keys"), o) if err != nil { return nil, err } diff --git a/client/keys/utils_test.go b/client/keys/utils_test.go new file mode 100644 index 000000000000..6b65bb55a529 --- /dev/null +++ b/client/keys/utils_test.go @@ -0,0 +1,39 @@ +package keys + +import ( + "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/stretchr/testify/require" + "io/ioutil" + "os" + "testing" +) + +func TestGetKeyBaseLocks(t *testing.T) { + dir, err := ioutil.TempDir("", "cosmos-sdk-keys") + require.Nil(t, err) + defer os.RemoveAll(dir) + + // Acquire db + kb, err := GetKeyBaseFromDirWithWritePerm(dir) + require.Nil(t, err) + _, _, err = kb.CreateMnemonic("foo", keys.English, "12345678", keys.Secp256k1) + require.Nil(t, err) + // Reset global variable + keybase = nil + // Try to acquire another keybase from the same storage + _, err = GetKeyBaseFromDirWithWritePerm(dir) + require.NotNil(t, err) + _, err = GetKeyBaseFromDirWithWritePerm(dir) + require.NotNil(t, err) + + // Close the db and try to acquire the lock + kb.CloseDB() + kb, err = GetKeyBaseFromDirWithWritePerm(dir) + require.Nil(t, err) + + // Try to acquire another read-only keybase from the same storage + _, err = GetKeyBaseFromDir(dir) + require.Nil(t, err) + + kb.CloseDB() +} diff --git a/client/lcd/certificates.go b/client/lcd/certificates.go index f47f2397c72e..0ec527e134ac 100644 --- a/client/lcd/certificates.go +++ b/client/lcd/certificates.go @@ -38,12 +38,13 @@ func generateSelfSignedCert(host string) (certBytes []byte, priv *ecdsa.PrivateK Subject: pkix.Name{ Organization: []string{"Gaia Lite"}, }, + DNSNames: []string{"localhost"}, NotBefore: notBefore, NotAfter: notAfter, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, - IsCA: true, + IsCA: true, } hosts := strings.Split(host, ",") for _, h := range hosts { diff --git a/client/lcd/certificates_test.go b/client/lcd/certificates_test.go index 14bddfa0f8bb..3f48c194c252 100644 --- a/client/lcd/certificates_test.go +++ b/client/lcd/certificates_test.go @@ -17,7 +17,7 @@ func TestGenerateSelfSignedCert(t *testing.T) { cert, err := x509.ParseCertificate(certBytes) require.Nil(t, err) require.Equal(t, 2, len(cert.IPAddresses)) - require.Equal(t, 1, len(cert.DNSNames)) + require.Equal(t, 2, len(cert.DNSNames)) require.True(t, cert.IsCA) } diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 6eef4afd0ae5..1be908e98bdb 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -10,20 +10,19 @@ import ( "testing" "time" - "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/spf13/viper" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys" p2p "github.com/tendermint/tendermint/p2p" ctypes "github.com/tendermint/tendermint/rpc/core/types" client "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" - rpc "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/codec" + cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" tests "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" version "github.com/cosmos/cosmos-sdk/version" @@ -32,10 +31,11 @@ import ( "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" ) func init() { - cryptoKeys.BcryptSecurityParameter = 1 + mintkey.BcryptSecurityParameter = 1 version.Version = os.Getenv("VERSION") } @@ -77,7 +77,7 @@ func TestKeys(t *testing.T) { // test if created account is the correct account expectedInfo, _ := GetKeyBase(t).CreateKey(newName, seed, newPassword) expectedAccount := sdk.AccAddress(expectedInfo.GetPubKey().Address().Bytes()) - assert.Equal(t, expectedAccount.String(), addr2Bech32) + require.Equal(t, expectedAccount.String(), addr2Bech32) // existing keys res, body = Request(t, port, "GET", "/keys", nil) @@ -159,11 +159,11 @@ func TestNodeStatus(t *testing.T) { res, body := Request(t, port, "GET", "/node_info", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var nodeInfo p2p.NodeInfo + var nodeInfo p2p.DefaultNodeInfo err := cdc.UnmarshalJSON([]byte(body), &nodeInfo) require.Nil(t, err, "Couldn't parse node info") - require.NotEqual(t, p2p.NodeInfo{}, nodeInfo, "res: %v", res) + require.NotEqual(t, p2p.DefaultNodeInfo{}, nodeInfo, "res: %v", res) // syncing res, body = Request(t, port, "GET", "/syncing", nil) @@ -266,7 +266,7 @@ func TestCoinSend(t *testing.T) { coins := acc.GetCoins() mycoins := coins[0] - require.Equal(t, "steak", mycoins.Denom) + require.Equal(t, stakeTypes.DefaultBondDenom, mycoins.Denom) require.Equal(t, initialBalance[0].Amount.SubRaw(1), mycoins.Amount) // query receiver @@ -274,7 +274,7 @@ func TestCoinSend(t *testing.T) { coins = acc.GetCoins() mycoins = coins[0] - require.Equal(t, "steak", mycoins.Denom) + require.Equal(t, stakeTypes.DefaultBondDenom, mycoins.Denom) require.Equal(t, int64(1), mycoins.Amount.Int64()) // test failure with too little gas @@ -327,7 +327,7 @@ func DisabledTestIBCTransfer(t *testing.T) { coins := acc.GetCoins() mycoins := coins[0] - require.Equal(t, "steak", mycoins.Denom) + require.Equal(t, stakeTypes.DefaultBondDenom, mycoins.Denom) require.Equal(t, initialBalance[0].Amount.SubRaw(1), mycoins.Amount) // TODO: query ibc egress packet state @@ -346,7 +346,7 @@ func TestCoinSendGenerateSignAndBroadcast(t *testing.T) { var msg auth.StdTx require.Nil(t, cdc.UnmarshalJSON([]byte(body), &msg)) require.Equal(t, len(msg.Msgs), 1) - require.Equal(t, msg.Msgs[0].Type(), "bank") + 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 @@ -480,11 +480,7 @@ func TestPoolParamsQuery(t *testing.T) { var pool stake.Pool err = cdc.UnmarshalJSON([]byte(body), &pool) require.Nil(t, err) - require.Equal(t, initialPool.DateLastCommissionReset, pool.DateLastCommissionReset) - require.Equal(t, initialPool.PrevBondedShares, pool.PrevBondedShares) require.Equal(t, initialPool.BondedTokens, pool.BondedTokens) - require.Equal(t, initialPool.NextInflation(params), pool.Inflation) - initialPool = initialPool.ProcessProvisions(params) // provisions are added to the pool every hour require.Equal(t, initialPool.LooseTokens, pool.LooseTokens) } @@ -496,7 +492,7 @@ func TestValidatorsQuery(t *testing.T) { require.Equal(t, 1, len(operAddrs)) validators := getValidators(t, port) - require.Equal(t, len(validators), 1) + require.Equal(t, 1, len(validators), fmt.Sprintf("%+v", validators)) // make sure all the validators were found (order unknown because sorted by operator addr) foundVal := false @@ -515,11 +511,11 @@ func TestValidatorQuery(t *testing.T) { require.Equal(t, 1, len(operAddrs)) validator := getValidator(t, port, operAddrs[0]) - assert.Equal(t, validator.OperatorAddr, operAddrs[0], "The returned validator does not hold the correct data") + require.Equal(t, validator.OperatorAddr, operAddrs[0], "The returned validator does not hold the correct data") } func TestBonding(t *testing.T) { - name, password, denom := "test", "1234567890", "steak" + name, password, denom := "test", "1234567890", stakeTypes.DefaultBondDenom addr, seed := CreateAddr(t, name, password, GetKeyBase(t)) cleanup, valPubKeys, operAddrs, port := InitializeTestLCD(t, 2, []sdk.AccAddress{addr}) @@ -543,15 +539,17 @@ func TestBonding(t *testing.T) { require.Equal(t, int64(40), coins.AmountOf(denom).Int64()) - // query validator + // query delegation bond := getDelegation(t, port, addr, operAddrs[0]) require.Equal(t, amt, bond.Shares) - summary := getDelegationSummary(t, port, addr) + delegatorDels := getDelegatorDelegations(t, port, addr) + require.Len(t, delegatorDels, 1) + require.Equal(t, amt, delegatorDels[0].Shares) - require.Len(t, summary.Delegations, 1, "Delegation summary holds all delegations") - require.Equal(t, amt, summary.Delegations[0].Shares) - require.Len(t, summary.UnbondingDelegations, 0, "Delegation summary holds all unbonding-delegations") + // query all delegations to validator + bonds := getValidatorDelegations(t, port, operAddrs[0]) + require.Len(t, bonds, 2) bondedValidators := getDelegatorValidators(t, port, addr) require.Len(t, bondedValidators, 1) @@ -561,11 +559,8 @@ func TestBonding(t *testing.T) { bondedValidator := getDelegatorValidator(t, port, addr, operAddrs[0]) require.Equal(t, operAddrs[0], bondedValidator.OperatorAddr) - ////////////////////// // testing unbonding - - // create unbond TX - resultTx = doBeginUnbonding(t, port, seed, name, password, addr, operAddrs[0], 60) + resultTx = doBeginUnbonding(t, port, seed, name, password, addr, operAddrs[0], 30) tests.WaitForHeight(resultTx.Height+1, port) require.Equal(t, uint32(0), resultTx.CheckTx.Code) @@ -574,36 +569,54 @@ 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("steak").Int64()) + require.Equal(t, int64(40), coins.AmountOf(stakeTypes.DefaultBondDenom).Int64()) unbonding := getUndelegation(t, port, addr, operAddrs[0]) - require.Equal(t, "60", unbonding.Balance.Amount.String()) + require.Equal(t, "30", unbonding.Balance.Amount.String()) + + // test redelegation + resultTx = doBeginRedelegation(t, port, seed, name, password, addr, operAddrs[0], operAddrs[1], 30) + tests.WaitForHeight(resultTx.Height+1, port) - summary = getDelegationSummary(t, port, addr) + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) - require.Len(t, summary.Delegations, 0, "Delegation summary holds all delegations") - require.Len(t, summary.UnbondingDelegations, 1, "Delegation summary holds all unbonding-delegations") - require.Equal(t, "60", summary.UnbondingDelegations[0].Balance.Amount.String()) + // 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()) - bondedValidators = getDelegatorValidators(t, port, addr) - require.Len(t, bondedValidators, 0, "There's no delegation as the user withdraw all funds") + delegatorUbds := getDelegatorUnbondingDelegations(t, port, addr) + require.Len(t, delegatorUbds, 1) + require.Equal(t, "30", delegatorUbds[0].Balance.Amount.String()) + + delegatorReds := getDelegatorRedelegations(t, port, addr) + require.Len(t, delegatorReds, 1) + require.Equal(t, "30", delegatorReds[0].Balance.Amount.String()) + + validatorUbds := getValidatorUnbondingDelegations(t, port, operAddrs[0]) + require.Len(t, validatorUbds, 1) + require.Equal(t, "30", validatorUbds[0].Balance.Amount.String()) + + validatorReds := getValidatorRedelegations(t, port, operAddrs[0]) + require.Len(t, validatorReds, 1) + require.Equal(t, "30", validatorReds[0].Balance.Amount.String()) // TODO Undonding status not currently implemented // require.Equal(t, sdk.Unbonding, bondedValidators[0].Status) - // TODO add redelegation, need more complex capabilities such to mock context and - // TODO check summary for redelegation - // assert.Len(t, summary.Redelegations, 1, "Delegation summary holds all redelegations") - // query txs txs := getBondingTxs(t, port, addr, "") - assert.Len(t, txs, 2, "All Txs found") + require.Len(t, txs, 3, "All Txs found") txs = getBondingTxs(t, port, addr, "bond") - assert.Len(t, txs, 1, "All bonding txs found") + require.Len(t, txs, 1, "All bonding txs found") txs = getBondingTxs(t, port, addr, "unbond") - assert.Len(t, txs, 1, "All unbonding txs found") + require.Len(t, txs, 1, "All unbonding txs found") + + txs = getBondingTxs(t, port, addr, "redelegate") + require.Len(t, txs, 1, "All redelegation txs found") } func TestSubmitProposal(t *testing.T) { @@ -613,15 +626,15 @@ func TestSubmitProposal(t *testing.T) { defer cleanup() // create SubmitProposal TX - resultTx := doSubmitProposal(t, port, seed, name, password, addr) + resultTx := doSubmitProposal(t, port, seed, name, password, addr, 5) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was committed require.Equal(t, uint32(0), resultTx.CheckTx.Code) require.Equal(t, uint32(0), resultTx.DeliverTx.Code) - var proposalID int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID) + var proposalID uint64 + cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID) // query proposal proposal := getProposal(t, port, proposalID) @@ -635,31 +648,31 @@ func TestDeposit(t *testing.T) { defer cleanup() // create SubmitProposal TX - resultTx := doSubmitProposal(t, port, seed, name, password, addr) + resultTx := doSubmitProposal(t, port, seed, name, password, addr, 5) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was committed require.Equal(t, uint32(0), resultTx.CheckTx.Code) require.Equal(t, uint32(0), resultTx.DeliverTx.Code) - var proposalID int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID) + var proposalID uint64 + cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID) // query proposal proposal := getProposal(t, port, proposalID) require.Equal(t, "Test", proposal.GetTitle()) // create SubmitProposal TX - resultTx = doDeposit(t, port, seed, name, password, addr, proposalID) + resultTx = doDeposit(t, port, seed, name, password, addr, proposalID, 5) tests.WaitForHeight(resultTx.Height+1, port) // query proposal proposal = getProposal(t, port, proposalID) - require.True(t, proposal.GetTotalDeposit().IsEqual(sdk.Coins{sdk.NewInt64Coin("steak", 10)})) + require.True(t, proposal.GetTotalDeposit().IsEqual(sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)})) // query deposit deposit := getDeposit(t, port, proposalID, addr) - require.True(t, deposit.Amount.IsEqual(sdk.Coins{sdk.NewInt64Coin("steak", 10)})) + require.True(t, deposit.Amount.IsEqual(sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)})) } func TestVote(t *testing.T) { @@ -669,22 +682,22 @@ func TestVote(t *testing.T) { defer cleanup() // create SubmitProposal TX - resultTx := doSubmitProposal(t, port, seed, name, password, addr) + resultTx := doSubmitProposal(t, port, seed, name, password, addr, 5) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was committed require.Equal(t, uint32(0), resultTx.CheckTx.Code) require.Equal(t, uint32(0), resultTx.DeliverTx.Code) - var proposalID int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID) + var proposalID uint64 + cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID) // query proposal proposal := getProposal(t, port, proposalID) require.Equal(t, "Test", proposal.GetTitle()) // create SubmitProposal TX - resultTx = doDeposit(t, port, seed, name, password, addr, proposalID) + resultTx = doDeposit(t, port, seed, name, password, addr, proposalID, 5) tests.WaitForHeight(resultTx.Height+1, port) // query proposal @@ -713,39 +726,69 @@ func TestUnjail(t *testing.T) { tests.WaitForHeight(4, port) require.Equal(t, true, signingInfo.IndexOffset > 0) require.Equal(t, time.Unix(0, 0).UTC(), signingInfo.JailedUntil) - require.Equal(t, true, signingInfo.SignedBlocksCounter > 0) + require.Equal(t, true, signingInfo.MissedBlocksCounter == 0) } func TestProposalsQuery(t *testing.T) { - name, password1 := "test", "1234567890" - name2, password2 := "test2", "1234567890" - addr, seed := CreateAddr(t, "test", password1, GetKeyBase(t)) - addr2, seed2 := CreateAddr(t, "test2", password2, GetKeyBase(t)) - cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr, addr2}) + addrs, seeds, names, passwords := CreateAddrs(t, GetKeyBase(t), 2) + + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addrs[0], addrs[1]}) defer cleanup() + depositParam := getDepositParam(t, port) + halfMinDeposit := depositParam.MinDeposit.AmountOf(stakeTypes.DefaultBondDenom).Int64() / 2 + getVotingParam(t, port) + getTallyingParam(t, port) + // Addr1 proposes (and deposits) proposals #1 and #2 - resultTx := doSubmitProposal(t, port, seed, name, password1, addr) - var proposalID1 int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID1) + resultTx := doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], halfMinDeposit) + var proposalID1 uint64 + cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID1) tests.WaitForHeight(resultTx.Height+1, port) - resultTx = doSubmitProposal(t, port, seed, name, password1, addr) - var proposalID2 int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID2) + + resultTx = doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], halfMinDeposit) + 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, seed2, name2, password2, addr2) - var proposalID3 int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID3) + resultTx = doSubmitProposal(t, port, seeds[1], names[1], passwords[1], addrs[1], halfMinDeposit) + 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, seed2, name2, password2, addr2, proposalID2) + resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID2, halfMinDeposit) + tests.WaitForHeight(resultTx.Height+1, port) + + resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID3, halfMinDeposit) tests.WaitForHeight(resultTx.Height+1, port) - resultTx = doDeposit(t, port, seed2, name2, password2, addr2, proposalID3) + + // check deposits match proposal and individual deposits + deposits := getDeposits(t, port, proposalID1) + require.Len(t, deposits, 1) + deposit := getDeposit(t, port, proposalID1, addrs[0]) + require.Equal(t, deposit, deposits[0]) + + deposits = getDeposits(t, port, proposalID2) + require.Len(t, deposits, 2) + deposit = getDeposit(t, port, proposalID2, addrs[0]) + require.True(t, deposit.Equals(deposits[0])) + deposit = getDeposit(t, port, proposalID2, addrs[1]) + require.True(t, deposit.Equals(deposits[1])) + + deposits = getDeposits(t, port, proposalID3) + require.Len(t, deposits, 1) + deposit = getDeposit(t, port, proposalID3, addrs[1]) + 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) tests.WaitForHeight(resultTx.Height+1, port) + deposits = getDeposits(t, port, proposalID1) + require.Len(t, deposits, 1) + // Only proposals #1 should be in Deposit Period proposals := getProposalsFilterStatus(t, port, gov.StatusDepositPeriod) require.Len(t, proposals, 1) @@ -757,13 +800,13 @@ func TestProposalsQuery(t *testing.T) { require.Equal(t, proposalID3, proposals[1].GetProposalID()) // Addr1 votes on proposals #2 & #3 - resultTx = doVote(t, port, seed, name, password1, addr, proposalID2) + resultTx = doVote(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID2) tests.WaitForHeight(resultTx.Height+1, port) - resultTx = doVote(t, port, seed, name, password1, addr, proposalID3) + resultTx = doVote(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID3) tests.WaitForHeight(resultTx.Height+1, port) // Addr2 votes on proposal #3 - resultTx = doVote(t, port, seed2, name2, password2, addr2, proposalID3) + resultTx = doVote(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID3) tests.WaitForHeight(resultTx.Height+1, port) // Test query all proposals @@ -773,37 +816,37 @@ func TestProposalsQuery(t *testing.T) { require.Equal(t, proposalID3, (proposals[2]).GetProposalID()) // Test query deposited by addr1 - proposals = getProposalsFilterDepositer(t, port, addr) + proposals = getProposalsFilterDepositer(t, port, addrs[0]) require.Equal(t, proposalID1, (proposals[0]).GetProposalID()) // Test query deposited by addr2 - proposals = getProposalsFilterDepositer(t, port, addr2) + proposals = getProposalsFilterDepositer(t, port, addrs[1]) require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) require.Equal(t, proposalID3, (proposals[1]).GetProposalID()) // Test query voted by addr1 - proposals = getProposalsFilterVoter(t, port, addr) + proposals = getProposalsFilterVoter(t, port, addrs[0]) require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) require.Equal(t, proposalID3, (proposals[1]).GetProposalID()) // Test query voted by addr2 - proposals = getProposalsFilterVoter(t, port, addr2) + proposals = getProposalsFilterVoter(t, port, addrs[1]) require.Equal(t, proposalID3, (proposals[0]).GetProposalID()) // Test query voted and deposited by addr1 - proposals = getProposalsFilterVoterDepositer(t, port, addr, addr) + proposals = getProposalsFilterVoterDepositer(t, port, addrs[0], addrs[0]) require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) // Test query votes on Proposal 2 votes := getVotes(t, port, proposalID2) require.Len(t, votes, 1) - require.Equal(t, addr, votes[0].Voter) + require.Equal(t, addrs[0], votes[0].Voter) // Test query votes on Proposal 3 votes = getVotes(t, port, proposalID3) require.Len(t, votes, 2) - require.True(t, addr.String() == votes[0].Voter.String() || addr.String() == votes[1].Voter.String()) - require.True(t, addr2.String() == votes[0].Voter.String() || addr2.String() == votes[1].Voter.String()) + require.True(t, addrs[0].String() == votes[0].Voter.String() || addrs[0].String() == votes[1].Voter.String()) + require.True(t, addrs[1].String() == votes[0].Voter.String() || addrs[1].String() == votes[1].Voter.String()) } //_____________________________________________________________________________ @@ -830,7 +873,7 @@ func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.Acc sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) // send - coinbz, err := cdc.MarshalJSON(sdk.NewInt64Coin("steak", 1)) + coinbz, err := cdc.MarshalJSON(sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 1)) if err != nil { panic(err) } @@ -916,7 +959,7 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Acc "account_number":"%d", "sequence":"%d" } - }`, "steak", name, password, chainID, accnum, sequence)) + }`, stakeTypes.DefaultBondDenom, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", fmt.Sprintf("/ibc/testchain/%s/send", receiveAddr), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -928,7 +971,7 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Acc } func getSigningInfo(t *testing.T, port string, validatorPubKey string) slashing.ValidatorSigningInfo { - res, body := Request(t, port, "GET", fmt.Sprintf("/slashing/signing_info/%s", validatorPubKey), nil) + res, body := Request(t, port, "GET", fmt.Sprintf("/slashing/validators/%s/signing_info", validatorPubKey), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var signingInfo slashing.ValidatorSigningInfo @@ -955,23 +998,47 @@ func getUndelegation(t *testing.T, port string, delegatorAddr sdk.AccAddress, va res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/unbonding_delegations/%s", delegatorAddr, validatorAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var unbondings stake.UnbondingDelegation - err := cdc.UnmarshalJSON([]byte(body), &unbondings) + var unbond stake.UnbondingDelegation + err := cdc.UnmarshalJSON([]byte(body), &unbond) + require.Nil(t, err) + + return unbond +} + +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) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var dels []stake.Delegation + + err := cdc.UnmarshalJSON([]byte(body), &dels) require.Nil(t, err) - return unbondings + return dels } -func getDelegationSummary(t *testing.T, port string, delegatorAddr sdk.AccAddress) stake.DelegationSummary { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s", delegatorAddr), nil) +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) require.Equal(t, http.StatusOK, res.StatusCode, body) - var summary stake.DelegationSummary + var ubds []stake.UnbondingDelegation - err := cdc.UnmarshalJSON([]byte(body), &summary) + err := cdc.UnmarshalJSON([]byte(body), &ubds) require.Nil(t, err) - return summary + return ubds +} + +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) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var reds []stake.Redelegation + + err := cdc.UnmarshalJSON([]byte(body), &reds) + require.Nil(t, err) + + return reds } func getBondingTxs(t *testing.T, port string, delegatorAddr sdk.AccAddress, query string) []tx.Info { @@ -1033,9 +1100,7 @@ func doDelegate(t *testing.T, port, seed, name, password string, } ], "begin_unbondings": [], - "complete_unbondings": [], "begin_redelegates": [], - "complete_redelegates": [], "base_req": { "name": "%s", "password": "%s", @@ -1043,7 +1108,7 @@ func doDelegate(t *testing.T, port, seed, name, password string, "account_number":"%d", "sequence":"%d" } - }`, delAddr, valAddr, "steak", amount, name, password, chainID, accnum, sequence)) + }`, delAddr, valAddr, stakeTypes.DefaultBondDenom, amount, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", fmt.Sprintf("/stake/delegators/%s/delegations", delAddr), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -1072,9 +1137,7 @@ func doBeginUnbonding(t *testing.T, port, seed, name, password string, "shares": "%d" } ], - "complete_unbondings": [], "begin_redelegates": [], - "complete_redelegates": [], "base_req": { "name": "%s", "password": "%s", @@ -1095,7 +1158,7 @@ func doBeginUnbonding(t *testing.T, port, seed, name, password string, } func doBeginRedelegation(t *testing.T, port, seed, name, password string, - delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) (resultTx ctypes.ResultBroadcastTxCommit) { + delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, amount int64) (resultTx ctypes.ResultBroadcastTxCommit) { acc := getAccount(t, port, delAddr) accnum := acc.GetAccountNumber() @@ -1106,16 +1169,14 @@ func doBeginRedelegation(t *testing.T, port, seed, name, password string, jsonStr := []byte(fmt.Sprintf(`{ "delegations": [], "begin_unbondings": [], - "complete_unbondings": [], "begin_redelegates": [ { "delegator_addr": "%s", "validator_src_addr": "%s", "validator_dst_addr": "%s", - "shares": "30" + "shares": "%d" } ], - "complete_redelegates": [], "base_req": { "name": "%s", "password": "%s", @@ -1123,7 +1184,7 @@ func doBeginRedelegation(t *testing.T, port, seed, name, password string, "account_number":"%d", "sequence":"%d" } - }`, delAddr, valSrcAddr, valDstAddr, name, password, chainID, accnum, sequence)) + }`, delAddr, valSrcAddr, valDstAddr, amount, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", fmt.Sprintf("/stake/delegators/%s/delegations", delAddr), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -1157,9 +1218,72 @@ func getValidator(t *testing.T, port string, validatorAddr sdk.ValAddress) stake return 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) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var delegations []stake.Delegation + err := cdc.UnmarshalJSON([]byte(body), &delegations) + require.Nil(t, err) + + return delegations +} + +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) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var ubds []stake.UnbondingDelegation + err := cdc.UnmarshalJSON([]byte(body), &ubds) + require.Nil(t, err) + + return ubds +} + +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 +} + // ============= Governance Module ================ -func getProposal(t *testing.T, port string, proposalID int64) gov.Proposal { +func getDepositParam(t *testing.T, port string) gov.DepositParams { + res, body := Request(t, port, "GET", "/gov/parameters/deposit", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var depositParams gov.DepositParams + err := cdc.UnmarshalJSON([]byte(body), &depositParams) + require.Nil(t, err) + return depositParams +} + +func getVotingParam(t *testing.T, port string) gov.VotingParams { + res, body := Request(t, port, "GET", "/gov/parameters/voting", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var votingParams gov.VotingParams + err := cdc.UnmarshalJSON([]byte(body), &votingParams) + require.Nil(t, err) + return votingParams +} + +func getTallyingParam(t *testing.T, port string) gov.TallyParams { + res, body := Request(t, port, "GET", "/gov/parameters/tallying", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var tallyParams gov.TallyParams + err := cdc.UnmarshalJSON([]byte(body), &tallyParams) + require.Nil(t, err) + return tallyParams +} + +func getProposal(t *testing.T, port string, proposalID uint64) gov.Proposal { res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d", proposalID), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var proposal gov.Proposal @@ -1168,7 +1292,16 @@ func getProposal(t *testing.T, port string, proposalID int64) gov.Proposal { return proposal } -func getDeposit(t *testing.T, port string, proposalID int64, depositerAddr sdk.AccAddress) gov.Deposit { +func getDeposits(t *testing.T, port string, proposalID uint64) []gov.Deposit { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var deposits []gov.Deposit + err := cdc.UnmarshalJSON([]byte(body), &deposits) + require.Nil(t, err) + return deposits +} + +func getDeposit(t *testing.T, port string, proposalID uint64, depositerAddr sdk.AccAddress) gov.Deposit { res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/deposits/%s", proposalID, depositerAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var deposit gov.Deposit @@ -1177,7 +1310,7 @@ func getDeposit(t *testing.T, port string, proposalID int64, depositerAddr sdk.A return deposit } -func getVote(t *testing.T, port string, proposalID int64, voterAddr sdk.AccAddress) gov.Vote { +func getVote(t *testing.T, port string, proposalID uint64, voterAddr sdk.AccAddress) gov.Vote { res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/votes/%s", proposalID, voterAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var vote gov.Vote @@ -1186,7 +1319,7 @@ func getVote(t *testing.T, port string, proposalID int64, voterAddr sdk.AccAddre return vote } -func getVotes(t *testing.T, port string, proposalID int64) []gov.Vote { +func getVotes(t *testing.T, port string, proposalID uint64) []gov.Vote { res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/votes", proposalID), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var votes []gov.Vote @@ -1245,7 +1378,7 @@ func getProposalsFilterStatus(t *testing.T, port string, status gov.ProposalStat return proposals } -func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress) (resultTx ctypes.ResultBroadcastTxCommit) { +func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, amount int64) (resultTx ctypes.ResultBroadcastTxCommit) { acc := getAccount(t, port, proposerAddr) accnum := acc.GetAccountNumber() @@ -1259,7 +1392,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA "description": "test", "proposal_type": "Text", "proposer": "%s", - "initial_deposit": [{ "denom": "steak", "amount": "5" }], + "initial_deposit": [{ "denom": "%s", "amount": "%d" }], "base_req": { "name": "%s", "password": "%s", @@ -1267,7 +1400,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA "account_number":"%d", "sequence":"%d" } - }`, proposerAddr, name, password, chainID, accnum, sequence)) + }`, proposerAddr, stakeTypes.DefaultBondDenom, amount, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", "/gov/proposals", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -1278,7 +1411,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA return results } -func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID int64) (resultTx ctypes.ResultBroadcastTxCommit) { +func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64, amount int64) (resultTx ctypes.ResultBroadcastTxCommit) { acc := getAccount(t, port, proposerAddr) accnum := acc.GetAccountNumber() @@ -1289,7 +1422,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk // deposit on proposal jsonStr := []byte(fmt.Sprintf(`{ "depositer": "%s", - "amount": [{ "denom": "steak", "amount": "5" }], + "amount": [{ "denom": "%s", "amount": "%d" }], "base_req": { "name": "%s", "password": "%s", @@ -1297,7 +1430,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk "account_number":"%d", "sequence": "%d" } - }`, proposerAddr, name, password, chainID, accnum, sequence)) + }`, proposerAddr, stakeTypes.DefaultBondDenom, amount, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -1308,7 +1441,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk return results } -func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID int64) (resultTx ctypes.ResultBroadcastTxCommit) { +func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64) (resultTx ctypes.ResultBroadcastTxCommit) { // get the account to get the sequence acc := getAccount(t, port, proposerAddr) accnum := acc.GetAccountNumber() diff --git a/client/lcd/root.go b/client/lcd/root.go index 475186ed0a6c..8366b6114c52 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -2,6 +2,7 @@ package lcd import ( "errors" + "fmt" "net" "net/http" "os" @@ -12,6 +13,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest" @@ -21,7 +23,6 @@ import ( "github.com/rakyll/statik/fs" "github.com/spf13/cobra" "github.com/spf13/viper" - cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" tmserver "github.com/tendermint/tendermint/rpc/lib/server" ) @@ -47,24 +48,38 @@ func ServeCommand(cdc *codec.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) (err error) { listenAddr := viper.GetString(flagListenAddr) handler := createHandler(cdc) + registerSwaggerUI(handler) + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "rest-server") maxOpen := viper.GetInt(flagMaxOpenConnections) sslHosts := viper.GetString(flagSSLHosts) certFile := viper.GetString(flagSSLCertFile) keyFile := viper.GetString(flagSSLKeyFile) - cleanupFunc := func() {} var listener net.Listener var fingerprint string + + server.TrapSignal(func() { + err := listener.Close() + logger.Error("error closing listener", "err", err) + }) + + var cleanupFunc func() + + // TODO: re-enable insecure mode once #2715 has been addressed if viper.GetBool(flagInsecure) { - listener, err = tmserver.StartHTTPServer( - listenAddr, handler, logger, - tmserver.Config{MaxOpenConnections: maxOpen}, + fmt.Println( + "Insecure mode is temporarily disabled, please locally generate an " + + "SSL certificate to test. Support will be re-enabled soon!", ) - if err != nil { - return - } + // listener, err = tmserver.StartHTTPServer( + // listenAddr, handler, logger, + // tmserver.Config{MaxOpenConnections: maxOpen}, + // ) + // if err != nil { + // return + // } } else { if certFile != "" { // validateCertKeyFiles() is needed to work around tendermint/tendermint#2460 @@ -72,6 +87,7 @@ func ServeCommand(cdc *codec.Codec) *cobra.Command { if err != nil { return err } + // cert/key pair is provided, read the fingerprint fingerprint, err = fingerprintFromFile(certFile) if err != nil { @@ -83,12 +99,15 @@ func ServeCommand(cdc *codec.Codec) *cobra.Command { if err != nil { return err } + cleanupFunc = func() { os.Remove(certFile) os.Remove(keyFile) } + defer cleanupFunc() } + listener, err = tmserver.StartHTTPAndTLSServer( listenAddr, handler, certFile, keyFile, @@ -98,16 +117,12 @@ func ServeCommand(cdc *codec.Codec) *cobra.Command { if err != nil { return } + logger.Info(fingerprint) + logger.Info("REST server started") } - logger.Info("REST server started") - // wait forever and cleanup - cmn.TrapSignal(func() { - defer cleanupFunc() - err := listener.Close() - logger.Error("error closing listener", "err", err) - }) + // logger.Info("REST server started") return nil }, @@ -124,6 +139,7 @@ func ServeCommand(cdc *codec.Codec) *cobra.Command { cmd.Flags().Int(flagMaxOpenConnections, 1000, "The number of maximum open connections") cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") cmd.Flags().Bool(client.FlagIndentResponse, false, "Add indent to JSON response") + viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/lcd/swagger-ui/swagger.yaml index b7fb70ff3398..920194ffa49f 100644 --- a/client/lcd/swagger-ui/swagger.yaml +++ b/client/lcd/swagger-ui/swagger.yaml @@ -2,15 +2,23 @@ swagger: '2.0' info: version: 1.1.0 - title: Gaia-Lite (former LCD) to interface with Cosmos BaseServer via REST - description: Specification for Gaia-lite provided by `gaiacli rest-server` + title: Gaia-Lite for Cosmos + description: A REST interface for state queries, transaction generation, signing, and broadcast. tags: - name: ICS0 description: Tendermint APIs, such as query blocks, transactions and validatorset +- name: ICS1 + description: Key management APIs - name: ICS20 - description: Create and sign a send tx + description: Create, sign and broadcast transactions +- name: ICS21 + description: Stake module APIs +- name: ICS22 + description: Governance module APIs +- name: ICS23 + description: Slashing module APIs - name: version - description: Information about the app version + description: Query app version schemes: - https securityDefinitions: @@ -25,16 +33,18 @@ paths: description: Get the version of gaia-lite running locally to compare against expected responses: 200: - description: Plaintext version i.e. "v0.5.0" + description: Plaintext version i.e. "v0.25.0" /node_version: get: summary: Version of the connected node tags: - - ICS0 + - version description: Get the version of the SDK running on the connected node to compare against expected responses: 200: - description: Plaintext version i.e. "v0.5.0" + description: Plaintext version i.e. "v0.25.0" + 500: + description: failed to query node version /node_info: get: description: Information about the connected node @@ -71,6 +81,8 @@ paths: type: array items: type: string + 500: + description: Failed to query node status /syncing: get: summary: Syncing state of node @@ -80,6 +92,8 @@ paths: responses: 200: description: '"true" or "false"' + 500: + description: Server internal error /blocks/latest: get: summary: Get the latest block @@ -91,7 +105,9 @@ paths: 200: description: The latest block schema: - $ref: "#/definitions/QueryBlock" + $ref: "#/definitions/BlockQuery" + 500: + description: Server internal error /blocks/{height}: get: summary: Get a block at a certain height @@ -109,9 +125,13 @@ paths: 200: description: The block at a specific height schema: - $ref: "#/definitions/QueryBlock" + $ref: "#/definitions/BlockQuery" 404: - description: Block at height is not available + description: Request block height doesn't + 400: + description: Invalid height + 500: + description: Server internal error /validatorsets/latest: get: summary: Get the latest validator set @@ -130,7 +150,9 @@ paths: validators: type: array items: - $ref: "#/definitions/Validator" + $ref: "#/definitions/TendermintValidator" + 500: + description: Server internal error /validatorsets/{height}: get: summary: Get a validator set a certain height @@ -155,9 +177,13 @@ paths: validators: type: array items: - $ref: "#/definitions/Validator" + $ref: "#/definitions/TendermintValidator" 404: description: Block at height not available + 400: + description: Invalid height + 500: + description: Server internal error /txs/{hash}: get: summary: Get a Tx by hash @@ -176,8 +202,8 @@ paths: description: Tx with the provided hash schema: $ref: "#/definitions/TxQuery" - 404: - description: Tx not available for provided hash + 500: + description: Internal Server Error /txs: get: tags: @@ -207,8 +233,10 @@ paths: type: array items: $ref: "#/definitions/TxQuery" - 404: - description: Pagination is out of bounds + 400: + description: Invalid search tags + 500: + description: Internal Server Error post: tags: - ICS0 @@ -224,12 +252,20 @@ paths: description: Build a StdTx transaction and serilize it to a byte array with amino, then the `"tx"` field in the post body will be the base64 encoding of the byte array. The supported return types includes `"block"`(return after tx commit), `"sync"`(return afer CheckTx) and `"async"`(return right away). required: true schema: - $ref: "#/definitions/TendertmintTx" + type: object + properties: + tx: + type: string + return: + type: string + example: block responses: 200: description: Broadcast tx result schema: $ref: "#/definitions/BroadcastTxCommitResult" + 500: + description: Internal Server Error /tx/sign: post: tags: @@ -246,14 +282,36 @@ paths: description: sign tx required: true schema: - $ref: "#/definitions/TxSign" + type: object + properties: + 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 responses: 200: description: The signed Tx schema: $ref: "#/definitions/StdTx" + 400: + description: The Tx was malformated or key doesn't exist 401: - description: Account name and/or password where wrong + description: Key password is wrong + 500: + description: Server internal error /tx/broadcast: post: tags: @@ -270,7 +328,10 @@ paths: description: broadcast tx required: true schema: - $ref: "#/definitions/TxBroadcast" + type: object + properties: + tx: + $ref: "#/definitions/StdTx" responses: 202: description: Tx was send and will probably be added to the next block @@ -278,6 +339,8 @@ paths: $ref: "#/definitions/BroadcastTxCommitResult" 400: description: The Tx was malformated + 500: + description: Server internal error /bank/balances/{address}: get: summary: Get the account balances @@ -299,7 +362,9 @@ paths: items: $ref: "#/definitions/Coin" 204: - description: There is no data for the requested account. This is not a 404 as the account might exist, just does not hold data. + description: There is no data for the requested account + 500: + description: Server internal error /bank/accounts/{address}/transfers: post: summary: Send coins (build -> sign -> send) @@ -345,7 +410,11 @@ paths: schema: $ref: "#/definitions/BroadcastTxCommitResult" 400: - description: The Tx was malformated + description: Invalid request + 401: + description: Key password is wrong + 500: + description: Server internal error /keys: get: summary: List of accounts stored locally @@ -359,7 +428,9 @@ paths: schema: type: array items: - $ref: '#/definitions/Account' + $ref: '#/definitions/KeyOutput' + 500: + description: Server internal error post: summary: Create a new account locally tags: @@ -389,7 +460,13 @@ paths: 200: description: Returns account information of the created key schema: - $ref: "#/definitions/Account" + $ref: "#/definitions/KeyOutput" + 400: + description: Invalid request + 409: + description: Key name confliction + 500: + description: Server internal error /keys/seed: get: summary: Create a new seed to create a new account with @@ -397,9 +474,10 @@ paths: - ICS1 responses: 200: - description: 16 word Seed + description: 24 word Seed schema: type: string + example: blossom pool issue kidney elevator blame furnace winter account merry vessel security depend exact travel bargain problem jelly rural net again mask roast chest /keys/{name}/recover: post: summary: Recover a account from a seed @@ -432,7 +510,13 @@ paths: 200: description: Returns account information of the recovered key schema: - $ref: "#/definitions/Account" + $ref: "#/definitions/KeyOutput" + 400: + description: Invalid request + 409: + description: Key name confliction + 500: + description: Server internal error /keys/{name}: parameters: - in: path @@ -450,9 +534,9 @@ paths: 200: description: Locally stored account schema: - $ref: "#/definitions/Account" + $ref: "#/definitions/KeyOutput" 404: - description: Account is not available + description: Key doesn't exist put: summary: Update the password for this account in the KMS tags: @@ -477,9 +561,9 @@ paths: 200: description: Updated password 401: - description: Password is wrong + description: Key password is wrong 404: - description: Account is not available + description: Key doesn't exist delete: summary: Remove an account tags: @@ -501,9 +585,9 @@ paths: 200: description: Removed account 401: - description: Password is wrong + description: Key password is wrong 404: - description: Account is not available + description: Key doesn't exist /auth/accounts/{address}: get: summary: Get the account information on blockchain @@ -521,190 +605,1080 @@ paths: 200: description: Account information on the blockchain schema: - $ref: "#/definitions/AccountQueryResponse" - 404: - description: Account is not available - -definitions: - CheckTxResult: - type: object - properties: - code: - type: integer - data: - type: string - gas_used: - type: integer - gas_wanted: - type: integer - info: - type: string - log: - type: string + type: object + properties: + type: + type: string + value: + type: object + properties: + account_number: + type: string + address: + type: string + coins: + type: array + items: + $ref: "#/definitions/Coin" + public_key: + type: string + sequence: + type: string + 204: + description: No content about this account address + 500: + description: Server internel error + /stake/delegators/{delegatorAddr}/delegations: + parameters: + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string + post: + summary: Submit delegation + parameters: + - in: query + name: simulate + description: if true, ignore the gas field and perform a simulation of a transaction, but don't broadcast it + required: false + type: boolean + - in: query + name: generate_only + description: if true, build an unsigned transaction and write it back + required: false + type: boolean + - in: body + name: delegation + description: The password of the account to remove from the KMS + schema: + type: object + properties: + base_req: + "$ref": "#/definitions/BaseReq" + delegations: + type: array + items: + type: object + properties: + delegator_addr: + $ref: "#/definitions/Address" + validator_addr: + $ref: "#/definitions/ValidatorAddress" + delegation: + $ref: "#/definitions/Coin" + begin_unbondings: + type: array + items: + type: object + properties: + delegator_addr: + $ref: "#/definitions/Address" + validator_addr: + $ref: "#/definitions/ValidatorAddress" + shares: + type: string + example: "100" + begin_redelegates: + type: array + items: + type: object + properties: + delegator_addr: + $ref: "#/definitions/Address" + validator_src_addr: + $ref: "#/definitions/ValidatorAddress" + validator_dst_addr: + $ref: "#/definitions/ValidatorAddress" + shares: + type: string + example: "100" tags: - type: array - items: - "$ref": "#/definitions/KVPair" - example: - code: 0 - data: data - log: log - gas_used: 5000 - gas_wanted: 10000 - info: info + - ICS21 + consumes: + - application/json + produces: + - application/json + responses: + 200: + description: OK + schema: + $ref: "#/definitions/BroadcastTxCommitResult" + 400: + description: Invalid delegator address or delegation body + 401: + description: Key password is wrong + 500: + description: Internal Server Error + get: + summary: Get all delegations from a delegator tags: - - '' - - '' - DeliverTxResult: - type: object - properties: - code: - type: integer - data: - type: string - gas_used: - type: integer - gas_wanted: - type: integer - info: - type: string - log: - type: string + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + type: array + items: + type: object + "$ref": "#/definitions/Delegation" + 400: + description: Invalid delegator address + 500: + description: Internal Server Error + /stake/delegators/{delegatorAddr}/unbonding_delegations: + parameters: + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string + get: + summary: Get all unbonding delegations from a delegator tags: - type: array - items: - "$ref": "#/definitions/KVPair" - example: - code: 5 - data: data - log: log - gas_used: 5000 - gas_wanted: 10000 - info: info + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + type: array + items: + type: object + "$ref": "#/definitions/UnbondingDelegation" + 400: + description: Invalid delegator address + 500: + description: Internal Server Error + /stake/delegators/{delegatorAddr}/redelegations: + parameters: + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string + get: + summary: Get all redelegations from a delegator tags: - - '' - - '' - BroadcastTxCommitResult: - type: object - properties: - check_tx: - $ref: "#/definitions/CheckTxResult" - deliver_tx: - $ref: "#/definitions/DeliverTxResult" - hash: - $ref: "#/definitions/Hash" - height: - type: integer - KVPair: - type: object - properties: - key: - type: string - value: - type: string - Fee: - type: object - properties: - gas: - type: string - amount: - type: array - items: - $ref: "#/definitions/Coin" - Msg: - type: string - Address: - type: string - description: bech32 encoded addres - example: cosmoszgnkwr7eyyv643dllwfpdwensmgdtz89yu73zq - ValidatorAddress: - type: string - description: bech32 encoded addres - example: cosmosvaloper:zgnkwr7eyyv643dllwfpdwensmgdtz89yu73zq - PubKey: - type: object - properties: - type: - type: string - example: "tendermint/PubKeySecp256k1" - value: - type: string - example: "Avz04VhtKJh8ACCVzlI8aTosGy0ikFXKIVHQ3jKMrosH" - ValidatorPubKey: - type: string - description: bech32 encoded public key - example: cosmosvalconspub:zgnkwr7eyyv643dllwfpdwensmgdtz89yu73zq - Coin: - type: object - properties: - denom: - type: string - example: steak - amount: - type: string - example: "50" - Hash: - type: string - example: EE5F3404034C524501629B56E0DDC38FAD651F04 - Result: - type: object - properties: - log: - type: string - gas_wanted: - type: string - example: "0" - gas_used: - type: string - example: "0" + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + type: array + items: + type: object + "$ref": "#/definitions/Redelegation" + 400: + description: Invalid delegator address + 500: + description: Internal Server Error + /stake/delegators/{delegatorAddr}/validators: + parameters: + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string + get: + summary: Query all validators that a delegator is bonded to tags: - type: array - items: - $ref: "#/definitions/KVPair" - TxQuery: - type: object - properties: - hash: - type: string - height: - type: number - tx: - $ref: "#/definitions/StdTx" - result: - $ref: "#/definitions/Result" - TendertmintTx: - type: object - properties: - tx: - type: string - return: - type: string - example: block - TxBroadcast: - type: object - properties: - tx: - $ref: "#/definitions/StdTx" - TxSign: - type: object - properties: - tx: - $ref: "#/definitions/StdTx" - name: - type: string - password: + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + type: array + items: + $ref: "#/definitions/Validator" + 400: + description: Invalid delegator address + 500: + description: Internal Server Error + /stake/delegators/{delegatorAddr}/validators/{validatorAddr}: + parameters: + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string + - in: path + name: validatorAddr + description: Bech32 ValAddress of Delegator + required: true + type: string + get: + summary: Query a validator that a delegator is bonded to + tags: + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + $ref: "#/definitions/Validator" + 400: + description: Invalid delegator address or validator address + 500: + description: Internal Server Error + /stake/delegators/{delegatorAddr}/txs: + parameters: + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string + get: + summary: Get all staking txs (i.e msgs) from a delegator + tags: + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + type: array + items: + $ref: "#/definitions/TxQuery" + 204: + description: No staking transaction about this delegator address + 400: + description: Invalid delegator address + 500: + description: Internal Server Error + /stake/delegators/{delegatorAddr}/delegations/{validatorAddr}: + parameters: + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string + get: + summary: Query the current delegation between a delegator and a validator + tags: + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + $ref: "#/definitions/Delegation" + 400: + description: Invalid delegator address or validator address + 500: + description: Internal Server Error + /stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}: + parameters: + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string + get: + summary: Query all unbonding delegations between a delegator and a validator + tags: + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + type: array + items: + type: object + "$ref": "#/definitions/UnbondingDelegation" + 400: + description: Invalid delegator address or validator address + 500: + description: Internal Server Error + /stake/validators: + get: + summary: Get all validator candidates + tags: + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + type: array + items: + $ref: "#/definitions/Validator" + 500: + description: Internal Server Error + /stake/validators/{validatorAddr}: + parameters: + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string + get: + summary: Query the information from a single validator + tags: + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + $ref: "#/definitions/Validator" + 400: + description: Invalid validator address + 500: + description: Internal Server Error + /stake/validators/{validatorAddr}/delegations: + parameters: + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string + get: + summary: Get all delegations from a validator + tags: + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + type: array + items: + $ref: "#/definitions/Delegation" + 400: + description: Invalid validator address + 500: + description: Internal Server Error + /stake/validators/{validatorAddr}/unbonding_delegations: + parameters: + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string + get: + summary: Get all unbonding delegations from a validator + tags: + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + type: array + items: + $ref: "#/definitions/UnbondingDelegation" + 400: + 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: + get: + summary: Get the current state of the staking pool + tags: + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + type: object + properties: + loose_tokens: + type: string + bonded_tokens: + type: string + inflation_last_time: + type: string + inflation: + type: string + date_last_commission_reset: + type: string + prev_bonded_shares: + type: string + 500: + description: Internal Server Error + /stake/parameters: + get: + summary: Get the current staking parameter values + tags: + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + type: object + properties: + inflation_rate_change: + type: string + inflation_max: + type: string + inflation_min: + type: string + goal_bonded: + type: string + unbonding_time: + type: string + max_validators: + type: integer + bond_denom: + type: string + 500: + description: Internal Server Error + /slashing/validators/{validatorPubKey}/signing_info: + get: + summary: Get sign info of given validator + description: Get sign info of given validator + produces: + - application/json + tags: + - ICS23 + parameters: + - type: string + description: Bech32 validator public key + name: validatorPubKey + required: true + in: path + responses: + 200: + description: OK + schema: + type: object + properties: + start_height: + type: string + index_offset: + type: string + jailed_until: + type: string + missed_blocks_counter: + type: string + 204: + description: No sign info of this validator + 400: + description: Invalid validator public key + 500: + description: Internal Server Error + /slashing/validators/{validatorAddr}/unjail: + post: + summary: Unjail a jailed validator + description: Send transaction to unjail a jailed validator + consumes: + - application/json + produces: + - application/json + tags: + - ICS23 + parameters: + - in: query + name: simulate + description: if true, ignore the gas field and perform a simulation of a transaction, but don't broadcast it + required: false + type: boolean + - in: query + name: generate_only + description: if true, build an unsigned transaction and write it back + required: false + type: boolean + - type: string + description: Bech32 validator address + name: validatorAddr + required: true + in: path + - description: '' + name: UnjailBody + in: body + required: true + schema: + type: object + properties: + base_req: + "$ref": "#/definitions/BaseReq" + responses: + 200: + description: OK + schema: + $ref: "#/definitions/BroadcastTxCommitResult" + 400: + description: Invalid validator address or base_req + 401: + description: Key password is wrong + 500: + description: Internal Server Error + /gov/proposals: + post: + summary: Submit a proposal + description: Send transaction to submit a proposal + consumes: + - application/json + produces: + - application/json + tags: + - ICS22 + parameters: + - in: query + name: simulate + description: if true, ignore the gas field and perform a simulation of a transaction, but don't broadcast it + required: false + type: boolean + - in: query + name: generate_only + description: if true, build an unsigned transaction and write it back + required: false + type: boolean + - description: valid value of `"proposal_type"` can be `"text"`, `"parameter_change"`, `"software_upgrade"` + name: post_proposal_body + in: body + required: true + schema: + type: object + properties: + base_req: + "$ref": "#/definitions/BaseReq" + title: + type: string + description: + type: string + proposal_type: + type: string + example: "text" + proposer: + "$ref": "#/definitions/Address" + initial_deposit: + type: array + items: + $ref: "#/definitions/Coin" + responses: + 200: + description: OK + schema: + "$ref": "#/definitions/BroadcastTxCommitResult" + 400: + description: Invalid proposal body + 401: + description: Key password is wrong + 500: + description: Internal Server Error + get: + summary: Query proposals + description: Query proposals information with parameters + produces: + - application/json + tags: + - ICS22 + parameters: + - in: query + name: voter + description: voter address + required: false + type: string + - in: query + name: depositer + description: depositer address + required: false + type: string + - in: query + name: status + description: proposal status, valid values can be `"deposit_period"`, `"voting_period"`, `"passed"`, `"rejected"` + required: false + type: string + responses: + 200: + description: OK + schema: + type: array + items: + "$ref": "#/definitions/TextProposal" + 400: + description: Invalid query parameters + 500: + description: Internal Server Error + /gov/proposals/{proposalId}/deposits: + post: + summary: Deposit tokens to a proposal + description: Send transaction to deposit tokens to a proposal + consumes: + - application/json + produces: + - application/json + tags: + - ICS22 + parameters: + - in: query + name: simulate + description: if true, ignore the gas field and perform a simulation of a transaction, but don't broadcast it + required: false + type: boolean + - in: query + name: generate_only + description: if true, build an unsigned transaction and write it back + required: false + type: boolean + - type: string + description: proposal id + name: proposalId + required: true + in: path + - description: '' + name: post_deposit_body + in: body + required: true + schema: + type: object + properties: + base_req: + "$ref": "#/definitions/BaseReq" + depositer: + "$ref": "#/definitions/Address" + amount: + type: array + items: + $ref: "#/definitions/Coin" + responses: + 200: + description: OK + schema: + "$ref": "#/definitions/BroadcastTxCommitResult" + 400: + description: Invalid proposal id or deposit body + 401: + description: Key password is wrong + 500: + description: Internal Server Error + get: + summary: Query deposits + description: Query deposits by proposalId + produces: + - application/json + tags: + - ICS22 + parameters: + - type: string + name: proposalId + required: true + in: path + responses: + 200: + description: OK + schema: + type: array + items: + "$ref": "#/definitions/Deposit" + 400: + description: Invalid proposal id + 500: + description: Internal Server Error + /gov/proposals/{proposalId}/votes: + post: + summary: Vote a proposal + description: Send transaction to vote a proposal + consumes: + - application/json + produces: + - application/json + tags: + - ICS22 + parameters: + - in: query + name: simulate + description: if true, ignore the gas field and perform a simulation of a transaction, but don't broadcast it + required: false + type: boolean + - in: query + name: generate_only + description: if true, build an unsigned transaction and write it back + required: false + type: boolean + - type: string + description: proposal id + name: proposalId + required: true + in: path + - description: valid value of `"option"` field can be `"yes"`, `"no"`, `"no_with_veto"` and `"abstain"` + name: post_vote_body + in: body + required: true + schema: + type: object + properties: + base_req: + "$ref": "#/definitions/BaseReq" + voter: + "$ref": "#/definitions/Address" + option: + type: string + example: "yes" + responses: + 200: + description: OK + schema: + "$ref": "#/definitions/BroadcastTxCommitResult" + 400: + description: Invalid proposal id or vote body + 401: + description: Key password is wrong + 500: + description: Internal Server Error + get: + summary: Query voters + description: Query voters information by proposalId + produces: + - application/json + tags: + - ICS22 + parameters: + - type: string + description: proposal id + name: proposalId + required: true + in: path + responses: + 200: + description: OK + schema: + type: array + items: + "$ref": "#/definitions/Vote" + 400: + description: Invalid proposal id + 500: + description: Internal Server Error + /gov/proposals/{proposalId}: + get: + summary: Query a proposal + description: Query a proposal by id + produces: + - application/json + tags: + - ICS22 + parameters: + - type: string + name: proposalId + required: true + in: path + responses: + 200: + description: OK + schema: + "$ref": "#/definitions/TextProposal" + 400: + description: Invalid proposal id + 500: + description: Internal Server Error + /gov/proposals/{proposalId}/deposits/{depositer}: + get: + summary: Query deposit + description: Query deposit by proposalId and depositer address + produces: + - application/json + tags: + - ICS22 + parameters: + - type: string + description: proposal id + name: proposalId + required: true + in: path + - type: string + description: Bech32 depositer address + name: depositer + required: true + in: path + responses: + 200: + description: OK + schema: + $ref: "#/definitions/Deposit" + 400: + description: Invalid proposal id or depositer address + 404: + description: Found no deposit + 500: + description: Internal Server Error + /gov/proposals/{proposalId}/votes/{voter}: + get: + summary: Query vote + description: Query vote information by proposal Id and voter address + produces: + - application/json + tags: + - ICS22 + parameters: + - type: string + description: proposal id + name: proposalId + required: true + in: path + - type: string + description: Bech32 voter address + name: voter + required: true + in: path + responses: + 200: + description: OK + schema: + $ref: "#/definitions/Vote" + 400: + description: Invalid proposal id or voter address + 404: + description: Found no vote + 500: + description: Internal Server Error + /gov/parameters/deposit: + get: + summary: Query governance deposit parameters + description: Query governance deposit parameters. The max_deposit_period units are in nanoseconds. + produces: + - application/json + tags: + - ICS22 + responses: + 200: + description: OK + schema: + type: object + properties: + min_deposit: + type: array + items: + $ref: "#/definitions/Coin" + max_deposit_period: + type: string + example: "86400000000000" + 400: + description: is not a valid query request path + 404: + description: Found no deposit parameters + 500: + description: Internal Server Error + /gov/parameters/tallying: + get: + summary: Query governance tally parameters + description: Query governance tally parameters + produces: + - application/json + tags: + - ICS22 + responses: + 200: + description: OK + schema: + properties: + threshold: + type: string + example: "0.5000000000" + veto: + type: string + example: "0.3340000000" + governance_penalty: + type: string + example: "0.0100000000" + 400: + description: is not a valid query request path + 404: + description: Found no tally parameters + 500: + description: Internal Server Error + /gov/parameters/voting: + get: + summary: Query governance voting parameters + description: Query governance voting parameters. The voting_period units are in nanoseconds. + produces: + - application/json + tags: + - ICS22 + responses: + 200: + description: OK + schema: + properties: + voting_period: + type: string + example: "86400000000000" + 400: + description: is not a valid query request path + 404: + description: Found no voting parameters + 500: + description: Internal Server Error + +definitions: + CheckTxResult: + type: object + properties: + code: + type: integer + data: type: string - chain_id: + gas_used: + type: integer + gas_wanted: + type: integer + info: type: string - account_number: + log: type: string - example: "0" - sequence: + tags: + type: array + items: + "$ref": "#/definitions/KVPair" + example: + code: 0 + data: data + log: log + gas_used: 5000 + gas_wanted: 10000 + info: info + tags: + - '' + - '' + DeliverTxResult: + type: object + properties: + code: + type: integer + data: type: string - example: "0" - append_sig: - type: boolean - example: true + gas_used: + type: integer + gas_wanted: + type: integer + info: + type: string + log: + type: string + tags: + type: array + items: + "$ref": "#/definitions/KVPair" + example: + code: 5 + data: data + log: log + gas_used: 5000 + gas_wanted: 10000 + info: info + tags: + - '' + - '' + BroadcastTxCommitResult: + type: object + properties: + check_tx: + $ref: "#/definitions/CheckTxResult" + deliver_tx: + $ref: "#/definitions/DeliverTxResult" + hash: + $ref: "#/definitions/Hash" + height: + type: integer + KVPair: + type: object + properties: + key: + type: string + value: + type: string + Msg: + type: string + Address: + type: string + description: bech32 encoded address + example: cosmos1depk54cuajgkzea6zpgkq36tnjwdzv4afc3d27 + ValidatorAddress: + type: string + description: bech32 encoded address + example: cosmosvaloper1depk54cuajgkzea6zpgkq36tnjwdzv4avv9cxd + Coin: + type: object + properties: + denom: + type: string + example: steak + amount: + type: string + example: "50" + Hash: + type: string + example: EE5F3404034C524501629B56E0DDC38FAD651F04 + TxQuery: + type: object + properties: + hash: + type: string + height: + type: number + tx: + $ref: "#/definitions/StdTx" + result: + type: object + properties: + log: + type: string + gas_wanted: + type: string + example: "0" + gas_used: + type: string + example: "0" + tags: + type: array + items: + $ref: "#/definitions/KVPair" StdTx: type: object properties: @@ -713,7 +1687,14 @@ definitions: items: $ref: "#/definitions/Msg" fee: - $ref: "#/definitions/Fee" + type: object + properties: + gas: + type: string + amount: + type: array + items: + $ref: "#/definitions/Coin" memo: type: string signature: @@ -723,51 +1704,37 @@ definitions: type: string example: MEUCIQD02fsDPra8MtbRsyB1w7bqTM55Wu138zQbFcWx4+CFyAIge5WNPfKIuvzBZ69MyqHsqD8S1IwiEp+iUb6VSdtlpgY= pub_key: - $ref: "#/definitions/PubKey" + type: object + properties: + type: + type: string + example: "tendermint/PubKeySecp256k1" + value: + type: string + example: "Avz04VhtKJh8ACCVzlI8aTosGy0ikFXKIVHQ3jKMrosH" account_number: type: string example: "0" sequence: type: string example: "0" - Account: + KeyOutput: type: object properties: name: type: string example: Main Account address: - $ref: "#/definitions/Address" + type: string + example: cosmos1depk54cuajgkzea6zpgkq36tnjwdzv4afc3d27 pub_key: type: string - example: "cosmospub1addwnpepqfgv3pakxazq2fgs8tmmhmzsrs94fptl7kyztyxprjpf0mkus3h7cxqe70s" + example: "cosmospub1addwnpepqtqc88gfdxyzcdm2eqr3tnlnxnelyqehy8k95qzgwdz5zccdft9rq5q7wj2" type: type: string example: local seed: type: string - AccountInfo: - type: object - properties: - account_number: - type: string - address: - type: string - coins: - type: array - items: - $ref: "#/definitions/Coin" - public_key: - type: string - sequence: - type: string - AccountQueryResponse: - type: object - properties: - type: - type: string - value: - $ref: "#/definitions/AccountInfo" BlockID: type: object properties: @@ -807,6 +1774,8 @@ definitions: $ref: "#/definitions/Hash" validators_hash: $ref: "#/definitions/Hash" + next_validators_hash: + $ref: "#/definitions/Hash" consensus_hash: $ref: "#/definitions/Hash" app_hash: @@ -815,6 +1784,8 @@ definitions: $ref: "#/definitions/Hash" evidence_hash: $ref: "#/definitions/Hash" + proposer_address: + $ref: "#/definitions/Address" Block: type: object properties: @@ -860,18 +1831,16 @@ definitions: signature: type: string example: '7uTC74QlknqYWEwg7Vn6M8Om7FuZ0EO4bjvuj6rwH1mTUJrRuMMZvAAqT9VjNgP0RA/TDp6u/92AqrZfXJSpBQ==' - BlockMeta: - type: object - properties: - header: - $ref: "#/definitions/BlockHeader" - block_id: - $ref: "#/definitions/BlockID" - QueryBlock: + BlockQuery: type: object properties: block_meta: - $ref: "#/definitions/BlockMeta" + type: object + properties: + header: + $ref: "#/definitions/BlockHeader" + block_id: + $ref: "#/definitions/BlockID" block: $ref: "#/definitions/Block" BaseReq: @@ -881,6 +1850,7 @@ definitions: type: string password: type: string + example: "12345678" chain_id: type: string account_number: @@ -895,16 +1865,170 @@ definitions: gas_adjustment: type: string example: "1.2" - Validator: + TendermintValidator: type: object properties: address: $ref: '#/definitions/ValidatorAddress' pub_key: - $ref: "#/definitions/ValidatorPubKey" + type: string + example: cosmosvalconspub1zcjduepq7sjfglw7ra4mjxpw4ph7dtdhdheh7nz8dfgl6t8u2n5szuuql9mqsrwquu power: - type: number - example: 1000 + type: string + example: "1000" accum: - type: number - example: 1000 + type: string + example: "1000" + TextProposal: + type: object + properties: + proposal_id: + type: integer + title: + type: string + description: + type: string + proposal_type: + type: string + proposal_status: + type: string + tally_result: + type: object + properties: + yes: + type: string + abstain: + type: string + no: + type: string + no_with_veto: + type: string + submit_time: + type: string + total_deposit: + type: array + items: + "$ref": "#/definitions/Coin" + voting_start_time: + type: string + Deposit: + type: object + properties: + amount: + type: array + items: + "$ref": "#/definitions/Coin" + proposal_id: + type: integer + depositer: + "$ref": "#/definitions/Address" + Vote: + type: object + properties: + voter: + type: string + proposal_id: + type: integer + option: + type: string + Validator: + type: object + properties: + operator_address: + $ref: '#/definitions/ValidatorAddress' + consensus_pubkey: + type: string + example: cosmosvalconspub1zcjduepq7sjfglw7ra4mjxpw4ph7dtdhdheh7nz8dfgl6t8u2n5szuuql9mqsrwquu + jailed: + type: boolean + status: + type: integer + tokens: + type: string + delegator_shares: + type: string + description: + type: object + properties: + moniker: + type: string + identity: + type: string + website: + type: string + details: + type: string + bond_height: + type: string + example: '0' + bond_intra_tx_counter: + type: integer + example: 0 + unbonding_height: + type: string + example: '0' + unbonding_time: + type: string + example: '1970-01-01T00:00:00Z' + commission: + type: object + properties: + rate: + type: string + example: '0' + max_rate: + type: string + example: '0' + max_change_rate: + type: string + example: '0' + update_time: + type: string + example: '1970-01-01T00:00:00Z' + Delegation: + type: object + properties: + delegator_addr: + type: string + validator_addr: + type: string + shares: + type: string + height: + type: integer + UnbondingDelegation: + type: object + properties: + delegator_addr: + type: string + validator_addr: + type: string + initial_balance: + type: string + balance: + type: string + creation_height: + type: integer + min_time: + type: integer + Redelegation: + type: object + properties: + delegator_addr: + type: string + validator_src_addr: + type: string + validator_dst_addr: + type: string + creation_height: + type: integer + min_time: + type: integer + initial_balance: + type: string + balance: + type: string + shares_src: + type: string + shares_dst: + type: string diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index cd48b89ea284..774fd4d6adb0 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -4,16 +4,21 @@ import ( "bytes" "encoding/json" "fmt" + "io/ioutil" "net" "net/http" "os" "path/filepath" + "sort" "strings" "testing" + stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/tendermint/tendermint/crypto/secp256k1" + "github.com/cosmos/cosmos-sdk/client" - keys "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/keys" gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/codec" crkeys "github.com/cosmos/cosmos-sdk/crypto/keys" @@ -21,10 +26,12 @@ 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" "github.com/spf13/viper" "github.com/stretchr/testify/require" + txbuilder "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" abci "github.com/tendermint/tendermint/abci/types" tmcfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto" @@ -90,7 +97,7 @@ func GetKeyBase(t *testing.T) crkeys.Keybase { viper.Set(cli.HomeFlag, dir) - keybase, err := keys.GetKeyBase() + keybase, err := keys.GetKeyBaseWithWritePerm() require.NoError(t, err) return keybase @@ -111,6 +118,69 @@ func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (sdk.Acc return sdk.AccAddress(info.GetPubKey().Address()), seed } +// Type that combines an Address with the pnemonic of the private key to that address +type AddrSeed struct { + Address sdk.AccAddress + Seed string + Name string + Password string +} + +// CreateAddr adds multiple address to the key store and returns the addresses and associated seeds in lexographical order by address. +// It also requires that the keys could be created. +func CreateAddrs(t *testing.T, kb crkeys.Keybase, numAddrs int) (addrs []sdk.AccAddress, seeds, names, passwords []string) { + var ( + err error + info crkeys.Info + seed string + ) + + addrSeeds := AddrSeedSlice{} + + for i := 0; i < numAddrs; i++ { + name := fmt.Sprintf("test%d", i) + password := "1234567890" + info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1) + require.NoError(t, err) + addrSeeds = append(addrSeeds, AddrSeed{Address: sdk.AccAddress(info.GetPubKey().Address()), Seed: seed, Name: name, Password: password}) + } + + sort.Sort(addrSeeds) + + for i := range addrSeeds { + addrs = append(addrs, addrSeeds[i].Address) + seeds = append(seeds, addrSeeds[i].Seed) + names = append(names, addrSeeds[i].Name) + passwords = append(passwords, addrSeeds[i].Password) + } + + return addrs, seeds, names, passwords +} + +// implement `Interface` in sort package. +type AddrSeedSlice []AddrSeed + +func (b AddrSeedSlice) Len() int { + return len(b) +} + +// Sorts lexographically by Address +func (b AddrSeedSlice) Less(i, j int) bool { + // bytes package already implements Comparable for []byte. + switch bytes.Compare(b[i].Address.Bytes(), b[j].Address.Bytes()) { + case -1: + return true + case 0, 1: + return false + default: + panic("not fail-able with `bytes.Comparable` bounded [-1, 1].") + } +} + +func (b AddrSeedSlice) Swap(i, j int) { + b[j], b[i] = b[i], b[j] +} + // InitializeTestLCD starts Tendermint and the LCD in process, listening on // their respective sockets where nValidators is the total number of validators // and initAddrs are the accounts to initialize with some steak tokens. It @@ -141,48 +211,58 @@ func InitializeTestLCD( genesisFile := config.GenesisFile() genDoc, err := tmtypes.GenesisDocFromFile(genesisFile) - require.NoError(t, err) - - // append initial (proposing) validator - genDoc.Validators[0] = tmtypes.GenesisValidator{ - PubKey: privVal.GetPubKey(), - Power: 100, // create enough power to enable 2/3 voting power - Name: "validator-1", - } + require.Nil(t, err) + genDoc.Validators = nil + genDoc.SaveAs(genesisFile) + genTxs := []json.RawMessage{} // append any additional (non-proposing) validators - for i := 1; i < nValidators; i++ { - genDoc.Validators = append(genDoc.Validators, - tmtypes.GenesisValidator{ - PubKey: ed25519.GenPrivKey().PubKey(), - Power: 1, - Name: fmt.Sprintf("validator-%d", i+1), - }, + var accs []gapp.GenesisAccount + for i := 0; i < nValidators; i++ { + operPrivKey := secp256k1.GenPrivKey() + operAddr := operPrivKey.PubKey().Address() + pubKey := privVal.PubKey + delegation := 100 + if i > 0 { + pubKey = ed25519.GenPrivKey().PubKey() + delegation = 1 + } + msg := stake.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()), ) - } - - var appGenTxs []json.RawMessage - - for _, gdValidator := range genDoc.Validators { - operAddr := ed25519.GenPrivKey().PubKey().Address() - pk := gdValidator.PubKey - - valConsPubKeys = append(valConsPubKeys, pk) + stdSignMsg := txbuilder.StdSignMsg{ + ChainID: genDoc.ChainID, + Msgs: []sdk.Msg{msg}, + } + sig, err := operPrivKey.Sign(stdSignMsg.Bytes()) + require.Nil(t, err) + tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{{Signature: sig, PubKey: operPrivKey.PubKey()}}, "") + txBytes, err := cdc.MarshalJSON(tx) + require.Nil(t, err) + genTxs = append(genTxs, txBytes) + valConsPubKeys = append(valConsPubKeys, pubKey) valOperAddrs = append(valOperAddrs, sdk.ValAddress(operAddr)) - appGenTx, _, _, err := gapp.GaiaAppGenTxNF(cdc, pk, sdk.AccAddress(operAddr), gdValidator.Name) - require.NoError(t, err) - - appGenTxs = append(appGenTxs, appGenTx) + accAuth := auth.NewBaseAccountWithAddress(sdk.AccAddress(operAddr)) + accAuth.Coins = sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 150)} + accs = append(accs, gapp.NewGenesisAccount(&accAuth)) } - genesisState, err := gapp.NewTestGaiaAppGenState(cdc, appGenTxs[:], genDoc.Validators, valOperAddrs) + appGenState := gapp.NewDefaultGenesisState() + appGenState.Accounts = accs + genDoc.AppState, err = cdc.MarshalJSON(appGenState) + require.NoError(t, err) + genesisState, err := gapp.GaiaAppGenState(cdc, *genDoc, genTxs) require.NoError(t, err) // add some tokens to init accounts for _, addr := range initAddrs { accAuth := auth.NewBaseAccountWithAddress(addr) - accAuth.Coins = sdk.Coins{sdk.NewInt64Coin("steak", 100)} + accAuth.Coins = sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 100)} acc := gapp.NewGenesisAccount(&accAuth) genesisState.Accounts = append(genesisState.Accounts, acc) genesisState.StakeData.Pool.LooseTokens = genesisState.StakeData.Pool.LooseTokens.Add(sdk.NewDec(100)) diff --git a/client/lcd/version.go b/client/lcd/version.go index a124388e6dfc..4f4ce611cee7 100644 --- a/client/lcd/version.go +++ b/client/lcd/version.go @@ -1,10 +1,10 @@ package lcd import ( - "fmt" "net/http" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/version" ) @@ -19,11 +19,11 @@ 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 { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("Could't query version. Error: %s", err.Error()))) + 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 1090fd2d25d9..b2db545dbdd1 100644 --- a/client/rpc/block.go +++ b/client/rpc/block.go @@ -115,19 +115,19 @@ func BlockRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { vars := mux.Vars(r) height, err := strconv.ParseInt(vars["height"], 10, 64) if err != nil { - w.WriteHeader(400) + w.WriteHeader(http.StatusBadRequest) w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/block/{height}'.")) return } chainHeight, err := GetChainHeight(cliCtx) if height > chainHeight { - w.WriteHeader(404) + w.WriteHeader(http.StatusNotFound) w.Write([]byte("ERROR: Requested block height is bigger then the chain length.")) return } output, err := getBlock(cliCtx, &height) if err != nil { - w.WriteHeader(500) + w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } @@ -140,13 +140,13 @@ func LatestBlockRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { height, err := GetChainHeight(cliCtx) if err != nil { - w.WriteHeader(500) + w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } output, err := getBlock(cliCtx, &height) if err != nil { - w.WriteHeader(500) + w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } diff --git a/client/rpc/root.go b/client/rpc/root.go index 74c5a69a2ed2..7bdcd8cd8486 100644 --- a/client/rpc/root.go +++ b/client/rpc/root.go @@ -22,15 +22,8 @@ func todoNotImplemented(_ *cobra.Command, _ []string) error { return errors.New("todo: Command not yet implemented") } -// AddCommands adds a number of rpc-related subcommands -func AddCommands(cmd *cobra.Command) { - cmd.AddCommand( - initClientCommand(), - statusCommand(), - ) -} - -func initClientCommand() *cobra.Command { +// InitClientCommand initializes client commands +func InitClientCommand() *cobra.Command { cmd := &cobra.Command{ Use: "init", Short: "Initialize light client", diff --git a/client/rpc/status.go b/client/rpc/status.go index 7f3494216024..0f81fc9c6f00 100644 --- a/client/rpc/status.go +++ b/client/rpc/status.go @@ -14,7 +14,8 @@ import ( ctypes "github.com/tendermint/tendermint/rpc/core/types" ) -func statusCommand() *cobra.Command { +// StatusCommand returns the status of the network +func StatusCommand() *cobra.Command { cmd := &cobra.Command{ Use: "status", Short: "Query remote node for status", @@ -69,7 +70,7 @@ func NodeInfoRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { status, err := getNodeStatus(cliCtx) if err != nil { - w.WriteHeader(500) + w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } @@ -84,14 +85,14 @@ func NodeSyncingRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { status, err := getNodeStatus(cliCtx) if err != nil { - w.WriteHeader(500) + w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } syncing := status.SyncInfo.CatchingUp if err != nil { - w.WriteHeader(500) + w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } diff --git a/client/rpc/validators.go b/client/rpc/validators.go index fc37881cba92..330966bf874f 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -140,21 +140,21 @@ func ValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { height, err := strconv.ParseInt(vars["height"], 10, 64) if err != nil { - w.WriteHeader(400) + w.WriteHeader(http.StatusBadRequest) w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/validatorsets/{height}'.")) return } chainHeight, err := GetChainHeight(cliCtx) if height > chainHeight { - w.WriteHeader(404) + w.WriteHeader(http.StatusNotFound) w.Write([]byte("ERROR: Requested block height is bigger then the chain length.")) return } output, err := getValidators(cliCtx, &height) if err != nil { - w.WriteHeader(500) + w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } @@ -167,14 +167,14 @@ func LatestValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerF return func(w http.ResponseWriter, r *http.Request) { height, err := GetChainHeight(cliCtx) if err != nil { - w.WriteHeader(500) + w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } output, err := getValidators(cliCtx, &height) if err != nil { - w.WriteHeader(500) + w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } diff --git a/client/tx/query.go b/client/tx/query.go index ec7daf41b4f8..3bbbf9a13223 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -3,9 +3,10 @@ package tx import ( "encoding/hex" "fmt" - "github.com/tendermint/tendermint/libs/common" "net/http" + "github.com/tendermint/tendermint/libs/common" + "github.com/gorilla/mux" "github.com/spf13/cobra" abci "github.com/tendermint/tendermint/abci/types" @@ -124,7 +125,7 @@ type Info struct { func parseTx(cdc *codec.Codec, txBytes []byte) (sdk.Tx, error) { var tx auth.StdTx - err := cdc.UnmarshalBinary(txBytes, &tx) + err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) if err != nil { return nil, err } @@ -142,8 +143,7 @@ func QueryTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.H output, err := queryTx(cdc, cliCtx, hashHexStr) if err != nil { - w.WriteHeader(500) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } utils.PostProcessResponse(w, cdc, output, cliCtx.Indent) diff --git a/client/tx/root.go b/client/tx/root.go index 0f237b59677e..5cd7eca0b8aa 100644 --- a/client/tx/root.go +++ b/client/tx/root.go @@ -2,20 +2,11 @@ package tx import ( "github.com/gorilla/mux" - "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" ) -// AddCommands adds a number of tx-query related subcommands -func AddCommands(cmd *cobra.Command, cdc *codec.Codec) { - cmd.AddCommand( - SearchTxCmd(cdc), - QueryTxCmd(cdc), - ) -} - // register REST routes func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) { r.HandleFunc("/txs/{hash}", QueryTxRequestHandlerFn(cdc, cliCtx)).Methods("GET") diff --git a/client/tx/search.go b/client/tx/search.go index 672df0e339f2..198bc4b3364b 100644 --- a/client/tx/search.go +++ b/client/tx/search.go @@ -30,7 +30,7 @@ func SearchTxCmd(cdc *codec.Codec) *cobra.Command { Use: "txs", Short: "Search for all transactions that match the given tags.", Long: strings.TrimSpace(` -Search for transactions that match the given tags. By default, transactions must match ALL tags +Search for transactions that match the given tags. By default, transactions must match ALL tags passed to the --tags option. To match any transaction, use the --any option. For example: @@ -141,7 +141,7 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http. return func(w http.ResponseWriter, r *http.Request) { tag := r.FormValue("tag") if tag == "" { - w.WriteHeader(400) + w.WriteHeader(http.StatusBadRequest) w.Write([]byte("You need to provide at least a tag as a key=value pair to search for. Postfix the key with _bech32 to search bech32-encoded addresses or public keys")) return } @@ -151,8 +151,7 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http. value, err := url.QueryUnescape(keyValue[1]) if err != nil { - w.WriteHeader(400) - w.Write([]byte("Could not decode address: " + err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, sdk.AppendMsgToErr("could not decode address", err.Error())) return } @@ -161,8 +160,7 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http. prefix := strings.Split(bech32address, "1")[0] bz, err := sdk.GetFromBech32(bech32address, prefix) if err != nil { - w.WriteHeader(400) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -171,8 +169,7 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http. txs, err := searchTxs(cliCtx, cdc, []string{tag}) if err != nil { - w.WriteHeader(500) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } diff --git a/client/utils/rest.go b/client/utils/rest.go index 07b258ce9686..53c6186921db 100644 --- a/client/utils/rest.go +++ b/client/utils/rest.go @@ -1,6 +1,5 @@ package utils -import "C" import ( "fmt" "io/ioutil" @@ -12,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "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" @@ -27,12 +27,12 @@ const ( // WriteErrorResponse prepares and writes a HTTP error // given a status code and an error message. -func WriteErrorResponse(w http.ResponseWriter, status int, msg string) { +func WriteErrorResponse(w http.ResponseWriter, status int, err string) { w.WriteHeader(status) - w.Write([]byte(msg)) + w.Write([]byte(err)) } -// WriteGasEstimateResponse prepares and writes an HTTP +// WriteSimulationResponse prepares and writes an HTTP // response for transactions simulations. func WriteSimulationResponse(w http.ResponseWriter, gas int64) { w.WriteHeader(http.StatusOK) @@ -65,6 +65,20 @@ func ParseInt64OrReturnBadRequest(w http.ResponseWriter, s string) (n int64, ok return n, true } +// ParseUint64OrReturnBadRequest converts s to a uint64 value. +func ParseUint64OrReturnBadRequest(w http.ResponseWriter, s string) (n uint64, ok bool) { + var err error + + n, err = strconv.ParseUint(s, 10, 64) + if err != nil { + err := fmt.Errorf("'%s' is not a valid uint64", s) + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return n, false + } + + return n, true +} + // ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a // default value, defaultIfEmpty, if the string is empty. func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEmpty float64) (n float64, ok bool) { @@ -230,9 +244,15 @@ func CompleteAndBroadcastTxREST(w http.ResponseWriter, r *http.Request, cliCtx c } txBytes, err := txBldr.BuildAndSign(baseReq.Name, baseReq.Password, msgs) - if err != nil { + if keyerror.IsErrKeyNotFound(err) { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } else if keyerror.IsErrWrongPassword(err) { WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return + } else if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return } res, err := cliCtx.BroadcastTx(txBytes) diff --git a/client/utils/utils.go b/client/utils/utils.go index 364b9e692fb6..fee7c7fe2557 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -107,7 +107,8 @@ func PrintUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msg // SignStdTx appends a signature to a StdTx and returns a copy of a it. If appendSig // is false, it replaces the signatures already attached with the new signature. -func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, stdTx auth.StdTx, appendSig bool) (auth.StdTx, error) { +// Don't perform online validation or lookups if offline is true. +func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, stdTx auth.StdTx, appendSig bool, offline bool) (auth.StdTx, error) { var signedStdTx auth.StdTx keybase, err := keys.GetKeyBase() @@ -122,10 +123,11 @@ func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, // Check whether the address is a signer if !isTxSigner(sdk.AccAddress(addr), stdTx.GetSigners()) { - fmt.Fprintf(os.Stderr, "WARNING: The generated transaction's intended signer does not match the given signer: '%v'", name) + return signedStdTx, fmt.Errorf( + "The generated transaction's intended signer does not match the given signer: %q", name) } - if txBldr.AccountNumber == 0 { + if !offline && txBldr.AccountNumber == 0 { accNum, err := cliCtx.GetAccountNumber(addr) if err != nil { return signedStdTx, err @@ -133,7 +135,7 @@ func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, txBldr = txBldr.WithAccountNumber(accNum) } - if txBldr.Sequence == 0 { + if !offline && txBldr.Sequence == 0 { accSeq, err := cliCtx.GetAccountSequence(addr) if err != nil { return signedStdTx, err @@ -165,7 +167,7 @@ func adjustGasEstimate(estimate int64, adjustment float64) int64 { func parseQueryResponse(cdc *amino.Codec, rawRes []byte) (int64, error) { var simulationResult sdk.Result - if err := cdc.UnmarshalBinary(rawRes, &simulationResult); err != nil { + if err := cdc.UnmarshalBinaryLengthPrefixed(rawRes, &simulationResult); err != nil { return 0, err } return simulationResult.GasUsed, nil diff --git a/client/utils/utils_test.go b/client/utils/utils_test.go index 731ded903ff0..c3bf315699ac 100644 --- a/client/utils/utils_test.go +++ b/client/utils/utils_test.go @@ -12,7 +12,7 @@ import ( func TestParseQueryResponse(t *testing.T) { cdc := app.MakeCodec() - sdkResBytes := cdc.MustMarshalBinary(sdk.Result{GasUsed: 10}) + sdkResBytes := cdc.MustMarshalBinaryLengthPrefixed(sdk.Result{GasUsed: 10}) gas, err := parseQueryResponse(cdc, sdkResBytes) assert.Equal(t, gas, int64(10)) assert.Nil(t, err) @@ -28,7 +28,7 @@ func TestCalculateGas(t *testing.T) { if wantErr { return nil, errors.New("") } - return cdc.MustMarshalBinary(sdk.Result{GasUsed: gasUsed}), nil + return cdc.MustMarshalBinaryLengthPrefixed(sdk.Result{GasUsed: gasUsed}), nil } } type args struct { diff --git a/cmd/cosmos-sdk-cli/cmd/init.go b/cmd/cosmos-sdk-cli/cmd/init.go index e9e9cd8a9287..fcf30b9724c2 100644 --- a/cmd/cosmos-sdk-cli/cmd/init.go +++ b/cmd/cosmos-sdk-cli/cmd/init.go @@ -14,7 +14,7 @@ import ( tmversion "github.com/tendermint/tendermint/version" ) -var remoteBasecoinPath = "github.com/cosmos/cosmos-sdk/examples/basecoin" +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 diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 40b55493ee63..765d624e9dc5 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -2,28 +2,33 @@ package app import ( "encoding/json" + "fmt" "io" "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" + "sort" 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" + distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" + "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" + 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" ) const ( appName = "GaiaApp" + // DefaultKeyPass contains the default key password for genesis transactions + DefaultKeyPass = "12345678" ) // default home directories for expected binaries @@ -43,17 +48,22 @@ type GaiaApp struct { keyStake *sdk.KVStoreKey tkeyStake *sdk.TransientStoreKey keySlashing *sdk.KVStoreKey + keyMint *sdk.KVStoreKey + keyDistr *sdk.KVStoreKey + tkeyDistr *sdk.TransientStoreKey keyGov *sdk.KVStoreKey keyFeeCollection *sdk.KVStoreKey keyParams *sdk.KVStoreKey tkeyParams *sdk.TransientStoreKey // Manage getting and setting accounts - accountMapper auth.AccountMapper + accountKeeper auth.AccountKeeper feeCollectionKeeper auth.FeeCollectionKeeper bankKeeper bank.Keeper stakeKeeper stake.Keeper slashingKeeper slashing.Keeper + mintKeeper mint.Keeper + distrKeeper distr.Keeper govKeeper gov.Keeper paramsKeeper params.Keeper } @@ -72,6 +82,9 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio 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"), @@ -79,55 +92,64 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio tkeyParams: sdk.NewTransientStoreKey("transient_params"), } - // define the accountMapper - app.accountMapper = auth.NewAccountMapper( + // define the accountKeeper + app.accountKeeper = auth.NewAccountKeeper( app.cdc, app.keyAccount, // target store auth.ProtoBaseAccount, // prototype ) // add handlers - app.bankKeeper = bank.NewBaseKeeper(app.accountMapper) - + app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper) + app.feeCollectionKeeper = auth.NewFeeCollectionKeeper( + app.cdc, + app.keyFeeCollection, + ) app.paramsKeeper = params.NewKeeper( app.cdc, app.keyParams, app.tkeyParams, ) - - app.stakeKeeper = stake.NewKeeper( + stakeKeeper := stake.NewKeeper( app.cdc, app.keyStake, app.tkeyStake, app.bankKeeper, app.paramsKeeper.Subspace(stake.DefaultParamspace), app.RegisterCodespace(stake.DefaultCodespace), ) - + app.mintKeeper = mint.NewKeeper(app.cdc, app.keyMint, + app.paramsKeeper.Subspace(mint.DefaultParamspace), + &stakeKeeper, app.feeCollectionKeeper, + ) + app.distrKeeper = distr.NewKeeper( + app.cdc, + app.keyDistr, + app.paramsKeeper.Subspace(distr.DefaultParamspace), + app.bankKeeper, &stakeKeeper, app.feeCollectionKeeper, + app.RegisterCodespace(stake.DefaultCodespace), + ) app.slashingKeeper = slashing.NewKeeper( app.cdc, app.keySlashing, - app.stakeKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), + &stakeKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), app.RegisterCodespace(slashing.DefaultCodespace), ) - - app.stakeKeeper = app.stakeKeeper.WithHooks( - app.slashingKeeper.Hooks(), - ) - app.govKeeper = gov.NewKeeper( app.cdc, app.keyGov, - app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamspace), app.bankKeeper, app.stakeKeeper, + app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamspace), app.bankKeeper, &stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace), ) - app.feeCollectionKeeper = auth.NewFeeCollectionKeeper( - app.cdc, - app.keyFeeCollection, - ) + // register the staking hooks + // NOTE: stakeKeeper above are passed by reference, + // so that it can be modified like below: + app.stakeKeeper = *stakeKeeper.SetHooks( + NewHooks(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)) @@ -136,13 +158,14 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio AddRoute("stake", stake.NewQuerier(app.stakeKeeper, app.cdc)) // initialize BaseApp + app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyStake, 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.SetEndBlocker(app.EndBlocker) - app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) - app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyStake, - app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams) - app.MountStoresTransient(app.tkeyParams, app.tkeyStake) + err := app.LoadLatestVersion(app.keyMain) if err != nil { cmn.Exit(err.Error()) @@ -156,6 +179,7 @@ func MakeCodec() *codec.Codec { var cdc = codec.New() bank.RegisterCodec(cdc) stake.RegisterCodec(cdc) + distr.RegisterCodec(cdc) slashing.RegisterCodec(cdc) gov.RegisterCodec(cdc) auth.RegisterCodec(cdc) @@ -168,6 +192,12 @@ func MakeCodec() *codec.Codec { func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { tags := slashing.BeginBlocker(ctx, req, app.slashingKeeper) + // distribute rewards from previous block + distr.BeginBlocker(ctx, req, app.distrKeeper) + + // mint new tokens for this new block + mint.BeginBlocker(ctx, app.mintKeeper) + return abci.ResponseBeginBlock{ Tags: tags.ToKVPairs(), } @@ -176,10 +206,10 @@ 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 := stake.EndBlocker(ctx, app.stakeKeeper) - // Add these new validators to the addr -> pubkey map. - app.slashingKeeper.AddValidators(ctx, validatorUpdates) + return abci.ResponseEndBlock{ ValidatorUpdates: validatorUpdates, Tags: tags, @@ -198,28 +228,66 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci // return sdk.ErrGenesisParse("").TraceCause(err, "") } + // sort by account number to maintain consistency + 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.accountMapper.GetNextAccountNumber(ctx) - app.accountMapper.SetAccount(ctx, acc) + acc.AccountNumber = app.accountKeeper.GetNextAccountNumber(ctx) + app.accountKeeper.SetAccount(ctx, acc) } // load the initial stake information validators, err := stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData) if err != nil { - panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 - // return sdk.ErrGenesisParse("").TraceCause(err, "") + panic(err) // TODO find a way to do this w/o panics } - // load the address to pubkey map + // initialize module-specific stores + auth.InitGenesis(ctx, app.feeCollectionKeeper, genesisState.AuthData) slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakeData) - 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) if err != nil { - // TODO find a way to do this w/o panics - panic(err) + panic(err) // TODO find a way to do this w/o panics + } + + if len(genesisState.GenTxs) > 0 { + for _, genTx := range genesisState.GenTxs { + var tx auth.StdTx + err = app.cdc.UnmarshalJSON(genTx, &tx) + if err != nil { + panic(err) + } + bz := app.cdc.MustMarshalBinaryLengthPrefixed(tx) + res := app.BaseApp.DeliverTx(bz) + if !res.IsOK() { + panic(res.Log) + } + } + + validators = app.stakeKeeper.ApplyAndReturnValidatorSetUpdates(ctx) + } + + // sanity check + if len(req.Validators) > 0 { + if len(req.Validators) != len(validators) { + panic(fmt.Errorf("len(RequestInitChain.Validators) != len(validators) (%d != %d)", + len(req.Validators), len(validators))) + } + sort.Sort(abci.ValidatorUpdates(req.Validators)) + sort.Sort(abci.ValidatorUpdates(validators)) + for i, val := range validators { + if !val.Equal(req.Validators[i]) { + panic(fmt.Errorf("validators[%d] != req.Validators[%d] ", i, i)) + } + } } return abci.ResponseInitChain{ @@ -238,13 +306,16 @@ func (app *GaiaApp) ExportAppStateAndValidators() (appState json.RawMessage, val accounts = append(accounts, account) return false } - app.accountMapper.IterateAccounts(ctx, appendAccount) - - genState := GenesisState{ - Accounts: accounts, - StakeData: stake.WriteGenesis(ctx, app.stakeKeeper), - GovData: gov.WriteGenesis(ctx, app.govKeeper), - } + app.accountKeeper.IterateAccounts(ctx, appendAccount) + genState := NewGenesisState( + accounts, + auth.ExportGenesis(ctx, app.feeCollectionKeeper), + stake.ExportGenesis(ctx, app.stakeKeeper), + mint.ExportGenesis(ctx, app.mintKeeper), + distr.ExportGenesis(ctx, app.distrKeeper), + gov.ExportGenesis(ctx, app.govKeeper), + slashing.ExportGenesis(ctx, app.slashingKeeper), + ) appState, err = codec.MarshalJSONIndent(app.cdc, genState) if err != nil { return nil, nil, err @@ -252,3 +323,55 @@ func (app *GaiaApp) ExportAppStateAndValidators() (appState json.RawMessage, val validators = stake.WriteValidators(ctx, app.stakeKeeper) return appState, validators, nil } + +//______________________________________________________________________________________________ + +// Combined Staking Hooks +type Hooks struct { + dh distr.Hooks + sh slashing.Hooks +} + +func NewHooks(dh distr.Hooks, sh slashing.Hooks) Hooks { + return Hooks{dh, sh} +} + +var _ sdk.StakingHooks = Hooks{} + +// nolint +func (h Hooks) OnValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { + h.dh.OnValidatorCreated(ctx, valAddr) + h.sh.OnValidatorCreated(ctx, valAddr) +} +func (h Hooks) OnValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { + h.dh.OnValidatorModified(ctx, valAddr) + h.sh.OnValidatorModified(ctx, valAddr) +} +func (h Hooks) OnValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.OnValidatorRemoved(ctx, consAddr, valAddr) + h.sh.OnValidatorRemoved(ctx, consAddr, valAddr) +} +func (h Hooks) OnValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.OnValidatorBonded(ctx, consAddr, valAddr) + h.sh.OnValidatorBonded(ctx, consAddr, valAddr) +} +func (h Hooks) OnValidatorPowerDidChange(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.OnValidatorPowerDidChange(ctx, consAddr, valAddr) + h.sh.OnValidatorPowerDidChange(ctx, consAddr, valAddr) +} +func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.OnValidatorBeginUnbonding(ctx, consAddr, valAddr) + h.sh.OnValidatorBeginUnbonding(ctx, consAddr, valAddr) +} +func (h Hooks) OnDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.OnDelegationCreated(ctx, delAddr, valAddr) + h.sh.OnDelegationCreated(ctx, delAddr, valAddr) +} +func (h Hooks) OnDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.OnDelegationSharesModified(ctx, delAddr, valAddr) + h.sh.OnDelegationSharesModified(ctx, delAddr, valAddr) +} +func (h Hooks) OnDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.OnDelegationRemoved(ctx, delAddr, valAddr) + h.sh.OnDelegationRemoved(ctx, delAddr, valAddr) +} diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index d16cba40ea6d..7023eb09c144 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/auth" + distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" "github.com/stretchr/testify/require" @@ -24,6 +25,7 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error { genesisState := GenesisState{ Accounts: genaccs, StakeData: stake.DefaultGenesisState(), + DistrData: distr.DefaultGenesisState(), SlashingData: slashing.DefaultGenesisState(), } diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index f4a5e2b41adb..e3c869ada1d6 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -4,237 +4,158 @@ import ( "encoding/json" "errors" "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server" - "github.com/cosmos/cosmos-sdk/server/config" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + distr "github.com/cosmos/cosmos-sdk/x/distribution" "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/spf13/pflag" - - "github.com/tendermint/tendermint/crypto" + stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" tmtypes "github.com/tendermint/tendermint/types" ) -// DefaultKeyPass contains the default key password for genesis transactions -const DefaultKeyPass = "12345678" - var ( // bonded tokens given to genesis validators/accounts freeFermionVal = int64(100) - freeFermionsAcc = sdk.NewInt(50) + freeFermionsAcc = sdk.NewInt(150) + bondDenom = stakeTypes.DefaultBondDenom ) // State to Unmarshal type GenesisState struct { Accounts []GenesisAccount `json:"accounts"` + AuthData auth.GenesisState `json:"auth"` StakeData stake.GenesisState `json:"stake"` + MintData mint.GenesisState `json:"mint"` + DistrData distr.GenesisState `json:"distr"` GovData gov.GenesisState `json:"gov"` SlashingData slashing.GenesisState `json:"slashing"` + GenTxs []json.RawMessage `json:"gentxs"` } -// GenesisAccount doesn't need pubkey or sequence +func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, stakeData stake.GenesisState, mintData mint.GenesisState, + distrData distr.GenesisState, govData gov.GenesisState, slashingData slashing.GenesisState) GenesisState { + + return GenesisState{ + Accounts: accounts, + AuthData: authData, + StakeData: stakeData, + MintData: mintData, + DistrData: distrData, + GovData: govData, + SlashingData: slashingData, + } +} + +// nolint type GenesisAccount struct { - Address sdk.AccAddress `json:"address"` - Coins sdk.Coins `json:"coins"` + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` + Sequence int64 `json:"sequence_number"` + AccountNumber int64 `json:"account_number"` } func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount { return GenesisAccount{ - Address: acc.Address, - Coins: acc.Coins, + Address: acc.Address, + Coins: acc.Coins, + AccountNumber: acc.AccountNumber, + Sequence: acc.Sequence, } } func NewGenesisAccountI(acc auth.Account) GenesisAccount { return GenesisAccount{ - Address: acc.GetAddress(), - Coins: acc.GetCoins(), + Address: acc.GetAddress(), + Coins: acc.GetCoins(), + AccountNumber: acc.GetAccountNumber(), + Sequence: acc.GetSequence(), } } // convert GenesisAccount to auth.BaseAccount func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount) { return &auth.BaseAccount{ - Address: ga.Address, - Coins: ga.Coins.Sort(), + Address: ga.Address, + Coins: ga.Coins.Sort(), + AccountNumber: ga.AccountNumber, + Sequence: ga.Sequence, } } // get app init parameters for server init command func GaiaAppInit() server.AppInit { - fsAppGenState := pflag.NewFlagSet("", pflag.ContinueOnError) - - fsAppGenTx := pflag.NewFlagSet("", pflag.ContinueOnError) - fsAppGenTx.String(server.FlagName, "", "validator moniker, required") - fsAppGenTx.String(server.FlagClientHome, DefaultCLIHome, - "home directory for the client, used for key generation") - fsAppGenTx.Bool(server.FlagOWK, false, "overwrite the accounts created") return server.AppInit{ - FlagsAppGenState: fsAppGenState, - FlagsAppGenTx: fsAppGenTx, - AppGenTx: GaiaAppGenTx, - AppGenState: GaiaAppGenStateJSON, + AppGenState: GaiaAppGenStateJSON, } } -// simple genesis tx -type GaiaGenTx struct { - Name string `json:"name"` - Address sdk.AccAddress `json:"address"` - PubKey string `json:"pub_key"` -} - -// GaiaAppGenTx generates a Gaia genesis transaction. -func GaiaAppGenTx( - cdc *codec.Codec, pk crypto.PubKey, genTxConfig config.GenTx, -) (appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { - if genTxConfig.Name == "" { - return nil, nil, tmtypes.GenesisValidator{}, errors.New("Must specify --name (validator moniker)") - } - - buf := client.BufferStdin() - prompt := fmt.Sprintf("Password for account '%s' (default %s):", genTxConfig.Name, DefaultKeyPass) - - keyPass, err := client.GetPassword(prompt, buf) - if err != nil && keyPass != "" { - // An error was returned that either failed to read the password from - // STDIN or the given password is not empty but failed to meet minimum - // length requirements. - return appGenTx, cliPrint, validator, err - } - - if keyPass == "" { - keyPass = DefaultKeyPass - } - - addr, secret, err := server.GenerateSaveCoinKey( - genTxConfig.CliRoot, - genTxConfig.Name, - keyPass, - genTxConfig.Overwrite, - ) - if err != nil { - return appGenTx, cliPrint, validator, err - } - - mm := map[string]string{"secret": secret} - bz, err := cdc.MarshalJSON(mm) - if err != nil { - return appGenTx, cliPrint, validator, err - } - - cliPrint = json.RawMessage(bz) - appGenTx, _, validator, err = GaiaAppGenTxNF(cdc, pk, addr, genTxConfig.Name) - - return appGenTx, cliPrint, validator, err -} - -// Generate a gaia genesis transaction without flags -func GaiaAppGenTxNF(cdc *codec.Codec, pk crypto.PubKey, addr sdk.AccAddress, name string) ( - appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { - - var bz []byte - gaiaGenTx := GaiaGenTx{ - Name: name, - Address: addr, - PubKey: sdk.MustBech32ifyConsPub(pk), - } - bz, err = codec.MarshalJSONIndent(cdc, gaiaGenTx) - if err != nil { - return - } - appGenTx = json.RawMessage(bz) +// Create the core parameters for genesis initialization for gaia +// note that the pubkey input is this machines pubkey +func GaiaAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( + genesisState GenesisState, err error) { - validator = tmtypes.GenesisValidator{ - PubKey: pk, - Power: freeFermionVal, + if err = cdc.UnmarshalJSON(genDoc.AppState, &genesisState); err != nil { + return genesisState, err } - return -} -// Create the core parameters for genesis initialization for gaia -// note that the pubkey input is this machines pubkey -func GaiaAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (genesisState GenesisState, err error) { + // if there are no gen txs to be processed, return the default empty state if len(appGenTxs) == 0 { - err = errors.New("must provide at least genesis transaction") - return + return genesisState, errors.New("there must be at least one genesis tx") } - // start with the default staking genesis state - stakeData := stake.DefaultGenesisState() - - slashingData := slashing.DefaultGenesisState() - - // get genesis flag account information - genaccs := make([]GenesisAccount, len(appGenTxs)) - for i, appGenTx := range appGenTxs { - - var genTx GaiaGenTx - err = cdc.UnmarshalJSON(appGenTx, &genTx) - if err != nil { - return + stakeData := genesisState.StakeData + for i, genTx := range appGenTxs { + var tx auth.StdTx + if err := cdc.UnmarshalJSON(genTx, &tx); err != nil { + return genesisState, err } - - // create the genesis account, give'm few steaks and a buncha token with there name - genaccs[i] = genesisAccountFromGenTx(genTx) - stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(freeFermionsAcc)) // increase the supply - - // add the validator - if len(genTx.Name) > 0 { - stakeData = addValidatorToStakeData(genTx, stakeData) + 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 { + return genesisState, fmt.Errorf( + "Genesis transaction %v does not contain a MsgCreateValidator", i) } } - // create the final app state - genesisState = GenesisState{ - Accounts: genaccs, - StakeData: stakeData, - GovData: gov.DefaultGenesisState(), - SlashingData: slashingData, - } - - return -} - -func addValidatorToStakeData(genTx GaiaGenTx, stakeData stake.GenesisState) stake.GenesisState { - desc := stake.NewDescription(genTx.Name, "", "", "") - validator := stake.NewValidator( - sdk.ValAddress(genTx.Address), sdk.MustGetConsPubKeyBech32(genTx.PubKey), desc, - ) - - stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDec(freeFermionVal)) // increase the supply - - // add some new shares to the validator - var issuedDelShares sdk.Dec - validator, stakeData.Pool, issuedDelShares = validator.AddTokensFromDel(stakeData.Pool, sdk.NewInt(freeFermionVal)) - stakeData.Validators = append(stakeData.Validators, validator) - - // create the self-delegation from the issuedDelShares - delegation := stake.Delegation{ - DelegatorAddr: sdk.AccAddress(validator.OperatorAddr), - ValidatorAddr: validator.OperatorAddr, - Shares: issuedDelShares, - Height: 0, + 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 + } + } } - - stakeData.Bonds = append(stakeData.Bonds, delegation) - return stakeData + genesisState.StakeData = stakeData + genesisState.GenTxs = appGenTxs + return genesisState, nil } -func genesisAccountFromGenTx(genTx GaiaGenTx) GenesisAccount { - accAuth := auth.NewBaseAccountWithAddress(genTx.Address) - accAuth.Coins = sdk.Coins{ - {genTx.Name + "Token", sdk.NewInt(1000)}, - {"steak", freeFermionsAcc}, +// NewDefaultGenesisState generates the default state for gaia. +func NewDefaultGenesisState() GenesisState { + return GenesisState{ + Accounts: nil, + StakeData: stake.DefaultGenesisState(), + MintData: mint.DefaultGenesisState(), + DistrData: distr.DefaultGenesisState(), + GovData: gov.DefaultGenesisState(), + SlashingData: slashing.DefaultGenesisState(), + GenTxs: nil, } - return NewGenesisAccount(&accAuth) } // GaiaValidateGenesisState ensures that the genesis state obeys the expected invariants @@ -246,11 +167,11 @@ func GaiaValidateGenesisState(genesisState GenesisState) (err error) { if err != nil { return } - err = stake.ValidateGenesis(genesisState.StakeData) - if err != nil { - return + // skip stakeData validation as genesis is created from txs + if len(genesisState.GenTxs) > 0 { + return nil } - return + return stake.ValidateGenesis(genesisState.StakeData) } // Ensures that there are no duplicate accounts in the genesis state, @@ -268,13 +189,109 @@ func validateGenesisStateAccounts(accs []GenesisAccount) (err error) { } // GaiaAppGenState but with JSON -func GaiaAppGenStateJSON(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { - +func GaiaAppGenStateJSON(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( + appState json.RawMessage, err error) { // create the final app state - genesisState, err := GaiaAppGenState(cdc, appGenTxs) + genesisState, err := GaiaAppGenState(cdc, genDoc, appGenTxs) if err != nil { return nil, err } - appState, err = codec.MarshalJSONIndent(cdc, genesisState) - return + return codec.MarshalJSONIndent(cdc, genesisState) +} + +// CollectStdTxs processes and validates application's genesis StdTxs and returns +// the list of appGenTxs, and persistent peers required to generate genesis.json. +func CollectStdTxs(cdc *codec.Codec, moniker string, genTxsDir string, genDoc tmtypes.GenesisDoc) ( + appGenTxs []auth.StdTx, persistentPeers string, err error) { + + var fos []os.FileInfo + fos, err = ioutil.ReadDir(genTxsDir) + if err != nil { + return appGenTxs, persistentPeers, err + } + + // prepare a map of all accounts in genesis state to then validate + // against the validators addresses + var appState GenesisState + if err := cdc.UnmarshalJSON(genDoc.AppState, &appState); err != nil { + return appGenTxs, persistentPeers, err + } + addrMap := make(map[string]GenesisAccount, len(appState.Accounts)) + for i := 0; i < len(appState.Accounts); i++ { + acc := appState.Accounts[i] + strAddr := string(acc.Address) + addrMap[strAddr] = acc + } + + // addresses and IPs (and port) validator server info + var addressesIPs []string + + for _, fo := range fos { + filename := filepath.Join(genTxsDir, fo.Name()) + if !fo.IsDir() && (filepath.Ext(filename) != ".json") { + continue + } + + // get the genStdTx + var jsonRawTx []byte + if jsonRawTx, err = ioutil.ReadFile(filename); err != nil { + return appGenTxs, persistentPeers, err + } + var genStdTx auth.StdTx + if err = cdc.UnmarshalJSON(jsonRawTx, &genStdTx); err != nil { + return appGenTxs, persistentPeers, err + } + appGenTxs = append(appGenTxs, genStdTx) + + // the memo flag is used to store + // the ip and node-id, for example this may be: + // "528fd3df22b31f4969b05652bfe8f0fe921321d5@192.168.2.37:26656" + nodeAddrIP := genStdTx.GetMemo() + if len(nodeAddrIP) == 0 { + return appGenTxs, persistentPeers, fmt.Errorf( + "couldn't find node's address and IP in %s", fo.Name()) + } + + // genesis transactions must be single-message + msgs := genStdTx.GetMsgs() + if len(msgs) != 1 { + + return appGenTxs, persistentPeers, errors.New( + "each genesis transaction must provide a single genesis message") + } + + // validate the validator address and funds against the accounts in the state + msg := msgs[0].(stake.MsgCreateValidator) + addr := string(sdk.AccAddress(msg.ValidatorAddr)) + acc, ok := addrMap[addr] + if !ok { + return appGenTxs, persistentPeers, fmt.Errorf( + "account %v not in genesis.json: %+v", addr, addrMap) + } + if acc.Coins.AmountOf(msg.Delegation.Denom).LT(msg.Delegation.Amount) { + err = fmt.Errorf("insufficient fund for the delegation: %s < %s", + acc.Coins.AmountOf(msg.Delegation.Denom), msg.Delegation.Amount) + } + + // exclude itself from persistent peers + if msg.Description.Moniker != moniker { + addressesIPs = append(addressesIPs, nodeAddrIP) + } + } + + sort.Strings(addressesIPs) + persistentPeers = strings.Join(addressesIPs, ",") + + return appGenTxs, persistentPeers, nil +} + +func NewDefaultGenesisAccount(addr sdk.AccAddress) GenesisAccount { + accAuth := auth.NewBaseAccountWithAddress(addr) + coins :=sdk.Coins{ + {"fooToken", sdk.NewInt(1000)}, + {bondDenom, freeFermionsAcc}, + } + coins.Sort() + accAuth.Coins = coins + return NewGenesisAccount(&accAuth) } diff --git a/cmd/gaia/app/genesis_test.go b/cmd/gaia/app/genesis_test.go index 9cbd1f49fc70..1b1c1964695a 100644 --- a/cmd/gaia/app/genesis_test.go +++ b/cmd/gaia/app/genesis_test.go @@ -1,11 +1,14 @@ package app import ( + "encoding/json" "testing" + "github.com/tendermint/tendermint/crypto/secp256k1" + tmtypes "github.com/tendermint/tendermint/types" + 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/stretchr/testify/require" @@ -25,28 +28,26 @@ var ( emptyPubkey crypto.PubKey ) -func makeGenesisState(genTxs []GaiaGenTx) GenesisState { +func makeGenesisState(t *testing.T, genTxs []auth.StdTx) GenesisState { // start with the default staking genesis state - stakeData := stake.DefaultGenesisState() + appState := NewDefaultGenesisState() + stakeData := appState.StakeData + genAccs := make([]GenesisAccount, len(genTxs)) - // get genesis flag account information - genaccs := make([]GenesisAccount, len(genTxs)) for i, genTx := range genTxs { - genaccs[i] = genesisAccountFromGenTx(genTx) - stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(freeFermionsAcc)) // increase the supply + msgs := genTx.GetMsgs() + require.Equal(t, 1, len(msgs)) + msg := msgs[0].(stake.MsgCreateValidator) - // add the validator - if len(genTx.Name) > 0 { - stakeData = addValidatorToStakeData(genTx, stakeData) - } + 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 } // create the final app state - return GenesisState{ - Accounts: genaccs, - StakeData: stakeData, - GovData: gov.DefaultGenesisState(), - } + appState.Accounts = genAccs + return appState } func TestToAccount(t *testing.T) { @@ -69,23 +70,43 @@ func TestGaiaAppGenTx(t *testing.T) { func TestGaiaAppGenState(t *testing.T) { cdc := MakeCodec() _ = cdc + var genDoc tmtypes.GenesisDoc + + // test unmarshalling error + _, err := GaiaAppGenState(cdc, genDoc, []json.RawMessage{}) + require.Error(t, err) + + appState := makeGenesisState(t, []auth.StdTx{}) + genDoc.AppState, err = json.Marshal(appState) + require.NoError(t, err) + + // test validation error + _, err = GaiaAppGenState(cdc, genDoc, []json.RawMessage{}) + require.Error(t, err) // TODO test must provide at least genesis transaction // TODO test with both one and two genesis transactions: // TODO correct: genesis account created, canididates created, pool token variance } +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, + 50), desc, comm) + return auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, nil, "") +} + func TestGaiaGenesisValidation(t *testing.T) { - genTxs := make([]GaiaGenTx, 2) - addr := pk1.Address() + genTxs := make([]auth.StdTx, 2) // Test duplicate accounts fails - genTxs[0] = GaiaGenTx{"", sdk.AccAddress(addr), ""} - genTxs[1] = GaiaGenTx{"", sdk.AccAddress(addr), ""} - genesisState := makeGenesisState(genTxs) + genTxs[0] = makeMsg("test-0", pk1) + genTxs[1] = makeMsg("test-1", pk1) + genesisState := makeGenesisState(t, genTxs) err := GaiaValidateGenesisState(genesisState) require.NotNil(t, err) // Test bonded + jailed validator fails - genesisState = makeGenesisState(genTxs[:1]) + genesisState = makeGenesisState(t, genTxs) val1 := stakeTypes.NewValidator(addr1, pk1, stakeTypes.Description{Moniker: "test #2"}) val1.Jailed = true val1.Status = sdk.Bonded @@ -94,10 +115,17 @@ func TestGaiaGenesisValidation(t *testing.T) { require.NotNil(t, err) // Test duplicate validator fails val1.Jailed = false - genesisState = makeGenesisState(genTxs[:1]) + 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) err = GaiaValidateGenesisState(genesisState) require.NotNil(t, err) } + +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(150), acc.Coins.AmountOf(bondDenom)) +} diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 2521e07883d2..e56344554a16 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -4,24 +4,32 @@ import ( "encoding/json" "flag" "fmt" + "io/ioutil" "math/rand" "os" "testing" + "time" "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" sdk "github.com/cosmos/cosmos-sdk/types" + 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" + distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation" "github.com/cosmos/cosmos-sdk/x/gov" govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation" + "github.com/cosmos/cosmos-sdk/x/mint" "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" ) var ( @@ -45,40 +53,99 @@ func init() { func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { var genesisAccounts []GenesisAccount + amount := int64(r.Intn(1e6)) + numInitiallyBonded := int64(r.Intn(250)) + numAccs := int64(len(accs)) + if numInitiallyBonded > numAccs { + numInitiallyBonded = numAccs + } + fmt.Printf("Selected randomly generated parameters for simulated genesis: {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.Coin{"steak", sdk.NewInt(100)}} + coins := sdk.Coins{sdk.Coin{stakeTypes.DefaultBondDenom, sdk.NewInt(amount)}} genesisAccounts = append(genesisAccounts, GenesisAccount{ Address: acc.Address, Coins: coins, }) } - // Default genesis state - govGenesis := gov.DefaultGenesisState() - stakeGenesis := stake.DefaultGenesisState() - slashingGenesis := slashing.DefaultGenesisState() + // Random genesis states + govGenesis := gov.GenesisState{ + StartingProposalID: uint64(r.Intn(100)), + DepositParams: gov.DepositParams{ + MinDeposit: sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, int64(r.Intn(1e3)))}, + MaxDepositPeriod: time.Duration(r.Intn(2*172800)) * time.Second, + }, + VotingParams: gov.VotingParams{ + VotingPeriod: time.Duration(r.Intn(2*172800)) * time.Second, + }, + TallyParams: gov.TallyParams{ + Threshold: sdk.NewDecWithPrec(5, 1), + Veto: sdk.NewDecWithPrec(334, 3), + GovernancePenalty: sdk.NewDecWithPrec(1, 2), + }, + } + fmt.Printf("Selected randomly generated governance parameters: %+v\n", govGenesis) + stakeGenesis := stake.GenesisState{ + Pool: stake.InitialPool(), + Params: stake.Params{ + UnbondingTime: time.Duration(r.Intn(60*60*24*3*2)) * time.Second, + MaxValidators: uint16(r.Intn(250)), + BondDenom: stakeTypes.DefaultBondDenom, + }, + } + fmt.Printf("Selected randomly generated staking parameters: %+v\n", stakeGenesis) + 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))), + }, + } + fmt.Printf("Selected randomly generated slashing parameters: %+v\n", slashingGenesis) + mintGenesis := mint.GenesisState{ + Minter: mint.Minter{ + InflationLastTime: time.Unix(0, 0), + Inflation: sdk.NewDecWithPrec(int64(r.Intn(99)), 2), + }, + Params: mint.Params{ + MintDenom: stakeTypes.DefaultBondDenom, + InflationRateChange: sdk.NewDecWithPrec(int64(r.Intn(99)), 2), + InflationMax: sdk.NewDecWithPrec(20, 2), + InflationMin: sdk.NewDecWithPrec(7, 2), + GoalBonded: sdk.NewDecWithPrec(67, 2), + }, + } + fmt.Printf("Selected randomly generated minting parameters: %v\n", mintGenesis) var validators []stake.Validator var delegations []stake.Delegation - // XXX Try different numbers of initially bonded validators - numInitiallyBonded := int64(50) + + valAddrs := make([]sdk.ValAddress, numInitiallyBonded) for i := 0; i < int(numInitiallyBonded); i++ { - validator := stake.NewValidator(sdk.ValAddress(accs[i].Address), accs[i].PubKey, stake.Description{}) - validator.Tokens = sdk.NewDec(100) - validator.DelegatorShares = sdk.NewDec(100) - delegation := stake.Delegation{accs[i].Address, sdk.ValAddress(accs[i].Address), sdk.NewDec(100), 0} + valAddr := sdk.ValAddress(accs[i].Address) + valAddrs[i] = valAddr + + validator := stake.NewValidator(valAddr, accs[i].PubKey, stake.Description{}) + validator.Tokens = sdk.NewDec(amount) + validator.DelegatorShares = sdk.NewDec(amount) + delegation := stake.Delegation{accs[i].Address, valAddr, sdk.NewDec(amount), 0} validators = append(validators, validator) delegations = append(delegations, delegation) } - stakeGenesis.Pool.LooseTokens = sdk.NewDec(int64(100*250) + (numInitiallyBonded * 100)) + stakeGenesis.Pool.LooseTokens = sdk.NewDec((amount * numAccs) + (numInitiallyBonded * amount)) stakeGenesis.Validators = validators stakeGenesis.Bonds = delegations - // No inflation, for now - stakeGenesis.Params.InflationMax = sdk.NewDec(0) - stakeGenesis.Params.InflationMin = sdk.NewDec(0) + genesis := GenesisState{ Accounts: genesisAccounts, StakeData: stakeGenesis, + MintData: mintGenesis, + DistrData: distr.DefaultGenesisWithValidators(valAddrs), SlashingData: slashingGenesis, GovData: govGenesis, } @@ -94,23 +161,30 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation { return []simulation.WeightedOperation{ - {100, banksim.SingleInputSendMsg(app.accountMapper, app.bankKeeper)}, + {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)}, - {100, govsim.SimulateMsgDeposit(app.govKeeper, app.stakeKeeper)}, - {100, stakesim.SimulateMsgCreateValidator(app.accountMapper, app.stakeKeeper)}, + {100, govsim.SimulateMsgDeposit(app.govKeeper)}, + {100, stakesim.SimulateMsgCreateValidator(app.accountKeeper, app.stakeKeeper)}, {5, stakesim.SimulateMsgEditValidator(app.stakeKeeper)}, - {100, stakesim.SimulateMsgDelegate(app.accountMapper, app.stakeKeeper)}, - {100, stakesim.SimulateMsgBeginUnbonding(app.accountMapper, app.stakeKeeper)}, - {100, stakesim.SimulateMsgBeginRedelegate(app.accountMapper, app.stakeKeeper)}, + {100, stakesim.SimulateMsgDelegate(app.accountKeeper, app.stakeKeeper)}, + {100, stakesim.SimulateMsgBeginUnbonding(app.accountKeeper, app.stakeKeeper)}, + {100, stakesim.SimulateMsgBeginRedelegate(app.accountKeeper, app.stakeKeeper)}, {100, slashingsim.SimulateMsgUnjail(app.slashingKeeper)}, } } func invariants(app *GaiaApp) []simulation.Invariant { return []simulation.Invariant{ - banksim.NonnegativeBalanceInvariant(app.accountMapper), + banksim.NonnegativeBalanceInvariant(app.accountKeeper), govsim.AllInvariants(), - stakesim.AllInvariants(app.bankKeeper, app.stakeKeeper, app.accountMapper), + distrsim.AllInvariants(app.distrKeeper, app.stakeKeeper), + stakesim.AllInvariants(app.bankKeeper, app.stakeKeeper, + app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper), slashingsim.AllInvariants(), } } @@ -122,7 +196,7 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { var logger log.Logger logger = log.NewNopLogger() var db dbm.DB - dir := os.TempDir() + dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") db, _ = dbm.NewGoLevelDB("Simulation", dir) defer func() { db.Close() @@ -164,7 +238,55 @@ func TestFullGaiaSimulation(t *testing.T) { } else { logger = log.NewNopLogger() } - db := dbm.NewMemDB() + var db dbm.DB + dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") + db, _ = dbm.NewGoLevelDB("Simulation", dir) + defer func() { + db.Close() + os.RemoveAll(dir) + }() + app := NewGaiaApp(logger, db, nil) + require.Equal(t, "GaiaApp", app.Name()) + + // Run randomized simulation + err := simulation.SimulateFromSeed( + t, app.BaseApp, appStateFn, seed, + testAndRunTxs(app), + []simulation.RandSetup{}, + invariants(app), + numBlocks, + blockSize, + commit, + ) + if commit { + // for memdb: + // fmt.Println("Database Size", db.Stats()["database.size"]) + fmt.Println("GoLevelDB Stats") + fmt.Println(db.Stats()["leveldb.stats"]) + fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) + } + require.Nil(t, err) +} + +func TestGaiaImportExport(t *testing.T) { + if !enabled { + t.Skip("Skipping Gaia import/export simulation") + } + + // Setup Gaia application + var logger log.Logger + if verbose { + logger = log.TestingLogger() + } else { + logger = log.NewNopLogger() + } + var db dbm.DB + dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") + db, _ = dbm.NewGoLevelDB("Simulation", dir) + defer func() { + db.Close() + os.RemoveAll(dir) + }() app := NewGaiaApp(logger, db, nil) require.Equal(t, "GaiaApp", app.Name()) @@ -179,9 +301,68 @@ func TestFullGaiaSimulation(t *testing.T) { commit, ) if commit { - fmt.Println("Database Size", db.Stats()["database.size"]) + // for memdb: + // fmt.Println("Database Size", db.Stats()["database.size"]) + fmt.Println("GoLevelDB Stats") + fmt.Println(db.Stats()["leveldb.stats"]) + fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) } require.Nil(t, err) + + fmt.Printf("Exporting genesis...\n") + + appState, _, err := app.ExportAppStateAndValidators() + if err != nil { + panic(err) + } + + fmt.Printf("Importing genesis...\n") + + newDir, _ := ioutil.TempDir("", "goleveldb-gaia-sim-2") + newDB, _ := dbm.NewGoLevelDB("Simulation-2", dir) + defer func() { + newDB.Close() + os.RemoveAll(newDir) + }() + newApp := NewGaiaApp(log.NewNopLogger(), newDB, nil) + require.Equal(t, "GaiaApp", newApp.Name()) + request := abci.RequestInitChain{ + AppStateBytes: appState, + } + newApp.InitChain(request) + newApp.Commit() + + fmt.Printf("Comparing stores...\n") + ctxA := app.NewContext(true, abci.Header{}) + ctxB := newApp.NewContext(true, abci.Header{}) + type StoreKeysPrefixes struct { + A sdk.StoreKey + B sdk.StoreKey + Prefixes [][]byte + } + 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.keySlashing, newApp.keySlashing, [][]byte{}}, + {app.keyMint, newApp.keyMint, [][]byte{}}, + {app.keyDistr, newApp.keyDistr, [][]byte{}}, + {app.keyFeeCollection, newApp.keyFeeCollection, [][]byte{}}, + {app.keyParams, newApp.keyParams, [][]byte{}}, + {app.keyGov, newApp.keyGov, [][]byte{}}, + } + for _, storeKeysPrefix := range storeKeysPrefixes { + storeKeyA := storeKeysPrefix.A + storeKeyB := storeKeysPrefix.B + prefixes := storeKeysPrefix.Prefixes + storeA := ctxA.KVStore(storeKeyA) + 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) + } + } // TODO: Make another test for the fuzzer itself, which just has noOp txs diff --git a/cmd/gaia/app/test_utils.go b/cmd/gaia/app/test_utils.go deleted file mode 100644 index 32e4c70a5998..000000000000 --- a/cmd/gaia/app/test_utils.go +++ /dev/null @@ -1,78 +0,0 @@ -package app - -import ( - "encoding/json" - "errors" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/gov" - "github.com/cosmos/cosmos-sdk/x/slashing" - "github.com/cosmos/cosmos-sdk/x/stake" - tmtypes "github.com/tendermint/tendermint/types" -) - -// NewTestGaiaAppGenState creates the core parameters for a test genesis -// initialization given a set of genesis txs, TM validators and their respective -// operating addresses. -func NewTestGaiaAppGenState( - cdc *codec.Codec, appGenTxs []json.RawMessage, tmVals []tmtypes.GenesisValidator, valOperAddrs []sdk.ValAddress, -) (GenesisState, error) { - - switch { - case len(appGenTxs) == 0: - return GenesisState{}, errors.New("must provide at least genesis transaction") - case len(tmVals) != len(valOperAddrs): - return GenesisState{}, errors.New("number of TM validators does not match number of operator addresses") - } - - // start with the default staking genesis state - stakeData := stake.DefaultGenesisState() - - // get genesis account information - genAccs := make([]GenesisAccount, len(appGenTxs)) - for i, appGenTx := range appGenTxs { - - var genTx GaiaGenTx - if err := cdc.UnmarshalJSON(appGenTx, &genTx); err != nil { - return GenesisState{}, err - } - - stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(freeFermionsAcc)) - - // create the genesis account for the given genesis tx - genAccs[i] = genesisAccountFromGenTx(genTx) - } - - for i, tmVal := range tmVals { - var issuedDelShares sdk.Dec - - // increase total supply by validator's power - power := sdk.NewInt(tmVal.Power) - stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(power)) - - // add the validator - desc := stake.NewDescription(tmVal.Name, "", "", "") - validator := stake.NewValidator(valOperAddrs[i], tmVal.PubKey, desc) - - validator, stakeData.Pool, issuedDelShares = validator.AddTokensFromDel(stakeData.Pool, power) - stakeData.Validators = append(stakeData.Validators, validator) - - // create the self-delegation from the issuedDelShares - selfDel := stake.Delegation{ - DelegatorAddr: sdk.AccAddress(validator.OperatorAddr), - ValidatorAddr: validator.OperatorAddr, - Shares: issuedDelShares, - Height: 0, - } - - stakeData.Bonds = append(stakeData.Bonds, selfDel) - } - - return GenesisState{ - Accounts: genAccs, - StakeData: stakeData, - SlashingData: slashing.DefaultGenesisState(), - GovData: gov.DefaultGenesisState(), - }, nil -} diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 04bc4c84a590..874c2a29a486 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -8,8 +8,11 @@ import ( "io/ioutil" "os" "path" + "path/filepath" "testing" + "github.com/tendermint/tendermint/types" + "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -26,6 +29,7 @@ import ( "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" ) var ( @@ -52,10 +56,10 @@ func TestGaiaCLIMinimumFees(t *testing.T) { barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) + require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) success := executeWrite(t, fmt.Sprintf( - "gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + "gaiacli tx send %v --amount=10%s --to=%s --from=foo", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) require.False(t, success) tests.WaitForNextNBlocksTM(2, port) @@ -118,40 +122,40 @@ func TestGaiaCLISend(t *testing.T) { barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) + require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) - executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10%s --to=%s --from=foo", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) - require.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak").Int64()) + 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("steak").Int64()) + require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) // Test --dry-run - success := executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo --dry-run", flags, barAddr), app.DefaultKeyPass) + success := executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10%s --to=%s --from=foo --dry-run", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) 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("steak").Int64()) + require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) // test autosequencing - executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10%s --to=%s --from=foo", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) - require.Equal(t, int64(20), barAcc.GetCoins().AmountOf("steak").Int64()) + 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("steak").Int64()) + require.Equal(t, int64(30), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) // test memo - executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo --memo 'testmemo'", flags, barAddr), app.DefaultKeyPass) + 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(2, port) barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) - require.Equal(t, int64(30), barAcc.GetCoins().AmountOf("steak").Int64()) + 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("steak").Int64()) + require.Equal(t, int64(20), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) } func TestGaiaCLIGasAuto(t *testing.T) { @@ -169,26 +173,26 @@ func TestGaiaCLIGasAuto(t *testing.T) { barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) + require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).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=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + success := executeWrite(t, fmt.Sprintf("gaiacli tx send %v --gas=10 --amount=10%s --to=%s --from=foo", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) require.False(t, success) tests.WaitForNextNBlocksTM(2, 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("steak").Int64()) + require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) // Test failure with negative gas - success = executeWrite(t, fmt.Sprintf("gaiacli tx send %v --gas=-100 --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + success = executeWrite(t, fmt.Sprintf("gaiacli tx send %v --gas=-100 --amount=10%s --to=%s --from=foo", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) require.False(t, success) // Test failure with 0 gas - success = executeWrite(t, fmt.Sprintf("gaiacli tx send %v --gas=0 --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + success = executeWrite(t, fmt.Sprintf("gaiacli tx send %v --gas=0 --amount=10%s --to=%s --from=foo", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) require.False(t, success) // Enable auto gas - success, stdout, _ := executeWriteRetStdStreams(t, fmt.Sprintf("gaiacli tx send %v --json --gas=simulate --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + 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) require.True(t, success) // check that gas wanted == gas used cdc := app.MakeCodec() @@ -202,12 +206,12 @@ func TestGaiaCLIGasAuto(t *testing.T) { tests.WaitForNextNBlocksTM(2, 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("steak").Int64()) + require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) } func TestGaiaCLICreateValidator(t *testing.T) { chainID, servAddr, port := initializeFixtures(t) - flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) + flags := fmt.Sprintf("--home=%s --chain-id=%v --node=%s", gaiacliHome, chainID, servAddr) // start gaiad server proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr)) @@ -220,24 +224,23 @@ func TestGaiaCLICreateValidator(t *testing.T) { barAddr, barPubKey := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) barCeshPubKey := sdk.MustBech32ifyConsPub(barPubKey) - executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10%s --to=%s --from=foo", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) - require.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak").Int64()) + 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("steak").Int64()) + require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) defaultParams := stake.DefaultParams() initialPool := stake.InitialPool() initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewDec(100)) // Delegate tx on GaiaAppGenState - initialPool = initialPool.ProcessProvisions(defaultParams) // provisions are added to the pool every hour // create validator - cvStr := fmt.Sprintf("gaiacli tx create-validator %v", flags) + cvStr := fmt.Sprintf("gaiacli tx stake create-validator %v", flags) cvStr += fmt.Sprintf(" --from=%s", "bar") cvStr += fmt.Sprintf(" --pubkey=%s", barCeshPubKey) - cvStr += fmt.Sprintf(" --amount=%v", "2steak") + 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") @@ -263,14 +266,18 @@ func TestGaiaCLICreateValidator(t *testing.T) { tests.WaitForNextNBlocksTM(2, port) barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) - require.Equal(t, int64(8), barAcc.GetCoins().AmountOf("steak").Int64(), "%v", barAcc) + require.Equal(t, int64(8), barAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64(), "%v", barAcc) - validator := executeGetValidator(t, fmt.Sprintf("gaiacli query validator %s --output=json %v", sdk.ValAddress(barAddr), flags)) + validator := executeGetValidator(t, fmt.Sprintf("gaiacli query stake validator %s --output=json %v", sdk.ValAddress(barAddr), flags)) require.Equal(t, validator.OperatorAddr, sdk.ValAddress(barAddr)) require.True(sdk.DecEq(t, sdk.NewDec(2), validator.Tokens)) + validatorDelegations := executeGetValidatorDelegations(t, fmt.Sprintf("gaiacli query stake delegations-to %s --output=json %v", sdk.ValAddress(barAddr), flags)) + require.Len(t, validatorDelegations, 1) + require.NotZero(t, validatorDelegations[0].Shares) + // unbond a single share - unbondStr := fmt.Sprintf("gaiacli tx unbond begin %v", flags) + unbondStr := fmt.Sprintf("gaiacli tx stake unbond begin %v", flags) unbondStr += fmt.Sprintf(" --from=%s", "bar") unbondStr += fmt.Sprintf(" --validator=%s", sdk.ValAddress(barAddr)) unbondStr += fmt.Sprintf(" --shares-amount=%v", "1") @@ -281,17 +288,21 @@ func TestGaiaCLICreateValidator(t *testing.T) { /* // 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("steak").Int64(), "%v", barAcc) + require.Equal(t, int64(9), barAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64(), "%v", barAcc) */ - validator = executeGetValidator(t, fmt.Sprintf("gaiacli query validator %s --output=json %v", sdk.ValAddress(barAddr), flags)) + validator = executeGetValidator(t, fmt.Sprintf("gaiacli query stake validator %s --output=json %v", sdk.ValAddress(barAddr), flags)) require.Equal(t, "1.0000000000", validator.Tokens.String()) - params := executeGetParams(t, fmt.Sprintf("gaiacli query parameters --output=json %v", flags)) + validatorUbds := executeGetValidatorUnbondingDelegations(t, + fmt.Sprintf("gaiacli query stake unbonding-delegations-from %s --output=json %v", + sdk.ValAddress(barAddr), flags)) + require.Len(t, validatorUbds, 1) + require.Equal(t, "1", validatorUbds[0].Balance.Amount.String()) + + params := executeGetParams(t, fmt.Sprintf("gaiacli query stake parameters --output=json %v", flags)) require.True(t, defaultParams.Equal(params)) - pool := executeGetPool(t, fmt.Sprintf("gaiacli query pool --output=json %v", flags)) - require.Equal(t, initialPool.DateLastCommissionReset, pool.DateLastCommissionReset) - require.Equal(t, initialPool.PrevBondedShares, pool.PrevBondedShares) + pool := executeGetPool(t, fmt.Sprintf("gaiacli query stake pool --output=json %v", flags)) require.Equal(t, initialPool.BondedTokens, pool.BondedTokens) } @@ -306,18 +317,22 @@ func TestGaiaCLISubmitProposal(t *testing.T) { tests.WaitForTMStart(port) tests.WaitForNextNBlocksTM(2, 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 --output=json --home=%s", gaiacliHome)) fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) + require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) - proposalsQuery := tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals %v", flags), "") + proposalsQuery, _ := tests.ExecuteT(t, fmt.Sprintf("gaiacli query gov proposals %v", flags), "") require.Equal(t, "No matching proposals found", proposalsQuery) // submit a test proposal - spStr := fmt.Sprintf("gaiacli tx submit-proposal %v", flags) + spStr := fmt.Sprintf("gaiacli tx gov submit-proposal %v", flags) spStr += fmt.Sprintf(" --from=%s", "foo") - spStr += fmt.Sprintf(" --deposit=%s", "5steak") + 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") @@ -340,18 +355,23 @@ func TestGaiaCLISubmitProposal(t *testing.T) { tests.WaitForNextNBlocksTM(2, port) fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(45), fooAcc.GetCoins().AmountOf("steak").Int64()) + require.Equal(t, int64(45), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) - proposal1 := executeGetProposal(t, fmt.Sprintf("gaiacli query proposal --proposal-id=1 --output=json %v", flags)) - require.Equal(t, int64(1), proposal1.GetProposalID()) + proposal1 := executeGetProposal(t, fmt.Sprintf("gaiacli query gov proposal --proposal-id=1 --output=json %v", flags)) + require.Equal(t, uint64(1), proposal1.GetProposalID()) require.Equal(t, gov.StatusDepositPeriod, proposal1.GetStatus()) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals %v", flags), "") + proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query gov proposals %v", flags), "") require.Equal(t, " 1 - Test", proposalsQuery) - depositStr := fmt.Sprintf("gaiacli tx deposit %v", flags) + deposit := executeGetDeposit(t, + fmt.Sprintf("gaiacli query gov deposit --proposal-id=1 --depositer=%s --output=json %v", + fooAddr, flags)) + require.Equal(t, int64(5), deposit.Amount.AmountOf(stakeTypes.DefaultBondDenom).Int64()) + + depositStr := fmt.Sprintf("gaiacli tx gov deposit %v", flags) depositStr += fmt.Sprintf(" --from=%s", "foo") - depositStr += fmt.Sprintf(" --deposit=%s", "10steak") + depositStr += fmt.Sprintf(" --deposit=%s", fmt.Sprintf("10%s", stakeTypes.DefaultBondDenom)) depositStr += fmt.Sprintf(" --proposal-id=%s", "1") // Test generate only @@ -367,13 +387,25 @@ func TestGaiaCLISubmitProposal(t *testing.T) { executeWrite(t, depositStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) + // test query deposit + deposits := executeGetDeposits(t, + fmt.Sprintf("gaiacli query gov deposits --proposal-id=1 --output=json %v", flags)) + require.Len(t, deposits, 1) + require.Equal(t, int64(15), deposits[0].Amount.AmountOf(stakeTypes.DefaultBondDenom).Int64()) + + deposit = executeGetDeposit(t, + fmt.Sprintf("gaiacli query gov deposit --proposal-id=1 --depositer=%s --output=json %v", + fooAddr, flags)) + require.Equal(t, int64(15), deposit.Amount.AmountOf(stakeTypes.DefaultBondDenom).Int64()) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(35), fooAcc.GetCoins().AmountOf("steak").Int64()) - proposal1 = executeGetProposal(t, fmt.Sprintf("gaiacli query proposal --proposal-id=1 --output=json %v", flags)) - require.Equal(t, int64(1), proposal1.GetProposalID()) + + require.Equal(t, int64(35), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) + proposal1 = executeGetProposal(t, fmt.Sprintf("gaiacli query gov proposal --proposal-id=1 --output=json %v", flags)) + require.Equal(t, uint64(1), proposal1.GetProposalID()) require.Equal(t, gov.StatusVotingPeriod, proposal1.GetStatus()) - voteStr := fmt.Sprintf("gaiacli tx vote %v", flags) + voteStr := fmt.Sprintf("gaiacli tx gov vote %v", flags) voteStr += fmt.Sprintf(" --from=%s", "foo") voteStr += fmt.Sprintf(" --proposal-id=%s", "1") voteStr += fmt.Sprintf(" --option=%s", "Yes") @@ -391,25 +423,25 @@ func TestGaiaCLISubmitProposal(t *testing.T) { executeWrite(t, voteStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - vote := executeGetVote(t, fmt.Sprintf("gaiacli query vote --proposal-id=1 --voter=%s --output=json %v", fooAddr, flags)) - require.Equal(t, int64(1), vote.ProposalID) + vote := executeGetVote(t, fmt.Sprintf("gaiacli query gov vote --proposal-id=1 --voter=%s --output=json %v", fooAddr, flags)) + require.Equal(t, uint64(1), vote.ProposalID) require.Equal(t, gov.OptionYes, vote.Option) - votes := executeGetVotes(t, fmt.Sprintf("gaiacli query votes --proposal-id=1 --output=json %v", flags)) + votes := executeGetVotes(t, fmt.Sprintf("gaiacli query gov votes --proposal-id=1 --output=json %v", flags)) require.Len(t, votes, 1) - require.Equal(t, int64(1), votes[0].ProposalID) + require.Equal(t, uint64(1), votes[0].ProposalID) require.Equal(t, gov.OptionYes, votes[0].Option) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --status=DepositPeriod %v", flags), "") + proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query gov proposals --status=DepositPeriod %v", flags), "") require.Equal(t, "No matching proposals found", proposalsQuery) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --status=VotingPeriod %v", flags), "") + proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query gov proposals --status=VotingPeriod %v", flags), "") require.Equal(t, " 1 - Test", proposalsQuery) // submit a second test proposal - spStr = fmt.Sprintf("gaiacli tx submit-proposal %v", flags) + spStr = fmt.Sprintf("gaiacli tx gov submit-proposal %v", flags) spStr += fmt.Sprintf(" --from=%s", "foo") - spStr += fmt.Sprintf(" --deposit=%s", "5steak") + 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") @@ -417,7 +449,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { executeWrite(t, spStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --latest=1 %v", flags), "") + proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query gov proposals --limit=1 %v", flags), "") require.Equal(t, " 2 - Apples", proposalsQuery) } @@ -426,7 +458,8 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) // start gaiad server - proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr)) + proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf( + "gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr)) defer proc.Stop(false) tests.WaitForTMStart(port) @@ -437,8 +470,8 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test generate sendTx with default gas success, stdout, stderr := executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli tx send %v --amount=10steak --to=%s --from=foo --generate-only", - flags, barAddr), []string{}...) + "gaiacli tx send %v --amount=10%s --to=%s --from=foo --generate-only", + flags, stakeTypes.DefaultBondDenom, barAddr), []string{}...) require.True(t, success) require.Empty(t, stderr) msg := unmarshalStdTx(t, stdout) @@ -448,8 +481,8 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test generate sendTx with --gas=$amount success, stdout, stderr = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli tx send %v --amount=10steak --to=%s --from=foo --gas=100 --generate-only", - flags, barAddr), []string{}...) + "gaiacli tx send %v --amount=10%s --to=%s --from=foo --gas=100 --generate-only", + flags, stakeTypes.DefaultBondDenom, barAddr), []string{}...) require.True(t, success) require.Empty(t, stderr) msg = unmarshalStdTx(t, stdout) @@ -459,8 +492,8 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test generate sendTx, estimate gas success, stdout, stderr = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli tx send %v --amount=10steak --to=%s --from=foo --gas=simulate --generate-only", - flags, barAddr), []string{}...) + "gaiacli tx send %v --amount=10%s --to=%s --from=foo --gas=simulate --generate-only", + flags, stakeTypes.DefaultBondDenom, barAddr), []string{}...) require.True(t, success) require.NotEmpty(t, stderr) msg = unmarshalStdTx(t, stdout) @@ -471,11 +504,11 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { unsignedTxFile := writeToNewTempFile(t, stdout) defer os.Remove(unsignedTxFile.Name()) - // Test sign --print-sigs + // Test sign --validate-signatures success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli tx sign %v --print-sigs %v", flags, unsignedTxFile.Name())) - require.True(t, success) - require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n", fooAddr.String()), stdout) + "gaiacli tx sign %v --validate-signatures %v", flags, unsignedTxFile.Name())) + 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( @@ -492,15 +525,17 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test sign --print-signatures success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli tx sign %v --print-sigs %v", flags, signedTxFile.Name())) + "gaiacli tx sign %v --validate-signatures %v", flags, signedTxFile.Name())) require.True(t, success) - require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n 0: %v\n", fooAddr.String(), fooAddr.String()), stdout) + 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("steak").Int64()) + require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) - success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf("gaiacli tx broadcast %v --json %v", flags, signedTxFile.Name())) + success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( + "gaiacli tx broadcast %v --json %v", flags, signedTxFile.Name())) require.True(t, success) var result struct { Response abci.ResponseDeliverTx @@ -511,9 +546,9 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { tests.WaitForNextNBlocksTM(2, port) barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) - require.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak").Int64()) + 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("steak").Int64()) + require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) } func TestGaiaCLIConfig(t *testing.T) { @@ -522,12 +557,11 @@ func TestGaiaCLIConfig(t *testing.T) { servAddr, port, err := server.FreeTCPAddr() require.NoError(t, err) node := fmt.Sprintf("%s:%s", servAddr, port) - chainID := executeInit(t, fmt.Sprintf("gaiad init -o --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome)) + chainID := executeInit(t, fmt.Sprintf("gaiad init -o --moniker=foo --home=%s", gaiadHome)) executeWrite(t, fmt.Sprintf("gaiacli --home=%s config", gaiadHome), gaiacliHome, node, "y") config, err := ioutil.ReadFile(path.Join(gaiacliHome, "config", "config.toml")) require.NoError(t, err) expectedConfig := fmt.Sprintf(`chain_id = "%s" -encoding = "btc" home = "%s" node = "%s" output = "text" @@ -546,7 +580,6 @@ trust_node = true // ensure it works without an initialized gaiad state expectedConfig = fmt.Sprintf(`chain_id = "" -encoding = "btc" home = "%s" node = "%s" output = "text" @@ -572,12 +605,27 @@ func initializeFixtures(t *testing.T) (chainID, servAddr, port string) { tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe-reset-all", gaiadHome), "") 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) - - chainID = executeInit(t, fmt.Sprintf("gaiad init -o --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome)) + executeWrite(t, fmt.Sprintf("gaiacli keys add --home=%s foo", gaiacliHome), app.DefaultKeyPass) executeWrite(t, fmt.Sprintf("gaiacli keys add --home=%s bar", gaiacliHome), app.DefaultKeyPass) - + fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf( + "gaiacli keys show foo --output=json --home=%s", gaiacliHome)) + chainID = executeInit(t, fmt.Sprintf("gaiad init -o --moniker=foo --home=%s", gaiadHome)) + genFile := filepath.Join(gaiadHome, "config", "genesis.json") + genDoc := readGenesisFile(t, genFile) + var appState app.GenesisState + err := codec.Cdc.UnmarshalJSON(genDoc.AppState, &appState) + require.NoError(t, err) + appState.Accounts = []app.GenesisAccount{app.NewDefaultGenesisAccount(fooAddr)} + appStateJSON, err := codec.Cdc.MarshalJSON(appState) + require.NoError(t, err) + genDoc.AppState = appStateJSON + genDoc.SaveAs(genFile) + executeWrite(t, fmt.Sprintf( + "gaiad gentx --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome), + app.DefaultKeyPass) + executeWrite(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() + servAddr, port, err = server.FreeTCPAddr() require.NoError(t, err) return } @@ -596,6 +644,18 @@ func writeToNewTempFile(t *testing.T, s string) *os.File { return fp } +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 +} + //___________________________________________________________________________________ // executors @@ -628,10 +688,10 @@ func executeWriteRetStdStreams(t *testing.T, cmdStr string, writes ...string) (b } func executeInit(t *testing.T, cmdStr string) (chainID string) { - out := tests.ExecuteT(t, cmdStr, app.DefaultKeyPass) + _, stderr := tests.ExecuteT(t, cmdStr, app.DefaultKeyPass) var initRes map[string]json.RawMessage - err := json.Unmarshal([]byte(out), &initRes) + err := json.Unmarshal([]byte(stderr), &initRes) require.NoError(t, err) err = json.Unmarshal(initRes["chain_id"], &chainID) @@ -641,7 +701,7 @@ func executeInit(t *testing.T, cmdStr string) (chainID string) { } func executeGetAddrPK(t *testing.T, cmdStr string) (sdk.AccAddress, crypto.PubKey) { - out := tests.ExecuteT(t, cmdStr, "") + out, _ := tests.ExecuteT(t, cmdStr, "") var ko keys.KeyOutput keys.UnmarshalJSON([]byte(out), &ko) @@ -655,7 +715,7 @@ func executeGetAddrPK(t *testing.T, cmdStr string) (sdk.AccAddress, crypto.PubKe } func executeGetAccount(t *testing.T, cmdStr string) auth.BaseAccount { - out := tests.ExecuteT(t, cmdStr, "") + 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) @@ -672,7 +732,7 @@ func executeGetAccount(t *testing.T, cmdStr string) auth.BaseAccount { // stake func executeGetValidator(t *testing.T, cmdStr string) stake.Validator { - out := tests.ExecuteT(t, cmdStr, "") + out, _ := tests.ExecuteT(t, cmdStr, "") var validator stake.Validator cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &validator) @@ -680,8 +740,35 @@ func executeGetValidator(t *testing.T, cmdStr string) stake.Validator { return validator } +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 +} + +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 +} + +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 executeGetPool(t *testing.T, cmdStr string) stake.Pool { - out := tests.ExecuteT(t, cmdStr, "") + out, _ := tests.ExecuteT(t, cmdStr, "") var pool stake.Pool cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &pool) @@ -690,7 +777,7 @@ func executeGetPool(t *testing.T, cmdStr string) stake.Pool { } func executeGetParams(t *testing.T, cmdStr string) stake.Params { - out := tests.ExecuteT(t, cmdStr, "") + out, _ := tests.ExecuteT(t, cmdStr, "") var params stake.Params cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), ¶ms) @@ -701,8 +788,35 @@ func executeGetParams(t *testing.T, cmdStr string) stake.Params { //___________________________________________________________________________________ // gov +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 +} + +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 +} + +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 +} + func executeGetProposal(t *testing.T, cmdStr string) gov.Proposal { - out := tests.ExecuteT(t, cmdStr, "") + out, _ := tests.ExecuteT(t, cmdStr, "") var proposal gov.Proposal cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &proposal) @@ -711,7 +825,7 @@ func executeGetProposal(t *testing.T, cmdStr string) gov.Proposal { } func executeGetVote(t *testing.T, cmdStr string) gov.Vote { - out := tests.ExecuteT(t, cmdStr, "") + out, _ := tests.ExecuteT(t, cmdStr, "") var vote gov.Vote cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &vote) @@ -720,10 +834,28 @@ func executeGetVote(t *testing.T, cmdStr string) gov.Vote { } func executeGetVotes(t *testing.T, cmdStr string) []gov.Vote { - out := tests.ExecuteT(t, cmdStr, "") + 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 } + +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 +} + +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 +} diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index ef64f7befe73..de99b0fc8fac 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -1,7 +1,11 @@ package main import ( + "os" + "path" + "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/tendermint/tendermint/libs/cli" @@ -9,33 +13,26 @@ import ( "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/cmd/gaia/app" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" - authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" - bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" - govcmd "github.com/cosmos/cosmos-sdk/x/gov/client/cli" - slashingcmd "github.com/cosmos/cosmos-sdk/x/slashing/client/cli" - stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli" _ "github.com/cosmos/cosmos-sdk/client/lcd/statik" - "github.com/cosmos/cosmos-sdk/cmd/gaia/app" - "github.com/spf13/viper" - "os" - "path" ) const ( - storeAcc = "acc" - storeGov = "gov" - storeSlashing = "slashing" - storeStake = "stake" + storeAcc = "acc" + storeGov = "gov" + storeSlashing = "slashing" + storeStake = "stake" + queryRouteStake = "stake" ) // rootCmd is the entry point for this binary var ( rootCmd = &cobra.Command{ Use: "gaiacli", - Short: "Gaia light-client", + Short: "Command line interface for interacting with gaiad", } ) @@ -43,87 +40,32 @@ func main() { cobra.EnableCommandSorting = false cdc := app.MakeCodec() + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + 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 - rootCmd.AddCommand(client.ConfigCmd()) - // add standard rpc commands - rpc.AddCommands(rootCmd) - - //Add query commands - queryCmd := &cobra.Command{ - Use: "query", - Aliases: []string{"q"}, - Short: "Querying subcommands", - } - queryCmd.AddCommand( - rpc.BlockCommand(), - rpc.ValidatorCommand(), - ) - tx.AddCommands(queryCmd, cdc) - queryCmd.AddCommand(client.LineBreak) - queryCmd.AddCommand(client.GetCommands( - authcmd.GetAccountCmd(storeAcc, cdc, authcmd.GetAccountDecoder(cdc)), - stakecmd.GetCmdQueryDelegation(storeStake, cdc), - stakecmd.GetCmdQueryDelegations(storeStake, cdc), - stakecmd.GetCmdQueryParams(storeStake, cdc), - stakecmd.GetCmdQueryPool(storeStake, cdc), - govcmd.GetCmdQueryProposal(storeGov, cdc), - govcmd.GetCmdQueryProposals(storeGov, cdc), - stakecmd.GetCmdQueryRedelegation(storeStake, cdc), - stakecmd.GetCmdQueryRedelegations(storeStake, cdc), - slashingcmd.GetCmdQuerySigningInfo(storeSlashing, cdc), - stakecmd.GetCmdQueryUnbondingDelegation(storeStake, cdc), - stakecmd.GetCmdQueryUnbondingDelegations(storeStake, cdc), - stakecmd.GetCmdQueryValidator(storeStake, cdc), - stakecmd.GetCmdQueryValidators(storeStake, cdc), - govcmd.GetCmdQueryVote(storeGov, cdc), - govcmd.GetCmdQueryVotes(storeGov, cdc), - )...) - - //Add query commands - txCmd := &cobra.Command{ - Use: "tx", - Short: "Transactions subcommands", - } - - //Add auth and bank commands - txCmd.AddCommand( - client.PostCommands( - bankcmd.GetBroadcastCommand(cdc), - authcmd.GetSignCommand(cdc, authcmd.GetAccountDecoder(cdc)), - )...) - txCmd.AddCommand(client.LineBreak) - - txCmd.AddCommand( - client.PostCommands( - stakecmd.GetCmdCreateValidator(cdc), - stakecmd.GetCmdEditValidator(cdc), - stakecmd.GetCmdDelegate(cdc), - govcmd.GetCmdDeposit(cdc), - stakecmd.GetCmdRedelegate(storeStake, cdc), - bankcmd.SendTxCmd(cdc), - govcmd.GetCmdSubmitProposal(cdc), - stakecmd.GetCmdUnbond(storeStake, cdc), - slashingcmd.GetCmdUnjail(cdc), - govcmd.GetCmdVote(cdc), - )...) + // Construct Root Command rootCmd.AddCommand( - queryCmd, - txCmd, + rpc.InitClientCommand(), + rpc.StatusCommand(), + client.ConfigCmd(), + queryCmd(cdc), + txCmd(cdc), + client.LineBreak, lcd.ServeCommand(cdc), client.LineBreak, - ) - - // add proxy, version and key info - rootCmd.AddCommand( keys.Commands(), client.LineBreak, version.VersionCmd, ) - // prepare and add flags + // Add flags and prefix all env exposed with GA executor := cli.PrepareMainCmd(rootCmd, "GA", app.DefaultCLIHome) err := initConfig(rootCmd) if err != nil { diff --git a/cmd/gaia/cmd/gaiacli/query.go b/cmd/gaia/cmd/gaiacli/query.go new file mode 100644 index 000000000000..3a806d36c262 --- /dev/null +++ b/cmd/gaia/cmd/gaiacli/query.go @@ -0,0 +1,83 @@ +package main + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/spf13/cobra" + + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + govcmd "github.com/cosmos/cosmos-sdk/x/gov/client/cli" + slashingcmd "github.com/cosmos/cosmos-sdk/x/slashing/client/cli" + stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli" + amino "github.com/tendermint/go-amino" +) + +func queryCmd(cdc *amino.Codec) *cobra.Command { + //Add query commands + queryCmd := &cobra.Command{ + Use: "query", + Aliases: []string{"q"}, + Short: "Querying subcommands", + } + + // Group staking queries under a subcommand + stakeQueryCmd := &cobra.Command{ + Use: "stake", + Short: "Querying commands for the staking module", + } + + stakeQueryCmd.AddCommand(client.GetCommands( + stakecmd.GetCmdQueryDelegation(storeStake, cdc), + stakecmd.GetCmdQueryDelegations(storeStake, cdc), + stakecmd.GetCmdQueryUnbondingDelegation(storeStake, cdc), + stakecmd.GetCmdQueryUnbondingDelegations(storeStake, cdc), + stakecmd.GetCmdQueryRedelegation(storeStake, cdc), + stakecmd.GetCmdQueryRedelegations(storeStake, cdc), + stakecmd.GetCmdQueryValidator(storeStake, cdc), + stakecmd.GetCmdQueryValidators(storeStake, cdc), + stakecmd.GetCmdQueryValidatorDelegations(storeStake, cdc), + stakecmd.GetCmdQueryValidatorUnbondingDelegations(queryRouteStake, cdc), + stakecmd.GetCmdQueryValidatorRedelegations(queryRouteStake, cdc), + stakecmd.GetCmdQueryParams(storeStake, cdc), + stakecmd.GetCmdQueryPool(storeStake, cdc))...) + + // Group gov queries under a subcommand + govQueryCmd := &cobra.Command{ + Use: "gov", + Short: "Querying commands for the governance module", + } + + govQueryCmd.AddCommand(client.GetCommands( + govcmd.GetCmdQueryProposal(storeGov, cdc), + govcmd.GetCmdQueryProposals(storeGov, cdc), + govcmd.GetCmdQueryVote(storeGov, cdc), + govcmd.GetCmdQueryVotes(storeGov, cdc), + govcmd.GetCmdQueryParams(storeGov, cdc), + govcmd.GetCmdQueryDeposit(storeGov, cdc), + govcmd.GetCmdQueryDeposits(storeGov, cdc))...) + + // Group slashing queries under a subcommand + slashingQueryCmd := &cobra.Command{ + Use: "slashing", + Short: "Querying commands for the slashing module", + } + + slashingQueryCmd.AddCommand(client.GetCommands( + slashingcmd.GetCmdQuerySigningInfo(storeSlashing, cdc))...) + + // Query commcmmand structure + queryCmd.AddCommand( + rpc.BlockCommand(), + rpc.ValidatorCommand(), + tx.SearchTxCmd(cdc), + tx.QueryTxCmd(cdc), + client.LineBreak, + client.GetCommands(authcmd.GetAccountCmd(storeAcc, cdc, authcmd.GetAccountDecoder(cdc)))[0], + stakeQueryCmd, + govQueryCmd, + slashingQueryCmd, + ) + + return queryCmd +} diff --git a/cmd/gaia/cmd/gaiacli/tx.go b/cmd/gaia/cmd/gaiacli/tx.go new file mode 100644 index 000000000000..fa0abc4ad8c3 --- /dev/null +++ b/cmd/gaia/cmd/gaiacli/tx.go @@ -0,0 +1,83 @@ +package main + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/spf13/cobra" + + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" + distrcmd "github.com/cosmos/cosmos-sdk/x/distribution/client/cli" + govcmd "github.com/cosmos/cosmos-sdk/x/gov/client/cli" + slashingcmd "github.com/cosmos/cosmos-sdk/x/slashing/client/cli" + stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli" + amino "github.com/tendermint/go-amino" +) + +func txCmd(cdc *amino.Codec) *cobra.Command { + //Add transaction generation commands + txCmd := &cobra.Command{ + Use: "tx", + Short: "Transactions subcommands", + } + + stakeTxCmd := &cobra.Command{ + Use: "stake", + Short: "Staking transaction subcommands", + } + + stakeTxCmd.AddCommand(client.PostCommands( + stakecmd.GetCmdCreateValidator(cdc), + stakecmd.GetCmdEditValidator(cdc), + stakecmd.GetCmdDelegate(cdc), + stakecmd.GetCmdRedelegate(storeStake, cdc), + stakecmd.GetCmdUnbond(storeStake, cdc), + )...) + + distTxCmd := &cobra.Command{ + Use: "dist", + Short: "Distribution transactions subcommands", + } + + distTxCmd.AddCommand(client.PostCommands( + distrcmd.GetCmdWithdrawRewards(cdc), + distrcmd.GetCmdSetWithdrawAddr(cdc), + )...) + + govTxCmd := &cobra.Command{ + Use: "gov", + Short: "Governance transactions subcommands", + } + + govTxCmd.AddCommand(client.PostCommands( + govcmd.GetCmdDeposit(cdc), + govcmd.GetCmdVote(cdc), + govcmd.GetCmdSubmitProposal(cdc), + )...) + + slashingTxCmd := &cobra.Command{ + Use: "slashing", + Short: "Slashing transactions subcommands", + } + + slashingTxCmd.AddCommand(client.PostCommands( + slashingcmd.GetCmdUnjail(cdc), + )...) + + txCmd.AddCommand( + //Add auth and bank commands + client.PostCommands( + bankcmd.SendTxCmd(cdc), + bankcmd.GetBroadcastCommand(cdc), + authcmd.GetSignCommand(cdc, authcmd.GetAccountDecoder(cdc)), + )...) + + txCmd.AddCommand( + client.LineBreak, + stakeTxCmd, + distTxCmd, + govTxCmd, + slashingTxCmd, + ) + + return txCmd +} diff --git a/cmd/gaia/cmd/gaiad/main.go b/cmd/gaia/cmd/gaiad/main.go index adfb12d5735d..ef7b39d1111e 100644 --- a/cmd/gaia/cmd/gaiad/main.go +++ b/cmd/gaia/cmd/gaiad/main.go @@ -18,10 +18,18 @@ import ( "github.com/cosmos/cosmos-sdk/cmd/gaia/app" gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init" "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" ) func main() { cdc := app.MakeCodec() + + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + config.Seal() + ctx := server.NewDefaultContext() cobra.EnableCommandSorting = false rootCmd := &cobra.Command{ @@ -31,7 +39,10 @@ func main() { } appInit := app.GaiaAppInit() rootCmd.AddCommand(gaiaInit.InitCmd(ctx, cdc, appInit)) - rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, appInit)) + rootCmd.AddCommand(gaiaInit.CollectGenTxsCmd(ctx, cdc)) + rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, server.AppInit{})) + rootCmd.AddCommand(gaiaInit.GenTxCmd(ctx, cdc)) + rootCmd.AddCommand(gaiaInit.AddGenesisAccountCmd(ctx, cdc)) server.AddCommands(ctx, cdc, rootCmd, appInit, newApp, exportAppStateAndTMValidators) diff --git a/cmd/gaia/cmd/gaiadebug/hack.go b/cmd/gaia/cmd/gaiadebug/hack.go index db22da0b5270..734a83df304f 100644 --- a/cmd/gaia/cmd/gaiadebug/hack.go +++ b/cmd/gaia/cmd/gaiadebug/hack.go @@ -138,7 +138,7 @@ type GaiaApp struct { tkeyParams *sdk.TransientStoreKey // Manage getting and setting accounts - accountMapper auth.AccountMapper + accountKeeper auth.AccountKeeper feeCollectionKeeper auth.FeeCollectionKeeper bankKeeper bank.Keeper stakeKeeper stake.Keeper @@ -165,15 +165,15 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp tkeyParams: sdk.NewTransientStoreKey("transient_params"), } - // define the accountMapper - app.accountMapper = auth.NewAccountMapper( + // define the accountKeeper + app.accountKeeper = auth.NewAccountKeeper( app.cdc, app.keyAccount, // target store auth.ProtoBaseAccount, // prototype ) // add handlers - app.bankKeeper = bank.NewBaseKeeper(app.accountMapper) + 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), app.RegisterCodespace(stake.DefaultCodespace)) app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), app.RegisterCodespace(slashing.DefaultCodespace)) @@ -187,7 +187,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp app.SetInitChainer(app.initChainer) app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) - app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) + app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper)) app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyStake, app.keySlashing, app.keyParams) app.MountStore(app.tkeyParams, sdk.StoreTypeTransient) err := app.LoadLatestVersion(app.keyMain) @@ -246,7 +246,7 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci // load the accounts for _, gacc := range genesisState.Accounts { acc := gacc.ToAccount() - app.accountMapper.SetAccount(ctx, acc) + app.accountKeeper.SetAccount(ctx, acc) } // load the initial stake information diff --git a/cmd/gaia/cmd/gaiadebug/main.go b/cmd/gaia/cmd/gaiadebug/main.go index 73840537b2bc..240a74cb4878 100644 --- a/cmd/gaia/cmd/gaiadebug/main.go +++ b/cmd/gaia/cmd/gaiadebug/main.go @@ -19,6 +19,13 @@ import ( ) func init() { + + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + config.Seal() + rootCmd.AddCommand(txCmd) rootCmd.AddCommand(pubkeyCmd) rootCmd.AddCommand(addrCmd) @@ -213,7 +220,7 @@ func runTxCmd(cmd *cobra.Command, args []string) error { var tx = auth.StdTx{} cdc := gaia.MakeCodec() - err = cdc.UnmarshalBinary(txBytes, &tx) + err = cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) if err != nil { return err } diff --git a/cmd/gaia/init/collect.go b/cmd/gaia/init/collect.go new file mode 100644 index 000000000000..cdfc1688c511 --- /dev/null +++ b/cmd/gaia/init/collect.go @@ -0,0 +1,118 @@ +package init + +import ( + "encoding/json" + "path/filepath" + + "github.com/cosmos/cosmos-sdk/client" + "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/x/auth" + "github.com/spf13/cobra" + "github.com/spf13/viper" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/types" +) + +type initConfig struct { + ChainID string + GenTxsDir string + Name string + NodeID string + ValPubKey crypto.PubKey +} + +// nolint +func CollectGenTxsCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "collect-gentxs", + Short: "Collect genesis txs and output a genesis.json file", + RunE: func(_ *cobra.Command, _ []string) error { + config := ctx.Config + config.SetRoot(viper.GetString(cli.HomeFlag)) + name := viper.GetString(client.FlagName) + + nodeID, valPubKey, err := InitializeNodeValidatorFiles(config) + if err != nil { + return err + } + + genDoc, err := loadGenesisDoc(cdc, config.GenesisFile()) + if err != nil { + return err + } + + toPrint := printInfo{ + Moniker: config.Moniker, + ChainID: genDoc.ChainID, + NodeID: nodeID, + } + + initCfg := initConfig{ + ChainID: genDoc.ChainID, + GenTxsDir: filepath.Join(config.RootDir, "config", "gentx"), + Name: name, + NodeID: nodeID, + ValPubKey: valPubKey, + } + + appMessage, err := genAppStateFromConfig(cdc, config, initCfg, genDoc) + if err != nil { + return err + } + + toPrint.AppMessage = appMessage + + // print out some key information + return displayInfo(cdc, toPrint) + }, + } + + cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") + return cmd +} + +func genAppStateFromConfig( + cdc *codec.Codec, config *cfg.Config, initCfg initConfig, genDoc types.GenesisDoc, +) (appState json.RawMessage, err error) { + + genFile := config.GenesisFile() + var ( + appGenTxs []auth.StdTx + persistentPeers string + genTxs []json.RawMessage + jsonRawTx json.RawMessage + ) + + // process genesis transactions, else create default genesis.json + appGenTxs, persistentPeers, err = app.CollectStdTxs( + cdc, config.Moniker, initCfg.GenTxsDir, genDoc, + ) + if err != nil { + return + } + + genTxs = make([]json.RawMessage, len(appGenTxs)) + config.P2P.PersistentPeers = persistentPeers + + for i, stdTx := range appGenTxs { + jsonRawTx, err = cdc.MarshalJSON(stdTx) + if err != nil { + return + } + genTxs[i] = jsonRawTx + } + + cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) + + appState, err = app.GaiaAppGenStateJSON(cdc, genDoc, genTxs) + if err != nil { + return + } + + err = ExportGenesisFile(genFile, initCfg.ChainID, nil, appState) + return +} diff --git a/cmd/gaia/init/genesis_accts.go b/cmd/gaia/init/genesis_accts.go new file mode 100644 index 000000000000..3d43712fcc71 --- /dev/null +++ b/cmd/gaia/init/genesis_accts.go @@ -0,0 +1,66 @@ +package init + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/libs/common" +) + +// 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]]", + Short: "Add genesis account to genesis.json", + Args: cobra.ExactArgs(2), + RunE: func(_ *cobra.Command, args []string) error { + config := ctx.Config + config.SetRoot(viper.GetString(cli.HomeFlag)) + + addr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + coins, err := sdk.ParseCoins(args[1]) + if err != nil { + return err + } + coins.Sort() + + genFile := config.GenesisFile() + if !common.FileExists(genFile) { + return fmt.Errorf("%s does not exist, run `gaiad init` first", genFile) + } + genDoc, err := loadGenesisDoc(cdc, genFile) + if err != nil { + return err + } + + var appState app.GenesisState + if err = cdc.UnmarshalJSON(genDoc.AppState, &appState); err != nil { + return err + } + + acc := auth.NewBaseAccountWithAddress(addr) + acc.Coins = coins + appState.Accounts = append(appState.Accounts, app.NewGenesisAccount(&acc)) + + appStateJSON, err := cdc.MarshalJSON(appState) + if err != nil { + return err + } + + return ExportGenesisFile(genFile, genDoc.ChainID, nil, appStateJSON) + }, + } + + cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") + return cmd +} diff --git a/cmd/gaia/init/gentx.go b/cmd/gaia/init/gentx.go new file mode 100644 index 000000000000..449eb2b85bf8 --- /dev/null +++ b/cmd/gaia/init/gentx.go @@ -0,0 +1,145 @@ +package init + +import ( + "fmt" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "github.com/cosmos/cosmos-sdk/x/stake/client/cli" + stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/spf13/cobra" + "github.com/spf13/viper" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + tmcli "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/libs/common" + "io/ioutil" + "os" + "path/filepath" +) + +const ( + defaultAmount = "100" + stakeTypes.DefaultBondDenom + defaultCommissionRate = "0.1" + defaultCommissionMaxRate = "0.2" + defaultCommissionMaxChangeRate = "0.01" +) + +// GenTxCmd builds the gaiad gentx command. +// nolint: errcheck +func GenTxCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "gentx", + Short: "Generate a genesis tx carrying a self delegation", + Long: fmt.Sprintf(`This command is an alias of the 'gaiad tx create-validator' command'. + +It creates a genesis piece carrying a self delegation with the +following delegation and commission default parameters: + + delegation amount: %s + commission rate: %s + commission max rate: %s + commission max change rate: %s +`, defaultAmount, defaultCommissionRate, defaultCommissionMaxRate, defaultCommissionMaxChangeRate), + RunE: func(cmd *cobra.Command, args []string) error { + + config := ctx.Config + config.SetRoot(viper.GetString(tmcli.HomeFlag)) + nodeID, valPubKey, err := InitializeNodeValidatorFiles(ctx.Config) + if err != nil { + return err + } + ip, err := server.ExternalIP() + if err != nil { + return err + } + genDoc, err := loadGenesisDoc(cdc, config.GenesisFile()) + if err != nil { + return err + } + + // Read --pubkey, if empty take it from priv_validator.json + if valPubKeyString := viper.GetString(cli.FlagPubKey); valPubKeyString != "" { + valPubKey, err = sdk.GetConsPubKeyBech32(valPubKeyString) + if err != nil { + return err + } + } + // Run gaiad tx create-validator + prepareFlagsForTxCreateValidator(config, nodeID, ip, genDoc.ChainID, valPubKey) + createValidatorCmd := cli.GetCmdCreateValidator(cdc) + + w, err := ioutil.TempFile("", "gentx") + if err != nil { + return err + } + unsignedGenTxFilename := w.Name() + defer os.Remove(unsignedGenTxFilename) + os.Stdout = w + if err = createValidatorCmd.RunE(nil, args); err != nil { + return err + } + w.Close() + + prepareFlagsForTxSign() + signCmd := authcmd.GetSignCommand(cdc, authcmd.GetAccountDecoder(cdc)) + if w, err = prepareOutputFile(config.RootDir, nodeID); err != nil { + return err + } + os.Stdout = w + return signCmd.RunE(nil, []string{unsignedGenTxFilename}) + }, + } + + cmd.Flags().String(tmcli.HomeFlag, app.DefaultNodeHome, "node's home directory") + cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") + cmd.Flags().String(client.FlagName, "", "name of private key with which to sign the gentx") + cmd.Flags().AddFlagSet(cli.FsCommissionCreate) + cmd.Flags().AddFlagSet(cli.FsAmount) + cmd.Flags().AddFlagSet(cli.FsPk) + cmd.MarkFlagRequired(client.FlagName) + return cmd +} + +func prepareFlagsForTxCreateValidator(config *cfg.Config, nodeID, ip, chainID string, + valPubKey crypto.PubKey) { + viper.Set(tmcli.HomeFlag, viper.GetString(flagClientHome)) // --home + viper.Set(client.FlagChainID, chainID) + viper.Set(client.FlagFrom, viper.GetString(client.FlagName)) // --from + viper.Set(cli.FlagNodeID, nodeID) // --node-id + viper.Set(cli.FlagIP, ip) // --ip + viper.Set(cli.FlagPubKey, sdk.MustBech32ifyConsPub(valPubKey)) // --pubkey + viper.Set(cli.FlagGenesisFormat, true) // --genesis-format + viper.Set(cli.FlagMoniker, config.Moniker) // --moniker + if config.Moniker == "" { + viper.Set(cli.FlagMoniker, viper.GetString(client.FlagName)) + } + if viper.GetString(cli.FlagAmount) == "" { + viper.Set(cli.FlagAmount, defaultAmount) + } + if viper.GetString(cli.FlagCommissionRate) == "" { + viper.Set(cli.FlagCommissionRate, defaultCommissionRate) + } + if viper.GetString(cli.FlagCommissionMaxRate) == "" { + viper.Set(cli.FlagCommissionMaxRate, defaultCommissionMaxRate) + } + if viper.GetString(cli.FlagCommissionMaxChangeRate) == "" { + viper.Set(cli.FlagCommissionMaxChangeRate, defaultCommissionMaxChangeRate) + } +} + +func prepareFlagsForTxSign() { + viper.Set("offline", true) +} + +func prepareOutputFile(rootDir, nodeID string) (w *os.File, err error) { + writePath := filepath.Join(rootDir, "config", "gentx") + if err = common.EnsureDir(writePath, 0700); err != nil { + return + } + filename := filepath.Join(writePath, fmt.Sprintf("gentx-%v.json", nodeID)) + return os.Create(filename) +} diff --git a/cmd/gaia/init/init.go b/cmd/gaia/init/init.go index a04c1d2ae77e..1f12da6bde7c 100644 --- a/cmd/gaia/init/init.go +++ b/cmd/gaia/init/init.go @@ -3,319 +3,97 @@ package init import ( "encoding/json" "fmt" - "io/ioutil" "os" - "path" "path/filepath" - "sort" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server" - servercfg "github.com/cosmos/cosmos-sdk/server/config" "github.com/spf13/cobra" "github.com/spf13/viper" cfg "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/libs/cli" "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/p2p" - "github.com/tendermint/tendermint/privval" - "github.com/tendermint/tendermint/types" ) -// get cmd to initialize all files for tendermint and application -func GenTxCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command { - cmd := &cobra.Command{ - Use: "gen-tx", - Short: "Create genesis transaction file (under [--home]/config/gentx/gentx-[nodeID].json)", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, args []string) error { - config := ctx.Config - config.SetRoot(viper.GetString(cli.HomeFlag)) - - ip := viper.GetString(server.FlagIP) - if len(ip) == 0 { - eip, err := server.ExternalIP() - if err != nil { - return err - } - ip = eip - } +const ( + flagOverwrite = "overwrite" + flagClientHome = "home-client" + flagMoniker = "moniker" +) - genTxConfig := servercfg.GenTx{ - viper.GetString(server.FlagName), - viper.GetString(server.FlagClientHome), - viper.GetBool(server.FlagOWK), - ip, - } - cliPrint, genTxFile, err := gentxWithConfig(cdc, appInit, config, genTxConfig) - if err != nil { - return err - } - toPrint := struct { - AppMessage json.RawMessage `json:"app_message"` - GenTxFile json.RawMessage `json:"gen_tx_file"` - }{ - cliPrint, - genTxFile, - } - out, err := codec.MarshalJSONIndent(cdc, toPrint) - if err != nil { - return err - } - fmt.Println(string(out)) - return nil - }, - } - cmd.Flags().String(server.FlagIP, "", "external facing IP to use if left blank IP will be retrieved from this machine") - cmd.Flags().AddFlagSet(appInit.FlagsAppGenTx) - return cmd +type printInfo struct { + Moniker string `json:"moniker"` + ChainID string `json:"chain_id"` + NodeID string `json:"node_id"` + AppMessage json.RawMessage `json:"app_message"` } -// NOTE: This will update (write) the config file with -// updated name (moniker) for node. -func gentxWithConfig(cdc *codec.Codec, appInit server.AppInit, config *cfg.Config, genTxConfig servercfg.GenTx) ( - cliPrint json.RawMessage, genTxFile json.RawMessage, err error) { - nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) - if err != nil { - return - } - nodeID := string(nodeKey.ID()) - pubKey := readOrCreatePrivValidator(config) - - appGenTx, cliPrint, validator, err := appInit.AppGenTx(cdc, pubKey, genTxConfig) - if err != nil { - return - } - - tx := server.GenesisTx{ - NodeID: nodeID, - IP: genTxConfig.IP, - Validator: validator, - AppGenTx: appGenTx, - } - bz, err := codec.MarshalJSONIndent(cdc, tx) +// nolint: errcheck +func displayInfo(cdc *codec.Codec, info printInfo) error { + out, err := codec.MarshalJSONIndent(cdc, info) if err != nil { - return - } - genTxFile = json.RawMessage(bz) - name := fmt.Sprintf("gentx-%v.json", nodeID) - writePath := filepath.Join(config.RootDir, "config", "gentx") - file := filepath.Join(writePath, name) - err = common.EnsureDir(writePath, 0700) - if err != nil { - return - } - err = common.WriteFile(file, bz, 0644) - if err != nil { - return + return err } - - // Write updated config with moniker - config.Moniker = genTxConfig.Name - configFilePath := filepath.Join(config.RootDir, "config", "config.toml") - cfg.WriteConfigFile(configFilePath, config) - - return + fmt.Fprintf(os.Stderr, "%s\n", string(out)) + return nil } // get cmd to initialize all files for tendermint and application -// nolint: golint +// nolint func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command { cmd := &cobra.Command{ Use: "init", - Short: "Initialize genesis config, priv-validator file, and p2p-node file", + Short: "Initialize private validator, p2p, genesis, and application configuration files", + Long: `Initialize validators's and node's configuration files.`, Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { - config := ctx.Config config.SetRoot(viper.GetString(cli.HomeFlag)) - initConfig := server.InitConfig{ - viper.GetString(server.FlagChainID), - viper.GetBool(server.FlagWithTxs), - filepath.Join(config.RootDir, "config", "gentx"), - viper.GetBool(server.FlagOverwrite), - } - chainID, nodeID, appMessage, err := initWithConfig(cdc, appInit, config, initConfig) - if err != nil { - return err + chainID := viper.GetString(client.FlagChainID) + if chainID == "" { + chainID = fmt.Sprintf("test-chain-%v", common.RandStr(6)) } - // print out some key information - 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) + + nodeID, _, err := InitializeNodeValidatorFiles(config) if err != nil { return err } - fmt.Println(string(out)) - return nil - }, - } - cmd.Flags().BoolP(server.FlagOverwrite, "o", false, "overwrite the genesis.json file") - cmd.Flags().String(server.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") - cmd.Flags().Bool(server.FlagWithTxs, false, "apply existing genesis transactions from [--home]/config/gentx/") - cmd.Flags().AddFlagSet(appInit.FlagsAppGenState) - cmd.Flags().AddFlagSet(appInit.FlagsAppGenTx) // need to add this flagset for when no GenTx's provided - cmd.AddCommand(GenTxCmd(ctx, cdc, appInit)) - return cmd -} - -func initWithConfig(cdc *codec.Codec, appInit server.AppInit, config *cfg.Config, initConfig server.InitConfig) ( - chainID string, nodeID string, appMessage json.RawMessage, err error) { - nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) - if err != nil { - return - } - nodeID = string(nodeKey.ID()) - pubKey := readOrCreatePrivValidator(config) - if initConfig.ChainID == "" { - initConfig.ChainID = fmt.Sprintf("test-chain-%v", common.RandStr(6)) - } - chainID = initConfig.ChainID + config.Moniker = viper.GetString(flagMoniker) - genFile := config.GenesisFile() - if !initConfig.Overwrite && common.FileExists(genFile) { - err = fmt.Errorf("genesis.json file already exists: %v", genFile) - return - } + var appState json.RawMessage + genFile := config.GenesisFile() - // process genesis transactions, or otherwise create one for defaults - var appGenTxs []json.RawMessage - var validators []types.GenesisValidator - var persistentPeers string - - if initConfig.GenTxs { - validators, appGenTxs, persistentPeers, err = processGenTxs(initConfig.GenTxsDir, cdc) - if err != nil { - return - } - config.P2P.PersistentPeers = persistentPeers - configFilePath := filepath.Join(config.RootDir, "config", "config.toml") - cfg.WriteConfigFile(configFilePath, config) - } else { - genTxConfig := servercfg.GenTx{ - viper.GetString(server.FlagName), - viper.GetString(server.FlagClientHome), - viper.GetBool(server.FlagOWK), - "127.0.0.1", - } - - // Write updated config with moniker - config.Moniker = genTxConfig.Name - configFilePath := filepath.Join(config.RootDir, "config", "config.toml") - cfg.WriteConfigFile(configFilePath, config) - appGenTx, am, validator, err := appInit.AppGenTx(cdc, pubKey, genTxConfig) - appMessage = am - if err != nil { - return "", "", nil, err - } - validators = []types.GenesisValidator{validator} - appGenTxs = []json.RawMessage{appGenTx} - } - - appState, err := appInit.AppGenState(cdc, appGenTxs) - if err != nil { - return - } - - err = writeGenesisFile(cdc, genFile, initConfig.ChainID, validators, appState) - if err != nil { - return - } - - return -} - -// append a genesis-piece -func processGenTxs(genTxsDir string, cdc *codec.Codec) ( - validators []types.GenesisValidator, appGenTxs []json.RawMessage, persistentPeers string, err error) { - - var fos []os.FileInfo - fos, err = ioutil.ReadDir(genTxsDir) - if err != nil { - return - } - - genTxs := make(map[string]server.GenesisTx) - var nodeIDs []string - for _, fo := range fos { - filename := path.Join(genTxsDir, fo.Name()) - if !fo.IsDir() && (path.Ext(filename) != ".json") { - continue - } - - // get the genTx - var bz []byte - bz, err = ioutil.ReadFile(filename) - if err != nil { - return - } - var genTx server.GenesisTx - err = cdc.UnmarshalJSON(bz, &genTx) - if err != nil { - return - } - - genTxs[genTx.NodeID] = genTx - nodeIDs = append(nodeIDs, genTx.NodeID) - } - - sort.Strings(nodeIDs) - - for _, nodeID := range nodeIDs { - genTx := genTxs[nodeID] - - // combine some stuff - validators = append(validators, genTx.Validator) - appGenTxs = append(appGenTxs, genTx.AppGenTx) + if appState, err = initializeEmptyGenesis(cdc, genFile, chainID, + viper.GetBool(flagOverwrite)); err != nil { + return err + } - // Add a persistent peer - comma := "," - if len(persistentPeers) == 0 { - comma = "" - } - persistentPeers += fmt.Sprintf("%s%s@%s:26656", comma, genTx.NodeID, genTx.IP) - } + if err = ExportGenesisFile(genFile, chainID, nil, appState); err != nil { + return err + } - return -} + toPrint := printInfo{ + ChainID: chainID, + Moniker: config.Moniker, + NodeID: nodeID, + AppMessage: appState, + } -// read of create the private key file for this config -func readOrCreatePrivValidator(tmConfig *cfg.Config) crypto.PubKey { - // private validator - privValFile := tmConfig.PrivValidatorFile() - var privValidator *privval.FilePV - if common.FileExists(privValFile) { - privValidator = privval.LoadFilePV(privValFile) - } else { - privValidator = privval.GenFilePV(privValFile) - privValidator.Save() - } - return privValidator.GetPubKey() -} + cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) -// writeGenesisFile creates and writes the genesis configuration to disk. An -// error is returned if building or writing the configuration to file fails. -// nolint: unparam -func writeGenesisFile(cdc *codec.Codec, genesisFile, chainID string, validators []types.GenesisValidator, appState json.RawMessage) error { - genDoc := types.GenesisDoc{ - ChainID: chainID, - Validators: validators, - AppState: appState, + return displayInfo(cdc, toPrint) + }, } - if err := genDoc.ValidateAndComplete(); err != nil { - return err - } + cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") + cmd.Flags().BoolP(flagOverwrite, "o", false, "overwrite the genesis.json file") + cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") + cmd.Flags().String(flagMoniker, "", "set the validator's moniker") + cmd.MarkFlagRequired(flagMoniker) - return genDoc.SaveAs(genesisFile) + return cmd } diff --git a/cmd/gaia/init/init_test.go b/cmd/gaia/init/init_test.go index 3a7f0a358d47..d1d9b9ce2d6a 100644 --- a/cmd/gaia/init/init_test.go +++ b/cmd/gaia/init/init_test.go @@ -8,43 +8,69 @@ import ( "testing" "time" - "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/tendermint/tendermint/libs/cli" + "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/server/mock" "github.com/stretchr/testify/require" abciServer "github.com/tendermint/tendermint/abci/server" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" "github.com/tendermint/tendermint/libs/log" + + "github.com/spf13/viper" ) func TestInitCmd(t *testing.T) { defer server.SetupViper(t)() + defer setupClientHome(t)() logger := log.NewNopLogger() cfg, err := tcmd.ParseConfig() require.Nil(t, err) + ctx := server.NewContext(cfg, logger) - cdc := codec.New() + cdc := app.MakeCodec() appInit := server.AppInit{ AppGenState: mock.AppGenState, - AppGenTx: mock.AppGenTx, } cmd := InitCmd(ctx, cdc, appInit) + + viper.Set(flagMoniker, "gaianode-test") + err = cmd.RunE(nil, nil) require.NoError(t, err) } +func setupClientHome(t *testing.T) func() { + clientDir, err := ioutil.TempDir("", "mock-sdk-cmd") + require.Nil(t, err) + viper.Set(flagClientHome, clientDir) + return func() { + if err := os.RemoveAll(clientDir); err != nil { + // TODO: Handle with #870 + panic(err) + } + } +} + func TestEmptyState(t *testing.T) { defer server.SetupViper(t)() + defer setupClientHome(t)() + logger := log.NewNopLogger() cfg, err := tcmd.ParseConfig() require.Nil(t, err) + ctx := server.NewContext(cfg, logger) - cdc := codec.New() + cdc := app.MakeCodec() appInit := server.AppInit{ - AppGenTx: mock.AppGenTx, AppGenState: mock.AppGenStateEmpty, } + + viper.Set(flagMoniker, "gaianode-test") + cmd := InitCmd(ctx, cdc, appInit) err = cmd.RunE(nil, nil) require.NoError(t, err) @@ -53,6 +79,7 @@ func TestEmptyState(t *testing.T) { r, w, _ := os.Pipe() os.Stdout = w cmd = server.ExportCmd(ctx, cdc, nil) + err = cmd.RunE(nil, nil) require.NoError(t, err) @@ -80,15 +107,17 @@ func TestStartStandAlone(t *testing.T) { defer func() { os.RemoveAll(home) }() + viper.Set(cli.HomeFlag, home) + viper.Set(client.FlagName, "moniker") + defer setupClientHome(t)() logger := log.NewNopLogger() cfg, err := tcmd.ParseConfig() require.Nil(t, err) ctx := server.NewContext(cfg, logger) - cdc := codec.New() + cdc := app.MakeCodec() appInit := server.AppInit{ AppGenState: mock.AppGenState, - AppGenTx: mock.AppGenTx, } initCmd := InitCmd(ctx, cdc, appInit) err = initCmd.RunE(nil, nil) @@ -109,3 +138,19 @@ func TestStartStandAlone(t *testing.T) { svr.Stop() } } + +func TestInitNodeValidatorFiles(t *testing.T) { + home, err := ioutil.TempDir("", "mock-sdk-cmd") + require.Nil(t, err) + defer func() { + os.RemoveAll(home) + }() + viper.Set(cli.HomeFlag, home) + viper.Set(client.FlagName, "moniker") + cfg, err := tcmd.ParseConfig() + require.Nil(t, err) + nodeID, valPubKey, err := InitializeNodeValidatorFiles(cfg) + require.Nil(t, err) + require.NotEqual(t, "", nodeID) + require.NotEqual(t, 0, len(valPubKey.Bytes())) +} diff --git a/cmd/gaia/init/testnet.go b/cmd/gaia/init/testnet.go index 48b714f2de21..b0676515c144 100644 --- a/cmd/gaia/init/testnet.go +++ b/cmd/gaia/init/testnet.go @@ -1,37 +1,46 @@ package init import ( + "encoding/json" "fmt" - "github.com/cosmos/cosmos-sdk/server" "net" - "path/filepath" - - "github.com/spf13/cobra" - - gc "github.com/cosmos/cosmos-sdk/server/config" - "os" + "path/filepath" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + 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/server" + "github.com/spf13/cobra" "github.com/spf13/viper" cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" ) var ( - nodeDirPrefix = "node-dir-prefix" - nValidators = "v" - outputDir = "output-dir" - nodeDaemonHome = "node-daemon-home" - nodeCliHome = "node-cli-home" - - startingIPAddress = "starting-ip-address" + flagNodeDirPrefix = "node-dir-prefix" + flagNumValidators = "v" + flagOutputDir = "output-dir" + flagNodeDaemonHome = "node-daemon-home" + flagNodeCliHome = "node-cli-home" + flagStartingIPAddress = "starting-ip-address" ) const nodeDirPerm = 0755 // get cmd to initialize all files for tendermint testnet and application -func TestnetFilesCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command { +func TestnetFilesCmd(ctx *server.Context, cdc *codec.Codec, + appInit server.AppInit) *cobra.Command { + cmd := &cobra.Command{ Use: "testnet", Short: "Initialize files for a Gaiad testnet", @@ -41,43 +50,65 @@ necessary files (private validator, genesis, config, etc.). Note, strict routability for addresses is turned off in the config file. Example: - - gaiad testnet --v 4 --o ./output --starting-ip-address 192.168.10.2 + gaiad testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2 `, RunE: func(_ *cobra.Command, _ []string) error { config := ctx.Config - err := testnetWithConfig(config, cdc, appInit) - return err + return initTestnet(config, cdc) }, } - cmd.Flags().Int(nValidators, 4, - "Number of validators to initialize the testnet with") - cmd.Flags().StringP(outputDir, "o", "./mytestnet", - "Directory to store initialization data for the testnet") - cmd.Flags().String(nodeDirPrefix, "node", - "Prefix the directory name for each node with (node results in node0, node1, ...)") - cmd.Flags().String(nodeDaemonHome, "gaiad", - "Home directory of the node's daemon configuration") - cmd.Flags().String(nodeCliHome, "gaiacli", - "Home directory of the node's cli configuration") - - cmd.Flags().String(startingIPAddress, "192.168.0.1", + + cmd.Flags().Int(flagNumValidators, 4, + "Number of validators to initialize the testnet with", + ) + cmd.Flags().StringP(flagOutputDir, "o", "./mytestnet", + "Directory to store initialization data for the testnet", + ) + cmd.Flags().String(flagNodeDirPrefix, "node", + "Prefix the directory name for each node with (node results in node0, node1, ...)", + ) + cmd.Flags().String(flagNodeDaemonHome, "gaiad", + "Home directory of the node's daemon configuration", + ) + cmd.Flags().String(flagNodeCliHome, "gaiacli", + "Home directory of the node's cli configuration", + ) + 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") + return cmd } -func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit server.AppInit) error { - outDir := viper.GetString(outputDir) - numValidators := viper.GetInt(nValidators) +func initTestnet(config *cfg.Config, cdc *codec.Codec) error { + var chainID string + outDir := viper.GetString(flagOutputDir) + numValidators := viper.GetInt(flagNumValidators) + + chainID = viper.GetString(client.FlagChainID) + if chainID == "" { + chainID = "chain-" + cmn.RandStr(6) + } + + monikers := make([]string, numValidators) + nodeIDs := make([]string, numValidators) + valPubKeys := make([]crypto.PubKey, numValidators) - // Generate private key, node ID, initial transaction + var ( + accs []app.GenesisAccount + genFiles []string + ) + + // generate private keys, node IDs, and initial transactions for i := 0; i < numValidators; i++ { - nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i) - nodeDaemonHomeName := viper.GetString(nodeDaemonHome) - nodeCliHomeName := viper.GetString(nodeCliHome) + nodeDirName := fmt.Sprintf("%s%d", viper.GetString(flagNodeDirPrefix), i) + nodeDaemonHomeName := viper.GetString(flagNodeDaemonHome) + nodeCliHomeName := viper.GetString(flagNodeCliHome) nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName) clientDir := filepath.Join(outDir, nodeDirName, nodeCliHomeName) gentxsDir := filepath.Join(outDir, "gentxs") + config.SetRoot(nodeDir) err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm) @@ -92,95 +123,232 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit server.AppI return err } + monikers = append(monikers, nodeDirName) config.Moniker = nodeDirName - ip, err := getIP(i) + + ip, err := getIP(i, viper.GetString(flagStartingIPAddress)) if err != nil { + _ = os.RemoveAll(outDir) return err } - genTxConfig := gc.GenTx{ - nodeDirName, - clientDir, - true, - ip, + nodeIDs[i], valPubKeys[i], err = InitializeNodeValidatorFiles(config) + if err != nil { + _ = os.RemoveAll(outDir) + return err } - // Run `init gen-tx` and generate initial transactions - cliPrint, genTxFile, err := gentxWithConfig(cdc, appInit, config, genTxConfig) + memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip) + genFiles = append(genFiles, config.GenesisFile()) + + buf := client.BufferStdin() + prompt := fmt.Sprintf( + "Password for account '%s' (default %s):", nodeDirName, app.DefaultKeyPass, + ) + + keyPass, err := client.GetPassword(prompt, buf) + if err != nil && keyPass != "" { + // An error was returned that either failed to read the password from + // STDIN or the given password is not empty but failed to meet minimum + // length requirements. + return err + } + + if keyPass == "" { + keyPass = app.DefaultKeyPass + } + + addr, secret, err := server.GenerateSaveCoinKey(clientDir, nodeDirName, keyPass, true) if err != nil { + _ = os.RemoveAll(outDir) return err } - // Save private key seed words - name := fmt.Sprintf("%v.json", "key_seed") - err = writeFile(name, clientDir, cliPrint) + info := map[string]string{"secret": secret} + + cliPrint, err := json.Marshal(info) if err != nil { return err } - // Gather gentxs folder - name = fmt.Sprintf("%v.json", nodeDirName) - err = writeFile(name, gentxsDir, genTxFile) + // save private key seed words + err = writeFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, cliPrint) if err != nil { return err } + + accs = append(accs, app.GenesisAccount{ + Address: addr, + Coins: sdk.Coins{ + sdk.NewInt64Coin(fmt.Sprintf("%sToken", nodeDirName), 1000), + sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 150), + }, + }) + + msg := stake.NewMsgCreateValidator( + sdk.ValAddress(addr), + valPubKeys[i], + sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 100), + stake.NewDescription(nodeDirName, "", "", ""), + stake.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) + + signedTx, err := txBldr.SignStdTx(nodeDirName, app.DefaultKeyPass, tx, false) + if err != nil { + _ = os.RemoveAll(outDir) + return err + } + + txBytes, err := cdc.MarshalJSON(signedTx) + if err != nil { + _ = os.RemoveAll(outDir) + return err + } + + // gather gentxs folder + err = writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBytes) + if err != nil { + _ = os.RemoveAll(outDir) + return err + } } - // Generate genesis.json and config.toml - chainID := "chain-" + cmn.RandStr(6) + if err := initGenFiles(cdc, chainID, accs, genFiles, numValidators); err != nil { + return err + } + + err := collectGenFiles( + cdc, config, chainID, monikers, nodeIDs, valPubKeys, numValidators, + outDir, viper.GetString(flagNodeDirPrefix), viper.GetString(flagNodeDaemonHome), + ) + if err != nil { + return err + } + + fmt.Printf("Successfully initialized %d node directories\n", numValidators) + return nil +} + +func initGenFiles( + cdc *codec.Codec, chainID string, accs []app.GenesisAccount, + genFiles []string, numValidators int, +) error { + + appGenState := app.NewDefaultGenesisState() + appGenState.Accounts = accs + + appGenStateJSON, err := codec.MarshalJSONIndent(cdc, appGenState) + if err != nil { + return err + } + + genDoc := types.GenesisDoc{ + ChainID: chainID, + AppState: appGenStateJSON, + Validators: nil, + } + + // generate empty genesis files for each validator and save for i := 0; i < numValidators; i++ { + if err := genDoc.SaveAs(genFiles[i]); err != nil { + return err + } + } + + return nil +} + +func collectGenFiles( + cdc *codec.Codec, config *cfg.Config, chainID string, + monikers, nodeIDs []string, valPubKeys []crypto.PubKey, + numValidators int, outDir, nodeDirPrefix, nodeDaemonHomeName string, +) error { - nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i) - nodeDaemonHomeName := viper.GetString(nodeDaemonHome) + var appState json.RawMessage + genTime := tmtime.Now() + + for i := 0; i < numValidators; i++ { + nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i) nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName) gentxsDir := filepath.Join(outDir, "gentxs") - initConfig := server.InitConfig{ - chainID, - true, - gentxsDir, - true, - } + moniker := monikers[i] config.Moniker = nodeDirName + config.SetRoot(nodeDir) - // Run `init` and generate genesis.json and config.toml - _, _, _, err := initWithConfig(cdc, appInit, config, initConfig) + nodeID, valPubKey := nodeIDs[i], valPubKeys[i] + initCfg := initConfig{ + ChainID: chainID, + GenTxsDir: gentxsDir, + Name: moniker, + NodeID: nodeID, + ValPubKey: valPubKey, + } + + genDoc, err := loadGenesisDoc(cdc, config.GenesisFile()) + if err != nil { + return err + } + + nodeAppState, err := genAppStateFromConfig(cdc, config, initCfg, genDoc) + if err != nil { + return err + } + + if appState == nil { + // set the canonical application state (they should not differ) + appState = nodeAppState + } + + genFile := config.GenesisFile() + + // overwrite each validator's genesis file to have a canonical genesis time + err = ExportGenesisFileWithTime(genFile, chainID, nil, appState, genTime) if err != nil { return err } } - fmt.Printf("Successfully initialized %v node directories\n", viper.GetInt(nValidators)) return nil } -func getIP(i int) (ip string, err error) { - ip = viper.GetString(startingIPAddress) - if len(ip) == 0 { +func getIP(i int, startingIPAddr string) (string, error) { + var ( + ip string + err error + ) + + if len(startingIPAddr) == 0 { ip, err = server.ExternalIP() if err != nil { return "", err } } else { - ip, err = calculateIP(ip, i) + ip, err = calculateIP(startingIPAddr, i) if err != nil { return "", err } } + return ip, nil } func writeFile(name string, dir string, contents []byte) error { writePath := filepath.Join(dir) file := filepath.Join(writePath, name) + err := cmn.EnsureDir(writePath, 0700) if err != nil { return err } + err = cmn.WriteFile(file, contents, 0600) if err != nil { return err } + return nil } @@ -193,5 +361,6 @@ func calculateIP(ip string, i int) (string, error) { for j := 0; j < i; j++ { ipv4[3]++ } + return ipv4.String(), nil } diff --git a/cmd/gaia/init/utils.go b/cmd/gaia/init/utils.go new file mode 100644 index 000000000000..58edc9b2a589 --- /dev/null +++ b/cmd/gaia/init/utils.go @@ -0,0 +1,112 @@ +package init + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "time" + + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/codec" + amino "github.com/tendermint/go-amino" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/types" +) + +// ExportGenesisFile creates and writes the genesis configuration to disk. An +// error is returned if building or writing the configuration to file fails. +func ExportGenesisFile( + genFile, chainID string, validators []types.GenesisValidator, appState json.RawMessage, +) error { + + genDoc := types.GenesisDoc{ + ChainID: chainID, + Validators: validators, + AppState: appState, + } + + if err := genDoc.ValidateAndComplete(); err != nil { + return err + } + + return genDoc.SaveAs(genFile) +} + +// ExportGenesisFileWithTime creates and writes the genesis configuration to disk. +// An error is returned if building or writing the configuration to file fails. +func ExportGenesisFileWithTime( + genFile, chainID string, validators []types.GenesisValidator, + appState json.RawMessage, genTime time.Time, +) error { + + genDoc := types.GenesisDoc{ + GenesisTime: genTime, + ChainID: chainID, + Validators: validators, + AppState: appState, + } + + if err := genDoc.ValidateAndComplete(); err != nil { + return err + } + + 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, +) { + + nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + if err != nil { + return nodeID, valPubKey, err + } + + nodeID = string(nodeKey.ID()) + valPubKey = ReadOrCreatePrivValidator(config.PrivValidatorFile()) + + return nodeID, valPubKey, nil +} + +func loadGenesisDoc(cdc *amino.Codec, genFile string) (genDoc types.GenesisDoc, err error) { + genContents, err := ioutil.ReadFile(genFile) + if err != nil { + return genDoc, err + } + + if err := cdc.UnmarshalJSON(genContents, &genDoc); err != nil { + return genDoc, err + } + + return genDoc, err +} + +func initializeEmptyGenesis( + cdc *codec.Codec, genFile, chainID string, overwrite bool, +) (appState json.RawMessage, err error) { + + if !overwrite && common.FileExists(genFile) { + return nil, fmt.Errorf("genesis.json file already exists: %v", genFile) + } + + return codec.MarshalJSONIndent(cdc, app.NewDefaultGenesisState()) +} diff --git a/crypto/keys/bcrypt/base64.go b/crypto/keys/bcrypt/base64.go deleted file mode 100644 index fc3116090818..000000000000 --- a/crypto/keys/bcrypt/base64.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package bcrypt - -import "encoding/base64" - -const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - -var bcEncoding = base64.NewEncoding(alphabet) - -func base64Encode(src []byte) []byte { - n := bcEncoding.EncodedLen(len(src)) - dst := make([]byte, n) - bcEncoding.Encode(dst, src) - for dst[n-1] == '=' { - n-- - } - return dst[:n] -} - -func base64Decode(src []byte) ([]byte, error) { - numOfEquals := 4 - (len(src) % 4) - for i := 0; i < numOfEquals; i++ { - src = append(src, '=') - } - - dst := make([]byte, bcEncoding.DecodedLen(len(src))) - n, err := bcEncoding.Decode(dst, src) - if err != nil { - return nil, err - } - return dst[:n], nil -} diff --git a/crypto/keys/bcrypt/bcrypt.go b/crypto/keys/bcrypt/bcrypt.go deleted file mode 100644 index e24120bfbed4..000000000000 --- a/crypto/keys/bcrypt/bcrypt.go +++ /dev/null @@ -1,297 +0,0 @@ -package bcrypt - -// MODIFIED BY TENDERMINT TO EXPOSE NONCE -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing -// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf - -// The code is a port of Provos and Mazières's C implementation. -import ( - "crypto/subtle" - "errors" - "fmt" - "strconv" - - "golang.org/x/crypto/blowfish" -) - -const ( - // the minimum allowable cost as passed in to GenerateFromPassword - MinCost int = 4 - // the maximum allowable cost as passed in to GenerateFromPassword - MaxCost int = 31 - // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword - DefaultCost int = 10 -) - -// The error returned from CompareHashAndPassword when a password and hash do -// not match. -var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password") - -// The error returned from CompareHashAndPassword when a hash is too short to -// be a bcrypt hash. -var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password") - -// The error returned from CompareHashAndPassword when a hash was created with -// a bcrypt algorithm newer than this implementation. -type HashVersionTooNewError byte - -func (hv HashVersionTooNewError) Error() string { - return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion) -} - -// The error returned from CompareHashAndPassword when a hash starts with something other than '$' -type InvalidHashPrefixError byte - -// Format error -func (ih InvalidHashPrefixError) Error() string { - return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih)) -} - -// Invalid bcrypt cost -type InvalidCostError int - -func (ic InvalidCostError) Error() string { - return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost)) // nolint: unconvert -} - -const ( - majorVersion = '2' - minorVersion = 'a' - maxSaltSize = 16 - maxCryptedHashSize = 23 - encodedSaltSize = 22 - encodedHashSize = 31 - minHashSize = 59 -) - -// magicCipherData is an IV for the 64 Blowfish encryption calls in -// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes. -var magicCipherData = []byte{ - 0x4f, 0x72, 0x70, 0x68, - 0x65, 0x61, 0x6e, 0x42, - 0x65, 0x68, 0x6f, 0x6c, - 0x64, 0x65, 0x72, 0x53, - 0x63, 0x72, 0x79, 0x44, - 0x6f, 0x75, 0x62, 0x74, -} - -type hashed struct { - hash []byte - salt []byte - cost int // allowed range is MinCost to MaxCost - major byte - minor byte -} - -// GenerateFromPassword returns the bcrypt hash of the password at the given -// cost. If the cost given is less than MinCost, the cost will be set to -// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package, -// to compare the returned hashed password with its cleartext version. -func GenerateFromPassword(salt []byte, password []byte, cost int) ([]byte, error) { - if len(salt) != maxSaltSize { - return nil, fmt.Errorf("salt len must be %v", maxSaltSize) - } - p, err := newFromPassword(salt, password, cost) - if err != nil { - return nil, err - } - return p.Hash(), nil -} - -// CompareHashAndPassword compares a bcrypt hashed password with its possible -// plaintext equivalent. Returns nil on success, or an error on failure. -func CompareHashAndPassword(hashedPassword, password []byte) error { - p, err := newFromHash(hashedPassword) - if err != nil { - return err - } - - otherHash, err := bcrypt(password, p.cost, p.salt) - if err != nil { - return err - } - - otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor} - if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 { - return nil - } - - return ErrMismatchedHashAndPassword -} - -// Cost returns the hashing cost used to create the given hashed -// password. When, in the future, the hashing cost of a password system needs -// to be increased in order to adjust for greater computational power, this -// function allows one to establish which passwords need to be updated. -func Cost(hashedPassword []byte) (int, error) { - p, err := newFromHash(hashedPassword) - if err != nil { - return 0, err - } - return p.cost, nil -} - -func newFromPassword(salt []byte, password []byte, cost int) (*hashed, error) { - if cost < MinCost { - cost = DefaultCost - } - p := new(hashed) - p.major = majorVersion - p.minor = minorVersion - - err := checkCost(cost) - if err != nil { - return nil, err - } - p.cost = cost - - p.salt = base64Encode(salt) - hash, err := bcrypt(password, p.cost, p.salt) - if err != nil { - return nil, err - } - p.hash = hash - return p, err -} - -func newFromHash(hashedSecret []byte) (*hashed, error) { - if len(hashedSecret) < minHashSize { - return nil, ErrHashTooShort - } - p := new(hashed) - n, err := p.decodeVersion(hashedSecret) - if err != nil { - return nil, err - } - hashedSecret = hashedSecret[n:] - n, err = p.decodeCost(hashedSecret) - if err != nil { - return nil, err - } - hashedSecret = hashedSecret[n:] - - // The "+2" is here because we'll have to append at most 2 '=' to the salt - // when base64 decoding it in expensiveBlowfishSetup(). - p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2) - copy(p.salt, hashedSecret[:encodedSaltSize]) - - hashedSecret = hashedSecret[encodedSaltSize:] - p.hash = make([]byte, len(hashedSecret)) - copy(p.hash, hashedSecret) - - return p, nil -} - -func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) { - cipherData := make([]byte, len(magicCipherData)) - copy(cipherData, magicCipherData) - - c, err := expensiveBlowfishSetup(password, uint32(cost), salt) - if err != nil { - return nil, err - } - - for i := 0; i < 24; i += 8 { - for j := 0; j < 64; j++ { - c.Encrypt(cipherData[i:i+8], cipherData[i:i+8]) - } - } - - // Bug compatibility with C bcrypt implementations. We only encode 23 of - // the 24 bytes encrypted. - hsh := base64Encode(cipherData[:maxCryptedHashSize]) - return hsh, nil -} - -func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) { - - csalt, err := base64Decode(salt) - if err != nil { - return nil, err - } - - // Bug compatibility with C bcrypt implementations. They use the trailing - // NULL in the key string during expansion. - ckey := append(key, 0) - - c, err := blowfish.NewSaltedCipher(ckey, csalt) - if err != nil { - return nil, err - } - - var i, rounds uint64 - rounds = 1 << cost - for i = 0; i < rounds; i++ { - blowfish.ExpandKey(ckey, c) - blowfish.ExpandKey(csalt, c) - } - - return c, nil -} - -func (p *hashed) Hash() []byte { - arr := make([]byte, 60) - arr[0] = '$' - arr[1] = p.major - n := 2 - if p.minor != 0 { - arr[2] = p.minor - n = 3 - } - arr[n] = '$' - n++ - copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost))) - n += 2 - arr[n] = '$' - n++ - copy(arr[n:], p.salt) - n += encodedSaltSize - copy(arr[n:], p.hash) - n += encodedHashSize - return arr[:n] -} - -func (p *hashed) decodeVersion(sbytes []byte) (int, error) { - if sbytes[0] != '$' { - return -1, InvalidHashPrefixError(sbytes[0]) - } - if sbytes[1] > majorVersion { - return -1, HashVersionTooNewError(sbytes[1]) - } - p.major = sbytes[1] - n := 3 - if sbytes[2] != '$' { - p.minor = sbytes[2] - n++ - } - return n, nil -} - -// sbytes should begin where decodeVersion left off. -func (p *hashed) decodeCost(sbytes []byte) (int, error) { - cost, err := strconv.Atoi(string(sbytes[0:2])) - if err != nil { - return -1, err - } - err = checkCost(cost) - if err != nil { - return -1, err - } - p.cost = cost - return 3, nil -} - -func (p *hashed) String() string { - return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor) -} - -func checkCost(cost int) error { - if cost < MinCost || cost > MaxCost { - return InvalidCostError(cost) - } - return nil -} diff --git a/crypto/keys/bip39/wordcodec.go b/crypto/keys/bip39/wordcodec.go deleted file mode 100644 index 074d1393c028..000000000000 --- a/crypto/keys/bip39/wordcodec.go +++ /dev/null @@ -1,66 +0,0 @@ -package bip39 - -import ( - "strings" - - "github.com/bartekn/go-bip39" -) - -// ValidSentenceLen defines the mnemonic sentence lengths supported by this BIP 39 library. -type ValidSentenceLen uint8 - -const ( - // FundRaiser is the sentence length used during the cosmos fundraiser (12 words). - FundRaiser ValidSentenceLen = 12 - // Size of the checksum employed for the fundraiser - FundRaiserChecksumSize = 4 - // FreshKey is the sentence length used for newly created keys (24 words). - FreshKey ValidSentenceLen = 24 - // Size of the checksum employed for new keys - FreshKeyChecksumSize = 8 -) - -// NewMnemonic will return a string consisting of the mnemonic words for -// the given sentence length. -func NewMnemonic(len ValidSentenceLen) (words []string, err error) { - // len = (entropySize + checksum) / 11 - var entropySize int - switch len { - case FundRaiser: - // entropySize = 128 - entropySize = int(len)*11 - FundRaiserChecksumSize - case FreshKey: - // entropySize = 256 - entropySize = int(len)*11 - FreshKeyChecksumSize - } - var entropy []byte - entropy, err = bip39.NewEntropy(entropySize) - if err != nil { - return - } - var mnemonic string - mnemonic, err = bip39.NewMnemonic(entropy) - if err != nil { - return - } - words = strings.Split(mnemonic, " ") - return -} - -// MnemonicToSeed creates a BIP 39 seed from the passed mnemonic (with an empty BIP 39 password). -// This method does not validate the mnemonics checksum. -func MnemonicToSeed(mne string) (seed []byte) { - // we do not checksum here... - seed = bip39.NewSeed(mne, "") - return -} - -// MnemonicToSeedWithErrChecking returns the same seed as MnemonicToSeed. -// It creates a BIP 39 seed from the passed mnemonic (with an empty BIP 39 password). -// -// Different from MnemonicToSeed it validates the checksum. -// For details on the checksum see the BIP 39 spec. -func MnemonicToSeedWithErrChecking(mne string) (seed []byte, err error) { - seed, err = bip39.NewSeedWithErrorChecking(mne, "") - return -} diff --git a/crypto/keys/bip39/wordcodec_test.go b/crypto/keys/bip39/wordcodec_test.go deleted file mode 100644 index a821239d7b40..000000000000 --- a/crypto/keys/bip39/wordcodec_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package bip39 - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestWordCodec_NewMnemonic(t *testing.T) { - _, err := NewMnemonic(FundRaiser) - require.NoError(t, err, "unexpected error generating fundraiser mnemonic") - - _, err = NewMnemonic(FreshKey) - require.NoError(t, err, "unexpected error generating new 24-word mnemonic") -} diff --git a/crypto/keys/hd/fundraiser_test.go b/crypto/keys/hd/fundraiser_test.go index 84de09758d59..5e3cf06f3f46 100644 --- a/crypto/keys/hd/fundraiser_test.go +++ b/crypto/keys/hd/fundraiser_test.go @@ -7,9 +7,10 @@ import ( "io/ioutil" "testing" - "github.com/bartekn/go-bip39" "github.com/stretchr/testify/require" + "github.com/cosmos/go-bip39" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/secp256k1" ) diff --git a/crypto/keys/hd/hdpath.go b/crypto/keys/hd/hdpath.go index ef2e6f783a2a..112abe0b662a 100644 --- a/crypto/keys/hd/hdpath.go +++ b/crypto/keys/hd/hdpath.go @@ -22,7 +22,6 @@ import ( "strings" "github.com/btcsuite/btcd/btcec" - "github.com/tendermint/tendermint/crypto/secp256k1" ) // BIP44Prefix is the parts of the BIP32 HD path that are fixed by what we used during the fundraiser. @@ -55,6 +54,77 @@ func NewParams(purpose, coinType, account uint32, change bool, addressIdx uint32 } } +// Parse the BIP44 path and unmarshal into the struct. +// nolint: gocyclo +func NewParamsFromPath(path string) (*BIP44Params, error) { + spl := strings.Split(path, "/") + if len(spl) != 5 { + return nil, fmt.Errorf("path length is wrong. Expected 5, got %d", len(spl)) + } + + if spl[0] != "44'" { + return nil, fmt.Errorf("first field in path must be 44', got %v", spl[0]) + } + + if !isHardened(spl[1]) || !isHardened(spl[2]) { + return nil, + fmt.Errorf("second and third field in path must be hardened (ie. contain the suffix ', got %v and %v", spl[1], spl[2]) + } + if isHardened(spl[3]) || isHardened(spl[4]) { + return nil, + fmt.Errorf("fourth and fifth field in path must not be hardened (ie. not contain the suffix ', got %v and %v", spl[3], spl[4]) + } + + purpose, err := hardenedInt(spl[0]) + if err != nil { + return nil, err + } + coinType, err := hardenedInt(spl[1]) + if err != nil { + return nil, err + } + account, err := hardenedInt(spl[2]) + if err != nil { + return nil, err + } + change, err := hardenedInt(spl[3]) + if err != nil { + return nil, err + } + if !(change == 0 || change == 1) { + return nil, fmt.Errorf("change field can only be 0 or 1") + } + + addressIdx, err := hardenedInt(spl[4]) + if err != nil { + return nil, err + } + + return &BIP44Params{ + purpose: purpose, + coinType: coinType, + account: account, + change: change > 0, + addressIdx: addressIdx, + }, nil +} + +func hardenedInt(field string) (uint32, error) { + field = strings.TrimSuffix(field, "'") + i, err := strconv.Atoi(field) + if err != nil { + return 0, err + } + if i < 0 { + return 0, fmt.Errorf("fields must not be negative. got %d", i) + } + return uint32(i), nil +} + +func isHardened(field string) bool { + return strings.HasSuffix(field, "'") +} + // NewFundraiserParams creates a BIP 44 parameter object from the params: // m / 44' / 118' / account' / 0 / address_index // The fixed parameters (purpose', coin_type', and change) are determined by what was used in the fundraiser. @@ -62,6 +132,21 @@ func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params { return NewParams(44, 118, account, false, addressIdx) } +// Return the BIP44 fields as an array. +func (p BIP44Params) DerivationPath() []uint32 { + change := uint32(0) + if p.change { + change = 1 + } + return []uint32{ + p.purpose, + p.coinType, + p.account, + change, + p.addressIdx, + } +} + func (p BIP44Params) String() string { var changeStr string if p.change { @@ -128,10 +213,15 @@ func derivePrivateKey(privKeyBytes [32]byte, chainCode [32]byte, index uint32, h data = append([]byte{byte(0)}, privKeyBytes[:]...) } else { // this can't return an error: - pubkey := secp256k1.PrivKeySecp256k1(privKeyBytes).PubKey() + _, ecPub := btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes[:]) + pubkeyBytes := ecPub.SerializeCompressed() + data = pubkeyBytes + /* By using btcec, we can remove the dependency on tendermint/crypto/secp256k1 + pubkey := secp256k1.PrivKeySecp256k1(privKeyBytes).PubKey() public := pubkey.(secp256k1.PubKeySecp256k1) data = public[:] + */ } data = append(data, uint32ToBytes(index)...) data2, chainCode2 := i64(chainCode[:], data) diff --git a/crypto/keys/hd/hdpath_test.go b/crypto/keys/hd/hdpath_test.go index 58398655f0b5..f310fc3555cb 100644 --- a/crypto/keys/hd/hdpath_test.go +++ b/crypto/keys/hd/hdpath_test.go @@ -3,9 +3,20 @@ package hd import ( "encoding/hex" "fmt" - "github.com/cosmos/cosmos-sdk/crypto/keys/bip39" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/cosmos/go-bip39" ) +var defaultBIP39Passphrase = "" + +// return bip39 seed with empty passphrase +func mnemonicToSeed(mnemonic string) []byte { + return bip39.NewSeed(mnemonic, defaultBIP39Passphrase) +} + //nolint func ExampleStringifyPathParams() { path := NewParams(44, 0, 0, false, 0) @@ -13,10 +24,57 @@ func ExampleStringifyPathParams() { // Output: 44'/0'/0'/0/0 } +func TestParamsFromPath(t *testing.T) { + goodCases := []struct { + params *BIP44Params + path string + }{ + {&BIP44Params{44, 0, 0, false, 0}, "44'/0'/0'/0/0"}, + {&BIP44Params{44, 1, 0, false, 0}, "44'/1'/0'/0/0"}, + {&BIP44Params{44, 0, 1, false, 0}, "44'/0'/1'/0/0"}, + {&BIP44Params{44, 0, 0, true, 0}, "44'/0'/0'/1/0"}, + {&BIP44Params{44, 0, 0, false, 1}, "44'/0'/0'/0/1"}, + {&BIP44Params{44, 1, 1, true, 1}, "44'/1'/1'/1/1"}, + {&BIP44Params{44, 118, 52, true, 41}, "44'/118'/52'/1/41"}, + } + + for i, c := range goodCases { + params, err := NewParamsFromPath(c.path) + errStr := fmt.Sprintf("%d %v", i, c) + assert.NoError(t, err, errStr) + assert.EqualValues(t, c.params, params, errStr) + assert.Equal(t, c.path, c.params.String()) + } + + badCases := []struct { + path string + }{ + {"43'/0'/0'/0/0"}, // doesnt start with 44 + {"44'/1'/0'/0/0/5"}, // too many fields + {"44'/0'/1'/0"}, // too few fields + {"44'/0'/0'/2/0"}, // change field can only be 0/1 + {"44/0'/0'/0/0"}, // first field needs ' + {"44'/0/0'/0/0"}, // second field needs ' + {"44'/0'/0/0/0"}, // third field needs ' + {"44'/0'/0'/0'/0"}, // fourth field must not have ' + {"44'/0'/0'/0/0'"}, // fifth field must not have ' + {"44'/-1'/0'/0/0"}, // no negatives + {"44'/0'/0'/-1/0"}, // no negatives + } + + for i, c := range badCases { + params, err := NewParamsFromPath(c.path) + errStr := fmt.Sprintf("%d %v", i, c) + assert.Nil(t, params, errStr) + assert.Error(t, err, errStr) + } + +} + //nolint func ExampleSomeBIP32TestVecs() { - seed := bip39.MnemonicToSeed("barrel original fuel morning among eternal " + + seed := mnemonicToSeed("barrel original fuel morning among eternal " + "filter ball stove pluck matrix mechanic") master, ch := ComputeMastersFromSeed(seed) fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)") @@ -35,14 +93,14 @@ func ExampleSomeBIP32TestVecs() { fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html") fmt.Println() - seed = bip39.MnemonicToSeed( + seed = mnemonicToSeed( "advice process birth april short trust crater change bacon monkey medal garment " + "gorilla ranch hour rival razor call lunar mention taste vacant woman sister") master, ch = ComputeMastersFromSeed(seed) priv, _ = DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4") fmt.Println(hex.EncodeToString(priv[:])) - seed = bip39.MnemonicToSeed("idea naive region square margin day captain habit " + + seed = mnemonicToSeed("idea naive region square margin day captain habit " + "gun second farm pact pulse someone armed") master, ch = ComputeMastersFromSeed(seed) priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420") @@ -53,7 +111,7 @@ func ExampleSomeBIP32TestVecs() { fmt.Println() // bip32 path: m/0/7 - seed = bip39.MnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history") + seed = mnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history") master, ch = ComputeMastersFromSeed(seed) priv, _ = DerivePrivateKeyForPath(master, ch, "0/7") fmt.Println(hex.EncodeToString(priv[:])) diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index 99632e764268..0bd500feef5a 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -6,11 +6,16 @@ import ( "os" "strings" + "github.com/pkg/errors" + + "github.com/cosmos/go-bip39" + "github.com/cosmos/cosmos-sdk/crypto" - "github.com/cosmos/cosmos-sdk/crypto/keys/bip39" "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" "github.com/cosmos/cosmos-sdk/types" - "github.com/pkg/errors" + + "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" tmcrypto "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/crypto/secp256k1" @@ -46,6 +51,14 @@ const ( infoSuffix = "info" ) +const ( + // used for deriving seed from mnemonic + defaultBIP39Passphrase = "" + + // bits of entropy to draw when creating a mnemonic + defaultEntropySize = 256 +) + var ( // ErrUnsupportedSigningAlgo is raised when the caller tries to use a // different signing scheme than secp256k1. @@ -85,12 +98,17 @@ func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string } // default number of words (24): - mnemonicS, err := bip39.NewMnemonic(bip39.FreshKey) + // this generates a mnemonic directly from the number of words by reading system entropy. + entropy, err := bip39.NewEntropy(defaultEntropySize) + if err != nil { + return + } + mnemonic, err = bip39.NewMnemonic(entropy) if err != nil { return } - mnemonic = strings.Join(mnemonicS, " ") - seed := bip39.MnemonicToSeed(mnemonic) + + seed := bip39.NewSeed(mnemonic, defaultBIP39Passphrase) info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath) return } @@ -102,7 +120,7 @@ func (kb dbKeybase) CreateKey(name, mnemonic, passwd string) (info Info, err err err = fmt.Errorf("recovering only works with 12 word (fundraiser) or 24 word mnemonics, got: %v words", len(words)) return } - seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic) + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase) if err != nil { return } @@ -119,7 +137,7 @@ func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Inf err = fmt.Errorf("recovering only works with 12 word (fundraiser), got: %v words", len(words)) return } - seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic) + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase) if err != nil { return } @@ -127,12 +145,12 @@ func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Inf return } -func (kb dbKeybase) Derive(name, mnemonic, passwd string, params hd.BIP44Params) (info Info, err error) { - seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic) +func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params) (info Info, err error) { + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase) if err != nil { return } - info, err = kb.persistDerivedKey(seed, passwd, name, params.String()) + info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String()) return } @@ -200,7 +218,7 @@ func (kb dbKeybase) List() ([]Info, error) { func (kb dbKeybase) Get(name string) (Info, error) { bs := kb.db.Get(infoKey(name)) if len(bs) == 0 { - return nil, fmt.Errorf("Key %s not found", name) + return nil, keyerror.NewErrKeyNotFound(name) } return readInfo(bs) } @@ -229,7 +247,7 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t err = fmt.Errorf("private key not available") return } - priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) + priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) if err != nil { return nil, nil, err } @@ -255,7 +273,7 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t if err != nil { return nil, nil, err } - cdc.MustUnmarshalBinary([]byte(signed), sig) + cdc.MustUnmarshalBinaryLengthPrefixed([]byte(signed), sig) return sig, linfo.GetPubKey(), nil } sig, err = priv.Sign(msg) @@ -279,7 +297,7 @@ func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcr err = fmt.Errorf("private key not available") return nil, err } - priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) + priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) if err != nil { return nil, err } @@ -296,7 +314,7 @@ func (kb dbKeybase) Export(name string) (armor string, err error) { if bz == nil { return "", fmt.Errorf("no key to export with name %s", name) } - return armorInfoBytes(bz), nil + return mintkey.ArmorInfoBytes(bz), nil } // ExportPubKey returns public keys in ASCII armored format. @@ -311,7 +329,7 @@ func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) { if err != nil { return } - return armorPubKeyBytes(info.GetPubKey().Bytes()), nil + return mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes()), nil } func (kb dbKeybase) Import(name string, armor string) (err error) { @@ -319,7 +337,7 @@ func (kb dbKeybase) Import(name string, armor string) (err error) { if len(bz) > 0 { return errors.New("Cannot overwrite data for name " + name) } - infoBytes, err := unarmorInfoBytes(armor) + infoBytes, err := mintkey.UnarmorInfoBytes(armor) if err != nil { return } @@ -335,7 +353,7 @@ func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) { if len(bz) > 0 { return errors.New("Cannot overwrite data for name " + name) } - pubBytes, err := unarmorPubKeyBytes(armor) + pubBytes, err := mintkey.UnarmorPubKeyBytes(armor) if err != nil { return } @@ -360,7 +378,7 @@ func (kb dbKeybase) Delete(name, passphrase string) error { switch info.(type) { case localInfo: linfo := info.(localInfo) - _, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) + _, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) if err != nil { return err } @@ -394,7 +412,7 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro switch info.(type) { case localInfo: linfo := info.(localInfo) - key, err := unarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass) + key, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass) if err != nil { return err } @@ -409,9 +427,14 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro } } +// CloseDB releases the lock and closes the storage backend. +func (kb dbKeybase) CloseDB() { + kb.db.Close() +} + func (kb dbKeybase) writeLocalKey(priv tmcrypto.PrivKey, name, passphrase string) Info { // encrypt private key using passphrase - privArmor := encryptArmorPrivKey(priv, passphrase) + privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase) // make Info pub := priv.PubKey() info := newLocalInfo(name, pub, privArmor) diff --git a/crypto/keys/keybase_test.go b/crypto/keys/keybase_test.go index 3273c229afe6..cafa2382a598 100644 --- a/crypto/keys/keybase_test.go +++ b/crypto/keys/keybase_test.go @@ -4,9 +4,12 @@ import ( "fmt" "testing" - "github.com/cosmos/cosmos-sdk/crypto/keys/hd" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" @@ -15,7 +18,7 @@ import ( ) func init() { - BcryptSecurityParameter = 1 + mintkey.BcryptSecurityParameter = 1 } // TestKeyManagement makes sure we can manipulate these keys well @@ -342,7 +345,7 @@ func TestSeedPhrase(t *testing.T) { // let us re-create it from the mnemonic-phrase params := *hd.NewFundraiserParams(0, 0) - newInfo, err := cstore.Derive(n2, mnemonic, p2, params) + newInfo, err := cstore.Derive(n2, mnemonic, defaultBIP39Passphrase, p2, params) require.NoError(t, err) require.Equal(t, n2, newInfo.GetName()) require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address()) diff --git a/crypto/keys/keyerror/errors.go b/crypto/keys/keyerror/errors.go new file mode 100644 index 000000000000..1042689e3891 --- /dev/null +++ b/crypto/keys/keyerror/errors.go @@ -0,0 +1,81 @@ +package keyerror + +import ( + "fmt" +) + +const ( + codeKeyNotFound = 1 + codeWrongPassword = 2 +) + +type keybaseError interface { + error + Code() int +} + +type errKeyNotFound struct { + code int + name string +} + +func (e errKeyNotFound) Code() int { + return e.code +} + +func (e errKeyNotFound) Error() string { + return fmt.Sprintf("Key %s not found", e.name) +} + +// NewErrKeyNotFound returns a standardized error reflecting that the specified key doesn't exist +func NewErrKeyNotFound(name string) error { + return errKeyNotFound{ + code: codeKeyNotFound, + name: name, + } +} + +// IsErrKeyNotFound returns true if the given error is errKeyNotFound +func IsErrKeyNotFound(err error) bool { + if err == nil { + return false + } + if keyErr, ok := err.(keybaseError); ok { + if keyErr.Code() == codeKeyNotFound { + return true + } + } + return false +} + +type errWrongPassword struct { + code int +} + +func (e errWrongPassword) Code() int { + return e.code +} + +func (e errWrongPassword) Error() string { + return fmt.Sprintf("Ciphertext decryption failed") +} + +// NewErrWrongPassword returns a standardized error reflecting that the specified password is wrong +func NewErrWrongPassword() error { + return errWrongPassword{ + code: codeWrongPassword, + } +} + +// IsErrWrongPassword returns true if the given error is errWrongPassword +func IsErrWrongPassword(err error) bool { + if err == nil { + return false + } + if keyErr, ok := err.(keybaseError); ok { + if keyErr.Code() == codeWrongPassword { + return true + } + } + return false +} diff --git a/crypto/keys/mintkey.go b/crypto/keys/mintkey/mintkey.go similarity index 76% rename from crypto/keys/mintkey.go rename to crypto/keys/mintkey/mintkey.go index 70e1bc44e259..35cb9ccabf6a 100644 --- a/crypto/keys/mintkey.go +++ b/crypto/keys/mintkey/mintkey.go @@ -1,16 +1,18 @@ -package keys +package mintkey import ( "encoding/hex" "fmt" - cmn "github.com/tendermint/tendermint/libs/common" + "golang.org/x/crypto/bcrypt" - "github.com/cosmos/cosmos-sdk/crypto/keys/bcrypt" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/armor" "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/crypto/xsalsa20symmetric" + + "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" + cmn "github.com/tendermint/tendermint/libs/common" ) const ( @@ -34,11 +36,16 @@ const ( // TODO: Consider increasing default var BcryptSecurityParameter = 12 -func armorInfoBytes(bz []byte) string { +//----------------------------------------------------------------- +// add armor + +// Armor the InfoBytes +func ArmorInfoBytes(bz []byte) string { return armorBytes(bz, blockTypeKeyInfo) } -func armorPubKeyBytes(bz []byte) string { +// Armor the PubKeyBytes +func ArmorPubKeyBytes(bz []byte) string { return armorBytes(bz, blockTypePubKey) } @@ -50,11 +57,16 @@ func armorBytes(bz []byte, blockType string) string { return armor.EncodeArmor(blockType, header, bz) } -func unarmorInfoBytes(armorStr string) (bz []byte, err error) { +//----------------------------------------------------------------- +// remove armor + +// Unarmor the InfoBytes +func UnarmorInfoBytes(armorStr string) (bz []byte, err error) { return unarmorBytes(armorStr, blockTypeKeyInfo) } -func unarmorPubKeyBytes(armorStr string) (bz []byte, err error) { +// Unarmor the PubKeyBytes +func UnarmorPubKeyBytes(armorStr string) (bz []byte, err error) { return unarmorBytes(armorStr, blockTypePubKey) } @@ -74,7 +86,11 @@ func unarmorBytes(armorStr, blockType string) (bz []byte, err error) { return } -func encryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string { +//----------------------------------------------------------------- +// encrypt/decrypt with armor + +// Encrypt and armor the private key. +func EncryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string { saltBytes, encBytes := encryptPrivKey(privKey, passphrase) header := map[string]string{ "kdf": "bcrypt", @@ -84,7 +100,22 @@ func encryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string { return armorStr } -func unarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) { +// encrypt the given privKey with the passphrase using a randomly +// generated salt and the xsalsa20 cipher. returns the salt and the +// encrypted priv key. +func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) { + saltBytes = crypto.CRandBytes(16) + key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) + if err != nil { + cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error()) + } + key = crypto.Sha256(key) // get 32 bytes + privKeyBytes := privKey.Bytes() + return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key) +} + +// Unarmor and decrypt the private key. +func UnarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) { var privKey crypto.PrivKey blockType, header, encBytes, err := armor.DecodeArmor(armorStr) if err != nil { @@ -107,17 +138,6 @@ func unarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, return privKey, err } -func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) { - saltBytes = crypto.CRandBytes(16) - key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) - if err != nil { - cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error()) - } - key = crypto.Sha256(key) // Get 32 bytes - privKeyBytes := privKey.Bytes() - return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key) -} - func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) { key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) if err != nil { @@ -125,7 +145,9 @@ func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privK } key = crypto.Sha256(key) // Get 32 bytes privKeyBytes, err := xsalsa20symmetric.DecryptSymmetric(encBytes, key) - if err != nil { + if err != nil && err.Error() == "Ciphertext decryption failed" { + return privKey, keyerror.NewErrWrongPassword() + } else if err != nil { return privKey, err } privKey, err = cryptoAmino.PrivKeyFromBytes(privKeyBytes) diff --git a/crypto/keys/types.go b/crypto/keys/types.go index c5e97d5fba31..eeb4fdfcf0ce 100644 --- a/crypto/keys/types.go +++ b/crypto/keys/types.go @@ -26,8 +26,12 @@ type Keybase interface { CreateKey(name, mnemonic, passwd string) (info Info, err error) // CreateFundraiserKey takes a mnemonic and derives, a password CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error) - // Derive derives a key from the passed mnemonic using a BIP44 path. - Derive(name, mnemonic, passwd string, params hd.BIP44Params) (Info, error) + // Compute a BIP39 seed from th mnemonic and bip39Passwd. + // Derive private key from the seed using the BIP44 params. + // Encrypt the key to disk using encryptPasswd. + // See https://github.com/cosmos/cosmos-sdk/issues/2095 + Derive(name, mnemonic, bip39Passwd, + encryptPasswd string, params hd.BIP44Params) (Info, error) // Create, store, and return a new Ledger key reference CreateLedger(name string, path ccrypto.DerivationPath, algo SigningAlgo) (info Info, err error) @@ -43,6 +47,9 @@ type Keybase interface { // *only* works on locally-stored keys. Temporary method until we redo the exporting API ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) + + // Close closes the database. + CloseDB() } // KeyType reflects a human-readable type for key listing. @@ -175,11 +182,11 @@ func (i offlineInfo) GetAddress() types.AccAddress { // encoding info func writeInfo(i Info) []byte { - return cdc.MustMarshalBinary(i) + return cdc.MustMarshalBinaryLengthPrefixed(i) } // decoding info func readInfo(bz []byte) (info Info, err error) { - err = cdc.UnmarshalBinary(bz, &info) + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &info) return } diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 7925697f5155..c4710379e0ca 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -1,6 +1,7 @@ module.exports = { - title: "Cosmos Network", + title: "Cosmos Documentation", description: "Documentation for the Cosmos Network.", + ga: "UA-51029217-2", dest: "./dist/docs", base: "/docs/", markdown: { @@ -11,97 +12,39 @@ module.exports = { nav: [{ text: "Back to Cosmos", link: "https://cosmos.network" }], sidebar: [ { - title: "Introduction", + title: "Overview", collapsable: false, children: [ - "/introduction/cosmos-hub", - "/introduction/tendermint-cosmos", - "/introduction/tendermint" + "/intro/", + "/intro/sdk-app-architecture", + "/intro/ocap" ] }, { - title: "Getting Started", + title: "Gaia", collapsable: false, children: [ - "/getting-started/voyager", - "/getting-started/installation", - "/getting-started/join-testnet", - "/getting-started/networks" - ] - }, - { - title: "Cosmos SDK", - collapsable: false, - children: [ - ["/sdk/overview", "Overview"], - ["/sdk/core/intro", "Core"], - "/sdk/core/app1", - "/sdk/core/app2", - "/sdk/core/app3", - "/sdk/core/app4", - "/sdk/core/app5", - // "/sdk/modules", - "/sdk/clients" - ] - }, - // { - // title: "Specifications", - // collapsable: false, - // children: [ - // ["/specs/overview", "Overview"], - // "/specs/governance", - // "/specs/ibc", - // "/specs/staking", - // "/specs/icts", - // ] - // }, - { - title: "SDK by Examples - Simple Governance", - collapsable: false, - children: [ - ["/sdk/sdk-by-examples/simple-governance/intro", "Intro"], - "/sdk/sdk-by-examples/simple-governance/setup-and-design", - "/sdk/sdk-by-examples/simple-governance/app-init", - "/sdk/sdk-by-examples/simple-governance/simple-gov-module", - "/sdk/sdk-by-examples/simple-governance/bridging-it-all", - "/sdk/sdk-by-examples/simple-governance/running-the-application" - ] - }, - { - title: "Lotion JS", - collapsable: false, - children: [ - ["/lotion/overview", "Overview"] - ] - }, - { - title: "Validators", - collapsable: false, - children: [ - ["/validators/overview", "Overview"], - ["/validators/security", "Security"], - ["/validators/validator-setup", "Validator Setup"], - "/validators/validator-faq" + "/gaia/installation", + "/gaia/join-testnet", + "/gaia/networks", + "/gaia/validators/overview", + "/gaia/validators/security", + "/gaia/validators/validator-faq", + "/gaia/validators/validator-setup", + "/gaia/ledger" ] }, { title: "Clients", collapsable: false, children: [ - ["/clients/service-providers", "Service Providers"] - ] - }, - { - title: "Resources", - collapsable: false, - children: [ - // ["/resources/faq" "General"], - "/resources/delegator-faq", - ["/resources/whitepaper", "Whitepaper - English"], - ["/resources/whitepaper-ko", "Whitepaper - 한국어"], - ["/resources/whitepaper-zh-CN", "Whitepaper - 中文"], - ["/resources/whitepaper-pt", "Whitepaper - Português"] - ] + "/clients/", + "/clients/cli", + "/clients/service-providers", + "/clients/lite/", // this renders the readme + "/clients/lite/getting_started", + "/clients/lite/specification" + ] } ] } diff --git a/docs/DOCS_README.md b/docs/DOCS_README.md index 59a1c9b6d476..43c8ac9ca4c2 100644 --- a/docs/DOCS_README.md +++ b/docs/DOCS_README.md @@ -1,4 +1,8 @@ -# Docs Build Workflow +## Updating the docs + +If you want to open a PR on the Cosmos SDK to update the documentation, please follow the guidelines in the [`CONTRIBUTING.md`](https://github.com/cosmos/cosmos-sdk/tree/master/CONTRIBUTING.md) + +## Docs Build Workflow The documentation for the Cosmos SDK is hosted at: @@ -15,34 +19,38 @@ Besides, gaia-lite API docs are also provided by gaia-lite. The default API docs https://localhost:1317/swagger-ui/ ``` -## How It Works +### Updating the Documentation + +Please read the `Updating Documentation` section in [`CONTRIBUTING.md` document](../CONTRIBUTING.MD#updating-documentation) before making any changes. + +### How It Works There is a Jenkins job listening for changes in the `/docs` directory, on both the `master` and `develop` branches. Any updates to files in this directory on those branches will automatically trigger a website deployment. Under the hood, a private website repository has make targets consumed by a standard Jenkins task. -## README +### README The [README.md](./README.md) is also the landing page for the documentation on the website. During the Jenkins build, the current commit is added to the bottom of the README. -## Config.js +### Config.js The [config.js](./.vuepress/config.js) generates the sidebar and Table of Contents on the website docs. Note the use of relative links and the omission of file extensions. Additional features are available to improve the look of the sidebar. -## Links +### Links **NOTE:** Strongly consider the existing links - both within this directory and to the website docs - when moving or deleting files. Relative links should be used nearly everywhere, having discovered and weighed the following: -### Relative +#### Relative Where is the other file, relative to the current one? @@ -50,7 +58,7 @@ Where is the other file, relative to the current one? - confusing / annoying to have things like: `../../../../myfile.md` - requires more updates when files are re-shuffled -### Absolute +#### Absolute Where is the other file, given the root of the repo? @@ -58,12 +66,12 @@ Where is the other file, given the root of the repo? - this is much nicer: `/docs/hereitis/myfile.md` - if you move that file around, the links inside it are preserved (but not to it, of course) -### Full +#### Full The full GitHub URL to a file or directory. Used occasionally when it makes sense to send users to the GitHub. -## Building Locally +### Building Locally To build and serve the documentation locally, run: @@ -94,12 +102,12 @@ python -m SimpleHTTPServer 8080 then navigate to localhost:8080 in your browser. -## Consistency +### Consistency 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 +### Update and Build the RPC docs 1. Execute the following command at the root directory to install the swagger-ui generate tool. ``` @@ -112,4 +120,4 @@ much as possible with its [counterpart in the Tendermint Core repo](https://gith 4. Compile gaiacli ``` make install - ``` \ No newline at end of file + ``` diff --git a/docs/PRIORITIES.md b/docs/PRIORITIES.md index 20eb20e1a36c..522f1b866069 100644 --- a/docs/PRIORITIES.md +++ b/docs/PRIORITIES.md @@ -1,13 +1,8 @@ # Post-0.25/GoS Pre-Release -## Staking/Slashing/Stability +## Staking / Slashing - Stability -- Other slashing issues blocking for launch - [#1256](https://github.com/cosmos/cosmos-sdk/issues/1256) -- Miscellaneous minor staking issues - - [List here](https://github.com/cosmos/cosmos-sdk/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Astaking+label%3Aprelaunch) - - Need to figure out scope of work here to estimate time - - @rigelrozanski to start next -- Consider "tombstone" / "prison" - double-sign and you can never validate again - https://github.com/cosmos/cosmos-sdk/issues/2363 +- [Prelaunch Issues](https://github.com/cosmos/cosmos-sdk/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Astaking+label%3Aprelaunch-2.0) ## Multisig @@ -16,31 +11,38 @@ ## ABCI Changes -- Need to update for new ABCI changes when/if they land - error string, tags are list of lists +- CheckEvidence/DeliverEvidence +- CheckTx/DeliverTx ordering semantics +- ABCI Error string update (only on the SDK side) - Need to verify correct proposer reward semantics -- CheckEvidence/DeliverEvidence, CheckTx/DeliverTx ordering semantics ## Gas -- Charge for transaction size -- Decide what "one gas" corresponds to (standard hardware benchmarks?) -- More benchmarking -- Consider charging based on maximum depth of IAVL tree iteration +- Write specification and explainer document for Gas in Cosmos + * Charge for transaction size + * Decide what "one gas" corresponds to (standard hardware benchmarks?) + * Consider charging based on maximum depth of IAVL tree iteration - Test out gas estimation in CLI and LCD and ensure the UX works ## LCD -- Bianje working on implementation of ICS standards -- Additional PR incoming for ICS 22 and ICS 23 -- Decide what ought to be ICS-standardized and what ought not to +- Bianje working with Voyager team (@fedekunze) to complete implementation and documentation. + +## Documentation +- gaiad / gaiacli +- LCD +- Each module +- Tags [#1780](https://github.com/cosmos/cosmos-sdk/issues/1780) # Lower priority +- Create some diagrams (see `docs/resources/diagrams/todo.md`) + ## Governance v2 - Circuit breaker - https://github.com/cosmos/cosmos-sdk/issues/926 - Parameter change proposals (roughly the same implementation as circuit breaker) -## Documentation +## Staking / Slashing - Stability -- gaiad / gaiacli / gaialite documentation! +- Consider "tombstone" / "prison" - double-sign and you can never validate again - https://github.com/cosmos/cosmos-sdk/issues/2363 diff --git a/docs/README.md b/docs/README.md index 13b831f39318..048f9f39f231 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,20 +1,38 @@ -# Welcome to the Cosmos Docs! +# Welcome to the SDK Docs! -![cosmonaut reading the cosmos docs in space](./graphics/cosmos-docs.jpg) +Welcome to the SDK docs! -Cosmos is a decentralized network of independent parallel blockchains, each powered by classical BFT consensus algorithms like Tendermint. -The first blockchain in the Cosmos Network is the Cosmos Hub, whose native token is the Atom. Cosmos is a permission-less network, meaning that anybody can build a blockchain on it. +![cosmonaut reading the cosmos docs in space](./cosmos-docs.jpg) -Cosmos can interoperate with multiple other applications and cryptocurrencies. By creating a new zone, you can plug any blockchain system into the Cosmos hub and pass tokens back and forth between those zones, without the need for an intermediary. +## Learn the SDK -## Quick Start +### SDK Intro -- [Getting started with the SDK](./sdk/core/intro.md) -- [SDK Examples](../examples) -- [Join the testnet](./getting-started/join-testnet.md#run-a-full-node) +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. -## Edit the Documentation +### 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: + +>*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). + +- [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). +- [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. + +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! + +## Join the public testnet for the Cosmos Hub + +To install the latest version of the `gaia` application and join the public testnet, **click [here](./gaia/README.md#join-the-cosmos-hub-public-testnet)** + +## Contribute See [this file](./DOCS_README.md) for details of the build process and considerations when making changes. diff --git a/docs/sdk/core/app1.md b/docs/_attic/sdk/core/app1.md similarity index 99% rename from docs/sdk/core/app1.md rename to docs/_attic/sdk/core/app1.md index 9338d30d99b6..a1aa76f72e39 100644 --- a/docs/sdk/core/app1.md +++ b/docs/_attic/sdk/core/app1.md @@ -14,10 +14,10 @@ Developers can create messages by implementing the `Msg` interface: ```go type Msg interface { - // Return the message type. + // Return the message Route. // Must be alphanumeric or empty. // Must correspond to name of message handler (XXX). - Type() string + Route() string // ValidateBasic does a simple validation check that // doesn't require access to any other information. @@ -49,7 +49,7 @@ type MsgSend struct { } // Implements Msg. -func (msg MsgSend) Type() string { return "send" } +func (msg MsgSend) Route() string { return "send" } ``` It specifies that the message should be JSON marshaled and signed by the sender: diff --git a/docs/sdk/core/app2.md b/docs/_attic/sdk/core/app2.md similarity index 97% rename from docs/sdk/core/app2.md rename to docs/_attic/sdk/core/app2.md index 6477617fb13d..b0e42fd10df7 100644 --- a/docs/sdk/core/app2.md +++ b/docs/_attic/sdk/core/app2.md @@ -28,11 +28,11 @@ type MsgIssue struct { } // Implements Msg. -func (msg MsgIssue) Type() string { return "issue" } +func (msg MsgIssue) Route() string { return "issue" } ``` -Note the `Type()` method returns `"issue"`, so this message is of a different -type and will be executed by a different handler than `MsgSend`. The other +Note the `Route()` method returns `"issue"`, so this message is of a different +route and will be executed by a different handler than `MsgSend`. The other methods for `MsgIssue` are similar to `MsgSend`. ## Handler @@ -178,7 +178,7 @@ func (tx app2Tx) GetMsgs() []sdk.Msg { func tx2Decoder(cdc *codec.Codec) sdk.TxDecoder { return func(txBytes []byte) (sdk.Tx, sdk.Error) { var tx app2Tx - err := cdc.UnmarshalBinary(txBytes, &tx) + err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) if err != nil { return nil, sdk.ErrTxDecode(err.Error()) } diff --git a/docs/sdk/core/app3.md b/docs/_attic/sdk/core/app3.md similarity index 91% rename from docs/sdk/core/app3.md rename to docs/_attic/sdk/core/app3.md index 595a3694fac5..2462e3e66a09 100644 --- a/docs/sdk/core/app3.md +++ b/docs/_attic/sdk/core/app3.md @@ -86,7 +86,7 @@ type BaseAccount struct { It simply contains a field for each of the methods. -### AccountMapper +### AccountKeeper In previous apps using our `appAccount`, we handled marshaling/unmarshaling the account from the store ourselves, by performing @@ -95,36 +95,36 @@ to work with in our applications. In the SDK, we use the term `Mapper` to refer to an abstaction over a KVStore that handles marshalling and unmarshalling a particular data type to and from the underlying store. -The `x/auth` module provides an `AccountMapper` that allows us to get and +The `x/auth` module provides an `AccountKeeper` that allows us to get and set `Account` types to the store. Note the benefit of using the `Account` interface here - developers can implement their own account type that extends the `BaseAccount` to store additional data without requiring another lookup from the store. -Creating an AccountMapper is easy - we just need to specify a codec, a +Creating an AccountKeeper is easy - we just need to specify a codec, a capability key, and a prototype of the object being encoded ```go -accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount) +accountKeeper := auth.NewAccountKeeper(cdc, keyAccount, auth.ProtoBaseAccount) ``` Then we can get, modify, and set accounts. For instance, we could double the amount of coins in an account: ```go -acc := accountMapper.GetAccount(ctx, addr) +acc := accountKeeper.GetAccount(ctx, addr) acc.SetCoins(acc.Coins.Plus(acc.Coins)) -accountMapper.SetAccount(ctx, addr) +accountKeeper.SetAccount(ctx, addr) ``` -Note that the `AccountMapper` takes a `Context` as the first argument, and will +Note that the `AccountKeeper` takes a `Context` as the first argument, and will load the KVStore from there using the capability key it was granted on creation. Also note that you must explicitly call `SetAccount` after mutating an account for the change to persist! -See the [AccountMapper API -docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth#AccountMapper) for more information. +See the [AccountKeeper API +docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth#AccountKeeper) for more information. ## StdTx @@ -224,10 +224,10 @@ all the relevant information. As we saw in `App2`, we can use an `AnteHandler` to authenticate transactions before we handle any of their internal messages. While previously we implemented our own simple `AnteHandler`, the `x/auth` module provides a much more advanced -one that uses `AccountMapper` and works with `StdTx`: +one that uses `AccountKeeper` and works with `StdTx`: ```go -app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper)) +app.SetAnteHandler(auth.NewAnteHandler(accountKeeper, feeKeeper)) ``` The AnteHandler provided by `x/auth` enforces the following rules: @@ -263,7 +263,7 @@ The fee is paid by the first address returned by `msg.GetSigners()` for the firs ## CoinKeeper -Now that we've seen the `auth.AccountMapper` and how its used to build a +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. @@ -274,26 +274,26 @@ which expose only limitted functionality on the underlying types stored by the ` 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, -rather than passing a `KVStore` or even an `AccountMapper` directly to the handler, +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 `Handler` can have on the store is to change the amount of coins in an account - it can't increment sequence numbers, change PubKeys, or otherwise. -A `bank.Keeper` is easily instantiated from an `AccountMapper`: +A `bank.Keeper` is easily instantiated from an `AccountKeeper`: ```go -bankKeeper = bank.NewBaseKeeper(accountMapper) +bankKeeper = bank.NewBaseKeeper(accountKeeper) ``` We can then use it within a handler, instead of working directly with the -`AccountMapper`. For instance, to add coins to an account: +`AccountKeeper`. For instance, to add coins to an account: ```go -// Finds account with addr in AccountMapper. +// Finds account with addr in AccountKeeper. // Adds coins to account's coin array. -// Sets updated account in AccountMapper +// Sets updated account in AccountKeeper app.bankKeeper.AddCoins(ctx, addr, coins) ``` @@ -311,12 +311,12 @@ 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 `AccountMapper`, +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.AccountMapper` 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: @@ -334,11 +334,11 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { keyFees := sdk.NewKVStoreKey("fee") // TODO // Set various mappers/keepers to interact easily with underlying stores - accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount) - bankKeeper := bank.NewBaseKeeper(accountMapper) + accountKeeper := auth.NewAccountKeeper(cdc, keyAccount, auth.ProtoBaseAccount) + bankKeeper := bank.NewBaseKeeper(accountKeeper) feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees) - app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper)) + app.SetAnteHandler(auth.NewAnteHandler(accountKeeper, feeKeeper)) // Register message routes. // Note the handler gets access to diff --git a/docs/sdk/core/app4.md b/docs/_attic/sdk/core/app4.md similarity index 100% rename from docs/sdk/core/app4.md rename to docs/_attic/sdk/core/app4.md diff --git a/docs/sdk/core/app5.md b/docs/_attic/sdk/core/app5.md similarity index 100% rename from docs/sdk/core/app5.md rename to docs/_attic/sdk/core/app5.md diff --git a/docs/sdk/core/examples/app1.go b/docs/_attic/sdk/core/examples/app1.go similarity index 98% rename from docs/sdk/core/examples/app1.go rename to docs/_attic/sdk/core/examples/app1.go index 13798345523f..8ee34fca59e7 100644 --- a/docs/sdk/core/examples/app1.go +++ b/docs/_attic/sdk/core/examples/app1.go @@ -56,8 +56,9 @@ func NewMsgSend(from, to sdk.AccAddress, amt sdk.Coins) MsgSend { } // Implements Msg. -func (msg MsgSend) Type() string { return "send" } -func (msg MsgSend) Name() string { return "send" } +// nolint +func (msg MsgSend) Route() string { return "send" } +func (msg MsgSend) Type() string { return "send" } // Implements Msg. Ensure the addresses are good and the // amount is positive. diff --git a/docs/sdk/core/examples/app2.go b/docs/_attic/sdk/core/examples/app2.go similarity index 97% rename from docs/sdk/core/examples/app2.go rename to docs/_attic/sdk/core/examples/app2.go index 3ff9d1dea3fa..24458384c0d3 100644 --- a/docs/sdk/core/examples/app2.go +++ b/docs/_attic/sdk/core/examples/app2.go @@ -76,8 +76,9 @@ type MsgIssue struct { } // Implements Msg. -func (msg MsgIssue) Type() string { return "issue" } -func (msg MsgIssue) Name() string { return "issue" } +// nolint +func (msg MsgIssue) Route() string { return "issue" } +func (msg MsgIssue) Type() string { return "issue" } // Implements Msg. Ensures addresses are valid and Coin is positive func (msg MsgIssue) ValidateBasic() sdk.Error { @@ -200,7 +201,7 @@ func (tx app2Tx) GetSignature() []byte { func tx2Decoder(cdc *codec.Codec) sdk.TxDecoder { return func(txBytes []byte) (sdk.Tx, sdk.Error) { var tx app2Tx - err := cdc.UnmarshalBinary(txBytes, &tx) + err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) if err != nil { return nil, sdk.ErrTxDecode(err.Error()) } diff --git a/docs/sdk/core/examples/app2_test.go b/docs/_attic/sdk/core/examples/app2_test.go similarity index 92% rename from docs/sdk/core/examples/app2_test.go rename to docs/_attic/sdk/core/examples/app2_test.go index 9139034880cd..c59dec26477f 100644 --- a/docs/sdk/core/examples/app2_test.go +++ b/docs/_attic/sdk/core/examples/app2_test.go @@ -39,7 +39,7 @@ func TestEncoding(t *testing.T) { cdc := NewCodec() testTxDecoder := tx2Decoder(cdc) - encodedSendTx, err := cdc.MarshalBinary(sendTxBefore) + encodedSendTx, err := cdc.MarshalBinaryLengthPrefixed(sendTxBefore) require.Nil(t, err, "Error encoding sendTx") @@ -69,7 +69,7 @@ func TestEncoding(t *testing.T) { Signature: sig, } - encodedIssueTx, err2 := cdc.MarshalBinary(issueTxBefore) + encodedIssueTx, err2 := cdc.MarshalBinaryLengthPrefixed(issueTxBefore) require.Nil(t, err2, "Error encoding issueTx") diff --git a/docs/sdk/core/examples/app3.go b/docs/_attic/sdk/core/examples/app3.go similarity index 90% rename from docs/sdk/core/examples/app3.go rename to docs/_attic/sdk/core/examples/app3.go index 9a101c281620..453970c1af3b 100644 --- a/docs/sdk/core/examples/app3.go +++ b/docs/_attic/sdk/core/examples/app3.go @@ -30,11 +30,11 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { keyFees := sdk.NewKVStoreKey("fee") // TODO // Set various mappers/keepers to interact easily with underlying stores - accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount) - bankKeeper := bank.NewBaseKeeper(accountMapper) + accountKeeper := auth.NewAccountKeeper(cdc, keyAccount, auth.ProtoBaseAccount) + bankKeeper := bank.NewBaseKeeper(accountKeeper) feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees) - app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper)) + app.SetAnteHandler(auth.NewAnteHandler(accountKeeper, feeKeeper)) // Register message routes. // Note the handler gets access to diff --git a/docs/sdk/core/examples/app4.go b/docs/_attic/sdk/core/examples/app4.go similarity index 85% rename from docs/sdk/core/examples/app4.go rename to docs/_attic/sdk/core/examples/app4.go index e4fa515ee232..6d45c403153e 100644 --- a/docs/sdk/core/examples/app4.go +++ b/docs/_attic/sdk/core/examples/app4.go @@ -28,17 +28,17 @@ func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp { keyAccount := sdk.NewKVStoreKey("acc") // Set various mappers/keepers to interact easily with underlying stores - accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount) - bankKeeper := bank.NewBaseKeeper(accountMapper) + accountKeeper := auth.NewAccountKeeper(cdc, keyAccount, auth.ProtoBaseAccount) + bankKeeper := bank.NewBaseKeeper(accountKeeper) // TODO keyFees := sdk.NewKVStoreKey("fee") feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees) - app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper)) + app.SetAnteHandler(auth.NewAnteHandler(accountKeeper, feeKeeper)) // Set InitChainer - app.SetInitChainer(NewInitChainer(cdc, accountMapper)) + app.SetInitChainer(NewInitChainer(cdc, accountKeeper)) // Register message routes. // Note the handler gets access to the account store. @@ -76,7 +76,7 @@ func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount, err error) { // InitChainer will set initial balances for accounts as well as initial coin metadata // MsgIssue can no longer be used to create new coin -func NewInitChainer(cdc *codec.Codec, accountMapper auth.AccountMapper) sdk.InitChainer { +func NewInitChainer(cdc *codec.Codec, accountKeeper auth.AccountKeeper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { stateJSON := req.AppStateBytes @@ -91,8 +91,8 @@ func NewInitChainer(cdc *codec.Codec, accountMapper auth.AccountMapper) sdk.Init if err != nil { panic(err) } - acc.AccountNumber = accountMapper.GetNextAccountNumber(ctx) - accountMapper.SetAccount(ctx, acc) + acc.AccountNumber = accountKeeper.GetNextAccountNumber(ctx) + accountKeeper.SetAccount(ctx, acc) } return abci.ResponseInitChain{} diff --git a/docs/sdk/core/examples/app4_test.go b/docs/_attic/sdk/core/examples/app4_test.go similarity index 100% rename from docs/sdk/core/examples/app4_test.go rename to docs/_attic/sdk/core/examples/app4_test.go diff --git a/docs/sdk/core/intro.md b/docs/_attic/sdk/core/intro.md similarity index 100% rename from docs/sdk/core/intro.md rename to docs/_attic/sdk/core/intro.md diff --git a/docs/sdk/core/multistore.md b/docs/_attic/sdk/core/multistore.md similarity index 100% rename from docs/sdk/core/multistore.md rename to docs/_attic/sdk/core/multistore.md diff --git a/docs/sdk/sdk-by-examples/intro.md b/docs/_attic/sdk/sdk-by-examples/intro.md similarity index 100% rename from docs/sdk/sdk-by-examples/intro.md rename to docs/_attic/sdk/sdk-by-examples/intro.md diff --git a/docs/sdk/sdk-by-examples/simple-governance/app-init.md b/docs/_attic/sdk/sdk-by-examples/simple-governance/app-init.md similarity index 100% rename from docs/sdk/sdk-by-examples/simple-governance/app-init.md rename to docs/_attic/sdk/sdk-by-examples/simple-governance/app-init.md diff --git a/docs/sdk/sdk-by-examples/simple-governance/bridging-it-all.md b/docs/_attic/sdk/sdk-by-examples/simple-governance/bridging-it-all.md similarity index 98% rename from docs/sdk/sdk-by-examples/simple-governance/bridging-it-all.md rename to docs/_attic/sdk/sdk-by-examples/simple-governance/bridging-it-all.md index aed6de85ef05..546eb7d875f6 100644 --- a/docs/sdk/sdk-by-examples/simple-governance/bridging-it-all.md +++ b/docs/_attic/sdk/sdk-by-examples/simple-governance/bridging-it-all.md @@ -45,7 +45,7 @@ type SimpleGovApp struct { simpleGovKeeper simpleGov.Keeper // Manage getting and setting accounts - accountMapper auth.AccountMapper + accountKeeper auth.AccountKeeper } ``` @@ -206,7 +206,7 @@ var cdc = MakeCodec() - Instantiate the keepers. Note that keepers generally need access to other module's keepers. In this case, make sure you only pass an instance of the keeper for the functionality that is needed. If a keeper only needs to read in another module's store, a read-only keeper should be passed to it. ```go -app.bankKeeper = bank.NewBaseKeeper(app.accountMapper) +app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper) app.stakeKeeper = simplestake.NewKeeper(app.capKeyStakingStore, app.bankKeeper,app.RegisterCodespace(simplestake.DefaultCodespace)) app.simpleGovKeeper = simpleGov.NewKeeper(app.capKeySimpleGovStore, app.bankKeeper, app.stakeKeeper, app.RegisterCodespace(simpleGov.DefaultCodespace)) ``` @@ -225,7 +225,7 @@ app.Router(). ```go // Initialize BaseApp. app.MountStoresIAVL(app.capKeyMainStore, app.capKeyAccountStore, app.capKeySimpleGovStore, app.capKeyStakingStore) - app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) + app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper)) err := app.LoadLatestVersion(app.capKeyMainStore) if err != nil { cmn.Exit(err.Error()) diff --git a/docs/sdk/sdk-by-examples/simple-governance/intro.md b/docs/_attic/sdk/sdk-by-examples/simple-governance/intro.md similarity index 100% rename from docs/sdk/sdk-by-examples/simple-governance/intro.md rename to docs/_attic/sdk/sdk-by-examples/simple-governance/intro.md diff --git a/docs/sdk/sdk-by-examples/simple-governance/running-the-application.md b/docs/_attic/sdk/sdk-by-examples/simple-governance/running-the-application.md similarity index 100% rename from docs/sdk/sdk-by-examples/simple-governance/running-the-application.md rename to docs/_attic/sdk/sdk-by-examples/simple-governance/running-the-application.md diff --git a/docs/sdk/sdk-by-examples/simple-governance/setup-and-design.md b/docs/_attic/sdk/sdk-by-examples/simple-governance/setup-and-design.md similarity index 100% rename from docs/sdk/sdk-by-examples/simple-governance/setup-and-design.md rename to docs/_attic/sdk/sdk-by-examples/simple-governance/setup-and-design.md diff --git a/docs/sdk/sdk-by-examples/simple-governance/simple-gov-module.md b/docs/_attic/sdk/sdk-by-examples/simple-governance/simple-gov-module.md similarity index 100% rename from docs/sdk/sdk-by-examples/simple-governance/simple-gov-module.md rename to docs/_attic/sdk/sdk-by-examples/simple-governance/simple-gov-module.md diff --git a/docs/architecture/adr-002-docs-structure.md b/docs/architecture/adr-002-docs-structure.md new file mode 100644 index 000000000000..4d80ad711579 --- /dev/null +++ b/docs/architecture/adr-002-docs-structure.md @@ -0,0 +1,94 @@ +# ADR 002: SDK Documentation Structure + +## 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. + +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.) +- All other docs (faqs, whitepaper, high-level material about Cosmos) would live on the website. + +## Decision + +Re-structure the `/docs` folder of the SDK github repo as follows: + +``` +docs/ +├── README +├── intro/ +├── gaia/ +│ ├── overview.md +│ ├── install.md +│ ├── join-testnet.md +│ ├── validator-node.md +│ ├── validator-faq.md +│ └── delegator-faq.md +├── reference/ +│ ├── baseapp +│ ├── types +│ ├── store +│ ├── server +│ ├── modules/ +│ │ ├── keeper +│ │ ├── handler +│ │ ├── cli +│ ├── gas +│ └── commands +├── examples/ +│ ├── basecoin/ +│ └── democoin/ +├── clients/ +│ ├── lite/ +│ ├── service-providers +├── spec/ +└── architecture/ +``` + +The files in each sub-folders do not matter and will likely change. What matters is the sectioning: + +- `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. +- `reference`: 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. +- `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. + +Website docs sidebar will only include the following sections: + +- `README` +- `intro` +- `gaia` +- `reference` +- `clients` + +`architecture` and `examples` need not be displayed on the website. As for `modules`, we might need to think about creating a modules manager, but this is out of scope for this document. + +## Status + +Proposed + +## Consequences + +### Positive + +- 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). +- Easier for developers to find what they need to update in the docs thanks to reworked architecture. +- 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 integrate content in `docs/sdk/docs/core` in `reference`. +- 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` + +## References + +- 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 diff --git a/docs/architecture/decision-records/README.md b/docs/architecture/decision-records/README.md deleted file mode 100644 index 7c669566df6c..000000000000 --- a/docs/architecture/decision-records/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Architecture Decision Records (ADR) - -This is a location to record all high-level architecture decisions in the cosmos-sdk project. - -You can read more about the ADR concept in this [blog post](https://product.reverb.com/documenting-architecture-decisions-the-reverb-way-a3563bb24bd0#.78xhdix6t). - -An ADR should provide: - -- Context on the relevant goals and the current state -- Proposed changes to achieve the goals -- Summary of pros and cons -- References -- Changelog - -Note the distinction between an ADR and a spec. The ADR provides the context, intuition, reasoning, and -justification for a change in architecture, or for the architecture of something -new. The spec is much more compressed and streamlined summary of everything as -it stands today. - -If recorded decisions turned out to be lacking, convene a discussion, record the new decisions here, and then modify the code to match. - -Note the context/background should be written in the present tense. diff --git a/docs/architecture/decision-records/adr-001-message-counter.md b/docs/architecture/decision-records/adr-001-message-counter.md deleted file mode 100644 index 0e9f25990eed..000000000000 --- a/docs/architecture/decision-records/adr-001-message-counter.md +++ /dev/null @@ -1,54 +0,0 @@ -# ADR 001: Global Message Counter - -## Context - -There is a desire for modules to have a concept of orderings between messages. - -One such example is in staking, we currently use an "intra bond tx counter" and -bond height. -The purpose these two serve is to providing an ordering for validators with equal stake, -for usage in the power-ranking of validators. -We can't use address here, as that would create a bad incentive to grind -addresses that optimized the sort function, which lowers the private key's -security. -Instead we order by whose transaction appeared first, as tracked by bondHeight -and intra bond tx counter. - -This logic however should not be unique to staking. -It is very conceivable that many modules in the future will want to be able to -know the ordering of messages / objects after they were initially created. - -## Decision - -Create a global message counter field of type int64. -Note that with int64's, there is no fear of overflow under normal use, -as it is only getting incremented by one, -and thus has a space of 9 quintillion values to go through. - -This counter must be persisted in state, but can just be read and written on -begin/end block respectively. -This field will get incremented upon every DeliverTx, -regardless if the transaction succeeds or not. -It must also be incremented within the check state for CheckTx. -The global message ordering field should be set within the context -so that modules can access it. - -## Corollary - Intra block ordering -In the event that there is desire to just have an intra block msg counter, -this can easily be derived from the global message counter. -Simply subtract current counter from first global message counter in the block. -Thus the relevant module could easily implement this. - -## Status -Proposed - -## Consequences - -### Positive -* Moves message ordering out of the set of things staking must keep track of -* Abstracts the logic well so other modules can use it - -### Negative -* Another thing to implement prelaunch. (Though this should be easy to implement) - -### Neutral diff --git a/docs/architecture/decision-records/adr-template.md b/docs/architecture/decision-records/adr-template.md deleted file mode 100644 index 6153a9d1ee21..000000000000 --- a/docs/architecture/decision-records/adr-template.md +++ /dev/null @@ -1,32 +0,0 @@ -# ADR {ADR-NUMBER}: {TITLE} - -## Changelog -* {date}: {changelog} - -## Context -> This section contains all the context one needs to understand the current state, and why there is a problem. It should be as succinct as possible and introduce the high level idea behind the solution. - -## Decision -> This section explains all of the details of the proposed solution, including implementation details. -It should also describe affects / corollary items that may need to be changed as a part of this. -If the proposed change will be large, please also indicate a way to do the change to maximize ease of review. -(e.g. the optimal split of things to do between separate PR's) - -## Status -> A decision may be "proposed" if it hasn't been agreed upon yet, or "accepted" once it is agreed upon. If a later ADR changes or reverses a decision, it may be marked as "deprecated" or "superseded" with a reference to its replacement. - -{Deprecated|Proposed|Accepted} - -## Consequences -> This section describes the consequences, after applying the decision. All consequences should be summarized here, not just the "positive" ones. - -### Positive - -### Negative - -### Neutral - -## References -> Are there any relevant PR comments, issues that led up to this, or articles referrenced for why we made the given design choice? If so link them here! - -* {reference link} diff --git a/docs/clients/README.md b/docs/clients/README.md new file mode 100644 index 000000000000..835b6ee5095b --- /dev/null +++ b/docs/clients/README.md @@ -0,0 +1,18 @@ +# Clients + +This section explains contains information on clients for SDK based blockchain. + +>*NOTE*: This section is a WIP. + +## Light-client + +Light-clients enable users to interact with your application without having to download the entire state history but with a good level of security. + +- [Overview of light clients](./lite/README.md) +- [Starting a light-client server](./lite/getting_started.md) +- [Light-client specification](./lite/specification.md) + +## Other clients + +- [Command-Line interface for SDK-based blockchain](./cli.md) +- [Service provider doc](./service-providers.md) diff --git a/docs/clients/cli.md b/docs/clients/cli.md index dbd234d3721c..fda1ef60b91e 100644 --- a/docs/clients/cli.md +++ b/docs/clients/cli.md @@ -1,8 +1,3 @@ # CLI -See `gaiacli --help` for more details. - -Also see the [testnet -tutorial](https://github.com/cosmos/cosmos-sdk/tree/develop/cmd/gaia/testnets). - -TODO: cleanup the UX and document this properly. +> TODO: Rewrite this section to explain how CLI works for a generic SDK app. diff --git a/docs/light/getting_started.md b/docs/clients/lite/getting_started.md similarity index 56% rename from docs/light/getting_started.md rename to docs/clients/lite/getting_started.md index 21497477a701..15bc8a2794a5 100644 --- a/docs/light/getting_started.md +++ b/docs/clients/lite/getting_started.md @@ -1,6 +1,7 @@ # Getting Started To start a REST server, we need to specify the following parameters: + | Parameter | Type | Default | Required | Description | | ----------- | --------- | ----------------------- | -------- | ---------------------------------------------------- | | chain-id | string | null | true | chain id of the full node to connect | @@ -9,21 +10,21 @@ To start a REST server, we need to specify the following parameters: | trust-node | bool | "false" | true | Whether this LCD is connected to a trusted full node | | trust-store | DIRECTORY | "$HOME/.lcd" | false | directory for save checkpoints and validator sets | -Sample command: +For example:: ```bash -gaiacli rest-server --chain-id=test \ +gaiacli advanced rest-server --chain-id=test \ --laddr=tcp://localhost:1317 \ - --node tcp://localhost:46657 \ + --node tcp://localhost:26657 \ --trust-node=false ``` The server listens on HTTPS by default. You can set the SSL certificate to be used by the server with these additional flags: ```bash -gaiacli rest-server --chain-id=test \ +gaiacli advanced rest-server --chain-id=test \ --laddr=tcp://localhost:1317 \ - --node tcp://localhost:46657 \ + --node tcp://localhost:26657 \ --trust-node=false \ --certfile=mycert.pem --keyfile=mykey.key ``` @@ -31,26 +32,4 @@ gaiacli rest-server --chain-id=test \ If no certificate/keyfile pair is supplied, a self-signed certificate will be generated and its fingerprint printed out. Append `--insecure` to the command line if you want to disable the secure layer and listen on an insecure HTTP port. -## Gaia Light Use Cases - -LCD could be very helpful for related service providers. For a wallet service provider, LCD could -make transaction faster and more reliable in the following cases. - -### Create an account - -![deposit](pics/create-account.png) - -First you need to get a new seed phrase :[get-seed](api.md#keysseed---get) - -After having new seed, you could generate a new account with it : [keys](api.md#keys---post) - -### Transfer a token - -![transfer](pics/transfer-tokens.png) - -The first step is to build an asset transfer transaction. Here we can post all necessary parameters -to /create_transfer to get the unsigned transaction byte array. Refer to this link for detailed -operation: [build transaction](api.md#create_transfer---post) - -Then sign the returned transaction byte array with users' private key. Finally broadcast the signed -transaction. Refer to this link for how to broadcast the signed transaction: [broadcast transaction](api.md#create_transfer---post) +For more information about the Gaia-Lite RPC, see the [swagger documentation](https://cosmos.network/rpc/) diff --git a/docs/light/pics/C2H.png b/docs/clients/lite/pics/C2H.png similarity index 100% rename from docs/light/pics/C2H.png rename to docs/clients/lite/pics/C2H.png diff --git a/docs/light/pics/H2C.png b/docs/clients/lite/pics/H2C.png similarity index 100% rename from docs/light/pics/H2C.png rename to docs/clients/lite/pics/H2C.png diff --git a/docs/light/pics/MA.png b/docs/clients/lite/pics/MA.png similarity index 100% rename from docs/light/pics/MA.png rename to docs/clients/lite/pics/MA.png diff --git a/docs/light/pics/absence1.png b/docs/clients/lite/pics/absence1.png similarity index 100% rename from docs/light/pics/absence1.png rename to docs/clients/lite/pics/absence1.png diff --git a/docs/light/pics/absence2.png b/docs/clients/lite/pics/absence2.png similarity index 100% rename from docs/light/pics/absence2.png rename to docs/clients/lite/pics/absence2.png diff --git a/docs/light/pics/absence3.png b/docs/clients/lite/pics/absence3.png similarity index 100% rename from docs/light/pics/absence3.png rename to docs/clients/lite/pics/absence3.png diff --git a/docs/light/pics/architecture.png b/docs/clients/lite/pics/architecture.png similarity index 100% rename from docs/light/pics/architecture.png rename to docs/clients/lite/pics/architecture.png diff --git a/docs/light/pics/changeProcess.png b/docs/clients/lite/pics/changeProcess.png similarity index 100% rename from docs/light/pics/changeProcess.png rename to docs/clients/lite/pics/changeProcess.png diff --git a/docs/light/pics/commitValidation.png b/docs/clients/lite/pics/commitValidation.png similarity index 100% rename from docs/light/pics/commitValidation.png rename to docs/clients/lite/pics/commitValidation.png diff --git a/docs/light/pics/create-account.png b/docs/clients/lite/pics/create-account.png similarity index 100% rename from docs/light/pics/create-account.png rename to docs/clients/lite/pics/create-account.png diff --git a/docs/light/pics/deposit.png b/docs/clients/lite/pics/deposit.png similarity index 100% rename from docs/light/pics/deposit.png rename to docs/clients/lite/pics/deposit.png diff --git a/docs/light/pics/existProof.png b/docs/clients/lite/pics/existProof.png similarity index 100% rename from docs/light/pics/existProof.png rename to docs/clients/lite/pics/existProof.png diff --git a/docs/light/pics/high-level.png b/docs/clients/lite/pics/high-level.png similarity index 100% rename from docs/light/pics/high-level.png rename to docs/clients/lite/pics/high-level.png diff --git a/docs/light/pics/light-client-architecture.png b/docs/clients/lite/pics/light-client-architecture.png similarity index 100% rename from docs/light/pics/light-client-architecture.png rename to docs/clients/lite/pics/light-client-architecture.png diff --git a/docs/light/pics/loadbalanceDiagram.png b/docs/clients/lite/pics/loadbalanceDiagram.png similarity index 100% rename from docs/light/pics/loadbalanceDiagram.png rename to docs/clients/lite/pics/loadbalanceDiagram.png diff --git a/docs/light/pics/simpleMerkleTree.png b/docs/clients/lite/pics/simpleMerkleTree.png similarity index 100% rename from docs/light/pics/simpleMerkleTree.png rename to docs/clients/lite/pics/simpleMerkleTree.png diff --git a/docs/light/pics/substoreProof.png b/docs/clients/lite/pics/substoreProof.png similarity index 100% rename from docs/light/pics/substoreProof.png rename to docs/clients/lite/pics/substoreProof.png diff --git a/docs/light/pics/transfer-tokens.png b/docs/clients/lite/pics/transfer-tokens.png similarity index 100% rename from docs/light/pics/transfer-tokens.png rename to docs/clients/lite/pics/transfer-tokens.png diff --git a/docs/light/pics/transfer.png b/docs/clients/lite/pics/transfer.png similarity index 100% rename from docs/light/pics/transfer.png rename to docs/clients/lite/pics/transfer.png diff --git a/docs/light/pics/trustPropagate.png b/docs/clients/lite/pics/trustPropagate.png similarity index 100% rename from docs/light/pics/trustPropagate.png rename to docs/clients/lite/pics/trustPropagate.png diff --git a/docs/light/pics/updateValidatorToHeight.png b/docs/clients/lite/pics/updateValidatorToHeight.png similarity index 100% rename from docs/light/pics/updateValidatorToHeight.png rename to docs/clients/lite/pics/updateValidatorToHeight.png diff --git a/docs/light/pics/validatorSetChange.png b/docs/clients/lite/pics/validatorSetChange.png similarity index 100% rename from docs/light/pics/validatorSetChange.png rename to docs/clients/lite/pics/validatorSetChange.png diff --git a/docs/light/pics/withdraw.png b/docs/clients/lite/pics/withdraw.png similarity index 100% rename from docs/light/pics/withdraw.png rename to docs/clients/lite/pics/withdraw.png diff --git a/docs/clients/lite/readme.md b/docs/clients/lite/readme.md new file mode 100644 index 000000000000..e7b57deedc16 --- /dev/null +++ b/docs/clients/lite/readme.md @@ -0,0 +1,86 @@ +# Lite Client Overview + +**See the Cosmos SDK lite Client RPC documentation [here](https://cosmos.network/rpc/)** + +## Introduction + +A lite client allows clients, such as mobile phones, to receive proofs of the state of the +blockchain from any full node. lite clients do not have to trust any full node, since they are able +to verify any proof they receive and hence full nodes cannot lie about the state of the network. + +A lite client can provide the same security as a full node with the minimal requirements on +bandwidth, computing and storage resource. As well, it can also provide modular functionality +according to users' configuration. These fantastic features allow developers to build fully secure, +efficient and usable mobile apps, websites or any other applications without deploying or +maintaining any full blockchain nodes. + +### What is a lite Client + +The Cosmos SDK Light Client (Gaia-lite) is split into two separate components. The first component is generic for any Tendermint +based application. It handles the security and connectivity aspects of following the header chain +and verify proofs from full nodes against locally trusted validator set. Furthermore it exposes +exactly the same API as any Tendermint Core node. The second component is specific for the Cosmos +Hub (`gaiad`). It works as a query endpoint and exposes the application specific functionality, which +can be arbitrary. All queries against the application state have to go through the query endpoint. +The advantage of the query endpoint is that it can verify the proofs that the application returns. + +### High-Level Architecture + +An application developer that wants to build a third party client application for the Cosmos Hub (or any +other zone) should build it against its canonical API. That API is a combination of multiple parts. +All zones have to expose ICS0 (TendermintAPI). Beyond that any zone is free to choose any +combination of module APIs, depending on which modules the state machine uses. The Cosmos Hub will +initially support [ICS0](https://cosmos.network/rpc/#/ICS0) (TendermintAPI), [ICS1](https://cosmos.network/rpc/#/ICS1) (KeyAPI), [ICS20](https://cosmos.network/rpc/#/ICS20) (TokenAPI), [ICS21](https://cosmos.network/rpc/#/ICS21) (StakingAPI), +[ICS22](https://cosmos.network/rpc/#/ICS22) (GovernanceAPI) and [ICS23](https://cosmos.network/rpc/#/ICS23) (SlashingAPI). + +![high-level](./pics/high-level.png) + +All applications are expected to only run against Gaia-lite. Gaia-lite is the only piece of software +that offers stability guarantees around the zone API. + +### Comparision + +A full node of ABCI is different from its lite client in the following ways: + +|| Full Node | Gaia-lite | Description| +|-| ------------- | ----- | -------------- | +| Execute and verify transactions|Yes|No|Full node will execute and verify all transactions while Gaia-lite won't| +| Verify and save blocks|Yes|No|Full node will verify and save all blocks while Gaia-lite won't| +| Participate consensus| Yes|No|Only when the full node is a validtor, it will participate consensus. Lite nodes never participate consensus| +| Bandwidth cost|Huge|Little|Full node will receive all blocks. if the bandwidth is limited, it will fall behind the main network. What's more, if it happens to be a validator,it will slow down the consensus process. Light clients requires little bandwidth. Only when serving local request, it will cost bandwidth| +| Computing resource|Huge|Little|Full node will execute all transactions and verify all blocks which require much computing resource| +| Storage resource|Huge|Little|Full node will save all blocks and ABCI states. Gaia-lite just saves validator sets and some checkpoints| +| Power consume|Huge|Little|Full nodes have to be deployed on machines which have high performance and will be running all the time. So power consume will be huge. Gaia-lite can be deployed on the same machines as users' applications, or on independent machines but with poor performance. Besides, lite clients can be shutdown anytime when necessary. So Gaia-lite only consume very little power, even mobile devices can meet the power requirement| +| Provide APIs|All cosmos APIs|Modular APIs|Full node supports all cosmos APIs. Gaia-lite provides modular APIs according to users' configuration| +| Secuity level| High|High|Full node will verify all transactions and blocks by itself. A light client can't do this, but it can query any data from other full nodes and verify the data independently. So both full nodes and light clients don't need to trust any third nodes, they all can achieve high security| + +According to the above table, Gaia-lite can meet all users' functionality and security requirements, but +only requires little resource on bandwidth, computing, storage and power. + +## Achieving Security + +### Trusted Validator Set + +The base design philosophy of Gaia-lite follows two rules: + +1. **Doesn't trust any blockchain nodes, including validator nodes and other full nodes** +2. **Only trusts the whole validator set** + +The original trusted validator set should be prepositioned into its trust store, usually this +validator set comes from genesis file. During runtime, if Gaia-lite detects a different validator set, +it will verify it and save new validated validator set to the trust store. + +![validator-set-change](./pics/validatorSetChange.png) + +### Trust Propagation + +From the above section, we come to know how to get trusted validator set and how lcd keeps track of +validator set evolution. Validator set is the foundation of trust, and the trust can propagate to +other blockchain data, such as block and transaction. The propagate architecture is shown as +follows: + +![change-process](./pics/trustPropagate.png) + +In general, by trusted validator set, a light client can verify each block commit which contains all pre-commit +data and block header data. Then the block hash, data hash and appHash are trusted. Based on this +and merkle proof, all transactions data and ABCI states can be verified too. diff --git a/docs/light/specification.md b/docs/clients/lite/specification.md similarity index 64% rename from docs/light/specification.md rename to docs/clients/lite/specification.md index 15f36b014460..032f840a59bc 100644 --- a/docs/light/specification.md +++ b/docs/clients/lite/specification.md @@ -12,7 +12,7 @@ we need to extract name, height and store root hash from these substores to buil Merkle leaf nodes, then calculate hash from leaf nodes to root. The root hash of the simple Merkle tree is the AppHash which will be included in block header. -![Simple Merkle Tree](pics/simpleMerkleTree.png) +![Simple Merkle Tree](./pics/simpleMerkleTree.png) As we have discussed in [LCD trust-propagation](https://github.com/irisnet/cosmos-sdk/tree/bianjie/lcd_spec/docs/spec/lcd#trust-propagation), the AppHash can be verified by checking voting power against a trusted validator set. Here we just @@ -65,7 +65,7 @@ type KeyExistsProof struct { The data structure of exist proof is shown as above. The process to build and verify existance proof is shown as follows: -![Exist Proof](pics/existProof.png) +![Exist Proof](./pics/existProof.png) Steps to build proof: @@ -92,12 +92,12 @@ the postition of the target key in the whole key set of this IAVL tree. As shown out the left key and the right key. If we can demonstrate that both left key and right key definitely exist, and they are adjacent nodes. Thus the target key definitely doesn't exist. -![Absence Proof1](pics/absence1.png) +![Absence Proof1](./pics/absence1.png) If the target key is larger than the right most leaf node or less than the left most key, then the target key definitely doesn't exist. -![Absence Proof2](pics/absence2.png)![Absence Proof3](pics/absence3.png) +![Absence Proof2](./pics/absence2.png)![Absence Proof3](./pics/absence3.png) ```go type proofLeafNode struct { @@ -147,7 +147,7 @@ in commitID equals to proof RootHash. If not, the proof is invalid. Then sort th commitInfo array by the hash of substore name. Finally, build the simple Merkle tree with all substore commitInfo array and verify if the Merkle root hash equal to appHash. -![substore proof](pics/substoreProof.png) +![substore proof](./pics/substoreProof.png) ```go func SimpleHashFromTwoHashes(left []byte, right []byte) []byte { @@ -187,7 +187,7 @@ Above sections refer appHash frequently. But where does the trusted appHash come appHash exist in block header, so next we need to verify blocks header at specific height against LCD trusted validator set. The validation flow is shown as follows: -![commit verification](pics/commitValidation.png) +![commit verification](./pics/commitValidation.png) When the trusted validator set doesn't match the block header, we need to try to update our validator set to the height of this block. LCD have a rule that each validator set change should not @@ -198,7 +198,7 @@ validator set update be accomplished. For instance: -![Update validator set to height](pics/updateValidatorToHeight.png) +![Update validator set to height](./pics/updateValidatorToHeight.png) * Update to 10000, tooMuchChangeErr * Update to 5050, tooMuchChangeErr @@ -207,150 +207,3 @@ For instance: * Update to 10000,tooMuchChangeErr * Update to 7525, Success * Update to 10000, Success - -## Load Balancing - -To improve LCD reliability and TPS, we recommend to connect LCD to more than one fullnode. But the -complexity will increase a lot. So load balancing module will be imported as the adapter. Please -refer to this link for detailed description: [load balancer](load_balancer.md) - -## ICS1 (KeyAPI) - -### [/keys - GET](api.md#keys---get) - -Load the key store: - -```go -db, err := dbm.NewGoLevelDB(KeyDBName, filepath.Join(rootDir, "keys")) -if err != nil { - return nil, err -} - -keybase = client.GetKeyBase(db) -``` - -Iterate through the key store. - -```go -var res []Info -iter := kb.db.Iterator(nil, nil) -defer iter.Close() - -for ; iter.Valid(); iter.Next() { - // key := iter.Key() - info, err := readInfo(iter.Value()) - if err != nil { - return nil, err - } - res = append(res, info) -} - -return res, nil -``` - -Encode the addresses and public keys in bech32. - -```go -bechAccount, err := sdk.Bech32ifyAcc(sdk.Address(info.PubKey.Address().Bytes())) -if err != nil { - return KeyOutput{}, err -} - -bechPubKey, err := sdk.Bech32ifyAccPub(info.PubKey) -if err != nil { - return KeyOutput{}, err -} - -return KeyOutput{ - Name: info.Name, - Address: bechAccount, - PubKey: bechPubKey, -}, nil -``` - -### [/keys/recover - POST](api.md#keys/recover---get) - -1. Load the key store. -2. Parameter checking. Name, password and seed should not be empty. -3. Check for keys with the same name. -4. Build the key from the name, password and seed. -5. Persist the key to key store. - -### [/keys/create - GET](api.md#keys/create---get)** - -1. Load the key store. -2. Create a new key in the key store. -3. Save the key to disk. -4. Return the seed. - -### [/keys/{name} - GET](api.md#keysname---get) - -1. Load the key store. -2. Iterate the whole key store to find the key by name. -3. Encode address and public key in bech32. - -### [/keys/{name} - PUT](api.md#keysname---put) - -1. Load the key store. -2. Iterate the whole key store to find the key by name. -3. Verify if that the old-password matches the current key password. -4. Re-persist the key with the new password. - -### [/keys/{name} - DELETE](api.md#keysname---delete) - -1. Load the key store. -2. Iterate the whole key store to find the key by name. -3. Verify that the specified password matches the current key password. -4. Delete the key from the key store. - -## ICS20 (TokenAPI) - -### [/bank/balance/{account}](api.md#bankbalanceaccount---get) - -1. Decode the address from bech32 to hex. -2. Send a query request to a full node. Ask for proof if required by Gaia Light. -3. Verify the proof against the root of trust. - -### [/bank/create_transfer](api.md#bankcreate_transfer---post) - -1. Check the parameters. -2. Build the transaction with the specified parameters. -3. Serialise the transaction and return the JSON encoded sign bytes. - -## ICS21 (StakingAPI) - -### [/stake/delegators/{delegatorAddr}](api.md#stakedelegatorsdelegatorAddr---get) - -TODO - -### [/stake/delegators/{delegatorAddr}/validators](api.md#stakedelegatorsdelegatorAddrvalidators---get) - -TODO - -### [/stake/delegators/{delegatorAddr}/validators/{validatorAddr}](api.md#stakedelegatorsdelegatorAddrvalidatorsvalidatorAddr---get) - -TODO - -### [/stake/delegators/{delegatorAddr}/txs](api.md#stakedelegatorsdelegatorAddrtxs---get) - -TODO - -### [/stake/delegators/{delegatorAddr}/delegations](api.md#stakedelegatorsdelegatorAddrdelegations---post) - -TODO - -### [/stake/delegators/{delegatorAddr}/delegations/{validatorAddr}](api.md#stakedelegatorsdelegatorAddrdelegationsvalidatorAddr---get) - -TODO - -### [/stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}](api.md#stakedelegatorsdelegatorAddrunbonding_delegationsvalidatorAddr---get) - -TODO - -### [/stake/validators](api.md#stakevalidators---get) - -TODO - -### [/stake/validators/{validatorAddr}](api.md#stakevalidatorsvalidatorAddr---get) - -TODO diff --git a/docs/clients/node.md b/docs/clients/node.md deleted file mode 100644 index fb3ff07b1595..000000000000 --- a/docs/clients/node.md +++ /dev/null @@ -1,45 +0,0 @@ -# Running a Node - -> TODO: Improve documentation of `gaiad` - - -## Basics - -To start a node: - -```shell -$ gaiad start -``` - -Options for running the `gaiad` binary are effectively the same as for `tendermint`. -See `gaiad --help` and the -[guide to using Tendermint](https://github.com/tendermint/tendermint/blob/master/docs/using-tendermint.md) -for more details. - -## Debugging - -Optionally, you can run `gaiad` with `--trace-store` to trace all store operations -to a specified file. - -```shell -$ gaiad start --trace-store=/path/to/trace.out -``` - -Key/value pairs will be base64 encoded. Additionally, the block number and any -correlated transaction hash will be included as metadata. - -e.g. -```json -... -{"operation":"write","key":"ATW6Bu997eeuUeRBwv1EPGvXRfPR","value":"BggEEBYgFg==","metadata":{"blockHeight":12,"txHash":"5AAC197EC45E6C5DE0798C4A4E2F54BBB695CA9E"}} -{"operation":"write","key":"AjW6Bu997eeuUeRBwv1EPGvXRfPRCgAAAAAAAAA=","value":"AQE=","metadata":{"blockHeight":12,"txHash":"5AAC197EC45E6C5DE0798C4A4E2F54BBB695CA9E"}} -{"operation":"read","key":"ATW6Bu997eeuUeRBwv1EPGvXRfPR","value":"BggEEBYgFg==","metadata":{"blockHeight":13}} -{"operation":"read","key":"AjW6Bu997eeuUeRBwv1EPGvXRfPRCwAAAAAAAAA=","value":"","metadata":{"blockHeight":13}} -... -``` - -You can then query for the various traced operations using a tool like [jq](https://github.com/stedolan/jq). - -```shell -$ jq -s '.[] | select((.key=="ATW6Bu997eeuUeRBwv1EPGvXRfPR") and .metadata.blockHeight==14)' /path/to/trace.out -``` \ No newline at end of file diff --git a/docs/clients/rest.md b/docs/clients/rest.md deleted file mode 100644 index b48ec7d8fc98..000000000000 --- a/docs/clients/rest.md +++ /dev/null @@ -1,6 +0,0 @@ -# REST - -See `gaiacli rest-server --help` for more. - -Also see the -[work in progress API specification](https://github.com/cosmos/cosmos-sdk/pull/1314) diff --git a/docs/clients/service-providers.md b/docs/clients/service-providers.md index 79fe2a667d35..358031b966f4 100644 --- a/docs/clients/service-providers.md +++ b/docs/clients/service-providers.md @@ -1,4 +1,4 @@ -# Integrate a Cosmos-SDK based blockchain as a Service Provider +# Service Providers We define 'service providers' as entities providing services for end-users that involve some form of interaction with a Cosmos-SDK based blockchain (this includes the Cosmos Hub). More specifically, this document will be focused around interactions with tokens. @@ -18,9 +18,9 @@ There are three main pieces to consider: We will describe the steps to run and interract with a full-node for the Cosmos Hub. For other SDK-based blockchain, the process should be similar. -First, you need to [install the software](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/getting-started/installation.md). +First, you need to [install the software](../getting-started/installation.md). -Then, you can start [running a full-node](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/getting-started/full-node.md). +Then, you can start [running a full-node](../getting-started/join-testnet.md). ### Command-Line interface @@ -28,7 +28,7 @@ Next you will find a few useful CLI commands to interact with the Full-Node. #### Creating a key-pair -To generate a new key (default ed25519 elliptic curve): +To generate a new key (default secp256k1 elliptic curve): ```bash gaiacli keys add @@ -62,13 +62,13 @@ gaiacli account Here is the command to send coins via the CLI: ```bash -gaiacli tx send --amount=10faucetToken --chain-id= --name= --to= +gaiacli send --amount=10faucetToken --chain-id= --from= --to= ``` Flags: - `--amount`: This flag accepts the format ``. - `--chain-id`: This flag allows you to specify the id of the chain. There will be different ids for different testnet chains and main chain. -- `--name`: Name of the key of the sending account. +- `--from`: Name of the key of the sending account. - `--to`: Address of the recipient. #### Help @@ -83,29 +83,32 @@ It will display all the available commands. For each command, you can use the `- ## Setting up the Rest Server -The Rest Server acts as an intermediary between the front-end and the full-node. You don't need to run the Rest Server on the same machine as the full-node. If you intend to run the Rest Server on another machine, you need to go through the [Installation and configuration](#installation-and-configuration) again on this machine. +The Rest Server acts as an intermediary between the front-end and the full-node. You don't need to run the Rest Server on the same machine as the full-node. To start the Rest server: ```bash -gaiacli rest-server --trust-node=false --node= +gaiacli advanced rest-server --node= ``` Flags: -- `--trust-node`: A boolean. If `true`, light-client verification is enabled. If `false`, it is disabled. For service providers, this should be set to `false`. -- `--node`: This is where you indicate the address and the port of your full-node. The format is . If the full-node is on the same machine, the address should be "tcp://localhost". -- `--laddr`: This flag allows you to specify the address and port for the Rest Server. You will mostly use this flag only to specify the port, in which case just input "localhost" for the address. The format is . +- `--trust-node`: A boolean. If `true`, light-client verification is disabled. If `false`, it is disabled. For service providers, this should be set to `true`. By default, it set to `true`. +- `--node`: This is where you indicate the address and the port of your full-node. The format is . If the full-node is on the same machine, the address should be `tcp://localhost:26657`. +- `--laddr`: This flag allows you to specify the address and port for the Rest Server (default `1317`). You will mostly use this flag only to specify the port, in which case just input "localhost" for the address. The format is . + ### Listening for incoming transaction The recommended way to listen for incoming transaction is to periodically query the blockchain through the following endpoint of the LCD: -[`/bank/balance/{account}`](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#bankbalanceaccount---get) +[`/bank/balance/{account}`](https://cosmos.network/rpc/#/ICS20/get_bank_balances__address_) ## Rest API -The Rest API documents all the available endpoints that you can use to interract with your full node. It can be found [here](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md). +The Rest API documents all the available endpoints that you can use to interract with your full node. It can be found [here](https://cosmos.network/rpc/). + +The API is divided into ICS standards for each category of endpoints. For example, the [ICS20](https://cosmos.network/rpc/#/ICS20/) describes the API to interact with tokens. -The API is divided into ICS standards for each category of endpoints. For example, the [ICS20](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#ics20---tokenapi) describes the API to interact with tokens. +To give more flexibility to implementers, we have included the ability to generate unsigned transactions, [sign](https://cosmos.network/rpc/#/ICS20/post_tx_sign) and [broadcast](https://cosmos.network/rpc/#/ICS20/post_tx_broadcast) them with different API endpoints. This allows service providers to use their own signing mechanism for instance. -To give more flexibility to implementers, we have separated the different steps that are involved in the process of sending transactions. You will be able to generate unsigned transactions (example with [coin transfer](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#post-banktransfers)), [sign](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#post-authtxsign) and [broadcast](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#post-authtxbroadcast) them with different API endpoints. This allows service providers to use their own signing mechanism for instance. +In order to generate an unsigned transaction (example with [coin transfer](https://cosmos.network/rpc/#/ICS20/post_bank_accounts__address__transfers)), you need to use the flag `?generate_only`. diff --git a/docs/graphics/cosmos-docs.jpg b/docs/cosmos-docs.jpg similarity index 100% rename from docs/graphics/cosmos-docs.jpg rename to docs/cosmos-docs.jpg diff --git a/docs/graphics/cosmos-sdk-image.png b/docs/cosmos-sdk-image.png similarity index 100% rename from docs/graphics/cosmos-sdk-image.png rename to docs/cosmos-sdk-image.png diff --git a/examples/README.md b/docs/examples/README.md similarity index 100% rename from examples/README.md rename to docs/examples/README.md diff --git a/examples/basecoin/app/app.go b/docs/examples/basecoin/app/app.go similarity index 90% rename from examples/basecoin/app/app.go rename to docs/examples/basecoin/app/app.go index 657cbcf0fdb8..9515feaadc52 100644 --- a/examples/basecoin/app/app.go +++ b/docs/examples/basecoin/app/app.go @@ -2,10 +2,11 @@ package app import ( "encoding/json" + "os" bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/examples/basecoin/types" + "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" @@ -21,6 +22,12 @@ 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 @@ -35,7 +42,7 @@ type BasecoinApp struct { keyIBC *sdk.KVStoreKey // manage getting and setting accounts - accountMapper auth.AccountMapper + accountKeeper auth.AccountKeeper feeCollectionKeeper auth.FeeCollectionKeeper bankKeeper bank.Keeper ibcMapper ibc.Mapper @@ -60,14 +67,14 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.Ba } // define and attach the mappers and keepers - app.accountMapper = auth.NewAccountMapper( + app.accountKeeper = auth.NewAccountKeeper( cdc, app.keyAccount, // target store func() auth.Account { return &types.AppAccount{} }, ) - app.bankKeeper = bank.NewBaseKeeper(app.accountMapper) + app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper) app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace)) // register message routes @@ -79,7 +86,7 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.Ba app.SetInitChainer(app.initChainer) app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) - app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) + app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper)) // mount the multistore and load the latest state app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC) @@ -146,8 +153,8 @@ func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) panic(err) } - acc.AccountNumber = app.accountMapper.GetNextAccountNumber(ctx) - app.accountMapper.SetAccount(ctx, acc) + acc.AccountNumber = app.accountKeeper.GetNextAccountNumber(ctx) + app.accountKeeper.SetAccount(ctx, acc) } return abci.ResponseInitChain{} @@ -170,7 +177,7 @@ func (app *BasecoinApp) ExportAppStateAndValidators() (appState json.RawMessage, return false } - app.accountMapper.IterateAccounts(ctx, appendAccountsFn) + app.accountKeeper.IterateAccounts(ctx, appendAccountsFn) genState := types.GenesisState{Accounts: accounts} appState, err = codec.MarshalJSONIndent(app.cdc, genState) diff --git a/examples/basecoin/app/app_test.go b/docs/examples/basecoin/app/app_test.go similarity index 92% rename from examples/basecoin/app/app_test.go rename to docs/examples/basecoin/app/app_test.go index da544f4803cd..64bc6a86e778 100644 --- a/examples/basecoin/app/app_test.go +++ b/docs/examples/basecoin/app/app_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/examples/basecoin/types" + "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/stretchr/testify/require" @@ -61,7 +61,7 @@ func TestGenesis(t *testing.T) { // create a context for the BaseApp ctx := baseApp.BaseApp.NewContext(true, abci.Header{}) - res := baseApp.accountMapper.GetAccount(ctx, baseAcct.Address) + res := baseApp.accountKeeper.GetAccount(ctx, baseAcct.Address) require.Equal(t, appAcct, res) // reload app and ensure the account is still there @@ -76,6 +76,6 @@ func TestGenesis(t *testing.T) { }) ctx = baseApp.BaseApp.NewContext(true, abci.Header{}) - res = baseApp.accountMapper.GetAccount(ctx, baseAcct.Address) + res = baseApp.accountKeeper.GetAccount(ctx, baseAcct.Address) require.Equal(t, appAcct, res) } diff --git a/examples/basecoin/cli_test/cli_test.go b/docs/examples/basecoin/cli_test/cli_test.go similarity index 67% rename from examples/basecoin/cli_test/cli_test.go rename to docs/examples/basecoin/cli_test/cli_test.go index 3a33135e399e..635b54c3c015 100644 --- a/examples/basecoin/cli_test/cli_test.go +++ b/docs/examples/basecoin/cli_test/cli_test.go @@ -6,6 +6,7 @@ import ( "os" "testing" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/tests" "github.com/stretchr/testify/require" @@ -13,10 +14,11 @@ import ( var ( basecoindHome = "" + basecliHome = "" ) func init() { - basecoindHome = getTestingHomeDir() + basecoindHome, basecliHome = getTestingHomeDirs() } func TestInitStartSequence(t *testing.T) { @@ -32,8 +34,8 @@ func executeInit(t *testing.T) { chainID string initRes map[string]json.RawMessage ) - out := tests.ExecuteT(t, fmt.Sprintf("basecoind --home=%s init", basecoindHome), "") - err := json.Unmarshal([]byte(out), &initRes) + _, 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) @@ -45,8 +47,9 @@ func executeStart(t *testing.T, servAddr, port string) { tests.WaitForTMStart(port) } -func getTestingHomeDir() string { +func getTestingHomeDirs() (string, string) { tmpDir := os.TempDir() basecoindHome := fmt.Sprintf("%s%s.test_basecoind", tmpDir, string(os.PathSeparator)) - return basecoindHome + basecliHome := fmt.Sprintf("%s%s.test_basecli", tmpDir, string(os.PathSeparator)) + return basecoindHome, basecliHome } diff --git a/examples/basecoin/cmd/basecli/main.go b/docs/examples/basecoin/cmd/basecli/main.go similarity index 84% rename from examples/basecoin/cmd/basecli/main.go rename to docs/examples/basecoin/cmd/basecli/main.go index cbfae5fe0cb9..3a16c8a97bbd 100644 --- a/examples/basecoin/cmd/basecli/main.go +++ b/docs/examples/basecoin/cmd/basecli/main.go @@ -1,16 +1,14 @@ package main import ( - "os" - "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/examples/basecoin/app" - "github.com/cosmos/cosmos-sdk/examples/basecoin/types" + "github.com/cosmos/cosmos-sdk/docs/examples/basecoin/app" + "github.com/cosmos/cosmos-sdk/docs/examples/basecoin/types" "github.com/cosmos/cosmos-sdk/version" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" @@ -41,16 +39,22 @@ func main() { // with the cdc. // add standard rpc, and tx commands - rpc.AddCommands(rootCmd) - rootCmd.AddCommand(client.LineBreak) - tx.AddCommands(rootCmd, cdc) - rootCmd.AddCommand(client.LineBreak) + 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( client.GetCommands( stakecmd.GetCmdQueryValidator("stake", cdc), stakecmd.GetCmdQueryValidators("stake", cdc), + stakecmd.GetCmdQueryValidatorUnbondingDelegations("stake", cdc), + stakecmd.GetCmdQueryValidatorRedelegations("stake", cdc), stakecmd.GetCmdQueryDelegation("stake", cdc), stakecmd.GetCmdQueryDelegations("stake", cdc), stakecmd.GetCmdQueryPool("stake", cdc), @@ -86,7 +90,7 @@ func main() { ) // prepare and add flags - executor := cli.PrepareMainCmd(rootCmd, "BC", os.ExpandEnv("$HOME/.basecli")) + executor := cli.PrepareMainCmd(rootCmd, "BC", app.DefaultCLIHome) err := executor.Execute() if err != nil { // Note: Handle with #870 diff --git a/docs/examples/basecoin/cmd/basecoind/main.go b/docs/examples/basecoin/cmd/basecoind/main.go new file mode 100644 index 000000000000..0e265d1897d4 --- /dev/null +++ b/docs/examples/basecoin/cmd/basecoind/main.go @@ -0,0 +1,133 @@ +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/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" + "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" +) + +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), + } + + appInit := server.DefaultAppInit + rootCmd.AddCommand(InitCmd(ctx, cdc, appInit)) + + server.AddCommands(ctx, cdc, rootCmd, appInit, + 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, appInit server.AppInit) *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 := appInit.AppGenState( + 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) ( + json.RawMessage, []tmtypes.GenesisValidator, error) { + bapp := app.NewBasecoinApp(logger, db) + return bapp.ExportAppStateAndValidators() +} diff --git a/examples/basecoin/types/account.go b/docs/examples/basecoin/types/account.go similarity index 97% rename from examples/basecoin/types/account.go rename to docs/examples/basecoin/types/account.go index 04d3e371efdc..41b437718041 100644 --- a/examples/basecoin/types/account.go +++ b/docs/examples/basecoin/types/account.go @@ -10,7 +10,7 @@ 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.AccountMapper, since auth.AccountMapper uses the flexible go-amino +// stock auth.AccountKeeper, since auth.AccountKeeper uses the flexible go-amino // library. type AppAccount struct { auth.BaseAccount diff --git a/examples/democoin/app/app.go b/docs/examples/democoin/app/app.go similarity index 85% rename from examples/democoin/app/app.go rename to docs/examples/democoin/app/app.go index 0449f6251edb..bc618e3606ae 100644 --- a/examples/democoin/app/app.go +++ b/docs/examples/democoin/app/app.go @@ -2,6 +2,7 @@ package app import ( "encoding/json" + "os" abci "github.com/tendermint/tendermint/abci/types" cmn "github.com/tendermint/tendermint/libs/common" @@ -16,17 +17,23 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/ibc" - "github.com/cosmos/cosmos-sdk/examples/democoin/types" - "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool" - "github.com/cosmos/cosmos-sdk/examples/democoin/x/pow" - "github.com/cosmos/cosmos-sdk/examples/democoin/x/simplestake" - "github.com/cosmos/cosmos-sdk/examples/democoin/x/sketchy" + "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 @@ -48,7 +55,7 @@ type DemocoinApp struct { stakeKeeper simplestake.Keeper // Manage getting and setting accounts - accountMapper auth.AccountMapper + accountKeeper auth.AccountKeeper } func NewDemocoinApp(logger log.Logger, db dbm.DB) *DemocoinApp { @@ -67,15 +74,15 @@ func NewDemocoinApp(logger log.Logger, db dbm.DB) *DemocoinApp { capKeyStakingStore: sdk.NewKVStoreKey("stake"), } - // Define the accountMapper. - app.accountMapper = auth.NewAccountMapper( + // Define the accountKeeper. + app.accountKeeper = auth.NewAccountKeeper( cdc, app.capKeyAccountStore, // target store types.ProtoAppAccount, // prototype ) // Add handlers. - app.bankKeeper = bank.NewBaseKeeper(app.accountMapper) + app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper) app.coolKeeper = cool.NewKeeper(app.capKeyMainStore, app.bankKeeper, app.RegisterCodespace(cool.DefaultCodespace)) app.powKeeper = pow.NewKeeper(app.capKeyPowStore, pow.NewConfig("pow", int64(1)), app.bankKeeper, app.RegisterCodespace(pow.DefaultCodespace)) app.ibcMapper = ibc.NewMapper(app.cdc, app.capKeyIBCStore, app.RegisterCodespace(ibc.DefaultCodespace)) @@ -91,7 +98,7 @@ func NewDemocoinApp(logger log.Logger, db dbm.DB) *DemocoinApp { // Initialize BaseApp. app.SetInitChainer(app.initChainerFn(app.coolKeeper, app.powKeeper)) app.MountStoresIAVL(app.capKeyMainStore, app.capKeyAccountStore, app.capKeyPowStore, app.capKeyIBCStore, app.capKeyStakingStore) - app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) + app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper)) err := app.LoadLatestVersion(app.capKeyMainStore) if err != nil { cmn.Exit(err.Error()) @@ -141,7 +148,7 @@ func (app *DemocoinApp) initChainerFn(coolKeeper cool.Keeper, powKeeper pow.Keep panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 // return sdk.ErrGenesisParse("").TraceCause(err, "") } - app.accountMapper.SetAccount(ctx, acc) + app.accountKeeper.SetAccount(ctx, acc) } // Application specific genesis handling @@ -175,12 +182,12 @@ func (app *DemocoinApp) ExportAppStateAndValidators() (appState json.RawMessage, accounts = append(accounts, account) return false } - app.accountMapper.IterateAccounts(ctx, appendAccount) + app.accountKeeper.IterateAccounts(ctx, appendAccount) genState := types.GenesisState{ Accounts: accounts, - POWGenesis: pow.WriteGenesis(ctx, app.powKeeper), - CoolGenesis: cool.WriteGenesis(ctx, app.coolKeeper), + POWGenesis: pow.ExportGenesis(ctx, app.powKeeper), + CoolGenesis: cool.ExportGenesis(ctx, app.coolKeeper), } appState, err = codec.MarshalJSONIndent(app.cdc, genState) if err != nil { diff --git a/examples/democoin/app/app_test.go b/docs/examples/democoin/app/app_test.go similarity index 88% rename from examples/democoin/app/app_test.go rename to docs/examples/democoin/app/app_test.go index fc00651b8f2e..93cef936c7fc 100644 --- a/examples/democoin/app/app_test.go +++ b/docs/examples/democoin/app/app_test.go @@ -5,8 +5,8 @@ import ( "testing" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/examples/democoin/types" - "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool" + "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" "github.com/stretchr/testify/require" @@ -60,13 +60,13 @@ func TestGenesis(t *testing.T) { require.Nil(t, err) // A checkTx context ctx := bapp.BaseApp.NewContext(true, abci.Header{}) - res1 := bapp.accountMapper.GetAccount(ctx, baseAcc.Address) + 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.accountMapper.GetAccount(ctx, baseAcc.Address) + res1 = bapp.accountKeeper.GetAccount(ctx, baseAcc.Address) require.Equal(t, acc, res1) } diff --git a/examples/democoin/cli_test/cli_test.go b/docs/examples/democoin/cli_test/cli_test.go similarity index 67% rename from examples/democoin/cli_test/cli_test.go rename to docs/examples/democoin/cli_test/cli_test.go index 2db2fff09641..9a3cba0df7e5 100644 --- a/examples/democoin/cli_test/cli_test.go +++ b/docs/examples/democoin/cli_test/cli_test.go @@ -6,6 +6,8 @@ import ( "os" "testing" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/tests" "github.com/stretchr/testify/require" @@ -13,10 +15,11 @@ import ( var ( democoindHome = "" + democliHome = "" ) func init() { - democoindHome = getTestingHomeDir() + democoindHome, democliHome = getTestingHomeDirs() } func TestInitStartSequence(t *testing.T) { @@ -32,8 +35,8 @@ func executeInit(t *testing.T) { chainID string initRes map[string]json.RawMessage ) - out := tests.ExecuteT(t, fmt.Sprintf("democoind --home=%s init", democoindHome), "") - err := json.Unmarshal([]byte(out), &initRes) + _, 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) @@ -45,8 +48,9 @@ func executeStart(t *testing.T, servAddr, port string) { tests.WaitForTMStart(port) } -func getTestingHomeDir() string { +func getTestingHomeDirs() (string, string) { tmpDir := os.TempDir() democoindHome := fmt.Sprintf("%s%s.test_democoind", tmpDir, string(os.PathSeparator)) - return democoindHome + democliHome := fmt.Sprintf("%s%s.test_democli", tmpDir, string(os.PathSeparator)) + return democoindHome, democliHome } diff --git a/examples/democoin/cmd/democli/main.go b/docs/examples/democoin/cmd/democli/main.go similarity index 68% rename from examples/democoin/cmd/democli/main.go rename to docs/examples/democoin/cmd/democli/main.go index 43f86504eb27..adb6169c9ee5 100644 --- a/examples/democoin/cmd/democli/main.go +++ b/docs/examples/democoin/cmd/democli/main.go @@ -1,8 +1,6 @@ package main import ( - "os" - "github.com/spf13/cobra" "github.com/tendermint/tendermint/libs/cli" @@ -18,11 +16,13 @@ import ( bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/client/cli" - "github.com/cosmos/cosmos-sdk/examples/democoin/app" - "github.com/cosmos/cosmos-sdk/examples/democoin/types" - coolcmd "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool/client/cli" - powcmd "github.com/cosmos/cosmos-sdk/examples/democoin/x/pow/client/cli" - simplestakingcmd "github.com/cosmos/cosmos-sdk/examples/democoin/x/simplestake/client/cli" + "github.com/cosmos/cosmos-sdk/docs/examples/democoin/app" + "github.com/cosmos/cosmos-sdk/docs/examples/democoin/types" + 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 @@ -40,15 +40,27 @@ func main() { // 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 - rpc.AddCommands(rootCmd) - rootCmd.AddCommand(client.LineBreak) - tx.AddCommands(rootCmd, cdc) - rootCmd.AddCommand(client.LineBreak) + + 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 @@ -91,7 +103,7 @@ func main() { ) // prepare and add flags - executor := cli.PrepareMainCmd(rootCmd, "BC", os.ExpandEnv("$HOME/.democli")) + executor := cli.PrepareMainCmd(rootCmd, "BC", app.DefaultCLIHome) err := executor.Execute() if err != nil { // handle with #870 diff --git a/docs/examples/democoin/cmd/democoind/main.go b/docs/examples/democoin/cmd/democoind/main.go new file mode 100644 index 000000000000..36ea11cc31b2 --- /dev/null +++ b/docs/examples/democoin/cmd/democoind/main.go @@ -0,0 +1,174 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/spf13/viper" + "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/p2p" + + "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" +) + +// init parameters +var CoolAppInit = server.AppInit{ + AppGenState: CoolAppGenState, +} + +// 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, appInit server.AppInit) *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 := appInit.AppGenState(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) ( + 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, CoolAppInit)) + rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, CoolAppInit)) + + server.AddCommands(ctx, cdc, rootCmd, CoolAppInit, + 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/examples/democoin/mock/validator.go b/docs/examples/democoin/mock/validator.go similarity index 87% rename from examples/democoin/mock/validator.go rename to docs/examples/democoin/mock/validator.go index a54cdfedf6bd..1d10c48b2459 100644 --- a/examples/democoin/mock/validator.go +++ b/docs/examples/democoin/mock/validator.go @@ -48,6 +48,11 @@ 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 @@ -77,8 +82,13 @@ func (vs *ValidatorSet) IterateValidators(ctx sdk.Context, fn func(index int64, } } -// IterateValidatorsBonded implements sdk.ValidatorSet -func (vs *ValidatorSet) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) { +// 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) } diff --git a/examples/democoin/types/account.go b/docs/examples/democoin/types/account.go similarity index 94% rename from examples/democoin/types/account.go rename to docs/examples/democoin/types/account.go index ad57c944d385..341f6cfc834c 100644 --- a/examples/democoin/types/account.go +++ b/docs/examples/democoin/types/account.go @@ -5,8 +5,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool" - "github.com/cosmos/cosmos-sdk/examples/democoin/x/pow" + "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) diff --git a/examples/democoin/x/assoc/validator_set.go b/docs/examples/democoin/x/assoc/validator_set.go similarity index 100% rename from examples/democoin/x/assoc/validator_set.go rename to docs/examples/democoin/x/assoc/validator_set.go diff --git a/examples/democoin/x/assoc/validator_set_test.go b/docs/examples/democoin/x/assoc/validator_set_test.go similarity index 97% rename from examples/democoin/x/assoc/validator_set_test.go rename to docs/examples/democoin/x/assoc/validator_set_test.go index 9fc6526f8d3e..66f03c840324 100644 --- a/examples/democoin/x/assoc/validator_set_test.go +++ b/docs/examples/democoin/x/assoc/validator_set_test.go @@ -10,7 +10,7 @@ import ( dbm "github.com/tendermint/tendermint/libs/db" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/examples/democoin/mock" + "github.com/cosmos/cosmos-sdk/docs/examples/democoin/mock" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" ) diff --git a/examples/democoin/x/cool/app_test.go b/docs/examples/democoin/x/cool/app_test.go similarity index 96% rename from examples/democoin/x/cool/app_test.go rename to docs/examples/democoin/x/cool/app_test.go index 35a656cebc8f..01cb73ef1738 100644 --- a/examples/democoin/x/cool/app_test.go +++ b/docs/examples/democoin/x/cool/app_test.go @@ -49,7 +49,7 @@ func getMockApp(t *testing.T) *mock.App { RegisterCodec(mapp.Cdc) keyCool := sdk.NewKVStoreKey("cool") - bankKeeper := bank.NewBaseKeeper(mapp.AccountMapper) + bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper) keeper := NewKeeper(keyCool, bankKeeper, mapp.RegisterCodespace(DefaultCodespace)) mapp.Router().AddRoute("cool", NewHandler(keeper)) @@ -84,7 +84,7 @@ func TestMsgQuiz(t *testing.T) { // A checkTx context (true) ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) - res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) + res1 := mapp.AccountKeeper.GetAccount(ctxCheck, addr1) require.Equal(t, acc1, res1) // Set the trend, submit a really cool quiz and check for reward diff --git a/examples/democoin/x/cool/client/cli/tx.go b/docs/examples/democoin/x/cool/client/cli/tx.go similarity index 96% rename from examples/democoin/x/cool/client/cli/tx.go rename to docs/examples/democoin/x/cool/client/cli/tx.go index c30743f03d9e..a21685a249f3 100644 --- a/examples/democoin/x/cool/client/cli/tx.go +++ b/docs/examples/democoin/x/cool/client/cli/tx.go @@ -6,7 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool" + "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/cool" sdk "github.com/cosmos/cosmos-sdk/types" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" diff --git a/examples/democoin/x/cool/codec.go b/docs/examples/democoin/x/cool/codec.go similarity index 100% rename from examples/democoin/x/cool/codec.go rename to docs/examples/democoin/x/cool/codec.go diff --git a/examples/democoin/x/cool/errors.go b/docs/examples/democoin/x/cool/errors.go similarity index 100% rename from examples/democoin/x/cool/errors.go rename to docs/examples/democoin/x/cool/errors.go diff --git a/examples/democoin/x/cool/handler.go b/docs/examples/democoin/x/cool/handler.go similarity index 95% rename from examples/democoin/x/cool/handler.go rename to docs/examples/democoin/x/cool/handler.go index a4c6ce7be6af..98da59690adb 100644 --- a/examples/democoin/x/cool/handler.go +++ b/docs/examples/democoin/x/cool/handler.go @@ -25,7 +25,7 @@ func NewHandler(k Keeper) sdk.Handler { case MsgQuiz: return handleMsgQuiz(ctx, k, msg) default: - errMsg := fmt.Sprintf("Unrecognized cool Msg type: %v", msg.Name()) + errMsg := fmt.Sprintf("Unrecognized cool Msg type: %v", msg.Type()) return sdk.ErrUnknownRequest(errMsg).Result() } } diff --git a/examples/democoin/x/cool/keeper.go b/docs/examples/democoin/x/cool/keeper.go similarity index 90% rename from examples/democoin/x/cool/keeper.go rename to docs/examples/democoin/x/cool/keeper.go index fef93b954d12..9f46b0209e8d 100644 --- a/examples/democoin/x/cool/keeper.go +++ b/docs/examples/democoin/x/cool/keeper.go @@ -29,7 +29,7 @@ func (k Keeper) GetTrend(ctx sdk.Context) string { return string(bz) } -// Implements sdk.AccountMapper. +// Implements sdk.AccountKeeper. func (k Keeper) setTrend(ctx sdk.Context, newTrend string) { store := ctx.KVStore(k.storeKey) store.Set(trendKey, []byte(newTrend)) @@ -49,8 +49,8 @@ func InitGenesis(ctx sdk.Context, k Keeper, data Genesis) error { return nil } -// WriteGenesis - output the genesis trend -func WriteGenesis(ctx sdk.Context, k Keeper) Genesis { +// ExportGenesis - output the genesis trend +func ExportGenesis(ctx sdk.Context, k Keeper) Genesis { trend := k.GetTrend(ctx) return Genesis{trend} } diff --git a/examples/democoin/x/cool/keeper_test.go b/docs/examples/democoin/x/cool/keeper_test.go similarity index 92% rename from examples/democoin/x/cool/keeper_test.go rename to docs/examples/democoin/x/cool/keeper_test.go index e3af7790e4b2..904681382c2a 100644 --- a/examples/democoin/x/cool/keeper_test.go +++ b/docs/examples/democoin/x/cool/keeper_test.go @@ -29,7 +29,7 @@ func TestCoolKeeper(t *testing.T) { cdc := codec.New() auth.RegisterBaseAccount(cdc) - am := auth.NewAccountMapper(cdc, capKey, auth.ProtoBaseAccount) + am := auth.NewAccountKeeper(cdc, capKey, auth.ProtoBaseAccount) ctx := sdk.NewContext(ms, abci.Header{}, false, nil) ck := bank.NewBaseKeeper(am) keeper := NewKeeper(capKey, ck, DefaultCodespace) @@ -37,7 +37,7 @@ func TestCoolKeeper(t *testing.T) { err := InitGenesis(ctx, keeper, Genesis{"icy"}) require.Nil(t, err) - genesis := WriteGenesis(ctx, keeper) + genesis := ExportGenesis(ctx, keeper) require.Nil(t, err) require.Equal(t, genesis, Genesis{"icy"}) diff --git a/examples/democoin/x/cool/types.go b/docs/examples/democoin/x/cool/types.go similarity index 91% rename from examples/democoin/x/cool/types.go rename to docs/examples/democoin/x/cool/types.go index f04811b14a90..11d3dde6bc7c 100644 --- a/examples/democoin/x/cool/types.go +++ b/docs/examples/democoin/x/cool/types.go @@ -32,8 +32,8 @@ func NewMsgSetTrend(sender sdk.AccAddress, cool string) MsgSetTrend { var _ sdk.Msg = MsgSetTrend{} // nolint -func (msg MsgSetTrend) Type() string { return "cool" } -func (msg MsgSetTrend) Name() string { return "set_trend" } +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) @@ -83,8 +83,8 @@ func NewMsgQuiz(sender sdk.AccAddress, coolerthancool string) MsgQuiz { var _ sdk.Msg = MsgQuiz{} // nolint -func (msg MsgQuiz) Type() string { return "cool" } -func (msg MsgQuiz) Name() string { return "quiz" } +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) diff --git a/examples/democoin/x/oracle/README.md b/docs/examples/democoin/x/oracle/README.md similarity index 89% rename from examples/democoin/x/oracle/README.md rename to docs/examples/democoin/x/oracle/README.md index b840dc0e8b6c..605e5c5bd408 100644 --- a/examples/democoin/x/oracle/README.md +++ b/docs/examples/democoin/x/oracle/README.md @@ -15,7 +15,7 @@ type MyPayload struct { } ``` -When you write a payload, its `.Type()` should return same name with your module is registered on the router. It is because `oracle.Msg` inherits `.Type()` from its embedded payload and it should be handled on the user modules. +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`. diff --git a/examples/democoin/x/oracle/errors.go b/docs/examples/democoin/x/oracle/errors.go similarity index 100% rename from examples/democoin/x/oracle/errors.go rename to docs/examples/democoin/x/oracle/errors.go diff --git a/examples/democoin/x/oracle/handler.go b/docs/examples/democoin/x/oracle/handler.go similarity index 100% rename from examples/democoin/x/oracle/handler.go rename to docs/examples/democoin/x/oracle/handler.go diff --git a/examples/democoin/x/oracle/keeper.go b/docs/examples/democoin/x/oracle/keeper.go similarity index 95% rename from examples/democoin/x/oracle/keeper.go rename to docs/examples/democoin/x/oracle/keeper.go index e55cd7083c0d..d061d2f8ed4f 100644 --- a/examples/democoin/x/oracle/keeper.go +++ b/docs/examples/democoin/x/oracle/keeper.go @@ -71,7 +71,7 @@ func (keeper Keeper) Info(ctx sdk.Context, p Payload) (res Info) { if bz == nil { return EmptyInfo(ctx) } - keeper.cdc.MustUnmarshalBinary(bz, &res) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &res) return } @@ -80,7 +80,7 @@ func (keeper Keeper) setInfo(ctx sdk.Context, p Payload, info Info) { store := ctx.KVStore(keeper.key) key := GetInfoKey(p, keeper.cdc) - bz := keeper.cdc.MustMarshalBinary(info) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(info) store.Set(key, bz) } diff --git a/examples/democoin/x/oracle/keeper_keys.go b/docs/examples/democoin/x/oracle/keeper_keys.go similarity index 85% rename from examples/democoin/x/oracle/keeper_keys.go rename to docs/examples/democoin/x/oracle/keeper_keys.go index 9b71aeaa1815..d678692be9fa 100644 --- a/examples/democoin/x/oracle/keeper_keys.go +++ b/docs/examples/democoin/x/oracle/keeper_keys.go @@ -7,13 +7,13 @@ import ( // GetInfoKey returns the key for OracleInfo func GetInfoKey(p Payload, cdc *codec.Codec) []byte { - bz := cdc.MustMarshalBinary(p) + 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.MustMarshalBinary(p) + bz := cdc.MustMarshalBinaryLengthPrefixed(p) return append([]byte{0x01}, bz...) } diff --git a/examples/democoin/x/oracle/oracle_test.go b/docs/examples/democoin/x/oracle/oracle_test.go similarity index 96% rename from examples/democoin/x/oracle/oracle_test.go rename to docs/examples/democoin/x/oracle/oracle_test.go index 0b921e9d95c9..b8b613e318d3 100644 --- a/examples/democoin/x/oracle/oracle_test.go +++ b/docs/examples/democoin/x/oracle/oracle_test.go @@ -10,7 +10,7 @@ import ( dbm "github.com/tendermint/tendermint/libs/db" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/examples/democoin/mock" + "github.com/cosmos/cosmos-sdk/docs/examples/democoin/mock" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -31,10 +31,10 @@ type seqOracle struct { Nonce int } -func (o seqOracle) Type() string { +func (o seqOracle) Route() string { return "seq" } -func (o seqOracle) Name() string { +func (o seqOracle) Type() string { return "seq" } @@ -82,7 +82,7 @@ func getSequence(ctx sdk.Context, key sdk.StoreKey) int { if seqbz == nil { seq = 0 } else { - codec.New().MustUnmarshalBinary(seqbz, &seq) + codec.New().MustUnmarshalBinaryLengthPrefixed(seqbz, &seq) } return seq @@ -96,7 +96,7 @@ func handleSeqOracle(ctx sdk.Context, key sdk.StoreKey, o seqOracle) sdk.Error { return sdk.NewError(sdk.CodespaceRoot, 1, "") } - bz := codec.New().MustMarshalBinary(seq + 1) + bz := codec.New().MustMarshalBinaryLengthPrefixed(seq + 1) store.Set([]byte("seq"), bz) return nil diff --git a/examples/democoin/x/oracle/types.go b/docs/examples/democoin/x/oracle/types.go similarity index 97% rename from examples/democoin/x/oracle/types.go rename to docs/examples/democoin/x/oracle/types.go index 5e47597b80a8..06590e3a0c62 100644 --- a/examples/democoin/x/oracle/types.go +++ b/docs/examples/democoin/x/oracle/types.go @@ -28,7 +28,7 @@ func (msg Msg) GetSigners() []sdk.AccAddress { // Payload defines inner data for actual execution type Payload interface { + Route() string Type() string - Name() string ValidateBasic() sdk.Error } diff --git a/examples/democoin/x/pow/app_test.go b/docs/examples/democoin/x/pow/app_test.go similarity index 95% rename from examples/democoin/x/pow/app_test.go rename to docs/examples/democoin/x/pow/app_test.go index 6e6f07f77098..5009b7ec5460 100644 --- a/examples/democoin/x/pow/app_test.go +++ b/docs/examples/democoin/x/pow/app_test.go @@ -25,7 +25,7 @@ func getMockApp(t *testing.T) *mock.App { RegisterCodec(mapp.Cdc) keyPOW := sdk.NewKVStoreKey("pow") - bankKeeper := bank.NewBaseKeeper(mapp.AccountMapper) + bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper) config := Config{"pow", 1} keeper := NewKeeper(keyPOW, config, bankKeeper, mapp.RegisterCodespace(DefaultCodespace)) mapp.Router().AddRoute("pow", keeper.Handler) @@ -69,7 +69,7 @@ func TestMsgMine(t *testing.T) { // A checkTx context (true) ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) - res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) + res1 := mapp.AccountKeeper.GetAccount(ctxCheck, addr1) require.Equal(t, acc1, res1) // Mine and check for reward diff --git a/examples/democoin/x/pow/client/cli/tx.go b/docs/examples/democoin/x/pow/client/cli/tx.go similarity index 95% rename from examples/democoin/x/pow/client/cli/tx.go rename to docs/examples/democoin/x/pow/client/cli/tx.go index 22de7e09855c..548aa9910746 100644 --- a/examples/democoin/x/pow/client/cli/tx.go +++ b/docs/examples/democoin/x/pow/client/cli/tx.go @@ -6,7 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/examples/democoin/x/pow" + "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/pow" sdk "github.com/cosmos/cosmos-sdk/types" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" diff --git a/examples/democoin/x/pow/codec.go b/docs/examples/democoin/x/pow/codec.go similarity index 100% rename from examples/democoin/x/pow/codec.go rename to docs/examples/democoin/x/pow/codec.go diff --git a/examples/democoin/x/pow/errors.go b/docs/examples/democoin/x/pow/errors.go similarity index 100% rename from examples/democoin/x/pow/errors.go rename to docs/examples/democoin/x/pow/errors.go diff --git a/examples/democoin/x/pow/handler.go b/docs/examples/democoin/x/pow/handler.go similarity index 92% rename from examples/democoin/x/pow/handler.go rename to docs/examples/democoin/x/pow/handler.go index 246246e96ec1..5aa4cbab5e01 100644 --- a/examples/democoin/x/pow/handler.go +++ b/docs/examples/democoin/x/pow/handler.go @@ -10,7 +10,7 @@ func (pk Keeper) Handler(ctx sdk.Context, msg sdk.Msg) sdk.Result { case MsgMine: return handleMsgMine(ctx, pk, msg) default: - errMsg := "Unrecognized pow Msg type: " + msg.Name() + errMsg := "Unrecognized pow Msg type: " + msg.Type() return sdk.ErrUnknownRequest(errMsg).Result() } } diff --git a/examples/democoin/x/pow/handler_test.go b/docs/examples/democoin/x/pow/handler_test.go similarity index 96% rename from examples/democoin/x/pow/handler_test.go rename to docs/examples/democoin/x/pow/handler_test.go index 8166ddfc5321..ce398b7c2869 100644 --- a/examples/democoin/x/pow/handler_test.go +++ b/docs/examples/democoin/x/pow/handler_test.go @@ -19,7 +19,7 @@ func TestPowHandler(t *testing.T) { cdc := codec.New() auth.RegisterBaseAccount(cdc) - am := auth.NewAccountMapper(cdc, capKey, auth.ProtoBaseAccount) + 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) diff --git a/examples/democoin/x/pow/keeper.go b/docs/examples/democoin/x/pow/keeper.go similarity index 97% rename from examples/democoin/x/pow/keeper.go rename to docs/examples/democoin/x/pow/keeper.go index 38a0d93c6a0a..6c3bfc4eb866 100644 --- a/examples/democoin/x/pow/keeper.go +++ b/docs/examples/democoin/x/pow/keeper.go @@ -43,8 +43,8 @@ func InitGenesis(ctx sdk.Context, k Keeper, genesis Genesis) error { return nil } -// WriteGenesis for the PoW module -func WriteGenesis(ctx sdk.Context, k Keeper) Genesis { +// ExportGenesis for the PoW module +func ExportGenesis(ctx sdk.Context, k Keeper) Genesis { difficulty, err := k.GetLastDifficulty(ctx) if err != nil { panic(err) diff --git a/examples/democoin/x/pow/keeper_test.go b/docs/examples/democoin/x/pow/keeper_test.go similarity index 93% rename from examples/democoin/x/pow/keeper_test.go rename to docs/examples/democoin/x/pow/keeper_test.go index dbd974c4d076..c8d5406f9cfb 100644 --- a/examples/democoin/x/pow/keeper_test.go +++ b/docs/examples/democoin/x/pow/keeper_test.go @@ -32,7 +32,7 @@ func TestPowKeeperGetSet(t *testing.T) { cdc := codec.New() auth.RegisterBaseAccount(cdc) - am := auth.NewAccountMapper(cdc, capKey, auth.ProtoBaseAccount) + 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) @@ -41,7 +41,7 @@ func TestPowKeeperGetSet(t *testing.T) { err := InitGenesis(ctx, keeper, Genesis{uint64(1), uint64(0)}) require.Nil(t, err) - genesis := WriteGenesis(ctx, keeper) + genesis := ExportGenesis(ctx, keeper) require.Nil(t, err) require.Equal(t, genesis, Genesis{uint64(1), uint64(0)}) diff --git a/examples/democoin/x/pow/mine.go b/docs/examples/democoin/x/pow/mine.go similarity index 100% rename from examples/democoin/x/pow/mine.go rename to docs/examples/democoin/x/pow/mine.go diff --git a/examples/democoin/x/pow/types.go b/docs/examples/democoin/x/pow/types.go similarity index 94% rename from examples/democoin/x/pow/types.go rename to docs/examples/democoin/x/pow/types.go index 4f808cbedcec..80bad3a584d1 100644 --- a/examples/democoin/x/pow/types.go +++ b/docs/examples/democoin/x/pow/types.go @@ -31,8 +31,8 @@ func NewMsgMine(sender sdk.AccAddress, difficulty uint64, count uint64, nonce ui } // nolint -func (msg MsgMine) Type() string { return "pow" } -func (msg MsgMine) Name() string { return "mine" } +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) diff --git a/examples/democoin/x/pow/types_test.go b/docs/examples/democoin/x/pow/types_test.go similarity index 98% rename from examples/democoin/x/pow/types_test.go rename to docs/examples/democoin/x/pow/types_test.go index dbe6ad35e953..44f79899dd9f 100644 --- a/examples/democoin/x/pow/types_test.go +++ b/docs/examples/democoin/x/pow/types_test.go @@ -19,7 +19,7 @@ func TestNewMsgMine(t *testing.T) { func TestMsgMineType(t *testing.T) { addr := sdk.AccAddress([]byte("sender")) msg := MsgMine{addr, 0, 0, 0, []byte("")} - require.Equal(t, msg.Type(), "pow") + require.Equal(t, msg.Route(), "pow") } func TestMsgMineValidation(t *testing.T) { diff --git a/examples/democoin/x/simplestake/client/cli/commands.go b/docs/examples/democoin/x/simplestake/client/cli/commands.go similarity index 97% rename from examples/democoin/x/simplestake/client/cli/commands.go rename to docs/examples/democoin/x/simplestake/client/cli/commands.go index b5c0730efa1d..3fe9c20c4ac7 100644 --- a/examples/democoin/x/simplestake/client/cli/commands.go +++ b/docs/examples/democoin/x/simplestake/client/cli/commands.go @@ -7,7 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/examples/democoin/x/simplestake" + "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/simplestake" sdk "github.com/cosmos/cosmos-sdk/types" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" diff --git a/examples/democoin/x/simplestake/codec.go b/docs/examples/democoin/x/simplestake/codec.go similarity index 100% rename from examples/democoin/x/simplestake/codec.go rename to docs/examples/democoin/x/simplestake/codec.go diff --git a/examples/democoin/x/simplestake/errors.go b/docs/examples/democoin/x/simplestake/errors.go similarity index 100% rename from examples/democoin/x/simplestake/errors.go rename to docs/examples/democoin/x/simplestake/errors.go diff --git a/examples/democoin/x/simplestake/handler.go b/docs/examples/democoin/x/simplestake/handler.go similarity index 100% rename from examples/democoin/x/simplestake/handler.go rename to docs/examples/democoin/x/simplestake/handler.go diff --git a/examples/democoin/x/simplestake/keeper.go b/docs/examples/democoin/x/simplestake/keeper.go similarity index 95% rename from examples/democoin/x/simplestake/keeper.go rename to docs/examples/democoin/x/simplestake/keeper.go index 7bdc17937b55..b757dd66de4c 100644 --- a/examples/democoin/x/simplestake/keeper.go +++ b/docs/examples/democoin/x/simplestake/keeper.go @@ -8,7 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" ) -const stakingToken = "steak" +const stakingToken = "stake" const moduleName = "simplestake" @@ -39,7 +39,7 @@ func (k Keeper) getBondInfo(ctx sdk.Context, addr sdk.AccAddress) bondInfo { return bondInfo{} } var bi bondInfo - err := k.cdc.UnmarshalBinary(bz, &bi) + err := k.cdc.UnmarshalBinaryLengthPrefixed(bz, &bi) if err != nil { panic(err) } @@ -48,7 +48,7 @@ func (k Keeper) getBondInfo(ctx sdk.Context, addr sdk.AccAddress) bondInfo { func (k Keeper) setBondInfo(ctx sdk.Context, addr sdk.AccAddress, bi bondInfo) { store := ctx.KVStore(k.key) - bz, err := k.cdc.MarshalBinary(bi) + bz, err := k.cdc.MarshalBinaryLengthPrefixed(bi) if err != nil { panic(err) } diff --git a/examples/democoin/x/simplestake/keeper_test.go b/docs/examples/democoin/x/simplestake/keeper_test.go similarity index 89% rename from examples/democoin/x/simplestake/keeper_test.go rename to docs/examples/democoin/x/simplestake/keeper_test.go index 68f28bd91b07..974cf50e9ecf 100644 --- a/examples/democoin/x/simplestake/keeper_test.go +++ b/docs/examples/democoin/x/simplestake/keeper_test.go @@ -35,8 +35,8 @@ func TestKeeperGetSet(t *testing.T) { cdc := codec.New() auth.RegisterBaseAccount(cdc) - accountMapper := auth.NewAccountMapper(cdc, authKey, auth.ProtoBaseAccount) - stakeKeeper := NewKeeper(capKey, bank.NewBaseKeeper(accountMapper), DefaultCodespace) + 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")) @@ -65,8 +65,8 @@ func TestBonding(t *testing.T) { ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - accountMapper := auth.NewAccountMapper(cdc, authKey, auth.ProtoBaseAccount) - bankKeeper := bank.NewBaseKeeper(accountMapper) + 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() @@ -75,10 +75,10 @@ func TestBonding(t *testing.T) { _, _, err := stakeKeeper.unbondWithoutCoins(ctx, addr) require.Equal(t, err, ErrInvalidUnbond(DefaultCodespace)) - _, err = stakeKeeper.bondWithoutCoins(ctx, addr, pubKey, sdk.NewInt64Coin("steak", 10)) + _, err = stakeKeeper.bondWithoutCoins(ctx, addr, pubKey, sdk.NewInt64Coin("stake", 10)) require.Nil(t, err) - power, err := stakeKeeper.bondWithoutCoins(ctx, addr, pubKey, sdk.NewInt64Coin("steak", 10)) + power, err := stakeKeeper.bondWithoutCoins(ctx, addr, pubKey, sdk.NewInt64Coin("stake", 10)) require.Nil(t, err) require.Equal(t, int64(20), power) diff --git a/examples/democoin/x/simplestake/msgs.go b/docs/examples/democoin/x/simplestake/msgs.go similarity index 87% rename from examples/democoin/x/simplestake/msgs.go rename to docs/examples/democoin/x/simplestake/msgs.go index aea984d18bb6..f3edb4ce0d84 100644 --- a/examples/democoin/x/simplestake/msgs.go +++ b/docs/examples/democoin/x/simplestake/msgs.go @@ -26,8 +26,8 @@ func NewMsgBond(addr sdk.AccAddress, stake sdk.Coin, pubKey crypto.PubKey) MsgBo } //nolint -func (msg MsgBond) Type() string { return moduleName } //TODO update "stake/createvalidator" -func (msg MsgBond) Name() string { return "bond" } +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 @@ -66,8 +66,8 @@ func NewMsgUnbond(addr sdk.AccAddress) MsgUnbond { } //nolint -func (msg MsgUnbond) Type() string { return moduleName } //TODO update "stake/createvalidator" -func (msg MsgUnbond) Name() string { return "unbond" } +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 } diff --git a/examples/democoin/x/simplestake/msgs_test.go b/docs/examples/democoin/x/simplestake/msgs_test.go similarity index 100% rename from examples/democoin/x/simplestake/msgs_test.go rename to docs/examples/democoin/x/simplestake/msgs_test.go diff --git a/examples/democoin/x/simplestake/types.go b/docs/examples/democoin/x/simplestake/types.go similarity index 100% rename from examples/democoin/x/simplestake/types.go rename to docs/examples/democoin/x/simplestake/types.go diff --git a/examples/democoin/x/sketchy/handler.go b/docs/examples/democoin/x/sketchy/handler.go similarity index 100% rename from examples/democoin/x/sketchy/handler.go rename to docs/examples/democoin/x/sketchy/handler.go diff --git a/examples/kvstore/kvstore b/docs/examples/kvstore/kvstore similarity index 100% rename from examples/kvstore/kvstore rename to docs/examples/kvstore/kvstore diff --git a/examples/kvstore/main.go b/docs/examples/kvstore/main.go similarity index 100% rename from examples/kvstore/main.go rename to docs/examples/kvstore/main.go diff --git a/examples/kvstore/tx.go b/docs/examples/kvstore/tx.go similarity index 97% rename from examples/kvstore/tx.go rename to docs/examples/kvstore/tx.go index bb3075ae4212..9c9cb8955677 100644 --- a/examples/kvstore/tx.go +++ b/docs/examples/kvstore/tx.go @@ -14,11 +14,11 @@ type kvstoreTx struct { bytes []byte } -func (tx kvstoreTx) Type() string { +func (tx kvstoreTx) Route() string { return "kvstore" } -func (tx kvstoreTx) Name() string { +func (tx kvstoreTx) Type() string { return "kvstore" } diff --git a/docs/gaia/README.md b/docs/gaia/README.md new file mode 100644 index 000000000000..9ecf3da2384d --- /dev/null +++ b/docs/gaia/README.md @@ -0,0 +1,19 @@ +# Gaia Documentation + +Welcome to the `Gaia` docs. `Gaia` is the current name of the Cosmos SDK application for the Cosmos Hub. + +## Join the Cosmos Hub public testnet + +- [Install the `gaia` application](./installation.md) +- [Join the current testnet](./join-testnet.md) +- [Upgrade to a validator node](./validators/validator-setup.md) + +## Setup your own `gaia` testnet + +- [Setup your own `gaia` testnet](./networks.md) + +## Additional resources + +- [Intro to validators](./validators/overview.md) +- [Validator FAQ](./validators/validator-faq.md) +- [Validator security considerations](./validators/security.md) diff --git a/docs/sdk/clients.md b/docs/gaia/gaiacli.md similarity index 72% rename from docs/sdk/clients.md rename to docs/gaia/gaiacli.md index 9637b3b4beb8..5749e1ca776c 100644 --- a/docs/sdk/clients.md +++ b/docs/gaia/gaiacli.md @@ -1,13 +1,15 @@ -# Clients - -::: tip Note -🚧 We are actively working on documentation for SDK clients. -::: +# Gaia client ## Gaia CLI ::: tip Note -🚧 We are actively working on improving documentation for Gaiacli and Gaiad. +If you receive this error message: + +```bash +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. @@ -70,10 +72,22 @@ View the validator pubkey for your node by typing: gaiad tendermint show-validator ``` +Note that this is the Tendermint signing key, *not* the operator key you will use in delegation transactions. + ::: danger Warning 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 + +You can generate and print a multisig public key by typing: + +```bash +gaiacli show --multisig-threshold K name1 name2 name3 [...] +``` + +`K` is the minimum weight, e.g. minimum number of private keys that must have signed the transactions that carry the generated public key. + ### Account #### Get Tokens @@ -85,7 +99,7 @@ The best way to get tokens is from the [Cosmos Testnet Faucet](https://faucetcos After receiving tokens to your address, you can view your account's balance by typing: ```bash -gaiacli account +gaiacli query account ``` ::: warning Note @@ -101,7 +115,7 @@ The following command could be used to send coins from one account to another: gaiacli tx send \ --amount=10faucetToken \ --chain-id= \ - --name= \ + --from= \ --to= ``` @@ -118,14 +132,14 @@ Gas estimate might be inaccurate as state changes could occur in between the end Now, view the updated balances of the origin and destination accounts: ```bash -gaiacli account -gaiacli account +gaiacli query account +gaiacli query account ``` You can also check your balance at a given block by using the `--block` flag: ```bash -gaiacli account --block= +gaiacli query account --block= ``` You can simulate a transaction without actually broadcasting it by appending the `--dry-run` flag to the command line: @@ -134,7 +148,7 @@ You can simulate a transaction without actually broadcasting it by appending the gaiacli tx send \ --amount=10faucetToken \ --chain-id= \ - --name= \ + --from= \ --to= \ --dry-run ``` @@ -145,7 +159,7 @@ Furthermore, you can build a transaction and print its JSON format to STDOUT by gaiacli tx send \ --amount=10faucetToken \ --chain-id= \ - --name= \ + --from= \ --to= \ --generate-only > unsignedSendTx.json ``` @@ -155,10 +169,16 @@ You can now sign the transaction file generated through the `--generate-only` fl ```bash gaiacli tx sign \ --chain-id= \ - --name= + --from= unsignedSendTx.json > signedSendTx.json ``` +You can validate the transaction's signagures by typing the following: + +```bash +gaiacli tx sign --validate-signatures signedSendTx.json +``` + You can broadcast the signed transaction to a node by providing the JSON file to the following command: ``` @@ -180,13 +200,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 validators +gaiacli query stake validators ``` If you want to get the information of a single validator you can check it with: ```bash -gaiacli query validator +gaiacli query stake validator ``` #### Bond Tokens @@ -194,13 +214,21 @@ gaiacli query 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 delegate \ +gaiacli tx stake delegate \ --amount=10steak \ - --validator=$(gaiad tendermint show-validator) \ - --name= \ + --validator= \ + --from= \ --chain-id= ``` +`` is the operator address of the validator to which you intend to delegate. If you are running a local testnet, you can find this with: + +```bash +gaiacli keys show [name] --bech val +``` + +where `[name]` is the name of the key you specified when you initialized `gaiad`. + While tokens are bonded, they are pooled with all the other bonded tokens in the network. Validators and delegators obtain a percentage of shares that equal their stake in this pool. ::: tip Note @@ -212,7 +240,7 @@ 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 delegation \ +gaiacli query stake delegation \ --address-delegator= \ --validator= ``` @@ -220,38 +248,31 @@ gaiacli query delegation \ Or if you want to check all your current delegations with disctinct validators: ```bash -gaiacli query delegations +gaiacli query stake delegations ``` You can also get previous delegation(s) status by adding the `--height` flag. #### Unbond Tokens -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-percent` (eg:`25`) with the corresponding flags. +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 unbond begin \ +gaiacli tx stake unbond \ --validator= \ - --shares-percent=100 \ + --shares-fraction=0.5 \ --from= \ --chain-id= ``` -Later you must complete the unbonding process by using the `gaiacli tx unbond complete` command: - -```bash -gaiacli tx unbond complete \ - --validator= \ - --from= \ - --chain-id= -``` +The unbonding will be automatically completed when the unbonding period has passed. ##### Query Unbonding-Delegations Once you begin an unbonding-delegation, you can see it's information by using the following command: ```bash -gaiacli query unbonding-delegation \ +gaiacli query stake unbonding-delegation \ --address-delegator= \ --validator= \ ``` @@ -259,53 +280,96 @@ gaiacli query unbonding-delegation \ Or if you want to check all your current unbonding-delegations with disctinct validators: ```bash -gaiacli query unbonding-delegations +gaiacli query stake unbonding-delegations +``` + +Additionally, as you can get all the unbonding-delegations from a particular validator: + +```bash + gaiacli query stake unbonding-delegations-from ``` -You can also get previous unbonding-delegation(s) status by adding the `--height` flag. +To get previous unbonding-delegation(s) status on past blocks, try adding the `--height` flag. #### Redelegate Tokens A redelegation is a type delegation that allows you to bond illiquid tokens from one validator to another: ```bash -gaiacli tx redelegate begin \ - --address-validator-source= \ - --address-validator-dest= \ - --shares-percent=50 \ +gaiacli tx stake redelegate \ + --addr-validator-source= \ + --addr-validator-dest= \ + --shares-fraction=50 \ --from= \ --chain-id= ``` -Here you can also redelegate a specific `shares-amount` or a `shares-percent` with the corresponding flags. +Here you can also redelegate a specific `shares-amount` or a `shares-fraction` with the corresponding flags. -Later you must complete the redelegation process by using the `gaiacli tx redelegate complete` command: - -```bash -gaiacli tx unbond complete \ - --validator= \ - --from= \ - --chain-id= -``` +The redelegation will be automatically completed when the unbonding period has passed. ##### Query Redelegations Once you begin an redelegation, you can see it's information by using the following command: ```bash -gaiacli query redelegation \ +gaiacli query stake redelegation \ --address-delegator= \ - --address-validator-source= \ - --address-validator-dest= \ + --addr-validator-source= \ + --addr-validator-dest= \ ``` Or if you want to check all your current unbonding-delegations with disctinct validators: ```bash -gaiacli query redelegations +gaiacli query stake redelegations ``` -You can also get previous redelegation(s) status by adding the `--height` flag. +Additionally, as you can get all the outgoing redelegations from a particular validator: + +```bash + gaiacli query stake redelegations-from +``` + +To get previous redelegation(s) status on past blocks, try adding the `--height` flag. + +#### Query Parameters + +Parameters define high level settings for staking. You can get the current values by using: + +```bash +gaiacli query stake parameters +``` + +With the above command you will get the values for: + +- Unbonding time +- Maximum numbers of validators +- Coin denomination for staking + +All these values will be subject to updates though a `governance` process by `ParameterChange` proposals. + +#### Query Pool + +A staking `Pool` defines the dynamic parameters of the current state. You can query them with the following command: + +```bash +gaiacli query stake pool +``` + +With the `pool` command you will get the values for: + +- Loose and bonded tokens +- Token supply +- Current anual inflation and the block in which the last inflation was processed +- Last recorded bonded shares + +##### Query Delegations To Validator + +You can also query all of the delegations to a particular validator: +```bash + gaiacli query delegations-to +``` ### Governance @@ -320,7 +384,7 @@ Some considerations about the voting process: - Voters can choose between options `Yes`, `No`, `NoWithVeto` and `Abstain` At the end of the voting period, a proposal is accepted if `(YesVotes/(YesVotes+NoVotes+NoWithVetoVotes))>1/2` and `(NoWithVetoVotes/(YesVotes+NoVotes+NoWithVetoVotes))<1/3`. It is rejected otherwise -For more information about the governance process and how it works, please check out the Governance module [specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/governance). +For more information about the governance process and how it works, please check out the Governance module [specification](./../spec/governance). #### Create a Governance proposal @@ -331,11 +395,10 @@ In order to create a governance proposal, you must submit an initial deposit alo - `type`: Type of proposal. Must be of value _Text_ (types _SoftwareUpgrade_ and _ParameterChange_ not supported yet). ```bash -gaiacli tx submit-proposal \ +gaiacli tx gov submit-proposal \ --title= \ --description=<description> \ --type=<Text/ParameterChange/SoftwareUpgrade> \ - --proposer=<account_cosmos> \ --deposit=<40steak> \ --from=<name> \ --chain-id=<chain_id> @@ -346,14 +409,13 @@ gaiacli tx submit-proposal \ Once created, you can now query information of the proposal: ```bash -gaiacli query proposal \ - --proposal-id=<proposal_id> +gaiacli query gov proposal --proposal-id=<proposal_id> ``` Or query all available proposals: ```bash -gaiacli query proposals +gaiacli query gov proposals ``` You can also query proposals filtered by `voter` or `depositer` by using the corresponding flags. @@ -363,9 +425,8 @@ You can also query proposals filtered by `voter` or `depositer` by using the cor 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: ```bash -gaiacli tx deposit \ +gaiacli tx gov deposit \ --proposal-id=<proposal_id> \ - --depositer=<account_cosmos> \ --deposit=<200steak> \ --from=<name> \ --chain-id=<chain_id> @@ -373,66 +434,54 @@ gaiacli tx deposit \ > _NOTE_: Proposals that don't meet this requirement will be deleted after `MaxDepositPeriod` is reached. +##### Query deposits + +Once a new proposal is created, you can query all the deposits submitted to it: + +```bash +gaiacli query gov deposits --proposal-id=<proposal_id> +``` + +You can also query a deposit submitted by a specific address: + +```bash +gaiacli query gov deposit \ + --proposal-id=<proposal_id> \ + --depositer=<account_cosmos> +``` + #### Vote on a proposal After a proposal's deposit reaches the `MinDeposit` value, the voting period opens. Bonded `Atom` holders can then cast vote on it: ```bash -gaiacli tx vote \ +gaiacli tx gov vote \ --proposal-id=<proposal_id> \ - --voter=<account_cosmos> \ --option=<Yes/No/NoWithVeto/Abstain> \ --from=<name> \ --chain-id=<chain_id> ``` -##### Query vote +##### Query votes Check the vote with the option you just submitted: ```bash -gaiacli query vote \ +gaiacli query gov vote \ --proposal-id=<proposal_id> \ --voter=<account_cosmos> ``` -#### Query Parameters - -You can get the current parameters that define high level settings for staking: +You can also get all the previous votes submitted to the proposal with: +```bash +gaiacli query gov votes --proposal-id=<proposal_id> ``` -gaiacli query parameters -``` - -With the above command you will get the values for: -- Maximum and minumum Inflation rate -- Maximum annual change in inflation rate, -- Goal of bonded tokens (%) -- Unbonding time -- Maximum numbers of validators -- Coin denomination for staking - -All this values can be updated though a `governance` process by submitting a parameter change `proposal`. +#### Query proposal tally results -#### Query Pool - -A staking `Pool` defines the dynamic parameters of the current state. You can query them with the following command: +To check the current tally of a given proposal you can use the `tally` command: +```bash +gaiacli query gov tally --proposal-id=<proposal_id> ``` -gaiacli query pool -``` - -With the `pool` command you will get the values for: - -- Loose and bonded tokens -- Token supply -- Current anual inflation and the block in which the last inflation was processed -- Last recorded bonded shares - - -## Gaia-Lite - -::: tip Note -🚧 We are actively working on documentation for Gaia-lite. -::: diff --git a/docs/getting-started/installation.md b/docs/gaia/installation.md similarity index 51% rename from docs/getting-started/installation.md rename to docs/gaia/installation.md index 66ccae4b85e8..c4b78b13cbc9 100644 --- a/docs/getting-started/installation.md +++ b/docs/gaia/installation.md @@ -1,6 +1,6 @@ -# Install the SDK +# Install Gaia -This guide will explain how to install the [Cosmos SDK](/sdk/overview.md) onto your system. With the SDK installed on a server, you can participate in the latest testnet as either a [Full Node](./join-testnet.md#run-a-full-node) or a [Validator](/validators/validator-setup.md). +This guide will explain how to install the `gaiad` and `gaiacli` entrypoints onto your system. With these installed on a server, you can participate in the latest testnet as either a [Full Node](./join-testnet.md#run-a-full-node) or a [Validator](./validators/validator-setup.md). ## Install Go @@ -14,17 +14,13 @@ echo "export PATH=$PATH:$GOBIN" >> ~/.bash_profile ``` ::: tip -**Go 1.10+** is required for the Cosmos SDK. +**Go 1.11+** is required for the Cosmos SDK. ::: ## Install Cosmos SDK -Next, let's install the testnet's version of the Cosmos SDK. -You can find information about the latest testnet and the right -version of the Cosmos-SDK for it in the [testnets -repo](https://github.com/cosmos/testnets#testnet-status). -Here we'll use the `master` branch, which contains the latest stable release. -If necessary, make sure you `git checkout` the correct +Next, let's install the latest version of Gaia. Here we'll use the `master` branch, which contains the latest stable release. +If necessary, make sure you `git checkout` the correct [released version](https://github.com/cosmos/cosmos-sdk/releases). ```bash @@ -35,6 +31,8 @@ cd cosmos-sdk && git checkout master make get_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. + That will install the `gaiad` and `gaiacli` binaries. Verify that everything is OK: ```bash @@ -44,4 +42,4 @@ $ gaiacli version ## Run a Full Node -With Cosmos SDK installed, you can run [a full node on the latest testnet](full-node.md). +With the binaries installed, you can run [a full node on the latest testnet](./join-testnet.md). diff --git a/docs/getting-started/join-testnet.md b/docs/gaia/join-testnet.md similarity index 72% rename from docs/getting-started/join-testnet.md rename to docs/gaia/join-testnet.md index 66ec97cad5f1..560bbcf6e77f 100644 --- a/docs/getting-started/join-testnet.md +++ b/docs/gaia/join-testnet.md @@ -1,21 +1,23 @@ # Join the Testnet ::: tip Current Testnet -See the [testnet repo](https://github.com/cosmos/testnets) for +See the [testnet repo](https://github.com/cosmos/testnets) for information on the latest testnet, including the correct version of the Cosmos-SDK to use and details about the genesis file. ::: -Please ensure you have the [Cosmos SDK](/getting-started/installation.md) installed. If you ran a full node on a previous testnet, please skip to [Upgrading From Previous Testnet](#upgrading-from-previous-testnet). +**Please ensure you have the [gaia binaries](./installation.md) installed.** + +If you ran a full node on a previous testnet, please skip to [Upgrading From Previous Testnet](#upgrading-from-previous-testnet). ## Setting Up a New Node -These instructions are for setting up a brand new full node from scratch. +These instructions are for setting up a brand new full node from scratch. First, initialize the node and create the necessary config files: ```bash -gaiad init --name <your_custom_name> +gaiad init ``` ::: warning Note @@ -75,10 +77,14 @@ git fetch --all && git checkout master make update_tools && make get_vendor_deps && make install ``` +::: tip +*NOTE*: If you have issues at this step, please check that you have the latest stable version of GO installed. +::: + Note we use `master` here since it contains the latest stable release. -See the [testnet repo](https://github.com/cosmos/testnets) -for details on which version is needed for which testnet, -and the [SDK release page](https://github.com/cosmos/cosmos-sdk/releases) +See the [testnet repo](https://github.com/cosmos/testnets) +for details on which version is needed for which testnet, +and the [SDK release page](https://github.com/cosmos/cosmos-sdk/releases) for details on each release. Your full node has been cleanly upgraded! @@ -94,20 +100,16 @@ mkdir -p $HOME/.gaiad/config curl https://raw.githubusercontent.com/cosmos/testnets/master/latest/genesis.json > $HOME/.gaiad/config/genesis.json ``` -Note we use the `latest` directory in the [testnets repo](https://github.com/cosmos/testnets) -which contains details for the latest testnet. If you are connecting to a different testnet, ensure you -get the right files. +Note we use the `latest` directory in the [testnets repo](https://github.com/cosmos/testnets) +which contains details for the latest testnet. If you are connecting to a different testnet, ensure you get the right files. ### Add Seed Nodes -Your node needs to know how to find peers. You'll need to add healthy seed nodes to `$HOME/.gaiad/config/config.toml`. Here are some seed nodes you can use: +Your node needs to know how to find peers. You'll need to add healthy seed nodes to `$HOME/.gaiad/config/config.toml`. The `testnets` repo contains links to the seed nodes for each testnet. If you are looking to join the running testnet please [check the repository for details](https://github.com/cosmos/testnets) on which nodes to use. -```toml -# Comma separated list of seed nodes to connect to -seeds = "718145d422a823fd2a4e1e36e91b92bb0c4ddf8e@gaia-testnet.coinculture.net:26656,5922bf29b48a18c2300b85cc53f424fce23927ab@67.207.73.206:26656,7c8b8fd03577cd4817f5be1f03d506f879df98d8@gaia-7000-seed1.interblock.io:26656,a28737ff02391a6e00a1d3b79befd57e68e8264c@gaia-7000-seed2.interblock.io:26656,987ffd26640cd03d08ed7e53b24dfaa7956e612d@gaia-7000-seed3.interblock.io:26656" -``` +If those seeds aren't working, you can find more seeds and persistent peers on the [Cosmos Explorer](https://explorer.cosmos.network/nodes). Open the the `Full Nodes` pane and select nodes that do not have private (`10.x.x.x`) or [local IP addresses](https://en.wikipedia.org/wiki/Private_network). The `Persistent Peer` field contains the connection string. For best results use 4-6. -If those seeds aren't working, you can find more seeds and persistent peers on the [Cosmos Explorer](https://explorecosmos.network/nodes). Open the the `Full Nodes` pane and select nodes that do not have private (`10.x.x.x`) or [local IP addresses](https://en.wikipedia.org/wiki/Private_network). The `Persistent Peer` field contains the connection string. For best results use 4-6. +You can also ask for peers on the [Validators Riot Room](https://riot.im/app/#/room/#cosmos-validators:matrix.org) For more information on seeds and peers, you can [read this](https://github.com/tendermint/tendermint/blob/develop/docs/using-tendermint.md#peers). @@ -130,4 +132,4 @@ View the status of the network with the [Cosmos Explorer](https://explorecosmos. ## Upgrade to Validator Node -You now have an active full node. What's the next step? You can upgrade your full node to become a Cosmos Validator. The top 100 validators have the ability to propose new blocks to the Cosmos Hub. Continue onto [the Validator Setup](../validators/validator-setup.md). +You now have an active full node. What's the next step? You can upgrade your full node to become a Cosmos Validator. The top 100 validators have the ability to propose new blocks to the Cosmos Hub. Continue onto [the Validator Setup](./validators/validator-setup.md). diff --git a/docs/clients/keys.md b/docs/gaia/keys.md similarity index 100% rename from docs/clients/keys.md rename to docs/gaia/keys.md diff --git a/docs/clients/ledger.md b/docs/gaia/ledger.md similarity index 100% rename from docs/clients/ledger.md rename to docs/gaia/ledger.md diff --git a/docs/getting-started/networks.md b/docs/gaia/networks.md similarity index 89% rename from docs/getting-started/networks.md rename to docs/gaia/networks.md index 67f0dab5aa45..3bc08c94b329 100644 --- a/docs/getting-started/networks.md +++ b/docs/gaia/networks.md @@ -56,8 +56,9 @@ make build-linux localnet-stop localnet-start ### Configuration -The `make localnet-start` creates files for a 4-node testnet in `./build` by calling the `gaiad testnet` command. -This outputs a handful of files in the `./build` directory: +The `make localnet-start` creates files for a 4-node testnet in `./build` by +calling the `gaiad testnet` command. This outputs a handful of files in the +`./build` directory: ```tree -L 2 build/ build/ @@ -103,12 +104,29 @@ Each `./build/nodeN` directory is mounted to the `/gaiad` directory in each cont ### Logging -Logs are saved under each `./build/nodeN/gaiad/gaia.log`. Watch them stream in with, for example: +Logs are saved under each `./build/nodeN/gaiad/gaia.log`. You can also watch logs +directly via Docker, for example: ``` -tail -f build/node0/gaiad/gaia.log +docker logs -f gaiadnode0 ``` +### Keys & Accounts + +To interact with `gaiacli` and start querying state or creating txs, you use the +`gaiacli` directory of any given node as your `home`, for example: + +```shell +gaiacli keys list --home ./build/node0/gaiacli +``` + +Now that accounts exists, you may create new accounts and send those accounts +funds! + +::: tip +**Note**: Each node's seed is located at `./build/nodeN/gaiacli/key_seed.json`. +::: + ### Special binaries If you have multiple binaries with different names, you can specify which one to run with the BINARY environment variable. The path of the binary is relative to the attached volume. For example: diff --git a/docs/validators/overview.md b/docs/gaia/validators/overview.md similarity index 100% rename from docs/validators/overview.md rename to docs/gaia/validators/overview.md diff --git a/docs/validators/security.md b/docs/gaia/validators/security.md similarity index 100% rename from docs/validators/security.md rename to docs/gaia/validators/security.md diff --git a/docs/validators/validator-faq.md b/docs/gaia/validators/validator-faq.md similarity index 100% rename from docs/validators/validator-faq.md rename to docs/gaia/validators/validator-faq.md diff --git a/docs/validators/validator-setup.md b/docs/gaia/validators/validator-setup.md similarity index 81% rename from docs/validators/validator-setup.md rename to docs/gaia/validators/validator-setup.md index 43a3b0672884..61872bcf9cd6 100644 --- a/docs/validators/validator-setup.md +++ b/docs/gaia/validators/validator-setup.md @@ -1,14 +1,14 @@ # Validator Setup -::: warning Current Testnet -The current testnet is `gaia-8000`. +::: 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. ::: Before setting up your validator node, make sure you've already gone through the [Full Node Setup](/docs/getting-started/full-node.md) guide. ## Running a Validator Node -[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, double sign a transaction, or don't cast their votes. 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](/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. ::: danger Warning If you want to become a validator for the Hub's `mainnet`, you should [research security](/validators/security.md). @@ -22,20 +22,19 @@ Your `cosmosvalconspub` can be used to create a new validator by staking tokens. gaiad tendermint show-validator ``` -Next, craft your `gaiacli tx create-validator` command: +Next, craft your `gaiacli tx stake create-validator` command: ::: warning Note Don't use more `steak` thank you have! You can always get more by using the [Faucet](https://faucetcosmos.network/)! ::: ```bash -gaiacli tx create-validator \ +gaiacli tx stake create-validator \ --amount=5steak \ --pubkey=$(gaiad tendermint show-validator) \ - --address-validator=<account_cosmosval> --moniker="choose a moniker" \ --chain-id=<chain_id> \ - --name=<key_name> \ + --from=<key_name> \ --commission-rate="0.10" \ --commission-max-rate="0.20" \ --commission-max-change-rate="0.01" @@ -45,6 +44,10 @@ __Note__: When specifying commission parameters, the `commission-max-change-rate is used to measure % _point_ change over the `commission-rate`. E.g. 1% to 2% is a 100% rate increase, but only 1 percentage point. +::: tip +Use `gaiacli tx stake create-validator -h` to get a list of all the available flags. +::: + ### Edit Validator Description You can edit your validator's public description. This info is to identify your validator, and will be relied on by delegators to decide which validators to stake to. Make sure to provide input for every flag below, otherwise the field will default to empty (`--moniker` defaults to the machine name). @@ -52,14 +55,13 @@ 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 edit-validator - --validator=<account_cosmos> +gaiacli tx stake edit-validator --moniker="choose a moniker" \ --website="https://cosmos.network" \ - --identity=6A0D65E29A4CBC8E - --details="To infinity and beyond!" + --identity=6A0D65E29A4CBC8E \ + --details="To infinity and beyond!" \ --chain-id=<chain_id> \ - --name=<key_name> \ + --from=<key_name> \ --commission-rate="0.10" ``` @@ -75,7 +77,7 @@ __Note__: The `commission-rate` value must adhere to the following invariants: View the validator's information with this command: ```bash -gaiacli query validator <account_cosmos> +gaiacli query stake validator <account_cosmos> ``` ### Track Validator Signing Information @@ -83,20 +85,18 @@ gaiacli query validator <account_cosmos> In order to keep track of a validator's signatures in the past you can do so by using the `signing-info` command: ```bash -gaiacli query signing-information <validator-pubkey>\ +gaiacli query slashing signing-info <validator-pubkey>\ --chain-id=<chain_id> ``` ### Unjail Validator -When a validator is "jailed" for downtime, you must submit an `Unjail` transaction in order to be able to get block proposer rewards again (depends on the zone fee distribution). +When a validator is "jailed" for downtime, you must submit an `Unjail` transaction from the operator account in order to be able to get block proposer rewards again (depends on the zone fee distribution). ```bash -gaiacli tx unjail \ +gaiacli tx slashing unjail \ --from=<key_name> \ --chain-id=<chain_id> - --validator=<account_cosmosval> \ - --chain-id=gaia-6002 ``` ### Confirm Your Validator is Running @@ -128,7 +128,7 @@ gaiad start Wait for your full node to catch up to the latest block. Next, run the following command. Note that `<cosmos>` is the address of your validator account, and `<name>` is the name of the validator account. You can find this info by running `gaiacli keys list`. ```bash -gaiacli tx unjail <cosmos> --chain-id=<chain_id> --name=<name> +gaiacli tx slashing unjail <cosmos> --chain-id=<chain_id> --from=<from> ``` ::: danger Warning diff --git a/docs/getting-started/voyager.md b/docs/getting-started/voyager.md deleted file mode 100644 index 7fa17f24cc1a..000000000000 --- a/docs/getting-started/voyager.md +++ /dev/null @@ -1,9 +0,0 @@ -# Download Voyager - -Voyager is the official desktop app for the Cosmos Network. It provides an intuitive interface for managing accounts, creating transactions, delegation, and governance. - -Download the [latest Voyager release](https://github.com/cosmos/voyager/releases). - -### Delving Deeper - -If you're familar with the CLI, you should consider running a full node or validator for the latest Cosmos testnet. [Learn more](/getting-started/installation.md). diff --git a/docs/intro/README.md b/docs/intro/README.md new file mode 100644 index 000000000000..83dcc31c56ee --- /dev/null +++ b/docs/intro/README.md @@ -0,0 +1,43 @@ +# SDK Intro + +The [Cosmos-SDK](https://github.com/cosmos/cosmos-sdk) is a framework for building multi-asset Proof-of-Stake (PoS) blockchains, like the Cosmos Hub, as well as Proof-Of-Authority (PoA) blockchains. + +The goal of the Cosmos SDK is to allow developers to easily create custom blockchains from scratch that can natively interoperate with other blockchains. We envision the SDK as the npm-like framework to build secure blockchain applications on top of [Tendermint](https://github.com/tendermint/tendermint). + +It is based on two major principles: + +- **Composability:** Anyone can create a module for the Cosmos-SDK, and integrating the already-built modules is as simple as importing them into your blockchain application. + +- **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](./capabilities.md). + +## Learn more about the SDK + +- [SDK application architecture](./design.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. +- `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. diff --git a/docs/intro/ocap.md b/docs/intro/ocap.md new file mode 100644 index 000000000000..7d57d0c34e2b --- /dev/null +++ b/docs/intro/ocap.md @@ -0,0 +1,102 @@ +# Object-Capability Model + +## Intro + +When thinking about security, it is good to start with a specific threat +model. Our threat model is the following: + +> We assume that a thriving ecosystem of Cosmos-SDK modules that are easy to compose into a blockchain application will contain faulty or malicious modules. + +The Cosmos SDK is designed to address this threat by being the +foundation of an object capability system. + +> The structural properties of object capability systems favor +> modularity in code design and ensure reliable encapsulation in +> code implementation. +> +> These structural properties facilitate the analysis of some +> security properties of an object-capability program or operating +> system. Some of these — in particular, information flow properties +> — can be analyzed at the level of object references and +> connectivity, independent of any knowledge or analysis of the code +> that determines the behavior of the objects. +> +> As a consequence, these security properties can be established +> and maintained in the presence of new objects that contain unknown +> and possibly malicious code. +> +> These structural properties stem from the two rules governing +> access to existing objects: +> +> 1. An object A can send a message to B only if object A holds a +> reference to B. +> 2. An object A can obtain a reference to C only +> if object A receives a message containing a reference to C. As a +> consequence of these two rules, an object can obtain a reference +> to another object only through a preexisting chain of references. +> In short, "Only connectivity begets connectivity." + +For an introduction to object-capabilities, see [this article](http://habitatchronicles.com/2017/05/what-are-capabilities/). + +Strictly speaking, Golang does not implement object capabilities +completely, because of several issues: + +- pervasive ability to import primitive modules (e.g. "unsafe", "os") +- pervasive ability to [override module vars](https://github.com/golang/go/issues/23161) +- data-race vulnerability where 2+ goroutines can create illegal interface values + +The first is easy to catch by auditing imports and using a proper +dependency version control system like Dep. The second and third are +unfortunate but it can be audited with some cost. + +Perhaps [Go2 will implement the object capability +model](https://github.com/golang/go/issues/23157). + +## Ocaps in practice + +The idea is to only reveal what is necessary to get the work done. + +For example, the following code snippet violates the object capabilities +principle: + +```go +type AppAccount struct {...} +var account := &AppAccount{ + Address: pub.Address(), + Coins: sdk.Coins{{"ATM", 100}}, +} +var sumValue := externalModule.ComputeSumValue(account) +``` + +The method `ComputeSumValue` implies a pure function, yet the implied +capability of accepting a pointer value is the capability to modify that +value. The preferred method signature should take a copy instead. + +```go +var sumValue := externalModule.ComputeSumValue(*account) +``` + +In the Cosmos SDK, you can see the application of this principle in the +[basecoin examples folder](../examples/basecoin). + +```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()) +} +``` + +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 new file mode 100644 index 000000000000..4b302bd33439 --- /dev/null +++ b/docs/intro/sdk-app-architecture.md @@ -0,0 +1,62 @@ +# SDK Application Architecture + +## Parts of a SDK blockchain + +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. + +>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 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: + +``` ++---------------------+ +| | +| Application | +| | ++--------+---+--------+ + ^ | + | | ABCI + | v ++--------+---+--------+ +| | +| | +| Tendermint | +| | +| | ++---------------------+ +``` + +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. + +Basecoin and the native `x/` extensions use go-amino for all serialization needs, including for transactions and accounts. + +## SDK tutorial + +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). + + diff --git a/docs/introduction/cosmos-hub.md b/docs/introduction/cosmos-hub.md deleted file mode 100644 index 0e40b7b4c199..000000000000 --- a/docs/introduction/cosmos-hub.md +++ /dev/null @@ -1,32 +0,0 @@ -# The Cosmos Hub -The first blockchain in the Cosmos network is the Cosmos hub. The Cosmos hub connects to zones via the novel [IBC](/specs/ibc.md) (inter-blockchain communication) protocol and keeps a record of the total number of tokens in each zone. Because all inter-zone transfers go through the Cosmos Hub, you can send tokens from one zone to another, quickly and securely, without the need for a liquid exchange or trusted third party between zones. - -The Cosmos Hub can connect to many different kinds of zones - public or private - as long as each zone speaks IBC. Tendermint-based Zones are natively compatible with IBC, but any fast-finality consensus algorithm can be used as a replacement. Cosmos can thus support a wide variety of currencies and scripting languages like those found in Bitcoin, Ethereum, ZeroCash, CryptoNote, and more. Atom's are the native token of the Cosmos Hub. It is a license for holders to stake and participate in governance. - -## Proof-of-Stake -Blockchain networks are secured by a set of validators, who are responsible for committing new blocks in the blockchain. In Proof-Of-Work systems such as Bitcoin, validators are called miners, and the probability of a given miner to produce the next block is proportional to its computing power. In contrast, the Cosmos Hub is a public Proof-of-Stake blockchain. Proof-of-Stake is a category of consensus algorithm that relies on validators’ economic stake in the network. - -## Atoms -In the case of the Cosmos Hub, the frequency at which a validator is selected to produce the next block is proportional to the number of Atoms locked up (i.e. bonded, or staked). - -These Atoms can be locked up by validators themselves, or delegated to them by Atom holders that do not want or cannot run validator operations, called delegators. The sum of a validator’s self-bonded and delegated Atoms is called its stake. The Atom is the only staking token of the Cosmos Hub. In return for locking up their Atoms, delegators earn block provisions (in Atoms), block rewards (in Photons) and transaction fees (in whitelisted fee tokens). When a bonded Atom holder wants to retrieve its deposit, it must wait for a 3 week unbonding period. - -## Photons -Atoms are designed to be bonded on the Hub. This means that they are not ideal to pay fees or to move on other Zones of the Cosmos Ecosystem. This is why Photons will be introduced. Photon is a fee token with much greater liquidity and velocity than Atom. It is the second whitelisted fee token on the Hub after Atom and can move on all the Zones that are connected to the Hub. - -## Hard spoon -A hard spoon occurs when a new cryptocurrency is minted by replicating the account balances of an existing cryptocurrency. In our case, we are hard spooning Ethereum by taking the account balances of existing Ethereum holders and mirroring those values. This means that ETH holders will have their coins replicated in this EVM zone and will be redeemable as fee tokens–Photons–within Ethermint. - -After launch, Atom holders will be able to vote on the hard spoon, specifically: - -- Whether the hard spoon should happen or not -- When the snapshot will occur -- How Photons are distributed (what goes to Ethereum holders, what goes to Atom holders and Photon inflation) - -## Validators -Validators of the Cosmos Hub are responsible for creating new blocks of transactions that are added to the blockchain. Running a validator is non-trivial. It requires technical knowledge and hardware investment. Additionally, due to the way that Tendermint—the underlying consensus engine on which the Cosmos Hub is built—works, the number of validators must be limited. Initially, this limit is fixed to 100. This means that only the top 100 addresses with the most stake that declared their intention to become validator will be validators. As a result, most Atom holders will not be validators. Instead, they will become delegators, thereby participating in deciding who among the validator candidates actually become validators. - -If you are interested in becoming a validator: learn more about validators [here](/validators/overview.md). - -## Delegators -People that cannot, or do not want to run validator operations, can still participate in the staking process as delegators. Indeed, validators are not chosen based on their own stake but based on their total stake, which is the sum of their own stake and of the stake that is delegated to them. If you are interested in staking your atoms to a Validator to earn revenue, or just want to learn more about delegators, read the [Delegator FAQ](/resources/delegator-faq.md). diff --git a/docs/introduction/tendermint-cosmos.md b/docs/introduction/tendermint-cosmos.md deleted file mode 100644 index f7aeb7746ca4..000000000000 --- a/docs/introduction/tendermint-cosmos.md +++ /dev/null @@ -1,27 +0,0 @@ -# Tendermint and Cosmos - -Blockchains can be divided into three conceptual layers: - -- **Networking:** Responsible for propagating transactions. -- **Consensus:** Enables validator nodes to agree on the next set of transactions to process (i.e. add blocks of transactions to the blockchain). -- **Application:** Responsible for updating the state given a set of transactions, i.e. processing transactions. - -The *networking* layer makes sure that each node receives transactions. The *consensus* layer makes sure that each node agrees on the same transactions to modify their local state. As for the *application* layer, it processes transactions. Given a transaction and a state, the application will return a new state. In Bitcoin for example, the application state is a ledger or list of balances for each account (in reality, it's a list of UTXO, short for Unspent Transaction Output, but let's call them balances for the sake of simplicity), and the transactions modify the application's state by changing these list of balances. In the case of Ethereum, the application is a virtual machine. Each transaction goes through this virtual machine and modifies the application state according to the the smart contract that is called within it. - -Before Tendermint, building a blockchain required building all three layers from the ground up. It was such a tedious task that most developers preferred to fork or replicate the Bitcoin codebase, but were constrainted by the limitations of Bitcoin's protocol. The Ethereum Virtual Machine (EVM) was designed to solve this problem and simplify decentralized application development by allowing customizable logic to be executed through smart contracts. But it did not resolve the limitations (interoperability, scalability and customization) of blockchains themselves. Go-Ethereum remains a very monolithic tech stack that is difficult to hard-fork much like Bitcoin's codebase. - -Tendermint was designed to address these issues and provide developers with an laternative. The goal of Tendermint is to provide the *networking* and *consensus* layers of a blockchain as a generic engine to power any application developers want to build. With Tendermint, developers only have to focus on the *application* layer, thereby saving hundreds of hours of work and costly development set-ups. For reference, Tendermint also designates the name of the byzantine fault tolerant consensus algorithm used within the Tendermint Core engine. - -Tendermint connects the blockchain engine, Tendermint Core (*networking* and *consensus* layers), to the *application* layer via a socket protocol called the [ABCI](https://github.com/tendermint/abci), short for Application-BlockChain Interface. Developers only have to implement a few messages to build an ABCI-enabled application that runs on top of the Tendermint Core engine. ABCI is language agnostic, meaning that developers can build the *application* part of their blockchain in any programming language. Building on top of the Tendermint Core engine also provides the following benefits: - -- **Public or private blockchain capable.** Developers can deploy any blockchain application, permissioned (private) and permissionless (public), on top of Tendermint Core. -- **Performance.** Tendermint Core is a state-of-the-art blockchain consensus engine able to handle large number of transactions in short timespan. A block time on Tendermint Core can be as low as one second and can process thousands of transactions in that time period. -- **Instant finality.** A property of the Tendermint consensus algorithm is instant finality, meaning that forks are never created, as long as less than a third of the validators are malicious (byzantine). Users can be sure their transactions are finalized as soon as a block is created. -- **Security.** Tendermint Core's consensus is not only fault tolerant, it’s optimally Byzantine fault-tolerant (BFT), with accountability. If the blockchain forks, there is a way to determine liability. -- **Light-client support**. Tendermint provides built-in light-clients. - -But most importantly, Tendermint is natively compatible with the [Inter-Blockchain Communication Protocol](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/ibc) (IBC). This means that any Tendermint-based blockchain, whether public or private, can be natively connected to the Cosmos ecosystem and securely exchange tokens with other blockchains in the ecosystem. Note that benefiting from interoperability via IBC and Cosmos preserves the sovereignty of your Tendermint chain. Non-Tendermint chains can also be connected to Cosmos via IBC adapters or Peg-Zones, but this is out of scope for this document. - -For a more detailed overview of the Cosmos ecosystem, you can read [this article](https://blog.cosmos.network/understanding-the-value-proposition-of-cosmos-ecaef63350d). - -For more on Tendermint, go [here](tendermint.md) \ No newline at end of file diff --git a/docs/introduction/tendermint.md b/docs/introduction/tendermint.md deleted file mode 100644 index 4192a03ea6e7..000000000000 --- a/docs/introduction/tendermint.md +++ /dev/null @@ -1,47 +0,0 @@ -# Tendermint - -Tendermint is software for securely and consistently replicating an application on many machines. By securely, we mean that Tendermint works even if up to 1/3 of machines fail in arbitrary ways. By consistently, we mean that every non-faulty machine sees the same transaction log and computes the same state. Secure and consistent replication is a fundamental problem in distributed systems; it plays a critical role in the fault tolerance of a broad range of applications, from currencies, to elections, to infrastructure orchestration, and beyond. - -Tendermint is designed to be easy-to-use, simple-to-understand, highly performant, and useful for a wide variety of distributed applications. - -## Byzantine Fault Tolerance - -The ability to tolerate machines failing in arbitrary ways, including becoming malicious, is known as Byzantine fault tolerance (BFT). The theory of BFT is decades old, but software implementations have only became popular recently, due largely to the success of “blockchain technology” like Bitcoin and Ethereum. Blockchain technology is just a re-formalization of BFT in a more modern setting, with emphasis on peer-to-peer networking and cryptographic authentication. The name derives from the way transactions are batched in blocks, where each block contains a cryptographic hash of the previous one, forming a chain. In practice, the blockchain data structure actually optimizes BFT design. - -## Application Blockchain Interface - -Tendermint consists of two chief technical components: a blockchain consensus engine and a generic application interface. The consensus engine, called Tendermint Core, ensures that the same transactions are recorded on every machine in the same order. The application interface, called the Application Blockchain Interface (ABCI), enables the transactions to be processed in any programming language. Unlike other blockchain and consensus solutions developers can use Tendermint for BFT state machine replication in any programming language or development environment. Visit the [Tendermint docs](https://tendermint.com/docs/introduction/introduction.html#abci-overview) for a deep dive into the ABCI. - -## Understanding the roles of the different layers - -It is important to have a good understanding of the respective responsibilities of both the *Application* and the *Consensus Engine*. - -Responsibilities of the *Consensus Engine*: -- Propagate transactions -- Agree on the order of valid transactions - -Reponsibilities of the *Application*: -- Generate Transactions -- Check if transactions are valid -- Process Transactions (includes state changes) - -It is worth underlining that the *Consensus Engine* has knowledge of a given validator set for each block, but that it is the responsiblity of the *Application* to trigger validator set changes. This is the reason why it is possible to build both **public and private chains** with the Cosmos-SDK and Tendermint. A chain will be public or private depending on the rules, defined at application level, that governs a validator's set changes. - -The ABCI establishes the connection between the *Consensus Engine* and the *Application*. Essentially, it boils down to two messages: - -- `CheckTx`: Ask the application if the transaction is valid. When a validator's node receives a transaction, it will run `CheckTx` on it. If the transaction is valid, it is added to the mempool. -- `DeliverTx`: Ask the application to process the transaction and update the state. - -Let us give a high-level overview of how the *Consensus Engine* and the *Application* interract with each other. - -- At all times, when the consensus engine (Tendermint Core) of a validator node receives a transaction, it passes it to the application via `CheckTx` to check its validity. If it is valid, the transaction is added to the mempool. -- Let us say we are at block N. There is a validator set V. A proposer of the next block is selected from V by the *Consensus Engine*. The proposer gathers valid transaction from its mempool to form a new block. Then, the block is gossiped to other validators to be signed/commited. The block becomes block N+1 once 2/3+ of V have signed a *precommit* on it (For a more detailed explanation of the consensus algorithm, click [here](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm)). -- When block N+1 is signed by 2/3+ of V, it is gossipped to full-nodes. When full-nodes receive the block, they confirm its validity. A block is valid if it it holds valid signatures from more than 2/3 of V and if all the transactions in the block are valid. To check the validity of transactions, the *Consensus Engine* transfers them to the application via `DeliverTx`. After each transaction, `DeliverTx` returns a new state if the transaction was valid. At the end of the block, a final state is committed. Of course, this means that the order of transaction within a block matters. - -## Application frameworks - -Even if Tendermint makes it easy for developers to build their own blockchain by enabling them to focus on the *Application* layer of their blockchain, building an *Application* can be a challenging task in itself. This is why *Application Frameworks* exist. They provide developers with a secure and features-heavy environment to develop Tendermint-based applications. Here are some examples of *Application Frameworks* : - -- The [Cosmos SDK](/sdk/overview.md) is an ABCI framework written in Go. -- [Lotion JS](/lotion/overview.md) is an ABCI framework written in JavaScript. - diff --git a/docs/introduction/what-is-cosmos.md b/docs/introduction/what-is-cosmos.md deleted file mode 100644 index f9470d7c9869..000000000000 --- a/docs/introduction/what-is-cosmos.md +++ /dev/null @@ -1,7 +0,0 @@ -# What is Cosmos? - -Cosmos is a decentralized network of independent parallel blockchains, each powered by classical BFT consensus algorithms like [Tendermint](). - -The first blockchain in the Cosmos Network is the [Cosmos Hub](), whose native token is the Atom. Cosmos is a permission-less network, meaning that anybody can build a blockchain on it. - -Cosmos can interoperate with multiple other applications and cryptocurrencies. By creating a new zone, you can plug any blockchain system into the Cosmos hub and pass tokens back and forth between those zones, without the need for an intermediary. \ No newline at end of file diff --git a/docs/light/api.md b/docs/light/api.md deleted file mode 100644 index 7fbf9fbe19f5..000000000000 --- a/docs/light/api.md +++ /dev/null @@ -1,961 +0,0 @@ -# Cosmos Hub (Gaia-Lite) LCD API - -This document describes the API that is exposed by the specific Light Client Daemon (LCD) implementation of the Cosmos Hub (Gaia). Those APIs are exposed by a REST server and can easily be accessed over HTTP/WS (websocket) -connections. - -The complete API is comprised of the sub-APIs of different modules. The modules in the Cosmos Hub (Gaia-Lite) API are: - -- ICS0 ([TendermintAPI](api.md#ics0---tendermintapi)) -- ICS1 ([KeyAPI](api.md#ics1---keyapi)) -- ICS20 ([TokenAPI](api.md#ics20---tokenapi)) -- ICS21 ([StakingAPI](api.md#ics21---stakingapi)) -- ICS22 ([GovernanceAPI](api.md#ics22---governanceapi)) -- ICS23 ([SlashingAPI](api.md#ics23---slashingapi)) - -Error messages my change and should be only used for display purposes. Error messages should not be -used for determining the error type. - -## ICS0 - TendermintAPI - -Exposes the same functionality as the Tendermint RPC from a full node. It aims to have a very similar API. - -### POST /txs - -- **URL**: `/txs` -- Query Parameters: - - `?return={sync|async|block}`: - - `return=sync`: Waits for the transaction to pass `CheckTx` - - `return=async`: Returns the request immediately after it is received by the server - - `return=block`: waits for for the transaction to be committed in a block -- POST Body: - -```json -{ - "transaction": "string", - "return": "string", -} -``` - -- Returns on success: - -```json -{ - "rest api":"2.0", - "code":200, - "error":"", - "result":{ - "code":0, - "hash":"0D33F2F03A5234F38706E43004489E061AC40A2E", - "data":"", - "log":"" - } -} -``` - -## ICS1 - KeyAPI - -This API exposes all functionality needed for key creation, signing and management. - -### GET /keys - -- **URL**: `/keys` -- **Functionality**: Gets a list of all the keys. -- Returns on success: - -```json -{ - "rest api":"1.0", - "code":200, - "error":"", - "result":{ - "account":[ - { - "name":"monkey", - "address":"cosmos1fedh326uxqlxs8ph9ej7cf854gz7fd5zlym5pd", - "pub_key":"cosmospub1zcjduc3q8s8ha96ry4xc5xvjp9tr9w9p0e5lk5y0rpjs5epsfxs4wmf72x3shvus0t" - }, - { - "name":"test", - "address":"cosmos1thlqhjqw78zvcy0ua4ldj9gnazqzavyw4eske2", - "pub_key":"cosmospub1zcjduc3qyx6hlf825jcnj39adpkaxjer95q7yvy25yhfj3dmqy2ctev0rxmse9cuak" - } - ], - "block_height":5241 - } -} -``` - -### POST /keys - -- **URL**: `/keys` -- **Functionality**: Create a new key. -- POST Body: - -```json -{ - "name": "string", - "password": "string", - "seed": "string", -} -``` - -Returns on success: - -```json -{ - "rest api":"1.0", - "code":200, - "error":"", - "result":{ - "seed":"crime carpet recycle erase simple prepare moral dentist fee cause pitch trigger when velvet animal abandon" - } -} -``` - -### GET /keys/{name} - -- **URL** : `/keys/{name}` -- **Functionality**: Get the information for the specified key. -- Returns on success: - -```json -{ - "rest api":"1.0", - "code":200, - "error":"", - "result":{ - "name":"test", - "address":"cosmos1thlqhjqw78zvcy0ua4ldj9gnazqzavyw4eske2", - "pub_key":"cosmospub1zcjduc3qyx6hlf825jcnj39adpkaxjer95q7yvy25yhfj3dmqy2ctev0rxmse9cuak" - } -} -``` - -### PUT /keys/{name} - -- **URL** : `/keys/{name}` -- **Functionality**: Change the encryption password for the specified key. -- PUT Body: - -```json -{ - "old_password": "string", - "new_password": "string", -} -``` - -- Returns on success: - -```json -{ - "rest api":"2.0", - "code":200, - "error":"", - "result":{} -} -``` - -### DELETE /keys/{name} - -- **URL**: `/keys/{name}` -- **Functionality**: Delete the specified key. -- DELETE Body: - -```json -{ - "password": "string", -} -``` - -- Returns on success: - -```json -{ - "rest api":"1.0", - "code":200, - "error":"", - "result":{} -} -``` - -### POST /keys/{name}/recover - -- **URL**: `/keys/{name}/recover` -- **Functionality**: Recover your key from seed and persist it encrypted with the password. -- POST Body: - -```json -{ - "password": "string", - "seed": "string", -} -``` - -- Returns on success: - -```json -{ - "rest api":"1.0", - "code":200, - "error":"", - "result":{ - "address":"BD607C37147656A507A5A521AA9446EB72B2C907" - } -} -``` - -### GET /auth/accounts/{address} - -- **URL**: `/auth/accounts/{address}` -- **Functionality**: Query the information of an account . -- Returns on success: - -```json -{ - "rest api":"1.0", - "code":200, - "error":"", - "result":{ - "address": "82A57F8575BDFA22F5164C75361A21D2B0E11089", - "public_key": "PubKeyEd25519{A0EEEED3C9CE1A6988DEBFE347635834A1C0EBA0B4BB1125896A7072D22E650D}", - "coins": [ - {"atom": 300}, - {"photon": 15} - ], - "account_number": 1, - "sequence": 7 - } -} -``` - -### POST /auth/tx/sign - -- **URL**: `/auth/tx/sign` -- **Functionality**: Sign a transaction without broadcasting it. -- Returns on success: - -```json -{ - "rest api": "1.0", - "code": 200, - "error": "", - "result": { - "type": "auth/StdTx", - "value": { - "msg": [ - { - "type": "cosmos-sdk/Send", - "value": { - "inputs": [ - { - "address": "cosmos1ql4ekxkujf3xllk8h5ldhhgh4ylpu7kwec6q3d", - "coins": [ - { - "denom": "steak", - "amount": "1" - } - ] - } - ], - "outputs": [ - { - "address": "cosmos1dhyqhg4px33ed3erqymls0hc7q2lxw9hhfwklj", - "coins": [ - { - "denom": "steak", - "amount": "1" - } - ] - } - ] - } - } - ], - "fee": { - "amount": [ - { - "denom": "", - "amount": "0" - } - ], - "gas": "2742" - }, - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A2A/f2IYnrPUMTMqhwN81oas9jurtfcsvxdeLlNw3gGy" - }, - "signature": "MEQCIGVn73y9QLwBa3vmsAD1bs3ygX75Wo+lAFSAUDs431ZPAiBWAf2amyqTCDXE9J87rL9QF9sd5JvVMt7goGSuamPJwg==", - "account_number": "1", - "sequence": "0" - } - ], - "memo": "" - } - } -} -``` - -### POST /auth/tx/broadcast - -- **URL**: `/auth/broadcast` -- **Functionality**: Broadcast a transaction. -- Returns on success: - -```json -{ - "rest api": "1.0", - "code": 200, - "error": "", - "result": - { - "check_tx": { - "log": "Msg 0: ", - "gasWanted": "2742", - "gasUsed": "1002" - }, - "deliver_tx": { - "log": "Msg 0: ", - "gasWanted": "2742", - "gasUsed": "2742", - "tags": [ - { - "key": "c2VuZGVy", - "value": "Y29zbW9zMXdjNTl6ZXU3MmNjdnp5ZWR6ZGE1N3pzcXh2eXZ2Y3poaHBhdDI4" - }, - { - "key": "cmVjaXBpZW50", - "value": "Y29zbW9zMTJ4OTNmY3V2azg3M3o1ejZnejRlNTl2dnlxcXp1eDdzdDcwNWd5" - } - ] - }, - "hash": "784314784503582AC885BD6FB0D2A5B79FF703A7", - "height": "5" - } -} -``` - -## ICS20 - TokenAPI - -The TokenAPI exposes all functionality needed to query account balances and send transactions. - -### GET /bank/balance/{account} - -- **URL**: `/bank/balance/{account}` -- **Functionality**: Query the specified account's balance. -- Returns on success: - -```json -{ - "rest api":"2.0", - "code":200, - "error":"", - "result": { - "atom":1000, - "photon":500, - "ether":20 - } -} -``` - -### POST /bank/transfers - -- **URL**: `/bank/transfers` -- **Functionality**: Create a transfer in the bank module. -- POST Body: - -```json -{ - "amount": [ - { - "denom": "string", - "amount": 64, - } - ], - "name": "string", - "password": "string", - "chain_id": "string", - "account_number": 64, - "sequence": 64, - "gas": 64, -} -``` - -- Returns on success: - -```json -{ - "rest api":"2.0", - "code":200, - "error":"", - "result":{ - "transaction":"TODO:<JSON sign bytes for the transaction>" - } -} -``` - -## ICS21 - StakingAPI - -The StakingAPI exposes all functionality needed for validation and delegation in Proof-of-Stake. - -### GET /stake/delegators/{delegatorAddr} - -- **URL**: `/stake/delegators/{delegatorAddr}` -- **Functionality**: Get all delegations (delegation, undelegation) from a delegator. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result": { - "atom":1000, - "photon":500, - "ether":20 - } -} -``` - -### GET /stake/delegators/{delegatorAddr}/validators - -- **URL**: `/stake/delegators/{delegatorAddr}/validators` -- **Functionality**: Query all validators that a delegator is bonded to. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{} -} -``` - -### GET /stake/delegators/{delegatorAddr}/validators/{validatorAddr} - -- **URL**: `/stake/delegators/{delegatorAddr}/validators/{validatorAddr}` -- **Functionality**: Query a validator that a delegator is bonded to -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{} -} -``` - -### GET /stake/delegators/{delegatorAddr}/txs - -- **URL**: `/stake/delegators/{delegatorAddr}/txs` -- **Functionality**: Get all staking txs (i.e msgs) from a delegator. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` - -### POST /stake/delegators/{delegatorAddr}/delegations - -- **URL**: `/stake/delegators/{delegatorAddr}/delegations` -- **Functionality**: Submit or edit a delegation. - <!--NOTE Should this be a PUT instead of a POST? the code indicates that this is an edit operation--> -- POST Body: - -```json -{ - "name": "string", - "password": "string", - "chain_id": "string", - "account_number": 64, - "sequence": 64, - "gas": 64, - "delegations": [ - { - "delegator_addr": "string", - "validator_addr": "string", - "delegation": { - "denom": "string", - "amount": 1234 - } - } - ], - "begin_unbondings": [ - { - "delegator_addr": "string", - "validator_addr": "string", - "shares": "string", - } - ], - "complete_unbondings": [ - { - "delegator_addr": "string", - "validator_addr": "string", - } - ], - "begin_redelegates": [ - { - "delegator_addr": "string", - "validator_src_addr": "string", - "validator_dst_addr": "string", - "shares": "string", - } - ], - "complete_redelegates": [ - { - "delegator_addr": "string", - "validator_src_addr": "string", - "validator_dst_addr": "string", - } - ] -} - -``` - -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` - -### GET /stake/delegators/{delegatorAddr}/delegations/{validatorAddr} - -- **URL**: `/stake/delegators/{delegatorAddr}/delegations/{validatorAddr}` -- **Functionality**: Query the current delegation status between a delegator and a validator. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` - -### GET /stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr} - -- **URL**: `/stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}` -- **Functionality**: Query all unbonding delegations between a delegator and a validator. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` - -### GET /stake/validators - -- **URL**: `/stake/validators` -- **Functionality**: Get all validator candidates. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` - -### GET /stake/validators/{validatorAddr} - -- **URL**: `/stake/validators/{validatorAddr}` -- **Functionality**: Query the information from a single validator. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` - -### GET /stake/parameters - -- **URL**: `/stake/parameters` -- **Functionality**: Get the current value of staking parameters. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "inflation_rate_change": 1300000000, - "inflation_max": 2000000000, - "inflation_min": 700000000, - "goal_bonded": 6700000000, - "unbonding_time": "72h0m0s", - "max_validators": 100, - "bond_denom": "atom" - } -} -``` - -### GET /stake/pool - -- **URL**: `/stake/pool` -- **Functionality**: Get the current value of the dynamic parameters of the current state (*i.e* `Pool`). -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "loose_tokens": 0, - "bonded_tokens": 0, - "inflation_last_time": "1970-01-01 01:00:00 +0100 CET", - "inflation": 700000000, - "date_last_commission_reset": 0, - "prev_bonded_shares": 0, - } -} -``` - -## ICS22 - GovernanceAPI - -The GovernanceAPI exposes all functionality needed for casting votes on plain text, software upgrades and parameter change proposals. - -### GET /gov/proposals - -- **URL**: `/gov/proposals` -- **Functionality**: Query all submited proposals -- Response on Success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result":{ - "proposals":[ - "TODO" - ] - } -} -``` - -### POST /gov/proposals - -- **URL**: `/gov/proposals` -- **Functionality**: Submit a proposal -- POST Body: - -```js -{ - "base_req": { - // Name of key to use - "name": "string", - // Password for that key - "password": "string", - "chain_id": "string", - "account_number": 64, - "sequence": 64, - "gas": 64 - }, - // Title of the proposal - "title": "string", - // Description of the proposal - "description": "string", - // PlainTextProposal supported now. SoftwareUpgradeProposal and other types may be supported soon - "proposal_type": "string", - // A cosmos address - "proposer": "string", - "initial_deposit": [ - { - "denom": "string", - "amount": 64, - } - ] -} -``` - -- Returns on success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result":{ - "TODO": "TODO", - } -} -``` - -### GET /gov/proposals/{proposal-id} - -- **URL**: `/gov/proposals/{proposal-id}` -- **Functionality**: Query a proposal -- Response on Success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result":{ - "proposal_id": 1, - "title": "Example title", - "description": "a larger description with the details of the proposal", - "proposal_type": "Text", - "proposal_status": "DepositPeriod", - "tally_result": { - "yes": 0, - "abstain": 0, - "no": 0, - "no_with_veto": 0 - }, - "submit_block": 5238512, - "total_deposit": {"atom": 50}, - "voting_start_block": -1 - } -} -``` - -### POST /gov/proposals/{proposal-id}/deposits - -- **URL**: `/gov/proposals/{proposal-id}/deposits` -- **Functionality**: Submit or rise a deposit to a proposal in order to make it active -- POST Body: - -```json -{ - "base_req": { - "name": "string", - "password": "string", - "chain_id": "string", - "account_number": 0, - "sequence": 0, - "gas": "simulate" - }, - "depositer": "string", - "amount": 0, -} -``` - -- Returns on success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result":{ - "TODO": "TODO", - } -} -``` - -### GET /gov/proposals/{proposal-id}/deposits/{address} - -- **URL**: `/gov/proposals/{proposal-id}/deposits/{address}` -- **Functionality**: Query a validator's deposit to submit a proposal -- Returns on success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result":{ - "amount": {"atom": 150}, - "depositer": "cosmos1fedh326uxqlxs8ph9ej7cf854gz7fd5zlym5pd", - "proposal-id": 16 - } -} -``` - -### GET /gov/proposals/{proposal-id}/tally - -- **URL**: `/gov/proposals/{proposal-id}/tally` -- **Functionality**: Get the tally of a given proposal. -- Returns on success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result": { - "yes": 0, - "abstain": 0, - "no": 0, - "no_with_veto": 0 - } -} -``` - - - -### GET /gov/proposals/{proposal-id}/votes - -- **URL**: `/gov/proposals/{proposal-id}/votes` -- **Functionality**: Query all votes from a specific proposal -- Returns on success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result": [ - { - "proposal-id": 1, - "voter": "cosmos1fedh326uxqlxs8ph9ej7cf854gz7fd5zlym5pd", - "option": "no_with_veto" - }, - { - "proposal-id": 1, - "voter": "cosmos1849m9wncrqp6v4tkss6a3j8uzvuv0cp7f75lrq", - "option": "yes" - }, - ] -} -``` - - - -### POST /gov/proposals/{proposal-id}/votes - -- **URL**: `/gov/proposals/{proposal-id}/votes` -- **Functionality**: Vote for a specific proposal -- POST Body: - -```js -{ - "base_req": { - "name": "string", - "password": "string", - "chain_id": "string", - "account_number": 0, - "sequence": 0, - "gas": "simulate" - }, - // A cosmos address - "voter": "string", - // Value of the vote option `Yes`, `No` `Abstain`, `NoWithVeto` - "option": "string", -} -``` - -- Returns on success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result":{ - "TODO": "TODO", - } -} -``` - -### GET /gov/proposals/{proposal-id}/votes/{address} - -- **URL** : `/gov/proposals/{proposal-id}/votes/{address}` -- **Functionality**: Get the current `Option` submited by an address -- Returns on success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result":{ - "proposal-id": 1, - "voter": "cosmos1fedh326uxqlxs8ph9ej7cf854gz7fd5zlym5pd", - "option": "no_with_veto" - } -} -``` - -## ICS23 - SlashingAPI - -The SlashingAPI exposes all functionalities needed to slash (*i.e* penalize) validators and delegators in Proof-of-Stake. The penalization is a fine of the staking coin and jail time, defined by governance parameters. During the jail period, the penalized validator is "jailed". - -### GET /slashing/validator/{validatorAddr}/signing-info - -- **URL**: `/slashing/validator/{validatorAddr}/signing-info` -- **Functionality**: Query the information from a single validator. -- Returns on success: - -```json -{ - "rest api":"2.3", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` - -### POST /slashing/validators/{validatorAddr}/unjail - -- **URL**: `/slashing/validators/{validatorAddr}/unjail` -- **Functionality**: Submit a message to unjail a validator after it has been penalized. -- POST Body: - -```js -{ - // Name of key to use - "name": "string", - // Password for that key - "password": "string", - "chain_id": "string", - "account_number": 64, - "sequence": 64, - "gas": 64, -} -``` - -- Returns on success: - -```json -{ - "rest api":"2.3", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` diff --git a/docs/light/load_balancer.md b/docs/light/load_balancer.md deleted file mode 100644 index 0cc280827ea8..000000000000 --- a/docs/light/load_balancer.md +++ /dev/null @@ -1,203 +0,0 @@ -# Load Balancing Module - WIP - -The LCD will be an important bridge between service providers and cosmos blockchain network. Suppose -a service provider wants to monitor token information for millions of accounts. Then it has to keep -sending a large mount of requests to LCD to query token information. As a result, LCD will send huge -requests to full node to get token information and necessary proof which will cost full node much -computing and bandwidth resource. Too many requests to a single full node may result in some bad -situations: - -```text -1. The full node crash possibility increases. -2. The reply delay increases. -3. The system reliability will decrease. -4. As the full node may belong to other people or associates, they may deny too frequent access from a single client. -``` - -It is very urgent to solve this problems. Here we consider to import load balancing into LCD. By the -help of load balancing, LCD can distribute millions of requests to a set of full nodes. Thus the -load of each full node won't be too heavy and the unavailable full nodes will be wiped out of query -list. In addition, the system reliability will increase. - -## Design - -This module need combine with client to realize the real load balancing. It can embed the -[HTTP Client](https://github.com/tendermint/tendermint/rpc/lib/client/httpclient.go). In other -words,we realise the new httpclient based on `HTTP`. - -```go -type HTTPLoadBalancer struct { - rpcs map[string]*rpcclient.JSONRPCClient - *WSEvents -} -``` - -## The Diagram of LCD RPC WorkFlow with LoadBalance - -![The Diagram of LCD RPC WorkFlow](pics/loadbalanceDiagram.png) - -In the above sequence diagram, application calls the `Request()`, and LCD finally call the -`HTTP.Request()` through the SecureClient `Wrapper`. In every `HTTP.Request()`, `Getclient()` -selects the current working rpcclient by the load balancing algorithm,then run the -`JSONRPCClient.Call()` to request from the Full Node, finally `UpdateClient()` updates the weight of - the current rpcclient according to the status that is returned by the full node. The `GetAddr()` - and `UpdateAddrWeight()` are realized in the load balancing module. - -There are some abilities to do: - -* Add the Remote Address -* Delete the Remote Address -* Update the weights of the addresses - -## Load balancing Strategies - -We can design some strategies like nginx to combine the different load balancing algorithms to get -the final remote. We can also get the status of the remote server to add or delete the addresses and - update weights of the addresses. - -In a word,it can make the entire LCD work more effective in actual conditions. -We are working this module independently in this [Github Repository](https://github.com/MrXJC/GoLoadBalance). - -## Interface And Type - -### Balancer - -This interface `Balancer`is the core of the package. Every load balancing algorithm should realize -it,and it defined two interfaces. - -* `init` initialize the balancer, assigns the variables which `DoBalance` needs. -* `DoBalance` load balance the full node addresses according to the current situation. - -```go -package balance - -type Balancer interface { - init(NodeAddrs) - DoBalance(NodeAddrs) (*NodeAddr,int,error) -} -``` - -### NodeAddr - -* host: ip address -* port: the number of port -* weight: the weight of this full node address,default:1 - -This NodeAddr is the base struct of the address. - -```go -type NodeAddr struct{ - host string - port int - weight int -} - -func (p *NodeAddr) GetHost() string - -func (p *NodeAddr) GetPort() int - -func (p *NodeAddr) GetWeight() int - -func (p *NodeAddr) updateWeight(weight int) -``` - -The `weight` is the important factor that schedules which full node the LCD calls. The weight can be -changed by the information from the full node. So we have the function `updateWegiht`. - -### NodeAddrs - ->in `balance/types.go` - -`NodeAddrs` is the list of the full node address. This is the member variable in the -BalanceManager(`BalancerMgr`). - -```go -type NodeAddrs []*NodeAddr -``` - -## Load Balancing Algorithm - -### Random - ->in `balance/random.go` - -Random algorithm selects a remote address randomly to process the request. The probability of them -being selected is the same. - -### RandomWeight - ->in `balance/random.go` - -RandomWeight Algorithm also selects a remote address randomly to process the request. But the higher -the weight, the greater the probability. - -### RoundRobin - ->in `balance/roundrobin.go` - -RoundRobin Algorithm selects a remote address orderly. Every remote address have the same -probability to be selected. - -### RoundRobinWeight - ->in `balance/roundrobin.go` - -RoundRobinWeight Algorthm selects a remote address orderly. But every remote address have different -probability to be selected which are determined by their weight. - -### Hash - -//TODO - -## Load Balancing Manager - -### BalanceMgr - ->in `balance/manager.go` - -* addrs: the set of the remote full node addresses -* balancers: map the string of balancer name to the specific balancer -* change: record whether the machine reinitialize after the `addrs` changes - -`BalanceMgr` is the manager of many balancer. It is the access of load balancing. Its main function -is to maintain the `NodeAddrs` and to call the specific load balancing algorithm above. - -```go -type BalanceMgr struct{ - addrs NodeAddrs - balancers map[string]Balancer - change map[string]bool -} - -func (p *BalanceMgr) RegisterBalancer(name string,balancer Balancer) - -func (p *BalanceMgr) updateBalancer(name string) - -func (p *BalanceMgr) AddNodeAddr(addr *NodeAddr) - -func (p *BalanceMgr) DeleteNodeAddr(i int) - -func (p *BalanceMgr) UpdateWeightNodeAddr(i int,weight int) - -func (p *BalanceMgr) GetAddr(name string)(*NodeAddr,int,error) { - // if addrs change,update the balancer which we use. - if p.change[name]{ - p.updateBalancer(name) - } - - // get the balancer by name - balancer := p.balancers[name] - - // use the load balancing algorithm - addr,index,err := balancer.DoBalance(p.addrs) - - return addr,index,err -} -``` - -* `RegisterBalancer`: register the basic balancer implementing the `Balancer` interface and initialize them. -* `updateBalancer`: update the specific balancer after the `addrs` change. -* `AddNodeAddr`: add the remote address and set all the values of the `change` to true. -* `DeleteNodeAddr`: delete the remote address and set all the values of the `change` to true. -* `UpdateWeightNodeAddr`: update the weight of the remote address and set all the values of the `change` to true. -* `GetAddr`:select the address by the balancer the `name` decides. diff --git a/docs/light/readme.md b/docs/light/readme.md deleted file mode 100644 index 55e0c72ac3de..000000000000 --- a/docs/light/readme.md +++ /dev/null @@ -1,101 +0,0 @@ -# Cosmos-Sdk Light Client - -## Introduction - -A light client allows clients, such as mobile phones, to receive proofs of the state of the -blockchain from any full node. Light clients do not have to trust any full node, since they are able -to verify any proof they receive and hence full nodes cannot lie about the state of the network. - -A light client can provide the same security as a full node with the minimal requirements on -bandwidth, computing and storage resource. Besides, it can also provide modular functionality -according to users' configuration. These fantastic features allow developers to build fully secure, -efficient and usable mobile apps, websites or any other applications without deploying or -maintaining any full blockchain nodes. - -LCD will be used in the Cosmos Hub, the first Hub in the Cosmos network. - -## Contents - -1. [**Overview**](##Overview) -2. [**Get Started**](getting_started.md) -3. [**API**](api.md) -4. [**Specifications**](specification.md) -4. [**Update API docs To Swagger-UI**](update_API_docs.md) - -## Overview - -### What is a Light Client - -The LCD is split into two separate components. The first component is generic for any Tendermint -based application. It handles the security and connectivity aspects of following the header chain -and verify proofs from full nodes against locally trusted validator set. Furthermore it exposes -exactly the same API as any Tendermint Core node. The second component is specific for the Cosmos -Hub (Gaiad). It works as a query endpoint and exposes the application specific functionality, which -can be arbitrary. All queries against the application state have to go through the query endpoint. -The advantage of the query endpoint is that it can verify the proofs that the application returns. - -### High-Level Architecture - -An application developer that would like to build a third party integration can ship his application -with the LCD for the Cosmos Hub (or any other zone) and only needs to initialise it. Afterwards his -application can interact with the zone as if it was running against a full node. - -![high-level](pics/high-level.png) - -An application developer that wants to build an third party application for the Cosmos Hub (or any -other zone) should build it against it's canonical API. That API is a combination of multiple parts. -All zones have to expose ICS0 (TendermintAPI). Beyond that any zone is free to choose any -combination of module APIs, depending on which modules the state machine uses. The Cosmos Hub will -initially support ICS0 (TendermintAPI), ICS1 (KeyAPI), ICS20 (TokenAPI), ICS21 (StakingAPI) and -ICS22 (GovernanceAPI). - -All applications are expected to only run against the LCD. The LCD is the only piece of software -that offers stability guarantees around the zone API. - -### Comparision - -A full node of ABCI is different from its light client in the following ways: - -|| Full Node | LCD | Description| -|-| ------------- | ----- | -------------- | -| Execute and verify transactions|Yes|No|Full node will execute and verify all transactions while LCD won't| -| Verify and save blocks|Yes|No|Full node will verify and save all blocks while LCD won't| -| Participate consensus| Yes|No|Only when the full node is a validtor, it will participate consensus. LCD nodes never participate consensus| -| Bandwidth cost|Huge|Little|Full node will receive all blocks. if the bandwidth is limited, it will fall behind the main network. What's more, if it happens to be a validator,it will slow down the consensus process. LCD requires little bandwidth. Only when serving local request, it will cost bandwidth| -| Computing resource|Huge|Little|Full node will execute all transactions and verify all blocks which require much computing resource| -| Storage resource|Huge|Little|Full node will save all blocks and ABCI states. LCD just saves validator sets and some checkpoints| -| Power consume|Huge|Little|Full nodes have to be deployed on machines which have high performance and will be running all the time. So power consume will be huge. LCD can be deployed on the same machines as users' applications, or on independent machines but with poor performance. Besides, LCD can be shutdown anytime when necessary. So LCD only consume very little power, even mobile devices can meet the power requirement| -| Provide APIs|All cosmos APIs|Modular APIs|Full node supports all cosmos APIs. LCD provides modular APIs according to users' configuration| -| Secuity level| High|High|Full node will verify all transactions and blocks by itself. LCD can't do this, but it can query any data from other full nodes and verify the data independently. So both full node and LCD don't need to trust any third nodes, they all can achieve high security| - -According to the above table, LCD can meet all users' functionality and security requirements, but -only requires little resource on bandwidth, computing, storage and power. - -## How does LCD achieve high security? - -### Trusted validator set - -The base design philosophy of lcd follows the two rules: - -1. **Doesn't trust any blockchain nodes, including validator nodes and other full nodes** -2. **Only trusts the whole validator set** - -The original trusted validator set should be prepositioned into its trust store, usually this -validator set comes from genesis file. During running time, if LCD detects different validator set, -it will verify it and save new validated validator set to trust store. - -![validator-set-change](pics/validatorSetChange.png) - -### Trust propagation - -From the above section, we come to know how to get trusted validator set and how lcd keeps track of -validator set evolution. Validator set is the foundation of trust, and the trust can propagate to -other blockchain data, such as block and transaction. The propagate architecture is shown as -follows: - -![change-process](pics/trustPropagate.png) - -In general, by trusted validator set, LCD can verify each block commit which contains all pre-commit -data and block header data. Then the block hash, data hash and appHash are trusted. Based on this -and merkle proof, all transactions data and ABCI states can be verified too. Detailed implementation -will be posted on technical specification. diff --git a/docs/light/todo.md b/docs/light/todo.md deleted file mode 100644 index ce1f8508a1f1..000000000000 --- a/docs/light/todo.md +++ /dev/null @@ -1,16 +0,0 @@ -# TODO - -This document is a place to gather all points for future development. - -## API - -* finalise ICS0 - TendermintAPI - * make sure that the explorer and voyager can use it -* add ICS21 - StakingAPI -* add ICS22 - GovernanceAPI -* split Gaia Light into reusable components that other zones can leverage - * it should be possible to register extra standards on the light client - * the setup should be similar to how the app is currently started -* implement Gaia light and the general light client in Rust - * export the API as a C interface - * write thin wrappers around the C interface in JS, Swift and Kotlin/Java diff --git a/docs/lotion/overview.md b/docs/lotion/overview.md deleted file mode 100644 index b28d2c75b41b..000000000000 --- a/docs/lotion/overview.md +++ /dev/null @@ -1,54 +0,0 @@ -# Overview - -Lotion is an alternative to the Cosmos SDK and allows you to create blockchain apps in JavaScript. It aims to make writing new blockchain apps fast and easy by using the ABCI protocol to build on top of Tendermint. Lotion lets you write secure, scalable applications that can easily interoperate with other blockchains on the Cosmos Network using IBC. - -Lotion itself is a tiny framework; its true power comes from the network of small, focused modules built upon it. Adding a fully-featured cryptocurrency to your blockchain, for example, takes only a few lines of code. - -For more information see the [website](https://lotionjs.com) and [GitHub repo](https://github.com/keppel/lotion), for complete documentation which expands on the following example. - -## Building an App - -### Installation - -::: tip -Lotion requires __node v7.6.0__ or higher, and a mac or linux machine. -::: - -``` -$ npm install lotion -``` - -### Simple App - -`app.js`: - -```js -let lotion = require('lotion') - -let app = lotion({ - initialState: { - count: 0 - } -}) - -app.use(function (state, tx) { - if(state.count === tx.nonce) { - state.count++ - } -}) - -app.listen(3000) -``` - -run `node app.js`, then: - -```bash -$ curl http://localhost:3000/state -# { "count": 0 } - -$ curl http://localhost:3000/txs -d '{ "nonce": 0 }' -# { "ok": true } - -$ curl http://localhost:3000/state -# { "count": 1 } -``` diff --git a/docs/reference/baseapp.md b/docs/reference/baseapp.md new file mode 100644 index 000000000000..3eaf1e2f7bf4 --- /dev/null +++ b/docs/reference/baseapp.md @@ -0,0 +1,14 @@ +# baseApp + +`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 most recent state. + +`baseApp` distinguishes between two handler types - the `AnteHandler` and the `MsgHandler`. The former is a global validity check (checking nonces, sigs and sufficient balances to pay fees, +e.g. things that apply to all transaction from all modules), the later is the full state transition function. +During CheckTx the state transition function is only applied to the checkTxState and should return +before any expensive state transitions are run (this is up to each developer). It also needs to return the estimated +gas cost. + +During DeliverTx the state transition function is applied to the blockchain state and the transactions +need to be fully executed. + +BaseApp is responsible for managing the context passed into handlers - it makes the block header available and provides the right stores for CheckTx and DeliverTx. BaseApp is completely agnostic to serialization formats. \ No newline at end of file diff --git a/docs/reference/store/README.md b/docs/reference/store/README.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/docs/resources/delegator-faq.md b/docs/resources/delegator-faq.md deleted file mode 100644 index 0de388a2c4ea..000000000000 --- a/docs/resources/delegator-faq.md +++ /dev/null @@ -1,69 +0,0 @@ -# Delegator FAQ - -## What is a delegator? - -People that cannot, or do not want to run [validator](/validators/overview.md) operations, can still participate in the staking process as delegators. Indeed, validators are not chosen based on their own stake but based on their total stake, which is the sum of their own stake and of the stake that is delegated to them. This is an important property, as it makes delegators a safeguard against validators that exhibit bad behavior. If a validator misbehaves, its delegators will move their Atoms away from it, thereby reducing its stake. Eventually, if a validator's stake falls under the top 100 addresses with highest stake, it will exit the validator set. - -Delegators share the revenue of their validators, but they also share the risks. In terms of revenue, validators and delegators differ in that validators can apply a commission on the revenue that goes to their delegator before it is distributed. This commission is known to delegators beforehand and can only change according to predefined constraints (see section below). In terms of risk, delegators' Atoms can be slashed if their validator misbehaves. For more, see Risks section. - -To become delegators, Atom holders need to send a "Bond transaction" from [Cosmos Voyager](/getting-started/voyager.md) where they specify how many Atoms they want to bond and to which validator. A list of validator candidates will be displayed in Cosmos Voyager. Later, if a delegator wants to unbond part or all of its stake, it needs to send an "Unbond transaction". From there, the delegator will have to wait 3 weeks to retrieve its Atoms. - -## Choosing a validator - -In order to choose their validators, delegators have access to a range of information directly in Cosmos Voyager. - -* Validator's name: Name that was chosen by the validator candidate when it declared candidacy. -* Validator's description: Description that was provided by the validator candidate when it declared candidacy. -* Validator's website: Link to the validator's website. -* Initial commission rate: The commission rate on revenue charged to any delegators (see below for more detail). -* Commission change rate: The maximum daily increase of the validator's commission -* Maximum commission: The maximum commission rate which this validator candidate can charge. -* Minimum self-bond amount: Minimum amount of Atoms the validator candidate need to have bonded at all time. If the validator's self-bonded stake falls below this limit, its entire staking pool (i.e. all its delegators) will unbond. This parameter exists as a safeguard for delegators. Indeed, when a validator misbehaves, part of its total stake gets slashed. This included the validator's own stake as well as its delegators' stake. Thus, a validator with a high amount of self-bonded Atoms has more skin-in-the-game than a validator with a low amount. The minimum self-bond amount parameter guarantees to delegators that a validator will never fall below a certain amount of self-bonded stake, thereby ensuring a minimum level of skin-in-the-game. - -## Directives of delegators - -Being a delegator is not a passive task. Here are the main directives of a delegator: - -* Perform careful due diligence on validators before delegating. If a validator misbehaves, part of its total stake, which includes the stake of its delegators, can be slashed. Delegators should therefore carefully select validators they think will behave correctly. -* Actively monitor their validator after having delegated. Delegators should ensure that the validators they're delegating to behaves correctly, meaning that they have good uptime, do not get hacked and participate in governance. They should also monitor the commission rate that is applied. If a delegator is not satisfied with its validator, it can unbond or switch to another validator. -* Participate in governance. Delegators can and are expected to actively participate in governance. A delegator's voting power is proportional to the size of its stake. If a delegator does not vote, it will inherit the vote of its validator. Delegators therefore act as a counterbalance to their validators. - -## Revenue - -Validators and delegators earn revenue in exchange for their services. This revenue is given in three forms: - -* Block provisions (Atoms): They are paid in newly created Atoms. Block provisions exist to incentivize Atom holders to stake. The yearly inflation rate fluctuates around a target of 2/3 bonded stake. If the total bonded stake is less than 2/3 of the total Atom supply, inflation increases until it reaches 20%. If the total bonded stake is more than 2/3 of the Atom supply, inflation decreases until it reaches 7%. This means that if total bonded stake stays less than 2/3 of the total Atom supply for a prolonged period of time, unbonded Atom holders can expect their Atom value to deflate by 20% per year. -* Block rewards ([Photons](https://blog.cosmos.network/cosmos-fee-token-introducing-the-photon-8a62b2f51aa): They are paid in Photons. Initial distribution of Photons will take the form of a hard spoon of the Ethereum chain. Atom holders will vote on the parameter of this hard spoon, like the date of the snapshot or the initial distribution. Additionally, bonded Atom holders will receive newly created Photons as block rewards. Photons will be distributed at a fixed rate in proportion to each bonded Atom holder's stake. This rate will be decided via governance. -* Transaction fees (various tokens): Each transfer on the Cosmos Hub comes with transactions fees. These fees can be paid in any currency that is whitelisted by the Hub's governance. Fees are distributed to bonded Atom holders in proportion to their stake. The first whitelisted tokens at launch are Atoms and Photons. - -## Validator's commission - -Each validator's staking pool receives revenue in proportion to its total stake. However, before this revenue is distributed to delegators inside the staking pool, the validator can apply a commission. In other words, delegators have to pay a commission to their validators on the revenue they earn. Let us look at a concrete example: - -We consider a validator whose stake (i.e. self-bonded stake + delegated stake) is 10% of the total stake of all validators. This validator has 20% self-bonded stake and applies a commission of 10%. Now let us consider a block with the following revenue: - -* 990 Atoms in block provisions -* 10 Photons in block reward -* 10 Atoms and 90 Photons in transaction fees. - -This amounts to a total of 1000 Atoms and 100 Photons to be distributed among all staking pools. - -Our validator's staking pool represents 10% of the total stake, which means the pool obtains 100 Atoms and 10 Photons. Now let us look at the internal distribution of revenue: - -* Commission = `10% * 80% * 100` Atoms + `10% * 80% * 10` Photons = 8 Atoms + 0.8 Photons -* Validator's revenue = `20% * 100` Atoms + `20% * 10` Photons + Commission = 28 Atoms + 2.8 Photons -* Delegators' total revenue = `80% * 100` Atoms + `20% * 10` Photons - Commission = 72 Atoms + 7.2 Photons - -Then, each delegator in the staking pool can claim its portion of the delegators' total revenue. - -## Risks - -Staking Atoms is not free of risk. First, staked Atoms are locked up, and retrieving them requires a 3 week waiting period called unbonding period. Additionally, if a validator misbehaves, a portion of its total stake can be slashed (i.e. destroyed). This includes the stake of their delegators. - -There are 3 main slashing conditions: - -* Double signing: If someone reports on chain A that a validator signed two blocks at the same height on chain A and chain B, this validator will get slashed on chain A -* Unavailability: If a validator's signature has not been included in the last X blocks, the validator will get slashed by a marginal amount proportional to X. If X is above a certain limit Y, then the validator will get unbonded -* Non-voting: If a validator did not vote on a proposal and once the fault is reported by a someone, its stake will receive a minor slash. - -This is why Atom holders should perform careful due diligence on validators before delegating. It is also important that delegators actively monitor the activity of their validators. If a validator behaves suspiciously or is too often offline, delegators can choose to unbond from it or switch to another validator. Delegators can also mitigate risk by distributing their stake across multiple validators. diff --git a/docs/resources/faq.md b/docs/resources/faq.md deleted file mode 100644 index 91d902dbbeb7..000000000000 --- a/docs/resources/faq.md +++ /dev/null @@ -1,95 +0,0 @@ -# FAQ - -## Overview - -### How do I get Atoms? - -If you participated in the fundraiser, you can check your suggested atom balance at [fundraiser.cosmos.network](https://fundraiser.cosmos.network). -If not, you must wait until the [Cosmos Network launches](/roadmap) and Atoms are traded on exchanges. - -### Are Atoms listed on exchanges? - -No. The Cosmos Network mainnet has not yet launched, which means Atoms are _not_ on exchanges. $CMOS and $ATOM tokens are _not_ Cosmos Network native tokens. - -### How do I participate in the fundraiser? - -The [fundraiser](https://fundraiser.cosmos.network) is closed. The Interchain Foundation raised funds from private individuals and has hosted a public fundraising event on which ended on April 6, 2017. Both $ETH and $BTC were accepted in the fundraiser. The security of the fundraising process has been vetted extremely carefully. - -### What is the initial allocation of Atoms? - -As a public, decentralized network, the allocation of Atoms is decided by those who run the software for the Cosmos Hub. To faciliate a decision, we are creating a Swiss non-profit, the [Interchain Foundation](https://interchain.io), which is responsible for co-ordinating fundraising and allocating funds to get the network off the ground. The foundation will suggest a allocation of Atoms according to the results of the fundraiser. Users will ultimately decide the distribution for themselves when they run the software. - -The Interchain Foundation will suggest that 5% of the Atoms go to its initial donors, 10% go to the Interchain Foundation, 10% go to the company developing most of the software, and the remaining 75% to be distributed according to the results of the private and public fundraisers. - -### What is the team developing the Cosmos Network? - -The Cosmos Network is the first project being funded by the Interchain Foundation. Its development is led primarily by the [Tendermint team](/about/team). - -### What's the difference between Tendermint, the Cosmos Network, and the Cosmos Hub? - -- [Tendermint](https://tendermint.com) is a general purpose blockchain engine that uses a Byzantine-fault tolerant consensus protocol and allows applications to be written in any programming language. -- The Cosmos Network is a heterogenous network of Proof-of-Stake blockchains that can interoperate with one-another. -- The Cosmos Hub is the first Proof-of-Stake blockchain to be launched by the Cosmos Network; it uses Tendermint consensus, contains a built in governance protocol, and serves as co-ordinater for interoperability between other blockchains. -- Atoms: The native cryptocurrency on the Cosmos Hub. Atoms are necessary for participating in the consensus protocol and transacting on the network. - -### When will the Cosmos Network launch? - -Please check [our roadmap](https://cosmos.network/roadmap). - -### What is the utility of Atoms? - -Public, decentralized networks require high levels of security and spam-prevention that are best achieved by economic means: participants in the consensus must incur some economic cost, and all transactions processed by the network must pay a fee. Since we want to use Proof-of-Stake validators instead of Proof-of-Work miners, we require validators of the Cosmos Hub to make a large security deposit in Atoms - if they misbehave, their Atoms are revoked by the protocol! - -The more Atoms in security deposits, the more stake on the line; the more skin-in-the-game; the greater the economic security. In this sense, the Atoms act like virtual miners. - -To achieve spam-prevention, all transactions on the Cosmos Hub must pay a fee in Atoms. The fee may be proportional to the amount of computation required by the transaction, similar to Ethereum's concept of "gas". The fees are collected by the validators and distributed proportionately to the Atoms held in security deposits. - -## Interoperability - -### What's an IBC packet? - -[IBC packets](https://blog.cosmos.network/developer-deep-dive-cosmos-ibc-5855aaf183fe) are packets of data that one blockchain wishes to send to another blockchain. But instead of literally sending a packet of bytes via the TCP/IP or UDP/IP protocol (which is designed for singular, physical, machines), IBC packets require cryptographic proof-of-existence. Since no single node or validator has the authority to speak on behalf of the entire blockchain, and, since we don't want to rely on the integrity of the IP internet infrastructure, instead we rely on a cryptographic proof of a blockchain hash commit (+2/3 of signatures for that blockchain hash) along with a Merkle-proof from the aforementioned blockhash to a packet in the blockchain's "application state", which proves that the blockchain validators agreed to publish this packet of information. So, anyone who sees an IBC packet (regardless of the source of this data) can verify its integrity. - -### How does one exchange currencies in this system? - -For tokens outside the Cosmos system, they can only be introduced via pegged -derivatives. Read about interoperating with existing blockchains here: [Peggy](https://blog.cosmos.network/the-internet-of-blockchains-how-cosmos-does-interoperability-starting-with-the-ethereum-peg-zone-8744d4d2bc3f). - -``` - _ peg smart contract - / - [ Ethereum ] <--> [ EtherCosmos Peg Zone ] <-IBC-> [ Cosmos Hub ] <-IBC-> (Bitcoin) [ PoW/Casper ] - [ Tendermint ] [ Tendermint ] <-IBC-> (exchange) -``` - -### How does Cosmos manage governance? - -In Cosmos, the stakeholders are well defined, as is the prior social contract. Ethereum had a hard time with the fork because they had to ask the ether holders as well as the miners, but the ether holders had no prior social contract or obligation to partake in governance, so no quorum could be reached in time. Asking the miners is necessary to ensure that the hard-fork will have support, but after a while they tend to simply follow the money and incentives. - -Cosmos is different because instead of anonymous miners we have social contract bound validators and delegators who have stake, and, they have the obligation to partake in governance. - -## Validators - -### What is the maximum number of validators in Cosmos? What about nodes? - -We will start with 100 validators. Anyone else can be a node. To start, the validators will be the same across all shards - they will run the shards concurrently. Over time, these restrictions will be loosened. Misbehaviour in the consensus on any shard will result in security deposits being revoked. - -### What will be the process for abandoning validators that misbehave? - -If a validator misbehaves on its own by double-signing at the same height & round, then the evidence is very short and simple -- it's just the two conflicting votes. This evidence can be included in the the Cosmos Hub as a Slash transaction, and the validator will immediately become inactive and slashed after the Slash transaction gets committed. - -If there is a zone fork, either of the Cosmos Hub or any of the zones, the two conflicting commits also constitute evidence. This is a much more complicated data structure. It is guaranteed to slash at least 1/3 of the validators' atoms for that zone. - -### What's the difference between a Delegator and a Validator? - -A [validator](/staking/validators) has an active key involved in signing votes in the consensus protocol. A validator must also have some Atoms in a security deposit. Since there will only be a limitted number of validators, [other Atom holders can delegate](/staking/delegators) to the validators, thereby contributing to the economic security of the system by putting their funds on the line if the validator misbehaves. In return, they earn a share of the transaction fees and any inflationary rewards. - -### Can delegators also be validators? - -Delegators are never validators. If a validator wishes to delegate, they need to do so with their free and unbonded Atoms. - -### How are validator voting powers determined and changed? - -Validators are initially determined according to a public vote among Atom holders to be carried out before the launch of the Cosmos Hub. Atom holders delegate to the various candidates, and the top 100 candidates will be the initial validators. Once [the Hub launches](/roadmap), the vote will be a continuous process where users shuffle around their delegated Atoms, thereby changing the validator set. - -Part of the purpose of the fundraiser is to distribute Atoms across a wide variety of individuals and organizations so that the validator set will be sufficiently decentralized for a robust network. In the event of attacks or mishaps, the blockchain may need to purge bad actors through socially co-ordinated hard-forks. The ability to account for misbehaviour and co-ordinate hardforks helps make the system antifragile. diff --git a/docs/resources/whitepaper-ko.md b/docs/resources/whitepaper-ko.md deleted file mode 100644 index f87460c4ca40..000000000000 --- a/docs/resources/whitepaper-ko.md +++ /dev/null @@ -1,756 +0,0 @@ -# 코스모스 - -분산원장 네트워크 - -**저자:** - -Jae Kwon <mailto:jae@tendermint.com><br> -Ethan Buchman <mailto:ethan@tendermint.com> - -For discussions, [join our community chat](https://riot.im/app/#/room/#cosmos:matrix.org)! - -**번역 및 제작:** - -한승환, 이승민, 김기현, 윤승완, HJ Kim(금마), 김석현 - -\[[toc]] - -## 서 론 (Introduction) - -오픈 소스 생태계, 탈중앙화 파일 공유, 퍼블릭 암호화폐 등이 연달아 성공하면서 탈중앙화 인터넷 프로토콜들이 사회경제적 인프라를 근본적으로 개선하는데 사용될 수 있다는 것을 알게 되었다. 비트코인[\[1\]](https://bitcoin.org/bitcoin.pdf)(암호화폐)과 제로캐시[\[2\]](http://zerocash-project.org/paper)(프라이버시용 암호화폐) 같은 특화된 블록체인 어플리케이션들이 있었고, Augur(예측 시장)와 TheDAO[\[4\]](https://download.slock.it/public/DAO/WhitePaper.pdf)(투자 클럽) 같은 무수한 분산 애플리케이션들을 가진 이더리움(Ethereum)[\[3\]](https://github.com/ethereum/wiki/wiki/White-Paper) 같은 범용 스마트 컨트랙트 플랫폼들이 있었다. - -그러나 지금까지 이런 블록체인들은 엄청난 에너지 비효율, 형편없거나 제한적인 성능, 미성숙한 거버넌스 메커니즘 등을 비롯해 많은 결함들을 경험해왔다. 세그위트(Segregated-Witness)[\[5\]](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)와 비트코인 NG(BitconNG)[\[6\]](https://arxiv.org/pdf/1510.02037v2.pdf) 같은 비트코인 이체 처리량을 확장하기 위한 여러 제안들이 있었지만, 완전 감사가능성(complete auditability)을 희생하지 않는 이상, 여전히 단일한 기계의 용량에 제한을 받는 수직적 스케일링 솔루션이다. 라이트닝 네트워크(Lightning Network)[\[7\]](https://lightning.network/lightning-network-paper-DRAFT-0.5.pdf)는 원장에서 특정한 이체들을 완전히 제외하여 비트코인의 확장성에 도움을 주며, 소액결제와 프라이버시 보호형 지급 플랫폼(privacy preserving payment rails)에 적합하지만, 보다 범용적인 확장에는 적합하지 않을 수 있다. - -가장 이상적인 해결책은 다수의 병렬 블록체인들이 각자의 보안 특성을 유지하면서 호환되는 것이다. 하지만 작업증명의 경우, 이것이 불가능은 아니더라도 상당히 어렵다는 것이 증명되었다. 예를 들어, 병합채굴(merged-mining)은 부모체인의 보안을 위해 수행된 작업을 자녀체인에서 재사용하는 것을 허용하지만, 이체들은 여전히 차례대로 각 노드에 의해 검증돼야 하고, 부모체인의 해시파워 대부분이 자녀체인에서도 병합채굴을 하고 있지 않을 경우, 병합 채굴된(merged mined) 블록체인이 공격에 취약해진다. 우리는 [대안 블록체인 네트워크 아키텍처들(alternative blockchain network architectures)](http://vukolic.com/iNetSec_2015.pdf)에 대한 학술적 고찰을 하였고, 그 중 일부에 대해서는 요약과 결점들을 아래의 [관련 연구(Related Work)](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#related-work)에서 다루었다. - -우리는 이 모든 문제들을 해결하는 새로운 블록체인 네트워크 아키텍처인 코스모스(Cosmos)를 제시하고자 한다. 코스모스는 존(zone)이라고 불리는 많은 독립된 블록체인들의 네트워크이다. 존은 텐더민트 코어(Tendermint Core)[\[8\]](https://github.com/tendermint/tendermint/wiki)를 통해 작동하는데, 텐더민트 코어는 일관적이고 안전한 고성능의 [유사](https://blog.cosmos.network/tendermint-vs-pbft-12e9f294c9ab?gi=7d54da26ffe)[PBFT](https://blog.cosmos.network/tendermint-vs-pbft-12e9f294c9ab?gi=c320a745ea23) 합의 엔진을 제공하며, 악의적인 공격자들에게 엄격한 [포크 책임(fork-accountability)](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#fork-accountability)을 묻는다. 텐더민트 코어의 BFT 합의 알고리즘은 공개형 지분증명 블록체인들의 확장성을 확보하는데 적합하다. - -코스모스 상의 첫 번째 존을 코스모스 허브(Cosmos Hub)라고 부른다. 코스모스 허브는 단순한 거버넌스 메커니즘을 통해 네트워크가 적응하고 업그레이드 할 수 있게 하는 ‘다중 자산 지분증명 암호화폐(multi-asset proof-of-stake cryptocurrency)’이다. 그에 더해서, 코스모스 허브는 다른 존들을 연결함으로 확장될 수 있다. - -코스모스 네트워크의 허브와 존들은 블록체인 간 통신(IBC: inter-blockchain communication) 프로토콜을 통하여 상호 통신한다. 이 프로토콜은 블록체인들을 위한 일종의 가상 UDP 또는 TCP 역할을 한다. 토큰들은 존들 간 거래소의 유동성(exchange liquidity)없이도 안전하고 신속하게 하나의 존에서 다른 존으로 전송될 수 있다. 존들 간 토큰 전송은 코스모스 허브를 통과하며, 코스모스 허브는 각 존이 보유한 토큰 총액을 추적한다. 허브는 각 존을 다른 존들의 실패(failure)로부터 격리한다. 또한 누구든지 코스모스 허브에 새로운 존을 연결할 수 있기 때문에, 존들은 앞으로의 새로운 블록체인 혁신과의 장래 호환성도 확보한다. - -## 텐더민트 (Tendermint) - -본 절에서는 텐더민트 합의 프로토콜과 이를 통한 애플리케이션을 구축을 위해 사용되는 인터페이스를 다룬다. 상세한 내용은 [부록(appendix)](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#appendix)참조 - -### 검증인 (Validators) - -전형적인 비잔틴 장애 허용 알고리즘들(BFT: Byzantine fault-tolerant algorithms)에서는 각 노드가 동일한 가중치를 갖는다. 텐더민트에서는 노드들이 ‘0 이상’의 *투표권(voting power)*을 가지며, 양(+)의 투표권을 갖는 노드들을 *검증인\*\*(validators)*라고 부른다. 검증인들은 다음 블록에 동의하는 암호서명(cryptographic signature), 즉 *투표(vote)*를 전파(broadcast)함으로써 합의 프로토콜에 참여한다. - -검증인(validator)들의 투표권은 제네시스(genesis) 당시에 결정되거나 블록체인에 의해 결정론적으로(deterministically) 변경되기도 한다. 예를 들어, 코스모스 허브와 같은 지분증명 애플리케이션에서는 투표권이 담보물로서 본딩된(bonded) 지분 토큰(staking token)의 양에 의해 결정될 수도 있다. - -_주: ⅔, ⅓ 과 같은 분수들은 모든 **검증인**들이 동등한 가중치를 갖지 않는 한, **검증인**들의 총수가 결코 아니라 총 투표권**에서 차지하는** **비중**을 가리킨다. 주: +⅔ 는 " ⅔ 초과"를 의미하**며**, ⅓+은 "⅓ 이상"을 의미한다._ - -### 합의 (Consensus) - -텐더민트는 DLS 합의 알고리즘에서 파생되는, 부분적 동기 BFT 합의 프로토콜(synchronous BFT consensus protocol)이다[\[20\]](http://groups.csail.mit.edu/tds/papers/Lynch/jacm88.pdf). 텐더민트는 단순함, 성능 그리고 [포크 책임(fork-accountability)](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#fork-accountability)이 특징이다. 이 프로토콜은 공개되있고 고정된 세트의 검증인들(a fixed, known set of validators)을 요구하는데, 각 검증인 공개 키에 의해 식별된다. 검증인들은 한 번에 하나의 블록, 즉 이체 목록(a list of transactions)에 대해 합의를 시도한다. 블록에 대한 합의는 라운드(round)를 통해 진행된다. 각 라운드에는 블록을 제안하는 라운드 리더(round-leader), 즉 제안자(proposer)가 있다. 그 다음, 검증인들은 제안된 블록을 받아들일 것인지 또는 다음 라운드로 넘어갈 것인지에 대한 단계별 투표를 진행한다. 각 라운드의 제안자는 검증인 리스트(ordered list)에서 투표권에 비례해 결정론적으로 선택된다. - -프로토콜에 대한 세부정보는 [여기](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm)를 참조 - -텐더민트 보안은 압도적 다수결(+⅔)과 잠금 메커니즘(locking mechanism)을 통해 최적의 비잔틴 장애 허용을 사용하는 것으로부터 유래한다. 다음을 보장한다. - -- 안전성을 저해(violation of safety)하기 위해서는 ⅓+의 투표권이 비잔틴이어야 하고, ‘2 개를 초과하는 값들’이 커밋(commit)되어야 한다. - -- 프로토콜은 임의의 검증인들 세트가 안전성을 저해하는 데 성공하거나 또는 시도만 하더라도, 이들을 식별할 수 있다. 충돌하는 블록들에 찬성 투표 하거나 정당하지 않은 투표들을 전파하는 이들 모두가 포함된다. - -텐더민트는 강력한 보장들에 더해 성능면에서도 탁월하다. 5 개 대륙 7 개 데이터센터에 분산되어 있는 64 개 노드의 대중품 클라우드(commodity cloud)를 기준으로, 텐더민트 합의는 약 1~2 초의 커밋 지연속도(commit latencies)와 함께 초당 수천 개의 트랜잭션(transaction)를 처리한다. 특히, 검증인들이 실패하거나 악의적으로 조작된 투표를 전파하는 가혹한 공격 상황(adversarial conditions)에서도 초당 1,000 번 수준의 트랜잭션 성능은 가볍게 능가한다. _아래 그림 참조_ - -![Figure of Tendermint throughput performance](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/images/tendermint_throughput_blocksize.png) - -### 라이트 클라이언트 (Light Clients) - -텐더민트 합의 알고리즘의 특징은 단순화된 라이트 클라이언트 보안인데, 모바일 및 사물인터넷에 사용되기도 적합하다. 비트코인 라이트 클라이언트는 ‘블록헤더 체인’들을 동기화(sync)하여 가장 많은 작업증명을 가진 체인을 찾아야 하지만, 텐더민트 라이트 클라이언트는 ‘검증인 세트’의 변경을 추적해서, 최신 블록에서 +⅔ 프리커밋(PreCommit)을 검증하기만 하면 최신 상태를 결정할 수 있다. - -간결한 라이트 클라이언트 증명들은 [블록체인 간 통신(inter-blockchain communication)](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#/h)도 가능하게 한다. - -### 공격 방지 (Preventing Attacks) - -텐더민트는 ‘[long-range-nothing-at-stake double spend](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#/h)’나 [검열(censorship)](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#/h) 등을 방지하기 위한 다양한 보호장치를 가지고 있다. 세부사항은 [부록(appendix)](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#/h)참조 - -### TMSP - -텐더민트 합의 알고리즘은 텐더민트 코어(Tendermint Core)라는 프로그램에서 구현된다. 텐더민트 코어는 모든 결정적 블랙박스 애플리케이션(deterministic blackbox application)을 분산 복제 블록체인(distributedly replicated blockchain)으로 전환시킬 수 있는 어플리케이션 어그노스틱(application-agnostic) "합의 엔진"이다. 아파치 웹 서버나 Nginx 가 CGI 나 FastCGI 를 통해 워드프레스 애플리케이션에 접속하는 것처럼, 텐더민트 코어는 텐더민트 소켓 프로토콜(TMSP: Tendermint Socket Protocol)을 통해 블록체인 애플리케이션들과 연결된다. - -비트코인을 예로 들어보자면, 비트코인은 각 노드가 완전 감사되는 UTXO(fully audited Unspent Transaction Output) 데이터베이스를 유지하는 암호화폐 블록체인이다. 만일 누군가 비트코인과 같은 시스템(Bitcoin-like system)을 TMSP 상에 만들고자 하면, ‘**텐더민트 코어**’는 아래와 같은 부분들을 책임진다. - -- 노드 간 블록 및 이체 공유 - -- 정규(canomical)/변경배제(immutable) 이체순서의 확립(블록체인) - -반면, ‘**TMSP 의 어플리케이션**’은 다음을 책임진다. - -- UTXO 데이터베이스 유지 - -- 이체의 암호서명 검증 - -- 존재하지 않는 이체를 지불방지 - -- 클라이언트들의 UTXO 데이터베이스에 대한 query 허용 - -텐더민트는 애플리케이션 프로세스와 합의 프로세스 사이에 매우 단순한 API 를 제공함으로써 블록체인 설계를 분해할 수 있다. - -## 코스모스 개요 (Cosmos Overview) - -코스모스는 독립 병렬 블록체인들의 네트워크이며, 해당 블록체인들은 각기 텐더민트와 같은 전형적인 BFT 합의 알고리즘에 기반한다[1]. - -코스모스의 최초 블록체인이 곧 코스모스 허브가 된다. 코스모스 허브는 블록체인 간 통신(IBC) 프로토콜을 통해 다른 많은 블록체인들(즉, _존들_)과 연결된다. 코스모스 허브는 수많은 유형의 토큰을 추적하고, 연결된 각 존의 토큰 총 수를 기록한다. 모든 존들 간의 코인 전송은 코스모스 허브를 거치기 때문에 토큰은 존들 간의 유동 거래소(liquid exchange) 방식 없이 - -이 아키텍처는 어플리케이션 상호운용성, 확장성 그리고 무결절 업그레이드 가능성(seamless upgradability)을 포함한 블록체인이 직면한 많은 문제들을 해결한다. 예를 들면, Bitcoind, Go-Ethereum, 크립토노트(CryptoNote), ZCash 등을 포함한 임의의 블록체인 시스템으로부터 파생된 존들이 코스모스 허브에 연결될 수 있다. 이러한 존들을 통해 코스모스는 전역 이체(global transaction) 요구를 충족시킬 때까지 무한히 확장할 수 있다. 그리고 존들은 탈중앙화 거래소(distributed exchange)에도 매우 적합하며, 실제로 지원될 것이다. - -코스모스는 단일 분산 원장에 불과한 것이 아니며, 코스모스 허브도 폐쇄형 플랫폼(walled garden, 울타리 친 정원)이 아니다. 오히려 코스모스는 미래 금융 시스템의 새로운 기초가 될 수 있는 개방형 분산원장 네트워크를 위한 프로토콜이며, 암호학 원리, 잘 설계된 경제(sound economics), 합의 이론, 투명성, 책임(accountability) 등의 원칙에 기반하고 있다. - -### Tendermint-BFT DPoS - -코스모스 허브는 텐더민트 BFT 합의 알고리즘에 기반하는, 코스모스 네트워크 내 최초의 퍼블릭 블록체인이다. 텐더민트 오픈 소스 프로젝트는 비트코인의 작업증명(PoW) 합의 알고리즘이 지닌 속도, 확장성, 환경 등의 문제를 처리하기 위해서 2014 년에 탄생했다. 1988 년 MIT 에서 개발한 증명된 BFT 알고리즘들을 활용하고 개선해서, 텐더민트 팀은 제 1 세대 지분증명(PoS) 암호화폐들이 겪은 Nothing-at-stake 문제를 해결하는 지분증명 암호화폐를 최초로 개념증명하였다. - -오늘날, 대부분의 비트코인 모바일 지갑들은 이체 검증(transaction verification)을 제공하는 신뢰할 수 있는 서버(trusted server)를 이용한다. 이는 PoS 가 여러번의 이체확인(confirmation)을 기다려야만, 이체확정된 것으로 간주되기 때문이다. 이중지불(double-spend) 공격이 이미 코인베이스(CoinBase) 같은 서비스들에서 일어난 바 있다. - -다른 블록체인 합의 시스템들과는 달리 텐더민트는 즉각적이고 안전한(provably-secure) 모바일-클라이언트 지불 검증을 제공한다. 텐더민트는 분기(fork)할 수 없도록 설계되었기 때문에, 모바일 지갑들에서 즉시 이체확인이 가능하며 실제로 신뢰 없는(trustless) 지불을 실현한다. 이는 IoT 관련 어플리케이션들에도 큰 영향을 줄 것이다. - -(비트코인 채굴자들과 유사한 역할이지만 그 대신에 암호서명을 통해 투표하는) 코스모스의 검증인들은 블록을 커밋 할 책임이 있는 안전한 전용 머신들이어야 한다. 검증인이 아닌 이들(non-validators)은 ‘아톰’이라고 부르는 지분 토큰(staking tokens)을 임의의 검증인에게 위임하여 일정한 블록 수수료(block fees)와 아톰 보상(atom rewards)을 얻을 수는 있으나, 위임 검증인(delegate validator)이 해킹당하거나 프로토콜을 위반할 경우, 처벌을 받게 되는 리스크가 있다. 텐터민트 BFT 합의의 입증된 안정성 보장과 (검증인 및 위임자) 주주들의 담보 보증(collateral deposit)은 노드들에 심지어는 라이트 클라이언트들에게 증명 가능하고 정량화 가능한 보안성(provable, quantifiable security)을 제공한다. - -### 거버넌스 (Governance) - -‘공개형 분산원장’은 헌법(constitution)과 거버넌스 시스템(governance system)을 가져야 한다. 비트코인은 업그레이드 등의 조정을 위해 일정 부분 비트코인 재단(Bitcoin Foundation)이나 채굴에 의존하지만, 진행속도가 느리다. 이더리움의 경우에는 The DAO 해킹의 처리를 위한 하드포크 후 ETH 와 ETC 로 분할되었는데, 이는 사전에 그런 의사결정을 하기 위한 사회적 계약이나 메커니즘이 존재하지 않았기 때문이다. - -코스모스 허브의 검증인과 위임자는 코스모스 허브의 정책들을 위한 ‘코드가 아니라 평이한 언어로 된 헌법(규칙)’을 표결에 부쳐 수정할 뿐 아니라 ‘블록 가스 한계(block gas limit)’ 같은 시스템의 사전에 설정된 제한들을 업그레이드를 통해 자동으로 변경하는 프로포잘(proposals)도 표결에 부칠 수 있다. 헌법은 The DAO 사건 같은 절도 및 버그관련 문제들의 이해관계자들이 응집(cohesion)할 수 있도록 도우며, 보다 신속하고 깔끔한 해결책이 나올 수 있도록 한다. - -각 존 역시 자체적인 헌법과 거버넌스 메커니즘을 가질 수 있다. 예를 들어, 코스모스 허브가 변경 배제(immutability) -코스모스 허브의 노드 실행 버그를 제외하고는 롤백 금지(no roll-backs)와 같은- 헌법을 적용한 경우라면, 각 존은 절도와 버그를 위한 롤백 관련 자체 정책을 설정할 수 있다. - -코스모스 네트워크는 상이한 정책의 존들 간 상호운용성을 확보함으로 사용자들에게 궁극적 자유와 실험의 기회를 제공한다. - -## 허브와 존 (The Hub and Zones) - -여기서는 탈중앙화(decentralization)와 확장성(scalability)의 새로운 모델을 설명한다. 코스모스는 텐더민트에 기반한 복수의 블록체인들의 네트워크이다. 기존의 프로포잘들이 전역적 이체 순서(total global transaction ordering)를 가진 ‘단일 블록체인’을 목표로 하는 반면에, 코스모스는 수많은 블록체인들이 상호 병행 실행(run concurrently)하는 동시에 상호운용성을 확보하도록 한다. - -기본적으로 코스모스 허브는 ‘존(zone)’이라고 부르는 여러 독립된 블록체인들을 관리한다(‘존’은 ‘샤드(shard)’로도 불림, 데이터베이스의 스케일링 기법인 ‘샤딩(sharding)’참조). 존들은 최근의 블록 커밋들을 끊임없이 허브로 전송하며, 허브는 이를 통해 항상 최신 상태를 유지한다. 마찬가지로, 각 존도 허브의 상태를 제공받는다(다만 허브를 통한 간접적인 경우를 제외하고는 존들끼리는 서로 이러한 작업을 하지 않는다). 존들은 정보의 송신과 수신의 증거인 머클 증명(Merkle-proofs)을 포스팅함으로 정보의 패킷들을 교환한다. 이 메커니즘을 ‘블록체인 간 통신(inter-blockchain communication)’ 또는 IBC 라고 칭한다. - -![Figure of hub and zones acknowledgement](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/images/hub_and_zones.png) - -어느 존이든 스스로 허브가 되어 비순환 그래프(acyclic graph)를 형성할 수 있으나 명료성을 위해 우리는 단지 하나의 허브와 많은 비-허브 존들이 존재하는 단순한 구성만 설명할 것이다. - -### 허브 (The Hub) - -코스모스 허브는 멀티자산 분산 원장(multi-asset distributed ledger)을 관리하는 블록체인인데, 여기서 토큰은 개인 사용자들이나 존 자체가 보유할 수 있다. 이 토큰들은 ‘코인 패킷(coin packet)’이라 부르는 특수 IBC 패킷을 통해 하나의 존에서 다른 존으로 이동할 수 있다. 허브는 존들에 걸쳐 각 토큰 총액의 전역적 불변성(global invariance)을 보존한다. IBC 코인 패킷 이체는 반드시 송신자, 허브 및 수신자 블록체인에 의해 커밋 되어야 한다. - -코스모스 허브는 전체 시스템을 위한 토큰의 ‘중앙 원장’ 역할을 하기 때문에, 보안이 무엇보다도 중요하다. 각 존은 단 4 명(혹은 BFT 합의가 필요하지 않을 경우에는 훨씬 더 적은 수)의 검증인으로도 안전해지는 텐더민트 블록체인일 수 있는 반면, 허브는 대륙 네트워크 분할(continental network partition)이나 국가 주도적 공격(nation-state sponsored attack) 같은 가장 심각한 공격 시나리오들에도 견딜 수 있어야 한다. 따라서 전역적으로 탈중앙화된 검증인 세트로 보안되어야 한다. - -### 존 (The Zones) - -코스모스 존은 허브와 IBC 메시지를 교환하는 독립 블록체인이다. 허브의 관점에서, 존은 IBC 패킷을 사용하여 토큰을 송수신할 수 있는 ‘멀티자산 동적구성원 멀티시그너처 계정(multi-asset dynamic-membership multi-signature account)’이다. 암호화폐 계정과 마찬가지로 존은 보유 중인 토큰보다 더 많은 토큰을 전송할 수 없지만, 토큰을 보유한 다른 존으로부터 토큰을 수신할 수는 있다. 존은 토큰 유형들의 ‘소스(source)’로 지정되어, 해당 토큰을 추가 공급할 수도 있다. - -코스모스 허브의 아톰들은 허브의 검증인이 아니라, ‘허브에 연결된 존의 검증인’들에 의해 스테이킹될 수도 있다. 이 존들에 대한 이중지불 공격은 텐더민트의 포크 책임인 아톰의 대폭 감소(slashing)라는 결과를 가져오게 되는 반면, 투표권의 +⅔ 이 비잔틴인 존이라면 무효한 상태(invalid state)를 커밋할 수 있다. 코스모스 허브는 다른 존들에 커밋 된 이체들은 검증하거나 실행하지 않으므로, 자신이 신뢰하는 존으로 토큰을 전송하는 것은 사용자들의 책임이다. 장래에는 코스모스 허브의 거버넌스 시스템이 존 실패(failures)를 위한 허브 개선 프로포잘을 만들수도 있다. 예를 들면, 공격이 탐지될 때, 일부(또는 전체) 존들의 아웃바운드 토큰 전송을 억제함으로 ‘긴급 서킷 브레이킹(토큰 전송 일시중단)’을 할 수도 있다. - -## 블록체인 간 커뮤니케이션 (Inter-blockchain Communication (IBC)) - -여기서는 허브와 존 간의 상호 통신방법을 설명한다. 예를 들어, ‘존 1(Zone 1)’, ‘존 2(Zone 2)’와 ‘허브(Hub)’의 세 블록체인이 있고, ‘존 1’이 ‘허브’를 통과하여 ‘존 2’로 가는 패킷을 만들고자 한다. 패킷이 하나의 블록체인에서 다른 블록체인으로 이동하기 위해, 송신 체인(sending chain)이 수신지로 가는 패킷을 발행했다는 증거를 수신 체인(receiving chain)에 포스팅한다. 수신 체인이 이 증거를 확인하기 위해서는 송신자의 블록 헤더(block headers) 정보를 알아야 한다. 이 메커니즘은 사이드체인(sidechain)이 사용하는 메커니즘과 유사한데, 상호 작용하는 두 체인이 ‘존재 증명 데이터그램의 양방향 스트림(a bidirectional stream of proof-of-existence datagram)’을 통해 서로를 ‘인식’해야한다 (이체). - -IBC 프로토콜을 두 가지 이체 방식을 통해 정의할 수 있다: 블록체인이 최근의 블록 해시를 임의의 관측자에게 증명하는 IBCBlockCommitTx 이체, 그리고 블록체인이 송신자의 어플리케이션이 패킷을 머클증명을 통해 최근 블록해시로 전파했다는 것을 증명하는 IBCPacketTx 이체 - -IBC 구동방식을 IBCBlockCommitTx 와 IBCPacketTx 로 분할함으로, 수신체인의 내부 수수료 시장 메커니즘(native fee market-mechanism)이 커밋될(승인될) 패킷을 결정하도록 하며, 송신체인이 얼마나 많은 아웃바운드 패킷들을 허용할 지 자유롭게 정할 수 있도록 한다. - -![Figure of Zone1, Zone2, and Hub IBC without acknowledgement](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_without_ack.png) - -위의 예에서, ‘존 1’의 블록해시를 (또는 ‘존 2’ 상의 ‘허브’의 블록 해시를) 업데이트 하기 위해서는, IBCBlockCommitTx 이체가 ‘존 1’의 블록해시와 함께 ‘허브’에 (또는 ‘허브’의 블록해시와 함께 ‘존 2’에) 반드시 포스팅 되어야 한다. - -_두 IBC 이체 유형에 관한 보다 상세한 정보는 [IBCBlockCommitTx](#ibcblockcommittx) 와 [IBCPacketTx](#ibcpacketcommit) 를 참조._ - -## 사용사례 (Use Cases) - -### 분산거래소 (Distributed Exchange) - -비트코인이 원장을 복사하고 분산하여 더 안전해지는 것처럼, 거래소도 블록체인 상에 올림으로 내/외부의 해킹으로부터 더욱 안전해질 수 있다. - -현재 커뮤니티에서 칭하는 탈중앙화 거래소(decentralized exchange)는 ‘아토믹 크로스체인(Atomic Cross-Chain, AXC)’ 이체에 기반하고 있다. 이를 통해 두 명의 유저는 두 개의 서로 다른 원장(체인)에서 이체를 일으키고 해당 이체는 동시에 각 원장에 기록되거나 또는 그 어떤 원장에도 기록되지 않는다. 예를 들어, AXC 를 이용하면 두 명의 유저가 서로의 이더와 비트코인(또는 서로 다른 원장의 토큰)을 교환할 수 있다. AXC 기반 거래소의 장점은, 두 명의 유저 모두 서로 또는 거래소 서비스를 신뢰할 필요가 없다는 점이다. 그러나 거래를 위해서는 양 당사자가 반드시 온라인 상태여야만 한다는 점이 치명적인 단점이다. - -또 다른 방식의 탈중앙화 거래소는 자체적인 분산원장에서 작동하는 ‘대규모의 복제 분산 거래소(a mass-replicated distributed exchange)’이다. 유저는 ‘지정가 주문(limit order)’을 내고 컴퓨터를 종료한다. 이 경우, 유저가 온라인 상태가 아니어도 거래가 성사된다. 블록체인이 해당 거래를 매칭시키고 완료시키는 것이다. - -중앙화된 거래소의 경우, 이미 많은 지정가 주문이 걸려 있고 따라서 유저들을 더 쉽게 유치하게 된다. 거래소에서 유동성은 더 큰 유동성을 야기하고 강한 네트워크 효과(또는 승자독식)를 일으키게 된다. 현재 24 시간 거래량 기준, Poloniex 가 $20M 으로 가장 많은 거래량을 가지고 있고, Bitfinex 가 $5M 로 그 다음에 위치하고 있다. 이러한 사례를 생각해보면, AXC 기반의 새로운 탈중앙화 거래소가 중앙화된 거래소를 이길 가능성은 거의 없다. 탈중앙화된 거래소가 중앙화된 거래소와 경쟁하기 위해서는 많은 거래량을 확보해야 한다. 오직 ‘분산 거래소(distributed exchange)’만이 그것을 가능하게 할 수 있다. - -텐더민트는 또한 매우 빠른 이체 커밋을 제공한다. 일관성의 훼손없이 빠른 완결성을 우선순위에 둠으로써 코스모스 상의 존(zone)들의 이체를 빠르게 완결한다. 이는 거래소의 이체나 IBC 를 통한 존들 간의 이체에 동일하게 적용된다. - -현재 암호화폐 거래소들의 상황을 고려할 때, 코스모스를 이용해 분산거래소(일명 ‘코스모스 덱스’)를 만드는 것은 매우 적절하다. 코스모스 덱스(Cosmos DEX)의 이체 처리량과 커밋 속도는 중앙화된 거래소에 필적한다. 유저들은 지정가 주문을 올리고, 이 주문은 당사자의 온라인 상태여부에 관계없이 완결된다. 트레이더들은 텐더민트, 코스모스 허브, 그리고 IBC 를 통해, 빠른 속도로 자금을 존(zone)들이나 거래소로 이동시킬 수 있다. - -### 다른 화폐에 가치고정 (Pegging to Other Cryptocurrencies) - -특권 존(privileged zone)은 다른 암호화폐를 페깅한 토큰의 소스 역할을 할 수 있다. 페그(peg)는 코스모스 허브와 존의 관계와 본질적으로 유사한데, 코스모스 허브와 존 모두는 토큰이 한 쪽에서 다른 쪽으로 이동했다는 증명을 검증하기 위해 상대방의 최신 블록을 반드시 알고 있어야 한다. 코스모스 네트워크의 페그 존(peg-zone)은 다른 암호화폐뿐 아니라 허브도 추적한다. 페그 존을 통한 간접참조(indirection)는 허브의 로직이 단순하면서도 비트코인의 작업증명 채굴 같은 다른 블록체인 합의 전략들에 독립적(agnostic)인 상태를 유지할 수 있게 한다. - -예를 들어, 허브의 검증인 세트와 동일한 어떤 검증인 세트를 갖는 존이 이더-페그(ether-peg) 역할을 할 수 있을 것이다. 여기서 ‘페그-존(peg-zone)’의 TMSP 애플리케이션은 ‘외부 이더리움 블록체인의 페그 컨트랙트(peg contract)’와 IBC 메시지를 교환할 수 있다. 이더 보유자들이 이더리움 상의 페그 컨트랙트로 이더를 전송함으로 코스모스의 페그 존에 이더를 전송하게 된다. 이렇게 이더가 일단 한번 수신되면, 페그 컨트랙트가 페그 존으로부터 IBC 패킷을 수신하지 않는 한 이더는 인출되지 못 한다. 페그 컨트랙트에서 특정 이더리움 계정으로부터 이더를 수신했다는 IBC 패킷을 페그 존으로 보내면, 이에 대응되는 계정이 동일한 잔고와 함께 페그 존 위에 생성된다. 페그 존 위의 (페깅된) 이더를 허브로 또는 허브로부터 전송할 수 있으며, 페깅 이더를 폐기하면서 이더리움 상의 특정 인출 주소로 그 이더를 도로 전송할 수 있고, 이더의 인출을 허용하기 위해 페그 존에서 이체가 발생했음을 증명하는 IBC 패킷을 이더리움 페그 컨트랙트에. - -물론 그런 페깅 컨트랙트는 악의적 검증인 세트를 통한 리스크에 노출되어 있다. ⅓+ 비잔틴 투표권이 분기(fork)를 만들어 페깅된 이더를 페그 존에 유지하는 동시에 이더리움 위의 페그 컨트랙트를 통해 이더를 인출할 수도 있다. 더 좋지 않은 시나리오는 +⅔ 비잔틴 투표권이, 페그 존의 원래 페깅 로직으로부터 이탈하여, 페그 컨트랙트로 보내지는 모든 이더를 노골적으로 훔칠 수도 있다는 것이다. - -이런 문제들은 페그를 ‘완전 책임적(totally accountable)’으로 설계하여 해결할 수 있다. 예를 들면, 허브나 원점(페깅될 토큰의 원래 블록체인)으로부터의 모든 IBC 패킷이 페그-존의 응답(acknowledgment)을 요구하도록 할 수도 있을 것이다. 허브와 원점은 페그-존 검증인들이 담보물(collateral)을 포스팅(post) 할 수 있게 하고, 독립 감사들(independent auditors)의 도전을 허용하기 위해 페그 컨트랙트로부터 나가는 토큰 전송은 지연되도록 하는 식이다. 그리고 담보 언본딩(unbonding) 기간은 충분히 길어야 한다. 이 부분은 미확정 상태로 두고, 누구든지 향후에 관련 제안을 해서 코스모스 거버넌스 시스템에 승인을 받아 적용할 수 있도록 한다. - -정치사회적 분위기가 아직은 갖추어져 있지 않긴 하지만, 국가 통화의 책임기관들, 특히 은행들의 조합으로부터 검증인 세트를 형성함으로 국가의 법정통화에 페깅하는 존들을 허용할 수도 있다. 물론, 강력한 법체계에 기반하는 화폐만 허용하도록 충분한 고려가 되어야 할 것이며, 충분히 큰 규모의 공증인이나 기관들을 통해 감사시스템을 강제하여 은행들의 활동을 관리해야할 것이다. - -이렇게 통합이 된다면, 참여 은행에 은행계좌가 있는 누구든지 존에 있는 은행계좌로부터 존에 있는 다른 계좌나 허브나 혹은 또 다른 존으로 법정화폐를 자유롭게 이동시킬 수 있을 것이다. 이 점에서 코스모스 허브는 법정화폐들과 암호화폐들 간의 전달자 역할을 하고, 지금까지 상호운용성을 거래소(exchanges)의 영역으로 한정시킨 장벽들을 제거할 수 있다. - -### 이더리움 확장성 (Ethereum Scaling) - -이더리움의 해결되지 않은 쟁점 하나는 스케일링 문제를 해결하는 방법이다. 현재는 각 이더리움 노드들이 모든 이체를 처리하고 또 모든 상태를 저장한다. [링크](https://docs.google.com/presentation/d/1CjD0W4l4-CwHKUvfF5Vlps76fKLEC6pIwu1a_kC_YRQ/mobilepresent?slide=id.gd284b9333_0_28) - -텐더민트는 이더리움의 작업증명보다 훨씬 빠르게 블록들을 커밋 할 수 있기 때문에, 텐더민트 합의에 기반한 페깅된 이더(pegged-ether)를 운용하는 EVM 존들은 이더리움 블록체인에 보다 높은 성능을 제공할 수 있다. 뿐만 아니라, 비록 코스모스 허브 및 IBC 패킷 자체는 임의계약 로직 실행을 허용하지 않지만, 서로 다른 존들에서 실행되는 이더리움 컨트랙트 간 토큰이동의 조정을 위해 사용되며, 샤딩(sharding)을 통한 토큰 중심 이더리움 스케일링을 위한 기반을 제공할 수 있다. - -### 멀티 어플리케이션 통합 (Multi-Application Integration) - -코스모스 존들은 존의 생성 초기에 정해진 어플리케이션 로직에 따라 작동하며, 거버넌스를 통해 지속적으로 업데이트될 수 있다. 그런 유연성 덕분에 코스모스 존들이 이더리움이나 비트코인 같은 다른 암호화폐들의 페그 역할을 하고, 동일한 코드베이스를 활용하면서도 상이한 검증인 세트와 초기 분포(initial distribution)를 갖는 블록체인 파생상품(derivatives)을 만들 수 있다. 이를 통해 이더리움, 제로캐시, 비트코인, 크립토노트 등과 같은 기존의 암호화폐 프레임워크들이 ‘고성능 합의 엔진(텐더민트 코어)’을 통해 공통의 네트워크에서 사용될 수 있으며, 엄청난 상호운용성의 기회를 가질 수 있게 된다. 뿐만 아니라, 멀티 자산 블록체인으로서, 단일 이체는 다수의 인풋과 아웃풋을 포함할 수 있고, 어떤 토큰이든지 인풋이 될 수 있기 때문에, 주문들은 다른 플랫폼들을 통해 매칭되더라도 코스모스가 직접 탈중앙화 거래소(exchange)를 위한 플랫폼 역할을 할 수 있다. 또한 존은 오더북 기능이 있는 ‘분산형 장애-허용 거래소’ 역할을 할 수도 있는데, 해킹 공격에 당하곤 하는 기존의 중앙집중형 암호화폐 거래소들에 대한 강력한 개선책이 될 수 있다. - -존은 또한 기업 및 정부 시스템들의 블록체인 버전(blockchain-backed versions)을 지원할 수도 있다. 전통적으로 조직에 의해 운영된 서비스에 대해서 기반이 된느 부분은 통제권을 유지하고, 특정 부분만을 존 위의 TMSP 애플리케이션으로 운영한다면, 퍼블릭 코스모스 네트워크의 보안과 상호운용성을 활용할 수 있게 된다. 따라서 코스모스는 블록체인 기술을 활용하고자 하지만 (분산된) 제 3 자에게 통제를 완전히 내주는 것은 경계하는 조직들에 일거양득을 제공할 수 있다. - -### 네트워크 분할 완화 (Network Partition Mitigation) - -일각에서는 텐더민트 같은 ‘일관성선호 합의 알고리즘(consistency-favouring consensus algorithm)’에 심각한 문제가 있다고 주장한다. +⅔ 투표권(⅓+은 오프라인)을 갖는 분할이 생기지 않도록 방지하는 것이 합의과정 자체를 멈추도록 할 수 있다는 것이다. 코스모스 아키텍처는 지역 자치 존(regional autonomous zone)을 갖는 전역 허브(global hub)를 사용함으로 이 문제를 완화할 수 있는데, 이 지역적 자치 존은 각 존의 투표권이 지리적 위치에 기초하여 분배되는 존을 가리킨다. 예를 들어, 개별 도시들이나 지역들이 자체적인 존을 운영하면서 공통의 허브(예. 코스모스 허브)를 공유하여, 일시적인 네트워크 분할로 허브가 중단되더라도 자치활동은 지속될 수 있도록 할 수 있다. 강인한 연합형 장애허용 시스템(robust federated fault-tolerant system)이 설계되기 위해서는 실제 지리, 정치 및 네트워크 토폴로지 관련 특징들이 고려되어야 할 것이다. - -### 연합 명칭 결의 시스템 (Federated Name Resolution System) - -네임코인(NameCoin)은 비트코인 블록체인을 이용하여 ‘명칭 결의(name-resolution)’ 문제를 해결하고자 한 최초의 블록체인 중 하나였다. 안타깝지만 이 접근법에는 몇 가지 문제들이 있었다. - -네임코인으로 예를 들면, *@satoshi*라는 명칭이 과거 어느 시점에 특정 공개키로 등록되었다는 것은 검증할 수 있지만, 이후의 모든 블록들을 다운로드 하지 않는 한 그 공개키가 이후로 업데이트 된 적이 있는지는 알 수 없다. 이는 비트코인의 UTXO 머클화 모델의 한계 때문인데, 이 모델에서는 ‘상태’가 아닌 ‘이체의 기록’만 블록 해시로 머클화된다--이는 존재를 증명하지만, 이후로 업데이트가 없었다는 것을 증명하지는 못한다. 따라서 완전 노드를 신뢰하거나 전체 블록체인을 다운로드 하면서 상당한 비용을 지불해야만 가장 최근 값을 확실히 알 수 있다. - -머클 탐색 트리(Merkle-ized search tree)가 네임코인에 구현되더라도, 작업증명에 대한 의존성 때문에 라이트 클라이언트 검증에서 문제가 생긴다. 라이트 클라이언트는 전체 블록체인의 블록헤더들(또는 적어도 마지막 업데이트 이후의 모든 헤더들)을 모두 다운로드 해야 한다. 이는 대역폭 사용이 소모시간과 함께 선형으로 증가한다는 것을 의미한다[\[21\]]][21]. 또한 작업증명 블록체인 위의 명칭 변경을 위해서는 추가적인 블록확인을 기다려야 하는데 이것이 비트코인에서는 최대 1 시간까지도 걸릴 수 있다. - -텐더민트의 경우, 변경을 위해 필요한 것은 (투표권을 통해) 검증인 정족수가 서명한 최근의 블록 해시, 그리고 명칭에 대한 현재 값(current value)의 머클 증명이다. 이를 통해, ‘명칭 값’을 간결하고 신속하며 안전한 라이트 클라이언트 검증을 할 수 있다. - -코스모스에서 이 개념을 확장시킬 수 있다. 코스모스의 각 명칭등록 존(name-registration zone)은 ‘.com’이나 ‘.org’와 같은 최상위 도메인(TLD: top-level-domain)을 가질 수 있고, 자체적인 거버넌스와 등록규칙을 설정할 수 있다. - -## 발행과 인센티브 (Issuance and Incentives) - -### 아톰 토큰 (The Atom Token) - -코스모스 허브는 ‘다중자산 분산원장’이며 특별한 내부 토큰인 ‘_아톰(atom)_’을 가지고 있다. 아톰은 코스모스 허브의 유일한 지분 토큰(staking token)이다. 아톰은 보유자가 투표, 검증 또는 다른 검증인들에게 위임을 하기 위해 필요하다. 이더리움의 이더와 마찬가지로 아톰 역시 스팸공격 완화를 위한 이체수수료(transaction fees) 지불을 위해 사용될 수 있다. 추가되는 인플레이션 아톰(inflationary atoms)과 블록 이체수수료가 검증인들에게 그리고 검증인들에게 위임한 위임자들(delegators)에게 보상된다. - -BurnAtomTx 이체 기능을 통해 ‘지급 준비금 풀(reserve pool)’에서 일정 비율의 토큰 금액(proportionate amount of tokens)을 회수(recover)할 수 있다. - -#### 크라우드펀딩 (Fundraiser) - -코스모스가 최초로 생성될 때 아톰 토큰과 검증인의 배분은 코스모스 크라우드세일의 자금제공자들(75%), 사전 자금 제공자들(5%)과 코스모스 주식회사(Cosmos Corp)(20%)로 가게 된다. 그 이후, 아톰 총액 중 1/3 이 본딩된(bonded) 검증인들과 위임자들에게 매년 보상될 것이다. - -이 계획은 변경될 수 있으며 상세내용은 [Crowdfund Plan](https://github.com/cosmos/cosmos/blob/master/PLAN.md) 참조 - -### 검증인수의 제한 (Limitations on the Number of Validators) - -비트코인 등의 작업증명 블록체인들과는 달리, 텐더민트 블록체인은 검증인의 수가 많아질수록 통신의 복잡도가 증가하여 속도가 느려진다. 물론 충분한 검증인들을 통해 블록체인의 전세계 분산, 매우 빠른 이체확인 속도를 제공할 수 있다. 또한 대역폭, 저장공간 및 병렬 컴퓨팅 용량이 증가함에 따라 장래에 더욱 많은 검증인들을 지원할 수 있을 것이다. - -제네시스 시에는 최대 검증인 수가 100 명으로 설정될 것이고, 이 수치는 10 년 동안 13%의 비율로 증가하여 최종적으로는 총 300 명의 검증인을 가지게 될 것이다. - - Year 0: 100 - Year 1: 113 - Year 2: 127 - Year 3: 144 - Year 4: 163 - Year 5: 184 - Year 6: 208 - Year 7: 235 - Year 8: 265 - Year 9: 300 - Year 10: 300 - ... - -### 제네시스 일이후에 검증인되기 (Becoming a Validator After Genesis Day) - -아톰 보유자들은 ‘BondTx 이체’를 서명하여 제출함으로써 검증인이 될 수 있다. 담보로 제공된 아톰 액수는 ‘0’보다 많아야 한다. 현재 검증인 세트의 크기가 허용된 최대 검증인 수보다 많은 경우를 제외하고는 누구라도 언제든지 검증인이 될 수 있다. 만일 검증인의 수가 허용치 이상인 경우, 가장 적은 검증인이 보유한 유효 아톰의 액수보다 더 높은 아톰 액수(위임받은 아톰 포함)를 담보로 제공해야만 한다. 이러한 방식으로 새로운 검증인이 기존 검증인를 대체할 수 있으며, 기존 검증인은 비활성이 되고 모든 아톰과 위임 아톰(delegated atom)은 언본딩 상태(unbonding state)로 돌아간다. - -### 검증인에 대한 처벌 (Penalties for Validators) - -고의든 아니든 검증인이 정해진 프로토콜을 어길 때에는, 처벌이 주어져야 한다. 동일한 높이(height) 및 라운드에서의 이중 서명이나 "prevote-the-lock"(텐더민트 합의 프로토콜의 규칙)의 위반 같은 행위는 증거를 통해 즉시 인정된다. 이 경우, 검증인은 유효한 지위(good standing)를 상실하게 되며, 본딩된 아톰과 ‘지급준비금 풀(reserve pool)’내 토큰의 비례분 즉, 통칭하여 ‘지분(stake)’을 상당수 잃을 것이다. - -때로는 지역 네트워크 단절, 전원 장애나 그 밖의 이유들로 인해 검증인이 단절될 수 있을 것이다. 만일 ‘과거 어느 시점의 ValidatorTimeoutWindow 블록’에서 검증인의 커밋 투표가 이루어지지 않은 횟수가 ValidatorTimeoutMax Absent 횟수 이상인 경우, 해당 검증인은 비활성화 되고 지분의 ‘Validator TimeoutPenalty(디폴트 1%)’만큼 잃게 될 것이다. - -어떤 ‘악의적’ 행위는 블록체인에 명확한 증거를 남기지 않을 수 있다. 이런 경우, 압도적 다수의 합의가 존재한다면, 검증인들이 외부에서 합의한 뒤 악의적인 검증인을 강제로 타임아웃(timeout) 시킬 수 있다. - -‘투표권 ⅓+’이 악의적으로 연합하여 코스모스를 중단시키거나, 이들이 악의적 행동의 증거가 블록체인으로 들어오지 않게 검열하여 삭제하는 경우, 허브는 하드포크를 통한 블록재조정(reorg) 프로포잘로 복구(recover)되어야 한다. - -### 이체 수수료 (Transaction Fees) - -코스모스 허브 검증인들은 아톰 뿐 아니라, 어떠한 유형의 토큰이라도 이체수수료로 받을 수 있다. 각 검증인은, BlockGasLimit 을 초과하지 않는 한, 원하는 어떤 교환비율이든 주관적으로 정할 수 있고 원하는 어떤 이체든 선택할 수 있다. 징수된 수수료는 아래에 명시된 세금들을 제외하고, 매 ValidatorPayoutPeriod(검증인지불기간, 디폴트 1 시간)마다 본딩된 아톰(bonded atoms)에 비례하여 본딩된 주주들(bonded stakeholders)에게 재분배된다. - -징수된 이체수수료 중 ‘지급준비금 세금(Reserve Tax, 디폴트 2%)’은 ‘지금준비금 풀’에 충당되며 이를 통해 ‘지금준비금 풀’을 늘리고 코스모스 네트워크의 보안과 가치를 높이는데 사용될 것이다. 또한 공유세(Commons Tax, 디폴트 3%)는 공유재의 자금으로 충당될 것이다. 이 자금들은 CustodianAddress 로 가게 되고, 거버넌스 시스템의 결정에 따라 분배된다. - -투표권을 다른 검증인들에게 위임하는 아톰 보유자들은 위임 받은 검증인에게 커미션(commission)을 지불한다. 커미션은 각 검증인이 정할 수 있다. - -### 해커에 대한 인센티브 제공 (Incentivizing Hackers) - -코스모스 허브의 보안은 검증인들과 위임자들의 검증인 선택에 달려있다. 취약성 발견 및 조기 보고를 권장하기 위해 코스모스 허브는 해커들이 "이 검증인은 해킹 당했다. 본 주소로 포상금을 보내주기 바란다."라고 알릴 수 있는 ‘ReporthackTx 이체’를 통해 해킹성공 발표를 권장한다. 해킹 즉시, 해당 검증인과 위임자들은 비활성화되고, 이들의 아톰 일부가 ‘HackPunishmentRatio(해킹처벌비율, 디폴트 5%)’만큼 감소되고, 해커는 포상금 주소를 통해 ‘HackRewardRatio(해크보상비율, 디폴트 5%)’만큼 보상받는다. 검증인은 백업 키(backup key)를 사용하여 나머지 아톰을 회수(recover)해야 한다. - -이러한 방법을 이용해 ‘본딩이 완료되지 않은 비귀속(unvested) 아톰’을 전송하려는 악의적 시도를 막기위해, 감별자와 위임자들의 귀속과 비귀속 아톰 비율은 ReportHackTx 전후로 동일하게 유지될 것이다. 해커 포상금은, 존재할 경우, 얼마의 비귀속 아톰을 포함할 것이다. - -### 거버넌스 (Governance Specification) - -코스모스 허브는 소프트웨어 업그레이드와 헌법(규정) 수정뿐 아니라 시스템의 변수 파라미터들과 같은 블록체인의 다양한 변화를 조정하기 위해서 명확한 거버넌스 메커니즘을 가진 분산형 조직에 의해 운영된다. - -모든 검증인들은 모든 프로포잘에 대한 투표책임이 있다. 적시에 투표하지 않는 경우, 해당 검증인은 ‘결석처벌기간(AbsenteeismPenaltyPeriod-디폴트 1 주)’ 동안 자동으로 비활성화된다. - -위임자들(delegators)은 그들이 위임한 검증인의 투표를 자동으로 물려받는다. 이 투표는 수동으로 취소(overriden manually)될 수 있다. 언본딩된(unbonded) 아톰들은 어떤 투표권도 얻지 않는다. - -각 프로포잘은 ‘최소 프로포잘 보증금(MinimumProposalDeposit)’ 토큰을 요구하는데, 이는 아톰을 포함한 하나 이상의 토큰들일 수 있다. 각 프로포잘에 대해 투표자들은 보증금을 사용하기로 투표할 수 있다. 투표자의 절반 이상이 보증금을 사용하기로 선택할 경우, 그 보증금은 지급보증금 풀에 충당된다. 다만, 프로포잘이 스팸이었다거나 기타의 경우라서 소각되는(burned) 아톰은 제외한다. - -각 프로포잘에 대해, 투표자들은 다음의 옵션으로 투표할 수 있다: - -- Yay(찬성) -- YayWithForce(강력히 찬성) -- Nay(반대) -- NayWithForce(강력히 반대) -- Abstain(기권) - -프로포잘의 통과 여부를 결정시에는 과반수의 투표가 요구되지만, 1/3+이 "강력히(with force)" 반대 투표함으로써 과반수의 결정을 거부할 수 있다. 다만 이렇게 과반수가 거부될 경우, 모두가 ‘거부권패널티블록(VetoPenaltyFeeBlocks, 디폴트 1 일 가치의 블록)’을 통해 수수료를 상실함으로 처벌 받고, 과반수 결정을 거부한 당사자는 자신의 아톰 중 ‘거부권패널티아톰(VetoPenaltyAtoms, 디폴트 0.1%)’ 만큼을 추가로 상실한다. - -### 파라미터 변경 프로포잘 (Parameter Change Proposal) - -여기서 정의된 파라미터들 중 어느 것이든 ParameterChangeProposal 의 통과를 통해 변경될 수 있다. - -### 텍스트 프로포잘 (Text Proposal) - -다른 모든 프로포잘들(예. 업그레이드 프로포잘)은 일반적인 TextProposal 을 통해 조정된다. - -## 로드맵 (Roadmap) - -[Plan](https://github.com/cosmos/cosmos/blob/master/PLAN.md) 참조. - -## 관련 연구 (Related Work) - -지난 수십 년 동안 블록체인 합의구조와 확장성 부분에서 많은 혁신이 있었다. 아래에서 중요한 몇 가지를 간략히 개관한다. - -### 합의 시스템 (Consensus Systems) - -#### 고전적 비잔틴 장애 저항 (Classic Byzantine Fault Tolerance) - -악의적인 참여자가 있는 합의는 80 년대 초로 거슬러 올라가는 문제인데, 이때 레슬리 램포트(Leslie Lamport)는 프로세스가 단순한 ‘충돌 장애(crash fault)’와는 대조되는 것으로, 의도된 행위로부터 벗어나는 임의의 프로세스 행위(process behavior)를 가리키는 ‘비잔틴 장애(Byzantine fault)'라는 용어를 만들었다. 초기의 솔루션들은 메시지 지연시간에 상한(upper bound)이 존재하는 동기식 네트워크들을 위한 것이었다. 하지만 실제 사용은 항공기 제어기와 원자시계를 통해 동기화되는 데이터센터 같은 고도로 통제된 환경들로 제한되었다. 임의의 프로세스 행위를 최대 ⅓ 까지 허용할 수 있는 효율적인 ‘부분 동기 합의 알고리즘’으로서 ‘실용적 비잔틴 장애 허용(PBFT: Practical Byzantine Fault Tolerance)’[\[11\]](11)이 도입된 것은 90 년대 말이 되고 나서였다. PBFT 는 표준 알고리즘이 되었고, 많은 파생 변형들을 만들어내었다. 가장 최근에는 IBM 이 하이퍼레저(Hyperledger)에 기여를 위해 만든 변형이 있다. - -PBFT 와 비교할때 텐더민트 합의구조의 중요한 장점은 텐더민트가 단순하고 개선된 기반 구조를 가진다는 것이고, 이 구조 중의 일부는 블록체인 패러다임을 수용한 결과이다. 텐더민트 블록들은 순서대로 커밋되는데, 이것이 PBFT 의 관점 변화(view-changes)와 관련 있는 복잡도와 통신 오버헤드를 제거한다. 코스모스를 포함한 많은 암호화폐에서는, 블록 _N_ 자체가 아직 커밋되지 않았을 때에는 블록 _N+i_(_i >= 1_)의 커밋을 고려할 필요가 없다. 블록 *N*이 코스모스 존에 커밋되지 않은 이유가 대역폭인 경우, _N+i_ 블록들에 대해 대역폭 공유 투표(bandwidth sharing votes)를 사용해도 의미가 없다. 만일 네트워크 분할이나 오프라인 노드들 때문에 블록 *N*이 커밋되지 않은 경우, 블록 *N+i*은 어차피 커밋되지 않는다. - -뿐만 아니라, 블록들 속으로 이체를 배칭(batching)함으로 PBFT 의 체크포인팅(checkpointing) 기법 같은 주기적 다이제스트가 아닌, ‘어플리케이션 상태의 정규 머클 해싱’이 가능하다. 또한 이를 통해, 라이트 클라이언트들을 위한 보다 빠른 증명 가능한 이체 커밋과, 보다 빠른 블록체인 간 통신이 가능하다. - -텐더민트 코어는 PBFT 에 명시된 것 이상의 많은 최적화 사항들과 기능들을 가지고 있다. 예를 들면, 검증인이 제안하는 블록들은 전파 성능을 개선하는 방식으로 부분화되고 머클화(Merkle-ized) 되고 가십화(gossipped)된다(영감은 LibSwift[\[19\]](19) 참조). 또한 텐더민트 코어는 점 대 점(point-to-point) 접속에 관한 어떤 가정도 하지 않으며, P2P 네트워크가 연결되어 있기만 하다면 작동한다. - -#### 비트쉐어 위임 지분 (BitShares delegated stake) - -비트쉐어(BitShares)[\[12\]](12)는 최초의 지분증명(PoS: proof-of-stake) 적용 사례는 아니지만, PoS 블록체인들, 특히 ‘위임(delegated)’ PoS 블록체인의 연구와 채택에 상당한 기여를 했다. 비트쉐어에서 지분 보유자들은 이체명령 및 커밋 책임이 있는 '증인(witnesses)'과 소프트웨어 업데이트와 패러미터 변경 책임이 있는 '델리게이트(delegates)'를 선출한다. 비트쉐어는 이상적인 상태에서 고성능(100k tx/s, 1 초 지연시간)을 달성하기는 하지만, 악의적인 증인들이 아무런 경제적 처벌 없이 블록체인을 분기해 이중지불 공격을 가할 수 있다. 즉 '무보증(Nothing-at-Stake)' 문제를 겪는다. 비트쉐어는 이체들이 최근의 블록-해시들을 참조하게 함으로써 이 문제를 완화하고자 한다. 물론 주주들은 부정행위 증인들을 매일 제거하거나 대체할 수 있다. 하지만 이것이 성공한 이중지불공격에 대한 분명한 처벌은 결코 아니다. - -#### 스텔라 (Stellar) - -리플(Ripple)이 개척한 방식을 토대로, 스텔라(Stellar)[\[13\]](13)는 ‘연합형 비잔틴 합의(Federated Byzantine Agreement)’ 모델을 개선했는데, 여기에서는 합의참여 프로세스에서 ‘전역적으로 알려진 고정된 집합’을 요구하지는 않는다. 오히려 프로세스 노드는 각기 신뢰할 수 있는 프로세스 집합을 구성하는 하나 이상의 '정족수 슬라이스(quorum slices)'를 배포한다. 스텔라에서 '정족수'는 노드들의 집합(set)이며, 적어도 각 노드당 하나 이상의 정족수 슬라이스를 포함하여 합의에 도달하도록 한다. - -스텔라 메커니즘의 보안은 _임의의_ 두 정족수의 교집합이 비공(non-empty)이라는 가정에 의존한다. 또한 노드의 가용성을 위해서는, 정족수 슬라이스들 중 적어도 하나가 완전히 올바른(entirely correct) 노드들로 구성되어야 하며, 신뢰에 관한 중요한 가정 없이는 큰 정족수 슬라이스와 작은 정족수 슬라이스 사용 간에 발생하는 상호관계의 균형유지가 어려울 수 있다. 궁극적으로는 노드들은 충분한 장애허용이 가능한 ‘적절한 정족수 슬라이스’ 또는 ‘온전한 노드들(intact nodes)’을 어떻게든 선택해야 한다. 또한 그런 구성을 보장하는 유일한 전략은 계층적이고, 경계 경로 프로토콜(BGP: Border Gateway Protocol)과도 유사하다. 이는 전역 라우팅 테이블 확립을 위해 인터넷의 최상위 계층 ISP 들에 의해 사용되며, TLS 인증서를 관리하기 위해 브라우저들이 사용하기도 한다. 그러나 두 가지 모두 약한 보안성으로 악명높다. - -스텔라 논문에서 비판한 ‘텐더민트 기반 지분 증명 시스템’들은 이곳에 기술된 토큰 전략을 통해 해명할 수 있다. 이 전략에서는 미래의 수수료 및 보상에 대한 권리인 *아톰*이라는 이름의 새로운 유형의 토큰이 발행된다. ‘텐더민트 기반 지분 증명’은 상대적으로 단순성을 유지하면서도, 충분한 그리고 입증 가능한 보안을 제공한다는 점에서 이점이 있다. - -#### 비트코인 NG (BitcoinNG) - -BitcoinNG 는 블록 크기 확장과 같은 수직 확장성을 제공하는 방법이다. 또한 이러한 확장이 초래하는 부정적 경제 요인들을 최소화하였다. 이러한 개선은 리더 선정(leader election)과 이체 전파를 분리시킴으로 가능하다: 리더들은 우선 ‘마이크로 블록(micro-block)’들에 있는 작업증명(PoW)을 통해 선정되며, 그 이후 새로운 마이크로블록이 발견될 때까지 커밋할 이체내역들을 전파한다. 이러한 방식은 PoW 경쟁을 이기기 위해 소요되는 대역폭 요구량을 줄여준다. 또한 소규모 채굴자들이 더욱 공정하게 경쟁하고 이체가 더 정기적으로 커밋될 수 있도록 한다. - -#### 캐스퍼 (Casper) - -캐스퍼[\[16\]](16)는 이더리움 용으로 제안된 지분증명(PoS) 합의 알고리즘이다. 주요 운용 방식은 '베팅에 의한 합의(consensus-by-bet)'이며, 검증인들은 지금까지 본 다른 베팅들(bets)에 기초해 블록체인에 커밋 될 것으로 생각되는 블록에 반복적으로 베팅하게 되며, 이러한 방식으로 결국 완결성(finality)이 달성된다는 논리를 전제한다. [링크](https://blog.ethereum.org/2015/12/28/understanding-serenity-part-2-casper/). 이러한 ‘합의 베팅’이 캐스퍼 팀의 주요 연구영역인데, 도전이 되는 부분은 베팅 메커니즘을 ‘진화적으로 안정된 전략(evolutionarily stable strategy)’이 되도록 설계해야 한다는 점이다. 텐더민트와 비교할 때 캐스퍼의 강점은 '일관성(consistency)보다 가용성(availability)'을 제공하는 것으로 볼 수 있다. 합의는 투표권의 +⅔ 정족수를 요구하지 않으며 커밋 속도나 구현 복잡도를 희생했다고 볼 수 있다. - -### 수평 스케일링 (Horizontal Scaling) - -#### 인터레저 프로토콜 (Interledger Protocol) - -인터레저 프로토콜(Interledger protocol)[\[14\]](14)은 엄밀히는 확장성 솔루션이 아니다. 느슨하게 연결된 ‘쌍방 관계 네트워크’를 통해 상이한 원장 시스템들 간 ‘애드혹 상호운용성(ad hoc interoperation)’을 제공하는 방식이다. 라이트닝 네트워크(Lightning Network)의 경우처럼, ILP 는 지불을 용이하게 하는 프로토콜인데, 특히 서로 다른 유형의 원장들 간 지불에 초점을 맞추며 ‘아톰 이체 메커니즘(atomic transaction mechanism)’을 확장하여 ‘해시 잠금(hash-locks)’과 ‘공증인 정족수(quorum of notaries)’를 포함하로독 했으며, 이를 ‘아톰 전송 프로트콜(Atomic Transport Protocol)’로 칭한다. ‘원장 간 이체의 원자성(atomicity)’을 확보하기 위한 이러한 메커니즘은 텐더민트의 ‘라이트 클라이언트 SPV’ 작동방식과도 유사하다. 하단에서 ILP 와 코스모스/IBC 를 비교해본다. - -1. ILP 의 커넥터(connector) 공증인들은 구성원(membership) 변경을 지원하지 않으며 공증인들 간 유연한 가중치 부여를 지원하지 않는다. 반면에 IBC 는 블록체인 전용으로 설계되었고, 검증인들이 상이한 가중치를 가질 수 있으며 블록체인을 통해 구성원들의 변경이 가능하다. - -2. 라이트닝 네트워크나 ILP 에서는 지불 수신자가 송신자에게 ‘확인’을 되돌려 보내기 위해 반드시 온라인 상태여야 한다. IBC 를 통한 토큰 전송에서는, 수신자가 아니라, 수신자 블록체인의 검증인 세트가 ‘확인’을 제공할 책임이 있다. - -3. 가장 근본적인 차이는 ILP 커넥터들은 지불에 대한 책임을 지거나 권한 상태를 유지하고 있지 않은 반면에, 코스모스에서는 허브의 검증인들이 IBC 토큰 전송과 각 존이 가진 토큰 총액에 대한 권한을 가진다는 점이다(그러나 존 내 각 계정이 보유한 토큰 금액에 대한 권한은 없다). 이는 존에서 존으로 비대칭 토큰 전송을 안전하게 실행하기 위한 근본적 혁신이다. - -4. ILP 에서 원장 간 지불을 하기 위해서는 거래소 오더북(exchange orderbook)의 지원이 필요한데, 이는 하나의 원장에서 다른 원장으로의 ‘코인 비대칭 전송’이 없고 ‘가치나 시장 등가물(market equivalents)’의 전송만 있기 때문이다. - -#### 사이드체인 (Sidechains) - -사이드체인(sidechains)[\[15\]](15)은 비트코인 블록체인에 '페깅(pegged)'된 ‘대안 블록체인’을 통해 비트코인 네트워크를 확장하고자 하는 시도이다. 사이드체인은 비트코인이 비트코인 블록체인으로부터 사이드체인으로 효과적으로 이동할 수 있게 하고 사이드체인에서 각 체인의 특징을 이용한 실험들을 가능하게 한다. 코스모스 허브와 마찬가지로, 사이드체인과 비트코인은 상호 간 라이트 클라이언트 역할을 하며 코인 전송 시점을 결정하기 위해 SPV 증명을 사용한다. 물론 비트코인은 작업증명을 사용하기 때문에 비트코인 중심의 사이드체인들은 작업증명 합의 메커니즘으로부터 발생하는 여러 문제들과 리스크를 갖는다. 이는 ‘비트코인 극단주의자(Bitcoin-maximalist)’적 솔루션이며, 코스모스처럼 자체적으로 다양한 토큰들과 존 간 네트워크 위상(inter-zone network topology)을 지원하지는 않는다. 하지만, ‘양방향 페그(two way peg)’의 핵심 메커니즘 자체는 코스모스 네트워크의 방식과 원칙적으로 동일하다. - -#### 이더리움 확장성 노력 (Ethereum Scalability Efforts) - -이더리움은 확장성 확보를 위해 ‘이더리움 블록체인 상태’를 샤딩(sharding)하기 위한 여러 전략들을 연구 중이다. 현재의 이더리움 가상 머신(EVM)를 통해 ‘공유 상태 공간(shared state space)’에 ‘대한 추상적 계층(abstraction layer)’을 유지하는 방식이다. 또한 다수의 연구들이 진행되고 있다.[\[18\]](18)[\[22\]](22) - -#### 코스모스 vs 이더리움 2.0 Mauve (Cosmos vs Ethereum 2.0 Mauve) - -코스모스와 이더리움 2.0 Mauve[\[22\]](22)의 설계 목표에는 차이가 있다. - -- 코스모스가 토큰들에 대한 것이라면 Mauve 는 일반 계산(general computation)의 스케일링에 관한 것이다. - -- 코스모스는 EVM 에 구속되지 않으며, 심지어는 서로 다른 VM 들이 상호 운용될 수 있다. - -- 코스모스는 존의 검증 책임자를 존의 생성자가 결정하도록 한다. - -- (거버넌스 시스템의 결정과 상충되지만 않는다면) 누구든지 코스모스에서 새 존을 시작할 수 있다. - -- 허브는 존 실패(zone failures)를 격리시키며, 이를 통해 ‘전역 토큰 불변성(global token invariants)’을 보증한다. - -### 일반 스케일링 (General Scaling) - -#### 라이트닝 네트워크 (Lightning Network) - -라이트닝 네트워크는 비트코인 블록체인(그리고 그 밖의 퍼블릭 블록체인들)의 상위 계층에서 운용되도록 제안된 토큰 전송 네트워크로, 합의원장(consensus ledger) 외부의 대다수 이체들을 소위 ‘지불채널(payment channels)’로 이동시켜서 이체처리량(throughput)을 획기적으로 개선시킨다. 온 체인(on-chain) 암호화폐 스크립트를 통해 당사자들이 쌍무 상태기반 계약(bilateral stateful contracts)을 체결할 수 있도록 한다. 계약들의 상태는 디지털 서명에 의해 업데이트 되며, 또한 ‘크로스 체인 아토믹 스왑(cross-chain atomic swap)’를 통해 최초에 설정된 방식에 따라 합당한 증명을 블록체인에 전파함으로 계약이 마감된다. 다수의 당사자와 함께 지불 채널을 개방함으로, 라이트닝 네트워크의 참여자들은 당사자들이 지불을 라우팅하는 ‘포컬포인트(focal point)’의 역할을 할 수 있다. 또한 이러한 지불 채널에 실제 자본을 고정시킴으로 완전히 연결된 지불 채널을 개설하는 것도 가능하다. - -라이트닝 네트워크는 다수의 블록체인들에 걸쳐 확장되면서 교환시장을 통해 *가치(value)*를 전송하도록 하지만, 하나의 블록체인에서 다른 블록체인으로 *토큰(token)*을 비대칭적으로 전송하는 것은 불가능하다. 이와 관련된 코스모스 네트워크의 주된 이점은 그러한 토큰의 직접 전송을 지원한다는 것이다. 물론, 비용 절감과 프라이버시 차원에서 토큰 전송 메커니즘이 지불 채널들과 라이트닝 네트워크와 함께 적용되기를 기대한다. - -#### 세그위트 (Segregated Witness) - -세그위트(Segregated Witness)는 블록 당 이체처리량을 2-3 배 가량 증가시키는 것을 목적으로 하며, 동시에 신규 노드들이 더욱 빠르게 블록 동기화(block syncing)를 하도록 돕는 비트코인 개선 제안이다([참조](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)). 이 솔루션은 비트코인의 현재 프로토콜 내에서 작동하면서 소프트 포크 업그레이드(soft-fork upgrade)를 허용하는 방식이다. 따라서, 이전의 소프트웨어 버전을 가진 클라이언트들도 업그레이드를 받아 정상적으로 기능할 수 있다. 텐더민트의 경우, 새로운 프로토콜을 만드는데 아무런 제약이 없기 때문에 확장성에 대한 새로운 우선순위를 가진다. 주로 텐더민트는 채굴 대신 암호 서명에 기반한 ‘BFT 라운드 로빈 알고리즘(BFT round-robin algorithm)’을 사용한다. 이를 통해, 다중 병렬 블록체인을 이용한 수평 스케일링을 가능하게 하면서도, 보다 정기적이고 자주 발생하는 블록 커밋에는 수직 스케일링을 적용할 수도 있다. - -<hr/> - -## 부 록 (Appendix) - -### 포크 책임 (Fork Accountability) - -잘 설계된 합의 프로토콜이라면 허용 용량(tolerance capacity)이 초과되거나 합의가 실패할 경우에도 문제가 없어야 한다. 이는 ‘비잔틴 행동’에 상당한 금전적 보상이 있을 수 있는 경제시스템에서 특히 중요하다. 그러한 보장 중 가장 중요한 것이 ‘_포크 책임(fork-accountability)_’인데, 포킹과 같은 합의실패를 초래한 프로세스들이 프로토콜 규칙이나 법적 체계(가능하다면)에 의해 식별되고 처벌받을 수 있다. 법 체계를 신뢰할 수 없거나 적용하기에 과도한 비용이 들 경우, 검증인들이 담보 보증(security deposits)을 내고 참여하도록 하고 악영향을 주는 행위 시 해당 담보를 차감하거나 강제로 환수함으로 처벌할 수 있다[\[10\]](10). - -‘포크 책임’에서 다루는 것은 비트코인에서처럼 ‘네트워크 비동시성’과 ‘부분적 해시충돌’ 특성으로 인해 일어나는 정상적 분기와는 큰 차이가 있다. 많은 경우, 비동시성 때문에 악의적 포킹을 가려내는 것이 거의 불가능하기 때문에 비트코인의 경우 채굴자들이 포킹을 시도하다가 고아블록이 되어버리는 기회비용을 제외하고는 특별히 ‘포크 책임’을 물게하는 것이 어렵다. - -### 텐더민트 합의 (Tendermint Consensus) - -투표 단계들은 *프리보트(PreVote)*와 *프리커밋(PreCommit)*으로 나뉜다. 투표는 특정 블록이나 ‘_무효(Nil)_’를 위해 행사될 수 있다. 동일 라운드에서 단일 블록에 대한 +⅔ PreVote 집합을 *폴카(Polka)*라고 부르며, 동일 라운드에서의 단일 블록에 대한 +⅔ PreCommit 집합을 *커밋(Commit)*이라고 부른다. 만일 동일 라운드에서 ‘무효(Nil)’에 +⅔ PreCommit 상태라면, 다음 라운드로 이동한다. - -결함 있는 리더들을 검출하고 무시하기 위해 엄격한 결정성(determinism)을 사용하다가 오히려 약한 동시성(synchrony)을 초래할 수 있다. 따라서 검증인들은 무효(Nil)를 Prevote 하기 전에, 일정 시간(*TimeoutPropose)*을 기다리게 되며 이 TimeoutPropose 값은 매 라운드마다 점점 증가한다. 검증인이 네트워크의 +⅔ 로부터 전파받을 때에만 단 한번 진행이 되므로, 라운드의 나머지 부분 진행은 완전히 비동시적이다. 실제로, 약한 동시성 가정( weak synchrony assumption)을 계속해서 좌절시키고 결국 블록 커밋을 위한 합의를 실패시키기 위해서는 극단적으로 강한 공격자가 필요하다. 또한 각 검증인에게 서로 다른 TimeoutPropose 랜덤값을 적용한다면, 공격은 더욱 어려워진다. - -‘추가 제약조건 집합’, 즉 ‘잠금 규칙(Locking Rules)’은 네트워크가 각 높이에서 단 하나의 블록만을 커밋하도록 보장한다. 특정한 높이에서 두 개 이상의 블록을 커밋되게 하려는 시도는 모두 발각될 수 있다. 첫째로, 블록에 대한 프리커밋(PreCommit)은 해당 블록에 대한 폴카의 형태로 정당화(justification)되어야 한다. 만일 검증인이 이미* R_1* 라운드에서 블록을 프리커밋했다면, 이는 해당 블록에 로킹*(locked)*되며, _R_2_ 라운드의 새로운 프리커밋은 반드시 *R_1 < R_polka <= R_2*인 ‘R_polka 라운드’에서 발생해야한다. 둘째로, 검증인들은 자신들이 로킹되어 있는 블록을 제안(Propose)하거나 아니면 사전투표(PreVote)하거나 또는 둘을 함께 해야한다. 이를 통해 검증인은 충분한 증거 없이 프리커밋하지 못하며, 이미 프리커밋한 검증인의 경우 다른 블록을 동시에 프리커밋할 수 없게 된다. 이렇게 합의 알고리즘의 안전성(safety)과 라이브성(liveness)을 보장하게 된다. - -프로토콜의 전체 세부내용은 [여기](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm)를 참조 - -### 텐더민트 라이트 클라이언트 (Tendermint Light Clients) - -대안 체인(포크)의 존재 덕분에 본딩된(bonded) 지분을 ⅓+이 대폭 삭감할(slashed) 수 있기 때문에, 텐더민트 PoS 에서는 모든 블록 헤더들을 동기화(sync)할 필요가 없다. 물론 이러한 삭감을 위해서는 누군가가 포킹의 증거를 공유해야 하기 때문에, 라이트 클라이언트는 전파받는 모든 블록해시 커밋을 저장해야 한다. 뿐만 아니라, 라이트 클라이언트들은 ‘검증인 세트의 변경’과 주기적으로 동기화하여 [장기 공격(long range attacks)](#preventing-long-range-attacks)을 피할 수 있을 것이다(물론 다른 방법도 적용가능). - -이더리움과 유사한 의도로, 텐더민트는 애플리케이션들이 각 블록에 ‘전역 머클 루트 해시(global Merkle root hash)’를 임베딩(embed)하도록 하고, 계정 잔고, 컨트랙트내 저장 값, UTXO 등 각 어플리케이션에 적합한 쉽게 검증이 가능한 ‘상태 쿼리(state queries)’를 이용하도록 한다. - -### 장기 공격 방지 (Preventing Long Range Attacks) - -‘충분한 회복력(resilient)의 전파 네트워크 집합’과 ‘정적(static) 검증인 세트’가 있다면, 블록체인의 모든 포킹이 검출될 수 있으며 문제가 되는 검증인들의 보증금을 대폭 삭감시킬 수 있다. 2014 년 초에 비탈릭 뷰터린(Vitalik Buterin)은 처음 지분증명 암호화폐들의 ‘무처벌(nothing-at-stake)’ 문제에 대한 해결책을 제시한다([관련 연구](#related-work) 참조). 그러나 검증인 세트들이 항상 고정되어 있는 것이 아니기 때문에 오랜 기간에 걸쳐 원래의 검증인들은 모두 언본딩 될 수 있고(may all become unbonded) 묶어둔 보증금이 사라지면서, 어떤 비용도 치르지 않고 제네시스 블록으로부터 새로운 체인을 자유로이 생성할 수 있게 된다. 이 공격은 단기 공격(Short Range Attack)과 대조되는 개념으로 ‘장기 공격(Long Range Attack)’으로 알려지게 되었다. 텐더민트 합의와 같은 ‘포크 책임 BFT 알고리즘(folk-accountable BFT algorithm)’이 존재한다면 현재 본딩된 검증인들이 포크를 초래하는 단기공격은 처벌이 가능하다. 반면 장기 공격은 지분증명 방식에 치명적인 공격으로 간주된다. - -다행히 ‘장기공격(LRA)’을 부분적으로 방지하는 것이 가능하다. 첫째로, 검증인이 보증금을 언본딩하여 보증금을 회수하고 동시에 더이상의 합의참여 수수료를 포기하기 위해서는 정해진 일정 기간, 즉 ‘언본딩 기간(unbonding period)’을 채워야 한다. 그 기간은 수주 또는 수개월이 된다. 둘째로, 라이트 클라이언트는 최초로 네트워크에 연결될 때 신뢰할 수 있는 한개 이상의 출처를 통해 네트워크의 최근 블록해시를 검증해야 한다. 이러한 조건을 ‘약한 주체성(weak subjectivity)’으로 부르기도 한다. 마지막으로, 보안성을 유지하기 위해서 적어도 매 ‘언본딩 기간’을 주기로 ‘최종 검증인 세트’와 동기화되어야 한다. 이를 통해, 라이트 클라이언트는 검증인이 보증금을 빼내고 ‘무보증 상태(Nothing at Stake)’가 되었는지 여부를 파악할 수 있게 된다. 이를 파악하지 못한다면, 검증인은 최초 본딩이 시작된 블록으로 돌아가 새로우 블록들을 다시 생성함으로 장기공격을 감행할 수 있다(물론 이를 위해서는 검증인이 충분히 많은 초기 개인키를 가지고 있어야 함). - -이러한 방식의 LRA 대응 방안은 기존 ‘작업증명(Proof-of-Work)’ 방식의 보안 기법에 대한 완전한 분해정비를 필요로 한다. ‘작업증명’에서는, 라이트 클라이언트가 언제든 전체 블록헤더의 작업증명을 처리하기만 한다면 신뢰할 수 있는 제네시스 블록을 통해 최신 블록체인을 동기화할 수 있다. 그러나 LRA 에 대응하기 위해서는, 라이트 클라이언트가 정기적으로 온라인 상태가 되어 검증인 세트를 추적해야하며, 최초 온라인이 되었을 때 반드시 신뢰할 만한 출처를 통해 네트워크에서 제공하는 정보의 진실성을 확인해야만 한다. 물론 신뢰할만한 출처를 이용하는 방법은 비트코인에서도 동일한데, 프로토콜이나 소프트웨어를 반드시 신뢰할 수 있는 출처에서 얻어야 하기 때문이다. - -LRA 를 방지하는 상기의 방법은, 검증인들과 텐더민트 기반 블록체인의 완전 노드들에 적합하다. 이 노드들은 네트워크에 계속 연결되어 있기 때문이다. 또한 이 방법은 네트워크에 자주 연결될 수 있는 라이트 클라이언트들에도 적합하다. 하지만 라이트 클라이언트들이 자주 연결될 수 없는 경우, 다른 방법이 사용될 수 있다. 검증인이 아닌 토큰 보유자들이 상당히 긴 기간동안 자신의 토큰을 담보로 제공하는 것이다. 그리고 라이트 클라이언트들에게 이차적인 방법으로 최근과 이전의 블록해시 값을 전달하는 것이다. 이러한 토큰들이 블록체인 합의와 관련된 보안에는 영향을 미치지 않으나 라이트 클라이언트에게는 강력한 보장을 제공할 수 있다. 역사적 블록-해시 질의가 이더리움에서 지원될 경우, 누구든지 특별 설계된 스마트 컨트랙트에서 자신의 토큰을 본딩하고 지불을 위한 인증 서비스(attestation services for pay)를 제공하여 라이트 클라이언트 LRA 보안 시장을 효과적으로 창출할 수 있을 것이다. - -### 포크 및 검열 공격 극복 (Overcoming Forks and Censorship Attacks) - -블록 커밋의 정의로 인해, ⅓+ 투표권 연합은 오프라인이 되거나 투표전파를 거부함으로 블록체인을 중단시킬 수 있다. 그러한 연합을 통해 특정한 이체를 포함한 블록을 검열하여 탈락시킬 수도 있으며, 이 경우 상당한 양의 프로포잘이 거부되고 블록커밋 속도를 늦추어 블록체인의 효용과 가치를 낮출수도 있다. 또한 연합은 소량의 투표만을 전파하여 블록체인의 블록커밋 속도를 서서히 늦출 수도 있다. 결정적으로 이들은 이중서명이나 잠금규정을 지키지 않음으로 블록체인을 분기(포킹)시킬 수도 있다. - -전역적 능동 공격자(global active adversary)가 관여할 경우, 네트워크를 분할시키고 다른 이가 책임이 있는 것처럼 보이게 할 수도 있다. 이는 텐더민트만의 문제는 아니며 네트워크 상에 능동 공격자가 존재하는 모든 합의 프로토콜이 가진 한계이다. - -이러한 유형의 공격이 가능해지려면, 발리데이터 부분집합(a subset of the validators)이 합동하여 외부수단을 이용해 포킹 목적의 블록재조정 프로포잘에 서명해야하고, 초기 발리데이터 부분집합도 이에 서명해야한다. 클라이언트들은 블록재조정 프로포잘의 서명을 유심히 검증하고, 이를 근거로 판단하거나 그 결정을 최종사용자에게 넘길 수 있다. - -어떤 ‘비동시성 비잔틴 장애 저항 알고리즘(non-synchronous Byzantine fault-tolerant algorithm)’도 투표권의 ⅓+이 부정직할 경우, 합의에 도달할 수 없다. 하지만, 포크의 경우 투표권의 ⅓+이 이미 이중 서명이나 잠금 변경(lock-changing)에 의해 부정직한 것으로 가정한다. 따라서 블록재조정 프로포잘(reorg-proposal)에 서명하는 것은 어떤 비동시성 프로토콜에서도(즉, 자동적으로 그리고 기반 네트워크의 신뢰성에 관한 가정없이는) 해결할 수 없는 조정 문제이다. 현재로서는 ‘블록재조정 프로포잘 조정(reorg-proposal coordination)’ 문제를 인터넷 매체 상에서 일어나는 인간들의 사회적 합의에 맡기고 있다. 상충되는 두 블록재조정 프로포잘에 서명하는 것을 방지하기 위해, 검증인들은 서명에 앞서 남은 네트워크 분할이 없도록 주의해야 한다. - -외부의 조정 수단 및 프로토콜이 강인하다면, 네트워크분기(포크)가 검열 공격보다는 덜 치명적이다. - -⅓ 비잔틴 투표권을 요구하는 포크 및 검열 외에, +⅔ 투표권의 연합이 임의의 무효 상태를 커밋할 수도 있다. 이는 모든 BFT 합의 시스템의 특징이다. 쉽게 검증 가능한 포크가 생성되는 이중서명과는 달리, 무효 상태 커밋(commitment of an invalid state)을 검증하기 위해서는 발리데이터가 아닌 피어들(non-validating peers)도 전체 블록들을 검증해야한다. 이는 피어들이 상태의 지역 사본(local copy)을 가지고 각 이체를 실행하며, 상태 루트(state root)를 독립적으로 계산한다는 것을 의미한다. 또한 무효상태커밋이 검출되면, 사회적 합의만이 이를 처리하는 유일한 방법이다. 예를 들어, (2013 년 3 월처럼) 소프트웨어 버그로 인한 분기이든, 또는 (2015 년 7 월처럼) 채굴자들의 비잔틴 행동으로 인한 무효 상태 커밋이든 비트코인이 실패한 상황들에서, 비즈니스, 개발자, 채굴자 및 그 밖의 조직들의 잘 연결된 커뮤니티는 네트워크 회복을 위해서 참여자들의 무엇을 해야하는지에 대한 사회적 합의를 확립했다. 텐더민트의 경우, 블록체인의 검증인들이 식별될 수 있을 것으로 예상되기 때문에, 필요한 경우, 무효 상태의 커밋을 법률이나 어떤 외부 관할권을 통해 처벌하는 것까지도 가능할 수 있다. - -### TMSP 명세 (TMSP specification) - -TMSP 는 코어에서 애플리케이션으로 전달되는 3 가지 주요 메시지 유형으로 구성된다. 애플리케이션은 대응하는 응답 메시지로 응답한다. - -AppendTx 메시지는 애플리케이션의 핵심 작업수단이다. 블록체인에서의 이체는 이 메시지와 함께 전달된다. 애플리케이션은 AppendTx 메시지와 함께 수신된 각 이체를 현재의 상태, 애플리케이션 프로토콜, 이체의 암호학적 요건 등과 비교해서 검증한다. 이후, 검증된 이체는 값을 키-값 저장장치(key values store)으로 결합하거나 UTXO 데이터베이스를 업데이트하여 애플리케이션 상태를 업데이트해야 한다. - -CheckTx 메시지는 AppendTx 와 유사하지만 이체 검증에만 사용된다. 텐더민트 코어의 mempool(메모리 풀)은 CheckTx 로 이체의 유효성을 검사하고 유효한 이체만 피어들에 전파한다. 애플리케이션들은 이체의 증가 논스 (incrementing nonce)를 검사하고 논스가 오래된 경우, CheckTx 에 오류를 반환할 수도 있다. - -Commit 메시지는 다음 블록 헤더에 포함될 현재 애플리케이션 상태에 대한 암호학적 커밋(cryptographic commitment)을 계산하기 위해 사용된다. 이를 통해 몇가지를 얻을 수 있다. 해당 상태 업데이트에서 불일치들이 포크의 형태로 나타나게 된다. 또한 머클-해시 증명들이 블록-해시와의 비교를 통해 검증될 수 있고 블록-해시는 (투표권에 의한) 검증인 정족수에 의해 서명되기 때문에, 단순한 방식으로 안전한 라이트 클라이언트 개발하는데 도움이 된다. - -추가적 TMSP 메시지를 통해 애플리케이션이 검증인 세트를 추적(keep track of) 및 변경하고, 높이와 커밋 투표 같은 블록 정보를 수신할 수 있다. - -TMSP 요구/응답(TMSP requests/responses)은 간단한 Protobuf 메시지들이다. [스키마 파일(schema file)](https://github.com/tendermint/abci/blob/master/types/types.proto) 확인 - -##### AppendTx - -- **Arguments**: - - `Data ([]byte)`: The request transaction bytes -- **Returns**: - - `Code (uint32)`: Response code - - `Data ([]byte)`: Result bytes, if any - - `Log (string)`: Debug or error message -- **사용법**:<br/> - 이체를 덧붙이고 실행한다. 이체가 유효할 경우, CodeType.OK 를 반환한다. - -##### CheckTx - -- **Arguments**: - - `Data ([]byte)`: The request transaction bytes -- **Returns**: - - `Code (uint32)`: Response code - - `Data ([]byte)`: Result bytes, if any - - `Log (string)`: Debug or error message -- **사용법**:<br/> - 이체를 검증한다. 이 메시지가 상태를 변경(mutate)해서는 안 된다. 이체들은 mempool 계층의 피어들에게 전파되기 전, CheckTx 를 통해 먼저 실행된다. 동일 블록 내 이체의 발생 순서를 고려하기 위해 CheckTx 를 semi-stateful(준-상태기반)로 만들고 Commit 또는 BeginBlock 시에 상태를 클리어(clear) 할 수 있다. - -##### 커밋 (Commit) - -- **Returns**: - - `Data ([]byte)`: The Merkle root hash - - `Log (string)`: Debug or error message -- **사용법**:<br/> - 애플리케이션 상태의 머클 루트 해시를 반환한다. - -##### Query - -- **Arguments**: - - `Data ([]byte)`: The query request bytes -- **Returns**: - - `Code (uint32)`: Response code - - `Data ([]byte)`: The query response bytes - - `Log (string)`: Debug or error message - -##### Flush - -- **사용법**:<br/> - 응답 대기행렬(response queue)을 플러시(flush)한다. types.Application 을 구현하는 애플리케이션들은 이 메시지를 구현할 필요가 없다--프로젝트에 의해 처리되기 때문이다. - -##### Info - -- **Returns**: - - `Data ([]byte)`: The info bytes -- **사용법**:<br/> - 애플리케이션 상태에 관한 정보를 반환한다. 애플리케이션 특정적이다. - -##### SetOption - -- **Arguments**: - - `Key (string)`: Key to set - - `Value (string)`: Value to set for key -- **Returns**: - - `Log (string)`: Debug or error message -- **사용법**:<br/> - 애플리케이션 옵션을 설정한다. 예를 들어, mempool 연결을 위해서는 Key='mode', Value='mempool', 또는 합의 연결을 위해서는 Key='mode', Value='consensus'. 다른 옵션들은 각 애플리케이션마다 다르다. - -##### InitChain - -- **Arguments**: - - `Validators ([]Validator)`: Initial genesis-validators -- **사용법**:<br/> - 제네시스 시, 1 회 호출됨. - -##### BeginBlock - -- **Arguments**: - - `Height (uint64)`: The block height that is starting -- **사용법**:<br/> - 새 블록의 시작을 알린다. 모든 AppendTx 에 앞서 호출됨. - -##### EndBlock - -- **Arguments**: - - `Height (uint64)`: The block height that ended -- **Returns**: - - `Validators ([]Validator)`: Changed validators with new voting powers (0 - to remove) -- **사용법**:<br/> - 블록의 끝을 알린다. 이체 후, 각 커밋에 앞서 호출됨. - -상세한 내용은 [TMSP 리포지토리(TMSP repository)](https://github.com/tendermint/abci) 참조. - -### IBC 패킷 전송 확인응답 (IBC Packet Delivery Acknowledgement) - -발신자가 수신 체인의 패킷전송 확인응답을 원할 여러 가지 경우가 존재한다. 예를 들어 장애가 있을 경우, 송신자는 수신 체인의 상태를 모를 수 있다. 또는 송신자는 (MaxHeight 패킷 필드로) 패킷에 타임아웃을 부여하기를 원하는 반면, 수신 체인이 갑작스런 수신 패킷 수 급증에 의한 서비스 거부(denial-of-service) 공격을 받을 수도 있다. - -이런 경우, 송신자는 초기 패킷 상태를 AckPending 으로 설정하여 전송 확인응답을 요구할 수 있다. 그러면 앱 머클 해시에 abbreviatedIBCPacket 을 포함하여 전송을 확인해 주는 것이 수신 체인의 책임이다. - -![Figure of Zone1, Zone2, and Hub IBC with acknowledgement](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_with_ack.png) - -먼저, 'Zone 1'에 IBCPacket 이 존재함을 증명하는 '허브'가 있다. 그리고 이 허브 위에 IBCBlockCommit 과 IBCPacketTx 가 포스팅된다. 예를 들어 IBCPacketTx 는 다음과 같은 값을 갖는다: - -- `FromChainID`: "Zone1" -- `FromBlockHeight`: 100 (say) -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 (say) - - `Status`: `AckPending` - - `Type`: "coin" - - `MaxHeight`: 350 (say "Hub" is currently at height 300) - - `Payload`: <The bytes of a "coin" payload> - -다음으로, '허브'에 IBCPacket 이 존재함을 증명하는 'Zone 2' 위에 IBCBlockCommit 과 IBCPacketTx 가 포스팅된다. 예를 들면, IBCPacketTx 는 다음과 같다: - -- `FromChainID`: "Hub" -- `FromBlockHeight`: 300 -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 - - `Status`: `AckPending` - - `Type`: "coin" - - `MaxHeight`: 350 - - `Payload`: <The same bytes of a "coin" payload> - -그 다음, 'Zone 2'는 앱-해시(app-hash)에 새로운 상태의 AckSent 를 보이는 생략형 패킷을 반드시 포함해야 한다. 생략형 IBCPacket 이 ‘Zone 2' 위에 존재함을 증명하는 '허브' 위에 IBCBlockCommit 와 IBCPacketTx 가 다시 포스팅된다. 예를 들면 IBCPacketTx 는 다음과 같다: - -- `FromChainID`: "Zone2" -- `FromBlockHeight`: 400 (say) -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 - - `Status`: `AckSent` - - `Type`: "coin" - - `MaxHeight`: 350 - - `PayloadHash`: <The hash bytes of the same "coin" payload> - -끝으로, '허브'는 패킷의 상태를 AckPending 에서 AckReceived 로 업데이트 해야 한다. 이 새로이 완결된 상태의 증거가 'Zone 2'로 되돌아간다. 예를 들면, IBCPacketTx 는 다음과 같은 값을 갖는다: - -- `FromChainID`: "Hub" -- `FromBlockHeight`: 301 -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 - - `Status`: `AckReceived` - - `Type`: "coin" - - `MaxHeight`: 350 - - `PayloadHash`: <The hash bytes of the same "coin" payload> - -한편, 'Zone 1'은 ‘코인’패킷 전송의 실패가 '허브' 상에서 증명되지 않는 한, 성공적으로 전송될 것이라고 가정할 것이다. 위의 예에서, '허브'는 ‘블록 350’를 통해 'Zone 2'의 AckSent 상태를 수신하지 않는다면, 상태를 타임아웃(Timeout)으로 자동 설정했을 것이다. 이 타임아웃 증거가 'Zone 1'에 다시 포스팅 될 수 있고, 모든 토큰은 반환될 수 있다. - -![Figure of Zone1, Zone2, and Hub IBC with acknowledgement and timeout](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_with_ack_timeout.png) - -### 머클 트리와 증명 명세 (Merkle Tree & Proof Specification) - -두 가지의 머클 트리 유형이 텐더민트/코스모스 생태계에서 지원된다: 단순 트리(Simple Tree)와 IAVL+ 트리. - -#### 단순 트리 (Simple Tree) - -단순트리는 요소들(elements)의 정적 리스트(static list)를 위한 머클 트리이다. 항목의 수가 2 의 거듭제곱이 아닐 경우, 잎들이 서로 다른 수준에 위치할 수 있다. 단순 트리는 트리의 양측면을 동일한 높이로 유지하고자 하지만, 좌측이 더 클 수도 있다. 이 머클 트리는 블록의 이체들을 그리고 애플리케이션 상태 루트의 최상위 요소들을 머클화 하기 위해 사용된다. - - * - / \ - / \ - / \ - / \ - * * - / \ / \ - / \ / \ - / \ / \ - * * * h6 - / \ / \ / \ - h0 h1 h2 h3 h4 h5 - - 7요소의 단순트리 - -#### IAVL+ Tree - -IAVL+ 데이터 구조의 목적은 애플리케이션 상태의 키-값 쌍들에 영구 저장(persistent storage)을 제공하는 것이다. 그렇게 하여 결정론적 머클 루트 해시를 효율적으로 계산한다. 트리는 [AVL 알고리즘](https://en.wikipedia.org/wiki/AVL_tree)의 변형(variant)을 사용하여 밸런싱되며(balancing), 모든 연산(operations)은 O(log(n))이다. - -AVL 트리에서는 임의 노드의 2 개 ‘자식 서브트리(child subtree)’의 높이가 최대 1 차이가 난다. 업데이트 시, 이 조건이 위반될 때마다 트리는 이전 트리의 비수정 노드들을 가리키는 O(log(n))의 새 노드들을 만들어서 다시 균형을 잡는다. 기존 AVL 알고리즘에서는 내부 노드들이 키-값 쌍들도 보유할 수 있다. AVL+ 알고리즘(플러스에 주의한다)은 잎 노드 상의 모든 값을 유지하기 위해 AVL 알고리즘을 수정하며, 가지 노드들만 사용하여 키를 저장한다. 이를 통해 알고리즘을 단순화하고, 머클 해시 트레일(merkle hash trail)을 짧게 유지한다. - -AVL+ 트리는 이더리움의 [패트리샤 트라이(Patricia tries)](https://en.wikipedia.org/wiki/Radix_tree)와 유사하다. 장단점이 존재한다. 키들은 IAVL+ 트리에 삽입되기 전에 해싱될 필요가 없고, 따라서 키 공간(key space)에서의 빠른 순서반복(ordered iteration)을 제공한다. 로직의 구현이 간편하고 내부 노드(inner nodes)와 잎 노드(leaf nodes)만 필요로 한다. 머클 증명은 일반적으로 짧고 균형 잡힌 이진 트리이다. 반면에 IAVL+ 트리의 머클 루트는 업데이트의 순서(order of updates)에 의존한다. - -이진 변형(binary variant) 사용이 가능해지면, 이더리움의 패트리샤 트라이 같은 효율적 머클 트리들을 추가로 지원할 것이다. - -### 이체 유형 (Transaction Types) - -일반적으로, 이체들은 TMSP 인터페이스를 통해 코스모스 허브 애플리케이션으로 흘러간다. - -코스모스 허브는 앞으로 SendTx, BondTx, UnbondTx, ReportHackTx, SlashTx, BurnAtomTx, ProposalCreateTx, ProposalVoteTx 등을 포함한 많은 주요 이체 유형들(primary transaction types)을 추가할 것이고 문서화할 것이다. 여기서는 IBC 의 두 주요 이체 유형인 IBCBlockCommitTx 와 IBCPacketTx 에 대해 설명한다. - -#### IBCBlockCommitTx - -IBCBlockCommitTx 이체는 다음으로 구성된다: - -- `ChainID (string)`: The ID of the blockchain -- `BlockHash ([]byte)`: The block-hash bytes, the Merkle root which includes the - app-hash -- `BlockPartsHeader (PartSetHeader)`: The block part-set header bytes, only - needed to verify vote signatures -- `BlockHeight (int)`: The height of the commit -- `BlockRound (int)`: The round of the commit -- `Commit ([]Vote)`: The +⅔ Tendermint `Precommit` votes that comprise a block - commit -- `ValidatorsHash ([]byte)`: A Merkle-tree root hash of the new validator set -- `ValidatorsHashProof (SimpleProof)`: A SimpleTree Merkle-proof for proving the - `ValidatorsHash` against the `BlockHash` -- `AppHash ([]byte)`: A IAVLTree Merkle-tree root hash of the application state -- `AppHashProof (SimpleProof)`: A SimpleTree Merkle-proof for proving the - `AppHash` against the `BlockHash` - -#### IBCPacketTx - -IBCPacket 은 다음으로 구성된다: - -- `Header (IBCPacketHeader)`: The packet header -- `Payload ([]byte)`: The bytes of the packet payload. _Optional_ -- `PayloadHash ([]byte)`: The hash for the bytes of the packet. _Optional_ - -Payload 또는 PayloadHash 가 반드시 존재해야 한다. IBCPacket 의 해시는 Header 와 Payload 의 단순 머클 루트이다. 전체 페이로드(full payload)가 없는 IBCPacket 은 _생략형 패킷_(_abbreviated packet_)이라고 부른다. - -IBCPacketHeader 는 다음으로 구성된다. - -- `SrcChainID (string)`: The source blockchain ID -- `DstChainID (string)`: The destination blockchain ID -- `Number (int)`: A unique number for all packets -- `Status (enum)`: Can be one of `AckPending`, `AckSent`, `AckReceived`, - `NoAck`, or `Timeout` -- `Type (string)`: The types are application-dependent. Cosmos reserves the - "coin" packet type -- `MaxHeight (int)`: If status is not `NoAckWanted` or `AckReceived` by this - height, status becomes `Timeout`. _Optional_ - -IBCPacketTx 이체는 다음으로 구성된다: - -- `FromChainID (string)`: The ID of the blockchain which is providing this - packet; not necessarily the source -- `FromBlockHeight (int)`: The blockchain height in which the following packet - is included (Merkle-ized) in the block-hash of the source chain -- `Packet (IBCPacket)`: A packet of data, whose status may be one of - `AckPending`, `AckSent`, `AckReceived`, `NoAck`, or `Timeout` -- `PacketProof (IAVLProof)`: A IAVLTree Merkle-proof for proving the packet's - hash against the `AppHash` of the source chain at given height - -'허브'를 통해 '존 1'에서 '존 2'로 패킷을 송신하기 위한 순서가 {그림 X}에 도시되어 있다. 먼저, IBCPacketTx 가 패킷이 '존 1'의 앱-상태(app-state)에 포함되어 있음을 '허브'에 증명한다. 그 다음에 또 다른 IBCPacketTx 가 패킷이 '허브'의 앱-상태에 포함되어 있음을 '존 2'에 증명한다. 이 절차 동안, IBCPacket 필드들은 동일하다: SrcChainID 는 언제나 'Zone1(존 1)'이고 DstChainID 는 언제나 'Zone2(존 2)'이다. - -PacketProof 은 반드시 다음과 같은 올바른 머클 증명 경로를 포함해야 한다: - - IBC/<SrcChainID>/<DstChainID>/<Number> - -'존 1'이 '허브'를 통해 '존 2'로 패킷을 전송할 경우, 패킷이 '존 1', '허브' 또는 '존 2'의 어디에서 머클화 되든지 상관 없이 IBCPacket 데이터는 동일하다. 변경 가능한 유일한 필드는 전송 추적을 위한 Status(상태)이다. - -## 감사의 글 (Acknowledgements) - -We thank our friends and peers for assistance in conceptualizing, reviewing, and -providing support for our work with Tendermint and Cosmos. - -- [Zaki Manian](https://github.com/zmanian) of - [SkuChain](http://www.skuchain.com/) provided much help in formatting and - wording, especially under the TMSP section -- [Jehan Tremback](https://github.com/jtremback) of Althea and Dustin Byington - for helping with initial iterations -- [Andrew Miller](https://soc1024.com/) of [Honey - Badger](https://eprint.iacr.org/2016/199) for feedback on consensus -- [Greg Slepak](https://fixingtao.com/) for feedback on consensus and wording -- Also thanks to [Bill Gleim](https://github.com/gleim) and [Seunghwan - Han](http://www.seunghwanhan.com) for various contributions. -- **Your name and organization here for your contribution** - -## 인용 (Citations) - -- [1] Bitcoin: <https://bitcoin.org/bitcoin.pdf> -- [2] ZeroCash: <http://zerocash-project.org/paper> -- [3] Ethereum: <https://github.com/ethereum/wiki/wiki/White-Paper> -- [4] TheDAO: <https://download.slock.it/public/DAO/WhitePaper.pdf> -- [5] Segregated Witness: <https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki> -- [6] BitcoinNG: <https://arxiv.org/pdf/1510.02037v2.pdf> -- [7] Lightning Network: <https://lightning.network/lightning-network-paper-DRAFT-0.5.pdf> -- [8] Tendermint: <https://github.com/tendermint/tendermint/wiki> -- [9] FLP Impossibility: <https://groups.csail.mit.edu/tds/papers/Lynch/jacm85.pdf> -- [10] Slasher: <https://blog.ethereum.org/2014/01/15/slasher-a-punitive-proof-of-stake-algorithm/> -- [11] PBFT: <http://pmg.csail.mit.edu/papers/osdi99.pdf> -- [12] BitShares: <https://bitshares.org/technology/delegated-proof-of-stake-consensus/> -- [13] Stellar: <https://www.stellar.org/papers/stellar-consensus-protocol.pdf> -- [14] Interledger: <https://interledger.org/rfcs/0001-interledger-architecture/> -- [15] Sidechains: <https://blockstream.com/sidechains.pdf> -- [16] Casper: <https://blog.ethereum.org/2015/08/01/introducing-casper-friendly-ghost/> -- [17] TMSP: <https://github.com/tendermint/abci> -- [18] Ethereum Sharding: <https://github.com/ethereum/EIPs/issues/53> -- [19] LibSwift: <http://www.ds.ewi.tudelft.nl/fileadmin/pds/papers/PerformanceAnalysisOfLibswift.pdf> -- [20] DLS: <http://groups.csail.mit.edu/tds/papers/Lynch/jacm88.pdf> -- [21] Thin Client Security: <https://en.bitcoin.it/wiki/Thin_Client_Security> -- [22] Ethereum 2.0 Mauve Paper: <https://cdn.hackaday.io/files/10879465447136/Mauve%20Paper%20Vitalik.pdf> - -#### 기타 링크 (Unsorted links) - -- [https://www.docdroid.net/ec7xGzs/314477721-ethereum-platform-review-opportunities-and-challenges-for-private-and-consortium-blockchains.pdf] diff --git a/docs/resources/whitepaper-pt.md b/docs/resources/whitepaper-pt.md deleted file mode 100644 index aff8c86ff962..000000000000 --- a/docs/resources/whitepaper-pt.md +++ /dev/null @@ -1,1482 +0,0 @@ -# Cosmos - -Uma Rede de Distribuição de Ledgers - -Jae Kwon jae@tendermint.com<br/> -Ethan Buchman ethan@tendermint.com - -Para discussões, [entre no nosso Matrix](https://riot.im/app/#/room/#cosmos:matrix.org)! - -_NOTA: Se você pode ler isso no GitHub, então ainda estamos desenvolvendo este documento ativamente. Por favor, cheque regularmente as atualizações!_ - -\[[toc]] - -O sucesso combinado do ecossistema de código aberto, compartilhamento -de arquivos descentralizado e criptomoedas públicas tem inspirado um conhecimento sobre -protocolos descentralizados na Internet que podem ser utilizados para melhorar radicalmente -a infraestrutura. Vimos aplicações de blockchain especializadas como Bitcoin -[\[1\]][1] (uma criptomoeda), Zerocash [\[2\]][2] (uma criptomoeda para privacidade -), and generalized smart contract platforms such as Ethereum [\[3\]][3], -com inúmeras aplicações distribuídas para a Etherium Virtual Machine (EVM), como Augur (uma previsão -de mercado) e TheDAO [\[4\]][4] (um clube de investimento). - -Contudo, até à data, estas blockchains sofreram uma série de inconvenientes, -incluindo sua ineficiência energética, desempenho fraco ou limitado e -mecanismos de governança imaturos. Propostas de escala -de processamento de transações da Bitcoin, como Testemunhas Separadas [\[5\]][5] e -BitcoinNG [\[6\]][6], soluções de escalonamento vertical que permanecem -limitadas pela capacidade de uma única máquina física, a fim de -proporcionar uma auditabilidade completa. A Rede Lightning [\[7\]][7] pode ajudar -o Bitcoin no quesito volume de transações, deixando algumas transações completamente -fora da carteira, e é bem adequado para micropagamentos e preservando a privacisadade por pagamentos -Rails, mas pode não ser adequado para necessidades de escala mais abrangente. - -Uma solução ideal é a de permitir blockchains paralelos múltiplos para -interoperação, mantendo suas propriedades de segurança. Isto provou -ser difícil, se não impossível, com prova de trabalho. A mineração combinada, por exemplo, -permite que o trabalho feito para proteger uma blockchain mãe seja reutilizado em uma blockchain nova, -mas as transações ainda devem ser validadas, em ordem, por cada nó, e uma -blockchain Merge-mined é vulnerável a ataques se a maioria do poder de -hashing sobre a mãe não é ativamente merge-mined da nova. Uma revisão acadêmica -do [arquiteturas de redes alternativas blockchain -](http://vukolic.com/iNetSec_2015.pdf) é fornecida para -contextualizar, e fornecemos resumos de outras propostas e suas desvantagens em -[Trabalho relatado](#trabalho-relatado). - -Nesse relato nós apresentamos a Cosmos, uma novela da arquitetura de rede blockchain que aborda todos -esses problemas. Cosmos é uma rede de muitos blockchains independentes, chamados -Zonas. As zonas são alimentadas pelo Tendermint Coreork [\[8\]][8], que fornece uma -alta performace, consistência, segurança -[PBFT-como](https://blog.cosmos.network/tendermint-vs-pbft-12e9f294c9ab?gi=20a63f2a00ee) um mecanismo de consenso -rigoroso, onde [fork-responsável](#fork-responsável) tem-se garantias de deter -comportamentos maliciosos. O algoritmo de consenso BFT do Tendermint Core é -bem adaptado para integrar blockchains públicas de prova de estaca. - -A primeira zona na Cosmos é chamada de Cosmos Hub. A Cosmos Hub é uma criptomoeda -multi-asset de prova de estaca com um simples mecanismo de governança -o qual permite a rede se adaptar e atualizar. Além disso, a Cosmos Hub pode ser -extendida por conexão com outras zonas. - -O hub e as zonas da rede Cosmos comunicam-se uma com a outra através de um -protocolo de comunicação Inter-blockchain (IBC), um tipo de UDP ou TCP virtual para -blockchains. Os tokens podem ser transferidos de uma zona para outra com segurança e -rapidez sem necessidade de liquidez cambial entre as zonas. Em vez disso, todas -as transferências de tokens inter-zonas passam pelo Hub Cosmos, que mantêm -a quantidade total de tokens detidas por cada zona. O hub isola cada zona da -falha das outras zonas. Porque qualquer um pode conectar uma nova zona no Hub Cosmos, -o que permite futuras compatibilidades com novas blockchains inovadoras. - -## Tendermint - -Nesta seção, descrevemos o protocolo de consenso da Tendermint e a interface -usada para construir aplicações através dele. Para mais detalhes, consulte o [apêndice](#apêndice). - -### Validadores - -No algorítimo de tolerância e falhas clássicas Bizantinas (BFT), cada node tem o mesmo -peso. Na Tendermint, nodes tem uma quantidade positiva de _poder de voto_, e -esses nodes que tem poder de voto positivo são chamados de _validadores_. Validadores -participam de um protocolo de consenso por transmissão de assinaturas criptográficas, -ou _votos_, para concordar com o próximo bloco. - -Os poderes de voto dos validadores são determinados na gênese, ou são alterados -de acordo com a blockchain, dependendo da aplicação. Por exemplo, -em uma aplicação de prova de participação, como o Hub da Cosmos, o poder de voto pode ser -determinado pela quantidade de tokens usados como garantia. - -_NOTA: Frações como ⅔ e ⅓ referem-se a frações do total de votos, -nunca o número total de validadores, a menos que todos os validadores tenham -peso._ -_NOTE: +⅔ significa "mais do que ⅔", enquanto ⅓+ significa "⅓ ou mais"._ - -### Consenso - -Tendermint é um protocolo de consenso BFT parcialmente sincronizado e derivado do -algoritmo de consenso DLS [\[20\]][20]. Tendermint é notável por sua simplicidade, -desempenho, e [fork-responsável](#fork-responsável). O protocolo -requer um grupo determinado de validadores, onde cada validador é identificado por -sua chave pública. Validadores chegarão a um consenso em um bloco por vez, -onde um bloco é uma lista de transações. A votação para o consenso sobre um bloco -acontece por rodada. Cada rodada tem uma líder-de-rodada, ou proponente, que propõe um bloco. Os -validadores, em seguida, votam, por etapas, sobre a aceitação do bloco proposto -ou passam para a próxima rodada. O proponente de uma rodada é escolhido -de acordo com uma lista ordenada de validadores, proporcionalmente à seu -poder de voto. - -Os detalhes completos do protocolo estão descritos -[aqui](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm). - -A segurança da Tendermint é baseada na tolerância e falhas clássicas Bizantinas ótimas -através de super-maioria (+⅔) e um mecanismo de bloqueio. Juntas, elas garantem -isso: - -- ⅓+ o poder de voto deve ser bizantino devido a violações de segurança, onde mais -   que dois valores são comprometidos. -- se algum conjunto de validadores tiver sucesso em violar a segurança, ou mesmo tentarem - para isso, eles podem ser identificados pelo protocolo. Isso inclui tanto o voto - para blocos conflitantes quanto a transmissão de votos injustificados. - -Apesar de suas fortes garantias, a Tendermint oferece um desempenho excepcional. Dentro -de Benchmarks de 64 nós distribuídos em 7 datacenters em 5 continentes, em -nuvens de commodities, o consenso da Tendermint pode processar milhares de -transações por segundo, com tempo de resposta entre um a dois -segundos. Notavelmente, o desempenho muito além de mil transações por segundo -é mantido mesmo em condições adversas, com validadores falhando ou -combinando votos maliciosamente. Veja a figura abaixo para mais detalhes. - -![Figura do desempenho da Tendermint](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/images/tendermint_throughput_blocksize.png) - -### Clientes Light - -O principal benefício do algoritmo de consenso da Tendermint é um cliente leve e simplificado -de segurança, tornando-o um candidato ideal para o uso de dispositivos móveis e casos de uso na -internet. Enquanto um cliente leve do Bitcoin deve sincronizar blockchains e encontrar -o que tem mais prova de trabalho, os clientes light da Tendermint precisa apenas -das alterações feitas pelo conjunto dos validadores, em seguida, verifica-se o +⅔ PreCommits -no último bloco para determinar o estado atual. - -Provas claras e sucintas do cliente também permite [comunicação-inter- -blockchain](#comunicação-inter-blockchain-ibc). - -### Previnindo ataques - -A Tendermint dispõe de medidas de proteção para evitar -ataques, como [gastos duplos em longa-distância-sem-estaca double -spends](#previnindo-ataques-de-longa-distância) e -[censura](#superando-forks-e-censurando-ataques). Esses são discutidos -completamente no [apêndice](#apêndice). - -### TMSP - -O algoritmo de consenso Tendermint é implementado através de um programa chamado Tendermint -Core. O Tendermint Core é um "mecanismo de consenso" independente de aplicações que -transformam qualquer aplicação blackbox em uma réplica distribuída na -Blockchain. Tendermint Core conecta-se ao blockchain -através de aplicações do Tendermint Socket Protocol (TMSP) [\[17\]][17]. Assim, o TMSP -permite que as aplicações da blockchain sejam programadas em qualquer idioma, não apenas -a linguagem de programação que o mecanismo de consenso é escrito, além disso, -o TMSP torna possível a troca fácil da camada de consenso de qualquer -tipo de blockchain. - -Nós fizemos uma analogia com a bem conhecida criptogradia do Bitcoin. Bitcoin é uma -blockchain de criptomoedas onde cada nó mantém uma Unspent totalmente auditada -e banco de dados de saída de transação (UTXO). Se alguém quisesse criar um Bitcoin-like -TMS, a Tendermint Core seria responsável por - -- Compartilhar blocos e transações entre os nós -- Estabelecer uma ordem de transações canônica/imutável (a blockchain) - -Entretanto, o aplicativo TMSP seria responsável por - -- Manter o banco de dados UTXO -- Validar a criptografia das assinaturas das transações -- Previnir transações vindas de gastos de fundos não exisentes -- Permitir aos clientes a consulta do banco de dados UTXO - -Tendermint é capaz de decompor o design da blockchain, oferecendo um simples -API entre o processo da aplicação e o processo do consenso. - -## Visão Geral da Cosmos - -Cosmos é uma rede de blockchains paralelos e independentes que são alimentadas pelo -clássico algorítimo de consenso BFT como a Tendermint -[1](https://github.com/tendermint/tendermint). - -A primeira blockchain dessa rede será a Cosmos Hub. A Cosmos Hub -conecta as outras blockchains (ou _zonas_) através do protocolo de comunicação-inter- -blockchain. A Cosmos Hub rastreia vários tipos de tokens e mantém -registo do número total de tokens em cada zona ligada. Os tokens podem ser -transferidos de uma zona para outra de forma segura e rápida, sem necessidade de -uma troca líquida entre zonas, porque todas as transferências de moedas ocorre -através da Cosmos Hub. - -Essa arquitetura resolve muitos dos problemas enfrentados atualmente pelas blockchains, -tais como interoperabilidade de aplicativos, escalabilidade e capacidade de atualização contínua. -Por exemplo, as zonas baseadas do Bitcoin, Go-Ethereum, CryptoNote, ZCash, ou qualquer -sistema blockchain pode ser ligado ao Cosmos Hub. Essas zonas permite a Cosmos -o escalonamento infinito para atender a demanda global de transações. As Zonas também são um grande -apoio para a exchange distribuída, que também serão apoiadas. - -Cosmos não é apenas uma única ledger distribuídos, o Cosmos Hub não é um -jardim cercado ou o centro do universo. Estamos elaborando um protocolo para -uma rede aberta de legers distribuídos que pode servir como um novo -futuros para sistemas financeiros, baseados em princípios de criptografia, economia -teoria de consenso, transparência e responsabilidade. - -### Tendermint-BFT - -O Cosmos Hub é a primeira blockchain pública na rede Cosmos, alimentada pelo -algoritimo de consenso BFT Tendermint. A Tendermint é um projeto de fonte aberta que -nasceu em 2014 para abordar a velocidade, a escalabilidade e as questões -do algoritimo de consenso da prova-de-trabalho do Bitcoin. Usando e melhorando -algoritmos BFT comprovados e desenvolvidos no MIT em 1988 [\[20\]][20], o time Tendermint foi o primeiro a -que demonstrou conceitualmente uma prova de estaca das criptomoedas que aborda o -problema de "sem-estaca" sofrido pelas criptomoedas da primeira geração -tais como NXT e BitShares. - -Hoje, praticamente todas carteiras móveis de Bitcoin usam servidores confiáveis, que fornece -a elas transações com verificação. Isso porque a prova-de-trabalho exige -muitas confirmações antes que uma transação possa ser considerada -irreversivel e completa. Os ataques de gasto-duplo já foram demonstrados em -serviços como a CoinBase. - -Ao contrário de outros sistemas de consenso blockchain, a Tendermint oferece -comprovação segura de pagamento para o cliente móvel. Uma vez que a Mint é -projetada para nunca passar por um fork, carteiras móveis podem receber confirmações de transações -instantâneas, o que torna os pagamentos confiáveis e práticos através de -smartphones. Isto tem implicações significativas para as aplicações da Internet. - -Validadores na Cosmos tem uma função similar aos mineiros do Bitcoin, mas usam -assinaturas criptografadas para votar. Validadores são máquinas seguras e dedicadas -que são responsáveis por validar os blocos. Os não validadores podem delegar através de seus tokens estacados -(chamados "atoms") a qualquer validador para ganhar uma parcela das taxas da blockchain -e recompensas de atoms, mas eles correm o risco de serem punidos (cortados) se o -o validador de delegados for invadido ou violar o protocolo. A segurança comprovada -garantida pelo consenso BFT da Tendermint, e o depósito de garantia das -partes interessadas - validadores e delegados - fornecem dados prováveis, -segurança para os nós e clientes light. - -### Governança - -Ledgers de distribuição pública devem ser constituídos de um sistema de governança. -O Bitcoin confia na Fundação Bitcoin e na mineração para -coordenar upgrades, mas este é um processo lento. Ethereum foi dividido em ETH e -ETC depois de hard-fork para se recuperar do hack TheDAO, em grande parte porque não havia -contrato sócial prévio, nem um mecanismo para tomar tais decisões. - -Os validadores e os delegados do Cosmos Hub podem votar propostas que -alteraram automaticamente os parâmetros predefinidos do sistema (tal como o gás limite do -bloco), coordenar upgrades, bem como votar em emendas para a -constituição que governa as políticas do Cosmos Hub. A Constituição -permite a coesão entre as partes interessadas em questões como o roubo -e bugs (como o incidente TheDAO), permitindo uma resolução mais rápida e mais limpa. - -Cada zona pode ter sua própria constituição e mecanismo de governança. -Por exemplo, o Cosmos Hub pode ter uma constituição que reforça a imutabilidade -no Hub (sem roll-backs, a não ser por bugs em implementações dos nós do Cosmos Hub), -enquanto cada zona pode ter sua própria política sobre os roll-backs. - -Ao disponibilizar a interoperabilidade em diferentes políticas das zonas, a rede Cosmos -dá aos usuários total liberdade e potenciais permissões para -experimentos. - -## O Hub e as Zonas - -Aqui nós descrevemos o modelo do roteiro de descentralização e ecalabilidade. Cosmos é uma -rede de muitas blockchains alimentadas pela Tendermint. Enquanto existirem propostas visando -criar um"blockchain solitário" com ordens de transações cheias, a Cosmos -permite que muitas blockchains rodem junto de outra enquanto mantêm a -interoperabilidade. - -Basicamente, o Cosmos Hub gerencia várias blockchains independentes chamadas "zonas" -(as vezes chamadas de "shards", em referência a técnica de escalonamento de -bando de dados conhecida como "sharding"). Uma constante transmissão de blocos recentes das -zonas atuando no Hub permite ao Hub manter o estado de cada zona atualizado. -Sendo assim, cada zona mantêm ativa o estado do Hub (mas as zonas não se mantêm ativas -com qualquer outro exceto o Hub). Pacotes de informação são -então comunicados de uma zona para outra atráves de Merkle-proofs como evidências, -essas informações são enviadas e recebidas. Esse mecanismo é chamado de -comunicação inter-blockchain, ou IBC para encurtar. - -![Figura de reconhecimento -do hub e das zonas](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/images/hub_and_zones.png) - -Qualquer uma das zonas podem ser hubs para formar gráficos acíclicos, mas -mas para deixar claro, nós vamos apenas descrever uma simples configuração para -um único hub, e várias zonas que não são hubs. - -### O Hub - -O Cosmos Hub é uma blockchain que hospeda um ledger de distribuíção de multi-asset, -onde os tokens podem ser mantidos por usuários individuais ou pelas próprias zonas. Esses -tokens podem ser movidos de uma zona para outra em um pacote IBC especial chamado -"coin packet". O hub é responsavel por preservar a manutenção global de -toda a quantia de cada token nas zonas. As transações de moedas no pacote IBC -precisam ser feitas pelo remetente, hub, e blockchain recebedor. - -Desde a atuação do Cosmos Hub como ledger principal para todos o -sistema, a segurança do Hub é de suma importância. Enquanto cada -zona pode ser uma blockchain Tendermint que é segurada por 4((ou talvez -menos caso o consenso BFT não seja necessário), o Hub precisa ser segurado por uma descentralização -globalizada feita pelos validadores que podem evitar os mais severos tipos de -ataques, como uma partição de rede continental ou um estado-nação fazendo -ataques. - -### As Zonas - -Uma zona Cosmos é uma blockchain independente das trocas de mensagens IBC com o -Hub. Na perspectiva do Hub, uma zona é uma conta multi-asset dynamic-membership -multi-signature que pode enviar e receber tokens usando pacotes IBC. Como -uma conta de criptomoeda, uma zona não pode transferir mais tokens do que ela possui, mas -pode receber tokens de outras que os tem. Uma zona pode ser usada como uma -"fonte" de um ou mais tipos de tokens, garantindo o poder de aumentar o estoque desse -token. - -Os atoms do Cosmos Hub podem ser estacados por validadores de uma zona conectada ao -Hub. Enquanto os ataques de gasto-duplo nesses zonas podem resultar em um core dos -atoms com o fork-responsável da Tendermint, uma zona onde +⅔ do poder de voto -são Bizantinos podem deixar o estado inválido. O Cosmos Hub não verifica ou -executa transações ocorridas em outras zonas, então essa é uma responsabilidade dos -usuários para enviar os tokes ara zonas que eles confiem. Futuramente, o sistema de -governança do Cosmos Hub irá implementar propostas para o Hub e para as falhas -das zonas. Por exemplo, um token de saída transferido para algumas (ou todas) zonas podem ser -segurados em caso de uma emergência de quebra de circuito das zonas(uma parada temporária -nas transferências dos tokens) quando um ataque é detectado. - -## Comunicação Inter-blockchain (IBC) - -Agora nós olhamos para como o Hub e as zonas vão se comunicar. Por exemplo, se -aqui são três blockchains, "Zona1", "Zona2", and "Hub", e nós queremos que a -"Zona1" produza um pacote destinado para a "Zona2" indo através do "Hub". Para mover um -pacote de uma blockchain para outra, uma prova é feita na -cadeia recebedora. A prova atesta esse envio publicado na cadeia de destino por uma alegação -de pacote. Para a cadeia recebedora checar essa prova, isso é possível -por um block principal de envio. Esse mecanismo é similar ao usado por -cadeias paralelas, que requerem duas cadeias interagindo uma com a outra via -transmissões bidirecionais por dados de prova-de-existência (transações). - -O protocolo IBC pode naturalmente ser definido usando dois tipos de transações: uma -transação `IBCBlockCommitTx`, a qual permite uma blockchain provar para qualquer -espectador o mais recente hash-de-bloco, e uma transação `IBCPacketTx`, a qual -permite uma blockchain provar para qualquer espectador que o pacote recebido foi realmente -publicado pelo remetente, via Merkle-proof para um hash-de-bloco -recente. - -Ao misturar o mecanismo ICB em duas transações separadas, nós permitimos -que o mecanismo de mercado de taxa nativa da blockchain recebedora determine quais pacotes -irão se comprometer (isto é, ser reconhecido), permitindo simultaneamente que uma -blockchain envie de quantos pacotes de saída forem permitidos. - -![Figura da Zona1, Zona2, e Hub IBC sem -reconhecimento](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_without_ack.png) - -No exemplo acima, para atualizar o hash de blocos da -"Zona1" no "Hub" (ou do "Hub" para a "Zona2"), uma transação `IBCBlockCommitTx` -precisa ser feita no "Hub" com o hash de bloco da "Zona1" (ou na -"Zona2" com o hash de bloco do "Hub"). - -_Veja [IBCBlockCommitTx](#ibcblockcommittx) e [IBCPacketTx](#ibcpacketcommit) -para mais informações sobre os 2 tipos de transação IBC._ - -## Casos de Uso - -### Exchange Distribuídas - -Da mesma forma que Bitcoin é mais seguro por ter uma distribuíção, -e replicação em massa, podemos tornar as exchanges menos vulneráveis a -Hacks internos executando-a no blockchain. Chamamos isso de exchange -distribuída. - -O que a comunidade de criptomoedas chama hoje de intercâmbio descentralizado -baseado em algo chamado transações "atomic cross-chain" (AXC). Com uma transação -AXC, dois usuários em duas diferentes cadeias podem fazer duas transações -de transferências que serão feitas juntas nas duas ledgers, ou nenhuma (isto é, -Atomicamente). Por exemplo, dois usuários podem trocar bitcoins por ether (ou qualquer dois -Tokens em dois ledgers diferentes) usando transações AXC, mesmo que o Bitcoin -e o Ethereum não estão conectados entre si. O benefício de executar um -troca em transações AXC é que nenhum dos usuários precisam confiar um no outro ou -no serviço de correspondência comercial. A desvantagem é que ambas as partes precisam estar -on-line para o negócio ocorrer. - -Outro tipo de intercâmbio descentralizado é um sistema de -exchange que funciona em seu próprio blockchain. Os usuários deste tipo de exchange podem -enviar uma ordem de limite e desligar o computador, e o negócio pode ser executado -sem que o usuário esteja online. O blockchain combina e completa o negócio -em nome do negociante. - -Uma exchange centralizada pode criar um vasto livro de ordens de ordens e -atrair mais comerciantes. A liquidez gera mais liquidez no mundo cambial, -e assim há um forte efeito na rede (ou pelo menos efeito de vencedor-leva-mais) -no negócio de câmbio. A atual líder para troca de criptomoedas -hoje é a Poloniex com um volume de 24 milhões de dólares por dia, e em segundo lugar a -Bitfinex com um volume de US$5 milhões por dia. Dados esses fortes efeitos na rede, -é improvável que as exchanges descentralizadas baseadas no AXC ganhem volume -centrais. Para uma exchange descentralizada competir com um -exchange centralizada, seria necessário dar suporte aos livros de -ordens. Somente uma exchange distribuída em uma blockchain pode fornecer isso. - -Tendermint fornece benefícios adicionais para realizar uma transação mais rápida. Com a -finalidade de dar prioridade a rapidez sem sacrificar a consistência, as zonas no Cosmos podem -finalizar transações rápidas - tanto para transações de ordem de -transferências de tokens quanto para outras zonas IBC. - -Dado o estado das exchanges de criptomoedas hoje em dia, uma grande -exchange distribuída da Cosmos (aka o Cosmos DEX). A transação e -a capacidade de processamento, bem como a latência de processos, podem ser -centrais. Os comerciantes podem enviar ordens de limite que podem ser executadas -sem que ambas as partes tenham que estar online. E com Tendermint, o Cosmos Hub, -e o IBC, os comerciantes podem mover fundos dentro e fora da exchange e para outras -zonas com rapidez. - -### Pegging para Outras Criptomoedas - -Uma zona privilegiada pode agir como token simbolico de uma -criptomoeda. A peg é semelhante à relação entre uma -zona e o Cosmos Hub; Ambos devem manter-se atualizados com os -outros, afim de verificar provas de que os tokens passaram de um para o outro. A -Peg-zone na rede Cosmos mantém-se com o Hub, bem como o -outra cryptomoeda. A indireção através da peg-zone permite a lógica de -que o Hub permaceça simples e imutável para outras estratégias de consenso blockchain -como a mineração de prova-de-trabalho do Bitcoin. - -Por exemplo, uma zona Cosmos com um conjunto validador específico, possivelmente o mesmo que -o Hub, poderia atuar como um ether-peg, onde a aplicação TMSP sobre -a zona ("peg-zone") tem mecanismos para trocar mensagens IBC com um -Peg-contract na blockchain (a "origem"). Este contrato -permite que os titulares de ether enviem ether para a zona de peg, enviando-o para -Peg-contract na Ethereum. Uma vez que o ether é recebido pelo peg-contract, o ether -não pode ser retirado a menos que um pacote IBC apropriado seja recebido pelo -Peg-contract da peg-zone. Quando uma zona recebe um pacote IBC provando -que o ether foi recebido no peg-contract para uma determinada conta Ethereum, -a conta correspondente é criada na peg-zone com esse saldo. O ether na -peg-zone ("pegged-ether") pode então ser transferido para o Hub, -e mais tarde ser destruído com uma transação que envia para um determinado -endereço de retirada no Ethereum. Um pacote IBC provando que a transação -na Peg-Zone podem ser lançados no peg-contract Ethereum para permitir que o -Ether seja retirado. - -Naturalmente, o risco do contrato do pegging e um conjunto de validadores desonestos. -bizantino. -O poder de voto bizantino poderia causar um fork, retirando o ether do -peg-contract mantendo o pegged-ether na peg-zone. Na pior das hipóteses, -\+⅔ do poder de voto bizantino pode roubar o ether daqueles que o enviaram para o -peg-contract, desviando-se da pegging e da peg-zone de origem. - -É possível abordar essas questões projetando o peg para ser totalmente -responsável. Por exemplo, todos os pacotes IBC, a partir do hub de -origem, poderão exigir um reconhecimento pela zona de fixação de tal forma que -as transições de estados da peg-zone podem ser desafiadas de forma eficiente e verificadas pelo -hub ou pelo peg-contract de origem. O Hub e a origem devem -permitir que os validadores da zona de fixação apresentem garantias e as transferências -contratuais devem ser atrasadas (e um prazo de vinculação de colateral suficientemente -longo) para permitir que quaisquer desafios sejam feitos por auditores independentes. Nós saímos -da concepção, da especificação e implementação deste sistema aberto como uma -futura proposta de melhoria da Cosmos, a ser aprovada pela governança do sistema do Cosmos -Hub. - -Embora a atmosfera sociopolítica ainda não esteja bastante desenvolvida, podemos -aumentar o mecanismo para permitir que as zonas se liguem as moedas FIAT de um -estado-nação, formando um validador responsável estabelecido a partir de uma combinação da -moeda da nação, mais particularmente, pelos seus bancos. Claro, -precauções adicionais devem ser tomadas para aceitar apenas moedas apoiadas por -sistemas que possam reforçar a capacidade de auditoria das atividades dos bancos -e de notário grupos de grandes instituições de confiança. - -Um resultado dessa integração poderia ser, por exemplo, permitir que -uma conta em um banco na zona possa mover dólares de sua conta bancária -para outras contas na zona, ou para o hub, ou inteiramente para outra zona. -Nesse sentido, o Cosmos Hub pode atuar como um canal sem -moedas e criptomoedas, removendo as barreiras que limitariam -sua interoperabilidade com o mundo dos intercâmbios. - -### Ethereum Scaling - -Resolver o problema de escalonamento é um problema aberto para a Ethereum. Atualmente, -os nós Ethereum processam cada transação única e também armazenam todos os estados. -[link](https://docs.google.com/presentation/d/1CjD0W4l4-CwHKUvfF5Vlps76fKLEC6pIwu1a_kC_YRQ/mobilepresent?slide=id.gd284b9333_0_28). - -Desde que a Tendermint pode realizar os blocos muito mais rápido do que a prova-de-trabalho da Ethereum, -as zonas EVM alimentadas e operando pelo consenso da Tendermint -fornecem maior desempenho para blocos da blockchain Ethereum. Além disso, embora o -Cosmos Hub e o mecanismo de pacotes IBC não permitam a execução da lógica de contratos -arbitrários, podem ser usados para coordenar os movimentos Ethereum e a execução de -contratos simbólicos em diferentes zonas, fornecendo uma base para -o token ethereum através de sharding. - -### Integração de Multi-Aplicação - -As zonas Cosmos executam lógica de aplicação arbitrária, que é definida no início da -vida da zona e podem potencialmente ser atualizados ao longo do tempo pela governança. Essa flexibilidade -permite que as zonas Cosmos ajam como pegs para outras criptomoedas como Ethereum ou -Bitcoin, e também permite derivados desses blockchains, que utilizam a -mesma base de código, mas com um conjunto de validador diferente e distribuição inicial. Isto -permite que muitos tipos de criptomoedas existentes, como as Ethereum, -Zerocash, Bitcoin, CryptoNote e assim por diante, possam ser usados com o Tendermint Core, -que é um motor de consenso de maior desempenho, em uma rede comum, abrindo -oportunidade de interoperabilidade entre as plataformas. Além disso, como -multi-asset blockchain, uma única transação pode conter vários -onde cada entrada pode ser qualquer tipo de token, permitindo a Cosmos -ser uma plataforma para a exchange descentralizada, mesmo que as ordens sejam -para outras plataformas. Alternativamente, uma zona pode servir como um -fault-tolerant (com livros de ordens), o que pode ser uma melhoria -nas exchanges centralizadas de criptomoeda que tendem a ser invadidas com -o tempo. - -As zonas também podem servir como versões bloqueadas de empresas e -sistemas, onde partes de um serviço particular da organização ou grupo de organizações -que são tradicionalmente executadas como um aplicativo TMSP -em uma certa zona, o que lhe permite receber a segurança e a interoperabilidade da -rede pública Cosmos sem sacrificar o controle sobre o serviço subjacente. -Assim, a Cosmos pode oferecer o melhor da tecnologia blockchain para ambos os mundos e -para as organizações, que se recusam a deixar completamente o controle -para um distribuidor terceirizado. - -### Redução de partição de rede - -Alguns afirmam que um grande problema da coerência-favorecendo algoritmos de consenso -como o Tendermint é que qualquer partição de rede que faz com que não haja uma única -partição com +⅔ de poder de votação (por exemplo, ⅓+ ficando offline) irá parar o consenso -completamente. A arquitetura Cosmos pode ajudar a mitigar esse problema usando umas -zonas regionais autônomas, onde o poder de voto para cada zona é -distribuído com base em uma região geográfica comum. Por exemplo, um -parâmetro pode ser para cidades individuais, ou regiões, para operar suas próprias zonas -de partilha com um centro em comum (por exemplo, o Cosmos Hub), permitindo que a -o hub possa parar devido a uma paralisação de rede temporária. -Observe que isso permite uma geologia real, política e rede-topológica, -que são recursos a serem considerados no projeto de sistemas robustos federados de fault-tolerant. - -### Sistema de Resolução de Nomes Federados - -NameCoin foi uma das primeiras blockchains a tentar resolver o -problema de resolução de nomes através de uma adaptação da blockchain do Bitcoin. Infelizmente -têm ocorrido várias questões com esta abordagem. - -Com a Namecoin, podemos verificar que, por exemplo, o nome <em>@satoshi</em> foi registrado como -particular, em algum momento do passado, mas não saberíamos se -a chave pública tinha sido atualizada recentemente, a menos que baixassemos todos os blocos -desde a última atualização desse nome. Isto é devido as limitações do modelo de -Merkle-ization de UTXO do Bitcoin, onde somente as transações (não -mutáveis) são Merkle-ized no hash do bloco. Isso nos permite -provar a existência, mas não a não-existência de atualizações posteriores a um nome. Assim, nós -não podemos saber com certeza o valor mais recente de um nome sem confiar em um -nó, ou recorrer a gastos significativos na largura de banda, baixando o -Blockchain. - -Mesmo se uma árvore de pesquisa Merkle-ized for implementada na NameCoin, sua dependência -sobre a prova-de-trabalho torna a verificação do cliente light problemática. Os clientes light devem -baixar uma cópia completa dos cabeçalhos para todos os blocos em toda a blockchain -(ou pelo menos todos os cabeçalhos desde a última atualização de um nome). Isso significa que -os requisitos de largura de banda crescem linearmente com a o passar do tempo [\[21\]][21]. -Além disso, as mudanças de nome em um bloco de prova-de-trabalho requerem -a confirmação do trabalho, o que pode levar até uma hora -no Bitcoin. - -Com Tendermint, tudo o que precisamos é o hash de bloco mais recente assinado por um quorum de -validadores (por poder de voto), e uma prova Merkle para o valor atual associado -com o nome. Isto torna possível ter uma solução sucinta, rápida e segura -para a verificação de valores de nome no cliente light. - -Na Cosmos, podemos aplicar este conceito e estendê-lo ainda mais. Cada -zona de registro de nomes na Cosmos pode ter um domínio de nível superior (TLD) -associado, como o ".com" ou ".org", e cada zona de registro de nome pode ter -suas próprias regras de governança e registro. - -## Emissão e Incentivos - -### O Token Atom - -Enquanto o Cosmos Hub é um ledger de distribuíção multi-asset, há um token nativo -especial chamado _atom_. Os atoms são o únicos símbolos do Cosmos -Hub. Os atoms são uma licença para o titular votar, validar ou delegar -validadores. Como o ether da Ethereum, os atoms também podem ser usados para -reduzir o spam. Atoms inflacionários adicionais e as taxas do bloco de transação -são recompensadas pelos validadores e delegados que -o validarão. - -A transação `BurnAtomTx` pode ser usada para cobrir proporcionalmente a quantidade -de tokens reservados para a pool. - -#### Levantamento de Fundos - -A distribuição inicial dos tokens atom e validadores na Genesis vão para os -doadores do Levantamento de Fundos da Cosmos (75%), doadores pesados (5%), Fundação da Rede -Cosmos (10%), e a ALL IN BITS, Inc (10%). A partir da Genesis em diante, 1/3 da -quantidade total de atoms será recompensada aos validadores e delegados durante -todo o ano. - -Veja o [Plano Cosmos](https://github.com/cosmos/cosmos/blob/master/PLAN.md) -para detalhes adicionais. - -#### Investindo - -Para evitar que o levantamento de fundos atraia especuladores de curto prazo apenas interessados -em esquemas de pump and dump, os atoms da Genesis não serão transferíveis até -eles tenham investido. Cada conta irá adquirir atoms durante um período de 2 anos com -taxa constante a cada hora, determinada pelo número total de atoms da Genesis/(2* -365 * 24) horas. Os atoms ganhos pela recompensa do bloco são pré-investidos, -e podem ser transferidos imediatamente, de modo que os validadores e os delegados ligados possam ganhar -mais da metade de seus atoms da Genesis após o primeiro ano. - -### Limitações do Número de Validadores - -Diferentemente do Bitcoin ou de outros blockchains de prova-de-trabalho, o blockchain Tendermint será -mais lento com mais validadores devido ao aumento da complexidade da comunicação. -Felizmente, podemos oferecer suporte a validadores suficientes para a -distribuição na Blockchain com tempos de confirmação de transação muito mais rápidos e, através de -largura de banda, armazenamento e aumento da capacidade de computação paralela, seremos capazes de -ter mais validadores no futuro. - -No dia da Genesis, o número máximo de validadores será definido como 100, -o número aumentará a uma taxa de 13% durante 10 anos até atingir a marca de 300 -Validadores. - - Ano 0: 100 - Ano 1: 113 - Ano 2: 127 - Ano 3: 144 - Ano 4: 163 - Ano 5: 184 - Ano 6: 208 - Ano 7: 235 - Ano 8: 265 - Ano 9: 300 - Ano 10: 300 - ... - -### Tornando-se um Validador depois do dia da Genesis - -Os titulares de atoms que ainda não são capazes de se tornarem validadores assinados e -submeter uma transação `BondTx`. A quantidade de atoms fornecida como garantia -deve ser diferente de zero. Qualquer pessoa pode se tornar um validador a qualquer momento, exceto quando o -tamanho do conjunto de validadores atual é maior que o número máximo de -validadores permitidos. Nesse caso, a transação só é válida se o montante -de atoms é maior do que a quantidade de atoms efetivos mantidos pelo menor -validador, onde atoms eficazes incluem atoms delegados. Quando um novo validador -substitui um validador existente de tal forma, o validador existente torna-se -inativo e todos os atoms e atoms delegados entram no estado de unbonding. - -### Penalidades para Validadores - -Deve haver alguma penalidade imposta aos validadores por qualquer desvio intencional -ou não intencional do protocolo sancionado. Algumas evidências são imediatamente admissíveis, -como um double-sign na mesma altura e volta, ou uma violação de "prevote-the-lock" -(uma regra do protocolo de consenso Tendermint). Tais evidências resultarão em que o -validador perca sua boa reputação e seus átomos ligados, bem como sua proporção de tokens -na pool reserva - coletivamente chamados de "stake" - serão cortados. - -Às vezes, os validadores não estarão disponíveis, devido a interrupções na rede regional, -falha de energia ou outros motivos. Se, em qualquer ponto nos blocos `ValidatorTimeoutWindow` -anteriores, o voto de validação de um validador não estiver incluído na cadeia de -blocos mais do que `ValidatorTimeoutMaxAbsent` vezes, esse validador ficará inativo e -perderá `ValidatorTimeoutPenalty` (PADRÃO DE 1%) de sua participação. - -Alguns comportamentos "maliciosos" não produzem provas obviamente discerníveis sobre -a blockchain. Nesses casos, os validadores podem coordenar fora da banda para forçar -o tempo limite desses validadores maliciosos, se houver um consenso majoritário. - -Em situações em que o Cosmos Hub parar devido a uma coalizão de ⅓+ de poder de voto -offline, ou em situações onde uma coalizão de ⅓+ de poder de voto censurar evidências de -comportamento malicioso entrando na blockchain, o hub deve recuperar com um hard-fork -de proposta reorganizacional. (Link to "Forks and Censorship Attacks"). - -### Taxas de Transação - -Os validadores do Cosmos Hub podem aceitar qualquer tipo de token ou combinação -de tipos como taxas para processar uma transação. Cada validador pode fixar subjetivamente a -taxa de câmbio que quiser e escolher as transações que desejar, desde que o `BlockGasLimit` -não seja excedido. As taxas cobradas, menos quaisquer impostos especificados abaixo, -são redistribuídas aos stakeholders ligados em proporção aos seus átomos ligados, cada `ValidatorPayoutPeriod` (PADRÃO DE 1 hora). - -Das taxas de transação cobradas, `ReserveTax` (PADRÃO DE 2%) irá para a pool reserva -para aumentar a pool reserva e aumentar a segurança e o valor da rede Cosmos. Além disso, um -`CommonsTax` (PADRÃO DE 3%) irá para o financiamento de bens comuns. Estes fundos vão para o -`CustodianAddress` para ser distribuído de acordo com as decisões tomadas pelo sistema de governança. - -Os titulares de átomos que delegam o seu poder de voto a outros validadores pagam uma comissão -ao validador delegado. A comissão pode ser definida por cada validador. - -### Incentivando Hackers - -A segurança do Cosmos Hub é uma função da segurança dos validadores subjacentes e da escolha -da delegação pelos delegados. A fim de incentivar a descoberta e notificação precoce de vulnerabilidades -encontradas, o Cosmos Hub incentiva os hackers a publicar exploits bem sucedidos através de uma transação -`ReportHackTx` que diz," Este validador foi hackeado. Por favor, envie recompensa para este endereço". -Depois de tal exploração, o validador e os delegados ficarão inativos, `HackPunishmentRatio` (PADRÃO DE 5%) -dos átomos de todos serão cortados, e`HackRewardRatio` (PADRÃO DE 5%) dos átomos de todos -serão recompensado com o endereço de recompensa do hacker. O validador deve recuperar os átomos -restantes usando sua chave de backup. - -Para evitar que esse recurso seja abusado para transferir átomos não invadidos, -a porção de átomos adquirido vs relativo de validadores e delegados antes e depois do `ReportHackTx` -permanecerá o mesmo, e o bounty do hacker irá incluir alguns átomos relativos, se houver. - -### Específicação de Governança - -O Cosmos Hub é operado por uma organização distribuída que requer um mecanismo de -governança bem definido para coordenar várias mudanças na blockchain, como parâmetros -variáveis do sistema, bem como atualizações de software e emendas constitucionais. - -Todos os validadores são responsáveis por votar em todas as propostas. -Não votar em uma proposta em tempo hábil resultará na desativação automática do -validador por um período de tempo denominado `AbsenteeismPenaltyPeriod` (PADRÃO DE 1 semana). - -Os delegados herdam automaticamente o voto do validador delegado. -Este voto pode ser anulado manualmente. Os átomos não ligados obtêm nenhum voto. - -Cada proposta requer um depósito de tokens de `MinimumProposalDeposit`, -que pode ser uma combinação de um ou mais tokens incluindo átomos. -Para cada proposta, os eleitores podem votar para receber o depósito. -Se mais da metade dos eleitores optarem por receber o depósito (por exemplo, porque a proposta era spam), -o depósito vai para a pool reserva, exceto os átomos que são queimados. - -Para cada proposta, os eleitores podem votar nas seguintes opições: - -- Sim -- Com Certeza -- Não -- Nunca -- Abstenção - -É necessário uma maioria estrita de votos Yea(Sim) ou YeaWithForce(Com certeza) -(ou votos Nay(Não) ou NayWithForce(Nunca)) para que a proposta seja decidida como aceita -(ou decidida como falha), mas 1/3+ pode vetar a decisão da maioria votando "Com certeza". -Quando uma maioria estrita é vetada, todos são punidos com a perda de `VetoPenaltyFeeBlocks` -(PADRÃO DE no valor de um dia de blocos) de taxas (exceto os impostos que não serão afetados), -e a parte que vetou a decisão da maioria será adicionalmente punida com a perda de `VetoPenaltyAtoms` -(PADRÃO DE 0.1%) de seus átomos. - -### Parâmetro de Mudança de Proposta - -Qualquer um dos parâmetros aqui definidos pode ser alterado com a aceitação -de um `ParameterChangeProposal`. - -### Texto da Proposta - -Todas as outras propostas, como uma proposta de atualização do protocolo, serão coordenadas através do genérico `TextProposal`. - -## Roteiro - -Veja [o Plano Cosmos](https://github.com/cosmos/cosmos/blob/master/PLAN.md). - -## Trabalho Relacionado - -Houve muitas inovações no consenso da blockchain e na escalabilidade nos últimos dois anos. -Esta seção fornece um breve levantamento de um seleto número das mais importantes. - -### Sistemas de Consenso - -#### Classic Byzantine Fault Tolerance - -Consenso na presença de participantes maliciosos é um problema que remonta ao início dos anos 1980, -quando Leslie Lamport cunhou a frase "falha bizantina" para se referir ao comportamento do processo -arbitrário que se desvia do comportamento pretendido, que contraste com uma "falha acidental", -em que um processo simplesmente falha. Soluções iniciais foram descobertas para redes síncronas onde -há um limite superior na latência da mensagem, embora o uso prático fosse limitado a ambientes altamente controlados, -como controladores de avião e datacenters sincronizados via relógios atômicos. -Não foi até o final dos anos 90 que a Practical Byzantine Fault Tolerance (PBFT) foi introduzida como -um eficiente algoritmo de consenso parcialmente síncrono capaz de tolerar até ⅓ de processos -comportando-se arbitrariamente. PBFT tornou-se o algoritmo padrão, gerando muitas variações, -incluindo mais recentemente uma criada pela IBM como parte de sua contribuição para a Hyperledger. - -O principal benefício do consenso Tendermint sobre PBFT é que o Tendermint tem uma estrutura -subjacente melhorada e simplificada, um dos quais é um resultado de adotar o paradigma blockchain. -Blocos Tendermint devem confirmar em ordem, o que evita a complexidade e sobrecarga de comunicação -associada a alteração de visão do PBFT's. No Cosmos e muitas outras criptomoedas, -não há necessidade de permitir o bloco <em>N+i</em> onde <em>i >= 1</em> se confirmar, -quando o próprio bloco <em>N</em> ainda não se confirmou. Se a largura de banda é a razão -pela qual o bloco <em>N</em> não se confirmou em uma zona do Cosmos, então isso não ajuda -a usar os votos de compartilhamento de largura de banda para blocos <em>N+i</em>. -Se uma partição de rede ou nós offline for a razão pela qual o bloco <em>N</em> não foi confirmado, -<em>N+i</em> não se comprometerá de qualquer maneira. - -Além disso, o lote de transações em blocos permite que o Merkle-hashing regule o estado da aplicação, -ao invés de resumos periódicos com esquemas de pontos de verificação como PBFT faz. -Isso permite confirmações de transações mais rápidas para clientes leves e uma comunicação mais rápida entre a blockchain. - -Tendermint Core também inclui muitas otimizações e recursos que vão acima e além do que é especificado no PBFT. -Por exemplo, os blocos propostos pelos validadores são divididos em partes, -Merkleized e inútilizados de tal forma que melhora o desempenho da transmissão -(ver LibSwift [\[19\]][19] para inspiração). Além disso, Tendermint Core não faz qualquer suposição sobre -a conectividade ponto-a-ponto, e funciona durante o tempo que a rede P2P está fracamente conectada. - -#### Participação delegada do BitShares - -Apesar de não serem os primeiros a implementar a prova-de-participação (Proof-of-Stake - PoS), -o BitShares [\[12\]][12] contribuiu consideravelmente para a pesquisa e adoção das blockchains que usam o PoS, -particularmente aqueles conhecidos como PoS "delegados". No BitShares, as partes interessadas elegem "testemunhas", -responsáveis por ordenar e confirmar transações e "delegados", responsáveis pela coordenação -de atualizações de software e alterações de parâmetros. Embora o BitShares atinja alto desempenho -(100k tx/s, 1s de latência) em condições ideais, ele está sujeito a ataques de duplo gasto por testemunhas -maliciosas que "forkem" a blockchain sem sofrer uma punição econômica explícita - ele sofre do problema -"nada a perder". O BitShares tenta suavizar o problema permitindo que as transações se refiram a -blocos-hashes recentes. Além disso, as partes interessadas podem remover ou substituir -testemunhas de má conduta diariamente, embora isso não faça nada para punir -explicitamente os ataques bem sucedidos de duplo gasto. - -#### Stellar - -Baseando-se em uma abordagem pioneira da Ripple, a Stellar [\[13\]][13] refinou um modelo do -Federated Byzantine Agreement em que os processos que participam do consenso não constituem -um conjunto fixo e globalmente conhecido. Em vez disso, cada nó de processo codifica uma ou mais -"fatias de quórum", cada uma constituindo um conjunto de processos confiáveis. Um "quórum" na -Stellar é definido como um conjunto de nós que contêm pelo menos uma fatia de quórum para cada -nó no conjunto, de modo que o acordo possa ser alcançado. - -A segurança do mecanismo Stellar baseia-se no pressuposto de que a intersecção de _qualquer_ dois -quóruns é não-vazia, enquanto a disponibilidade de um nó requer pelo menos uma das suas fatias de -quórum para consistir inteiramente de nós corretos, criando um troca externa entre o uso de grandes -ou pequenas fatias-quórum que podem ser difíceis de equilíbrar sem impor pressupostos significativos -sobre a confiança. Em última análise, os nós precisam, de alguma forma, escolher fatias de quórum adequadas -para que haja tolerância suficiente a falhas (ou qualquer "nó intacto" em geral, do qual muitos dos -resultados do trabalho dependem) e a única estratégia fornecida para garantir tal configuração é -hierárquica e similar ao Border Gateway Protocol (BGP), usado por ISPs de primeira linha na -internet para estabelecer tabelas de roteamento globais e usado pelos navegadores para gerenciar -certificados TLS; Ambos notórios por sua insegurança. - -A crítica sobre papel da Stellar nos sistemas PoS baseados em Tendermint é atenuada pela estratégia -de token descrita aqui, em que um novo tipo de token chamado _atom_ é emitido para representar -reivindicações para futuras porções de taxas e recompensas. A vantagem do PoS baseado em Tendermint, -portanto, é a sua relativa simplicidade, ao mesmo tempo que oferece garantias de segurança suficientes e prováveis. - -#### BitcoinNG - -O BitcoinNG é uma proposta de melhoria do Bitcoin que permitiria formas de escalabilidade vertical, -como o aumento do tamanho do bloco, sem as conseqüências econômicas negativas normalmente associadas a tal mudança, -como o impacto desproporcionalmente grande sobre os pequenos mineradores. Esta melhoria é conseguida separando -a eleição do líder da transmissão da transação: os líderes são eleitos pela primeira vez -por prova de trabalho(PoW) em "microblocos", e então são capazes de transmitir transações a -serem confirmadas até que um novo microbloco seja encontrado. Isso reduz os requisitos -de largura de banda necessários para vencer a corrida PoW, permitindo que os pequenos -mineiros possam competir mais justamente, e permitindo que as transações sejam confirmadas -com mais regularidade pelo último minerador para encontrar um micro-bloco. - -#### Casper - -Casper [\[16\]][16] é uma proposta de algoritmo de consenso PoS para o Ethereum. -Seu modo principal de operação é "consenso-por-aposta". Ao permitir que os validadores apostem -iterativamente em qual bloco eles acreditam que será confirmado na blockchain com base nas -outras apostas que eles têm visto até agora, a finalidade pode ser alcançada eventualmente. -[link](https://blog.ethereum.org/2015/12/28/understanding-serenity-part-2-casper/). Esta é uma área ativa -de pesquisa da equipe de Casper. O desafio está na construção de um mecanismo de apostas que pode ser -comprovado como uma estratégia evolutivamente estável. O principal benefício da Casper em relação à -Tendermint pode ser a oferta de "disponibilidade sobre a consistência" - consenso não requer -um quórum +⅔ de poder de voto - talvez ao custo de velocidade de confirmação ou complexidade de implementação. - -### Escala Horizontal - -#### Protocolo Interledger - -O Protocolo Interledger [\[14\]][14] não é estritamente uma solução de escalabilidade. -Ele fornece uma interoperabilidade ad hoc entre diferentes sistemas de ledger através de uma rede -de relações bilaterais livremente acopladas. Tal como a Lightning Network, a finalidade do -ILP é facilitar pagamentos, mas focaliza especificamente pagamentos em diferentes tipos de ledger, -estendendo o mecanismo de transações atômicas para incluir não apenas hash-locks, mas também um -quórum de notários (chamado de Atomic Transport Protocol). O último mecanismo para reforçar a -atomicidade em transacções entre-ledger é semelhante ao mecanismo SPV do cliente leve do Tendermint, -então uma ilustração da distinção entre ILP e Cosmos/IBC é garantida, e fornecida abaixo. - -1. Os notários de um conector em ILP não suportam mudanças de consentimento, e não permitem uma - pesagem flexível entre notários. Por outro lado, o IBC é projetado especificamente para blockchains, - onde os validadores podem ter diferentes pesos, e onde o consentimento pode mudar ao longo da cadeia de blocos. - -2. Como na Lightning Network, o receptor do pagamento em ILP deve estar on-line para enviar - uma confirmação de volta ao remetente. Em uma transferência de token sobre IBC, o conjunto - de validadores da blockchain do receptor é responsável por fornecer a confirmação, não o usuário receptor. - -3. A diferença mais notável é que os conectores do ILP não são responsáveis ou mantêm o estado - autoritário sobre os pagamentos, enquanto que no Cosmos, os validadores de um hub são a autoridade - do estado das transferências de tokens do IBC, bem como a autoridade da quantidade de tokens - mantidos por cada zona (mas não a quantidade de tokens mantidos por cada conta dentro de uma zona). - Esta é a inovação fundamental que permite a tranferência assimétrica segura de tokens de zona para - zona; O conector analógico do ILP no Cosmos é uma persistente e maximamente segura ledger de blockchain, o Cosmos Hub. - -4. Os pagamentos entre contas no ILP precisam ser suportados por uma ordem de compra/venda, uma - vez que não há transferência assimétrica de moedas de um ledger para outro, apenas a transferência - de valor ou equivalentes de mercado. - -#### Sidechains - -Sidechains [\[15\]][15] são um mecanismo proposto para dimensionar a rede Bitcoin através de -blockchains alternativas que são "atreladas" para a blockchain do Bitcoin. As Sidechains -permitem que bitcoins se movam efetivamente da blockchain do Bitcoin para a sidechain e retornarem, -e permitem a experimentação em novos recursos na sidechain. Como no Cosmos Hub, a sidechain e -Bitcoin servem como clientes leves uns dos outros, usando provas SPV para determinar quando as moedas -devem ser transferidas para a cadeia lateral e retornarem. Claro, como o Bitcoin usa PoW, sidechains -centradas em torno do Bitcoin sofrem dos muitos problemas e riscos do PoW como um mecanismo de consenso. -Além disso, esta é uma solução Bitcoin-maximalista que não suporta nativamente uma variedade de tokens e -topologia de rede entre-zona como o Cosmos faz. Dito isto, o mecanismo de núcleo bidirecional atrelado é, -em princípio, o mesmo que o empregado pela rede Cosmos. - -#### Esforços de Escalabilidade do Ethereum - -Ethereum está atualmente pesquisando uma série de estratégias diferentes para fragmentar o -estado da blockchain do Ethereum para atender às necessidades de escalabilidade. -Esses esforços têm como objetivo manter a camada de abstração oferecida pela atual -Ethereum Virtual Machine através do espaço de estado compartilhado. Vários esforços de -pesquisa estão em andamento neste momento. [\[18\]][18][\[22\]][22] - -##### Cosmos vs Ethereum 2.0 Mauve - -Cosmos e Ethereum 2.0 Mauve [\[22\]][22] tem diferentes objetivos de projeto. - -- Cosmos é especificamente sobre tokens. Malva é sobre escalonamento de computação geral. -- O Cosmos não está ligado ao EVM, por isso mesmo VMs diferentes podem interoperar. -- Cosmos permite que o criador da zona determine quem valida a zona. -- Qualquer pessoa pode iniciar uma nova zona no Cosmos (a menos que a governança decida o contrário). -- O hub isola falhas de zonas de modo que tokens invariantes sejam preservados. - -### Escala Geral - -#### Lightning Network - -A Lightning Network é uma proposta de rede de transferência de token operando em uma camada acima -da blockchain do Bitcoin (e outras blockchains públicas), permitindo a melhoria de muitas ordens -de magnitude no processamento de transações movendo a maioria das transações fora da ledger de consenso -para o chamado "Canais de pagamento". Isso é possível graças a scripts de criptomoedas em cadeia, -que permitem que as partes entrem em contratos estatais bilaterais onde o estado pode ser atualizado -compartilhando assinaturas digitais, e os contratos podem ser fechados definitivamente publicando -evidências na blockchain, um mecanismo primeiramente popularizado por trocas atômicas de -cross-chains(cadeias cruzadas). Ao abrir canais de pagamento com muitas partes, os participantes -da Lightning Network podem se tornar pontos focais para encaminhar os pagamentos de outros, -levando a uma rede de canais de pagamento totalmente conectada, ao custo do capital estar ligado aos canais de pagamento. - -Enquanto a Lightning Network também pode facilmente se estender através de várias blockchains independentes -para permitir a transferência de _value_ através de um mercado de câmbio, não pode ser usado para -transferir assimetricamente _tokens_ de uma blockchain para outra. O principal benefício da rede Cosmos -descrita aqui é permitir tais transferências diretas de tokens. Dito isto, esperamos que os canais de -pagamento e a Lightning Network sejam amplamente adotados juntamente com nosso mecanismo de transferência -de token, por razões de economia de custos e privacidade. - -#### Segregated Witness - -Segregated Witness é uma proposta de melhoria do Bitcoin -[link](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki) que visa aumentar em 2X ou 3X a -taxa de transferência por bloco, ao mesmo tempo que faz a sincronização de blocos ser mais rapida para -novos nós. O brilho desta solução é de como ele funciona dentro das limitações do protocolo atual do Bitcoin -e permite uma atualização de soft-fork (ou seja, os clientes com versões mais antigas do software -continuarão funcionando após a atualização). O Tendermint, sendo um novo protocolo, não tem restrições -de projeto, por isso tem prioridades diferentes de escalonamento. Sobretudo, o Tendermint usa um algoritmo -de rodízio BFT baseado em assinaturas criptográficas em vez de mineração, o que trivialmente permite escalonamento -horizontal através de múltiplas blockchains paralelas, enquanto que os regulares e mais frequentes blocos confirmam -a escala vertical também. - -<hr/> - -## Apêndice - -### Responsabilidade de Fork - -Um protocolo de consenso bem projetado deve fornecer algumas garantias no caso da capacidade de -tolerância ser excedida e o consenso falhar. Isto é especialmente necessário nos sistemas econômicos, -onde o comportamento Bizantino pode ter recompensa financeira substancial. A -garantia maisimportante é uma forma de _fork-accountability_, onde os processos que -fizeram com que o consenso falhasse (ou seja, clientes do protocolo -motivados para aceitar valores diferentes - um fork) podem ser identificados e punidos de acordo com as -regras do protocolo , Ou, possivelmente, o sistema jurídico. Quando o sistema jurídico não é confiável -ou é excessivamente caro para suplicar, os validadores podem ser forçados a fazerem depósitos de segurança -para participar, e esses depósitos podem ser revogados ou cortados, quando um comportamento malicioso é detectado [\[10\]][10]. - -Observe que isso é diferente do Bitcoin, onde o fork é uma ocorrência regular devido à assincronia de -rede e à natureza probabilística de encontrar colisões de hash parciais. Uma vez que, em muitos casos, -um fork malicioso é indistinguível de um fork devido à assincronia, o Bitcoin não pode implementar de -forma confiável a responsabilidade de um fork, com exceção do custo implícito pago por mineradores que -tem a oportunidade de minerarem um bloco órfão. - -### Consenso Tendermint - -Chamamos as fases de votação de _PreVote_ e _PreCommit_. Um voto pode ser para um bloco em particular ou -para _Nil_. Chamamos uma coleção de +⅔ PreVotes para um único bloco na mesma rodada de um _Polka_, e uma -coleção de +⅔ PreCommits para um único bloco na mesma rodada de um _Commit_. Se +⅔ PreCommit para Nil na -mesma rodada, eles passam para a próxima rodada. - -Observe que o determinismo estrito no protocolo incorre em uma suposição de sincronia fraca, pois os líderes -com falhas devem ser detectados e ignorados. Assim, os validadores aguardam algum tempo, _TimeoutPropose_, -antes de Prevote Nil, e o valor de TimeoutPropose aumenta a cada rodada. A progressão através do -resto de uma rodada é totalmente assincrôna, onde o progresso é feito somente quando um validador -ouve de +⅔ da rede. Na prática, seria necessário um adversário extremamente forte para impedir -indefinidamente a suposição de sincronia fraca (fazendo com que o consenso deixasse de confirmar um bloco), -e isso pode ser ainda mais difícil usando valores randomizados de TimeoutPropose em cada validador. - -Um conjunto adicional de restrições, ou Locking Rules(Regras de bloqueio), garante que a rede acabará -por confirmar apenas um bloco em cada altura. Qualquer tentativa maliciosa de confirmar de causar um -bloco a ser confirmado a uma determinada altura pode ser identificada. Primeiro, um PreCommit para um -bloco deve vir com justificação, na forma de um Polka para esse bloco. Se o validador já tiver PreCommit -um bloco na rodada <em>R*1</em>, nós dizemos que eles estão \_locked* nesse bloco, e o Polka usado -para justificar o novo PreCommit na rodada <em>R_2</em> deve vir de uma rodada <em>R_polka</em> -onde <em>R_1 < R_polka <= R_2</em>. Em segundo lugar, os validadores devem propor e/ou pré-votar -o bloco que eles estão travados. Juntas, essas condições garantem que um validador não PreCommit -sem evidência suficiente como justificativa, e que os validadores que já têm PreCommit não podem -contribuir para a evidência de PreCommit algo mais. Isso garante a segurança e a vivacidade do algoritmo de consenso. - -Os detalhes completos do protocolo são descritos -[aqui](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm). - -### Clientes Leves do Tendermint - -A necessidade de sincronizar todos os cabeçalhos de bloco é eliminada no Tendermint-PoS, como por exemplo -a existência de uma cadeia alternativa (um fork) significando que ⅓+ do stake ligado pode ser reduzido. -Naturalmente, a partir que dividir requer que _someone_ compartilhe evidência de um fork, clientes leves -devem armazenar qualquer bloco-hash comprometido que eles vêem. Além disso, os clientes leves podem -periodicamente ficarem sincronizados com as alterações no conjunto de validadores, para evitar -[ataques de longo alcance](#preventing-long-range-attacks) (mas outras soluções são possíveis). - -Em espírito semelhante do Ethereum, o Tendermint permite que os aplicativos incorporem um hash de raiz -Merkle global em cada bloco, permitindo verifícações fáceis de consultas de estado para fins como saldos -de contas, o valor armazenado em um contrato ou a existência de saída de uma transação não gasta, -dependendo da natureza da aplicação. - -### Prevenção de ataques de longo alcance - -Assumindo uma coleção suficientemente elástica de redes de difusão e um conjunto de validador -estático, qualquer fork na blockchain pode ser detectado e os depósitos dos validadores ofensivos cortados. -Esta inovação, sugerida pela primeira vez por Vitalik Buterin no início de 2014, resolve o problema do "nada a perder" de outras -criptomoedas de PoW (ver [Trabalho Relacionado](#related-work)). No entanto, uma vez que os conjuntos de -validadores devem ser capazes de mudar, durante um longo período de tempo, os validadores originais podem -tornar-se não ligados e, portanto, seriam livres para criar uma nova cadeia a partir do bloco gênese, -não incorrendo nenhum custo, visto que eles não tem depósitos trancados. Este ataque veio a ser conhecido -como Ataque de Longo Alcance (Long Range Attack - LRA), em contraste com um Ataque de Curto Alcance, -onde os validadores que estão atualmente ligados causam um fork e são, portanto, puníveis -(assumindo um algoritimo BFT de fork-responsável como o consenso Tendermint). -Ataques de longo alcance são muitas vezes pensados para serem um golpe crítico para o PoW. - -Felizmente, o LRA pode ser atenuado da seguinte forma. Em primeiro lugar, para que um validador se -desatar (assim recuperando seu depósito colateral e não mais ganhando taxas para participar no consenso), -o depósito deve ser tornado intransferível por um período de tempo conhecido como o "unbonding period" -(período de desatamento), que pode ser na ordem de semanas ou meses. Em segundo lugar, para um cliente -leve ser seguro, a primeira vez que ele se conecta à rede, ele deve verificar um hash de bloqueio recente -contra uma fonte confiável ou, preferencialmente, várias fontes. Esta condição é por vezes referida como -"subjetividade fraca". Finalmente, para permanecer seguro, ele deve sincronizar com o mais recente -validador definido, pelo menos, tão frequentemente quanto a duração do período de desatamento. -Isso garante que o cliente leve saiba sobre as alterações no conjunto de validação definido antes de -um validador não ter mais o seu capital ligado e, portanto, não mais em jogo, o que permitiria enganar -o cliente, executando um ataque de longo alcance, criando novos blocos re-começando em uma altura -a qual foi ligado (assumindo que tem controle de muitas das primeiras chaves privadas). - -Note que superar o LRA desta forma requer uma revisão do modelo de segurança original do PoW. No PoW, -presume-se que um cliente leve pode sincronizar com a altura atual do bloco gênese confiável a qualquer -momento simplesmente processando o PoW em cada cabeçalho de bloco. Para superar o LRA, entretanto, -exigimos que um cliente leve entre em linha com alguma regularidade para rastrear mudanças no conjunto -de validadores e que, na primeira vez em que eles fiquem on-line, eles devem ser particularmente cuidadosos -para autenticar o que ouvem da rede contra fontes confiáveis . Naturalmente, este último requisito é -semelhante ao do Bitcoin, onde o protocolo e o software também devem ser obtidos a partir de uma fonte confiável. - -O método acima para prevenir LRA é bem adequado para validadores e nós completos de uma blockchain alimentada -por Tendermint porque estes nós são destinados a permanecerem conectados à rede. O método também é adequado -para clientes leves que podem ser esperados para sincronizar com a rede com freqüência. No entanto, para -os clientes leves que não se espera ter acesso frequente à Internet ou à rede da blockchain, ainda pode -ser utilizada outra solução para superar o LRA. Os detentores de tokens não validadores podem publicar -os seus tokens como colaterais com um período de não ligação muito longo (por exemplo, muito mais longo -do que o período de não ligação para validadores) e servir clientes leves com um método secundário de -atestar a validade dos blocos atuais e hashes de blocos passados. Embora esses tokens não contam para a -segurança do consenso da blockchain, eles podem fornecer fortes garantias para clientes leves. Se a -consulta histórica de hash de blocos fosse suportada no Ethereum, qualquer pessoa poderia vincular -seus tokens em um contrato inteligente projetado especialmente para isso e fornecer serviços de -comprovação de pagamentos, efetivamente criando um mercado para a segurança contra LRA de cliente leve. - -### Superando Forks e Ataques de Censura - -Devido à definição de uma confimação de bloco, qualquer coalizão de poder de voto ⅓+ pode interromper a -blockchain ficando off-line ou não transmitir os seus votos. Tal coalizão também pode censurar transações -particulares rejeitando blocos que incluem essas transações, embora isso resultaria em uma proporção -significativa de propostas de blocos a serem rejeitadas, o que iria retardar a taxa de blocos -confirmados da blockchain, reduzindo sua utilidade e valor. A coalizão mal-intencionada também pode transmitir -votos em um fio de modo a triturar os blocos confirmados da blockchain para quase parar, ou se envolver em -qualquer combinação desses ataques. Finalmente, isso pode fazer com que a cadeia de blocos "forke" (bifurque), -por dupla assinatura ou violação as regras de bloqueio. - -Se um adversário globalmente ativo também estivesse envolvido, poderia dividir a rede de tal maneira que -possa parecer que o subconjunto errado de validadores era responsável pela desaceleração. Esta não é apenas -uma limitação do Tendermint, mas sim uma limitação de todos os protocolos de consenso cuja -rede é potencialmente controlada por um adversário ativo. - -Para estes tipos de ataques, um subconjunto de validadores deve coordenar através de meios externos -para assinar um proposta de reorganização que escolhe um fork (e qualquer prova disso) e o -subconjunto inicial de validadores com suas assinaturas. Os validadores que assinam tal -proposta de reorganização deixam seu colateral em todos os outros forks. Os clientes -devem verificar as assinaturas na proposta de reorganização, verificar qualquer -evidência e fazer um julgamento ou solicitar ao usuário final uma decisão. Por exemplo, -uma carteira para celular um aplicativo que pode alertar o usuário com um aviso de segurança, -enquanto um refrigerador pode aceitar qualquer proposta de reorganização assinada por -\+½ dos validadores originais por poder de voto. - -Nenhum algoritmo não-sincrônico tolerante a falhas Bizantino pode chegar a um consenso quando ⅓+ -de poder de voto for desonesto, mas um fork supõe que ⅓+ do poder de voto já foram desonestos por -dupla assinatura ou bloqueio de mudança sem justificativa. Portanto, assinar a proposta de -reorganização é um problema de coordenação que não pode ser resolvido por qualquer protocolo -não-sincronico (isto é, automaticamente e sem fazer suposições sobre a confiabilidade da rede subjacente). -Por enquanto, deixamos o problema da coordenação da proposta de reorganização para a coordenação -humana através do consenso social na mídia na internet. Os validadores devem ter cuidado para garantir -que não haja partições de rede remanescentes antes de assinar uma proposta de reorganização, -para evitar situações em que duas propostas de reorganização em conflito sejam assinadas. - -Assumindo que o meio de coordenação é externo e o protocolo é robusto, resulta-se que os forks são -uma preocupação menor do que os ataques de censura. - -Além de forks e censura, que exigem ⅓+ poder de votação Bizantina, uma coalizão de +⅔ poder de -voto pode ser pratica arbitrária, estado inválido. Esta é a característica de qualquer sistema -de consenso (BFT). Ao contrário da dupla assinatura, que cria forks com provas facilmente -verificáveis, a detecção de obrigatoriedade de um estado inválido requer que os pares não -validadores verifiquem blocos inteiros, o que implica que eles mantêm uma cópia local do estado -e executam cada transação, computando a raiz de estado de forma independente para eles mesmos. -Uma vez detectado, a única maneira de lidar com essa falha é através do consenso social. -Por exemplo, em situações em que o Bitcoin falhou, seja por causa de bugs de software -(como em março de 2013), ou praticar um estado inválido devido ao comportamento Bizantino -dos mineradores (como em julho de 2015), a comunidade bem conectada de negócios, desenvolvedores, -mineradores e outras organizações estabeleceu um consenso social sobre quais ações manuais se -faziam necessárias para curar a rede. Além disso, uma vez que se pode esperar que os validadores -de uma cadeia de blocos de Tendermint sejam identificáveis, o compromisso de um estado inválido -pode até ser punido por lei ou por alguma jurisprudência externa, se desejado. - -### Especificação TMSP - -TMSP consiste em 3 tipos de mensagens primárias que são entregues do núcleo para o aplicativo. -O aplicativo responde com mensagens de resposta correspondentes. - -A mensagem `AppendTx` é o cavalo de trabalho da aplicação. Cada transação na blockchain -é entregue com esta mensagem. O aplicativo precisa validar cada transação recebida com a -mensagem AppendTx contra o estado atual, o protocolo de aplicativo e as credenciais -criptográficas da transação. Uma transação validada precisa atualizar o estado do -aplicativo - vinculando um valor a um armazenamento de valores chave ou atualizando o banco de dados UTXO. - -A mensagem `CheckTx` é semelhante à AppendTx, mas é apenas para validar transações. O mempool do -Tendermint Core primeiro verifica a validade de uma transação com o CheckTx e apenas relata -transações válidas para seus pares. Os aplicativos podem verificar um nonce incremental na transação -e retornar um erro em CheckTx se o nonce é antigo. - -A mensagem `Commit` é usada para calcular uma obrigação criptográfica com o estado atual da aplicação, -para ser colocada no próximo cabeçalho do bloco. Isso tem algumas propriedades úteis. Inconsistências -na atualização desse estado agora aparecerão como forks do blockchain que captura uma classe inteira -de erros de programação. Isso também simplifica o desenvolvimento de clientes leves e seguros, -já que as provas de Merkle-hash podem ser provadas verificando o hash de blocos, -e o hash de blocos é assinado por um quórum de validadores (por poder de voto). - -Mensagens TMSP adicionais permitem que o aplicativo acompanhe e altere o conjunto -de validadores e que o aplicativo receba as informações do bloco, como a altura e os votos de confirmação. - -Pedidos/respostas TMSP são simples mensagens Protobuf. -Confira o [arquivo do esquema](https://github.com/tendermint/abci/blob/master/types/types.proto). - -##### AppendTx - -- **Arguments**: - - `Data ([]byte)`: Os bytes de transação solicitada -- **Returns**: - - `Code (uint32)`: Código de resposta - - `Data ([]byte)`: Bytes de resultado, se houver - - `Log (string)`: Debug ou mensagem de erro -- **Usage**:<br/> - Acrescentar e executar uma transação. Se a transação for válida, - CodeType.OK - -##### CheckTx - -- **Arguments**: - - `Data ([]byte)`: Os bytes de transação solicitados -- **Returns**: - - `Code (uint32)`: Código de resposta - - `Data ([]byte)`: Bytes de resultado, se houver - - `Log (string)`: Debug ou mensagem de erro -- **Usage**:<br/> - Validar uma transação. Esta mensagem não deve mutar o estado. - As transações são primeiro executadas através do CheckTx antes da transmissão para os pares na camada mempool. - Você pode fazer o CheckTx semi-stateful e limpar o estado após `Commit` ou - `BeginBlock`, - para permitir sequências dependentes de transações no mesmo bloco. - -##### Commit - -- **Returns**: - - `Data ([]byte)`: O hash Merkle raiz - - `Log (string)`: Debug ou erro de mensagem -- **Usage**:<br/> - Retorna um hash Merkle raiz do estado da aplicação. - -##### Query - -- **Arguments**: - - `Data ([]byte)`: Os bytes de solicitação consultada -- **Returns**: - - `Code (uint32)`: Código de resposta - - `Data ([]byte)`: Os bytes de resposta consultada - - `Log (string)`: Debug ou erro de mensagem - -##### Flush - -- **Usage**:<br/> - Limpar a fila de resposta. Aplicações que implementam `types.Application` - não precisa implementar esta mensagem - é tratada pelo projeto. - -##### Info - -- **Returns**: - - `Data ([]byte)`: Os bytes de informação -- **Usage**:<br/> - Retorna informações sobre o estado da aplicação. Aplicação específicão. - -##### SetOption - -- **Arguments**: - - `Key (string)`: Chave para definir - - `Value (string)`: Valor a definir para a chave -- **Returns**: - - `Log (string)`: Debug ou mensagem de erro -- **Usage**:<br/> - Define as opções do aplicativo. Exemplo Key="mode", Value="mempool" para uma conexão mempool - , ou Key="mode", Value="consensus" para uma conexão de consenso. - Outras opções são específicas da aplicação. - -##### InitChain - -- **Arguments**: - - `Validators ([]Validator)`: validadores de genesis iniciais -- **Usage**:<br/> - Chamado uma vez na genesis - -##### BeginBlock - -- **Arguments**: - - `Height (uint64)`: A altura do bloco que está começando -- **Usage**:<br/> - Sinaliza o início de um novo bloco. Chamado antes de qualquer AppendTxs. - -##### EndBlock - -- **Arguments**: - - `Height (uint64)`: A altura do bloco que terminou -- **Returns**: - - `Validators ([]Validator)`: Mudança de validadores com novos poderes de voto (0 - para remover) -- **Usage**:<br/> - Sinaliza o fim de um bloco. Chamado antes de cada Commit após todas as - transações - -Veja [o repositório TMSP](https://github.com/tendermint/abci) para mais detalhes. - -### Reconhecimento de entrega de pacotes IBC - -Há várias razões pelas quais um remetente pode querer o reconhecimento da entrega de um pacote -pela cadeia de recebimento. Por exemplo, o remetente pode não saber o status da cadeia de -destino, se for esperado que esteja com defeito. Ou, o remetente pode querer impor um tempo -limite no pacote (com o campo `MaxHeight`), enquanto qualquer cadeia de destino pode sofrer -de um ataque de negação de serviço com um aumento repentino no número de pacotes de entrada. - -Nesses casos, o remetente pode exigir confirmação de entrega configurando o status -do pacote inicial como `AckPending`. Em seguida, é a responsabilidade da -cadeia receptora confirmar a entrega, incluindo uma abreviada `IBCPacket` no app Merkle hash. - -![Figura da Zone1, Zone2, e Hub IBC com -reconhecimento](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_with_ack.png) - -Primeiro, um `IBCBlockCommit` e`IBCPacketTx` são postados no "Hub" que prova -a existência de um `IBCPacket` na "Zone1". Digamos que `IBCPacketTx` tem o seguinte valor: - -- `FromChainID`: "Zone1" -- `FromBlockHeight`: 100 (say) -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 (say) - - `Status`: `AckPending` - - `Type`: "moeda" - - `MaxHeight`: 350 (Dizer que "Hub" está atualmente na altura 300) - - `Payload`: <Os bytes de uma carga paga de "moeda"> - -Em seguida, um `IBCBlockCommit` e `IBCPacketTx` são publicados na "Zone2" que comprova -a existência de um `IBCPacket` em "Hub". Digamos que `IBCPacketTx` tem o seguinte valor: - -- `FromChainID`: "Hub" -- `FromBlockHeight`: 300 -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 - - `Status`: `AckPending` - - `Type`: "moeda" - - `MaxHeight`: 350 - - `Payload`: <Os mesmos bytes de uma carga paga de "moeda"> - -Em seguida, "Zone2" deve incluir em seu app-hash um pacote abreviado que mostra o novo -status de `AckSent`. Um `IBCBlockCommit` e `IBCPacketTx` são colocados de volta no "Hub" -que comprova a existência de um `IBCPacket` abreviado na "Zone2". Digamos que `IBCPacketTx` tem o seguinte valor: - -- `FromChainID`: "Zone2" -- `FromBlockHeight`: 400 (say) -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 - - `Status`: `AckSent` - - `Type`: "moeda" - - `MaxHeight`: 350 - - `PayloadHash`: <Os bytes de hash da mesma carga paga de "moeda"> - -Finalmente, "Hub" deve atualizar o status do pacote de `AckPending` para`AckReceived`. -A evidência desse novo status finalizado deve voltar a "Zone2". Digamos que `IBCPacketTx` tem o seguinte valor: - -- `FromChainID`: "Hub" -- `FromBlockHeight`: 301 -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 - - `Status`: `AckReceived` - - `Type`: "moeda" - - `MaxHeight`: 350 - - `PayloadHash`: <Os bytes de hash da mesma carga paga de "moeda"> - -Enquanto isso, "Zone1" pode assumir de maneira otimista a entrega bem-sucedida de um pacote -de "moeda", a menos que provas em contrário sejam comprovadas em "Hub". No exemplo acima, -se "Hub" não tivesse recebido um status `AckSent` de "Zone2" pelo bloco 350, ele teria -definido o status automaticamente para `Timeout`. Essa evidência de um tempo limite pode -ser postada novamente na "Zone1", e quaisquer tokens podem ser retornados. - -![Figura da Zone1, Zone2, e Hub IBC com reconhecimento e -timeout](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_with_ack_timeout.png) - -### Árvore Merkle e Especificação de Prova - -Existem dois tipos de árvores Merkle suportadas no ecossistema Tendermint / Cosmos: A Árvore Simples e a Árvore IAVL+. - -#### Árvore Simples - -A Árvore Simples é uma árvore Merkle para uma lista estática de elementos. Se o número de -itens não for um poder de dois, algumas folhas estarão em níveis diferentes. Árvore Simples -tenta manter ambos os lados da árvore da mesma altura, mas a esquerda pode ter um maior. -Esta árvore Merkle é usada para Merkle-lizar as transações de um bloco, e os elementos de -nível superior da raiz do estado do aplicativo. - - * - / \ - / \ - / \ - / \ - * * - / \ / \ - / \ / \ - / \ / \ - * * * h6 - / \ / \ / \ - h0 h1 h2 h3 h4 h5 - - Uma ÁrvoreSimples com sete elementos - -#### Árvore IAVL+ - -O objetivo da estrutura de dados IAVL+ é fornecer armazenamento persistente para pares de valores-chave -no estado do aplicativo, de modo que um hash determinista de raiz Merkle possa ser calculado -eficientemente. A árvore é balanceada usando uma variante do [algoritmo AVL](https://en.wikipedia.org/wiki/AVL_tree), e todas as operações são O(log(n)). - -Em uma árvore AVL, as alturas das duas subárvores filhas de qualquer nó diferem por no máximo um. -Sempre que esta condição for violada após uma atualização, a árvore é rebalanceada criando O(log(n)) -novos nós que apontam para nós não modificados da árvore antiga. No algoritmo AVL original, os nós -internos também podem conter pares de valores-chave. O algoritmo AVL + (observe o sinal de adição) -modifica o algoritmo AVL para manter todos os valores em folha de nós, enquanto -usando apenas nós de ramo para armazenar chaves. Isso simplifica o algoritmo, mantendo a trilha hash merkle curta. - -A Árvore AVL + é análoga à Ethereum [Patricia tries](https://en.wikipedia.org/wiki/Radix_tree). -Há compensações. Chaves não precisam ser hasheadas antes da inserção em árvores IAVL+, portanto, -isso fornece iteração mais rápida ordenada no espaço-chave que pode beneficiar algumas aplicações. -A lógica é mais simples de implementar, requerendo apenas dois tipos de nós - nós internos e nós de folhas. -A prova de Merkle é em média mais curta, sendo uma árvore binária equilibrada. Por outro lado, -a raiz Merkle de uma árvore IAVL+ depende da ordem das atualizações. - -Iremos apoiar outras árvores Merkle eficientes, como Patricia Trie, da Ethereum, quando a variante binária estiver disponível. - -### Tipos de Transação - -Na implementação canônica, as transações são transmitidas para o aplicativo Cosmos hub através da interface TMSP. - -O Cosmos Hub aceitará uma série de tipos de transações primárias, incluindo `SendTx`, -`BondTx`, `UnbondTx`, `ReportHackTx`, `SlashTx`, `BurnAtomTx`, `ProposalCreateTx` e `ProposalVoteTx`, -que são relativamente auto-explicativas e será documentado em uma futura revisão deste artigo. -Aqui documentamos os dois principais tipos de transação para IBC: `IBCBlockCommitTx` e `IBCPacketTx`. - -#### IBCBlockCommitTx - -Uma transação `IBCBlockCommitTx` é composta de: - -- `ChainID (string)`: O ID da blockchain -- `BlockHash ([]byte)`: Os bytes de hash de bloco, a raiz Merkle que inclui o app-hash -- `BlockPartsHeader (PartSetHeader)`: Os bytes de cabeçalho do conjunto de blocos, - apenas necessários para verificar assinaturas de voto -- `BlockHeight (int)`: A altura do commit -- `BlockRound (int)`: A rodada do commit -- `Commit ([]Vote)`: O +⅔ Tendermint `Precommit` de votos que compõem um bloco -- `ValidatorsHash ([]byte)`: O hash da raiz da árvore-Merkle do novo conjunto de validadores -- `ValidatorsHashProof (SimpleProof)`: Uma ÁrvoreSimples da prova-Merkle para provar o - `ValidatorsHash` contra o `BlockHash` -- `AppHash ([]byte)`: Um hash da raiz da árvore-Merkle da Árvore IAVL do estado de aplicação -- `AppHashProof (SimpleProof)`: Uma ÁrvoreSimples da prova-Merkle para provar o - `AppHash` contra o `BlockHash` - -#### IBCPacketTx - -Um `IBCPacket` é composto de: - -- `Header (IBCPacketHeader)`: O cabeçalho do pacote -- `Payload ([]byte)`: Os bytes da carga paga do pacote. _Optional_ -- `PayloadHash ([]byte)`: O hash para os bytes do pacote. _Optional_ - -Qualquer um dos `Payload` ou `PayloadHash` deve estar presente. O hash de um `IBCPacket` -é uma raiz Merkle simples dos dois itens, `Header` e `Payload`. Um `IBCPacket` sem a carga completa -é chamado de _abbreviated packet_. - -Um `IBCPacketHeader` é composto de: - -- `SrcChainID (string)`: O ID da blockchain fonte -- `DstChainID (string)`: O ID da blockchain destino -- `Number (int)`: Um número exclusivo para todos os pacotes -- `Status (enum)`: Pode ser um `AckPending`, `AckSent`, `AckReceived`, - `NoAck`, ou `Timeout` -- `Type (string)`: Os tipos são dependentes da aplicação. Cosmos reserva-se ao tipo de pacote "moeda" -- `MaxHeight (int)`: Se status não for `NoAckWanted` ou `AckReceived` por essa altura, o status se tornará `Timeout`. _Opcional_ - -Uma transação `IBCPacketTx` é composta de: - -- `FromChainID (string)`: O ID da blockchain que está fornecendo este pacote; Não necessariamente a fonte -- `FromBlockHeight (int)`: A altura da blockchain na qual o seguinte pacote é incluído (Merkle-izado) no hash da blockchain de origem -- `Packet (IBCPacket)`: Um pacote de dados, cujo estado pode ser um - `AckPending`, `AckSent`, `AckReceived`, `NoAck`, ou `Timeout` -- `PacketProof (IAVLProof)`: Uma prova-Merkle da Árvore IAVL para para provar o hash do pacote contra o \`AppHash' da cadeia de origem em determinada altura - -A seqüência para enviar um pacote da "Zone1" para a "Zone2" através do "Hub" é mostrada em {Figure X}. -Primeiro, um `IBCPacketTx` prova ao "Hub" que o pacote está incluído no estado da aplicação de "Zone1". -Em seguida, outro `IBCPacketTx` prova a "Zone2" que o pacote está incluído no estado da aplicação "Hub". -Durante esse procedimento, os campos `IBCPacket` são idênticos: o `SrcChainID` é sempre "Zone1", -e o `DstChainID` é sempre" Zone2 ". - -O `PacketProof` deve ter o caminho correto da prova-Merkle, da seguinte maneira: - - IBC/<SrcChainID>/<DstChainID>/<Number> - -Quando "Zone1" quer enviar um pacote para "Zone2" através do "Hub", os dados de `IBCPacket` -são idênticos se o pacote é Merkle-izado em "Zone1", no "Hub" ou "Zone2". O único campo mutável -é `Status` para acompanhar a entrega, conforme mostrado abaixo. - -## Agradecimentos - -Agradecemos aos nossos amigos e colegas por sua ajuda na conceituação, revisão e apoio no nosso trabalho com Tendermint e Cosmos. - -- [Zaki Manian](https://github.com/zmanian) da - [SkuChain](http://www.skuchain.com/) forneceu muita ajuda na formatação e redacção, especialmente sob a seção TMSP -- [Jehan Tremback](https://github.com/jtremback) da Althea and Dustin Byington - por ajudar com iterações iniciais -- [Andrew Miller](https://soc1024.com/) da [Honey - Badger](https://eprint.iacr.org/2016/199) pelo feedback sobre consenso -- [Greg Slepak](https://fixingtao.com/) pelo feedback sobre consenso e redação -- Também agradecemos ao [Bill Gleim](https://github.com/gleim) e [Seunghwan - Han](http://www.seunghwanhan.com) por várias contribuições. -- [Pedro Augusto](https://github.com/ShooterXD) pela tradução para - Português - -## Citações - -- [1] Bitcoin: <https://bitcoin.org/bitcoin.pdf> -- [2] ZeroCash: <http://zerocash-project.org/paper> -- [3] Ethereum: <https://github.com/ethereum/wiki/wiki/White-Paper> -- [4] TheDAO: <https://download.slock.it/public/DAO/WhitePaper.pdf> -- [5] Segregated Witness: <https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki> -- [6] BitcoinNG: <https://arxiv.org/pdf/1510.02037v2.pdf> -- [7] Lightning Network: <https://lightning.network/lightning-network-paper-DRAFT-0.5.pdf> -- [8] Tendermint: <https://github.com/tendermint/tendermint/wiki> -- [9] FLP Impossibility: <https://groups.csail.mit.edu/tds/papers/Lynch/jacm85.pdf> -- [10] Slasher: <https://blog.ethereum.org/2014/01/15/slasher-a-punitive-proof-of-stake-algorithm/> -- [11] PBFT: <http://pmg.csail.mit.edu/papers/osdi99.pdf> -- [12] BitShares: <https://bitshares.org/technology/delegated-proof-of-stake-consensus/> -- [13] Stellar: <https://www.stellar.org/papers/stellar-consensus-protocol.pdf> -- [14] Interledger: <https://interledger.org/rfcs/0001-interledger-architecture/> -- [15] Sidechains: <https://blockstream.com/sidechains.pdf> -- [16] Casper: <https://blog.ethereum.org/2015/08/01/introducing-casper-friendly-ghost/> -- [17] TMSP: <https://github.com/tendermint/abci> -- [18] Ethereum Sharding: <https://github.com/ethereum/EIPs/issues/53> -- [19] LibSwift: <http://www.ds.ewi.tudelft.nl/fileadmin/pds/papers/PerformanceAnalysisOfLibswift.pdf> -- [20] DLS: <http://groups.csail.mit.edu/tds/papers/Lynch/jacm88.pdf> -- [21] Thin Client Security: <https://en.bitcoin.it/wiki/Thin_Client_Security> -- [22] Ethereum 2.0 Mauve Paper: <https://cdn.hackaday.io/files/10879465447136/Mauve%20Paper%20Vitalik.pdf> - -#### Links não classificados - -- <https://www.docdroid.net/ec7xGzs/314477721-ethereum-platform-review-opportunities-and-challenges-for-private-and-consortium-blockchains.pdf> diff --git a/docs/resources/whitepaper-zh-CN.md b/docs/resources/whitepaper-zh-CN.md deleted file mode 100644 index 126deabd4860..000000000000 --- a/docs/resources/whitepaper-zh-CN.md +++ /dev/null @@ -1,1013 +0,0 @@ -# Cosmos -**A Network of Distributed Ledgers** - -**分布式账本网络** - -Jae Kwon jae@tendermint.com <br/> -Ethan Buchman ethan@tendermint.com - -讨论[请加入Telegram](https://t.me/cosmosproject)! - -_注意:如果你能在github上阅读,我们仍然定时更新这个文档,请定期检查更新!_ - -## Table of Contents ########################################################### - * [介绍](#介绍) - * [Tendermint](#tendermint) - * [验证人](#验证人) - * [共识](#共识) - * [轻客户端](#轻客户端) - * [防止攻击](#防止攻击) - * [ABCI](#abci) - * [Cosmos 概述](#Cosmos-概述 ) - * [Tendermint-拜占庭容错](#Tendermint-拜占庭容错) - * [治理](#治理) - * [枢纽与分区](#枢纽与分区) - * [枢纽](#枢纽) - * [分区](#分区) - * [跨链通信 IBC](#跨链通信-IBC) - * [用例](#用例) - * [分布式交易所](#分布式交易所) - * [作为其他加密货币的纽带](#作为其他加密货币的纽带) - * [以太坊的扩展](#以太坊的扩展) - * [多用一体化](#多用一体化) - * [缓解网络分区问题](#缓解网络分区问题) - * [联邦式名称解析系统](#联邦式名称解析系统) - * [发行与激励](#发行与激励) - * [Atom 代币](#Atom-代币) - * [众筹](#众筹) - * [验证人的数量限制](#验证人的数量限制) - * [成为创世日后的验证人](#成为创世日后的验证人) - * [对验证人的惩罚](#对验证人的惩罚) - * [交易费用](#交易费用) - * [激励黑客](#激励黑客) - * [治理规范](#治理规范) - * [参数变更提案](#参数变更提案) - * [文本提案](#文本提案) - * [路线图](#路线图) - * [相关工作 ](#相关工作 ) - * [共识系统](#共识系统) - * [经典拜占庭容错](#经典拜占庭容错) - * [BitShare委托权益](#BitShare委托权益) - * [Stellar](#stellar) - * [BitcoinNG](#bitcoinng) - * [Casper](#casper) - * [水平扩展](#水平扩展) - * [Interledger协议](#Interledger协议) - * [侧链](#侧链) - * [以太坊扩展性的努力](#以太坊扩展性的努力) - * [普遍扩展](#普遍扩展) - * [闪电网络](#闪电网络) - * [隔离验证人](#隔离验证人) - * [附录](#附录) - * [分叉问责制](#分叉问责制) - * [Tendermint共识](#Tendermint共识) - * [Tendermint轻客户端](#Tendermint轻客户端) - * [远程攻击的防御](#远程攻击的防御) - * [克服分叉与审查攻击](#克服分叉与审查攻击) - * [ABCI说明](#ABCI说明) - * [IBC数据包交付确认](#IBC数据包交付确认) - * [默克尔树及默克尔证明的说明](#默克尔树及默克尔证明的说明) - * [交易类型](#交易类型) - * [IBCBlockCommitTx](#ibcblockcommittx) - * [IBCPacketTx](#ibcpackettx) - * [鸣谢](#鸣谢) - * [引用](#引用) - -## 介绍 ################################################################ - - -开源的生态系统、去中心化的文件共享、以及公共的加密货币,这一系列技术的成功使人们启发和理解,去中心化的互联网协议是可以从根本上改善社会经济基础架构的。我们已经见识过个有专长的区块链应用,诸如比特币[\[1\]][1](加密货币),ZCASH [\[2\]][2] (隐私加密货币),也看到了例如以太坊 [\[3\]][3] 的大众智能合约平台,还有无数基于 EVM (以太坊虚拟机)开发的分布式应用,例如 Augur(预测市场)和 TheDAO [\[4\]][4] (投资俱乐部) - - -然而,迄今为止,这些区块链已经暴露了各种缺陷,包括总体能效低下,性能不佳或受到限制和缺乏成熟的治理机制。为了扩大比特币交易吞吐量,已经研发了许多诸如隔离见证 [\[5\]][5](Segregated-Witness)和BitcoinNG [\[6\]][6](一种新的可扩展协议)这样的解决方案,但这些垂直扩展解决方案仍然受到单一物理机容量的限制,以确保完整的可审计性。闪电网络 [\[7\]][7] 可以通过部分交易完全记录在主链账本外来扩展比特币的交易容量,这种方法十分适用于微支付和隐私保护支付通道,但是无法适用于更通用的扩展需求。 - - -理想的解决方案是允许多个并行的区块链交互操作的同时保持其安全特性。事实证明,采用工作量证明很难做到这一点,但也并非不可能。例如合并挖矿,允许在工作完成的同时,确保母链在子链上被重复使用,但交易必须通过每个节点依次进行验证,而且如果母链上的大多数哈希算力没有积极地对子链进行合并挖矿,那么就容易遭受到攻击。关于[可替代区块链网络架构的学术回顾](http://vukolic.com/iNetSec_2015.pdf) 将在附件中展示,我们也会在[相关工作](#related-work)中对其他(技术)方案和缺陷进行概括。 - - -这里我们要介绍的 Cosmos,一个全新的区块链网络架构,能够解决所有这些问题。Cosmos 是由许多被称之为“分区”的独立区块链组成的网络。分区在 Tendermint Core [\[8\]][8]的支持下运行,Tendermint Core 是一个[类似拜占庭容错](http://tendermint.com/blog/tendermint-vs-pbft/)安全共识引擎,具有高性能、一致性的特性,并且在严格的[分叉追责](#fork-accountability) 机制下能够制止恶意破坏者的行为。Tendermint Core 的拜占庭容错共识算法十分适合用于扩展权益证明(PoS)机制下的公共区块链。使用其他共识模型的区块链, 包括类似基于权益证明(PoS)的以太坊,以及比特币也能够通过使用适配分区被 Cosmos 网络连接。 - - -Cosmos 的第一个分区称之为 Cosmos 枢纽。Cosmos 枢纽是一种多资产权益证明加密货币网络,它通过简单的治理机制能够对网络进行适配和升级。此外,Cosmos 枢纽可以通过链接其他分区来实现扩展。 - - -Cosmos 网络的枢纽及各个分区可以通过区块链间通信(IBC)协议进行通信,这种协议就是针对区块链的虚拟用户数据报协议(UDP)或者传输控制协议(TCP)。代币可以安全、快速地从一个分区转到其他分区,而无需在两个分区之间拥具有汇兑流动性。相反,所有跨分区的代币转移都会通过 Cosmos 枢纽,以此来追踪记录每个分区持有代币的总量。这个枢纽会将每个分区与其他故障分区隔离开。因为每个人都可以将新的分区连接到 Cosmos 枢纽,所以分区将可以向后兼容新的区块链技术。 - - -利用 Cosmos 可以实现区块链间的互操作。这是一个具有潜力的有价值的互联网络,其中的资产由不同的验证人发布和控制,并可以在不依靠需要信任的第三方的情况下实现跨链资产无缝的转移和交易。 - -## Tendermint - - -在这一部分我们将阐述Tendermint共识协议和用于建立其应用程序的接口。 更多信息,请参见[附录](#附录) - -### 验证人 - - - -在经典的拜占庭容错算法中,每个节点有相同的权重。在 Tendermint,节点有着不同数量(非负)的 _投票权_,而那些拥有相当数量投票权的节点称之为 _验证人_。验证人通过广播加密签名、投票或者对下一个区块表决同意来参与共识协议。 - -验证者的投票权是一开始就确定好了,或者根据应用程序由区块链来决定修改投票权。例如,在像Cosmos 枢纽的权益证明应用里,投票权可由绑定为押金的代币数量来决定。 - - -注意:像⅔和⅓这样的分数指的是占总投票权的分数,而不是总验证人,除非所有验证人拥有相同权重。而>⅔ 的意思是"超过⅔ ",≥⅓则是"⅓或者更多"的意思。 - -### 共识 - - -Tendermint 是部分同步运作的拜占庭容错共识协议,这种协议源自DLS共识算法 [\[20\]][20]。Tendermint以简易性、高性能以及[分叉问责制](#fork-accountability)而著称。协议要求这组验证人固定且被熟知,并且每个验证人都有其公钥验证身份。这些验证人试图同时在一个区块上达成共识,这些区块是一系列的交易记录。每个区块的共识轮流进行,每一轮都会有个领头人,或者提议人,由他们来发起区块。之后验证人分阶段对是否接受该区块,或者是否进入下一轮做出投票。每轮的提议人会从验证人顺序列表中按照其投票权比例来选择确定。 - - -更多协议的全部细节,请点击[这里](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm). - - -Tendermint 采用了使用大多数投票(超过三分之二)和锁定机制的最优拜占庭容错,来确保其安全性。这些能够保证: - -* 蓄意破坏者想要造成安全性问题,必须有三分之一以上的投票权,并且要提交超过两份以上的值。 -* 如果有一组验证人成功破坏了安全性,或者曾试图这么做,他们会被协议识别。协议包括对有冲突的区块进行投票和广播那些有问题的投票。 - - -除了其超强的安全性外,Tendermint还具备杰出的性能。以商用型云平台为例,Tendermint共识以分布在五大洲七个数据中心的64位节点为基准,其每秒可以处理成千上万笔交易,订单提交延迟时间为1-2秒。而值得关注的是,即使是在极其恶劣的敌对环境中,比如验证人崩溃了或者是广播恶意破坏的投票,也能维持这种每秒超过千笔交易的较高性能。详见下图。 - -![Figure of Tendermint throughput performance](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/images/tendermint_throughput_blocksize.png) - -### 轻客户端 - - -Tendermint 共识算法的主要好处是具有安全简易的客户端,使其成为手机和物联网用例的理想选择。比特币轻客户端必须同步运行区块头组成的链,并且找到工作量证明最多的那一条链,而Tendermint轻客戸端只需和验证组的变化保持一致,然后简单地验证最新区块中预先提交的>⅔,来确定最新情况。 - -这种简单的轻客戸端证明机制也可以实现[区块链之间的通信](#inter-blockchain-communication-ibc)。 - -### 防止攻击 - - -Tendermint 有各种各样的防御措施来防止一些明显的攻击,比如[远程无利害关系双花攻击](#preventing-long-range-attacks) 及[审查制度](#overcoming-forks-and-censorship-attacks)。 这些在[附录](#appendix)中有更详细的讨论。 - -### ABCI - - -Tendermint共识算法是在叫做 Tendermint Core 的程序中实现的。这个程序是独立于应用的“共识引擎”,可以将任何已经确定的黑盒应用转变为分布式、可复制的区块链。Tendermint Core 可以通过应用区块链接口(ABCI) [\[17\]][17]与其他区块链应用连接。而且,应用区块链接口(ABCI) 接口允许区块链应用以任何语言编程实现,而不仅仅是写这个共识引擎所使用的语言。此外,应用区块链接口(ABCI) 也让交换任何现有区块链栈的共识层成为可能。 - - -我们将其与知名加密货币比特币进行了类比。在比特币这种加密币区块链中,每个节点都维持着完整的审核过的 UTXO(未使用交易输出)数据库。如果您想要在应用区块链接口(ABCI)基础上,创建出类似比特币的系统,那么 Tendermint Core 可以做到: - -* 在节点间共享区块及交易 -* 创建规范或不可改变的交易顺序(区块链) - - -同时,ABCI应用也可以做到: - -* 维护 UTXO 数据库 -* 验证交易的加密签名 -* 防止出现不存在的余额被交易 -* 允许客户访问UTXO数据库 - - - -## Cosmos 概述 - - -Cosmos是一个独立平行的区块链网络,其中每条区块链通过 Tendermint[1](http://github.com/tendermint/tendermint)这样的经典拜占庭容错共识算法来运行。 - - -网络中第一条区块链将会是 Cosmos 枢纽。Cosmos 枢纽通过全新的区块链间通信协议来连接其他众多区块链(或将其称之为 _分区_)。Cosmos 枢纽可以追踪无数代币的种类,并且在各个连接的分区里记录各种代币总数。代币可以安全快速地从一个分区转移到另一个分区,两者之间无需体现汇兑流动性,因为所有分区之间的代币传输都会经过 Cosmos 枢纽。 - - -这一架构解决了当今区块链领域面临的许多问题,包括应用程序互操作性、可扩展性、以及可无缝升级的能力。比如,从Bitcoind、Go-Ethereum、CryptoNote、ZCash或其他区块链系统中衍生出来的分区,都能被锚定接入 Cosmos 枢纽。这些分区允许 Cosmos 实现无限扩展,从而满足全球交易的需求。此外,分区也完全适用于分布式交易所,反之交易所也支持分区运行。 - - - -Cosmos 不仅仅是单一的分布式账本,而 Cosmos 枢纽也不是封闭式庭院或宇宙的中心。我们正在为分布式账本的开放网络设计一套协议,这套协议将基于密码学、稳健经济学、共识理论、透明性及可追责制的原则,成为未来金融系统的全新基础。 - -### Tendermint-拜占庭容错 - - -Cosmos 枢纽是 Cosmos 网络中第一条公共区块链,通过 Tendermint 的拜占庭共识算法运行。Tendermint 开源项目创立于2014年,旨在解决比特币工作量证明共识算法的速度、可扩展性以及造成的环境问题。通过采用并提高已经过验证的拜占庭算法(1988年在麻省理工学院开发)[\[20\]][20],Tendermint 成为了首个在概念论证了权益证明加密货币的团队,这种机制可以解决 NXT 和 BitShares 这些第一代权益证明加密币面临的"无利害关系"的问题。 - - -如今,实际上所有比特币移动钱包都要使用可靠的服务器来进行交易验证。这是因为工作量证明机制需要在交易被认定为无法逆转前进行多次确认。而在 Coinbase 之类的服务中也已经出现双重支付攻击。 - - -和其他区块链共识系统不同,Tendermint 提供的是即时、可证明安全的移动客户端支付验证方式。因为 Tendermint 被设计为完全不分叉,所以移动钱包就可以实时接收交易确认,从而在智能手机上真正实现去信任的支付方式。这一点也大大影响了物联网应用程序。 - - -Cosmos 中的验证人角色类似比特币矿工,但是他们采用加密签名来进行投票。验证人是专门用来提交区块的安全机器。非验证人可以将权益代币(也叫做"atom"币)委托给任何验证人来赚取一定的区块费用以及atom奖励,但是如果验证人被黑客攻击或者违反协议规定,那么代币就会面临被惩罚(削减)的风险。Tendermint 拜占庭共识的可证明安全机制,以及利益相关方(验证人和委托人)的抵押品保证,为节点甚至是轻客户端提供了可证明、可量化的安全性。 - -### 治理 - -分布式公共账本应该要有一套章程与治理体系。比特币依靠比特币基金会以及挖矿来协作更新,但是这是一个反应缓慢的治理制度。以太坊在采用硬分叉成 ETH 和 ETC 来解决 The DAO 黑客,这主要是因为之前没有设定社会契约或机制来进行这类决定。 - - -Cosmos 枢纽的验证人与委托人可以对提案进行投票,从而改变预先默认设置好的系统参数(比如区块转账费用限制),协作更新,并对可读性的章程进行修订投票,从而治理 Cosmos 枢纽制度。这个章程允许权益相关者聚集到一起,来解决盗窃及漏洞等相关问题(比如The DAO事件),并得出更快更明确的解决方案。 - - - -每个分区也可以制定自己的一套章程及治理机制。比如,Cosmos 枢纽的章程可以设置为强制实现枢纽的不可改变性(不能回滚,除了 Cosmos 枢纽节点产生的漏洞),而每个分区则可设置自己的回滚政策。 - - -Cosmos 网络能够在制度不同的分区间实现互操作性,这一点给客户极高的自由度和潜力而无需许可即可实验(新技术)。 - -## 枢纽与分区 - - -这里我们将描述一个全新的去中心化与可扩展性模型。Cosmos 网络通过 Tendermint 机制来运行众多的区块链。虽然现存提案的目标是创建一个包含全球所有交易订单的"单一区块链",但是 Cosmos 允许众多区块链在并行运行的同时,保持可互操作性。 - - -在这个基础上,Cosmos枢纽负责管理称之为“分区”的众多独立区块链(有时也叫做"分片",参考自众所周知的数据库扩展技术"分片")。枢纽上的分片会源源不断地提交最新区块,这一点可以让枢纽同步每一个分区的状态。同样地,每个分区也会和枢纽的状态保持一致(不过分区之间不会同彼此的同步,除非间接通过枢纽来实现)。通过发布默克尔证明来证明消息被接受和发送,来让消息从一个分区传递到另一个分区。这种机制叫做"区块链间通信",或者简称为"IBC"机制。 - -![Figure of hub and zones acknowledgement](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/images/hub_and_zones.png) - - -任何分区都可以自行成为枢纽来建立非循环图表,但为了清楚起见,我们只描述这种只有一个枢纽和许多非枢纽的分区这样简单的配置 - - -### 枢纽 - - -Cosmos枢纽是承载多种分布式账本资产的区块链,其中代币可以由个人或分区自己持有。这些代币能够通过特殊的IBC数据包,即"代币数据包"(coin packet)从一个分区转移到另一个分区。枢纽负责保持各个分区中各类代币总量不变。IBC代币数据包交易必须由发送人、枢纽及区块接受者执行。 - - - -因为Cosmos枢纽在整个系统中扮演着中央代币账本的角色,其安全性极其重要。虽然每个分区可能都是一个Tendermint区块链——只需通过4个,(或者在无需拜占庭容错共识的情况下更少的验证人来保证安全),但是Cosmos枢纽必须通过全球去中心化验证组来保证安全,而且这个验证组要能够承受最严重的攻击,比如区域网络分裂或者由国家发起的攻击。 - -### 分区 - - -Cosmos分区是独立的区块链,能够和Cosmos枢纽进行IBC消息交换。从枢纽的角度上看,分区是一种多重资产、动态会员制的多重签名账户,可以通过IBC数据包用来发送和接受代币。就像加密币账户一样,分区不能转移超出其持有量的代币,不过可以从其他拥有代币的人那里接收代币。分区可能会被指定为一种或多种代币的"来源",从而赋予其增加代币供应量的权力。 - - -Cosmos 枢纽的 Atom 或可作为分区验证人连接到枢纽的筹码。虽然在Tendermint分叉责任制下,分区出现双重支付攻击会导致atom数量减少,但是如果分区中有超过⅔的选票都出现拜占庭问题的话,那这个分区就可以提交无效状态。Cosmos 枢纽不会验证或执行提交到其他分区的交易,因此将代币发送到可靠的分区间就是用户的责任了。未来 Cosmos 枢纽的管理系统可能会通过改善提案,来解决分区故障问题。比如,在检测到袭击时,可以将有些分区(或全部分区)发起的代币转账将被暂停,实现紧急断路(即暂时中止代币转账)。 - -## 跨链通信-IBC - -现在我们来介绍下枢纽与分区之间通信的方法。假如现在有三个区块链,分别是"分区1"、“分区2"以及"枢纽”,我们想要"分区1"生成一个数据包,通过"枢纽"发送给"分区2"。为了让数据包从一个区块链转移到另一个区块链,需要在接收方区块链上发布一个证明,来明确发送方已经发起了一个数据包到指定目的地。接收方要验证的这个证明,必须和发送方区块头保持一致。这种机制就类似与侧链采用的机制,它需要两个相互作用的链,通过双向传送存在证明数据元(交易),来"知晓"另一方的情况。 - -IBC协议可以自然定义为两种交易的使用:一种是IBCBlockCommitTx 交易,这种交易可以让区块链向任何观察员证明其最新区块哈希值;另一种是IBCPacketTx 交易,这种交易则可以证明某个数据包确实由发送者的应用程序,通过默克尔证明机制(Merkle-proof)传送到了最新区块的哈希值上。 - -通过将IBC机制分离成两个单独的交易,即IBCBlockCommitTx 交易与 IBCPacketTx 交易,我们可以让接收方链的本地费用市场机制,来决定承认哪个数据包,与此同时还能确保发送方的完全自由,让其自行决定能够传出的数据包数量。 - -![Figure of Zone1, Zone2, and Hub IBC without acknowledgement](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_without_ack.png) - - - - -在上述案例中,为了更新"枢纽"上"分区1"的区块哈希(或者说"分区2"上"枢纽"的区块哈希),必须将IBCBlockCommitTx交易的"分区1"区块哈希值发布到"枢纽"上(或者将该交易的"枢纽"区块哈希值发布到"分区2"中)。 - -_关于两种IBC交易类型,详细请参见 [IBCBlockCommitTx](#ibcblockcommittx) 和 [IBCPacketTx](#ibcpacketcommit) - -## 用例 - -### 分布式交易所 - -比特币借助大量复制来增加分布式账本的安全性。用类似的方式,我们可以在区块链上运行交易所,来降低其受内部及外部攻击的可能性。我们称之为去中心化交易所。 - -现今,加密货币社区认为的去中心化交易所基于"跨链原子事务"交易( AXC 交易)。通过AXC交易,两条不同链上的两个用户可以发起两笔转账交易,交易在两个账本上要么一起提交执行,或者两个账本都不执行(即交易的原子性)。比如,两位用户可以通过AXC交易来实现比特币和以太币之间的交易(或是在不同账本上的任意两种代币),即使比特币和以太坊的区块链之间并没有彼此连接。AXC 交易模式下的交易所用户双方不需要彼此信任,也不用依赖交易匹配服务。其弊端是,交易双方必须同时在线才能进行交易。 - -另一种去中心化交易所是进行大量复制的具有独立区块链的分布式交易所。该种交易所的用户可以提交限价订单并关闭他们的计算机,交易可以在用户离线状态下执行。区块链将会代表交易者去完成匹配和交易。 - -一个中心化的交易所可以构建一个有大交易量的限价交易的买卖盘账目,以此来吸引更多的交易者。在交易所领域,流动性会引发更多流动性,因此在交易所业务中,其具有的网络效应也愈发明显(或者说至少产生了"赢家通吃"效应)。目前加密货币交易所 Poloniex 以每24小时2,000万美元的交易量排名第一, Bitfinex 则每24小时500万美元的交易额位列第二。在这种强大的网络效应之下,基于AXC的去中心化交易所的成交量是不太可能超过中心化交易所。去中心化交易所要想和中心化交易所一争高下,就需要支持以限价订单构成的具有深度的交易买卖盘账目的运行。而只有基于区块链的去中心化交易所可以实现这一点。 - -Tendermint提供的快速交易执行是另一大优势。Cosmos的内部网络可以在不牺牲一致性的前提下优先快速的确定最终性,来实现交易的快速完成 —— 同时针对交易订单交易,以及IBC(跨区块链通信)代币与其他网络的交易。 - -综上,根据现有加密货币交易所的情况,Cosmos的一项重大应用就是去中心化交易所(称为 Cosmos DEX)。其交易吞吐能量和委托延时可以与那些中心化交易所媲美。交易者可以在各方离线的状态下提交限价订单。并且,基于Tendermint,Cosmos枢纽以及IBC的情况下,交易者可以快速地完成在交易所及其他网络的资金进出。 - -### 作为其他加密货币的纽带 - -特权分区可以作为和其他加密货币挂钩的代币来源。这种挂钩类似Cosmos枢纽与分区之间的关系,两者都必须及时更新彼此最新的区块链,从而验证代币已经从一方转移到另一方。Cosmos网络上挂钩的”桥接分区“要和中心以及其他加密币保持同步。这种间接通过”桥接分区“可以保持枢纽逻辑的简洁。并且不必要了解其他的链上共识战略,如比特币工作量证明挖矿机制。 - -#### 向Cosmos枢纽发送代币 - -每个挂钩桥接分区的验证人都会在基于Tendermint公式的区块链之上,运行带有特殊的ABCI桥接应用程序,但同时也会运行一个原有区块链的“全节点”。 - -在原有区块链挖出新区块时,桥接分区验证人员将通过签署和分享起始点区块链的提示,各自局部视角可以达成一致。当一个桥接分区收到原有区块链的支付时(如在以太坊或比特币等PoW机制的链上有足够数目的确认),则在该桥接分区上创建具有该对应账户的余额。 - -就以太坊而言,桥接分区可以和Cosmos枢纽共享相同的验证人。以太坊方面(原本区块链),一个桥接合约将允许以太拥有者通过将以太币发送到以太坊的桥接分区的桥接合约上。一旦挂桥接合约接收到以太币,以太币就不能被撤回,除非从桥接分区接收到对应的IBC数据包。桥接合约跟随桥接分区的验证组,它可能与Cosmos枢纽的验证人组相同。 - -就比特币而言,概念是相似,除了代替一个桥接合约,每个UTXO将由一个门限多重签名P2SH数据库限制。由于P2SH系统的限制,签名者不能与Cosmos枢纽的验证人组相同。 - -#### 从Cosmos枢纽提出代币 - -桥接分区上的以太币(“桥接以太币”)可以在枢纽间转进,转出,完成传送到特定以太坊提取地址后,转出的“桥接以太币”被彻底删除。一个IBC消息可以证明桥接分区上的交易,这个消息将被公布到以太坊桥接合约中,以便以太币被取出。 - -就比特币而言,严谨的交易脚本系统让IBC币的镜像转换机制很难实现。每个UTXO都有自己的特定的脚本,所以当比特币履约签名者发生变化时,每个UTXO都必须迁移到新的UTXO。一个解决方案是根据需要,压缩和解压缩UTXO-set,以保持UTXO的总数量下降。 - -#### 挂钩区完全责任制 - -这类挂钩合约存在风险的风险是,可能会出现恶意的验证人组。如果拜占庭投票权超过⅓,就会造成分叉,即从以太坊桥接合约中提取以太币的同时,还能保持桥接分区中的挂钩以太币不变。甚至,如果拜占庭投票权超过⅔,可能会有人直接通过脱离原始桥接分区的桥接逻辑,对发送以太币发到桥接合约中的帐户下手,盗取以太币。 - -如果将这个桥接方法完全设计成责任制,就有可能解决这一问题。比如,枢纽及起始点的全部IBC包裹可能需要先通过桥接分区的认可,即让枢纽或起始点中的桥接合约对桥接分区的所有状态转换进行有效验证。枢纽及起始点要允许桥接分区的验证人提供抵押物,而侨界合约的代币转出需要延时(且抵押品解绑时间也要足够长),从而让单独的审计人有时间发起任何的质询。我们会把这一系统的设计说明以及执行方式开放,作为未来Cosmos改善的提议,以待Cosmos枢纽的管理系统审批通过。 - -### 以太坊的扩展 - - -众所周知,扩展问题是一直困扰着以太坊的问题。目前以太坊节点会处理节点上每笔交易,并且存储所有的状态[参考](https://docs.google.com/presentation/d/1CjD0W4l4-CwHKUvfF5Vlps76fKLEC6pIwu1a_kC_YRQ/mobilepresent?slide=id.gd284b9333_0_28).。 - -Tendermint提交区块的速度比以太坊工作量证明要快,所以由Tendermint共识推动且使用桥接以太币运行的以太坊虚拟机分区能够强化太坊区块链的性能。此外,虽然Cosmos枢纽及IBC包裹机制不能实现每秒合约逻辑的执行,但是它可以用来协调不同分区中以太坊合约之间的代币流通,通过分片方式为以代币为中心的以太坊扩展奠定基础。 - -### 多用一体化 - -Cosmos分区可以运行任意的应用逻辑,应用在分区创建时设定好,可通过管理者可以不断更新。这种灵活度使得Cosmos分区可以作为其他加密币的挂钩载体,比如以太坊或比特币,并且它还能和这些区块链的衍生品挂钩,利用同样的代码库,而在验证程序及初始分配有所区分。这样就允许多种现有加密币框架得以运行,如以太坊、Zerocash、比特币、CryptoNote等等,将其同Tendermint Core结合,成为通用网络中性能更优的共识引擎,为平台之间提供更多的交互机会。此外,作为多资产区块链,每笔交易都有可能包含多个输入输出项,其中每个输入项都可以是任意代币,使Cosmos直接成为去中心化交易所,当然这里假设交易订单通过其他平台进行匹配。替代方案是,让分区作为分布式容错交易所(包含买卖盘账目),这算是对中心化加密币交易所之上的严格改进——现行交易所在过去时常发生被攻击的事件。 - -分区也可以作为区块链版的企业及政府系统,其原本由一个或多个组织运行的特定服务,现在作为ABCI应用在某个分区上运行,从而在不放弃对底层服务控制的前提下,维持公共Cosmos网络的安全性及交互性。所以,Cosmos或可为那些既想使用区块链技术,又不愿彻底放弃控制权给分布式第三方的人,提供绝佳的运行环境。 - -### 缓解网络分区问题 - -有人认为像Tendermint这种支持一致性的共识算法有一个重大问题,就是网络分区会导致没有任何一个分区会拥有超过⅔的投票权(比如超过⅓投票权在线下),这会中断共识。而Cosmos架构可以缓解这个问题,它可以使用全球中心,同时,各分区实行地区自治,然后让每个分区的投票权按照正常的地域位置进行分配。如,一般范例就有可能是针对个别城市或地区的,让他们各自运行自己分区的同时,还能共享共同的枢纽(比如Cosmos枢纽),并且在临时的网络分区导致的中断期间,也可以继续维持地区自治活动。注意,这样一来在设计稳健的联邦式容错系统过程中,就可以去考虑真实的地理、政治及网络拓扑的特征了。 - -### 联邦式名称解析系统 - -NameCoin是首批试图通过比特币技术解决名称解析问题的区块链之一。不过,这个方案存在一些不足。 - -例如,我们可以通过Namecoin来验证@satoshi(聪)这个号是在过去某个时间点用特定公钥进行注册的。但是,该公钥是否更新过我们就不得而知了,除非将该名称最后一次更新之前的所有全部下载。这一点是比特币UTXO交易模式中默克尔化模型的局限性导致的,这类模型中只有交易(而非可变的应用状态)会以默克尔化加入到区块哈希中。它让我们得在之后用更新证明名称的存在,而非不存在。因此,我们必须依靠全节点才能明确这个名称的最近的值,或者花费大量资源下载整个区块链。 - -即使在NameCoin上运用默克尔化的搜索树,其工作量证明的独立性还是会导致轻客戸端的验证出现问题。轻客戸端必须下载区块链中所有区块头的完整备份(或者至少是自其最后的名称更新的所有区块头)。这意味着带宽需要随着时间做线性的扩展。 [21]此外,在工作量证明制度使区块链上的名称更改需要等额外的工作量证明验证确认才能进行,它在比特币上可能要花费一个小时。 - - -有了Tendermint,我们只需用到由法定数量验证人签署(通过投票权)的区块哈希,以及与名称相关的当前值的默克尔证明。这点让简易、快速、安全的轻客戸端名称值验证成为可能。 - -在Cosmos中,我们可以利用这个概念并延伸。每一个在Cosmos上的名称注册都能有一个相关的最高级别域名(TLD),比如".com"或者".org"等,而且每个名称注册分区都有自己的管理和登记规则。 - -## 发行与激励 - -### Atom 代币 - -Cosmos 枢纽是多资产分布式账本,它有自己的代币,是 Atom 。Atom 是 Cosmos 枢纽唯一的权益代币。Atom是持有人投票、验证或委托给其他验证人的许可证明,就像以太坊上的以太币一样,Atom也可以用来支付交易费以减少电子垃圾。额外的通胀 Atom 和区块交易费用就作为激励分给验证人及委托验证人。 - -`BurnAtomTx`交易可以用来恢复储蓄池中任意比例的代币。 - -#### 众筹 - -创世块上的 Atom 代币及验证人的初次分发是 Cosmos 众售参与者持有75%,预售参与者持有5%,Cosmos网络基金会持有10%,ALL IN BITS, 集团持有10%。从创世块开始,Atom 总量的1/3将作为奖励发放给每年担保持有的验证人以及委托人。 - -更多细节见 [Cosmos Plan](https://github.com/cosmos/cosmos/blob/master/PLAN.md) - -### 验证人的数量限制 - -与比特币或其他工作量证明区块链不同的是, 由于通信的复杂性增加, Tendermint 区块链会随着验证人的增加而变慢。幸运的是, 我们可以支持足够多的验证人来实现可靠的全球化分布式区块链, 并具有非常快的交易确认时间。 而且随着带宽、存储和并行计算容量的增加, 我们将来能够支持更多的验证人。 - -在创世日, 验证人的最大数量将设置为 100, 这个数字将以13% 的速度增长10年, 最终达到300位。 - -``` -Year 0: 100 -Year 1: 113 -Year 2: 127 -Year 3: 144 -Year 4: 163 -Year 5: 184 -Year 6: 208 -Year 7: 235 -Year 8: 265 -Year 9: 300 -Year 10: 300 -... -``` - -### 成为创世日后的验证人 - - -Atom 持有者可以通过签署和提交 `BondTx` 交易成为验证人。抵押的 atom 数量不能为零。任何人任何时候都成为验证人, 除非当前验证人组的数量超过了最大值。在这种情况下, 只有当持有 atom 的数量大于现有验证人中持有有效 atom 数量的最少者, 该交易才有效, 其中有效 atom 包括受委托的 atom。当一个新的验证人以这种方式替换现有的验证人时, 现有的验证人将离线, 其所有的 atom 和受委托的 atom 进入解绑状态。 - -### 对验证人的惩罚 - - -对于任何有意或无意的偏离认可协议的验证人, 必须对其施加一定的惩罚。有些证据立即可予受理, 比如在同样高度和回合的双重签名, 或违反 "预投票锁定" (Tendermint 协商一致议定书的规则)。这样的证据将导致验证人失去其良好的声誉, 其绑定的 atom 以及在储备池中的比例份额 – 统称为 “权益” – 将被大幅削减。 - -有时, 由于区域网络中断、电源故障或其他原因, 验证人将不可用。如果在过去任意时间点的 `ValidatorTimeoutWindow` 块中, 验证人的提交投票不包括在区块链中超过 `ValidatorTimeoutMaxAbsent` 次, 该验证人将离线, 并减少 `ValidatorTimeoutPenalty` (默认 1%) 的权益。 - - -一些 "恶意" 行为在区块链上并没有产生明显的证据。在这些情况下, 如果存在多数的协商一致, 则验证人可以在带外协调,强制将这些恶意验证人超时。 - - -如果 Cosmos 枢纽 因为超过⅓的投票权离线而出现了中止情况,或者说超过⅓的投票权审查到进入区块链的恶意行为,这时候枢纽就必须借助硬分叉重组协议来恢复。(详见“分叉与审查攻击”) - -### 交易费用 - - -Cosmos 枢纽验证人可以接受任何种类的代币或组合作为处理交易的费用。每个验证人可自行设置兑换率, 并选择其想要的交易, 只要不超过 `BlockGasLimit`, 每隔 `ValidatorPayoutPeriod` (默认为1小时) 时间会根据权益相关人绑定的 Atom 比例进行分配。 - - -在所收取的交易费用中, `ReserveTax` (默认 2%) 将存入储备池来增加储备量, 增加 Cosmos 枢纽的安全性和价值。这些资金也可以按照治理系统的决策进行分配。 - - -将投票权委托给其他验证人的 Atom 持有人会支付一定佣金给委托方,而这笔费用可以由每个验证人进行设置。 - -### 激励黑客 - -Cosmos 枢纽的安全取决于底层验证人的安全性和委托人的委托选择。为了鼓励发现和早期报告发现的漏洞, Cosmos 枢纽鼓励黑客通过 `ReportHackTx` 交易发布成功的漏洞, 说, "这个验证人被入侵了,请把赏金发送到这个地址"。这种情况下, 验证人和委托人将挂起闲置, 每个人 `HackPunishmentRatio` (默认 5%) 的 atom 将被削减, `HackRewardRatio` (默认 5%) 的 atom 将发送到黑客的赏金地址作为奖励。验证人必须使用其备份密钥来恢复剩余的 atom。 - -为了防止这一特性被滥用于转移未授权的 atom, 在 `ReportHackTx` 前后,Atom的比例(授权的与未授权的) 将保持不变, 而黑客的赏金将包括一些未授权的 atom (如果有的话)。 - -### 治理规范 - -Cosmos 枢纽是由一个分布式组织管理的, 需要一个明确的治理机制, 以协调对区块链的各种变化, 如系统的参数变量, 以及软件升级和宪法修订. - -所有验证人负责对所有提案进行表决。如果未能及时对提案进行表决, 将导致验证人被自动停用一段时间。 这段时间被称为 `AbsenteeismPenaltyPeriod` (默认1周)。 - - -委托人自动继承其委托的验证人的投票权。这一投票可以被手动覆盖掉。而未绑定的 Atom 是没有投票权的。 - - -每个提案都需要 `MinimumProposalDeposit` 代币的保证金, 这可能是一个或多个代币 (包括atom) 的组合。对于每项提案, 投票者可以投票表决取走保证金。如果超过半数的投票者选择取走保证金 (例如, 因为提案是垃圾信息), 那么保证金就会存入储备池, 除了被燃烧的 atoms。 - -对于每项提案, 投票人可以选择下列方案: - -* 同意 -* 强烈同意 -* 反对 -* 强烈反对 -* 弃权 - -决定采纳(或不采纳)提案需要严格的多数投“同意”或“强烈同意”(或者“反对”及“强烈反对”),但是超过1/3的人投“强烈反对”或“强烈支持”的话就可以否决大多数人的决定。如果大多数人票被否决,那么他们每个人都会失去 `VetoPenaltyFeeBlocks` (默认是一天的区块值 ,税费除外) 作为惩罚,而否决大多数决定的那一方还将额外失去 `VetoPenaltyAtoms` 默认为0.1%)的 Atom 作为惩罚。 - -### 参数变更提案 - - -这里定义的任何参数都可在 `ParameterChangeProposal` 通过后改变。 - -### 赏金提案 - - -通过 `BountyProposal` 后, Atom 可以增发和预留储备池资金作为赏金。 - -### 文本提案 - - -所有其他提案,比如用来更新协议的提案,都会通过通用的 TextProposal 来协调。 - - -## 路线图 - - -详见[计划](https://github.com/cosmos/cosmos/blob/master/PLAN.md). - -## 相关工作 - -过去几年涌现了很多区块链共识及扩展性方面的创新。在这一部分中将挑选一些重要的创新进行简单分析。 - -### 共识系统 - -#### 经典拜占庭容错 - -二十世纪八十年代早期就开始研究存在恶意参与者的共识机制,当时Leslie Lamport创造了”拜占庭容错”这个词,用来指那些图谋不轨参与者做出的恶意的行为,与”死机故障”不同,后者只是处理过程崩溃而已。早期针对同步网络也探索出了一些解决方案,网络信息滞后有一个上限,但实际使用是在高度受控的环境下进行,比如精密飞行仪器以及使用原子钟同步的数据中心。直到九十年代后期,实用拜占庭容错( Practical Byzantine Fault Tolerance ,PBFT)[\[11\]][11]才作为有效的、部分同步的共识算法被逐步推广。它可以容忍⅓参与者有恶意行为。PBFT成为标准算法,催生了各种版本,包括最近由IBM提出并使用于Hyperledger超级账本中的算法。 - -和PBFT相比,Tendermint共识的主要好处在于其改善且简化了的底层结构,其中有些是遵循了区块链典范的结果。Tendermint中,区块必须按顺序提交,这就消除复杂性,节省PBFT中状态变化相关的通信开支。在Cosmos和众多加密币中,如果区块N本身没有提交,那么就不能让它之后的区块N+i(i>=1)提交。如果是通信带宽限制导致了区块N未提交到Cosmos Zone上,那么将通信带宽用于分享选票给区块N+i是一种浪费。如果由于网络分区或者节点掉线导致的区块N未提交,那么N+i就无论如何也不能提交。 - -此外,将交易打包成块可以用默克尔哈希纪录应用程序的状态,而不是用PBFT检查机制进行定时摘要。这可以让轻客戸端更快的提交交易证明,以及更快的跨链通信。 - -Tendermint Core中也优化了很多PBFT特性以外的功能。比如,验证人提交的区块被分割多个部分,对其默克尔化后,然后在节点间广播。这种方式可以提高其广播性能(具体请查看LibSwift [19])。而且,Tendermint Core不会对点对点连接做任何假设,只要点对点间的网络不断开,那么它就能正常运行。 - -#### BitShare委托权益 - -BitShares [\[12\]][12]不是第一个采用权益证明机制(proof-of-stake,PoS)的区块链,但是其对PoS在区块链上的研究与推进做出了巨大的贡献,尤其是在DPoS,即受委托权益证明方面。在BitShares中,相关方选择”见证者”负责提交交易顺序并提交;相关方选择”委托人”负责协调软件更新与参数变化。尽管BitShare在理想环境下能达到很高的性能:100k tx/s,1秒的滞后。每一块只有一个单独的签名,得到交易的最终性的时间比区块时间略长。一个标准的协议仍在开发中。利益相关者可以每天去除或者替换有恶意行为的验证人,但是不同于Tendermint PoS的保证金机制,BitShares没有要求验证人或者代理人的提交押金,如果发生双花攻击的话,押金不会被削减。 - -#### Stellar - -Stellar [\[13\]][13]是以Ripple推行的解决方案为基础,它优化了联邦拜占庭协议模型,其中参与共识过程的并不构成一个固定的全局过程。 相反,每个进程节点组织一个或多个“仲裁片”,每个“仲裁片”构成一组可信进程。 Stellar中的“法定人数”被定义为一组包含至少个个节点的一个仲裁片。从而可达成一致。 - -恒星机制的安全性依赖于任何两个仲裁的交集都是非空的假设,同时,节点的可用性要求至少一个“仲裁片”完全由诚实节点组成。这就需要在“法定人数”的大小上作出妥协:人数过少难以获得共识,人数过多则难以信任所有人。可能难以平衡而不对信任做出重大假设。 除此之外,节点必须维护一定数量的仲裁片以获得足够的容错能力(或者任何“完整节点”,大部分结果的依赖),并且提供一种层级化的配置策略,类似于边界网关协议(Border Gateway Protocol,BGP)。BGP被互联网服务供应商(Internet Service Provider,ISP)用来建立全球路由表,也被浏览器用来管理传输层安全协议(Transport Layer Security,TLS)证书。他们都因为不安全而臭名昭着。 - - -对于Stellar论文中基于Tendermint的PoS批评可以通过本文描述的代币策略来缓解。本文提出了名为 _atom_ 的新的代币,它代表未来交易过程中产生的费用和奖励。 基于Tendermint PoS的优势在于其原理相对简单,同时仍可充分保证和证明安全性。 - -#### BitcoinNG - -BitcoinNG是对比特币的改进,允许垂直扩展,比如增加块的大小而避免带来负面的经济后果,例如对矿工造成的影响严重不成比例。 这种改进是通过将leader选举与交易广播分开来实现的:leader首先通过“微块micro-blocks”的PoW选举,然后leader能够广播交易直至下一个新的“微块”。 这减少了赢得PoW比赛所需的带宽要求,使矿主竞争更公平,并通过允许最后一名矿工提交微块以加快交易提交频率。 - -#### Casper - -Casper [\[16\]][16]是以太坊提出的PoS共识算法。 它的主要操作模式是“预测押注”的一致。 通过让验证者基于他们目前看到的其他投注来迭代地下注他们认为哪个块将提交入区块链。最终性可以立即实现。 - -[链接](https://blog.ethereum.org/2015/12/28/understanding-serenity-part-2-casper/). 这是Casper团队研究的一个热点领域。 挑战在于构建一个可以证明是一个演化稳定策略的投注机制。 与Tendermint相比,Casper的主要优势可能在于提供“可用性超越一致性” — 达成共识不需要超过50%的投票权 — 可能是以提交速度或实现复杂性为代价的。 - -### 水平扩展 - -#### Interledger协议 - -Interledger协议(The Interledger Protocol,ILP)[\[14\]][14]不是一种严格的扩展方案。 它通过一个松散耦合的双边关系网络,提供一种指定的跨不同账本系统交互操作。像闪电网络一样,ILP的目的是实现支付,但是它特别关注跨账本类型的支付,并扩展原子事务的处理机制,使得事务的处理不仅支持哈希锁,而且还包括法定人数的公证人(称为原子运输协议)。 后者在账本间交易中实施原子性的机制与Tendermint的轻客户SPV机制类似,因此比较ILP和Cosmos / IBC之间的区别是有必要的,具体见下文。 - -1.ILP不支持连接器公证员的变更,也不允许公证员之间有灵活的权重。 另一方面,IBC是专门为区块链设计的,验证人可以拥有不同的权重,并且随着区块链的发展,成员可以随时更改。 - -2.与闪电网络一样,ILP中的接收人必须在线才能向发起人发送确认。 在IBC代币传输中,接收者所在区块链的验证人集合负责提供确认,而不是接收用户本人。 - -3.最大的不同在于ILP连接器不需要负责对支付状态保持权威性,然而在Cosmos中,hub的验证人负责IBC代币传输状态和每个zone内持有代币数量的权威性。允许从zone之间的安全地不对称地交换代币是本质的创新。在cosmos中的ILP连接器可以看作是一个持久和最安全的区块链分类账:cosmos hub。 - -4.ILP内的跨账本支付需要一个交易所的指令集的支持。因为不存在从一个分类账到另一个分类账不对称的代币转移,只能做到市场等价物的转移。 - -#### 侧链 - -Sidechains [\[15\]][15]是一种通过使用与比特币区块链“双向挂钩”替代区块链来扩展比特币网络性能的机制。(双向挂钩相当于桥接,在cosmos中被称为“桥接”与市场挂钩区分)。侧链使得比特币可以方便的在比特币区块链和侧链间移动,并允许在侧链上实验新功能。在Cosmos Hub中,侧链和比特币是彼此的轻客户端,在比特币区块链和侧链间移动时使用SPV证明。当然,由于比特币使用PoW,以比特币为中心的侧链遭受许多由于PoW作为共识机制的引起的问题和风险。而且,这是一个比特币利益最大化的解决方案,并不像Cosmos那样本地支持各种代币和zone间网络拓扑结构。但是,双向挂钩的核心机制原则上与Cosmos所采用的机制相同。 - -#### 以太坊扩展性的努力 - - -以太坊目前正在研究许多不同的战略,将以太坊区块链的状态分区化,以解决可扩展性的需求。 这些努力的目标是在共享状态空间之上,维持当前以太坊虚拟机提供抽象层。 目前,多项研究工作正在进行。[\[18\]][18] [\[22\]][22] - -##### Cosmos vs Ethereum 2.0 Mauve - - -Cosmos 和 Ethereum 2.0 Mauve [\[22\]][[22] 有不同的设计理念。 - -* Cosmos是针对代币儿 Mauve 是关于扩大计算能力。 -* Cosmos不仅限于 EVM, 所以即使不同的VM也可以交互。 -* Cosmos 让zone的创建者决定验证人。 -* 任何人都可以在Cosmos中建立新的zone(除非管理者另做决定)。 -* hub与zone的失效隔离,所以全局的代币不变量可以保持。 - -### 普遍扩展 - -#### 闪电网络 - - -闪电网络被设计成一种代币传输网络,在比特币区块链(及其他公有区块链)上一层运行,通过把大部分交易从共识分类账之外转移到所谓的“ 付款渠道“。 这通过链上加密货币脚本来实现,这些脚本使双方能够进入双方持有的状态化合同,通过共享数字签名来更新状态,并且在合同结束后最终通过在区块链上发布证据,这种机制首先受到跨链原子互换交易的欢迎。 通过与多方开通支付渠道,闪电网络的参与者可以成为集中点,为其他人的支付提供路由,从而导致支付渠道网络的完全联通,其代价是绑定在支付渠道上的资金。 - -虽然闪电网络也可以轻松地跨越多个独立的区块链,并借助交易市场实现价值转移,但它不能实现从一个区块链到另一个区块链的非对称代币交易。 这里描述的Cosmos网络的主要优点是实现直接的代币交换。 也就是说,我们希望支付渠道和闪电网络将会与我们的代币传输机制一起被广泛采用,从而节省成本和保护隐私。 - -#### 隔离验证人 - - -隔离见证是一个比特币改进建议BIP,[链接](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki) 旨在将每块交易吞吐量提高2倍或3倍,同时使新节点能更快同步区块。 这个解决方案的亮点在于它如何在比特币当前协议的局限下,允许软分叉升级(例如,具有旧版本软件的客户端将在升级后仍可继续运行)。 Tendermint作为一个新的协议没有设计的限制,所以它具有不同的扩展优先级。 TendermintBFT的循环算法,主要基于加密签名而不是采矿,这种算法允许通过多个并行区块链进行水平扩展,而更常规、更频繁的区块提交也允许垂直扩展。 - - - -## 附录 - -### 分叉问责制 - -经过精心设计的共识协议应该能够在超出容错能力或者共识出错的情况下为系统提供一定的保障。这在能够通过拜占庭行为获取实质经济回报的金融系统中显得尤为必要。_分叉问责制_ 就属于这种非常重要的保障机制,这种制度会使一个导致共识出错的进程(例如使协议客户端开始接受不同值——即分叉)被识别出来,并且根据协议规则对其进行惩罚,甚至将其移送至司法系统处置。但当司法系统不可靠或者诉讼费极其昂贵时,为了让验证人们都参与到这个机制当中,该制度会强制要求他们建立安全保证金,一旦检测到恶意行为,那么这些保证金将会被罚没或者削减[\[10\]][10]. - - - -注意这与比特币有所不同,由于网络非同步与局部哈希碰撞的概率特性,比特币的分叉是定期出现的。因为在很多情况下,恶意分叉与非同步引起的分叉是无法分辨的,除非让矿工为挖到的孤立区块支付隐性的机会成本,否则比特币就很难准确地执行分叉问责制。 - -### Tendermint共识 - - -我们把投票阶段分为 _预投票_ 和 _预提交_ 两个阶段。一个投票可以是用于特定区块,也可以用于 _Nil_。我们把同一轮超过⅔的单个区块的预投票总和称为 _Polka_ ,把同一轮超过⅔的单个区块的预提交总和称为 _Commit_。如果在同一轮中对Nil的预提交超过⅔,他们就进入到下一轮中。 - - -注意到协议中的严格的确定性会引发一个弱同步假设,因为错误的发起者必须被检测到并将其略过。验证人们在为Nil预投票之前会等待一段时间,称之为超时提议,这个超时提议的hi等待时间也会随着每一轮的进行而递增。每一轮的进行都是完全不同步的,在这个过程中,只有当验证人收听到超过⅔的网络投票才能进入到下一轮中。实际上,它需要极其强大的阻碍才能阻挠这个弱同步假设(导致无达成共识,无法提交区块),而且通过每个验证人超时提议(TimeoutPropose)的随机值还可更加提高这么做的难度。 - -另一个附加的约束条件,或者叫锁定条例,它能确保网络最终在每个高度只提交一个区块。任何试图在给定高度提交超过一个区块的恶意行为都会被识别出来。首先,一个区块的预提交必须被认为是正当的,并且以Polka的形式提交。如果验证人已经准备在R_1轮中预提交一个区块,我们称他们锁定了这个区块,并且然后用于验证R_2轮新预提交动作的Polka必须进入R_polka轮,其中 R_1 < R_polka <= R_2。其次,验证人们必须为他们锁定的区块提议并且(或者)预投票。这两个条件共同作用,确保了验证人在对其正当性没有充分论证的情况下不能进行预提交操作,并且保证已经完成预提交的验证人不能再为其他东西的预提交贡献证明投票。这样不但可以保证共识算法的安全性,还能保证它的活跃度。 - - -关于这一协议的全部细节参考[这里](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm). - -### Tendermint轻客户端 - -因为生成侧链(一个分叉)意味着至少⅓的担保权益被罚没,Tendermint权益证明(Tendermint-PoS)取消了同步所有区块头的要求。当然,罚没保证金也是需要有人共享分叉证据的,所以轻客戸端就要存储任何它见证的区块哈希提交。另外,轻客戸端可以定期地与验证人组的改变保持同步,以避免出现远程攻击(但是其他解决方案也具有可能性)。 - - -与以太坊类似,Tendermint能够让应用程序在每个区块中嵌入一个全局默克尔根的哈希值,可以简单方便的验证的状态查询,比如查询账户余额、在智能合约中的值,或者未使用交易输出(UTXO)的存在,具体由应用程序的特性决定。 - -### 远程攻击的防御 - - -假设有一个足够有弹性的广播网络采集与一组静态验证人组存在,那么任何区块链分叉都可以被检测到,而且发起攻击的验证人提交的保证金会被罚没。这个由Vitalik Buterin在2014年首次提出的新方法,解决了其他权益证明加密货币的"没有任何相关权益"问题(详见 [相关工作部分](#related-work))。但由于验证人组必须是能够变化的,在较长的时间段内最初的一些验证人会解除j押金绑定,这就使他们可以自由地从创世区块中创建新链,并且因为他们不再有被锁定的保证金,他们将不需要为这个行为支付任何费用。这类攻击被称为远程攻击(LRA),与短程攻击相比,后者对处在押金绑定中的验证人发起的分叉是可以对其进行惩罚的(假设有类似Tendermint共识这样的分叉问责制拜占庭容错算法)。所以远程攻击经常被认为是对权益证明机制的一种危险打击。 - - -幸运的是,远程攻击(LRA)可以用以下的途径来缓解。第一,对于解除绑定的验证人而言(取回抵押保证金并且不再从参与共识中获取费用),保证金在一定时间内不能转移,也可以称其为"解绑周期",这个周期可能长达数周或数月。第二,对于轻客户端的安全性而言,其首次连接到网络时必须根据可信源验证最新的一个区块哈希或者多个最好的区块哈希。这种情况有时被称为"弱主观性"。最后,为了保证安全,必须与最新的验证人组进行频繁的同步,时长与解绑周期一致。这样就确保了轻客戸端在因验证人解绑资金而失去任何权益之前,知道验证人组的变化情况,否则解绑的验证人就会在其绑定的高度后面开始创建新区块来实施远程攻击,以此来欺骗客户端(假设它可以控制足够多的早期私钥)。 - -注意到用这样的方式来对抗远程攻击(LRA)需要对工作量证明(proof-of-work)的原始安全模块进行彻底检查。在工作量证明中(PoW),一个轻客户端可以通过在每一个区块头中运行工作量证明,以此简便地在任何时候与可信任的创始区块的当前高度进行同步。但是,为了对抗远程攻击(LRA),我们需要轻客戸端定期上线追踪验证人组的变动,其中在首次上线时必须格外仔细地根据可靠源来验证从网络采集的信息。诚然,后面这个要求和比特币类似,其协议和软件也必须从可靠源获得。 - - -以上这些为了防止远程攻击的方法,比较好地适用于由Tendermint驱动下的区块链验证人节点以及全节点,因为这些节点需要保持与网络的连接。这些方法同样适用于希望频繁地与网络同步的轻客户端。但是,对于那些不希望频繁接入互联网或者区块链网络的轻客户端来说,还有另一种方法可以解决远程攻击的问题。非验证人节点可以在很长的解绑期内(比如比验证人的解绑期更久)使用代币作为保证金,并且为轻客戸端提供二级证明当前有效性以及过去区块哈希的解决方案。虽然这些代币对于区块链共识的安全性并没有价值,不过他们还是可以为轻客戸端提供强大的保障。如果在以太坊中支持历史区块哈希查询,那么任何人都可以用特定的智能合约来绑定他们的代币,并且提供付费证明服务,从而有效地针对轻客戸端LRA安全问题开发出一个市场。 - -### 克服分叉与审查攻击 - -由于提交区块流程的定义,任何联合后不少于⅓的投票权的节点都可以通过下线或者不广播选票来中止区块链运行。这样的联合也可以通过拒绝包含这些交易的区块来审查特定的交易,尽管这将导致大多数区块提案被拒绝,致使区块提交速率减缓,降低了它的实用性与价值。恶意的联合或许仍然会陆陆续续地广播选票,用阻挠区块链的区块提交来将其逼停,或者使用任何这些攻击的组合攻击。最终,它会通过双重签名或者违反锁定规则来造成区块链分叉。 - -如果一个全球活跃的作恶者也参与进来,就会用可能出现错误的验证组人子集导致速度降低的方法来分割网络。这不只是Tendermint面临的局限性,更确切地说是所有被活跃敌对者控制了网络的共识协议所面临的局限性1。 - - -对于这些类型的攻击,验证人的子集应该通过外部的方式进行协调,以签署选择一个分叉(及关系到的所有证据)与带有签名的验证人的初始子集的重组提案。签署了这样一份重组提案的验证者,将放弃在所有其他分叉上属于他们的保证金。客户端应在重组提案中验证签名以及任何相关的证据,并作出判断或提示终端用户作出决定。例如,一个手机钱包app应该在可能接受任何由一半以上的初始验证人们通过投票权利签署的重组提案时,给予用户安全警告提示。 - -当多于⅓的投票权益是不诚实的时候,一个非同步的拜占庭容错算法步能够达成共识,然而,分叉假设不少于⅓的投票权益已经因不正当的双重签名或者锁定改变而成为不诚实的。因此,签署重组提案是一个协调问题,任何非同步协议都无法解决这个问题(也就是自动的,并且不考虑底层网络的可靠性)。目前,我们通过互联网媒体的社会共识,把重组提案的协调问题留给了用户去协调。验证人必须在签署重组提案之前就确保没有出现网络分割的问题,以此来避免签署两个相冲突的重组提议的情况发生。 - - -假设外部协调媒介和协议是可靠的,对于分叉的担心会比审查攻击要少许多。 - - -除了需要大于⅓的拜占庭投票权益才能启动的分叉和审查制度以外,超过⅔的联合投票权益可能会提交任意、无效的状态。这是任何拜占庭容错算法的共识系统所特有的问题。与利用简单可验证证明来创建分叉的双重签名不同,检测无效状态的提交需要非验证节点来验证整个区块,这意味着非验证节点会保留一份本地的状态副本并执行每一笔交易,然后为他们自己独立计算出状态的根源。一旦检测出来,处理这类故障的唯一方法就是社会共识。打一个比方,在比特币出现问题的情况下,无论是由于软件漏洞造成的分叉(正如2013年3月),还是由于矿工拜占庭行为提交的无效状态(正如2015年7月),由商户、开发者、矿工和其他组织组成的联系紧密的社区所建立起来的社会共识会让他们按照分工来参与到修复网络的工作当中去。此外,由于Tendermint区块链的验证人身份是可识别的,那么如果需要的话,无效状态的提交实际上是可以被法律或其他外部法律体系惩治的。 - -### ABCI说明 - - -ABCI由3种主要的信息类型组成,这三类信息从共识引擎传递到应用程序上,然后应用程序用相应回复信息做出应答。 - - -`AppendTx` 信息是应用程序的主要传递媒介。区块链中的每一笔交易都通过这个信息来传递。应用程序需要验证每笔交易,这将通过接收针对当前状态、应用协议和交易密码证书的AppendTx信息来实现。验证过的交易将需要通过添加数值到键值存储或者更新UTXO数据库的方式来更新应用状态。 - - -`CheckTx`信息与AppendTx信息类似,但它只是为了交易验证。Tendermint Core的内存池会先用CheckTx验证交易有效性,并且只会将有效的交易传递给其他的节点。应用程序会检查交易序列号,如果序列号过期就会根据CheckTx返回一个错误。 - -`Commit`信息是用来计算之后会存入到下一区块头中的当前应用状态的加密提交项。这具有便利的特性。状态的前后矛盾性将会像引起程序错误,从而导致区块链分叉。这也简化了安全轻客戸端的开发,因为默克尔哈希证明可以通过检查区块哈希来加以验证,而区块哈希是由规定人数的验证人们签署的(通过投票权益)。 - - - -此外,ABCI信息允许应用程序保持追踪验证人组的改变,并让应用程序接收诸如高度和提交选票之类的区块信息。 - - -ABCI请求/响应是简单的Protobuf信息。请参考这里的[模式文件](https://github.com/tendermint/abci/blob/master/types/types.proto)。 - -##### AppendTx - * __命令行参数__: - * `Data ([]byte)`: 交易请求信息 - * __Returns__: - * `Code (uint32)`: 回复代码 - * `Data ([]byte)`: 结果字节,如果有的话 - * `Log (string)`: 错误信息 - * __使用__: - 提交并执行一笔交易,如果交易有效,那返回CodeType.OK - -##### CheckTx - * __命令行参数__: - * `Data ([]byte)`: 交易请求信息 - * __Returns__: - * `Code (uint32)`: 回复代码 - * `Data ([]byte)`: 结果字节,如果有的话 - * `Log (string)`: 错误信息 - * __使用__: - 验证一笔交易。这个信息不应该改变应用状态。交易在广播给其他节点前,首先通过CheckTx运行。你可以发起半状态化CheckTx,并在Commit or BeginBlock上清算状态,以允许序列执行同一区块中相关的交易。 - -##### Commit - * __返回值__: - * `Data ([]byte)`: 默克尔根值 - * `Log (string)`: 调试或出错信息 - * __使用__: - 返回当前应用状态。 - -##### Query - * __命令行参数__: - * `Data ([]byte)`: 请求数据 - * __返回值__: - * `Code (uint32)`: 回复代码 - * `Data ([]byte)`: 查询回复字节 - * `Log (string)`: 调试或出错信息 - -##### Flush - * __使用__: - 刷新回复队列。应用types.Application的应用程序无需实施这条信息——这个由项目进行处理。 - -##### Info - * __返回值__: - * `Data ([]byte)`: 信息字节串 - * __使用__: - 返回关于应用程序状态的信息. 应用指定。 - -##### SetOption - * __参数__: - * `Key (string)`: 设置参数 - * `Value (string)`: 参数值 - * __返回值__: - * `Log (string)`: Debug or error message - * __使用__: - 比如,针对内存池的连接可以将键设置为"mode"(模式),值为"mempool"(内存池)。或者针对共识连接,将键设置为"mode",值设置为"consensus"(共识)。其他选项根据可具体应用进行专门设置。 - -##### InitChain - * __参数__: - * `Validators ([]Validator)`: 初始化创世验证人 - * __使用__: - 在创世区块创建时调用 - -##### BeginBlock - * __参数__: - - - * * `Height (uint64)`: 区块刚开始的高度 - * __使用__: - 为新区块的开始提供信号。在附加交易(AppendTxs)前进行调用。 - -##### EndBlock - * __参数__: - * `Height (uint64)`: 结束时的区块高度 - * __返回值__: - * `Validators ([]Validator)`: 具有新选票的变动后的验证人(归零就去除) - * __使用__: - 为区块结束提供信号。在每次提交前所有交易后调用。 - -See [the ABCI repository](https://github.com/tendermint/abci#message-types) for more details - -更多细节请参考 [ABCI知识库](https://github.com/tendermint/abci#message-types)。 - -### IBC数据包交付确认 - - -发送者有很多需要接收链提供数据包交付确认的原因。比如,如果预计目的链会出错,那发送者就可能无法了解目的链的状态。或者,当目的链可能遇到因接收数据包猛烈增多而形成的拒绝服务攻击时,发送者会想要设定数据包超时时限(借助`MaxHeight` 即最大值包域)。 - - -在这些案例中,发送人可以通过在`AckPending`上设置初始数据包状态来要求提供交付确认。然后就由接收链通过包含一个简化的`IBCPacket`的应用默克尔哈希来确认交付。 - -![Figure of Zone1, Zone2, and Hub IBC with acknowledgement](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_with_ack.png) - - -首先,一个`IBCBlockCommit`和`IBCPacketTx`是被上传到“枢纽”上用来证明"分区1"上的`IBCPacket`的存在的。假设`IBCPacketTx`的值如下: - -- `FromChainID`: "Zone1" -- `FromBlockHeight`: 100 (假设) -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 (say) - - `Status`: `AckPending` - - `Type`: "coin" - - `MaxHeight`: 350 (假设 "枢纽" 当前高度为 300) - - `Payload`: <一个"代币"的有效负荷字节> - - -- `FromChainID`: "Zone1" -- `FromBlockHeight`: 100 (假设) -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 (假设) - - `Status`: `AckPending` - - `Type`: "coin" - - `MaxHeight`: 350 (假设"枢纽"目前的高度是300) - - `Payload`: <一个"代币"的有效负荷字节> - - -其次,一个`IBCBlockCommit` 和 `IBCPacketTx`被传输都“分区2”上用来证明`IBCPacket`在“枢纽”上的存在。假设`IBCPacketTx`的值如下: - -- `FromChainID`: "Hub" -- `FromBlockHeight`: 300 -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 - - `Status`: `AckPending` - - `Type`: "coin" - - `MaxHeight`: 350 - - `Payload`: <一个"代币"相同的有效负荷字节> - - -- `FromChainID`: "Hub" -- `FromBlockHeight`: 300 -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 - - `Status`: `AckPending` - - `Type`: "coin" - - `MaxHeight`: 350 - - `Payload`: <一个"代币"相同的有效负荷字节> - - -接下来,"Zone2"必须将缩写的来显示`AckSent`的最新状态包添加到应用程序状态哈希中。 -`IBCBlockCommitand` 和`IBCPacketTx` 会传输到“枢纽"上来证明简化的`IBCPacket` 存在于"分区2"上。假设`IBCPacketTx` 的值如下: - -- `FromChainID`: "Zone2" -- `FromBlockHeight`: 400 (假设) -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 - - `Status`: `AckSent` - - `Type`: "coin" - - `MaxHeight`: 350 - - `PayloadHash`: <一个"代币"相同的有效负荷字节的哈希值> - - -- `FromChainID`: "Zone2" -- `FromBlockHeight`: 400 (假设) -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 - - `Status`: `AckSent` - - `Type`: "coin" - - `MaxHeight`: 350 - - `PayloadHash`: <一个"代币"相同的有效负荷字节的哈希值> - - - -最后,“枢纽”必须更新从`AckPending` 到`AckReceived`的数据包状态。这个新完成状态的证明应该返回到“分区2”上。假设`IBCPacketTx`的值如下: - -- `FromChainID`: "Hub" -- `FromBlockHeight`: 301 -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 - - `Status`: `AckReceived` - - `Type`: "coin" - - `MaxHeight`: 350 - - `PayloadHash`: <The hash bytes of the same "coin" payload> - - -- `FromChainID`: "Hub" -- `FromBlockHeight`: 301 -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 - - `Status`: `AckReceived` - - `Type`: "coin" - - `MaxHeight`: 350 - - `PayloadHash`: <相同"代币"有效负荷的哈希字节> - - - -与此同时,“分区1”会假设“代币”包的交付已经成功,除非"枢纽"上有证据给出相反的证明。在上述例子中,如果"枢纽"没有从"分区2"接收到第350个区块的`AckSent` 状态,那么它就会自动将其设置为`Timeout`(超时)。这个超时的证据可以贴回到"Zone1"上,然后所有代币都会被返还。 - -![Figure of Zone1, Zone2, and Hub IBC with acknowledgement and timeout](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_with_ack_timeout.png) - -### 默克尔树及默克尔证明的说明 - -Tendermint/Cosmos生态支持的两种默克尔树:简单树和IAVL+树。 - -#### 简易版默克尔树 - - -简易版默克尔树针对基础的静态列表。如果项目的数量不是2的次方,那么有些树叶就会在不同的层上。简易树试图让树的两侧在同一高度,但是左边可能会稍大一点。这种默克尔树就是用于一个区块交易的默克尔化的,而顶层元素就是应用状态的根。 - -``` - * - / \ - / \ - / \ - / \ - * * - / \ / \ - / \ / \ - / \ / \ - * * * h6 - / \ / \ / \ - h0 h1 h2 h3 h4 h5 - - A SimpleTree with 7 elements -``` - -#### IAVL+树 - -IAVL+数据结构的目的是永久储存应用状态中的密钥对,这样就可以对确定的默克尔根哈希进行高效的运算。这个树的平衡通过 AVL算法的变体来实现,所有运行都是O(log(n))。 - -在AVL树中,任意节点的两个子树的高度至多有一处不同。无论在什么时候这种情况都是与更新相违背的,这个树都会通过创造O(log(n))新节点(指向旧树上未修改的节点)来再次达到平衡。在初始的AVL算法中,内部节点也可以保留密钥值对。AVL+算法(注意这里有个"+"号)对AVL算法进行了修改,来维持所有数值都在树叶节点上,同时还只需采用分支-节点来存储密钥。这样在维持较短的默克尔哈希轨迹对的同时,还简化了算法。 - -AVL+树类似于以太坊的[帕氏树](http://en.wikipedia.org/wiki/Radix_tree)。其中也有一定的折中。密钥不需要在嵌入到IAVL+树之前生成哈希,所以这就为密钥空间提供了较快的命令迭代,这会为很多应用程序带来好处。逻辑实现更简单,只需要内部节点和树叶节点这两种节点类型。作为一个平衡的二叉树,其默克尔证明平均更短。而另一方面,IAVL+树的默克尔根有取决于命令的更新。 -我们将支持额外有效的默克尔树,比如当二元变量可用时的以太坊帕氏树。 - - -### 交易类型 - - -在标准的执行中,交易通过ABCI界面涌入Cosmos Hub的应用程序中。 - -Cosmos Hub将接收几类主要的交易类型,包括`SendTx`, `BondTx`, `UnbondTx`, `ReportHackTx`, `SlashTx`, `BurnAtomTx`, -`ProposalCreateTx`,以及`ProposalVoteTx`(发送交易、绑定交易、解绑交易、攻击报告交易、削减交易、Atom燃烧交易,创建提案交易、以及提案投票交易),这些都不需要加以说明,会在未来版本的文档中加以备案。这里我们主要列举两个主要的IBC交易类型: `IBCBlockCommitTx` 以及`IBCPacketTx`(即IBC区块提交交易以及IBC数据包交易) - -#### IBCBlockCommitTx - - -IBCBlockCommitTx 交易主要由这些组成: - -- `ChainID (string)`: 区块链ID -- `BlockHash ([]byte)`: 区块哈希字节,就是包括应用程序哈希默克尔根 -- `BlockPartsHeader (PartSetHeader)`: 区块部分设置的头字节,只用于验证投票签名 -- `BlockHeight (int)`: 提交高度 -- `BlockRound (int)`: 提交回合 -- `Commit ([]Vote)`: 超过⅔的包括区块提交的Tendermint预提交投票 -- `ValidatorsHash ([]byte)`: 新验证组的默克尔树根哈希 -- `ValidatorsHashProof (SimpleProof)`: 在区块哈希中证明验证人哈希的简易树默克尔证明 -- `AppHash ([]byte)`: IAVL树,应用程序状态的默克尔树根哈希 -- `AppHashProof (SimpleProof)`: 在区块哈希中验证应用程序哈希的简易版默克尔树证明`AppHash` against the `BlockHash` - -#### IBCPacketTx - - -`IBCPacket` 由下列项组成: - -- `Header (IBCPacketHeader)`: 数据包头 -- `Payload ([]byte)`: 数据包有效负荷字节。可选择。 -- `PayloadHash ([]byte)`: 数据包字节哈希。可选择。 - - - -有效负荷`Payload`或有效负荷哈希`PayloadHash`必须存在一个。`IBCPacket` 的哈希就是两个项的简易版默克尔根,即头和有效负荷。没有完整有效负荷的`IBCPacket` 被称作 _缩写版包_ 。 - - - -`IBCPacketHeader`由下列项组成: - -`SrcChainID (string)`: 源区块链 ID -DstChainID (string)`: 目标区块链ID -Number (int)`: 所有数据包的唯一数字 -Status (enum)`:可以是AckPending,AckSent,AckReceived,NoAck,或Timeout任意一个 -Type (string)`: 种类根据应用程序决定。Cosmos保留"coin"(币)包种类。 -`MaxHeight (int)`: 如果状态不是这个高度给出的`NoAckWanted` 或者`AckReceived` ,那么状态就算超时。可选择。 - -An `IBCPacketTx` transaction is composed of: - -- `FromChainID (string)`: The ID of the blockchain which is providing this - packet; not necessarily the source -- `FromBlockHeight (int)`: The blockchain height in which the following packet - is included (Merkle-ized) in the block-hash of the source chain -- `Packet (IBCPacket)`: A packet of data, whose status may be one of - `AckPending`, `AckSent`, `AckReceived`, `NoAck`, or `Timeout` -- `PacketProof (IAVLProof)`: A IAVLTree Merkle-proof for proving the packet's - hash against the `AppHash` of the source chain at given height - -`IBCPacketTx` 交易有下列项组成: -- `FromChainID (string)`: 提供给这个数据包的区块链ID,不是源所必须的 -- `FromBlockHeight (int)`: 区块链高度,其中接下来的包会包含在(默克尔化的)源链的区块哈希中 -- `Packet (IBCPacket)`:数据包, 其状态可能是`AckPending`, `AckSent`, `AckReceived`, `NoAck`, 或者 `Timeout`其中的一个 -- `PacketProof (IAVLProof)`: IAVL树默克尔证明,用于验证在给定高度下的源链应用哈希中的数据包哈希 - - -通过"Hub",将数据包从"Zone1"发送到"Zone2"的序列,描述在{Figure X}函数中。首先,一个`IBCPacketTx`会向"Hub"证明数据包是包含在"Zone1"的应用程序状态中。然后,另一个`IBCPacketTx` 会向"Zone2"证明数据包包含在"Hub"的应用程序状态中。在这个过程中,`IBCPacketTx` 的字段是相同的:`SrcChainID`永远是"Zone1",而`DstChainID` 永远是"Zone2"。 - -The `PacketProof` must have the correct Merkle-proof path, as follows: - -`PacketProof` 必须有正确的默克尔证明路径,如下: - -``` -IBC/<SrcChainID>/<DstChainID>/<Number> - -``` - - -当“Zone1”要通过“Hub”将数据包传送到“Zone2”中,无论数据包是否在“Zone1”、“Hub”、或者“Zone2”中默克尔化了,`IBCPacket`数据都是相同的。唯一易变的字段是为追踪交付的`Status`。 - -## 鸣谢 ############################################################ - -我们为所有朋友欲同行们在概念成型与检查方面给予的帮助,以及对我们在Tendermint与Cosmos工作中的大力支持,表示衷心地感谢。 - -* [Zaki Manian](https://github.com/zmanian) of - [SkuChain](https://www.skuchain.com/) provided much help in formatting and -wording, especially under the ABCI section -* [Jehan Tremback](https://github.com/jtremback) of Althea and Dustin Byington - for helping with initial iterations -* [Andrew Miller](http://soc1024.com/) of [Honey - Badger](https://eprint.iacr.org/2016/199) for feedback on consensus -* [Greg Slepak](https://fixingtao.com/) for feedback on consensus and wording -* Also thanks to [Bill Gleim](https://github.com/gleim) and [Seunghwan - Han](http://www.seunghwanhan.com) for various contributions. -* __Your name and organization here for your contribution__ - -* [SkuChain](https://www.skuchain.com/)的[Zaki Manian](https://github.com/zmanian)在格式与措辞方面提供了很多帮助,尤其在ABCI部分。 -* Althea的[Jehan Tremback](https://github.com/jtremback)和Dustin Byington在初始迭代方面的帮助。 -* [Honey - Badger](https://eprint.iacr.org/2016/199)的 [Andrew Miller](http://soc1024.com/) 在共识部分给予的反馈。 -* [Greg Slepak](https://fixingtao.com/)对共识部分的反馈以及在措辞方面的帮助。 -* 同时还要感谢 [Bill Gleim](https://github.com/gleim)和 [Seunghwan - Han](http://www.seunghwanhan.com)在多方面的支持与贡献。 -* **此处还有您及您的组织对本文的贡献。 - -## 引用 - -[1]: https://bitcoin.org/bitcoin.pdf -[2]: http://zerocash-project.org/paper -[3]: https://github.com/ethereum/wiki/wiki/White-Paper -[4]: https://download.slock.it/public/DAO/WhitePaper.pdf -[5]: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki -[6]: https://arxiv.org/pdf/1510.02037v2.pdf -[7]: https://lightning.network/lightning-network-paper-DRAFT-0.5.pdf -[8]: https://github.com/tendermint/tendermint/wiki -[9]: https://groups.csail.mit.edu/tds/papers/Lynch/jacm85.pdf -[10]: https://blog.ethereum.org/2014/01/15/slasher-a-punitive-proof-of-stake-algorithm/ -[11]: http://pmg.csail.mit.edu/papers/osdi99.pdf -[12]: https://bitshares.org/technology/delegated-proof-of-stake-consensus/ -[13]: https://www.stellar.org/papers/stellar-consensus-protocol.pdf -[14]: https://interledger.org/rfcs/0001-interledger-architecture/ -[15]: https://blockstream.com/sidechains.pdf -[16]: https://blog.ethereum.org/2015/08/01/introducing-casper-friendly-ghost/ -[17]: https://github.com/tendermint/abci -[18]: https://github.com/ethereum/EIPs/issues/53 -[19]: http://www.ds.ewi.tudelft.nl/fileadmin/pds/papers/PerformanceAnalysisOfLibswift.pdf -[20]: http://groups.csail.mit.edu/tds/papers/Lynch/jacm88.pdf -[21]: https://en.bitcoin.it/wiki/Thin_Client_Security -[22]: http://vitalik.ca/files/mauve_paper.html - -* [1] Bitcoin: https://bitcoin.org/bitcoin.pdf -* [2] ZeroCash: http://zerocash-project.org/paper -* [3] Ethereum: https://github.com/ethereum/wiki/wiki/White-Paper -* [4] TheDAO: https://download.slock.it/public/DAO/WhitePaper.pdf -* [5] Segregated Witness: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki -* [6] BitcoinNG: https://arxiv.org/pdf/1510.02037v2.pdf -* [7] Lightning Network: https://lightning.network/lightning-network-paper-DRAFT-0.5.pdf -* [8] Tendermint: https://github.com/tendermint/tendermint/wiki -* [9] FLP Impossibility: https://groups.csail.mit.edu/tds/papers/Lynch/jacm85.pdf -* [10] Slasher: https://blog.ethereum.org/2014/01/15/slasher-a-punitive-proof-of-stake-algorithm/ -* [11] PBFT: http://pmg.csail.mit.edu/papers/osdi99.pdf -* [12] BitShares: https://bitshares.org/technology/delegated-proof-of-stake-consensus/ -* [13] Stellar: https://www.stellar.org/papers/stellar-consensus-protocol.pdf -* [14] Interledger: https://interledger.org/rfcs/0001-interledger-architecture/ -* [15] Sidechains: https://blockstream.com/sidechains.pdf -* [16] Casper: https://blog.ethereum.org/2015/08/01/introducing-casper-friendly-ghost/ -* [17] ABCI: https://github.com/tendermint/abci -* [18] Ethereum Sharding: https://github.com/ethereum/EIPs/issues/53 -* [19] LibSwift: http://www.ds.ewi.tudelft.nl/fileadmin/pds/papers/PerformanceAnalysisOfLibswift.pdf -* [20] DLS: http://groups.csail.mit.edu/tds/papers/Lynch/jacm88.pdf -* [21] Thin Client Security: https://en.bitcoin.it/wiki/Thin_Client_Security -* [22] Ethereum 2.0 Mauve Paper: http://vitalik.ca/files/mauve_paper.html - -#### 未分类链接 - -* https://www.docdroid.net/ec7xGzs/314477721-ethereum-platform-review-opportunities-and-challenges-for-private-and-consortium-blockchains.pdf.html diff --git a/docs/resources/whitepaper.md b/docs/resources/whitepaper.md deleted file mode 100644 index f77608072d70..000000000000 --- a/docs/resources/whitepaper.md +++ /dev/null @@ -1,1590 +0,0 @@ -# Cosmos - -A Network of Distributed Ledgers - -Jae Kwon <mailto:jae@tendermint.com><br/> -Ethan Buchman <mailto:ethan@tendermint.com> - -For discussions, [join our community chat](https://riot.im/app/#/room/#cosmos:matrix.org)! - -_NOTE: If you can read this on GitHub, then we're still actively developing this -document. Please check regularly for updates!_ - -\[[toc]] - -## Introduction - -The combined success of the open-source ecosystem, decentralized -file-sharing, and public cryptocurrencies has inspired an understanding that -decentralized internet protocols can be used to radically improve socio-economic -infrastructure. We have seen specialized blockchain applications like Bitcoin -[\[1\]][1] (a cryptocurrency), Zerocash [\[2\]][2] (a cryptocurrency for -privacy), and generalized smart contract platforms such as Ethereum [\[3\]][3], -with countless distributed applications for the Ethereum Virtual Machine (EVM) such as Augur (a prediction -market) and TheDAO [\[4\]][4] (an investment club). - -To date, however, these blockchains have suffered from a number of drawbacks, -including their gross energy inefficiency, poor or limited performance, and -immature governance mechanisms. Proposals to scale -Bitcoin's transaction throughput, such as Segregated-Witness [\[5\]][5] and -BitcoinNG [\[6\]][6], are vertical scaling solutions that remain -limited by the capacity of a single physical machine, in order to ensure the -property of complete auditability. The Lightning Network [\[7\]][7] can help -scale Bitcoin transaction volume by leaving some transactions off the ledger -completely, and is well suited for micropayments and privacy-preserving payment -rails, but may not be suitable for more generalized scaling needs. - -An ideal solution is one that allows multiple parallel blockchains to -interoperate while retaining their security properties. This has proven -difficult, if not impossible, with proof-of-work. Merged mining, for instance, -allows the work done to secure a parent chain to be reused on a child chain, -but transactions must still be validated, in order, by each node, and a -merge-mined blockchain is vulnerable to attack if a majority of the hashing -power on the parent is not actively merge-mining the child. An academic review -of [alternative blockchain network -architectures](http://vukolic.com/iNetSec_2015.pdf) is provided for additional -context, and we provide summaries of other proposals and their drawbacks in -[Related Work](#related-work). - -Here we present Cosmos, a novel blockchain network architecture that addresses all -of these problems. Cosmos is a network of many independent blockchains, called -zones. The zones are powered by Tendermint Core [\[8\]][8], which provides a -high-performance, consistent, secure -[PBFT-like](https://blog.cosmos.network/tendermint-vs-pbft-12e9f294c9ab?gi=1777e47b6fc6) consensus engine, -where strict [fork-accountability](#fork-accountability) guarantees hold over -the behaviour of malicious actors. Tendermint Core's BFT consensus algorithm is -well suited for scaling public proof-of-stake blockchains. - -The first zone on Cosmos is called the Cosmos Hub. The Cosmos Hub is a -multi-asset proof-of-stake cryptocurrency with a simple governance mechanism -which enables the network to adapt and upgrade. In addition, the Cosmos Hub can be -extended by connecting other zones. - -The hub and zones of the Cosmos network communicate with each other via an -inter-blockchain communication (IBC) protocol, a kind of virtual UDP or TCP for -blockchains. Tokens can be transferred from one zone to another securely and -quickly without the need for exchange liquidity between zones. Instead, all -inter-zone token transfers go through the Cosmos Hub, which keeps track of the -total amount of tokens held by each zone. The hub isolates each zone from the -failure of other zones. Because anyone can connect a new zone to the Cosmos Hub, -zones allow for future-compatibility with new blockchain innovations. - -## Tendermint - -In this section we describe the Tendermint consensus protocol and the interface -used to build applications with it. For more details, see the [appendix](#appendix). - -### Validators - -In classical Byzantine fault-tolerant (BFT) algorithms, each node has the same -weight. In Tendermint, nodes have a non-negative amount of _voting power_, and -nodes that have positive voting power are called _validators_. Validators -participate in the consensus protocol by broadcasting cryptographic signatures, -or _votes_, to agree upon the next block. - -Validators' voting powers are determined at genesis, or are changed -deterministically by the blockchain, depending on the application. For example, -in a proof-of-stake application such as the Cosmos Hub, the voting power may be -determined by the amount of staking tokens bonded as collateral. - -_NOTE: Fractions like ⅔ and ⅓ refer to fractions of the total voting power, -never the total number of validators, unless all the validators have equal -weight. >⅔ means "more than ⅔", ≥⅓ means "at least ⅓"._ - -### Consensus - -Tendermint is a partially synchronous BFT consensus protocol derived from the -DLS consensus algorithm [\[20\]][20]. Tendermint is notable for its simplicity, -performance, and [fork-accountability](#fork-accountability). The protocol -requires a fixed known set of validators, where each validator is identified by -their public key. Validators attempt to come to consensus on one block at a time, -where a block is a list of transactions. Voting for consensus on a block proceeds in -rounds. Each round has a round-leader, or proposer, who proposes a block. The -validators then vote, in stages, on whether to accept the proposed block -or move on to the next round. The proposer for a round is chosen -deterministically from the ordered list of validators, in proportion to their -voting power. - -The full details of the protocol are described -[here](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm). - -Tendermint’s security derives from its use of optimal Byzantine fault-tolerance -via super-majority (>⅔) voting and a locking mechanism. Together, they ensure -that: - -- ≥⅓ voting power must be Byzantine to cause a violation of safety, where more - than two values are committed. -- if any set of validators ever succeeds in violating safety, or even attempts - to do so, they can be identified by the protocol. This includes both voting - for conflicting blocks and broadcasting unjustified votes. - -Despite its strong guarantees, Tendermint provides exceptional performance. In -benchmarks of 64 nodes distributed across 7 datacenters on 5 continents, on -commodity cloud instances, Tendermint consensus can process thousands of -transactions per second, with commit latencies on the order of one to two -seconds. Notably, performance of well over a thousand transactions per second -is maintained even in harsh adversarial conditions, with validators crashing or -broadcasting maliciously crafted votes. See the figure below for details. - -![Figure of Tendermint throughput performance](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/images/tendermint_throughput_blocksize.png) - -### Light Clients - -A major benefit of Tendermint's consensus algorithm is simplified light client -security, making it an ideal candidate for mobile and internet-of-things use -cases. While a Bitcoin light client must sync chains of block headers and find -the one with the most proof of work, Tendermint light clients need only to keep -up with changes to the validator set, and then verify the >⅔ PreCommits -in the latest block to determine the latest state. - -Succinct light client proofs also enable [inter-blockchain -communication](#inter-blockchain-communication-ibc). - -### Preventing Attacks - -Tendermint has protective measures for preventing certain notable -attacks, like [long-range-nothing-at-stake double -spends](#preventing-long-range-attacks) and -[censorship](#overcoming-forks-and-censorship-attacks). These are discussed more -fully in the [appendix](#appendix). - -### ABCI - -The Tendermint consensus algorithm is implemented in a program called Tendermint -Core. Tendermint Core is an application-agnostic "consensus engine" that can -turn any deterministic blackbox application into a distributedly replicated -blockchain. Tendermint Core connects to blockchain -applications via the Application Blockchain Interface (ABCI) [\[17\]][17]. Thus, ABCI -allows for blockchain applications to be programmed in any language, not just -the programming language that the consensus engine is written in. Additionally, -ABCI makes it possible to easily swap out the consensus layer of any existing -blockchain stack. - -We draw an analogy with the well-known cryptocurrency Bitcoin. Bitcoin is a -cryptocurrency blockchain where each node maintains a fully audited Unspent -Transaction Output (UTXO) database. If one wanted to create a Bitcoin-like -system on top of ABCI, Tendermint Core would be responsible for - -- Sharing blocks and transactions between nodes -- Establishing a canonical/immutable order of transactions (the blockchain) - -Meanwhile, the ABCI application would be responsible for - -- Maintaining the UTXO database -- Validating cryptographic signatures of transactions -- Preventing transactions from spending non-existent funds -- Allowing clients to query the UTXO database - -Tendermint is able to decompose the blockchain design by offering a very simple -API between the application process and consensus process. - -## Cosmos Overview - -Cosmos is a network of independent parallel blockchains that are each powered by -classical BFT consensus algorithms like Tendermint -[1](https://github.com/tendermint/tendermint). - -The first blockchain in this network will be the Cosmos Hub. The Cosmos Hub -connects to many other blockchains (or _zones_) via a novel inter-blockchain -communication protocol. The Cosmos Hub tracks numerous token types and keeps -record of the total number of tokens in each connected zone. Tokens can be -transferred from one zone to another securely and quickly without the need for -a liquid exchange between zones, because all inter-zone coin transfers go -through the Cosmos Hub. - -This architecture solves many problems that the blockchain space faces today, -such as application interoperability, scalability, and seamless upgradability. -For example, zones derived from Bitcoind, Go-Ethereum, CryptoNote, ZCash, or any -blockchain system can be plugged into the Cosmos Hub. These zones allow Cosmos -to scale infinitely to meet global transaction demand. Zones are also a great -fit for a distributed exchange, which will be supported as well. - -Cosmos is not just a single distributed ledger, and the Cosmos Hub isn't a -walled garden or the center of its universe. We are designing a protocol for -an open network of distributed ledgers that can serve as a new foundation for -future financial systems, based on principles of cryptography, sound economics, -consensus theory, transparency, and accountability. - -### Tendermint-BFT - -The Cosmos Hub is the first public blockchain in the Cosmos Network, powered by -Tendermint's BFT consensus algorithm. The Tendermint open-source project was -born in 2014 to address the speed, scalability, and environmental issues of -Bitcoin's proof-of-work consensus algorithm. By using and improving upon -proven BFT algorithms developed at MIT in 1988 [\[20\]][20], the Tendermint -team was the first to conceptually demonstrate a proof-of-stake cryptocurrency -that addresses the nothing-at-stake problem suffered by first-generation -proof-of-stake cryptocurrencies such as NXT and BitShares1.0. - -Today, practically all Bitcoin mobile wallets use trusted servers to provide -them with transaction verification. This is because proof-of-work requires -waiting for many confirmations before a transaction can be considered -irreversibly committed. Double-spend attacks have already been demonstrated on -services like CoinBase. - -Unlike other blockchain consensus systems, Tendermint offers instant and -provably secure mobile-client payment verification. Since the Tendermint is -designed to never fork at all, mobile wallets can receive instant transaction -confirmation, which makes trustless and practical payments a reality on -smartphones. This has significant ramifications for Internet of Things applications as well. - -Validators in Cosmos have a similar role to Bitcoin miners, but instead use -cryptographic signatures to vote. Validators are secure, dedicated machines -that are responsible for committing blocks. Non-validators can delegate their -staking tokens (called "atoms") to any validator to earn a portion of block fees -and atom rewards, but they incur the risk of getting punished (slashed) if the -delegate validator gets hacked or violates the protocol. The proven safety -guarantees of Tendermint BFT consensus, and the collateral deposit of -stakeholders--validators and delegators--provide provable, quantifiable -security for nodes and light clients. - -### Governance - -Distributed public ledgers should have a constitution and a governance system. -Bitcoin relies on the Bitcoin Foundation and mining to -coordinate upgrades, but this is a slow process. Ethereum split into ETH and -ETC after hard-forking to address TheDAO hack, largely because there was no -prior social contract nor mechanism for making such decisions. - -Validators and delegators on the Cosmos Hub can vote on proposals that can -change preset parameters of the system automatically (such as the block gas -limit), coordinate upgrades, as well as vote on amendments to the human-readable -constitution that govern the policies of the Cosmos Hub. The constitution -allows for cohesion among the stakeholders on issues such as theft -and bugs (such as TheDAO incident), allowing for quicker and cleaner resolution. - -Each zone can also have their own constitution and governance mechanism as well. -For example, the Cosmos Hub could have a constitution that enforces immutability -at the Hub (no roll-backs, save for bugs of the Cosmos Hub node implementation), -while each zone can set their own policies regarding roll-backs. - -By enabling interoperability among differing policy zones, the Cosmos network -gives its users ultimate freedom and potential for permissionless -experimentation. - -## The Hub and Zones - -Here we describe a novel model of decentralization and scalability. Cosmos is a -network of many blockchains powered by Tendermint. While existing proposals aim -to create a "single blockchain" with total global transaction ordering, Cosmos -permits many blockchains to run concurrently with one another while retaining -interoperability. - -At the basis, the Cosmos Hub manages many independent blockchains called "zones" -(sometimes referred to as "shards", in reference to the database scaling -technique known as "sharding"). A constant stream of recent block commits from -zones posted on the Hub allows the Hub to keep up with the state of each zone. -Likewise, each zone keeps up with the state of the Hub (but zones do not keep up -with each other except indirectly through the Hub). Packets of information are -then communicated from one zone to another by posting Merkle-proofs as evidence -that the information was sent and received. This mechanism is called -inter-blockchain communication, or IBC for short. - -![Figure of hub and zones -acknowledgement](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/images/hub_and_zones.png) - -Any of the zones can themselves be hubs to form an acyclic graph, but -for the sake of clarity we will only describe the simple configuration where -there is only one hub, and many non-hub zones. - -### The Hub - -The Cosmos Hub is a blockchain that hosts a multi-asset distributed ledger, -where tokens can be held by individual users or by zones themselves. These -tokens can be moved from one zone to another in a special IBC packet called a -"coin packet". The hub is responsible for preserving the global invariance of -the total amount of each token across the zones. IBC coin packet transactions -must be committed by the sender, hub, and receiver blockchains. - -Since the Cosmos Hub acts as the central ledger for the whole -system, the security of the Hub is of paramount importance. While each -zone may be a Tendermint blockchain that is secured by as few as 4 (or even -less if BFT consensus is not needed), the Hub must be secured by a globally -decentralized set of validators that can withstand the most severe attack -scenarios, such as a continental network partition or a nation-state sponsored -attack. - -### The Zones - -A Cosmos zone is an independent blockchain that exchanges IBC messages with the -Hub. From the Hub's perspective, a zone is a multi-asset dynamic-membership -multi-signature account that can send and receive tokens using IBC packets. Like -a cryptocurrency account, a zone cannot transfer more tokens than it has, but -can receive tokens from others who have them. A zone may be designated as an -"source" of one or more token types, granting it the power to inflate that token -supply. - -Atoms of the Cosmos Hub may be staked by validators of a zone connected to the -Hub. While double-spend attacks on these zones would result in the slashing of -atoms with Tendermint's fork-accountability, a zone where >⅔ of the voting power -are Byzantine can commit invalid state. The Cosmos Hub does not verify or -execute transactions committed on other zones, so it is the responsibility of -users to send tokens to zones that they trust. In the future, the Cosmos Hub's -governance system may pass Hub improvement proposals that account for zone -failures. For example, outbound token transfers from some (or all) zones may be -throttled to allow for the emergency circuit-breaking of zones (a temporary halt -of token transfers) when an attack is detected. - -## Inter-blockchain Communication (IBC) - -Now we look at how the Hub and zones communicate with each other. For example, if -there are three blockchains, "Zone1", "Zone2", and "Hub", and we wish for -"Zone1" to produce a packet destined for "Zone2" going through "Hub". To move a -packet from one blockchain to another, a proof is posted on the -receiving chain. The proof states that the sending chain published a packet for the alleged -destination. For the receiving chain to check this proof, it must be able keep -up with the sender's block headers. This mechanism is similar to that used by -sidechains, which requires two interacting chains to be aware of one another via a -bidirectional stream of proof-of-existence datagrams (transactions). - -The IBC protocol can naturally be defined using two types of transactions: an -`IBCBlockCommitTx` transaction, which allows a blockchain to prove to any -observer of its most recent block-hash, and an `IBCPacketTx` transaction, which -allows a blockchain to prove to any observer that the given packet was indeed -published by the sender's application, via a Merkle-proof to the recent -block-hash. - -By splitting the IBC mechanics into two separate transactions, we allow the -native fee market-mechanism of the receiving chain to determine which packets -get committed (i.e. acknowledged), while allowing for complete freedom on the -sending chain as to how many outbound packets are allowed. - -![Figure of Zone1, Zone2, and Hub IBC without -acknowledgement](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_without_ack.png) - -In the example above, in order to update the block-hash of -"Zone1" on "Hub" (or of "Hub" on "Zone2"), an `IBCBlockCommitTx` -transaction must be posted on "Hub" with the block-hash of "Zone1" (or on -"Zone2" with the block-hash of "Hub"). - -_See [IBCBlockCommitTx](#ibcblockcommittx) and [IBCPacketTx](#ibcpacketcommit) -for for more information on the two IBC transaction types._ - -## Use Cases - -### Distributed Exchange - -In the same way that Bitcoin is more secure by being a distributed, -mass-replicated ledger, we can make exchanges less vulnerable to external and -internal hacks by running it on the blockchain. We call this a distributed -exchange. - -What the cryptocurrency community calls a decentralized exchange today are -based on something called "atomic cross-chain" (AXC) transactions. With an AXC -transaction, two users on two different chains can make two transfer -transactions that are committed together on both ledgers, or none at all (i.e. -atomically). For example, two users can trade bitcoins for ether (or any two -tokens on two different ledgers) using AXC transactions, even though Bitcoin -and Ethereum are not connected to each other. The benefit of running an -exchange on AXC transactions is that neither users need to trust each other or -the trade-matching service. The downside is that both parties need to be -online for the trade to occur. - -Another type of decentralized exchange is a mass-replicated distributed -exchange that runs on its own blockchain. Users on this kind of exchange can -submit a limit order and turn their computer off, and the trade can execute -without the user being online. The blockchain matches and completes the trade -on behalf of the trader. - -A centralized exchange can create a deep orderbook of limit orders and thereby -attract more traders. Liquidity begets more liquidity in the exchange world, -and so there is a strong network effect (or at least a winner-take-most effect) -in the exchange business. The current leader for cryptocurrency exchanges -today is Poloniex with a 24-hour volume of $20M, and in second place is -Bitfinex with a 24-hour volume of $5M. Given such strong network effects, it -is unlikely for AXC-based decentralized exchanges to win volume over the -centralized exchanges. For a decentralized exchange to compete with a -centralized exchange, it would need to support deep orderbooks with limit -orders. Only a distributed exchange on a blockchain can provide that. - -Tendermint provides additional benefits of faster transaction commits. By -prioritizing fast finality without sacrificing consistency, zones in Cosmos can -finalize transactions fast -- for both exchange order transactions as well as -IBC token transfers to and from other zones. - -Given the state of cryptocurrency exchanges today, a great application for -Cosmos is the distributed exchange (aka the Cosmos DEX). The transaction -throughput capacity as well as commit latency can be comparable to those of -centralized exchanges. Traders can submit limit orders that can be executed -without both parties having to be online. And with Tendermint, the Cosmos hub, -and IBC, traders can move funds in and out of the exchange to and from other -zones with speed. - -### Bridging to Other Cryptocurrencies - -A privileged zone can act as the source of a bridged token of another -cryptocurrency. A bridge is similar to the relationship between a -Cosmos hub and zone; both must keep up with the latest blocks of the -other in order to verify proofs that tokens have moved from one to the other. A -"bridge-zone" on the Cosmos network keeps up with the Hub as well as the -other cryptocurrency. The indirection through the bridge-zone allows the logic of -the Hub to remain simple and agnostic to other blockchain consensus strategies -such as Bitcoin's proof-of-work mining. - -#### Sending Tokens to the Cosmos Hub - -Each bridge-zone validator would run a Tendermint-powered blockchain with a special -ABCI bridge-app, but also a full-node of the "origin" blockchain. - -When new blocks are mined on the origin, the bridge-zone validators will come -to agreement on committed blocks by signing and sharing their respective local -view of the origin's blockchain tip. When a bridge-zone receives payment on the -origin (and sufficient confirmations were agreed to have been seen in the case -of a PoW chain such as Ethereum or Bitcoin), a corresponding account is created -on the bridge-zone with that balance. - -In the case of Ethereum, the bridge-zone can share the same validator-set as the -Cosmos Hub. On the Ethereum side (the origin), a bridge-contract would allow -ether holders to send ether to the bridge-zone by sending it to the bridge-contract -on Ethereum. Once ether is received by the bridge-contract, the ether cannot be -withdrawn unless an appropriate IBC packet is received by the bridge-contract from -the bridge-zone. The bridge-contract tracks the validator-set of the bridge-zone, which -may be identical to the Cosmos Hub's validator-set. - -In the case of Bitcoin, the concept is similar except that instead of a single -bridge-contract, each UTXO would be controlled by a threshold multisignature P2SH -pubscript. Due to the limitations of the P2SH system, the signers cannot be -identical to the Cosmos Hub validator-set. - -#### Withdrawing Tokens from Cosmos Hub - -Ether on the bridge-zone ("bridged-ether") can be transferred to and from the -Hub, and later be destroyed with a transaction that sends it to a particular -withdrawal address on Ethereum. An IBC packet proving that the transaction -occurred on the bridge-zone can be posted to the Ethereum bridge-contract to -allow the ether to be withdrawn. - -In the case of Bitcoin, the restricted scripting system makes it difficult to -mirror the IBC coin-transfer mechanism. Each UTXO has its own independent -pubscript, so every UTXO must be migrated to a new UTXO when there is a change -in the set of Bitcoin escrow signers. One solution is to compress and -decompress the UTXO-set as necessary to keep the total number of UTXOs down. - -#### Total Accountability of Bridge Zones - -The risk of such a bridgeging contract is a rogue validator set. ≥⅓ Byzantine -voting power could cause a fork, withdrawing ether from the bridge-contract on -Ethereum while keeping the bridged-ether on the bridge-zone. Worse, >⅔ Byzantine -voting power can steal ether outright from those who sent it to the -bridge-contract by deviating from the original bridgeging logic of the bridge-zone. - -It is possible to address these issues by designing the bridge to be totally -accountable. For example, all IBC packets, from the hub and the origin, might -require acknowledgement by the bridge-zone in such a way that all state -transitions of the bridge-zone can be efficiently challenged and verified by -either the hub or the origin's bridge-contract. The Hub and the origin should -allow the bridge-zone validators to post collateral, and token transfers out of -the bridge-contract should be delayed (and collateral unbonding period -sufficiently long) to allow for any challenges to be made by independent -auditors. We leave the design of the specification and implementation of this -system open as a future Cosmos improvement proposal, to be passed by the Cosmos -Hub's governance system. - -### Ethereum Scaling - -Solving the scaling problem is an open issue for Ethereum. Currently, -Ethereum nodes process every single transaction and also store all the states. -[link](https://docs.google.com/presentation/d/1CjD0W4l4-CwHKUvfF5Vlps76fKLEC6pIwu1a_kC_YRQ/mobilepresent?slide=id.gd284b9333_0_28). - -Since Tendermint can commit blocks much faster than Ethereum's proof-of-work, -EVM zones powered by Tendermint consensus and operating on bridged-ether can -provide higher performance to Ethereum blockchains. Additionally, though the -Cosmos Hub and IBC packet mechanics does not allow for arbitrary contract logic -execution per se, it can be used to coordinate token movements between Ethereum -contracts running on different zones, providing a foundation for token-centric -Ethereum scaling via sharding. - -### Multi-Application Integration - -Cosmos zones run arbitrary application logic, which is defined at the beginning of the -zone's life and can potentially be updated over time by governance. Such flexibility -allows Cosmos zones to act as bridges to other cryptocurrencies such as Ethereum or -Bitcoin, and it also permits derivatives of those blockchains, utilizing the -same codebase but with a different validator set and initial distribution. This -allows many existing cryptocurrency frameworks, such as those of Ethereum, -Zerocash, Bitcoin, CryptoNote and so on, to be used with Tendermint Core, -which is a higher performance consensus engine, on a common network, opening tremendous -opportunity for interoperability across platforms. Furthermore, as a -multi-asset blockchain, a single transaction may contain multiple inputs and -outputs, where each input can be any token type, enabling Cosmos to serve -directly as a platform for decentralized exchange, though orders are assumed to -be matched via other platforms. Alternatively, a zone can serve as a distributed -fault-tolerant exchange (with orderbooks), which can be a strict improvement -over existing centralized cryptocurrency exchanges which tend to get hacked over -time. - -Zones can also serve as blockchain-backed versions of enterprise and government -systems, where pieces of a particular service that are traditionally run by an -organization or group of organizations are instead run as a ABCI application on -a certain zone, which allows it to inherit the security and interoperability of the -public Cosmos network without sacrificing control over the underlying service. -Thus, Cosmos may offer the best of both worlds for organizations looking to -utilize blockchain technology but who are wary of relinquishing control completely -to a distributed third party. - -### Network Partition Mitigation - -Some claim that a major problem with consistency-favouring consensus algorithms -like Tendermint is that any network partition which causes there to be no single -partition with >⅔ voting power (e.g. ≥⅓ going offline) will halt consensus -altogether. The Cosmos architecture can help mitigate this problem by using a global -hub with regional autonomous zones, where voting power for each zone are -distributed based on a common geographic region. For instance, a common -paradigm may be for individual cities, or regions, to operate their own zones -while sharing a common hub (e.g. the Cosmos Hub), enabling municipal activity to -persist in the event that the hub halts due to a temporary network partition. -Note that this allows real geological, political, and network-topological -features to be considered in designing robust federated fault-tolerant systems. - -### Federated Name Resolution System - -NameCoin was one of the first blockchains to attempt to solve the -name-resolution problem by adapting the Bitcoin blockchain. Unfortunately there -have been several issues with this approach. - -With Namecoin, we can verify that, for example, <em>@satoshi</em> was registered with a -particular public key at some point in the past, but we wouldn’t know whether -the public key had since been updated recently unless we download all the blocks -since the last update of that name. This is due to the limitation of Bitcoin's -UTXO transaction Merkle-ization model, where only the transactions (but not -mutable application state) are Merkle-ized into the block-hash. This lets us -prove existence, but not the non-existence of later updates to a name. Thus, we -can't know for certain the most recent value of a name without trusting a full -node, or incurring significant costs in bandwidth by downloading the whole -blockchain. - -Even if a Merkle-ized search tree were implemented in NameCoin, its dependency -on proof-of-work makes light client verification problematic. Light clients must -download a complete copy of the headers for all blocks in the entire blockchain -(or at least all the headers since the last update to a name). This means that -the bandwidth requirements scale linearly with the amount of time [\[21\]][21]. -In addition, name-changes on a proof-of-work blockchain requires waiting for -additional proof-of-work confirmation blocks, which can take up to an hour on -Bitcoin. - -With Tendermint, all we need is the most recent block-hash signed by a quorum of -validators (by voting power), and a Merkle proof to the current value associated -with the name. This makes it possible to have a succinct, quick, and secure -light-client verification of name values. - -In Cosmos, we can take this concept and extend it further. Each -name-registration zone in Cosmos can have an associated top-level-domain -(TLD) name such as ".com" or ".org", and each name-registration zone can have -its own governance and registration rules. - -## Issuance and Incentives - -### The Atom Token - -While the Cosmos Hub is a multi-asset distributed ledger, there is a special -native token called the _atom_. Atoms are the only staking token of the Cosmos -Hub. Atoms are a license for the holder to vote, validate, or delegate to other -validators. Like Ethereum's ether, atoms can also be used to pay for -transaction fees to mitigate spam. Additional inflationary atoms and block -transaction fees are rewarded to validators and delegators who delegate to -validators. - -The `BurnAtomTx` transaction can be used to recover any proportionate amount of -tokens from the reserve pool. - -#### Fundraiser - -The initial distribution of atom tokens and validators on Genesis will go to the -donors of the Cosmos Fundraiser (75%), lead donors (5%), Cosmos Network -Foundation (10%), and ALL IN BITS, Inc (10%). From genesis onward, 1/3 of the -total amount of atoms will be rewarded to bonded validators and delegators -every year. - -See the [Cosmos Plan](https://github.com/cosmos/cosmos/blob/master/PLAN.md) -for additional details. - -### Limitations on the Number of Validators - -Unlike Bitcoin or other proof-of-work blockchains, a Tendermint blockchain gets -slower with more validators due to the increased communication complexity. -Fortunately, we can support enough validators to make for a robust globally -distributed blockchain with very fast transaction confirmation times, and, as -bandwidth, storage, and parallel compute capacity increases, we will be able to -support more validators in the future. - -On genesis day, the maximum number of validators will be set to 100, and this -number will increase at a rate of 13% for 10 years, and settle at 300 -validators. - - Year 0: 100 - Year 1: 113 - Year 2: 127 - Year 3: 144 - Year 4: 163 - Year 5: 184 - Year 6: 208 - Year 7: 235 - Year 8: 265 - Year 9: 300 - Year 10: 300 - ... - -### Becoming a Validator After Genesis Day - -Atom holders who are not already can become validators by signing and -submitting a `BondTx` transaction. The amount of atoms provided as collateral -must be nonzero. Anyone can become a validator at any time, except when the -size of the current validator set is greater than the maximum number of -validators allowed. In that case, the transaction is only valid if the amount -of atoms is greater than the amount of effective atoms held by the smallest -validator, where effective atoms include delegated atoms. When a new validator -replaces an existing validator in such a way, the existing validator becomes -inactive and all the atoms and delegated atoms enter the unbonding state. - -### Penalties for Validators - -There must be some penalty imposed on the validators for any intentional -or unintentional deviation from the sanctioned protocol. Some evidence is -immediately admissible, such as a double-sign at the same height and round, or a -violation of "prevote-the-lock" (a rule of the Tendermint consensus protocol). -Such evidence will result in the validator losing its good standing and its -bonded atoms as well its proportionate share of tokens in the reserve pool -- -collectively called its "stake" -- will get slashed. - -Sometimes, validators will not be available, either due to regional network -disruptions, power failure, or other reasons. If, at any point in the past -`ValidatorTimeoutWindow` blocks, a validator's commit vote is not included in -the blockchain more than `ValidatorTimeoutMaxAbsent` times, that validator will -become inactive, and lose `ValidatorTimeoutPenalty` (DEFAULT 1%) of its stake. - -Some "malicious" behavior does not produce obviously discernable evidence on the -blockchain. In these cases, the validators can coordinate out of band to force -the timeout of these malicious validators, if there is a supermajority -consensus. - -In situations where the Cosmos Hub halts due to a ≥⅓ coalition of voting power -going offline, or in situations where a ≥⅓ coalition of voting power censor -evidence of malicious behavior from entering the blockchain, the hub must -recover with a hard-fork reorg-proposal. (Link to "Forks and Censorship -Attacks"). - -### Transaction Fees - -Cosmos Hub validators can accept any token type or combination of types as fees -for processing a transaction. Each validator can subjectively set whatever -exchange rate it wants, and choose whatever transactions it wants, as long as -the `BlockGasLimit` is not exceeded. The collected fees, minus any taxes -specified below, are redistributed to the bonded stakeholders in proportion to -their bonded atoms, every `ValidatorPayoutPeriod` (DEFAULT 1 hour). - -Of the collected transaction fees, `ReserveTax` (DEFAULT 2%) will go toward the -reserve pool to increase the reserve pool and increase the security and value of -the Cosmos network. These funds can also be distributed in accordance with the -decisions made by the governance system. - -Atom holders who delegate their voting power to other validators pay a -commission to the delegated validator. The commission can be set by each -validator. - -### Incentivizing Hackers - -The security of the Cosmos Hub is a function of the security of the underlying -validators and the choice of delegation by delegators. In order to encourage -the discovery and early reporting of found vulnerabilities, the Cosmos Hub -encourages hackers to publish successful exploits via a `ReportHackTx` -transaction that says, "This validator got hacked. Please send -bounty to this address". Upon such an exploit, the validator and delegators -will become inactive, `HackPunishmentRatio` (default 5%) of everyone's atoms -will get slashed, and `HackRewardRatio` (default 5%) of everyone's atoms will -get rewarded to the hacker's bounty address. The validator must recover the -remaining atoms by using their backup key. - -In order to prevent this feature from being abused to transfer unvested atoms, -the portion of vested vs unvested atoms of validators and delegators before and -after the `ReportHackTx` will remain the same, and the hacker bounty will -include some unvested atoms, if any. - -### Governance Specification - -The Cosmos Hub is operated by a distributed organization that requires a well-defined -governance mechanism in order to coordinate various changes to the blockchain, -such as the variable parameters of the system, as well as software upgrades and -constitutional amendments. - -All validators are responsible for voting on all proposals. Failing to vote on -a proposal in a timely manner will result in the validator being deactivated -automatically for a period of time called the `AbsenteeismPenaltyPeriod` -(DEFAULT 1 week). - -Delegators automatically inherit the vote of the delegated validator. This vote -may be overridden manually. Unbonded atoms get no vote. - -Each proposal requires a deposit of `MinimumProposalDeposit` tokens, which may -be a combination of one or more tokens including atoms. For each proposal, the -voters may vote to take the deposit. If more than half of the voters choose to -take the deposit (e.g. because the proposal was spam), the deposit goes to the -reserve pool, except any atoms which are burned. - -For each proposal, voters may vote with the following options: - -- Yea -- YeaWithForce -- Nay -- NayWithForce -- Abstain - -A strict majority of Yea or YeaWithForce votes (or Nay or NayWithForce votes) is -required for the proposal to be decided as passed (or decided as failed), but -1/3+ can veto the majority decision by voting "with force". When a strict -majority is vetoed, everyone gets punished by losing `VetoPenaltyFeeBlocks` -(DEFAULT 1 day's worth of blocks) worth of fees (except taxes which will not be -affected), and the party that vetoed the majority decision will be additionally -punished by losing `VetoPenaltyAtoms` (DEFAULT 0.1%) of its atoms. - -### Parameter Change Proposal - -Any of the parameters defined here can be changed with the passing of a -`ParameterChangeProposal`. - -### Bounty Proposal - -Atoms can be inflated and reserve pool funds spent with the passing of a `BountyProposal`. - -### Text Proposal - -All other proposals, such as a proposal to upgrade the protocol, will be -coordinated via the generic `TextProposal`. - -## Roadmap - -See [the Plan](https://github.com/cosmos/cosmos/blob/master/PLAN.md). - -## Related Work - -There have been many innovations in blockchain consensus and scalability in the -past couple of years. This section provides a brief survey of a select number -of important ones. - -### Consensus Systems - -#### Classic Byzantine Fault Tolerance - -Consensus in the presence of malicious participants is a problem dating back to -the early 1980s, when Leslie Lamport coined the phrase "Byzantine fault" to -refer to arbitrary process behavior that deviates from the intended behavior, -in contrast to a "crash fault", wherein a process simply crashes. Early -solutions were discovered for synchronous networks where there is an upper -bound on message latency, though practical use was limited to highly controlled -environments such as airplane controllers and datacenters synchronized via -atomic clocks. It was not until the late 90s that Practical Byzantine Fault -Tolerance (PBFT) [\[11\]][11] was introduced as an efficient partially -synchronous consensus algorithm able to tolerate up to ⅓ of processes behaving -arbitrarily. PBFT became the standard algorithm, spawning many variations, -including most recently one created by IBM as part of their contribution to -Hyperledger. - -The main benefit of Tendermint consensus over PBFT is that Tendermint has an -improved and simplified underlying structure, some of which is a result of -embracing the blockchain paradigm. Tendermint blocks must commit in order, -which obviates the complexity and communication overhead associated with PBFT's -view-changes. In Cosmos and many cryptocurrencies, there is no need to allow -for block <em>N+i</em> where <em>i >= 1</em> to commit, when block <em>N</em> -itself hasn't yet committed. If bandwidth is the reason why block <em>N</em> -hasn't committed in a Cosmos zone, then it doesn't help to use bandwidth sharing -votes for blocks <em>N+i</em>. If a network partition or offline nodes is the -reason why block <em>N</em> hasn't committed, then <em>N+i</em> won't commit -anyway. - -In addition, the batching of transactions into blocks allows for regular -Merkle-hashing of the application state, rather than periodic digests as with -PBFT's checkpointing scheme. This allows for faster provable transaction -commits for light-clients and faster inter-blockchain communication. - -Tendermint Core also includes many optimizations and features that go above and -beyond what is specified in PBFT. For example, the blocks proposed by -validators are split into parts, Merkle-ized, and gossipped in such a way that -improves broadcasting performance (see LibSwift [\[19\]][19] for inspiration). -Also, Tendermint Core doesn't make any assumption about point-to-point -connectivity, and functions for as long as the P2P network is weakly connected. - -#### BitShares delegated stake - -While not the first to deploy proof-of-stake (PoS), BitShares1.0 [\[12\]][12] -contributed considerably to research and adoption of PoS blockchains, -particularly those known as "delegated" PoS. In BitShares, stake holders elect -"witnesses", responsible for ordering and committing transactions, and -"delegates", responsible for coordinating software updates and parameter -changes. BitShares2.0 aims to achieve high performance (100k tx/s, 1s latency) -in ideal conditions, with each block signed by a single signer, and transaction -finality taking quite a bit longer than the block interval. A canonical -specification is still in development. Stakeholders can remove or replace -misbehaving witnesses on a daily basis, but there is no significant collateral -of witnesses or delegators in the likeness of Tendermint PoS that get slashed -in the case of a successful double-spend attack. - -#### Stellar - -Building on an approach pioneered by Ripple, Stellar [\[13\]][13] refined a -model of Federated Byzantine Agreement wherein the processes participating in -consensus do not constitute a fixed and globally known set. Rather, each -process node curates one or more "quorum slices", each constituting a set of -trusted processes. A "quorum" in Stellar is defined to be a set of nodes that -contain at least one quorum slice for each node in the set, such that agreement -can be reached. - -The security of the Stellar mechanism relies on the assumption that the -intersection of _any_ two quorums is non-empty, while the availability of a node -requires at least one of its quorum slices to consist entirely of correct nodes, -creating a trade-off between using large or small quorum-slices that may be -difficult to balance without imposing significant assumptions about trust. -Ultimately, nodes must somehow choose adequate quorum slices for there to be -sufficient fault-tolerance (or any "intact nodes" at all, of which much of the -results of the paper depend on), and the only provided strategy for ensuring -such a configuration is hierarchical and similar to the Border Gateway Protocol -(BGP), used by top-tier ISPs on the internet to establish global routing tables, -and by that used by browsers to manage TLS certificates; both notorious for -their insecurity. - -The criticism in the Stellar paper of the Tendermint-based proof-of-stake -systems is mitigated by the token strategy described here, wherein a new type of -token called the _atom_ is issued that represent claims to future portions of -fees and rewards. The advantage of Tendermint-based proof-of-stake, then, is its -relative simplicity, while still providing sufficient and provable security -guarantees. - -#### BitcoinNG - -BitcoinNG is a proposed improvement to Bitcoin that would allow for forms of -vertical scalability, such as increasing the block size, without the negative -economic consequences typically associated with such a change, such as the -disproportionately large impact on small miners. This improvement is achieved -by separating leader election from transaction broadcast: leaders are first -elected by proof-of-work in "micro-blocks", and then able to broadcast -transactions to be committed until a new micro-block is found. This reduces the -bandwidth requirements necessary to win the PoW race, allowing small miners to -more fairly compete, and allowing transactions to be committed more regularly by -the last miner to find a micro-block. - -#### Casper - -Casper [\[16\]][16] is a proposed proof-of-stake consensus algorithm for -Ethereum. Its prime mode of operation is "consensus-by-bet". By letting -validators iteratively bet on which block they believe will become committed -into the blockchain based on the other bets that they have seen so far, -finality can be achieved eventually. -[link](https://blog.ethereum.org/2015/12/28/understanding-serenity-part-2-casper/). -This is an active area of research by the Casper team. The challenge is in -constructing a betting mechanism that can be proven to be an evolutionarily -stable strategy. The main benefit of Casper as compared to Tendermint may be in -offering "availability over consistency" -- consensus does not require a >⅔ -quorum of voting power -- perhaps at the cost of commit speed or -implementation complexity. - -### Horizontal Scaling - -#### Interledger Protocol - -The Interledger Protocol [\[14\]][14] is not strictly a scalability solution. -It provides an ad hoc interoperation between different ledger systems through a -loosely coupled bilateral relationship network. Like the Lightning Network, -the purpose of ILP is to facilitate payments, but it specifically focuses on -payments across disparate ledger types, and extends the atomic transaction -mechanism to include not only hash-locks, but also a quorum of notaries (called -the Atomic Transport Protocol). The latter mechanism for enforcing atomicity -in inter-ledger transactions is similar to Tendermint's light-client SPV -mechanism, so an illustration of the distinction between ILP and Cosmos/IBC is -warranted, and provided below. - -1. The notaries of a connector in ILP do not support membership changes, and - do not allow for flexible weighting between notaries. On the other hand, - IBC is designed specifically for blockchains, where validators can have - different weights, and where membership can change over the course of the - blockchain. - -2. As in the Lightning Network, the receiver of payment in ILP must be online to - send a confirmation back to the sender. In a token transfer over IBC, the - validator-set of the receiver's blockchain is responsible for providing - confirmation, not the receiving user. - -3. The most striking difference is that ILP's connectors are not responsible or - keeping authoritative state about payments, whereas in Cosmos, the validators - of a hub are the authority of the state of IBC token transfers as well as the - authority of the amount of tokens held by each zone (but not the amount of - tokens held by each account within a zone). This is the fundamental innovation - that allows for secure asymmetric transfer of tokens from zone to zone; the - analog to ILP's connector in Cosmos is a persistent and maximally secure - blockchain ledger, the Cosmos Hub. - -4. The inter-ledger payments in ILP need to be backed by an exchange orderbook, - as there is no asymmetric transfer of coins from one ledger to another, only - the transfer of value or market equivalents. - -#### Sidechains - -Sidechains [\[15\]][15] are a proposed mechanism for scaling the Bitcoin -network via alternative blockchains that are "two-way pegged" to the Bitcoin -blockchain. (Two-way pegging is equivalent to bridging. In Cosmos we say -"bridging" to distinguish from market-pegging). Sidechains allow bitcoins to -effectively move from the Bitcoin blockchain to the sidechain and back, and -allow for experimentation in new features on the sidechain. As in the Cosmos -Hub, the sidechain and Bitcoin serve as light-clients of each other, using SPV -proofs to determine when coins should be transferred to the sidechain and back. -Of course, since Bitcoin uses proof-of-work, sidechains centered around Bitcoin -suffer from the many problems and risks of proof-of-work as a consensus -mechanism. Furthermore, this is a Bitcoin-maximalist solution that doesn't -natively support a variety of tokens and inter-zone network topology as Cosmos -does. That said, the core mechanism of the two-way peg is in principle the same -as that employed by the Cosmos network. - -#### Ethereum Scalability Efforts - -Ethereum is currently researching a number of different strategies to shard the -state of the Ethereum blockchain to address scalability needs. These efforts -have the goal of maintaining the abstraction layer offered by the current -Ethereum Virtual Machine across the shared state space. Multiple research -efforts are underway at this time. [\[18\]][18][\[22\]][22] - -##### Cosmos vs Ethereum 2.0 Mauve - -Cosmos and Ethereum 2.0 Mauve [\[22\]][22] have different design goals. - -- Cosmos is specifically about tokens. Mauve is about scaling general computation. -- Cosmos is not bound to the EVM, so even different VMs can interoperate. -- Cosmos lets the zone creator determine who validates the zone. -- Anyone can start a new zone in Cosmos (unless governance decides otherwise). -- The hub isolates zone failures so global token invariants are preserved. - -### General Scaling - -#### Lightning Network - -The Lightning Network is a proposed token transfer network operating at a layer -above the Bitcoin blockchain (and other public blockchains), enabling improvement of many -orders of magnitude in transaction throughput by moving the majority -of transactions outside of the consensus ledger into so-called "payment -channels". This is made possible by on-chain cryptocurrency scripts, which -enable parties to enter into bilateral stateful contracts where the state can -be updated by sharing digital signatures, and contracts can be closed by finally -publishing evidence onto the blockchain, a mechanism first popularized by -cross-chain atomic swaps. By opening payment channels with many parties, -participants in the Lightning Network can become focal points for routing the -payments of others, leading to a fully connected payment channel network, at the -cost of capital being tied up on payment channels. - -While the Lightning Network can also easily extend across multiple independent -blockchains to allow for the transfer of _value_ via an exchange market, it -cannot be used to asymmetrically transfer _tokens_ from one blockchain to -another. The main benefit of the Cosmos network described here is to enable -such direct token transfers. That said, we expect payment channels and the -Lightning Network to become widely adopted along with our token transfer -mechanism, for cost-saving and privacy reasons. - -#### Segregated Witness - -Segregated Witness is a Bitcoin improvement proposal -[link](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki) that aims -to increase the per-block transaction throughput 2X or 3X, while simultaneously -making block syncing faster for new nodes. The brilliance of this solution is -in how it works within the limitations of Bitcoin's current protocol and allows -for a soft-fork upgrade (i.e. clients with older versions of the software will -continue to function after the upgrade). Tendermint, being a new protocol, has no -design restrictions, so it has a different scaling priorities. Primarily, -Tendermint uses a BFT round-robin algorithm based on cryptographic signatures -instead of mining, which trivially allows horizontal scaling through multiple -parallel blockchains, while regular, more frequent block commits allow for -vertical scaling as well. - -<hr/> - -## Appendix - -### Fork Accountability - -A well designed consensus protocol should provide some guarantees in the event that the tolerance -capacity is exceeded and the consensus fails. This is especially necessary in -economic systems, where Byzantine behaviour can have substantial financial -reward. The most important such guarantee is a form of _fork-accountability_, -where the processes that caused the consensus to fail (ie. caused clients of -the protocol to accept different values - a fork) can be identified and punished -according to the rules of the protocol, or, possibly, the legal system. When -the legal system is unreliable or excessively expensive to invoke, validators can be forced to make security -deposits in order to participate, and those deposits can be jailed, or slashed, -when malicious behaviour is detected [\[10\]][10]. - -Note this is unlike Bitcoin, where forking is a regular occurence due to -network asynchrony and the probabilistic nature of finding partial hash -collisions. Since in many cases a malicious fork is indistinguishable from a -fork due to asynchrony, Bitcoin cannot reliably implement fork-accountability, -other than the implicit opportunity cost paid by miners for mining an orphaned -block. - -### Tendermint Consensus - -We call the voting stages _PreVote_ and _PreCommit_. A vote can be for a -particular block or for _Nil_. We call a collection of >⅔ PreVotes for a single -block in the same round a _Polka_, and a collection of >⅔ PreCommits for a -single block in the same round a _Commit_. If >⅔ PreCommit for Nil in the same -round, they move to the next round. - -Note that strict determinism in the protocol incurs a weak synchrony assumption -as faulty leaders must be detected and skipped. Thus, validators wait some -amount of time, _TimeoutPropose_, before they Prevote Nil, and the value of -TimeoutPropose increases with each round. Progression through the rest of a -round is fully asynchronous, in that progress is only made once a validator hears -from >⅔ of the network. In practice, it would take an extremely strong -adversary to indefinitely thwart the weak synchrony assumption (causing the -consensus to fail to ever commit a block), and doing so can be made even more -difficult by using randomized values of TimeoutPropose on each validator. - -An additional set of constraints, or Locking Rules, ensure that the network will -eventually commit just one block at each height. Any malicious attempt to cause -more than one block to be committed at a given height can be identified. First, -a PreCommit for a block must come with justification, in the form of a Polka for -that block. If the validator has already PreCommit a block at round -<em>R*1</em>, we say they are \_locked* on that block, and the Polka used to -justify the new PreCommit at round <em>R_2</em> must come in a round -<em>R_polka</em> where <em>R_1 < R_polka <= R_2</em>. Second, validators -must Propose and/or PreVote the block they are locked on. Together, these -conditions ensure that a validator does not PreCommit without sufficient -evidence as justification, and that validators which have already PreCommit -cannot contribute to evidence to PreCommit something else. This ensures both -safety and liveness of the consensus algorithm. - -The full details of the protocol are described -[here](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm). - -### Tendermint Light Clients - -The need to sync all block headers is eliminated in Tendermint-PoS as the -existence of an alternative chain (a fork) means ≥⅓ of bonded stake can be -slashed. Of course, since slashing requires that _someone_ share evidence of a -fork, light clients should store any block-hash commits that it sees. -Additionally, light clients could periodically stay synced with changes to the -validator set, in order to avoid [long range -attacks](#preventing-long-range-attacks) (but other solutions are possible). - -In spirit similar to Ethereum, Tendermint enables applications to embed a -global Merkle root hash in each block, allowing easily verifiable state queries -for things like account balances, the value stored in a contract, or the -existence of an unspent transaction output, depending on the nature of the -application. - -### Preventing Long Range Attacks - -Assuming a sufficiently resilient collection of broadcast networks and a static -validator set, any fork in the blockchain can be detected and the deposits of -the offending validators slashed. This innovation, first suggested by Vitalik -Buterin in early 2014, solves the nothing-at-stake problem of other -proof-of-stake cryptocurrencies (see [Related Work](#related-work)). However, -since validator sets must be able to change, over a long range of time the -original validators may all become unbonded, and hence would be free to create a -new chain from the genesis block, incurring no cost as they no longer have -deposits locked up. This attack came to be known as the Long Range Attack (LRA), -in contrast to a Short Range Attack, where validators who are currently bonded -cause a fork and are hence punishable (assuming a fork-accountable BFT algorithm -like Tendermint consensus). Long Range Attacks are often thought to be a -critical blow to proof-of-stake. - -Fortunately, the LRA can be mitigated as follows. First, for a validator to -unbond (thereby recovering their collateral deposit and no longer earning fees -to participate in the consensus), the deposit must be made untransferable for an -amount of time known as the "unbonding period", which may be on the order of -weeks or months. Second, for a light client to be secure, the first time it -connects to the network it must verify a recent block-hash against a trusted -source, or preferably multiple sources. This condition is sometimes referred to -as "weak subjectivity". Finally, to remain secure, it must sync up with the -latest validator set at least as frequently as the length of the unbonding -period. This ensures that the light client knows about changes to the validator -set before a validator has its capital unbonded and thus no longer at stake, -which would allow it to deceive the client by carrying out a long range attack -by creating new blocks beginning back at a height where it was bonded (assuming -it has control of sufficiently many of the early private keys). - -Note that overcoming the LRA in this way requires an overhaul of the original -security model of proof-of-work. In PoW, it is assumed that a light client can -sync to the current height from the trusted genesis block at any time simply by -processing the proof-of-work in every block header. To overcome the LRA, -however, we require that a light client come online with some regularity to -track changes in the validator set, and that the first time they come online -they must be particularly careful to authenticate what they hear from the -network against trusted sources. Of course, this latter requirement is similar -to that of Bitcoin, where the protocol and software must also be obtained from a -trusted source. - -The above method for preventing LRA is well suited for validators and full nodes -of a Tendermint-powered blockchain because these nodes are meant to remain -connected to the network. The method is also suitable for light clients that -can be expected to sync with the network frequently. However, for light clients -that are not expected to have frequent access to the internet or the blockchain -network, yet another solution can be used to overcome the LRA. Non-validator -token holders can post their tokens as collateral with a very long unbonding -period (e.g. much longer than the unbonding period for validators) and serve -light clients with a secondary method of attesting to the validity of current -and past block-hashes. While these tokens do not count toward the security of -the blockchain's consensus, they nevertheless can provide strong guarantees for -light clients. If historical block-hash querying were supported in Ethereum, -anyone could bond their tokens in a specially designed smart contract and -provide attestation services for pay, effectively creating a market for -light-client LRA security. - -### Overcoming Forks and Censorship Attacks - -Due to the definition of a block commit, any ≥⅓ coalition of voting power can -halt the blockchain by going offline or not broadcasting their votes. Such a -coalition can also censor particular transactions by rejecting blocks that -include these transactions, though this would result in a significant proportion -of block proposals to be rejected, which would slow down the rate of block -commits of the blockchain, reducing its utility and value. The malicious -coalition might also broadcast votes in a trickle so as to grind blockchain -block commits to a near halt, or engage in any combination of these attacks. -Finally, it can cause the blockchain to fork, by double-signing or violating the -locking rules. - -If a globally active adversary were also involved, it could partition the network in -such a way that it may appear that the wrong subset of validators were -responsible for the slowdown. This is not just a limitation of Tendermint, but -rather a limitation of all consensus protocols whose network is potentially -controlled by an active adversary. - -For these types of attacks, a subset of the validators should coordinate through -external means to sign a reorg-proposal that chooses a fork (and any evidence -thereof) and the initial subset of validators with their signatures. Validators -who sign such a reorg-proposal forego their collateral on all other forks. -Clients should verify the signatures on the reorg-proposal, verify any evidence, -and make a judgement or prompt the end-user for a decision. For example, a -phone wallet app may prompt the user with a security warning, while a -refrigerator may accept any reorg-proposal signed by +½ of the original -validators by voting power. - -No non-synchronous Byzantine fault-tolerant algorithm can come to consensus when -≥⅓ of voting power are dishonest, yet a fork assumes that ≥⅓ of voting power -have already been dishonest by double-signing or lock-changing without -justification. So, signing the reorg-proposal is a coordination problem that -cannot be solved by any non-synchronous protocol (i.e. automatically, and -without making assumptions about the reliability of the underlying network). -For now, we leave the problem of reorg-proposal coordination to human -coordination via social consensus on internet media. Validators must take care -to ensure that there are no remaining network partitions prior to signing a -reorg-proposal, to avoid situations where two conflicting reorg-proposals are -signed. - -Assuming that the external coordination medium and protocol is robust, it -follows that forks are less of a concern than censorship attacks. - -In addition to forks and censorship, which require ≥⅓ Byzantine voting power, a -coalition of >⅔ voting power may commit arbitrary, invalid state. This is -characteristic of any (BFT) consensus system. Unlike double-signing, which -creates forks with easily verifiable evidence, detecting committment of an -invalid state requires non-validating peers to verify whole blocks, which -implies that they keep a local copy of the state and execute each transaction, -computing the state root independently for themselves. Once detected, the only -way to handle such a failure is via social consensus. For instance, in -situations where Bitcoin has failed, whether forking due to software bugs (as in -March 2013), or committing invalid state due to Byzantine behavior of miners (as -in July 2015), the well connected community of businesses, developers, miners, -and other organizations established a social consensus as to what manual actions -were required by participants to heal the network. Furthermore, since -validators of a Tendermint blockchain may be expected to be identifiable, -commitment of an invalid state may even be punishable by law or some external -jurisprudence, if desired. - -### ABCI Specification - -ABCI consists of 3 primary message types that get delivered from the core to the -application. The application replies with corresponding response messages. - -The `AppendTx` message is the work horse of the application. Each transaction in -the blockchain is delivered with this message. The application needs to validate -each transactions received with the AppendTx message against the current state, -application protocol, and the cryptographic credentials of the transaction. A -validated transaction then needs to update the application state — by binding a -value into a key values store, or by updating the UTXO database. - -The `CheckTx` message is similar to AppendTx, but it’s only for validating -transactions. Tendermint Core’s mempool first checks the validity of a -transaction with CheckTx, and only relays valid transactions to its peers. -Applications may check an incrementing nonce in the transaction and return an -error upon CheckTx if the nonce is old. - -The `Commit` message is used to compute a cryptographic commitment to the -current application state, to be placed into the next block header. This has -some handy properties. Inconsistencies in updating that state will now appear as -blockchain forks which catches a whole class of programming errors. This also -simplifies the development of secure lightweight clients, as Merkle-hash proofs -can be verified by checking against the block-hash, and the block-hash is signed -by a quorum of validators (by voting power). - -Additional ABCI messages allow the application to keep track of and change the -validator set, and for the application to receive the block information, such as -the height and the commit votes. - -ABCI requests/responses are simple Protobuf messages. Check out the [schema -file](https://github.com/tendermint/abci/blob/master/types/types.proto). - -##### AppendTx - -- **Arguments**: - - `Data ([]byte)`: The request transaction bytes -- **Returns**: - - `Code (uint32)`: Response code - - `Data ([]byte)`: Result bytes, if any - - `Log (string)`: Debug or error message -- **Usage**:<br/> - Append and run a transaction. If the transaction is valid, returns - CodeType.OK - -##### CheckTx - -- **Arguments**: - - `Data ([]byte)`: The request transaction bytes -- **Returns**: - - `Code (uint32)`: Response code - - `Data ([]byte)`: Result bytes, if any - - `Log (string)`: Debug or error message -- **Usage**:<br/> - Validate a transaction. This message should not mutate the state. - Transactions are first run through CheckTx before broadcast to peers in the - mempool layer. - You can make CheckTx semi-stateful and clear the state upon `Commit` or - `BeginBlock`, - to allow for dependent sequences of transactions in the same block. - -##### Commit - -- **Returns**: - - `Data ([]byte)`: The Merkle root hash - - `Log (string)`: Debug or error message -- **Usage**:<br/> - Return a Merkle root hash of the application state. - -##### Query - -- **Arguments**: - - `Data ([]byte)`: The query request bytes -- **Returns**: - - `Code (uint32)`: Response code - - `Data ([]byte)`: The query response bytes - - `Log (string)`: Debug or error message - -##### Flush - -- **Usage**:<br/> - Flush the response queue. Applications that implement `types.Application` - need not implement this message -- it's handled by the project. - -##### Info - -- **Returns**: - - `Data ([]byte)`: The info bytes -- **Usage**:<br/> - Return information about the application state. Application specific. - -##### SetOption - -- **Arguments**: - - `Key (string)`: Key to set - - `Value (string)`: Value to set for key -- **Returns**: - - `Log (string)`: Debug or error message -- **Usage**:<br/> - Set application options. E.g. Key="mode", Value="mempool" for a mempool - connection, or Key="mode", Value="consensus" for a consensus connection. - Other options are application specific. - -##### InitChain - -- **Arguments**: - - `Validators ([]Validator)`: Initial genesis-validators -- **Usage**:<br/> - Called once upon genesis - -##### BeginBlock - -- **Arguments**: - - `Height (uint64)`: The block height that is starting -- **Usage**:<br/> - Signals the beginning of a new block. Called prior to any AppendTxs. - -##### EndBlock - -- **Arguments**: - - `Height (uint64)`: The block height that ended -- **Returns**: - - `Validators ([]Validator)`: Changed validators with new voting powers (0 - to remove) -- **Usage**:<br/> - Signals the end of a block. Called prior to each Commit after all - transactions - -See [the ABCI repository](https://github.com/tendermint/abci#message-types) for more details. - -### IBC Packet Delivery Acknowledgement - -There are several reasons why a sender may want the acknowledgement of delivery -of a packet by the receiving chain. For example, the sender may not know the -status of the destination chain, if it is expected to be faulty. Or, the sender -may want to impose a timeout on the packet (with the `MaxHeight` packet field), -while any destination chain may suffer from a denial-of-service attack with a -sudden spike in the number of incoming packets. - -In these cases, the sender can require delivery acknowledgement by setting the -initial packet status to `AckPending`. Then, it is the receiving chain's -responsibility to confirm delivery by including an abbreviated `IBCPacket` in the -app Merkle hash. - -![Figure of Zone1, Zone2, and Hub IBC with -acknowledgement](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_with_ack.png) - -First, an `IBCBlockCommit` and `IBCPacketTx` are posted on "Hub" that proves -the existence of an `IBCPacket` on "Zone1". Say that `IBCPacketTx` has the -following value: - -- `FromChainID`: "Zone1" -- `FromBlockHeight`: 100 (say) -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 (say) - - `Status`: `AckPending` - - `Type`: "coin" - - `MaxHeight`: 350 (say "Hub" is currently at height 300) - - `Payload`: <The bytes of a "coin" payload> - -Next, an `IBCBlockCommit` and `IBCPacketTx` are posted on "Zone2" that proves -the existence of an `IBCPacket` on "Hub". Say that `IBCPacketTx` has the -following value: - -- `FromChainID`: "Hub" -- `FromBlockHeight`: 300 -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 - - `Status`: `AckPending` - - `Type`: "coin" - - `MaxHeight`: 350 - - `Payload`: <The same bytes of a "coin" payload> - -Next, "Zone2" must include in its app-hash an abbreviated packet that shows the -new status of `AckSent`. An `IBCBlockCommit` and `IBCPacketTx` are posted back -on "Hub" that proves the existence of an abbreviated `IBCPacket` on -"Zone2". Say that `IBCPacketTx` has the following value: - -- `FromChainID`: "Zone2" -- `FromBlockHeight`: 400 (say) -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 - - `Status`: `AckSent` - - `Type`: "coin" - - `MaxHeight`: 350 - - `PayloadHash`: <The hash bytes of the same "coin" payload> - -Finally, "Hub" must update the status of the packet from `AckPending` to -`AckReceived`. Evidence of this new finalized status should go back to -"Zone2". Say that `IBCPacketTx` has the following value: - -- `FromChainID`: "Hub" -- `FromBlockHeight`: 301 -- `Packet`: an `IBCPacket`: - - `Header`: an `IBCPacketHeader`: - - `SrcChainID`: "Zone1" - - `DstChainID`: "Zone2" - - `Number`: 200 - - `Status`: `AckReceived` - - `Type`: "coin" - - `MaxHeight`: 350 - - `PayloadHash`: <The hash bytes of the same "coin" payload> - -Meanwhile, "Zone1" may optimistically assume successful delivery of a "coin" -packet unless evidence to the contrary is proven on "Hub". In the example -above, if "Hub" had not received an `AckSent` status from "Zone2" by block -350, it would have set the status automatically to `Timeout`. This evidence of -a timeout can get posted back on "Zone1", and any tokens can be returned. - -![Figure of Zone1, Zone2, and Hub IBC with acknowledgement and -timeout](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_with_ack_timeout.png) - -### Merkle Tree & Proof Specification - -There are two types of Merkle trees supported in the Tendermint/Cosmos -ecosystem: The Simple Tree, and the IAVL+ Tree. - -#### Simple Tree - -The Simple Tree is a Merkle tree for a static list of elements. If the number -of items is not a power of two, some leaves will be at different levels. Simple -Tree tries to keep both sides of the tree the same height, but the left may be -one greater. This Merkle tree is used to Merkle-ize the transactions of a -block, and the top level elements of the application state root. - - * - / \ - / \ - / \ - / \ - * * - / \ / \ - / \ / \ - / \ / \ - * * * h6 - / \ / \ / \ - h0 h1 h2 h3 h4 h5 - - A SimpleTree with 7 elements - -#### IAVL+ Tree - -The purpose of the IAVL+ data structure is to provide persistent storage for -key-value pairs in the application state such that a deterministic Merkle root -hash can be computed efficiently. The tree is balanced using a variant of the -[AVL algorithm](https://en.wikipedia.org/wiki/AVL_tree), and all operations are -O(log(n)). - -In an AVL tree, the heights of the two child subtrees of any node differ by at -most one. Whenever this condition is violated upon an update, the tree is -rebalanced by creating O(log(n)) new nodes that point to unmodified nodes of the -old tree. In the original AVL algorithm, inner nodes can also hold key-value -pairs. The AVL+ algorithm (note the plus) modifies the AVL algorithm to keep -all values on leaf nodes, while only using branch-nodes to store keys. This -simplifies the algorithm while keeping the merkle hash trail short. - -The AVL+ Tree is analogous to Ethereum's [Patricia -tries](https://en.wikipedia.org/wiki/Radix_tree). There are tradeoffs. Keys do -not need to be hashed prior to insertion in IAVL+ trees, so this provides faster -ordered iteration in the key space which may benefit some applications. The -logic is simpler to implement, requiring only two types of nodes -- inner nodes -and leaf nodes. The Merkle proof is on average shorter, being a balanced binary -tree. On the other hand, the Merkle root of an IAVL+ tree depends on the order -of updates. - -We will support additional efficient Merkle trees, such as Ethereum's Patricia -Trie when the binary variant becomes available. - -### Transaction Types - -In the canonical implementation, transactions are streamed to the Cosmos hub -application via the ABCI interface. - -The Cosmos Hub will accept a number of primary transaction types, including -`SendTx`, `BondTx`, `UnbondTx`, `ReportHackTx`, `SlashTx`, `BurnAtomTx`, -`ProposalCreateTx`, and `ProposalVoteTx`, which are fairly self-explanatory and -will be documented in a future revision of this paper. Here we document the two -primary transaction types for IBC: `IBCBlockCommitTx` and `IBCPacketTx`. - -#### IBCBlockCommitTx - -An `IBCBlockCommitTx` transaction is composed of: - -- `ChainID (string)`: The ID of the blockchain -- `BlockHash ([]byte)`: The block-hash bytes, the Merkle root which includes the - app-hash -- `BlockPartsHeader (PartSetHeader)`: The block part-set header bytes, only - needed to verify vote signatures -- `BlockHeight (int)`: The height of the commit -- `BlockRound (int)`: The round of the commit -- `Commit ([]Vote)`: The >⅔ Tendermint `Precommit` votes that comprise a block - commit -- `ValidatorsHash ([]byte)`: A Merkle-tree root hash of the new validator set -- `ValidatorsHashProof (SimpleProof)`: A SimpleTree Merkle-proof for proving the - `ValidatorsHash` against the `BlockHash` -- `AppHash ([]byte)`: A IAVLTree Merkle-tree root hash of the application state -- `AppHashProof (SimpleProof)`: A SimpleTree Merkle-proof for proving the - `AppHash` against the `BlockHash` - -#### IBCPacketTx - -An `IBCPacket` is composed of: - -- `Header (IBCPacketHeader)`: The packet header -- `Payload ([]byte)`: The bytes of the packet payload. _Optional_ -- `PayloadHash ([]byte)`: The hash for the bytes of the packet. _Optional_ - -Either one of `Payload` or `PayloadHash` must be present. The hash of an -`IBCPacket` is a simple Merkle root of the two items, `Header` and `Payload`. -An `IBCPacket` without the full payload is called an _abbreviated packet_. - -An `IBCPacketHeader` is composed of: - -- `SrcChainID (string)`: The source blockchain ID -- `DstChainID (string)`: The destination blockchain ID -- `Number (int)`: A unique number for all packets -- `Status (enum)`: Can be one of `AckPending`, `AckSent`, `AckReceived`, - `NoAck`, or `Timeout` -- `Type (string)`: The types are application-dependent. Cosmos reserves the - "coin" packet type -- `MaxHeight (int)`: If status is not `NoAckWanted` or `AckReceived` by this - height, status becomes `Timeout`. _Optional_ - -An `IBCPacketTx` transaction is composed of: - -- `FromChainID (string)`: The ID of the blockchain which is providing this - packet; not necessarily the source -- `FromBlockHeight (int)`: The blockchain height in which the following packet - is included (Merkle-ized) in the block-hash of the source chain -- `Packet (IBCPacket)`: A packet of data, whose status may be one of - `AckPending`, `AckSent`, `AckReceived`, `NoAck`, or `Timeout` -- `PacketProof (IAVLProof)`: A IAVLTree Merkle-proof for proving the packet's - hash against the `AppHash` of the source chain at given height - -The sequence for sending a packet from "Zone1" to "Zone2" through the -"Hub" is depicted in {Figure X}. First, an `IBCPacketTx` proves to -"Hub" that the packet is included in the app-state of "Zone1". Then, -another `IBCPacketTx` proves to "Zone2" that the packet is included in the -app-state of "Hub". During this procedure, the `IBCPacket` fields are -identical: the `SrcChainID` is always "Zone1", and the `DstChainID` is always -"Zone2". - -The `PacketProof` must have the correct Merkle-proof path, as follows: - - IBC/<SrcChainID>/<DstChainID>/<Number> - -When "Zone1" wants to send a packet to "Zone2" through "Hub", the -`IBCPacket` data are identical whether the packet is Merkle-ized on "Zone1", -the "Hub", or "Zone2". The only mutable field is `Status` for tracking -delivery. - -## Acknowledgements - -We thank our friends and peers for assistance in conceptualizing, reviewing, and -providing support for our work with Tendermint and Cosmos. - -- [Zaki Manian](https://github.com/zmanian) of - [SkuChain](http://www.skuchain.com/) provided much help in formatting and - wording, especially under the ABCI section -- [Jehan Tremback](https://github.com/jtremback) of Althea and Dustin Byington - for helping with initial iterations -- [Andrew Miller](https://soc1024.com/) of [Honey - Badger](https://eprint.iacr.org/2016/199) for feedback on consensus -- [Greg Slepak](https://fixingtao.com/) for feedback on consensus and wording -- Also thanks to [Bill Gleim](https://github.com/gleim) and [Seunghwan - Han](http://www.seunghwanhan.com) for various contributions. -- **Your name and organization here for your contribution** - -## Citations - -- [1] Bitcoin: <https://bitcoin.org/bitcoin.pdf> -- [2] ZeroCash: <http://zerocash-project.org/paper> -- [3] Ethereum: <https://github.com/ethereum/wiki/wiki/White-Paper> -- [4] TheDAO: <https://download.slock.it/public/DAO/WhitePaper.pdf> -- [5] Segregated Witness: <https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki> -- [6] BitcoinNG: <https://arxiv.org/pdf/1510.02037v2.pdf> -- [7] Lightning Network: <https://lightning.network/lightning-network-paper-DRAFT-0.5.pdf> -- [8] Tendermint: <https://github.com/tendermint/tendermint/wiki> -- [9] FLP Impossibility: <https://groups.csail.mit.edu/tds/papers/Lynch/jacm85.pdf> -- [10] Slasher: <https://blog.ethereum.org/2014/01/15/slasher-a-punitive-proof-of-stake-algorithm/> -- [11] PBFT: <http://pmg.csail.mit.edu/papers/osdi99.pdf> -- [12] BitShares: <https://bitshares.org/technology/delegated-proof-of-stake-consensus/> -- [13] Stellar: <https://www.stellar.org/papers/stellar-consensus-protocol.pdf> -- [14] Interledger: <https://interledger.org/rfcs/0001-interledger-architecture/> -- [15] Sidechains: <https://blockstream.com/sidechains.pdf> -- [16] Casper: <https://blog.ethereum.org/2015/08/01/introducing-casper-friendly-ghost/> -- [17] ABCI: <https://github.com/tendermint/abci> -- [18] Ethereum Sharding: <https://github.com/ethereum/EIPs/issues/53> -- [19] LibSwift: <http://www.ds.ewi.tudelft.nl/fileadmin/pds/papers/PerformanceAnalysisOfLibswift.pdf> -- [20] DLS: <http://groups.csail.mit.edu/tds/papers/Lynch/jacm88.pdf> -- [21] Thin Client Security: <https://en.bitcoin.it/wiki/Thin_Client_Security> -- [22] Ethereum 2.0 Mauve Paper: <https://cdn.hackaday.io/files/10879465447136/Mauve%20Paper%20Vitalik.pdf> - -#### Unsorted links - -- <https://www.docdroid.net/ec7xGzs/314477721-ethereum-platform-review-opportunities-and-challenges-for-private-and-consortium-blockchains.pdf> diff --git a/docs/sdk/cosmos-sdk-cli.md b/docs/sdk/cosmos-sdk-cli.md deleted file mode 100644 index 615d722522b9..000000000000 --- a/docs/sdk/cosmos-sdk-cli.md +++ /dev/null @@ -1,34 +0,0 @@ -# cosmos-sdk-cli -Create a new blockchain project based on cosmos-sdk with a single command. - ---- - -# Installation - -```shell -$ go get github.com/cosmos/cosmos-sdk -$ cd $GOPATH/src/github.com/cosmos/cosmos-sdk -$ make install_cosmos-sdk-cli -``` - -This will install a binary cosmos-sdk-cli - -# Creating a new project - -**$cosmos-sdk-cli init** _Your-Project-Name_ - -This will initialize a project, the dependencies, directory structures with the specified project name. - -### Example: -```shell -$ cosmos-sdk-cli init testzone -p github.com/your_user_name/testzone -``` -`-p [remote-project-path]`. If this is not provided, it creates testzone under $GOPATH/src/ - - -```shell -$ cd $GOPATH/src/github.com/your_user_name/testzone -$ make -``` -This will create two binaries(testzonecli and testzoned) under bin folder. testzoned is the full node of the application which you can run, and testzonecli is your light client. - diff --git a/docs/sdk/modules.md b/docs/sdk/modules.md deleted file mode 100644 index 7df8fb2bb0bb..000000000000 --- a/docs/sdk/modules.md +++ /dev/null @@ -1,19 +0,0 @@ -# Modules - -::: tip Note -🚧 We are actively working on documentation for SDK modules. -::: - -The Cosmos SDK has all the necessary pre-built modules to add functionality on top of a `BaseApp`, which is the template to build a blockchain dApp in Cosmos. In this context, a `module` is a fundamental unit in the Cosmos SDK. - -Each module is an extension of the `BaseApp`'s functionalities that defines transactions, handles application state and manages the state transition logic. Each module also contains handlers for messages and transactions, queriers for handling query requests, as well as REST and CLI for secure user interactions. - -Some of the most important modules in the SDK are: - -- `Auth`: Defines a standard account structure (`BaseAccount`) and how transaction signers are authenticated. -- `Bank`: Defines how coins or tokens (i.e cryptocurrencies) are transferred. -- `Fees`: -- `Governance`: Governance related implementation including proposals and voting. -- `IBC`: Defines the intereoperability of blockchain zones according to the specifications of the [IBC Protocol](https://cosmos.network/whitepaper#inter-blockchain-communication-ibc). -- `Slashing`: -- `Staking`: Proof of Stake related implementation including bonding and delegation transactions, inflation, fees, unbonding, etc. diff --git a/docs/sdk/overview.md b/docs/sdk/overview.md deleted file mode 100644 index 905982f62131..000000000000 --- a/docs/sdk/overview.md +++ /dev/null @@ -1,205 +0,0 @@ -# Cosmos SDK Overview - -The [Cosmos-SDK](https://github.com/cosmos/cosmos-sdk) is a framework for building multi-asset Proof-of-Stake (PoS) blockchains, like the Cosmos Hub, as well as Proof-Of-Authority (PoA) blockchains. - -The goal of the Cosmos-SDK is to allow developers to easily create custom interoperable blockchain applications within the Cosmos Network without having to recreate common blockchain functionality, thus removing the complexity of building a Tendermint ABCI application. We envision the SDK as the npm-like framework to build secure blockchain applications on top of Tendermint. - -In terms of its design, the SDK optimizes flexibility and security. The framework is designed around a modular execution stack which allows applications to mix and match elements as desired. In addition, all modules are sandboxed for greater application security. - -It is based on two major principles: - -- **Composability:** Anyone can create a module for the Cosmos-SDK and integrating the already-built modules is as simple as importing them into your blockchain application. - -- **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 and that we assume that some of those modules may be malicious, we designed the SDK using object-capabilities (ocaps) based principles. 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 3rd party module. For a deeper look at capabilities, you can read this [article](http://habitatchronicles.com/2017/05/what-are-capabilities/). - -For an introduction to object-capabilities, see this [article](http://habitatchronicles.com/2017/05/what-are-capabilities/). - -## 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. - -## Directory Structure - -The SDK is laid out in the following directories: - -- `baseapp`: Defines the template for a basic [ABCI](https://cosmos.network/whitepaper#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. -- `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. - -## Object-Capability Model - -When thinking about security, it's good to start with a specific threat -model. Our threat model is the following: - -> We assume that a thriving ecosystem of Cosmos-SDK modules that are easy to compose into a blockchain application will contain faulty or malicious modules. - -The Cosmos-SDK is designed to address this threat by being the -foundation of an object capability system. - -> The structural properties of object capability systems favor -> modularity in code design and ensure reliable encapsulation in -> code implementation. -> -> These structural properties facilitate the analysis of some -> security properties of an object-capability program or operating -> system. Some of these — in particular, information flow properties -> — can be analyzed at the level of object references and -> connectivity, independent of any knowledge or analysis of the code -> that determines the behavior of the objects. -> -> As a consequence, these security properties can be established -> and maintained in the presence of new objects that contain unknown -> and possibly malicious code. -> -> These structural properties stem from the two rules governing -> access to existing objects: -> -> 1. An object A can send a message to B only if object A holds a -> reference to B. -> 2. An object A can obtain a reference to C only -> if object A receives a message containing a reference to C. As a -> consequence of these two rules, an object can obtain a reference -> to another object only through a preexisting chain of references. -> In short, "Only connectivity begets connectivity." - -See the [wikipedia -article](https://en.wikipedia.org/wiki/Object-capability_model) on the Object Capability Model for more -information. - -Strictly speaking, Golang does not implement object capabilities -completely, because of several issues: - -- pervasive ability to import primitive modules (e.g. "unsafe", "os") -- pervasive ability to override module vars <https://github.com/golang/go/issues/23161> -- data-race vulnerability where 2+ goroutines can create illegal interface values - -The first is easy to catch by auditing imports and using a proper -dependency version control system like Dep. The second and third are -unfortunate but it can be audited with some cost. - -Perhaps [Go2 will implement the object capability -model](https://github.com/golang/go/issues/23157). - -### What does it look like? - -Only reveal what is necessary to get the work done. - -For example, the following code snippet violates the object capabilities -principle: - -```go -type AppAccount struct {...} -var account := &AppAccount{ - Address: pub.Address(), - Coins: sdk.Coins{{"ATM", 100}}, -} -var sumValue := externalModule.ComputeSumValue(account) -``` - -The method `ComputeSumValue` implies a pure function, yet the implied -capability of accepting a pointer value is the capability to modify that -value. The preferred method signature should take a copy instead. - -```go -var sumValue := externalModule.ComputeSumValue(*account) -``` - -In the Cosmos SDK, you can see the application of this principle in the -basecoin examples folder. - -```go -// File: cosmos-sdk/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.accountMapper)) - app.router.AddRoute("sketchy", sketchy.NewHandler()) -} -``` - -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). - -## Application Architecture - -The SDK has multiple levels of "application": the ABCI app, the BaseApp, the BasecoinApp, and now your App. - -### ABCI App - -The basic ABCI interface allowing Tendermint to drive the applications state machine with transaction blocks. - -### BaseApp - -Implements an ABCI App using a MultiStore 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). - -BaseApp requires stores to be mounted via capabilities keys - handlers can only access -stores they're given the key for. 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 most recent state. - -BaseApp distinguishes between two handler types - the `AnteHandler` and the `MsgHandler`. -The former is a global validity check (checking nonces, sigs and sufficient balances to pay fees, -e.g. things that apply to all transaction from all modules), the later is the full state transition function. -During CheckTx the state transition function is only applied to the checkTxState and should return -before any expensive state transitions are run (this is up to each developer). It also needs to return the estimated -gas cost. - -During DeliverTx the state transition function is applied to the blockchain state and the transactions -need to be fully executed. - -BaseApp is responsible for managing the context passed into handlers - it makes the block header available and provides the right stores for CheckTx and DeliverTx. BaseApp is completely agnostic to serialization formats. - -### 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. - -The native extensions of the SDK, useful for building Cosmos Zones, live under `x`. -Basecoin implements a `BaseApp` state machine using the `x/auth` and `x/bank` extensions, -which define how transaction signers are authenticated and how coins are transferred. -It should also use `x/ibc` and probably a simple staking extension. - -Basecoin and the native `x` extensions use go-amino for all serialization needs, -including for transactions and accounts. - -### Your Cosmos App - -Your Cosmos App is a fork of Basecoin - copy the `examples/basecoin` directory and modify it to your needs. -You may want to: - -- add fields to accounts -- copy and modify handlers -- add new handlers for new transaction types -- add new stores for better isolation across handlers - -The Cosmos Hub takes Basecoin and adds more stores and extensions to handle additional -transaction types and logic, like the advanced staking logic and the governance process. - -## Ethermint - -Ethermint is a new implementation of `BaseApp` that does not depend on Basecoin. -Instead of `cosmos-sdk/x/` it has its own `ethermint/x` based on `go-ethereum`. - -Ethermint uses a Patricia store for its accounts, and an IAVL store for IBC. -It has `x/ante`, which is quite similar to Basecoin's but uses RLP instead of go-amino. -Instead of `x/bank`, it has `x/eth`, which defines the single Ethereum transaction type -and all the semantics of the Ethereum state machine. - -Within `x/eth`, transactions sent to particular addresses can be handled in unique ways, -for instance to handle IBC and staking. diff --git a/docs/spec/README.md b/docs/spec/README.md index 49ea67b7a212..047b1ca98ecf 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -1,28 +1,31 @@ -# Cosmos Hub Spec +# Specifications -This directory contains specifications for the state transition machine of the -Cosmos Hub. +This directory contains specifications for the modules of the Cosmos SDK as well as Interchain Standards (ICS) and other specifications. -The Cosmos Hub holds all of its state in a Merkle store. Updates to +SDK applications hold this state in a Merkle store. Updates to the store may be made during transactions and at the beginning and end of every block. -While the first implementation of the Cosmos Hub is built using the Cosmos-SDK, -these specifications aim to be independent of any implementation details. That -said, they provide a detailed resource for understanding the Cosmos-SDK. - -- [Store](store) - The core Merkle store that holds the state. -- [Auth](auth) - The structure and authentication of accounts and transactions. -- [Bank](bank) - Sending tokens. -- [Governance](governance) - Proposals and voting. -- [Staking](staking) - Proof-of-stake bonding, delegation, etc. -- [Slashing](slashing) - Validator punishment mechanisms. -- [Distribution](distribution) - Fee distribution, and atom provision distribution -- [Inflation](inflation) - Atom provision creation -- [IBC](ibc) - Inter-Blockchain Communication (IBC) protocol. -- [Other](other) - Other components of the Cosmos Hub, including the reserve - pool, All in Bits vesting, etc. +### SDK specifications: + +- [Store](./store) - The core Merkle store that holds the state. +- [Bech32](./other/bech32.md) - Address format for Cosmos SDK applications. + +### Modules specifications: + +- [Auth](./auth) - The structure and authentication of accounts and transactions. +- [Bank](./bank) - Sending tokens. +- [Governance](./governance) - Proposals and voting. +- [Staking](./staking) - Proof-of-stake bonding, delegation, etc. +- [Slashing](./slashing) - Validator punishment mechanisms. +- [Distribution](./distribution) - Fee distribution, and staking token provision distribution . +- [Inflation](./inflation) - Staking token provision creation +- [IBC](./ibc) - Inter-Blockchain Communication (IBC) protocol. + +### Interchain standards + +- [ICS30](./ics/ics-030-signed-messages.md) - Signed messages standard. +- For details on the underlying blockchain and p2p protocols, see the [Tendermint specification](https://github.com/tendermint/tendermint/tree/develop/docs/spec). - diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index c5c25ecaed03..b8785619b2aa 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -1,159 +1,469 @@ -## Vesting - -### Intro and Requirements - -This paper specifies 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 endtime 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. -The vesting account should also be able to spend any coins it receives from other users. -Thus, the bank module's `MsgSend` handler should error if a vesting account is trying to send an amount that exceeds their -unlocked coin amount. - -### Implementation - -##### Vesting Account implementation - -NOTE: `Now = ctx.BlockHeader().Time` +# Vesting + +<!-- TOC --> + +- [Vesting](#vesting) + - [Intro and Requirements](#intro-and-requirements) + - [Vesting Account Types](#vesting-account-types) + - [Vesting Account Specification](#vesting-account-specification) + - [Determining Vesting & Vested Amounts](#determining-vesting--vested-amounts) + - [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) + - [Delegating](#delegating) + - [Continuously Vesting Accounts](#continuously-vesting-accounts-2) + - [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts-2) + - [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](#keepers--handlers) + - [Initializing at Genesis](#initializing-at-genesis) + - [Examples](#examples) + - [Simple](#simple) + - [Slashing](#slashing) + - [Glossary](#glossary) + +<!-- /TOC --> + +## Intro and Requirements + +This paper specifies 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`. + +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. + +In addition, a vesting account vests all of its coin denominations at the same +rate. This may be subject to change. + +**Note**: A vesting account could have some vesting and non-vesting coins. To +support such a feature, the `GenesisAccount` type will need to be updated in +order to make such a distinction. + +## Vesting Account Types ```go +// VestingAccount defines an interface that any vesting account type must +// implement. type VestingAccount interface { Account - AssertIsVestingAccount() // existence implies that account is vesting. + 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) +} + +// BaseVestingAccount implements the VestingAccount interface. It contains all +// the necessary fields needed for any vesting account implementation. +type BaseVestingAccount struct { + BaseAccount - // Calculates amount of coins that can be sent to other accounts given the current time - SendableCoins(sdk.Context) sdk.Coins + OriginalVesting Coins // coins in account upon initialization + DelegatedFree Coins // coins that are vested and delegated + EndTime Time // when the coins become unlocked } -// Implements Vesting Account -// Continuously vests by unlocking coins linearly with respect to time +// ContinuousVestingAccount implements the VestingAccount interface. It +// continuously vests by unlocking coins linearly with respect to time. type ContinuousVestingAccount struct { BaseAccount - OriginalVestingCoins sdk.Coins // Coins in account on Initialization - ReceivedCoins sdk.Coins // Coins received from other accounts - SentCoins sdk.Coins // Coins sent to other accounts + BaseVestingAccount - // StartTime and EndTime used to calculate how much of OriginalCoins is unlocked at any given point - StartTime time.Time - EndTime time.Time + DelegatedVesting Coins // coins that vesting and delegated + StartTime Time // when the coins start to vest } -// Uses time in context to calculate total unlocked coins -SendableCoins(vacc ContinuousVestingAccount, ctx sdk.Context) sdk.Coins: - - // Coins unlocked by vesting schedule - unlockedCoins := ReceivedCoins - SentCoins + OriginalVestingCoins * (Now - StartTime) / (EndTime - StartTime) +// 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 +} +``` - // Must still check for currentCoins constraint since some unlocked coins may have been delegated. - currentCoins := vacc.BaseAccount.GetCoins() +## Vesting Account Specification - // min will return sdk.Coins with each denom having the minimum amount from unlockedCoins and currentCoins - return min(unlockedCoins, currentCoins) +Given a vesting account, we define the following in the proceeding operations: +- `OV`: The original vesting coin amount. It is a constant value. +- `V`: The number of `OV` coins that are still _vesting_. It is derived by `OV`, `StartTime` and `EndTime`. This value is computed on demand and not on a per-block basis. +- `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. + +### Determining Vesting & Vested Amounts + +It is important to note that these values are computed on demand and not on a +mandatory per-block basis. + +#### Continuously Vesting Accounts + +To determine the amount of coins that are vested for a given block `B`, the +following is performed: + +1. Compute `X := B.Time - StartTime` +2. Compute `Y := EndTime - StartTime` +3. Compute `V' := OV * (X / Y)` +4. Compute `V := OV - V'` + +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 { + return ZeroCoins + } + + x := b.Time - 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) +} ``` -The `VestingAccount` interface is used to assert that an account is a vesting account like so: +#### 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). ```go -vacc, ok := acc.(VestingAccount); ok +func (dva DelayedVestingAccount) GetVestedCoins(b Block) Coins { + if b.Time >= dva.EndTime { + return dva.OriginalVesting + } + + return ZeroCoins +} + +func (dva DelayedVestingAccount) GetVestingCoins(b Block) Coins { + return cva.OriginalVesting - cva.GetVestedCoins(b) +} ``` -as well as to calculate the SendableCoins at any given moment. +### Transferring/Sending -The `ContinuousVestingAccount` struct implements the Vesting account interface. It uses `OriginalVestingCoins`, `ReceivedCoins`, -`SentCoins`, `StartTime`, and `EndTime` to calculate how many coins are sendable at any given point. -Since the vesting restrictions need to be implemented on a per-module basis, the `ContinuousVestingAccount` implements -the `Account` interface exactly like `BaseAccount`. Thus, `ContinuousVestingAccount.GetCoins()` will return the total of -both locked coins and unlocked coins currently in the account. Delegated coins are deducted from `Account.GetCoins()`, but do not count against unlocked coins because they are still at stake and will be reinstated (partially if slashed) after waiting the full unbonding period. +#### Continuously Vesting Accounts -##### Changes to Keepers/Handler +At any given time, a continuous vesting account may transfer: `min((BC + DV) - V, BC)`. -Since a vesting account should be capable of doing everything but sending with its locked coins, the restriction should be -handled at the `bank.Keeper` level. Specifically in methods that are explicitly used for sending like -`sendCoins` and `inputOutputCoins`. These methods must check that an account is a vesting account using the check described above. +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 -if acc is VestingAccount and Now < vestingAccount.EndTime: - // Check if amount is less than currently allowed sendable coins - if msg.Amount > vestingAccount.SendableCoins(ctx) then fail - else: - vestingAccount.SentCoins += msg.Amount +func (cva ContinuousVestingAccount) SpendableCoins() Coins { + bc := cva.GetCoins() + return min((bc + cva.DelegatedVesting) - cva.GetVestingCoins(), bc) +} +``` -else: - // Account has fully vested, treat like regular account - if msg.Amount > account.GetCoins() then fail +##### Delayed/Discrete Vesting Accounts -// All checks passed, send the coins -SendCoins(inputs, outputs) +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() +} ``` -Coins that are sent to a vesting account after initialization by users sending them coins should be spendable -immediately after receiving them. Thus, handlers (like staking or bank) that send coins that a vesting account did not -originally own should increment `ReceivedCoins` by the amount sent. -Unlocked coins that are sent to other accounts will increment the vesting account's `SentCoins` attribute. +##### Keepers/Handlers -CONTRACT: Handlers SHOULD NOT update `ReceivedCoins` if they were originally sent from the vesting account. For example, if a vesting account unbonds from a validator, their tokens should be added back to account but staking handlers SHOULD NOT update `ReceivedCoins`. -However when a user sends coins to vesting account, then `ReceivedCoins` SHOULD be incremented. +The corresponding `x/bank` keeper should appropriately handle sending coins +based on if the account is a vesting account or not. -### Initializing at Genesis +```go +func SendCoins(from Account, to Account amount Coins) { + if isVesting(from) { + sc := from.SpendableCoins() + } else { + sc := from.GetCoins() + } + + if amount <= sc { + from.SetCoins(sc - amount) + to.SetCoins(amount) + // save accounts... + } +} +``` -To initialize both vesting accounts and base accounts, the `GenesisAccount` struct will include an EndTime. Accounts meant to be -BaseAccounts will have `EndTime = 0`. The `initChainer` method will parse the GenesisAccount into BaseAccounts and VestingAccounts -as appropriate. +### Delegating + +#### Continuously Vesting Accounts + +For a continuous 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) +3. Compute `Y := D - X` (portion of `D` that is free) +4. Set `DV += X` +5. Set `DF += Y` +6. Set `BC -= D` ```go -type GenesisAccount struct { - Address sdk.AccAddress `json:"address"` - GenesisCoins sdk.Coins `json:"coins"` - EndTime int64 `json:"lock"` +func (cva ContinuousVestingAccount) TrackDelegation(amount Coins) { + x := min(max(cva.GetVestingCoins() - cva.DelegatedVesting, 0), amount) + y := amount - x + + cva.DelegatedVesting += x + cva.DelegatedFree += y } +``` -initChainer: - for gacc in GenesisAccounts: - baseAccount := BaseAccount{ - Address: gacc.Address, - Coins: gacc.GenesisCoins, +##### 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`. + +```go +func (dva DelayedVestingAccount) TrackDelegation(amount Coins) { + dva.DelegatedFree += amount +} +``` + +##### 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 gacc.EndTime != 0: - vestingAccount := ContinuouslyVestingAccount{ - BaseAccount: baseAccount, - OriginalVestingCoins: gacc.GenesisCoins, - StartTime: RequestInitChain.Time, - EndTime: gacc.EndTime, - } - AddAccountToState(vestingAccount) - else: - AddAccountToState(baseAccount) + } else { + sc := from.GetCoins() + if amount <= sc { + from.SetCoins(sc - amount) + // save account... + } + } +} ``` -### Formulas +### Undelegating -`OriginalVestingCoins`: Amount of coins in account at Genesis +#### Continuously Vesting Accounts -`CurrentCoins`: Coins currently in the baseaccount (both locked and unlocked: `vestingAccount.GetCoins`) +For a continuous vesting account attempting to undelegate `D` coins, the +following is performed: -`ReceivedCoins`: Coins received from other accounts (always unlocked) +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` +6. Set `BC += D` -`LockedCoins`: Coins that are currently locked +```go +func (cva ContinuousVestingAccount) TrackUndelegation(amount Coins) { + y := min(cva.DelegatedFree, amount) + x := amount - y -`Delegated`: Coins that have been delegated (no longer in account; may be locked or unlocked) + cva.DelegatedVesting -= x + cva.DelegatedFree -= y +} +``` -`Sent`: Coins sent to other accounts (MUST be unlocked) +**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 +undelegating free coins are prioritized. -Maximum amount of coins vesting schedule allows to be sent: +##### Delayed/Discrete Vesting Accounts -`ReceivedCoins - SentCoins + OriginalVestingCoins * (Now - StartTime) / (EndTime - StartTime)` +For a delayed vesting account, it only needs to add back the `DF` amount since +the account is fully vested. -`ReceivedCoins - SentCoins + OriginalVestingCoins - LockedCoins` +```go +func (dva DelayedVestingAccount) TrackUndelegation(amount Coins) { + dva.DelegatedFree -= amount +} +``` -Coins currently in Account: +##### Keepers/Handlers -`CurrentCoins = OriginalVestingCoins + ReceivedCoins - Delegated - Sent` +```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 { + AddCoins(to, amount) + // save account... + } +} +``` + +## 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 +coins, must call explicit methods on the `x/bank` keeper (e.g. `DelegateCoins`) +opposed to `SendCoins` and `SubtractCoins`. + +In addition, the vesting account should also be able to spend any coins it +receives from other users. Thus, the bank module's `MsgSend` handler should +error if a vesting account is trying to send an amount that exceeds their +unlocked coin amount. -`CurrentCoins = vestingAccount.GetCoins()` +See the above specification for full implementation details. -**Maximum amount of coins spendable right now:** +## Initializing at Genesis + +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. + +```go +type GenesisAccount struct { + Address sdk.AccAddress + GenesisCoins sdk.Coins + EndTime int64 +} + +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, + } + + AddAccountToState(vestingAccount) + } else { + AddAccountToState(baseAccount) + } + } +} +``` + +## Examples + +### Simple + +Given a continuous vesting account with 10 vesting coins. + +``` +OV = 10 +DF = 0 +DV = 0 +BC = 10 +V = 10 +V' = 0 +``` -`min( ReceivedCoins - SentCoins + OriginalVestingCoins - LockedCoins, CurrentCoins )` +1. Immediately receives 1 coin + ``` + BC = 11 + ``` +2. Time passes, 2 coins vest + ``` + V = 8 + V' = 2 + ``` +3. Delegates 4 coins to validator A + ``` + DV = 4 + BC = 7 + ``` +4. Sends 3 coins + ``` + BC = 4 + ``` +5. More time passes, 2 more coins vest + ``` + 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. + ``` + BC = 2 + ``` + +### Slashing + +Same initial starting conditions as the simple example. + +1. Time passes, 5 coins vest + ``` + V = 5 + V' = 5 + ``` +2. Delegate 5 coins to validator A + ``` + DV = 5 + BC = 5 + ``` +3. Delegate 5 coins to validator B + ``` + 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) + ``` + 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. + ``` + 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`. + +## Glossary + +- OriginalVesting: The amount of coins (per denomination) that are initially part of a vesting account. These coins are set at genesis. +- StartTime: The BFT time at which a vesting account starts to vest. +- EndTime: The BFT time at which a vesting account is fully vested. +- DelegatedFree: The tracked amount of coins (per denomination) that are delegated from a vesting account that have been fully vested at time of delegation. +- DelegatedVesting: The tracked amount of coins (per denomination) that are delegated from a vesting account that were vesting at time of delegation. +- ContinuousVestingAccount: A vesting account implementation that vests coins linearly over time. +- DelayedVestingAccount: A vesting account implementation that only fully vests all coins at a given time. diff --git a/docs/spec/distribution/end_block.md b/docs/spec/distribution/end_block.md index 952d1832ac5b..2a3db9d4b29d 100644 --- a/docs/spec/distribution/end_block.md +++ b/docs/spec/distribution/end_block.md @@ -15,7 +15,7 @@ pool which validator holds individually (`ValidatorDistribution.ProvisionsRewardPool`). ``` -func AllocateFees(feesCollected sdk.Coins, global Global, proposer ValidatorDistribution, +func AllocateTokens(feesCollected sdk.Coins, feePool FeePool, proposer ValidatorDistribution, sumPowerPrecommitValidators, totalBondedTokens, communityTax, proposerCommissionRate sdk.Dec) @@ -28,13 +28,11 @@ func AllocateFees(feesCollected sdk.Coins, global Global, proposer ValidatorDist proposer.Pool += proposerReward - commission communityFunding = feesCollectedDec * communityTax - global.CommunityFund += communityFunding + feePool.CommunityFund += communityFunding poolReceived = feesCollectedDec - proposerReward - communityFunding - global.Pool += poolReceived - global.EverReceivedPool += poolReceived - global.LastReceivedPool = poolReceived + feePool.Pool += poolReceived SetValidatorDistribution(proposer) - SetGlobal(global) + SetFeePool(feePool) ``` diff --git a/docs/spec/distribution/hooks.md b/docs/spec/distribution/hooks.md index e1a18ef593d6..8c2b4b91a10a 100644 --- a/docs/spec/distribution/hooks.md +++ b/docs/spec/distribution/hooks.md @@ -6,7 +6,7 @@ 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 -`DelegatorDistInfo.WithdrawalHeight` to the height of the triggering transaction. +`DelegationDistInfo.WithdrawalHeight` to the height of the triggering transaction. ## Commission rate change diff --git a/docs/spec/distribution/overview.md b/docs/spec/distribution/overview.md index 13a4ea3f1431..8cbe9355a8d0 100644 --- a/docs/spec/distribution/overview.md +++ b/docs/spec/distribution/overview.md @@ -2,71 +2,75 @@ ## Overview -This _simple_ distribution mechanism describes a functional way to passively -distribute rewards between validator and delegators. Note that this mechanism does -not distribute funds in as precisely as active reward distribution and will therefore -be upgraded in the future. +This _simple_ distribution mechanism describes a functional way to passively +distribute rewards between validators and delegators. Note that this mechanism does +not distribute funds in as precisely as active reward distribution mechanisms and +will therefore be upgraded in the future. The mechanism operates as follows. Collected rewards are pooled globally and divided out passively to validators and delegators. Each validator has the opportunity to charge commission to the delegators on the rewards collected on -behalf of the delegators by the validators. Fees are paid directly into a -global reward pool, and validator proposer-reward pool. Due to the nature of -passive accounting, whenever changes to parameters which affect the rate of reward -distribution occurs, withdrawal of rewards must also occur. - - - Whenever withdrawing, one must withdraw the maximum amount they are entitled - too, leaving nothing in the pool. - - Whenever bonding, unbonding, or re-delegating tokens to an existing account, a +behalf of the delegators. Fees are collected directly into a global reward pool +and validator proposer-reward pool. Due to the nature of passive accounting, +whenever changes to parameters which affect the rate of reward distribution +occurs, withdrawal of rewards must also occur. + +- Whenever withdrawing, one must withdraw the maximum amount they are entitled + to, leaving nothing in the pool. +- Whenever bonding, unbonding, or re-delegating tokens to an existing account, a full withdrawal of the rewards must occur (as the rules for lazy accounting change). - - Whenever a validator chooses to change the commission on rewards, all accumulated +- Whenever a validator chooses to change the commission on rewards, all accumulated commission rewards must be simultaneously withdrawn. The above scenarios are covered in `hooks.md`. -The distribution mechanism outlines herein is used to lazily distribute the +The distribution mechanism outlined herein is used to lazily distribute the following rewards between validators and associated delegators: - - multi-token fees to be socially distributed, - - proposer reward pool, - - inflated atom provisions, and - - validator commission on all rewards earned by their delegators stake + +- multi-token fees to be socially distributed +- proposer reward pool +- inflated atom provisions +- validator commission on all rewards earned by their delegators stake Fees are pooled within a global pool, as well as validator specific proposer-reward pools. The mechanisms used allow for validators and delegators to independently and lazily withdraw their rewards. -## Shortcomings +## Shortcomings As a part of the lazy computations, each delegator holds an accumulation term specific to each validator which is used to estimate what their approximate -fair portion of tokens held in the global pool is owed to them. +fair portion of tokens held in the global fee pool is owed to them. ``` entitlement = delegator-accumulation / all-delegators-accumulation ``` -Under the circumstance that there were constant and equal flow of incoming +Under the circumstance that there was constant and equal flow of incoming reward tokens every block, this distribution mechanism would be equal to the active distribution (distribute individually to all delegators each block). -However this is unrealistic so deviations from the active distribution will +However, this is unrealistic so deviations from the active distribution will occur based on fluctuations of incoming reward tokens as well as timing of -reward withdrawal by other delegators. +reward withdrawal by other delegators. -If you happen to know that incoming rewards are about significantly move up, +If you happen to know that incoming rewards are about to significantly increase, you are incentivized to not withdraw until after this event, increasing the -worth of your existing _accum_. +worth of your existing _accum_. See [#2764](https://github.com/cosmos/cosmos-sdk/issues/2764) +for further details. ## Affect on Staking Charging commission on Atom provisions while also allowing for Atom-provisions to be auto-bonded (distributed directly to the validators bonded stake) is -problematic within DPoS. Fundamentally these two mechanisms are mutually -exclusive. If there are Atom commissions and auto-bonding Atoms, the portion -of Atoms the reward distribution calculation would become very large as the Atom -portion for each delegator would change each block making a withdrawal of rewards -for a delegator require a calculation for every single block since the last -withdrawal. In conclusion, we can only have Atom commission and unbonded atoms +problematic within BPoS. Fundamentally, these two mechanisms are mutually +exclusive. If both commission and auto-bonding mechanisms are simultaneously +applied to the staking-token then the distribution of staking-tokens between +any validator and its delegators will change with each block. This then +necessitates a calculation for each delegation records for each block - +which is considered computationally expensive. + +In conclusion, we can only have Atom commission and unbonded atoms provisions or bonded atom provisions with no Atom commission, and we elect to implement the former. Stakeholders wishing to rebond their provisions may elect -to set up a script to periodically withdraw and rebond rewards. +to set up a script to periodically withdraw and rebond rewards. diff --git a/docs/spec/distribution/state.md b/docs/spec/distribution/state.md index 3e3669789abc..576f5390ba28 100644 --- a/docs/spec/distribution/state.md +++ b/docs/spec/distribution/state.md @@ -1,9 +1,9 @@ ## State -### Global +### FeePool All globally tracked parameters for distribution are stored within -`Global`. Rewards are collected and added to the reward pool and +`FeePool`. Rewards are collected and added to the reward pool and distributed to validators/delegators from here. Note that the reward pool holds decimal coins (`DecCoins`) to allow @@ -11,7 +11,7 @@ for fractions of coins to be received from operations like inflation. When coins are distributed from the pool they are truncated back to `sdk.Coins` which are non-decimal. - - Global: `0x00 -> amino(global)` + - FeePool: `0x00 -> amino(FeePool)` ```golang // coins with decimal @@ -22,7 +22,7 @@ type DecCoin struct { Denom string } -type Global struct { +type FeePool struct { TotalValAccumUpdateHeight int64 // last height which the total validator accum was updated TotalValAccum sdk.Dec // total valdator accum held by validators Pool DecCoins // funds for all validators which have yet to be withdrawn @@ -42,7 +42,7 @@ Validator distribution information for the relevant validator is updated each ti ```golang type ValidatorDistInfo struct { - GlobalWithdrawalHeight int64 // last height this validator withdrew from the global pool + FeePoolWithdrawalHeight int64 // last height this validator withdrew from the global fee pool Pool DecCoins // rewards owed to delegators, commission has already been charged (includes proposer reward) PoolCommission DecCoins // commission collected by this validator (pending withdrawal) @@ -59,10 +59,10 @@ properties change (aka bonded tokens etc.) its properties will remain constant and the delegator's _accumulation_ factor can be calculated passively knowing only the height of the last withdrawal and its current properties. - - DelegatorDistInfo: ` 0x02 | DelegatorAddr | ValOperatorAddr -> amino(delegatorDist)` + - DelegationDistInfo: ` 0x02 | DelegatorAddr | ValOperatorAddr -> amino(delegatorDist)` ```golang -type DelegatorDistInfo struct { +type DelegationDistInfo struct { WithdrawalHeight int64 // last time this delegation withdrew rewards } ``` diff --git a/docs/spec/distribution/transactions.md b/docs/spec/distribution/transactions.md index 0b89ae44ee6d..821c4ba352e5 100644 --- a/docs/spec/distribution/transactions.md +++ b/docs/spec/distribution/transactions.md @@ -1,16 +1,15 @@ # Transactions -## TxWithdrawDelegationRewardsAll +## MsgWithdrawDelegationRewardsAll When a delegator wishes to withdraw their rewards it must send -`TxWithdrawDelegationRewardsAll`. Note that parts of this transaction logic are also +`MsgWithdrawDelegationRewardsAll`. Note that parts of this transaction logic are also triggered each with any change in individual delegations, such as an unbond, redelegation, or delegation of additional tokens to a specific validator. ```golang -type TxWithdrawDelegationRewardsAll struct { - delegatorAddr sdk.AccAddress - withdrawAddr sdk.AccAddress // address to make the withdrawal to +type MsgWithdrawDelegationRewardsAll struct { + DelegatorAddr sdk.AccAddress } func WithdrawDelegationRewardsAll(delegatorAddr, withdrawAddr sdk.AccAddress) @@ -26,31 +25,30 @@ func GetDelegatorRewardsAll(delegatorAddr sdk.AccAddress, height int64) DecCoins // collect all entitled rewards withdraw = 0 pool = stake.GetPool() - global = GetGlobal() + feePool = GetFeePool() for delegation = range delegations delInfo = GetDelegationDistInfo(delegation.DelegatorAddr, delegation.ValidatorAddr) valInfo = GetValidatorDistInfo(delegation.ValidatorAddr) validator = GetValidator(delegation.ValidatorAddr) - global, diWithdraw = delInfo.WithdrawRewards(global, valInfo, height, pool.BondedTokens, + feePool, diWithdraw = delInfo.WithdrawRewards(feePool, valInfo, height, pool.BondedTokens, validator.Tokens, validator.DelegatorShares, validator.Commission) withdraw += diWithdraw - SetGlobal(global) + SetFeePool(feePool) return withdraw ``` -## TxWithdrawDelegationReward +## MsgWithdrawDelegationReward under special circumstances a delegator may wish to withdraw rewards from only a single validator. ```golang -type TxWithdrawDelegationReward struct { - delegatorAddr sdk.AccAddress - validatorAddr sdk.AccAddress - withdrawAddr sdk.AccAddress // address to make the withdrawal to +type MsgWithdrawDelegationReward struct { + DelegatorAddr sdk.AccAddress + ValidatorAddr sdk.ValAddress } func WithdrawDelegationReward(delegatorAddr, validatorAddr, withdrawAddr sdk.AccAddress) @@ -58,39 +56,38 @@ func WithdrawDelegationReward(delegatorAddr, validatorAddr, withdrawAddr sdk.Acc // get all distribution scenarios pool = stake.GetPool() - global = GetGlobal() + feePool = GetFeePool() delInfo = GetDelegationDistInfo(delegatorAddr, validatorAddr) valInfo = GetValidatorDistInfo(validatorAddr) validator = GetValidator(validatorAddr) - global, withdraw = delInfo.WithdrawRewards(global, valInfo, height, pool.BondedTokens, + feePool, withdraw = delInfo.WithdrawRewards(feePool, valInfo, height, pool.BondedTokens, validator.Tokens, validator.DelegatorShares, validator.Commission) - SetGlobal(global) + SetFeePool(feePool) AddCoins(withdrawAddr, withdraw.TruncateDecimal()) ``` -## TxWithdrawValidatorRewardsAll +## MsgWithdrawValidatorRewardsAll When a validator wishes to withdraw their rewards it must send -`TxWithdrawValidatorRewardsAll`. Note that parts of this transaction logic are also +`MsgWithdrawValidatorRewardsAll`. Note that parts of this transaction logic are also triggered each with any change in individual delegations, such as an unbond, redelegation, or delegation of additional tokens to a specific validator. This transaction withdraws the validators commission fee, as well as any rewards earning on their self-delegation. ``` -type TxWithdrawValidatorRewardsAll struct { - operatorAddr sdk.AccAddress // validator address to withdraw from - withdrawAddr sdk.AccAddress // address to make the withdrawal to +type MsgWithdrawValidatorRewardsAll struct { + OperatorAddr sdk.ValAddress // validator address to withdraw from } func WithdrawValidatorRewardsAll(operatorAddr, withdrawAddr sdk.AccAddress) height = GetHeight() - global = GetGlobal() + feePool = GetFeePool() pool = GetPool() ValInfo = GetValidatorDistInfo(delegation.ValidatorAddr) validator = GetValidator(delegation.ValidatorAddr) @@ -99,10 +96,10 @@ func WithdrawValidatorRewardsAll(operatorAddr, withdrawAddr sdk.AccAddress) withdraw = GetDelegatorRewardsAll(validator.OperatorAddr, height) // withdrawal validator commission rewards - global, commission = valInfo.WithdrawCommission(global, valInfo, height, pool.BondedTokens, + feePool, commission = valInfo.WithdrawCommission(feePool, valInfo, height, pool.BondedTokens, validator.Tokens, validator.Commission) withdraw += commission - SetGlobal(global) + SetFeePool(feePool) AddCoins(withdrawAddr, withdraw.TruncateDecimal()) ``` @@ -117,7 +114,7 @@ block. The accum is always additive to the existing accum. This term is to be updated each time rewards are withdrawn from the system. ``` -func (g Global) UpdateTotalValAccum(height int64, totalBondedTokens Dec) Global +func (g FeePool) UpdateTotalValAccum(height int64, totalBondedTokens Dec) FeePool blocks = height - g.TotalValAccumUpdateHeight g.TotalValAccum += totalDelShares * blocks g.TotalValAccumUpdateHeight = height @@ -140,7 +137,7 @@ func (vi ValidatorDistInfo) UpdateTotalDelAccum(height int64, totalDelShares Dec return vi ``` -### Global pool to validator pool +### FeePool pool to validator pool Every time a validator or delegator executes a withdrawal or the validator is the proposer and receives new tokens, the relevant validator must move tokens @@ -148,14 +145,14 @@ from the passive global pool to their own pool. It is at this point that the commission is withdrawn ``` -func (vi ValidatorDistInfo) TakeAccum(g Global, height int64, totalBonded, vdTokens, commissionRate Dec) ( - vi ValidatorDistInfo, g Global) +func (vi ValidatorDistInfo) TakeFeePoolRewards(g FeePool, height int64, totalBonded, vdTokens, commissionRate Dec) ( + vi ValidatorDistInfo, g FeePool) g.UpdateTotalValAccum(height, totalBondedShares) // update the validators pool - blocks = height - vi.GlobalWithdrawalHeight - vi.GlobalWithdrawalHeight = height + blocks = height - vi.FeePoolWithdrawalHeight + vi.FeePoolWithdrawalHeight = height accum = blocks * vdTokens withdrawalTokens := g.Pool * accum / g.TotalValAccum commission := withdrawalTokens * commissionRate @@ -175,12 +172,12 @@ For delegations (including validator's self-delegation) all rewards from reward pool have already had the validator's commission taken away. ``` -func (di DelegatorDistInfo) WithdrawRewards(g Global, vi ValidatorDistInfo, +func (di DelegationDistInfo) WithdrawRewards(g FeePool, vi ValidatorDistInfo, height int64, totalBonded, vdTokens, totalDelShares, commissionRate Dec) ( - di DelegatorDistInfo, g Global, withdrawn DecCoins) + di DelegationDistInfo, g FeePool, withdrawn DecCoins) vi.UpdateTotalDelAccum(height, totalDelShares) - g = vi.TakeAccum(g, height, totalBonded, vdTokens, commissionRate) + g = vi.TakeFeePoolRewards(g, height, totalBonded, vdTokens, commissionRate) blocks = height - di.WithdrawalHeight di.WithdrawalHeight = height @@ -200,11 +197,11 @@ func (di DelegatorDistInfo) WithdrawRewards(g Global, vi ValidatorDistInfo, Commission is calculated each time rewards enter into the validator. ``` -func (vi ValidatorDistInfo) WithdrawCommission(g Global, height int64, +func (vi ValidatorDistInfo) WithdrawCommission(g FeePool, height int64, totalBonded, vdTokens, commissionRate Dec) ( - vi ValidatorDistInfo, g Global, withdrawn DecCoins) + vi ValidatorDistInfo, g FeePool, withdrawn DecCoins) - g = vi.TakeAccum(g, height, totalBonded, vdTokens, commissionRate) + g = vi.TakeFeePoolRewards(g, height, totalBonded, vdTokens, commissionRate) withdrawalTokens := vi.PoolCommission vi.PoolCommission = 0 diff --git a/docs/spec/governance/README.md b/docs/spec/governance/README.md index 0ddbc7cfdcff..6cc1edb50845 100644 --- a/docs/spec/governance/README.md +++ b/docs/spec/governance/README.md @@ -2,7 +2,7 @@ ## Abstract -This paper specifies the Governance module of the Cosmos-SDK, which was first described in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper) in June 2016. +This paper specifies the Governance module of the Cosmos-SDK, which was first described in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper) in June 2016. The module enables Cosmos-SDK based blockchain to support an on-chain governance system. In this system, holders of the native staking token of the chain can vote on proposals on a 1 token 1 vote basis. Next is a list of features the module currently supports: @@ -24,7 +24,7 @@ The following specification uses *Atom* as the native staking token. The module 1. **[Design overview](overview.md)** 2. **Implementation** 1. **[State](state.md)** - 1. Procedures + 1. Parameters 2. Proposals 3. Proposal Processing Queue 2. **[Transactions](transactions.md)** diff --git a/docs/spec/governance/state.md b/docs/spec/governance/state.md index 5b577cec3918..3858ba06b69f 100644 --- a/docs/spec/governance/state.md +++ b/docs/spec/governance/state.md @@ -2,36 +2,35 @@ ## State -### Procedures and base types - -`Procedures` define the rule according to which votes are run. There can only -be one active procedure at any given time. If governance wants to change a -procedure, either to modify a value or add/remove a parameter, a new procedure -has to be created and the previous one rendered inactive. +### Parameters and base types +`Parameters` define the rules according to which votes are run. There can only +be one active parameter set at any given time. If governance wants to change a +parameter set, either to modify a value or add/remove a parameter field, a new +parameter set has to be created and the previous one rendered inactive. ```go -type DepositProcedure struct { - MinDeposit sdk.Coins // Minimum deposit for a proposal to enter voting period. - MaxDepositPeriod time.Time // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months +type DepositParams struct { + MinDeposit sdk.Coins // Minimum deposit for a proposal to enter voting period. + MaxDepositPeriod time.Time // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months } ``` ```go -type VotingProcedure struct { - VotingPeriod time.Time // Length of the voting period. Initial value: 2 weeks +type VotingParams struct { + VotingPeriod time.Time // Length of the voting period. Initial value: 2 weeks } ``` ```go -type TallyingProcedure struct { - Threshold sdk.Dec // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 - Veto sdk.Dec // Minimum proportion of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 - GovernancePenalty sdk.Dec // Penalty if validator does not vote +type TallyParams struct { + Threshold sdk.Dec // Minimum proportion of Yes votes for proposal to pass. Initial value: 0.5 + Veto sdk.Dec // Minimum proportion of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 + GovernancePenalty sdk.Dec // Penalty if validator does not vote } ``` -Procedures are stored in a global `GlobalParams` KVStore. +Parameters are stored in a global `GlobalParams` KVStore. Additionally, we introduce some basic types: @@ -61,7 +60,7 @@ const ( ProposalStatusActive = 0x2 // MinDeposit is reachhed, participants can vote ProposalStatusAccepted = 0x3 // Proposal has been accepted ProposalStatusRejected = 0x4 // Proposal has been rejected - ProposalStatusClosed. = 0x5 // Proposal never reached MinDeposit + ProposalStatusClosed = 0x5 // Proposal never reached MinDeposit ) ``` @@ -76,7 +75,7 @@ const ( ### ValidatorGovInfo -This type is used in a temp map when tallying +This type is used in a temp map when tallying ```go type ValidatorGovInfo struct { @@ -87,7 +86,7 @@ This type is used in a temp map when tallying ### Proposals -`Proposals` are an item to be voted on. +`Proposals` are an item to be voted on. ```go type Proposal struct { @@ -96,10 +95,12 @@ type Proposal struct { Type ProposalType // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} TotalDeposit sdk.Coins // Current deposit on this proposal. Initial value is set at InitialDeposit Deposits []Deposit // List of deposits on the proposal - SubmitTime time.Time // Time of the block where TxGovSubmitProposal was included - Submitter sdk.Address // Address of the submitter - - VotingStartTime time.Time // Time of the block where MinDeposit was reached. time.Time{} if MinDeposit is not reached + SubmitTime time.Time // Time of the block where TxGovSubmitProposal was included + DepositEndTime time.Time // Time that the DepositPeriod of a proposal would expire + Submitter sdk.AccAddress // Address of the submitter + + VotingStartTime time.Time // Time of the block where MinDeposit was reached. time.Time{} if MinDeposit is not reached + VotingEndTime time.Time // Time of the block that the VotingPeriod for a proposal will end. CurrentStatus ProposalStatus // Current status of the proposal YesVotes sdk.Dec @@ -133,47 +134,27 @@ For pseudocode purposes, here are the two function we will use to read or write ### Proposal Processing Queue **Store:** -* `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the - `ProposalIDs` of proposals that reached `MinDeposit`. Each round, the oldest - element of `ProposalProcessingQueue` is checked during `BeginBlock` to see if - `CurrentTime == VotingStartTime + activeProcedure.VotingPeriod`. If it is, - then the application tallies the votes, compute the votes of each validator and checks if every validator in the valdiator set have voted - and, if not, applies `GovernancePenalty`. If the proposal is accepted, deposits are refunded. - After that proposal is ejected from `ProposalProcessingQueue` and the next element of the queue is evaluated. +* `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the + `ProposalIDs` of proposals that reached `MinDeposit`. Each `EndBlock`, all the proposals + that have reached the end of their voting period are processed. + To process a finished proposal, the application tallies the votes, compute the votes of + each validator and checks if every validator in the valdiator set have voted. + If the proposal is accepted, deposits are refunded. And the pseudocode for the `ProposalProcessingQueue`: ```go - in EndBlock do - - checkProposal() // First call of the recursive function - - - // Recursive function. First call in BeginBlock - func checkProposal() - proposalID = ProposalProcessingQueue.Peek() - if (proposalID == nil) - return - - proposal = load(Governance, <proposalID|'proposal'>) // proposal is a const key - votingProcedure = load(GlobalParams, 'VotingProcedure') - - if (CurrentTime == proposal.VotingStartTime + votingProcedure.VotingPeriod && proposal.CurrentStatus == ProposalStatusActive) + in EndBlock do - // End of voting period, tally + for finishedProposalID in GetAllFinishedProposalIDs(block.Time) + proposal = load(Governance, <proposalID|'proposal'>) // proposal is a const key - ProposalProcessingQueue.pop() - validators = + validators = Keeper.getAllValidators() + tmpValMap := map(sdk.AccAddress)ValidatorGovInfo - - Keeper.getAllValidators() - tmpValMap := map(sdk.Address)ValidatorGovInfo - - // Initiate mapping at 0. Validators that remain at 0 at the end of tally will be punished + // Initiate mapping at 0. This is the amount of shares of the validator's vote that will be overridden by their delegator's votes for each validator in validators - tmpValMap(validator).Minus = 0 - - + tmpValMap(validator.OperatorAddr).Minus = 0 // Tally voterIterator = rangeQuery(Governance, <proposalID|'addresses'>) //return all the addresses that voted on the proposal @@ -189,7 +170,7 @@ And the pseudocode for the `ProposalProcessingQueue`: if (isVal) tmpValMap(voterAddress).Vote = vote - tallyingProcedure = load(GlobalParams, 'TallyingProcedure') + tallyingParam = load(GlobalParams, 'TallyingParam') // Update tally if validator voted they voted for each validator in validators @@ -200,17 +181,16 @@ And the pseudocode for the `ProposalProcessingQueue`: // Check if proposal is accepted or rejected totalNonAbstain := proposal.YesVotes + proposal.NoVotes + proposal.NoWithVetoVotes - if (proposal.Votes.YesVotes/totalNonAbstain > tallyingProcedure.Threshold AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < tallyingProcedure.Veto) + if (proposal.Votes.YesVotes/totalNonAbstain > tallyingParam.Threshold AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < tallyingParam.Veto) // proposal was accepted at the end of the voting period // refund deposits (non-voters already punished) proposal.CurrentStatus = ProposalStatusAccepted for each (amount, depositer) in proposal.Deposits depositer.AtomBalance += amount - else + else // proposal was rejected proposal.CurrentStatus = ProposalStatusRejected store(Governance, <proposalID|'proposal'>, proposal) - checkProposal() ``` diff --git a/docs/spec/governance/transactions.md b/docs/spec/governance/transactions.md index 5ce91284fdc1..68966bcfc45c 100644 --- a/docs/spec/governance/transactions.md +++ b/docs/spec/governance/transactions.md @@ -4,7 +4,7 @@ ### Proposal Submission -Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` +Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` transaction. ```go @@ -24,7 +24,7 @@ type TxGovSubmitProposal struct { * If `MinDeposit` is reached: * Push `proposalID` in `ProposalProcessingQueue` -A `TxGovSubmitProposal` transaction can be handled according to the following +A `TxGovSubmitProposal` transaction can be handled according to the following pseudocode. ```go @@ -32,57 +32,47 @@ pseudocode. // Check if TxGovSubmitProposal is valid. If it is, create proposal // upon receiving txGovSubmitProposal from sender do - - if !correctlyFormatted(txGovSubmitProposal) + + if !correctlyFormatted(txGovSubmitProposal) // check if proposal is correctly formatted. Includes fee payment. throw - - initialDeposit = txGovSubmitProposal.InitialDeposit - if (initialDeposit.Atoms <= 0) OR (sender.AtomBalance < initialDeposit.Atoms) + + initialDeposit = txGovSubmitProposal.InitialDeposit + if (initialDeposit.Atoms <= 0) OR (sender.AtomBalance < initialDeposit.Atoms) // InitialDeposit is negative or null OR sender has insufficient funds throw if (txGovSubmitProposal.Type != ProposalTypePlainText) OR (txGovSubmitProposal.Type != ProposalTypeSoftwareUpgrade) - + sender.AtomBalance -= initialDeposit.Atoms - + + depositParam = load(GlobalParams, 'DepositParam') + proposalID = generate new proposalID proposal = NewProposal() - + proposal.Title = txGovSubmitProposal.Title proposal.Description = txGovSubmitProposal.Description proposal.Type = txGovSubmitProposal.Type proposal.TotalDeposit = initialDeposit - proposal.SubmitBlock = CurrentBlock + proposal.SubmitTime = <CurrentTime> + proposal.DepositEndTime = <CurrentTime>.Add(depositParam.MaxDepositPeriod) proposal.Deposits.append({initialDeposit, sender}) proposal.Submitter = sender proposal.YesVotes = 0 proposal.NoVotes = 0 proposal.NoWithVetoVotes = 0 proposal.AbstainVotes = 0 - - depositProcedure = load(GlobalParams, 'DepositProcedure') - - if (initialDeposit < depositProcedure.MinDeposit) - // MinDeposit is not reached - - proposal.CurrentStatus = ProposalStatusOpen - - else - // MinDeposit is reached - - proposal.CurrentStatus = ProposalStatusActive - proposal.VotingStartBlock = CurrentBlock - ProposalProcessingQueue.push(proposalID) - + proposal.CurrentStatus = ProposalStatusOpen + store(Proposals, <proposalID|'proposal'>, proposal) // Store proposal in Proposals mapping return proposalID ``` ### Deposit -Once a proposal is submitted, if -`Proposal.TotalDeposit < ActiveProcedure.MinDeposit`, Atom holders can send +Once a proposal is submitted, if +`Proposal.TotalDeposit < ActiveParam.MinDeposit`, Atom holders can send `TxGovDeposit` transactions to increase the proposal's deposit. ```go @@ -99,7 +89,7 @@ type TxGovDeposit struct { * If `MinDeposit` is reached: * Push `proposalID` in `ProposalProcessingQueueEnd` -A `TxGovDeposit` transaction has to go through a number of checks to be valid. +A `TxGovDeposit` transaction has to go through a number of checks to be valid. These checks are outlined in the following pseudocode. ```go @@ -108,27 +98,27 @@ These checks are outlined in the following pseudocode. upon receiving txGovDeposit from sender do // check if proposal is correctly formatted. Includes fee payment. - - if !correctlyFormatted(txGovDeposit) + + if !correctlyFormatted(txGovDeposit) throw - + proposal = load(Proposals, <txGovDeposit.ProposalID|'proposal'>) // proposal is a const key, proposalID is variable - if (proposal == nil) + if (proposal == nil) // There is no proposal for this proposalID throw - + if (txGovDeposit.Deposit.Atoms <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit.Atoms) OR (proposal.CurrentStatus != ProposalStatusOpen) - // deposit is negative or null + // deposit is negative or null // OR sender has insufficient funds // OR proposal is not open for deposit anymore throw - depositProcedure = load(GlobalParams, 'DepositProcedure') + depositParam = load(GlobalParams, 'DepositParam') - if (CurrentBlock >= proposal.SubmitBlock + depositProcedure.MaxDepositPeriod) + if (CurrentBlock >= proposal.SubmitBlock + depositParam.MaxDepositPeriod) proposal.CurrentStatus = ProposalStatusClosed else @@ -137,21 +127,21 @@ upon receiving txGovDeposit from sender do proposal.Deposits.append({txGovVote.Deposit, sender}) proposal.TotalDeposit.Plus(txGovDeposit.Deposit) - - if (proposal.TotalDeposit >= depositProcedure.MinDeposit) + + if (proposal.TotalDeposit >= depositParam.MinDeposit) // MinDeposit is reached, vote opens - + proposal.VotingStartBlock = CurrentBlock proposal.CurrentStatus = ProposalStatusActive - ProposalProcessingQueue.push(txGovDeposit.ProposalID) + ProposalProcessingQueue.push(txGovDeposit.ProposalID) store(Proposals, <txGovVote.ProposalID|'proposal'>, proposal) ``` ### Vote -Once `ActiveProcedure.MinDeposit` is reached, voting period starts. From there, -bonded Atom holders are able to send `TxGovVote` transactions to cast their +Once `ActiveParam.MinDeposit` is reached, voting period starts. From there, +bonded Atom holders are able to send `TxGovVote` transactions to cast their vote on the proposal. ```go @@ -167,25 +157,25 @@ vote on the proposal. *Note: Gas cost for this message has to take into account the future tallying of the vote in EndBlocker* -Next is a pseudocode proposal of the way `TxGovVote` transactions are +Next is a pseudocode proposal of the way `TxGovVote` transactions are handled: ```go // PSEUDOCODE // // Check if TxGovVote is valid. If it is, count vote// - + upon receiving txGovVote from sender do - // check if proposal is correctly formatted. Includes fee payment. - - if !correctlyFormatted(txGovDeposit) + // check if proposal is correctly formatted. Includes fee payment. + + if !correctlyFormatted(txGovDeposit) throw - + proposal = load(Proposals, <txGovDeposit.ProposalID|'proposal'>) - if (proposal == nil) + if (proposal == nil) // There is no proposal for this proposalID throw - + if (proposal.CurrentStatus == ProposalStatusActive) diff --git a/docs/ics/ics-030-signed-messages.md b/docs/spec/ics/ics-030-signed-messages.md similarity index 100% rename from docs/ics/ics-030-signed-messages.md rename to docs/spec/ics/ics-030-signed-messages.md diff --git a/docs/spec/mint/begin_block.md b/docs/spec/mint/begin_block.md new file mode 100644 index 000000000000..7588db38b1b2 --- /dev/null +++ b/docs/spec/mint/begin_block.md @@ -0,0 +1,28 @@ +# Begin-Block + +## Inflation + +Inflation occurs at the beginning of each block. + +### NextInflation + +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 desired 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%. + +NextInflation(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) { + inflationRateChangePerYear = (1 - bondedRatio/params.GoalBonded) * params.InflationRateChange + inflationRateChange = inflationRateChangePerYear/hrsPerYr + + // increase the new annual inflation for this next cycle + inflation += inflationRateChange + if inflation > params.InflationMax { + inflation = params.InflationMax + } + if inflation < params.InflationMin { + inflation = params.InflationMin + } + + return inflation diff --git a/docs/spec/mint/state.md b/docs/spec/mint/state.md new file mode 100644 index 000000000000..98e8e63dd237 --- /dev/null +++ b/docs/spec/mint/state.md @@ -0,0 +1,31 @@ +## State + +### Minter + +The minter is a space for holding current inflation information. + + - Minter: `0x00 -> amino(minter)` + +```golang +type Minter struct { + InflationLastTime time.Time // block time which the last inflation was processed + Inflation sdk.Dec // current annual inflation rate +} +``` + +### Params + +Minting params are held in the global params store. + + - Params: `mint/params -> amino(params)` + +```golang +type Params struct { + MintDenom string // type of coin to mint + InflationRateChange sdk.Dec // maximum annual change in inflation rate + InflationMax sdk.Dec // maximum inflation rate + InflationMin sdk.Dec // minimum inflation rate + GoalBonded sdk.Dec // goal of percent bonded atoms +} +``` + diff --git a/docs/spec/params/README.md b/docs/spec/params/README.md new file mode 100644 index 000000000000..d9c8b9221fb3 --- /dev/null +++ b/docs/spec/params/README.md @@ -0,0 +1,20 @@ +# Params module specification + +## Abstract + +Package params provides a globally available parameter store. + +There are two main types, Keeper and Subspace. Subspace is an isolated namespace for a +paramstore, where keys are prefixed by preconfigured spacename. Keeper has a +permission to access all existing spaces. + +Subspace can be used by the individual keepers, who needs a private parameter store +that the other keeper cannot modify. Keeper can be used by the Governance keeper, +who need to modify any parameter in case of the proposal passes. + +The following contents explains how to use params module for master and user modules. + +## Contents + +1. [Keeper](keeper.md) +1. [Subspace](subspace.md) diff --git a/docs/spec/params/keeper.md b/docs/spec/params/keeper.md new file mode 100644 index 000000000000..fe376911edf9 --- /dev/null +++ b/docs/spec/params/keeper.md @@ -0,0 +1,17 @@ +# Keeper + +In the app initialization stage, `Keeper.Subspace(Paramspace)` is passed to the user modules, and the subspaces are stored in `Keeper.spaces`. Later it can be retrieved with `Keeper.GetSubspace`, so the keepers holding `Keeper` can access to any subspace. For example, Gov module can take `Keeper` as its argument and modify parameter of any subspace when a `ParameterChangeProposal` is accepted. + +```go +type MasterKeeper struct { + pk params.Keeper +} + +func (k MasterKeeper) SetParam(ctx sdk.Context, space string, key string, param interface{}) { + space, ok := k.ps.GetSubspace(space) + if !ok { + return + } + space.Set(ctx, key, param) +} +``` diff --git a/docs/spec/params/subspace.md b/docs/spec/params/subspace.md new file mode 100644 index 000000000000..378177898273 --- /dev/null +++ b/docs/spec/params/subspace.md @@ -0,0 +1,76 @@ +# Subspace + +## Basic Usage + +First, declare parameter space and parameter keys for the module. Then include params.Subspace in the keeper. Since we prefix the keys with the spacename, it is recommended to use the same name with the module's. + +```go +const ( + DefaultParamspace = "mymodule" +) + +const ( + KeyParameter1 = "myparameter1" + KeyParameter2 = "myparameter2" +) + +type Keeper struct { + cdc *wire.Codec + key sdk.StoreKey + + ps params.Subspace +} +``` + +Pass a params.Subspace to NewKeeper with DefaultParamSubspace (or another) + +```go +app.myKeeper = mymodule.NewKeeper(cdc, key, app.paramStore.SubStore(mymodule.DefaultParamspace)) +``` + +`NewKeeper` should register a `TypeTable`, which defines a map from parameter keys from types. + +```go +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, space params.Subspace) Keeper { + return Keeper { + cdc: cdc, + key: key, + ps: space.WithTypeTable(ParamTypeTable()), + } +} +``` + +Now we can access to the paramstore using Paramstore Keys + +```go +var param MyStruct +k.ps.Get(KeyParameter1, ¶m) +k.ps.Set(KeyParameter2, param) +``` + +# Genesis Usage + +Declare a struct for parameters and make it implement params.ParamSet. It will then be able to be passed to SetParamSet. + +```go +type MyParams struct { + Parameter1 uint64 + Parameter2 string +} + +// Implements params.ParamSet +// KeyValuePairs must return the list of (ParamKey, PointerToTheField) +func (p *MyParams) KeyValuePairs() params.KeyValuePairs { + return params.KeyFieldPairs { + {KeyParameter1, &p.Parameter1}, + {KeyParameter2, &p.Parameter2}, + } +} + +func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { + k.ps.SetParamSet(ctx, &data.params) +} +``` + +The method is pointer receiver because there could be a case that we read from the store and set the result to the struct. + diff --git a/docs/spec/slashing/begin-block.md b/docs/spec/slashing/begin-block.md index 375e191859a4..43691b38d211 100644 --- a/docs/spec/slashing/begin-block.md +++ b/docs/spec/slashing/begin-block.md @@ -93,25 +93,27 @@ for val in block.Validators: index := signInfo.IndexOffset % SIGNED_BLOCKS_WINDOW signInfo.IndexOffset++ - previous = SigningBitArray.Get(val.Address, index) + previous = MissedBlockBitArray.Get(val.Address, index) // update counter if array has changed - if previous and val in block.AbsentValidators: - SigningBitArray.Set(val.Address, index, false) - signInfo.SignedBlocksCounter-- - else if !previous and val not in block.AbsentValidators: - SigningBitArray.Set(val.Address, index, true) - signInfo.SignedBlocksCounter++ + if !previous and val in block.AbsentValidators: + MissedBlockBitArray.Set(val.Address, index, true) + signInfo.MissedBlocksCounter++ + else if previous and val not in block.AbsentValidators: + MissedBlockBitArray.Set(val.Address, index, false) + signInfo.MissedBlocksCounter-- // else previous == val not in block.AbsentValidators, no change // validator must be active for at least SIGNED_BLOCKS_WINDOW // before they can be automatically unbonded for failing to be // included in 50% of the recent LastCommits minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW - minSigned = SIGNED_BLOCKS_WINDOW / 2 - if height > minHeight AND signInfo.SignedBlocksCounter < minSigned: + maxMissed = SIGNED_BLOCKS_WINDOW / 2 + if height > minHeight AND signInfo.MissedBlocksCounter > maxMissed: signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION - + signInfo.IndexOffset = 0 + signInfo.MissedBlocksCounter = 0 + clearMissedBlockBitArray() slash & unbond the validator SigningInfo.Set(val.Address, signInfo) diff --git a/docs/spec/slashing/hooks.md b/docs/spec/slashing/hooks.md index 36dde61f940c..1888c1d2f847 100644 --- a/docs/spec/slashing/hooks.md +++ b/docs/spec/slashing/hooks.md @@ -12,6 +12,17 @@ and `SlashedSoFar` of `0`: ``` onValidatorBonded(address sdk.ValAddress) + signingInfo, found = getValidatorSigningInfo(address) + if !found { + signingInfo = ValidatorSigningInfo { + StartHeight : CurrentHeight, + IndexOffset : 0, + JailedUntil : time.Unix(0, 0), + MissedBloskCounter : 0 + } + setValidatorSigningInfo(signingInfo) + } + slashingPeriod = SlashingPeriod{ ValidatorAddr : address, StartHeight : CurrentHeight, diff --git a/docs/spec/slashing/state.md b/docs/spec/slashing/state.md index ae426db7b392..9a9a188ff2d2 100644 --- a/docs/spec/slashing/state.md +++ b/docs/spec/slashing/state.md @@ -17,18 +17,18 @@ Information about validator activity is tracked in a `ValidatorSigningInfo`. It is indexed in the store as follows: - SigningInfo: ` 0x01 | ValTendermintAddr -> amino(valSigningInfo)` -- SigningBitArray: ` 0x02 | ValTendermintAddr | LittleEndianUint64(signArrayIndex) -> VarInt(didSign)` +- MissedBlocksBitArray: ` 0x02 | ValTendermintAddr | LittleEndianUint64(signArrayIndex) -> VarInt(didMiss)` The first map allows us to easily lookup the recent signing info for a validator, according to the Tendermint validator address. The second map acts as -a bit-array of size `SIGNED_BLOCKS_WINDOW` that tells us if the validator signed for a given index in the bit-array. +a bit-array of size `SIGNED_BLOCKS_WINDOW` that tells us if the validator missed the block for a given index in the bit-array. The index in the bit-array is given as little endian uint64. The result is a `varint` that takes on `0` or `1`, where `0` indicates the -validator did not sign the corresponding block, and `1` indicates they did. +validator did not miss (did sign) the corresponding block, and `1` indicates they missed the block (did not sign). -Note that the SigningBitArray is not explicitly initialized up-front. Keys are +Note that the MissedBlocksBitArray is not explicitly initialized up-front. Keys are added as we progress through the first `SIGNED_BLOCKS_WINDOW` blocks for a newly bonded validator. @@ -40,7 +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 - SignedBlocksCounter int64 // Running counter of signed blocks + MissedBlocksCounter int64 // Running counter of missed blocks } ``` @@ -49,7 +49,7 @@ 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 -* `SignedBlocksCounter` is a counter kept to avoid unnecessary array reads. `SignedBlocksBitArray.Sum() == SignedBlocksCounter` always. +* `MissedBlocksCounter` is a counter kept to avoid unnecessary array reads. `MissedBlocksBitArray.Sum() == MissedBlocksCounter` always. ## Slashing Period diff --git a/docs/spec/slashing/transactions.md b/docs/spec/slashing/transactions.md index be33ee096539..c33e96047d61 100644 --- a/docs/spec/slashing/transactions.md +++ b/docs/spec/slashing/transactions.md @@ -25,10 +25,6 @@ handleMsgUnjail(tx TxUnjail) if block time < info.JailedUntil fail with "Validator still jailed, cannot unjail until period has expired" - // Update the start height so the validator won't be immediately unbonded again - info.StartHeight = BlockHeight - setValidatorSigningInfo(info) - validator.Jailed = false setValidator(validator) 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 new file mode 100644 index 0000000000000000000000000000000000000000..12e7d5906e58b2efa6f94679c342d341854d2f6f GIT binary patch literal 160589 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®ryLZ+Am4HXXm3kv{(f^rp^LK&zv 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�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%0Df73crk!+s{UEn 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%0AZb%{<Bb3>LW*Zsh4`JVVJ0J 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%0A0A;C4NhWOH7?FR~99Yj37|Lk`Vu0bOl4 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�jV-%FZ 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 literal 0 HcmV?d00001 diff --git a/docs/spec/spec-proposals/f1-fee-distribution/f1_fee_distr.tex b/docs/spec/spec-proposals/f1-fee-distribution/f1_fee_distr.tex new file mode 100644 index 000000000000..969e77ec945e --- /dev/null +++ b/docs/spec/spec-proposals/f1-fee-distribution/f1_fee_distr.tex @@ -0,0 +1,135 @@ +\documentclass[]{article} +\usepackage{hyperref} + +%opening +\title{F1 Fee Distribution Draft-00} +\author{Dev Ojha} + +\begin{document} + +\maketitle + +\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. +\end{abstract} + +\section{F1 Fee Distribution} + +In a proof of stake model, each validator has an associated stake, with delegators each contributing some amount to a validator's stake. +The validator is rewarded transaction fees every block for the service they are providing the network. +In the F1 distribution, each validator is permitted to take a commission from the fees they receive, and the remaining fees should be evenly distributed across the validator's delegators, such that every delegator the percentage of the validator's stake that came from the delegator is the proportion of that validator's remaining tx fees which they are getting. +Iterating over all delegators for every validator each block is too expensive for a blockchain environment. +Instead there is an explicit withdraw fees action which a delegator can take, which will give the delegator the same total amount of fees as though they were receiving it every block. + +Suppose a delegator delegates $x$ stake to a validator at block $h$. +Let the amount of stake the validator has at block $i$ be $s_i$ and the amount of fees they receive at this height be $f_i$. +Then if a delegator contributing $x$ stake decides to withdraw at block $n$, the rewards they receive is +$$\sum_{i = h}^{n} \frac{x}{s_i}f_i = x \sum_{i = h}^{n} \frac{f_i}{s_i}$$ + +However $s_i$ will not change every block. +It only changes if the validator gets slashed, or if someone new has bonded or unbonded. +Handling slashes is relegated to \autoref{ssec:slashing}. +Define a period as the set of blocks between two changes in a given validator's total stake. +A new period begins every time that validator's total stake changes. +The above iteration will be converted to iteration over periods. +Let the total amount of stake for the validator in period $p$ be $n_p$. +Let $T_p$ be the total fees this validator accrued within this period. +Let $h$ be the start of period $p_{init}$, and height $n$ be the end of $p_{final}$. +It follows that +$$x \sum_{i = h}^{n} \frac{f_i}{s_i} = x \sum_{p = p_{init}}^{p_{final}} \frac{T_p}{n_p}$$ + +Let $p_0$ represent the period from when the validator first bonded until the first change to the validators stake. +The central idea to the F1 model is that at the end of the $k$th period, the following is stored at a state location indexable by $k$: $\sum_{i=0}^{k}\frac{T_i}{n_i}$. +When a delegator wants to delegate or withdraw their reward, they first create a new entry in state to end the current period. Let the index of the current period be $f$. +Then this entry is created using the previous entry as follows: $$\sum_{i=0}^{f}\frac{T_i}{n_i} = \sum_{i=0}^{f-1}\frac{T_i}{n_i} + \frac{T_f}{n_f} = entry_{f-1} + \frac{T_f}{n_f}$$ +Where $T_f$ is the fees the validator has accrued in period $f$, and $n_f$ is the validators total amount of stake in period $f$. + +The withdrawer's delegation object has the index $k$ for the period which they started accruing fees for. +Thus the reward they should receive when withdrawing is: + +$$x\left(entry_f - entry_k\right) = x\left(\left(\sum_{i=0}^{f}\frac{T_i}{n_i}\right) - \left(\sum_{i=0}^{k}\frac{T_i}{n_i}\right)\right) = x \sum_{i = k}^{f} \frac{T_i}{n_i}$$ + +The first summation is the state entry for $f$, and the second sum is the state entry at $k$. +It is clear from the equations that this payout mechanism maintains correctness, and required no iterations. + +$T_f$ is a separate variable in state for the amount of fees this validator has accrued since the last update to its power. +This variable is incremented at every block by however much fees this validator received that block. +On the update to the validators power, this variable is used to create the entry in state at $f$. + +This fee distribution proposal is agnostic to how all of the blocks fees are divied up between validators. +This creates many nice properties, for example only rewarding validators who signed that block. + +\section{Additional add-ons} +\subsection{Commission Rates} +Commission rates are the idea that a validator can take a fixed $x\%$ cut of all of their received fees, before redistributing evenly to the constituent delegators. +This can easily be done as follows: + +In block $h$ a validator receives $f_h$ fees. +Instead of incrementing that validators ``total accrued fees this period variable" by $f_h$, it is instead incremented by $(1 - commission\_rate) * f_p$. +Then $commission\_rate * f_p$ is deposited directly to the validator. +This scheme allow for updates to a validator's commission rate every block if desired. + +\subsection{Slashing} +\label{ssec:slashing} +Slashing is distinct from withdrawals, since not only does it lower the validators total amount of stake, but it also lowers each of its delegator's stake by a fixed percentage. +Since noone 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. +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. +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 withdrawer must pay for due to the gas. + +\subsection{Inflation} +Inflation is the idea that we want every staked coin to grow in value as time progresses. Each block, every staked token should each be rewarded $x$ staking tokens as inflation, where $x$ is calculated from function which takes state and the block information as input. This also allows for many seemless upgrade's to $x$'s algorithm. This can be added efficiently into the fee distribution model as follows: + +Make each block have an inflation number, by which every staked token should produce $x$ additional staking tokens. In state there is a variable for the sum of all such inflation numbers. Then each period will store this total inflation sum in addition to $\sum_{i=0}^{end}\frac{T_i}{n_i}$. When withdrawing perform a subtraction on the inflation sums at the end and at the start to see how many new staking tokens to produce per staked token. + +This works great in the model where the inflation rate should be dynamic each block, but apply the same to each validator. Inflation creation can trivially be epoched as long as inflation isn't required within the epoch, through changes to the $x$ function. + +Note that this process is extremely efficient. + +The above can be trivially amended if we want inflation to proceed differently for different validators each block. (e.g. depending on who was offline) It can also be made to be easily adapted in a live chain. It is unclear if either of these two are more desirable settings. + +\subsection{Delegation updates} +Updating your delegation amount is equivalent to withdrawing earned rewards and a fully independent new delegation occuring in the same block. + +\subsection{Jailing / being kicked out of the validator set} +This basically requires no change. In each block you only iterate over the currently bonded validators. 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. +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. + +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} + +This is an extremely simple scheme with many nice benefits. +\begin{itemize} + \item The overhead per block is a simple iteration over the bonded validator set, which occurs anyway. (Thus it can be implemented ``for-free" with an optimized code-base) + \item Withdrawing earned fees only requires iterating over slashes since when you bonded. (Which is a negligible iteration) + \item There are no approximations in any of the calculations. (modulo minor errata resulting from fixed precision decimals used in divisions) + \item Supports arbitrary inflation models. (Thus could even vary upon block signers) + \item Supports arbitrary fee distribution amongst the validator set. (Thus can account for things like only online validators get fees, which has important incentivization impacts) + \item The above two can change on a live chain with no issues. + \item Validator commission rates can be changed every block + \item The simplicity of this scheme lends itself well to implementation +\end{itemize} + +Thus this scheme has efficiency improvements, simplicity improvements, and expressiveness improvements over the currently proposed schemes. With a correct fee distribution amongst the validator set, this solves the existing problem where one could withhold their signature for risk-free gain. + +\end{document} diff --git a/docs/spec/staking/end_block.md b/docs/spec/staking/end_block.md index 2502baf0dc00..e1d769ed4632 100644 --- a/docs/spec/staking/end_block.md +++ b/docs/spec/staking/end_block.md @@ -1,5 +1,24 @@ # 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 +``` + ## Validator Set Changes The Tendermint validator set may be updated by state transitions that run at @@ -46,4 +65,4 @@ redelegationQueue(currTime time.Time): for all redelegations whose CompleteTime < currTime: removeRedelegation(redelegation) return -``` \ No newline at end of file +``` diff --git a/docs/spec/staking/hooks.md b/docs/spec/staking/hooks.md index bcc496e3d6e0..3644155a67ad 100644 --- a/docs/spec/staking/hooks.md +++ b/docs/spec/staking/hooks.md @@ -5,12 +5,12 @@ The staking module allow for the following hooks to be registered with staking e ``` golang // event hooks for staking validator object type StakingHooks interface { - OnValidatorCreated(ctx Context, address ValAddress) // called when a validator is created - OnValidatorCommissionChange(ctx Context, address ValAddress) // called when a validator's commission is modified - OnValidatorRemoved(ctx Context, address ValAddress) // called when a validator is deleted + 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) // called when a validator begins unbonding + 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 diff --git a/docs/spec/staking/state.md b/docs/spec/staking/state.md index 9454aca7ddd4..6568293cc3f0 100644 --- a/docs/spec/staking/state.md +++ b/docs/spec/staking/state.md @@ -12,10 +12,6 @@ inflation information, etc. type Pool struct { LooseTokens int64 // tokens not associated with any bonded validator BondedTokens int64 // reserve of bonded tokens - InflationLastTime int64 // block which the last inflation was processed // TODO make time - Inflation sdk.Dec // current annual inflation rate - - DateLastCommissionReset int64 // unix timestamp for last commission accounting reset (daily) } ``` @@ -28,11 +24,6 @@ overall functioning of the stake module. ```golang type Params struct { - InflationRateChange sdk.Dec // maximum annual change in inflation rate - InflationMax sdk.Dec // maximum inflation rate - InflationMin sdk.Dec // minimum inflation rate - GoalBonded sdk.Dec // Goal of percent bonded atoms - MaxValidators uint16 // maximum number of validators BondDenom string // bondable coin denomination } diff --git a/docs/spec/staking/transactions.md b/docs/spec/staking/transactions.md index 74ea67c85dcc..ee5b2dc938d8 100644 --- a/docs/spec/staking/transactions.md +++ b/docs/spec/staking/transactions.md @@ -180,7 +180,7 @@ startUnbonding(tx TxStartUnbonding): validator = updateValidator(validator) - if validator.DelegatorShares == 0 { + if validator.Status == Unbonded && validator.DelegatorShares == 0 { removeValidator(validator.Operator) return @@ -189,8 +189,7 @@ startUnbonding(tx TxStartUnbonding): ### TxRedelegation The redelegation command allows delegators to instantly switch validators. Once -the unbonding period has passed, the redelegation must be completed with -txRedelegationComplete. +the unbonding period has passed, the redelegation is automatically completed in the EndBlocker. ```golang type TxRedelegate struct { diff --git a/examples/basecoin/cmd/basecoind/main.go b/examples/basecoin/cmd/basecoind/main.go deleted file mode 100644 index 36219354449b..000000000000 --- a/examples/basecoin/cmd/basecoind/main.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "encoding/json" - "io" - "os" - - "github.com/cosmos/cosmos-sdk/baseapp" - gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init" - - "github.com/cosmos/cosmos-sdk/examples/basecoin/app" - "github.com/cosmos/cosmos-sdk/server" - "github.com/spf13/cobra" - "github.com/spf13/viper" - 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" -) - -func main() { - cdc := app.MakeCodec() - ctx := server.NewDefaultContext() - - rootCmd := &cobra.Command{ - Use: "basecoind", - Short: "Basecoin Daemon (server)", - PersistentPreRunE: server.PersistentPreRunEFn(ctx), - } - - appInit := server.DefaultAppInit - rootCmd.AddCommand(gaiaInit.InitCmd(ctx, cdc, appInit)) - rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, appInit)) - - server.AddCommands(ctx, cdc, rootCmd, appInit, - 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) - } -} - -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) (json.RawMessage, []tmtypes.GenesisValidator, error) { - bapp := app.NewBasecoinApp(logger, db) - return bapp.ExportAppStateAndValidators() -} diff --git a/examples/democoin/cmd/democoind/main.go b/examples/democoin/cmd/democoind/main.go deleted file mode 100644 index 7f8c7c54d17f..000000000000 --- a/examples/democoin/cmd/democoind/main.go +++ /dev/null @@ -1,88 +0,0 @@ -package main - -import ( - "encoding/json" - "io" - "os" - - "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/examples/democoin/app" - "github.com/cosmos/cosmos-sdk/server" -) - -// init parameters -var CoolAppInit = server.AppInit{ - AppGenState: CoolAppGenState, - AppGenTx: server.SimpleAppGenTx, -} - -// coolGenAppParams sets up the app_state and appends the cool app state -func CoolAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { - appState, err = server.SimpleAppGenState(cdc, 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 -} - -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) (json.RawMessage, []tmtypes.GenesisValidator, error) { - dapp := app.NewDemocoinApp(logger, db) - return dapp.ExportAppStateAndValidators() -} - -func main() { - cdc := app.MakeCodec() - ctx := server.NewDefaultContext() - - rootCmd := &cobra.Command{ - Use: "democoind", - Short: "Democoin Daemon (server)", - PersistentPreRunE: server.PersistentPreRunEFn(ctx), - } - - rootCmd.AddCommand(gaiaInit.InitCmd(ctx, cdc, CoolAppInit)) - rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, CoolAppInit)) - - server.AddCommands(ctx, cdc, rootCmd, CoolAppInit, - 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/scripts/Makefile b/scripts/Makefile new file mode 100644 index 000000000000..9abc497e45d4 --- /dev/null +++ b/scripts/Makefile @@ -0,0 +1,54 @@ +### +# Find OS and Go environment +# GO contains the Go binary +# FS contains the OS file separator +### +ifeq ($(OS),Windows_NT) + GO := $(shell where go.exe 2> NUL) + FS := \\ +else + GO := $(shell command -v go 2> /dev/null) + FS := / +endif + +ifeq ($(GO),) + $(error could not find go. Is it in PATH? $(GO)) +endif + +GOPATH ?= $(shell $(GO) env GOPATH) +GITHUBDIR := $(GOPATH)$(FS)src$(FS)github.com + +### +# Functions +### + +go_get = $(if $(findstring Windows_NT,$(OS)),\ +IF NOT EXIST $(GITHUBDIR)$(FS)$(1)$(FS) ( mkdir $(GITHUBDIR)$(FS)$(1) ) else (cd .) &\ +IF NOT EXIST $(GITHUBDIR)$(FS)$(1)$(FS)$(2)$(FS) ( cd $(GITHUBDIR)$(FS)$(1) && git clone https://github.com/$(1)/$(2) ) else (cd .) &\ +,\ +mkdir -p $(GITHUBDIR)$(FS)$(1) &&\ +(test ! -d $(GITHUBDIR)$(FS)$(1)$(FS)$(2) && cd $(GITHUBDIR)$(FS)$(1) && git clone https://github.com/$(1)/$(2)) || true &&\ +)\ +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 +### +all: get_tools +get_tools: dep gometalinter statik + +dep: + $(call go_get,golang,dep,22125cfaa6ddc71e145b1535d4b7ee9744fefff2) + cd $(GITHUBDIR)$(FS)golang$(FS)dep$(FS)cmd$(FS)dep && $(GO) install + +#v2.0.11 +gometalinter: + $(call go_install,alecthomas,gometalinter,17a7ffa42374937bfecabfb8d2efbd4db0c26741) + +statik: + $(call go_install,rakyll,statik,v0.1.5) + +.PHONY: all get_tools dep gometalinter statik + diff --git a/scripts/get_tools.sh b/scripts/get_tools.sh deleted file mode 100755 index 79ab32deca5d..000000000000 --- a/scripts/get_tools.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash -set -e - -# This file downloads all of the binary dependencies we have, and checks out a -# specific git hash. -# -# repos it installs: -# github.com/golang/dep/cmd/dep -# gopkg.in/alecthomas/gometalinter.v2 -# github.com/rakyll/statiik - -## check if GOPATH is set -if [ -z ${GOPATH+x} ]; then - echo "please set GOPATH (https://github.com/golang/go/wiki/SettingGOPATH)" - exit 1 -fi - -mkdir -p "$GOPATH/src/github.com" -cd "$GOPATH/src/github.com" || exit 1 - -installFromGithub() { - repo=$1 - commit=$2 - # optional - subdir=$3 - echo "--> Installing $repo ($commit)..." - if [ ! -d "$repo" ]; then - mkdir -p "$repo" - git clone "https://github.com/$repo.git" "$repo" - fi - if [ ! -z ${subdir+x} ] && [ ! -d "$repo/$subdir" ]; then - echo "ERROR: no such directory $repo/$subdir" - exit 1 - fi - pushd "$repo" && \ - git fetch origin && \ - git checkout -q "$commit" && \ - if [ ! -z ${subdir+x} ]; then cd "$subdir" || exit 1; fi && \ - go install && \ - if [ ! -z ${subdir+x} ]; then cd - || exit 1; fi && \ - popd || exit 1 - echo "--> Done" - echo "" -} - -installFromGithub golang/dep 22125cfaa6ddc71e145b1535d4b7ee9744fefff2 cmd/dep -## gometalinter v2.0.11 -installFromGithub alecthomas/gometalinter 17a7ffa42374937bfecabfb8d2efbd4db0c26741 -installFromGithub rakyll/statik v0.1.5 \ No newline at end of file diff --git a/scripts/import-export-sim.sh b/scripts/import-export-sim.sh new file mode 100755 index 000000000000..8ace8e0d56c8 --- /dev/null +++ b/scripts/import-export-sim.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +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 + +echo "Running multi-seed import-export simulation with seeds ${seeds[@]}" +echo "Running $blocks blocks per seed" +echo "Edit scripts/import-export-sim.sh to add new seeds. Keeping parameters in the file makes failures easy to reproduce." +echo "This script will kill all sub-simulations on SIGINT/SIGTERM (i.e. Ctrl-C)." + +trap 'kill $(jobs -pr)' SIGINT SIGTERM + +tmpdir=$(mktemp -d) +echo "Using temporary log directory: $tmpdir" + +sim() { + seed=$1 + echo "Running import/export Gaia simulation with seed $seed. This may take awhile!" + file="$tmpdir/gaia-simulation-seed-$seed-date-$(date -Iseconds -u).stdout" + echo "Writing stdout to $file..." + go test ./cmd/gaia/app -run TestGaiaImportExport -SimulationEnabled=true -SimulationNumBlocks=$blocks \ + -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=$seed -v -timeout 24h > $file +} + +i=0 +pids=() +for seed in ${seeds[@]}; do + sim $seed & + pids[${i}]=$! + i=$(($i+1)) + sleep 10 # start in order, nicer logs +done + +echo "Simulation processes spawned, waiting for completion..." + +code=0 + +i=0 +for pid in ${pids[*]}; do + wait $pid + last=$? + seed=${seeds[${i}]} + if [ $last -ne 0 ] + then + echo "Import/export simulation with seed $seed failed!" + code=1 + else + echo "Import/export simulation with seed $seed OK" + fi + i=$(($i+1)) +done + +exit $code diff --git a/scripts/multisim.sh b/scripts/multisim.sh index 8ffa338b84e5..2376eb3a8f53 100755 --- a/scripts/multisim.sh +++ b/scripts/multisim.sh @@ -1,6 +1,7 @@ #!/bin/bash -seeds=(1 2 4 7 9 20 32 123 124 582 1893 2989 3012 4728 37827 981928 87821 891823782 989182 89182391) +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 echo "Running multi-seed simulation with seeds ${seeds[@]}" @@ -16,7 +17,7 @@ echo "Using temporary log directory: $tmpdir" sim() { seed=$1 echo "Running full Gaia simulation with seed $seed. This may take awhile!" - file="$tmpdir/gaia-simulation-seed-$seed-date-$(date -Iseconds -u).stdout" + 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 TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=$blocks \ -SimulationVerbose=true -SimulationCommit=true -SimulationSeed=$seed -v -timeout 24h > $file diff --git a/server/config/config.go b/server/config/config.go index bd0d966e35b4..239097b13737 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -35,16 +35,3 @@ func (c *Config) MinimumFees() sdk.Coins { // DefaultConfig returns server's default configuration. func DefaultConfig() *Config { return &Config{BaseConfig{MinFees: defaultMinimumFees}} } - -//_____________________________________________________________________ - -// Configuration structure for command functions that share configuration. -// For example: init, init gen-tx and testnet commands need similar input and run the same code - -// Storage for init gen-tx command input parameters -type GenTx struct { - Name string - CliRoot string - Overwrite bool - IP string -} diff --git a/server/init.go b/server/init.go index 091ffa9484e6..75e13f452653 100644 --- a/server/init.go +++ b/server/init.go @@ -2,111 +2,61 @@ package server import ( "encoding/json" + "errors" "fmt" "github.com/cosmos/cosmos-sdk/crypto/keys" - "github.com/pkg/errors" - "github.com/spf13/pflag" "github.com/tendermint/tendermint/crypto" - dbm "github.com/tendermint/tendermint/libs/db" - tmtypes "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/types" clkeys "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/codec" - serverconfig "github.com/cosmos/cosmos-sdk/server/config" sdk "github.com/cosmos/cosmos-sdk/types" + tmtypes "github.com/tendermint/tendermint/types" ) -//Parameter names, for init gen-tx command -var ( - FlagName = "name" - FlagClientHome = "home-client" - FlagOWK = "owk" -) - -//parameter names, init command -var ( - FlagOverwrite = "overwrite" - FlagWithTxs = "with-txs" - FlagIP = "ip" - FlagChainID = "chain-id" -) - -// genesis piece structure for creating combined genesis -type GenesisTx struct { - NodeID string `json:"node_id"` - IP string `json:"ip"` - Validator tmtypes.GenesisValidator `json:"validator"` - AppGenTx json.RawMessage `json:"app_gen_tx"` -} - -// Storage for init command input parameters -type InitConfig struct { - ChainID string - GenTxs bool - GenTxsDir string - Overwrite bool -} - -//________________________________________________________________________________________ - -//_____________________________________________________________________ - // Core functionality passed from the application to the server init command type AppInit struct { - - // flags required for application init functions - FlagsAppGenState *pflag.FlagSet - FlagsAppGenTx *pflag.FlagSet - - // create the application genesis tx - AppGenTx func(cdc *codec.Codec, pk crypto.PubKey, genTxConfig serverconfig.GenTx) ( - appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) - // AppGenState creates the core parameters initialization. It takes in a // pubkey meant to represent the pubkey of the validator of this machine. - AppGenState func(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) + AppGenState func(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( + appState json.RawMessage, err error) +} + +// SimpleGenTx is a simple genesis tx +type SimpleGenTx struct { + Addr sdk.AccAddress `json:"addr"` } //_____________________________________________________________________ // simple default application init var DefaultAppInit = AppInit{ - AppGenTx: SimpleAppGenTx, AppGenState: SimpleAppGenState, } -// simple genesis tx -type SimpleGenTx struct { - Addr sdk.AccAddress `json:"addr"` -} - // Generate a genesis transaction -func SimpleAppGenTx(cdc *codec.Codec, pk crypto.PubKey, genTxConfig serverconfig.GenTx) ( - appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { - +func SimpleAppGenTx(cdc *codec.Codec, pk crypto.PubKey) ( + appGenTx, cliPrint json.RawMessage, validator types.GenesisValidator, err error) { var addr sdk.AccAddress var secret string addr, secret, err = GenerateCoinKey() if err != nil { return } - var bz []byte - simpleGenTx := SimpleGenTx{addr} + simpleGenTx := SimpleGenTx{Addr: addr} bz, err = cdc.MarshalJSON(simpleGenTx) if err != nil { return } appGenTx = json.RawMessage(bz) - mm := map[string]string{"secret": secret} bz, err = cdc.MarshalJSON(mm) if err != nil { return } cliPrint = json.RawMessage(bz) - validator = tmtypes.GenesisValidator{ PubKey: pk, Power: 10, @@ -115,15 +65,16 @@ func SimpleAppGenTx(cdc *codec.Codec, pk crypto.PubKey, genTxConfig serverconfig } // create the genesis app state -func SimpleAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { +func SimpleAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( + appState json.RawMessage, err error) { if len(appGenTxs) != 1 { err = errors.New("must provide a single genesis transaction") return } - var genTx SimpleGenTx - err = cdc.UnmarshalJSON(appGenTxs[0], &genTx) + var tx SimpleGenTx + err = cdc.UnmarshalJSON(appGenTxs[0], &tx) if err != nil { return } @@ -138,7 +89,7 @@ func SimpleAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState } ] }] -}`, genTx.Addr)) +}`, tx.Addr)) return } @@ -167,7 +118,7 @@ func GenerateCoinKey() (sdk.AccAddress, string, error) { func GenerateSaveCoinKey(clientRoot, keyName, keyPass string, overwrite bool) (sdk.AccAddress, string, error) { // get the keystore from the client - keybase, err := clkeys.GetKeyBaseFromDir(clientRoot) + keybase, err := clkeys.GetKeyBaseFromDirWithWritePerm(clientRoot) if err != nil { return sdk.AccAddress([]byte{}), "", err } @@ -176,7 +127,8 @@ func GenerateSaveCoinKey(clientRoot, keyName, keyPass string, overwrite bool) (s if !overwrite { _, err := keybase.Get(keyName) if err == nil { - return sdk.AccAddress([]byte{}), "", errors.New("key already exists, overwrite is disabled") + return sdk.AccAddress([]byte{}), "", fmt.Errorf( + "key already exists, overwrite is disabled (clientRoot: %s)", clientRoot) } } diff --git a/server/mock/app.go b/server/mock/app.go index abdec6be5081..dd8f3f5aba7a 100644 --- a/server/mock/app.go +++ b/server/mock/app.go @@ -3,17 +3,15 @@ package mock import ( "encoding/json" "fmt" + "github.com/tendermint/tendermint/types" "path/filepath" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" - tmtypes "github.com/tendermint/tendermint/types" bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" - gc "github.com/cosmos/cosmos-sdk/server/config" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -105,7 +103,8 @@ func InitChainer(key sdk.StoreKey) func(sdk.Context, abci.RequestInitChain) abci // AppGenState can be passed into InitCmd, returns a static string of a few // key-values that can be parsed by InitChainer -func AppGenState(_ *codec.Codec, _ []json.RawMessage) (appState json.RawMessage, err error) { +func AppGenState(_ *codec.Codec, _ types.GenesisDoc, _ []json.RawMessage) (appState json. + RawMessage, err error) { appState = json.RawMessage(`{ "values": [ { @@ -122,18 +121,8 @@ func AppGenState(_ *codec.Codec, _ []json.RawMessage) (appState json.RawMessage, } // AppGenStateEmpty returns an empty transaction state for mocking. -func AppGenStateEmpty(_ *codec.Codec, _ []json.RawMessage) (appState json.RawMessage, err error) { +func AppGenStateEmpty(_ *codec.Codec, _ types.GenesisDoc, _ []json.RawMessage) ( + appState json.RawMessage, err error) { appState = json.RawMessage(``) return } - -// Return a validator, not much else -func AppGenTx(_ *codec.Codec, pk crypto.PubKey, genTxConfig gc.GenTx) ( - appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { - - validator = tmtypes.GenesisValidator{ - PubKey: pk, - Power: 10, - } - return -} diff --git a/server/mock/app_test.go b/server/mock/app_test.go index 05ec86521943..a5f2a078b263 100644 --- a/server/mock/app_test.go +++ b/server/mock/app_test.go @@ -1,6 +1,7 @@ package mock import ( + "github.com/tendermint/tendermint/types" "testing" "github.com/stretchr/testify/require" @@ -20,7 +21,7 @@ func TestInitApp(t *testing.T) { require.NoError(t, err) // initialize it future-way - appState, err := AppGenState(nil, nil) + appState, err := AppGenState(nil, types.GenesisDoc{}, nil) require.NoError(t, err) //TODO test validators in the init chain? diff --git a/server/mock/tx.go b/server/mock/tx.go index 3bd248c7441b..750d6984d213 100644 --- a/server/mock/tx.go +++ b/server/mock/tx.go @@ -27,11 +27,11 @@ func NewTx(key, value string) kvstoreTx { } } -func (tx kvstoreTx) Type() string { +func (tx kvstoreTx) Route() string { return "kvstore" } -func (tx kvstoreTx) Name() string { +func (tx kvstoreTx) Type() string { return "kvstore_tx" } diff --git a/server/start.go b/server/start.go index 82fbbbbebadd..cf39ff71b62a 100644 --- a/server/start.go +++ b/server/start.go @@ -93,7 +93,6 @@ func startStandAlone(ctx *Context, appCreator AppCreator) error { return nil } -// nolint: unparam func startInProcess(ctx *Context, appCreator AppCreator) (*node.Node, error) { cfg := ctx.Config home := cfg.RootDir @@ -135,7 +134,12 @@ func startInProcess(ctx *Context, appCreator AppCreator) (*node.Node, error) { return nil, err } - // trap signal (run forever) - tmNode.RunForever() - return tmNode, nil + TrapSignal(func() { + if tmNode.IsRunning() { + _ = tmNode.Stop() + } + }) + + // run forever (the node will not be returned) + select {} } diff --git a/server/test_helpers.go b/server/test_helpers.go index 8de164e08ae4..4347bad6c354 100644 --- a/server/test_helpers.go +++ b/server/test_helpers.go @@ -2,6 +2,7 @@ package server import ( "fmt" + "github.com/cosmos/cosmos-sdk/client" "io/ioutil" "net" "os" @@ -42,6 +43,7 @@ func SetupViper(t *testing.T) func() { rootDir, err := ioutil.TempDir("", "mock-sdk-cmd") require.Nil(t, err) viper.Set(cli.HomeFlag, rootDir) + viper.Set(client.FlagName, "moniker") return func() { err := os.RemoveAll(rootDir) if err != nil { diff --git a/server/tm_cmds.go b/server/tm_cmds.go index 82652bdec578..5aeacf92f380 100644 --- a/server/tm_cmds.go +++ b/server/tm_cmds.go @@ -63,17 +63,17 @@ func ShowValidatorCmd(ctx *Context) *cobra.Command { func ShowAddressCmd(ctx *Context) *cobra.Command { cmd := &cobra.Command{ Use: "show-address", - Short: "Shows this node's tendermint validator 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()) - valAddr := (sdk.ValAddress)(privValidator.Address) + valConsAddr := (sdk.ConsAddress)(privValidator.Address) if viper.GetBool(client.FlagJson) { - return printlnJSON(valAddr) + return printlnJSON(valConsAddr) } - fmt.Println(valAddr.String()) + fmt.Println(valConsAddr.String()) return nil }, } diff --git a/server/util.go b/server/util.go index 5199c12071e1..633ad8870415 100644 --- a/server/util.go +++ b/server/util.go @@ -4,7 +4,10 @@ import ( "encoding/json" "net" "os" + "os/signal" "path/filepath" + "syscall" + "time" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -90,7 +93,7 @@ func interceptLoadConfig() (conf *cfg.Config, err error) { conf.P2P.RecvRate = 5120000 conf.P2P.SendRate = 5120000 conf.TxIndex.IndexAllTags = true - conf.Consensus.TimeoutCommit = 5000 + conf.Consensus.TimeoutCommit = 5 * time.Second cfg.WriteConfigFile(configFilePath, conf) // Fall through, just so that its parsed into memory. } @@ -203,6 +206,23 @@ func ExternalIP() (string, error) { return "", errors.New("are you connected to the network?") } +// TrapSignal traps SIGINT and SIGTERM and terminates the server correctly. +func TrapSignal(cleanupFunc func()) { + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + go func() { + sig := <-sigs + switch sig { + case syscall.SIGTERM: + defer cleanupFunc() + os.Exit(128 + int(syscall.SIGTERM)) + case syscall.SIGINT: + defer cleanupFunc() + os.Exit(128 + int(syscall.SIGINT)) + } + }() +} + func skipInterface(iface net.Interface) bool { if iface.Flags&net.FlagUp == 0 { return true // interface down diff --git a/store/cachekvstore.go b/store/cachekvstore.go index 9eb4ae9324db..4a2940f4df41 100644 --- a/store/cachekvstore.go +++ b/store/cachekvstore.go @@ -61,6 +61,7 @@ func (ci *cacheKVStore) Set(key []byte, value []byte) { ci.mtx.Lock() defer ci.mtx.Unlock() ci.assertValidKey(key) + ci.assertValidValue(value) ci.setCacheValue(key, value, false, true) } @@ -196,6 +197,12 @@ func (ci *cacheKVStore) assertValidKey(key []byte) { } } +func (ci *cacheKVStore) assertValidValue(value []byte) { + if value == nil { + panic("value is nil") + } +} + // Only entrypoint to mutate ci.cache. func (ci *cacheKVStore) setCacheValue(key, value []byte, deleted bool, dirty bool) { ci.cache[string(key)] = cValue{ diff --git a/store/iavlstore.go b/store/iavlstore.go index d535fd436c5b..fccde38e27e0 100644 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -5,9 +5,9 @@ import ( "io" "sync" - "github.com/tendermint/go-amino" "github.com/tendermint/iavl" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/merkle" cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" @@ -210,44 +210,59 @@ func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) { res.Height = getHeight(tree, req) switch req.Path { - case "/store", "/key": // Get by key - key := req.Data // Data holds the key bytes + case "/key": // get by key + key := req.Data // data holds the key bytes + res.Key = key if !st.VersionExists(res.Height) { res.Log = cmn.ErrorWrap(iavl.ErrVersionDoesNotExist, "").Error() break } + if req.Prove { value, proof, err := tree.GetVersionedWithProof(key, res.Height) if err != nil { res.Log = err.Error() break } - res.Value = value - cdc := amino.NewCodec() - p, err := cdc.MarshalBinary(proof) - if err != nil { - res.Log = err.Error() - break + if proof == nil { + // Proof == nil implies that the store is empty. + if value != nil { + panic("unexpected value for an empty proof") + } + } + if value != nil { + // value was found + res.Value = value + res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{iavl.NewIAVLValueOp(key, proof).ProofOp()}} + } else { + // value wasn't found + res.Value = nil + res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{iavl.NewIAVLAbsenceOp(key, proof).ProofOp()}} } - res.Proof = p } else { _, res.Value = tree.GetVersioned(key, res.Height) } + case "/subspace": + var KVs []KVPair + subspace := req.Data res.Key = subspace - var KVs []KVPair + iterator := sdk.KVStorePrefixIterator(st, subspace) for ; iterator.Valid(); iterator.Next() { KVs = append(KVs, KVPair{Key: iterator.Key(), Value: iterator.Value()}) } + iterator.Close() - res.Value = cdc.MustMarshalBinary(KVs) + res.Value = cdc.MustMarshalBinaryLengthPrefixed(KVs) + default: msg := fmt.Sprintf("Unexpected Query path: %v", req.Path) return sdk.ErrUnknownRequest(msg).QueryResult() } + return } diff --git a/store/iavlstore_test.go b/store/iavlstore_test.go index 1e9263b7bbcf..d26cee055f60 100644 --- a/store/iavlstore_test.go +++ b/store/iavlstore_test.go @@ -394,9 +394,9 @@ func TestIAVLStoreQuery(t *testing.T) { {Key: k1, Value: v3}, {Key: k2, Value: v2}, } - valExpSubEmpty := cdc.MustMarshalBinary(KVs0) - valExpSub1 := cdc.MustMarshalBinary(KVs1) - valExpSub2 := cdc.MustMarshalBinary(KVs2) + valExpSubEmpty := cdc.MustMarshalBinaryLengthPrefixed(KVs0) + valExpSub1 := cdc.MustMarshalBinaryLengthPrefixed(KVs1) + valExpSub2 := cdc.MustMarshalBinaryLengthPrefixed(KVs2) cid := iavlStore.Commit() ver := cid.Version @@ -459,7 +459,7 @@ func TestIAVLStoreQuery(t *testing.T) { require.Equal(t, valExpSub2, qres.Value) // default (height 0) will show latest -1 - query0 := abci.RequestQuery{Path: "/store", Data: k1} + query0 := abci.RequestQuery{Path: "/key", Data: k1} qres = iavlStore.Query(query0) require.Equal(t, uint32(sdk.CodeOK), qres.Code) require.Equal(t, v1, qres.Value) diff --git a/store/list.go b/store/list.go new file mode 100644 index 000000000000..b38f11b803ee --- /dev/null +++ b/store/list.go @@ -0,0 +1,112 @@ +package store + +import ( + "fmt" + "strconv" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Key for the length of the list +func LengthKey() []byte { + return []byte{0x00} +} + +// Key for the elements of the list +func ElemKey(index uint64) []byte { + return append([]byte{0x01}, []byte(fmt.Sprintf("%020d", index))...) +} + +// List defines an integer indexable mapper +// It panics when the element type cannot be (un/)marshalled by the codec +type List struct { + cdc *codec.Codec + store sdk.KVStore +} + +// NewList constructs new List +func NewList(cdc *codec.Codec, store sdk.KVStore) List { + return List{ + cdc: cdc, + store: store, + } +} + +// Len() returns the length of the list +// The length is only increased by Push() and not decreased +// List dosen't check if an index is in bounds +// The user should check Len() before doing any actions +func (m List) Len() (res uint64) { + bz := m.store.Get(LengthKey()) + if bz == nil { + return 0 + } + + m.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &res) + return +} + +// Get() returns the element by its index +func (m List) Get(index uint64, ptr interface{}) error { + bz := m.store.Get(ElemKey(index)) + return m.cdc.UnmarshalBinaryLengthPrefixed(bz, ptr) +} + +// Set() stores the element to the given position +// Setting element out of range will break length counting +// Use Push() instead of Set() to append a new element +func (m List) Set(index uint64, value interface{}) { + bz := m.cdc.MustMarshalBinaryLengthPrefixed(value) + m.store.Set(ElemKey(index), bz) +} + +// Delete() deletes the element in the given position +// Other elements' indices are preserved after deletion +// Panics when the index is out of range +func (m List) Delete(index uint64) { + m.store.Delete(ElemKey(index)) +} + +// Push() inserts the element to the end of the list +// It will increase the length when it is called +func (m List) Push(value interface{}) { + length := m.Len() + m.Set(length, value) + m.store.Set(LengthKey(), m.cdc.MustMarshalBinaryLengthPrefixed(length+1)) +} + +// Iterate() is used to iterate over all existing elements in the list +// Return true in the continuation to break +// The second element of the continuation will indicate the position of the element +// Using it with Get() will return the same one with the provided element + +// CONTRACT: No writes may happen within a domain while iterating over it. +func (m List) Iterate(ptr interface{}, fn func(uint64) bool) { + iter := sdk.KVStorePrefixIterator(m.store, []byte{0x01}) + for ; iter.Valid(); iter.Next() { + v := iter.Value() + m.cdc.MustUnmarshalBinaryLengthPrefixed(v, ptr) + + k := iter.Key() + s := string(k[len(k)-20:]) + + index, err := strconv.ParseUint(s, 10, 64) + if err != nil { + panic(err) + } + + if fn(index) { + break + } + } + + iter.Close() +} + +func subspace(prefix []byte) (start, end []byte) { + end = make([]byte, len(prefix)) + copy(end, prefix) + end[len(end)-1]++ + return prefix, end +} diff --git a/store/list_test.go b/store/list_test.go new file mode 100644 index 000000000000..396e2d1a1996 --- /dev/null +++ b/store/list_test.go @@ -0,0 +1,75 @@ +package store + +import ( + "math/rand" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestList(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + lm := NewList(cdc, store) + + val := S{1, true} + var res S + + lm.Push(val) + require.Equal(t, uint64(1), lm.Len()) + lm.Get(uint64(0), &res) + require.Equal(t, val, res) + + val = S{2, false} + lm.Set(uint64(0), val) + lm.Get(uint64(0), &res) + require.Equal(t, val, res) + + val = S{100, false} + lm.Push(val) + require.Equal(t, uint64(2), lm.Len()) + lm.Get(uint64(1), &res) + require.Equal(t, val, res) + + lm.Delete(uint64(1)) + require.Equal(t, uint64(2), lm.Len()) + + lm.Iterate(&res, func(index uint64) (brk bool) { + var temp S + lm.Get(index, &temp) + require.Equal(t, temp, res) + + require.True(t, index != 1) + return + }) + + lm.Iterate(&res, func(index uint64) (brk bool) { + lm.Set(index, S{res.I + 1, !res.B}) + return + }) + + lm.Get(uint64(0), &res) + require.Equal(t, S{3, true}, res) +} + +func TestListRandom(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + list := NewList(cdc, store) + mocklist := []uint32{} + + for i := 0; i < 100; i++ { + item := rand.Uint32() + list.Push(item) + mocklist = append(mocklist, item) + } + + for k, v := range mocklist { + var i uint32 + require.NotPanics(t, func() { list.Get(uint64(k), &i) }) + require.Equal(t, v, i) + } +} diff --git a/store/multistoreproof.go b/store/multistoreproof.go index d62bc4aca509..96f0a48373ea 100644 --- a/store/multistoreproof.go +++ b/store/multistoreproof.go @@ -2,90 +2,139 @@ package store import ( "bytes" + "fmt" - "github.com/pkg/errors" "github.com/tendermint/iavl" + "github.com/tendermint/tendermint/crypto/merkle" cmn "github.com/tendermint/tendermint/libs/common" ) // MultiStoreProof defines a collection of store proofs in a multi-store type MultiStoreProof struct { StoreInfos []storeInfo - StoreName string - RangeProof iavl.RangeProof } -// buildMultiStoreProof build MultiStoreProof based on iavl proof and storeInfos -func buildMultiStoreProof(iavlProof []byte, storeName string, storeInfos []storeInfo) []byte { - var rangeProof iavl.RangeProof - cdc.MustUnmarshalBinary(iavlProof, &rangeProof) +func NewMultiStoreProof(storeInfos []storeInfo) *MultiStoreProof { + return &MultiStoreProof{StoreInfos: storeInfos} +} - msp := MultiStoreProof{ - StoreInfos: storeInfos, - StoreName: storeName, - RangeProof: rangeProof, +// ComputeRootHash returns the root hash for a given multi-store proof. +func (proof *MultiStoreProof) ComputeRootHash() []byte { + ci := commitInfo{ + Version: -1, // TODO: Not needed; improve code. + StoreInfos: proof.StoreInfos, } + return ci.Hash() +} - proof := cdc.MustMarshalBinary(msp) - return proof +// RequireProof returns whether proof is required for the subpath. +func RequireProof(subpath string) bool { + // XXX: create a better convention. + // Currently, only when query subpath is "/key", will proof be included in + // response. If there are some changes about proof building in iavlstore.go, + // we must change code here to keep consistency with iavlStore#Query. + if subpath == "/key" { + return true + } + + return false } -// VerifyMultiStoreCommitInfo verify multiStoreCommitInfo against appHash -func VerifyMultiStoreCommitInfo(storeName string, storeInfos []storeInfo, appHash []byte) ([]byte, error) { - var substoreCommitHash []byte - var height int64 - for _, storeInfo := range storeInfos { - if storeInfo.Name == storeName { - substoreCommitHash = storeInfo.Core.CommitID.Hash - height = storeInfo.Core.CommitID.Version - } +//----------------------------------------------------------------------------- + +var _ merkle.ProofOperator = MultiStoreProofOp{} + +// the multi-store proof operation constant value +const ProofOpMultiStore = "multistore" + +// TODO: document +type MultiStoreProofOp struct { + // Encoded in ProofOp.Key + key []byte + + // To encode in ProofOp.Data. + Proof *MultiStoreProof `json:"proof"` +} + +func NewMultiStoreProofOp(key []byte, proof *MultiStoreProof) MultiStoreProofOp { + return MultiStoreProofOp{ + key: key, + Proof: proof, } - if len(substoreCommitHash) == 0 { - return nil, cmn.NewError("failed to get substore root commit hash by store name") +} + +// MultiStoreProofOpDecoder returns a multi-store merkle proof operator from a +// given proof operation. +func MultiStoreProofOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) { + if pop.Type != ProofOpMultiStore { + return nil, cmn.NewError("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpMultiStore) } - ci := commitInfo{ - Version: height, - StoreInfos: storeInfos, + // XXX: a bit strange as we'll discard this, but it works + var op MultiStoreProofOp + + err := cdc.UnmarshalBinaryLengthPrefixed(pop.Data, &op) + if err != nil { + return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into MultiStoreProofOp") } - if !bytes.Equal(appHash, ci.Hash()) { - return nil, cmn.NewError("the merkle root of multiStoreCommitInfo doesn't equal to appHash") + + return NewMultiStoreProofOp(pop.Key, op.Proof), nil +} + +// ProofOp return a merkle proof operation from a given multi-store proof +// operation. +func (op MultiStoreProofOp) ProofOp() merkle.ProofOp { + bz := cdc.MustMarshalBinaryLengthPrefixed(op) + return merkle.ProofOp{ + Type: ProofOpMultiStore, + Key: op.key, + Data: bz, } - return substoreCommitHash, nil } -// VerifyRangeProof verify iavl RangeProof -func VerifyRangeProof(key, value []byte, substoreCommitHash []byte, rangeProof *iavl.RangeProof) error { +// String implements the Stringer interface for a mult-store proof operation. +func (op MultiStoreProofOp) String() string { + return fmt.Sprintf("MultiStoreProofOp{%v}", op.GetKey()) +} - // verify the proof to ensure data integrity. - err := rangeProof.Verify(substoreCommitHash) - if err != nil { - return errors.Wrap(err, "proof root hash doesn't equal to substore commit root hash") +// GetKey returns the key for a multi-store proof operation. +func (op MultiStoreProofOp) GetKey() []byte { + return op.key +} + +// Run executes a multi-store proof operation for a given value. It returns +// the root hash if the value matches all the store's commitID's hash or an +// error otherwise. +func (op MultiStoreProofOp) Run(args [][]byte) ([][]byte, error) { + if len(args) != 1 { + return nil, cmn.NewError("Value size is not 1") } - if len(value) != 0 { - // verify existence proof - err = rangeProof.VerifyItem(key, value) - if err != nil { - return errors.Wrap(err, "failed in existence verification") - } - } else { - // verify absence proof - err = rangeProof.VerifyAbsence(key) - if err != nil { - return errors.Wrap(err, "failed in absence verification") + value := args[0] + root := op.Proof.ComputeRootHash() + + for _, si := range op.Proof.StoreInfos { + if si.Name == string(op.key) { + if bytes.Equal(value, si.Core.CommitID.Hash) { + return [][]byte{root}, nil + } + + return nil, cmn.NewError("hash mismatch for substore %v: %X vs %X", si.Name, si.Core.CommitID.Hash, value) } } - return nil + return nil, cmn.NewError("key %v not found in multistore proof", op.key) } -// RequireProof return whether proof is require for the subpath -func RequireProof(subpath string) bool { - // Currently, only when query subpath is "/store" or "/key", will proof be included in response. - // If there are some changes about proof building in iavlstore.go, we must change code here to keep consistency with iavlstore.go:212 - if subpath == "/store" || subpath == "/key" { - return true - } - return false +//----------------------------------------------------------------------------- + +// XXX: This should be managed by the rootMultiStore which may want to register +// more proof ops? +func DefaultProofRuntime() (prt *merkle.ProofRuntime) { + prt = merkle.NewProofRuntime() + prt.RegisterOpDecoder(merkle.ProofOpSimpleValue, merkle.SimpleValueOpDecoder) + prt.RegisterOpDecoder(iavl.ProofOpIAVLValue, iavl.IAVLValueOpDecoder) + prt.RegisterOpDecoder(iavl.ProofOpIAVLAbsence, iavl.IAVLAbsenceOpDecoder) + prt.RegisterOpDecoder(ProofOpMultiStore, MultiStoreProofOpDecoder) + return } diff --git a/store/multistoreproof_test.go b/store/multistoreproof_test.go index 45a102cd3ba4..db3b65cad53d 100644 --- a/store/multistoreproof_test.go +++ b/store/multistoreproof_test.go @@ -1,123 +1,174 @@ package store import ( - "encoding/hex" "testing" - "github.com/stretchr/testify/assert" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" - "github.com/tendermint/iavl" - cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/libs/db" + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" ) -func TestVerifyMultiStoreCommitInfo(t *testing.T) { - appHash, _ := hex.DecodeString("69959B1B4E68E0F7BD3551A50C8F849B81801AF2") +func TestVerifyIAVLStoreQueryProof(t *testing.T) { + // Create main tree for testing. + db := dbm.NewMemDB() + iStore, err := LoadIAVLStore(db, CommitID{}, sdk.PruneNothing) + store := iStore.(*iavlStore) + require.Nil(t, err) + store.Set([]byte("MYKEY"), []byte("MYVALUE")) + cid := store.Commit() + + // Get Proof + res := store.Query(abci.RequestQuery{ + Path: "/key", // required path to get key/value+proof + Data: []byte("MYKEY"), + Prove: true, + }) + require.NotNil(t, res.Proof) - substoreRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") - storeName := "acc" + // Verify proof. + prt := DefaultProofRuntime() + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte("MYVALUE")) + require.Nil(t, err) - var storeInfos []storeInfo + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY_NOT", []byte("MYVALUE")) + require.NotNil(t, err) - gocRootHash, _ := hex.DecodeString("62c171bb022e47d1f745608ff749e676dbd25f78") - storeInfos = append(storeInfos, storeInfo{ - Name: "gov", - Core: storeCore{ - CommitID: CommitID{ - Version: 689, - Hash: gocRootHash, - }, - }, - }) + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) - storeInfos = append(storeInfos, storeInfo{ - Name: "main", - Core: storeCore{ - CommitID: CommitID{ - Version: 689, - Hash: nil, - }, - }, - }) + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) - accRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") - storeInfos = append(storeInfos, storeInfo{ - Name: "acc", - Core: storeCore{ - CommitID: CommitID{ - Version: 689, - Hash: accRootHash, - }, - }, - }) + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte("MYVALUE_NOT")) + require.NotNil(t, err) - storeInfos = append(storeInfos, storeInfo{ - Name: "ibc", - Core: storeCore{ - CommitID: CommitID{ - Version: 689, - Hash: nil, - }, - }, - }) + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte(nil)) + require.NotNil(t, err) +} - stakeRootHash, _ := hex.DecodeString("987d1d27b8771d93aa3691262f661d2c85af7ca4") - storeInfos = append(storeInfos, storeInfo{ - Name: "stake", - Core: storeCore{ - CommitID: CommitID{ - Version: 689, - Hash: stakeRootHash, - }, - }, - }) +func TestVerifyMultiStoreQueryProof(t *testing.T) { + // Create main tree for testing. + db := dbm.NewMemDB() + store := NewCommitMultiStore(db) + iavlStoreKey := sdk.NewKVStoreKey("iavlStoreKey") + + store.MountStoreWithDB(iavlStoreKey, sdk.StoreTypeIAVL, nil) + store.LoadVersion(0) - slashingRootHash, _ := hex.DecodeString("388ee6e5b11f367069beb1eefd553491afe9d73e") - storeInfos = append(storeInfos, storeInfo{ - Name: "slashing", - Core: storeCore{ - CommitID: CommitID{ - Version: 689, - Hash: slashingRootHash, - }, - }, + iavlStore := store.GetCommitStore(iavlStoreKey).(*iavlStore) + iavlStore.Set([]byte("MYKEY"), []byte("MYVALUE")) + cid := store.Commit() + + // Get Proof + res := store.Query(abci.RequestQuery{ + Path: "/iavlStoreKey/key", // required path to get key/value+proof + Data: []byte("MYKEY"), + Prove: true, }) + require.NotNil(t, res.Proof) + + // Verify proof. + prt := DefaultProofRuntime() + err := prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE")) + require.Nil(t, err) - commitHash, err := VerifyMultiStoreCommitInfo(storeName, storeInfos, appHash) + // Verify proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE")) require.Nil(t, err) - require.Equal(t, commitHash, substoreRootHash) - appHash, _ = hex.DecodeString("29de216bf5e2531c688de36caaf024cd3bb09ee3") + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY_NOT", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "iavlStoreKey/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) - _, err = VerifyMultiStoreCommitInfo(storeName, storeInfos, appHash) - require.Error(t, err, "appHash doesn't match to the merkle root of multiStoreCommitInfo") + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE_NOT")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte(nil)) + require.NotNil(t, err) +} + +func TestVerifyMultiStoreQueryProofEmptyStore(t *testing.T) { + // Create main tree for testing. + db := dbm.NewMemDB() + store := NewCommitMultiStore(db) + iavlStoreKey := sdk.NewKVStoreKey("iavlStoreKey") + + store.MountStoreWithDB(iavlStoreKey, sdk.StoreTypeIAVL, nil) + store.LoadVersion(0) + cid := store.Commit() // Commit with empty iavl store. + + // Get Proof + res := store.Query(abci.RequestQuery{ + Path: "/iavlStoreKey/key", // required path to get key/value+proof + Data: []byte("MYKEY"), + Prove: true, + }) + require.NotNil(t, res.Proof) + + // Verify proof. + prt := DefaultProofRuntime() + err := prt.VerifyAbsence(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY") + require.Nil(t, err) + + // Verify (bad) proof. + prt = DefaultProofRuntime() + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) } -func TestVerifyRangeProof(t *testing.T) { - tree := iavl.NewMutableTree(db.NewMemDB(), 0) - - rand := cmn.NewRand() - rand.Seed(0) // for determinism - for _, ikey := range []byte{0x11, 0x32, 0x50, 0x72, 0x99} { - key := []byte{ikey} - tree.Set(key, []byte(rand.Str(8))) - } - - root := tree.WorkingHash() - - key := []byte{0x32} - val, proof, err := tree.GetWithProof(key) - assert.Nil(t, err) - assert.NotEmpty(t, val) - assert.NotEmpty(t, proof) - err = VerifyRangeProof(key, val, root, proof) - assert.Nil(t, err) - - key = []byte{0x40} - val, proof, err = tree.GetWithProof(key) - assert.Nil(t, err) - assert.Empty(t, val) - assert.NotEmpty(t, proof) - err = VerifyRangeProof(key, val, root, proof) - assert.Nil(t, err) +func TestVerifyMultiStoreQueryProofAbsence(t *testing.T) { + // Create main tree for testing. + db := dbm.NewMemDB() + store := NewCommitMultiStore(db) + iavlStoreKey := sdk.NewKVStoreKey("iavlStoreKey") + + store.MountStoreWithDB(iavlStoreKey, sdk.StoreTypeIAVL, nil) + store.LoadVersion(0) + + iavlStore := store.GetCommitStore(iavlStoreKey).(*iavlStore) + iavlStore.Set([]byte("MYKEY"), []byte("MYVALUE")) + cid := store.Commit() // Commit with empty iavl store. + + // Get Proof + res := store.Query(abci.RequestQuery{ + Path: "/iavlStoreKey/key", // required path to get key/value+proof + Data: []byte("MYABSENTKEY"), + Prove: true, + }) + require.NotNil(t, res.Proof) + + // Verify proof. + prt := DefaultProofRuntime() + err := prt.VerifyAbsence(res.Proof, cid.Hash, "/iavlStoreKey/MYABSENTKEY") + require.Nil(t, err) + + // Verify (bad) proof. + prt = DefaultProofRuntime() + err = prt.VerifyAbsence(res.Proof, cid.Hash, "/MYABSENTKEY") + require.NotNil(t, err) + + // Verify (bad) proof. + prt = DefaultProofRuntime() + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYABSENTKEY", []byte("")) + require.NotNil(t, err) } diff --git a/store/prefixstore_test.go b/store/prefixstore_test.go index 8445991b6e21..4e555292c3c2 100644 --- a/store/prefixstore_test.go +++ b/store/prefixstore_test.go @@ -119,10 +119,6 @@ func TestPrefixStoreIterate(t *testing.T) { } func incFirstByte(bz []byte) { - if bz[0] == byte(255) { - bz[0] = byte(0) - return - } bz[0]++ } diff --git a/store/queue.go b/store/queue.go new file mode 100644 index 000000000000..f41ecb7d7a19 --- /dev/null +++ b/store/queue.go @@ -0,0 +1,88 @@ +package store + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Key for the top element position in the queue +func TopKey() []byte { + return []byte{0x02} +} + +// Queue is a List wrapper that provides queue-like functions +// It panics when the element type cannot be (un/)marshalled by the codec +type Queue struct { + List List +} + +// NewQueue constructs new Queue +func NewQueue(cdc *codec.Codec, store sdk.KVStore) Queue { + return Queue{NewList(cdc, store)} +} + +func (m Queue) getTop() (res uint64) { + bz := m.List.store.Get(TopKey()) + if bz == nil { + return 0 + } + + m.List.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &res) + return +} + +func (m Queue) setTop(top uint64) { + bz := m.List.cdc.MustMarshalBinaryLengthPrefixed(top) + m.List.store.Set(TopKey(), bz) +} + +// Push() inserts the elements to the rear of the queue +func (m Queue) Push(value interface{}) { + m.List.Push(value) +} + +// Popping/Peeking on an empty queue will cause panic +// The user should check IsEmpty() before doing any actions +// Peek() returns the element at the front of the queue without removing it +func (m Queue) Peek(ptr interface{}) error { + top := m.getTop() + return m.List.Get(top, ptr) +} + +// Pop() returns the element at the front of the queue and removes it +func (m Queue) Pop() { + top := m.getTop() + m.List.Delete(top) + m.setTop(top + 1) +} + +// IsEmpty() checks if the queue is empty +func (m Queue) IsEmpty() bool { + top := m.getTop() + length := m.List.Len() + return top >= length +} + +// Flush() removes elements it processed +// Return true in the continuation to break +// The interface{} is unmarshalled before the continuation is called +// Starts from the top(head) of the queue +// CONTRACT: Pop() or Push() should not be performed while flushing +func (m Queue) Flush(ptr interface{}, fn func() bool) { + top := m.getTop() + length := m.List.Len() + + var i uint64 + for i = top; i < length; i++ { + err := m.List.Get(i, ptr) + if err != nil { + // TODO: Handle with #870 + panic(err) + } + m.List.Delete(i) + if fn() { + break + } + } + m.setTop(i) +} diff --git a/store/queue_test.go b/store/queue_test.go new file mode 100644 index 000000000000..58e96f56a19e --- /dev/null +++ b/store/queue_test.go @@ -0,0 +1,102 @@ +package store + +import ( + "testing" + + "github.com/stretchr/testify/require" + + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type S struct { + I uint64 + B bool +} + +func defaultComponents(key sdk.StoreKey) (sdk.Context, *codec.Codec) { + db := dbm.NewMemDB() + cms := NewCommitMultiStore(db) + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + cms.LoadLatestVersion() + ctx := sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) + cdc := codec.New() + return ctx, cdc +} + +func TestQueue(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + + qm := NewQueue(cdc, store) + + val := S{1, true} + var res S + + qm.Push(val) + qm.Peek(&res) + require.Equal(t, val, res) + + qm.Pop() + empty := qm.IsEmpty() + + require.True(t, empty) + require.NotNil(t, qm.Peek(&res)) + + qm.Push(S{1, true}) + qm.Push(S{2, true}) + qm.Push(S{3, true}) + qm.Flush(&res, func() (brk bool) { + if res.I == 3 { + brk = true + } + return + }) + + require.False(t, qm.IsEmpty()) + + qm.Pop() + require.True(t, qm.IsEmpty()) +} + +func TestKeys(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + queue := NewQueue(cdc, store) + + for i := 0; i < 10; i++ { + queue.Push(i) + } + + var len uint64 + var top uint64 + var expected int + var actual int + + // Checking keys.LengthKey + err := cdc.UnmarshalBinaryLengthPrefixed(store.Get(LengthKey()), &len) + require.Nil(t, err) + require.Equal(t, len, queue.List.Len()) + + // Checking keys.ElemKey + for i := 0; i < 10; i++ { + queue.List.Get(uint64(i), &expected) + bz := store.Get(ElemKey(uint64(i))) + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &actual) + require.Nil(t, err) + require.Equal(t, expected, actual) + } + + queue.Pop() + + err = cdc.UnmarshalBinaryLengthPrefixed(store.Get(TopKey()), &top) + require.Nil(t, err) + require.Equal(t, top, queue.getTop()) +} diff --git a/store/rootmultistore.go b/store/rootmultistore.go index 7de465f7703e..cd2d0135f113 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -295,13 +295,23 @@ func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery { return res } + if res.Proof == nil || len(res.Proof.Ops) == 0 { + return sdk.ErrInternal("substore proof was nil/empty when it should never be").QueryResult() + } + commitInfo, errMsg := getCommitInfo(rs.db, res.Height) if errMsg != nil { return sdk.ErrInternal(errMsg.Error()).QueryResult() } - res.Proof = buildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos) + // Restore origin path and append proof op. + res.Proof.Ops = append(res.Proof.Ops, NewMultiStoreProofOp( + []byte(storeName), + NewMultiStoreProof(commitInfo.StoreInfos), + ).ProofOp()) + // TODO: handle in another TM v0.26 update PR + // res.Proof = buildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos) return res } @@ -313,11 +323,14 @@ func parsePath(path string) (storeName string, subpath string, err sdk.Error) { err = sdk.ErrUnknownRequest(fmt.Sprintf("invalid path: %s", path)) return } + paths := strings.SplitN(path[1:], "/", 2) storeName = paths[0] + if len(paths) == 2 { subpath = "/" + paths[1] } + return } @@ -386,11 +399,12 @@ type commitInfo struct { // Hash returns the simple merkle root hash of the stores sorted by name. func (ci commitInfo) Hash() []byte { - // TODO cache to ci.hash []byte - m := make(map[string]merkle.Hasher, len(ci.StoreInfos)) + // TODO: cache to ci.hash []byte + m := make(map[string][]byte, len(ci.StoreInfos)) for _, storeInfo := range ci.StoreInfos { - m[storeInfo.Name] = storeInfo + m[storeInfo.Name] = storeInfo.Hash() } + return merkle.SimpleHashFromMap(m) } @@ -422,13 +436,15 @@ type storeCore struct { func (si storeInfo) Hash() []byte { // Doesn't write Name, since merkle.SimpleHashFromMap() will // include them via the keys. - bz, _ := cdc.MarshalBinary(si.Core) // Does not error + bz, _ := cdc.MarshalBinaryLengthPrefixed(si.Core) hasher := tmhash.New() + _, err := hasher.Write(bz) if err != nil { // TODO: Handle with #870 panic(err) } + return hasher.Sum(nil) } @@ -441,16 +457,18 @@ func getLatestVersion(db dbm.DB) int64 { if latestBytes == nil { return 0 } - err := cdc.UnmarshalBinary(latestBytes, &latest) + + err := cdc.UnmarshalBinaryLengthPrefixed(latestBytes, &latest) if err != nil { panic(err) } + return latest } // Set the latest version. func setLatestVersion(batch dbm.Batch, version int64) { - latestBytes, _ := cdc.MarshalBinary(version) // Does not error + latestBytes, _ := cdc.MarshalBinaryLengthPrefixed(version) batch.Set([]byte(latestVersionKey), latestBytes) } @@ -491,21 +509,19 @@ func getCommitInfo(db dbm.DB, ver int64) (commitInfo, error) { return commitInfo{}, fmt.Errorf("failed to get rootMultiStore: no data") } - // Parse bytes. var cInfo commitInfo - err := cdc.UnmarshalBinary(cInfoBytes, &cInfo) + + err := cdc.UnmarshalBinaryLengthPrefixed(cInfoBytes, &cInfo) if err != nil { return commitInfo{}, fmt.Errorf("failed to get rootMultiStore: %v", err) } + return cInfo, nil } // Set a commitInfo for given version. func setCommitInfo(batch dbm.Batch, version int64, cInfo commitInfo) { - cInfoBytes, err := cdc.MarshalBinary(cInfo) - if err != nil { - panic(err) - } + cInfoBytes := cdc.MustMarshalBinaryLengthPrefixed(cInfo) cInfoKey := fmt.Sprintf(commitInfoKeyFmt, version) batch.Set([]byte(cInfoKey), cInfoBytes) } diff --git a/store/rootmultistore_test.go b/store/rootmultistore_test.go index d6a714c66efa..501c5b730c25 100644 --- a/store/rootmultistore_test.go +++ b/store/rootmultistore_test.go @@ -215,7 +215,7 @@ func getExpectedCommitID(store *rootMultiStore, ver int64) CommitID { } func hashStores(stores map[StoreKey]CommitStore) []byte { - m := make(map[string]merkle.Hasher, len(stores)) + m := make(map[string][]byte, len(stores)) for key, store := range stores { name := key.Name() m[name] = storeInfo{ @@ -224,7 +224,7 @@ func hashStores(stores map[StoreKey]CommitStore) []byte { CommitID: store.LastCommitID(), // StoreType: store.GetStoreType(), }, - } + }.Hash() } return merkle.SimpleHashFromMap(m) } diff --git a/tests/gobash.go b/tests/gobash.go index 6282f2fda4c4..87d56a2974d2 100644 --- a/tests/gobash.go +++ b/tests/gobash.go @@ -14,7 +14,7 @@ import ( // ExecuteT executes the command, pipes any input to STDIN and return STDOUT, // logging STDOUT/STDERR to t. // nolint: errcheck -func ExecuteT(t *testing.T, cmd, input string) (out string) { +func ExecuteT(t *testing.T, cmd, input string) (stdout, stderr string) { t.Log("Running", cmn.Cyan(cmd)) // split cmd to name and args @@ -50,8 +50,10 @@ func ExecuteT(t *testing.T, cmd, input string) (out string) { t.Log("Stderr:", cmn.Red(string(errbz))) } - out = strings.Trim(string(outbz), "\n") - return out + stdout = strings.Trim(string(outbz), "\n") + stderr = strings.Trim(string(errbz), "\n") + + return } // Execute the command, launch goroutines to log stdout/err to t. diff --git a/types/address.go b/types/address.go index ae13b2ad0afc..36eb5ac32bc5 100644 --- a/types/address.go +++ b/types/address.go @@ -55,7 +55,8 @@ func AccAddressFromHex(address string) (addr AccAddress, err error) { // AccAddressFromBech32 creates an AccAddress from a Bech32 string. func AccAddressFromBech32(address string) (addr AccAddress, err error) { - bz, err := GetFromBech32(address, Bech32PrefixAccAddr) + bech32PrefixAccAddr := GetConfig().GetBech32AccountAddrPrefix() + bz, err := GetFromBech32(address, bech32PrefixAccAddr) if err != nil { return nil, err } @@ -124,7 +125,8 @@ func (aa AccAddress) Bytes() []byte { // String implements the Stringer interface. func (aa AccAddress) String() string { - bech32Addr, err := bech32.ConvertAndEncode(Bech32PrefixAccAddr, aa.Bytes()) + bech32PrefixAccAddr := GetConfig().GetBech32AccountAddrPrefix() + bech32Addr, err := bech32.ConvertAndEncode(bech32PrefixAccAddr, aa.Bytes()) if err != nil { panic(err) } @@ -169,7 +171,8 @@ func ValAddressFromHex(address string) (addr ValAddress, err error) { // ValAddressFromBech32 creates a ValAddress from a Bech32 string. func ValAddressFromBech32(address string) (addr ValAddress, err error) { - bz, err := GetFromBech32(address, Bech32PrefixValAddr) + bech32PrefixValAddr := GetConfig().GetBech32ValidatorAddrPrefix() + bz, err := GetFromBech32(address, bech32PrefixValAddr) if err != nil { return nil, err } @@ -239,7 +242,8 @@ func (va ValAddress) Bytes() []byte { // String implements the Stringer interface. func (va ValAddress) String() string { - bech32Addr, err := bech32.ConvertAndEncode(Bech32PrefixValAddr, va.Bytes()) + bech32PrefixValAddr := GetConfig().GetBech32ValidatorAddrPrefix() + bech32Addr, err := bech32.ConvertAndEncode(bech32PrefixValAddr, va.Bytes()) if err != nil { panic(err) } @@ -284,7 +288,8 @@ func ConsAddressFromHex(address string) (addr ConsAddress, err error) { // ConsAddressFromBech32 creates a ConsAddress from a Bech32 string. func ConsAddressFromBech32(address string) (addr ConsAddress, err error) { - bz, err := GetFromBech32(address, Bech32PrefixConsAddr) + bech32PrefixConsAddr := GetConfig().GetBech32ConsensusAddrPrefix() + bz, err := GetFromBech32(address, bech32PrefixConsAddr) if err != nil { return nil, err } @@ -359,7 +364,8 @@ func (ca ConsAddress) Bytes() []byte { // String implements the Stringer interface. func (ca ConsAddress) String() string { - bech32Addr, err := bech32.ConvertAndEncode(Bech32PrefixConsAddr, ca.Bytes()) + bech32PrefixConsAddr := GetConfig().GetBech32ConsensusAddrPrefix() + bech32Addr, err := bech32.ConvertAndEncode(bech32PrefixConsAddr, ca.Bytes()) if err != nil { panic(err) } @@ -387,7 +393,8 @@ func (ca ConsAddress) Format(s fmt.State, verb rune) { // Bech32ifyAccPub returns a Bech32 encoded string containing the // Bech32PrefixAccPub prefix for a given account PubKey. func Bech32ifyAccPub(pub crypto.PubKey) (string, error) { - return bech32.ConvertAndEncode(Bech32PrefixAccPub, pub.Bytes()) + bech32PrefixAccPub := GetConfig().GetBech32AccountPubPrefix() + return bech32.ConvertAndEncode(bech32PrefixAccPub, pub.Bytes()) } // MustBech32ifyAccPub returns the result of Bech32ifyAccPub panicing on failure. @@ -403,7 +410,8 @@ func MustBech32ifyAccPub(pub crypto.PubKey) string { // Bech32ifyValPub returns a Bech32 encoded string containing the // Bech32PrefixValPub prefix for a given validator operator's PubKey. func Bech32ifyValPub(pub crypto.PubKey) (string, error) { - return bech32.ConvertAndEncode(Bech32PrefixValPub, pub.Bytes()) + bech32PrefixValPub := GetConfig().GetBech32ValidatorPubPrefix() + return bech32.ConvertAndEncode(bech32PrefixValPub, pub.Bytes()) } // MustBech32ifyValPub returns the result of Bech32ifyValPub panicing on failure. @@ -419,7 +427,8 @@ func MustBech32ifyValPub(pub crypto.PubKey) string { // Bech32ifyConsPub returns a Bech32 encoded string containing the // Bech32PrefixConsPub prefixfor a given consensus node's PubKey. func Bech32ifyConsPub(pub crypto.PubKey) (string, error) { - return bech32.ConvertAndEncode(Bech32PrefixConsPub, pub.Bytes()) + bech32PrefixConsPub := GetConfig().GetBech32ConsensusPubPrefix() + return bech32.ConvertAndEncode(bech32PrefixConsPub, pub.Bytes()) } // MustBech32ifyConsPub returns the result of Bech32ifyConsPub panicing on @@ -436,7 +445,8 @@ func MustBech32ifyConsPub(pub crypto.PubKey) string { // GetAccPubKeyBech32 creates a PubKey for an account with a given public key // string using the Bech32 Bech32PrefixAccPub prefix. func GetAccPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) { - bz, err := GetFromBech32(pubkey, Bech32PrefixAccPub) + bech32PrefixAccPub := GetConfig().GetBech32AccountPubPrefix() + bz, err := GetFromBech32(pubkey, bech32PrefixAccPub) if err != nil { return nil, err } @@ -463,7 +473,8 @@ func MustGetAccPubKeyBech32(pubkey string) (pk crypto.PubKey) { // GetValPubKeyBech32 creates a PubKey for a validator's operator with a given // public key string using the Bech32 Bech32PrefixValPub prefix. func GetValPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) { - bz, err := GetFromBech32(pubkey, Bech32PrefixValPub) + bech32PrefixValPub := GetConfig().GetBech32ValidatorPubPrefix() + bz, err := GetFromBech32(pubkey, bech32PrefixValPub) if err != nil { return nil, err } @@ -490,7 +501,8 @@ func MustGetValPubKeyBech32(pubkey string) (pk crypto.PubKey) { // GetConsPubKeyBech32 creates a PubKey for a consensus node with a given public // key string using the Bech32 Bech32PrefixConsPub prefix. func GetConsPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) { - bz, err := GetFromBech32(pubkey, Bech32PrefixConsPub) + bech32PrefixConsPub := GetConfig().GetBech32ConsensusPubPrefix() + bz, err := GetFromBech32(pubkey, bech32PrefixConsPub) if err != nil { return nil, err } diff --git a/types/address_test.go b/types/address_test.go index e2ec36876cd5..51c44a12cd60 100644 --- a/types/address_test.go +++ b/types/address_test.go @@ -9,6 +9,8 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" + "strings" + "github.com/cosmos/cosmos-sdk/types" ) @@ -178,3 +180,44 @@ func TestConsAddress(t *testing.T) { require.NotNil(t, err) } } + +const letterBytes = "abcdefghijklmnopqrstuvwxyz" + +func RandString(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Intn(len(letterBytes))] + } + return string(b) +} + +func TestConfiguredPrefix(t *testing.T) { + var pub ed25519.PubKeyEd25519 + for length := 1; length < 10; length++ { + for times := 1; times < 20; times++ { + rand.Read(pub[:]) + // Test if randomly generated prefix of a given length works + prefix := RandString(length) + // Assuming that GetConfig is not sealed. + config := types.GetConfig() + config.SetBech32PrefixForAccount(prefix+"acc", prefix+"pub") + acc := types.AccAddress(pub.Address()) + require.True(t, strings.HasPrefix(acc.String(), prefix+"acc")) + bech32Pub := types.MustBech32ifyAccPub(pub) + require.True(t, strings.HasPrefix(bech32Pub, prefix+"pub")) + + config.SetBech32PrefixForValidator(prefix+"valaddr", prefix+"valpub") + val := types.ValAddress(pub.Address()) + require.True(t, strings.HasPrefix(val.String(), prefix+"valaddr")) + bech32ValPub := types.MustBech32ifyValPub(pub) + require.True(t, strings.HasPrefix(bech32ValPub, prefix+"valpub")) + + config.SetBech32PrefixForConsensusNode(prefix+"consaddr", prefix+"conspub") + cons := types.ConsAddress(pub.Address()) + require.True(t, strings.HasPrefix(cons.String(), prefix+"consaddr")) + bech32ConsPub := types.MustBech32ifyConsPub(pub) + require.True(t, strings.HasPrefix(bech32ConsPub, prefix+"conspub")) + } + + } +} diff --git a/types/coin.go b/types/coin.go index d7484a66997d..31f0e98a4239 100644 --- a/types/coin.go +++ b/types/coin.go @@ -49,7 +49,7 @@ func (coin Coin) IsGTE(other Coin) bool { // IsLT returns true if they are the same type and the receiver is // a smaller value func (coin Coin) IsLT(other Coin) bool { - return !coin.IsGTE(other) + return coin.SameDenomAs(other) && coin.Amount.LT(other.Amount) } // IsEqual returns true if the two sets of Coins have the same value @@ -142,7 +142,11 @@ func (coins Coins) Plus(coinsB Coins) Coins { coinA, coinB := coins[indexA], coinsB[indexB] switch strings.Compare(coinA.Denom, coinB.Denom) { case -1: - sum = append(sum, coinA) + if coinA.IsZero() { + // ignore 0 sum coin type + } else { + sum = append(sum, coinA) + } indexA++ case 0: if coinA.Amount.Add(coinB.Amount).IsZero() { @@ -153,7 +157,11 @@ func (coins Coins) Plus(coinsB Coins) Coins { indexA++ indexB++ case 1: - sum = append(sum, coinB) + if coinB.IsZero() { + // ignore 0 sum coin type + } else { + sum = append(sum, coinB) + } indexB++ } } @@ -176,10 +184,19 @@ func (coins Coins) Minus(coinsB Coins) Coins { return coins.Plus(coinsB.Negative()) } -// IsGTE returns True iff coins is NonNegative(), and for every -// currency in coinsB, the currency is present at an equal or greater -// amount in coinsB -func (coins Coins) IsGTE(coinsB Coins) bool { +// IsAllGT returns True iff for every denom in coins, the denom is present at a +// greater amount in coinsB. +func (coins Coins) IsAllGT(coinsB Coins) bool { + diff := coins.Minus(coinsB) + if len(diff) == 0 { + return false + } + return diff.IsPositive() +} + +// IsAllGTE returns True iff for every denom in coins, the denom is present at an +// equal or greater amount in coinsB. +func (coins Coins) IsAllGTE(coinsB Coins) bool { diff := coins.Minus(coinsB) if len(diff) == 0 { return true @@ -187,14 +204,27 @@ func (coins Coins) IsGTE(coinsB Coins) bool { return diff.IsNotNegative() } -// IsLT returns True iff every currency in coins, the currency is -// present at a smaller amount in coins -func (coins Coins) IsLT(coinsB Coins) bool { - return !coins.IsGTE(coinsB) +// IsAllLT returns True iff for every denom in coins, the denom is present at +// a smaller amount in coinsB. +func (coins Coins) IsAllLT(coinsB Coins) bool { + diff := coinsB.Minus(coins) + if len(diff) == 0 { + return false + } + return diff.IsPositive() +} + +// IsAllLTE returns True iff for every denom in coins, the denom is present at +// a smaller or equal amount in coinsB. +func (coins Coins) IsAllLTE(coinsB Coins) bool { + diff := coinsB.Minus(coins) + if len(diff) == 0 { + return true + } + return diff.IsNotNegative() } -// IsZero returns true if there are no coins -// or all coins are zero. +// IsZero returns true if there are no coins or all coins are zero. func (coins Coins) IsZero() bool { for _, coin := range coins { if !coin.IsZero() { diff --git a/types/coin_test.go b/types/coin_test.go index bc0441279155..77307f22a514 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -49,8 +49,8 @@ func TestSameDenomAsCoin(t *testing.T) { {NewInt64Coin("A", 1), NewInt64Coin("A", 1), true}, {NewInt64Coin("A", 1), NewInt64Coin("a", 1), false}, {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, - {NewInt64Coin("steak", 1), NewInt64Coin("steak", 10), true}, - {NewInt64Coin("steak", -11), NewInt64Coin("steak", 10), true}, + {NewInt64Coin("stake", 1), NewInt64Coin("stake", 10), true}, + {NewInt64Coin("stake", -11), NewInt64Coin("stake", 10), true}, } for tcIndex, tc := range cases { @@ -86,7 +86,10 @@ func TestIsLTCoin(t *testing.T) { {NewInt64Coin("A", 1), NewInt64Coin("A", 1), false}, {NewInt64Coin("A", 2), NewInt64Coin("A", 1), false}, {NewInt64Coin("A", -1), NewInt64Coin("A", 5), true}, - {NewInt64Coin("a", 0), NewInt64Coin("b", 1), true}, + {NewInt64Coin("a", 0), NewInt64Coin("b", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("a", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("a", 2), true}, } for tcIndex, tc := range cases { @@ -104,8 +107,8 @@ func TestIsEqualCoin(t *testing.T) { {NewInt64Coin("A", 1), NewInt64Coin("A", 1), true}, {NewInt64Coin("A", 1), NewInt64Coin("a", 1), false}, {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, - {NewInt64Coin("steak", 1), NewInt64Coin("steak", 10), false}, - {NewInt64Coin("steak", -11), NewInt64Coin("steak", 10), false}, + {NewInt64Coin("stake", 1), NewInt64Coin("stake", 10), false}, + {NewInt64Coin("stake", -11), NewInt64Coin("stake", 10), false}, } for tcIndex, tc := range cases { @@ -245,9 +248,9 @@ func TestCoins(t *testing.T) { assert.True(t, good.IsValid(), "Coins are valid") 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.IsGTE(empty), "Expected %v to be >= %v", good, empty) - assert.False(t, good.IsLT(empty), "Expected %v to be < %v", good, empty) - assert.True(t, empty.IsLT(good), "Expected %v to be < %v", empty, good) + assert.True(t, good.IsAllGTE(empty), "Expected %v to be >= %v", good, empty) + assert.False(t, good.IsAllLT(empty), "Expected %v to be < %v", good, empty) + assert.True(t, empty.IsAllLT(good), "Expected %v to be < %v", empty, good) assert.False(t, neg.IsPositive(), "Expected neg coins to not be positive: %v", neg) assert.Zero(t, len(sum), "Expected 0 coins") assert.False(t, badSort1.IsValid(), "Coins are not sorted") @@ -257,6 +260,60 @@ func TestCoins(t *testing.T) { } +func TestCoinsGT(t *testing.T) { + one := NewInt(1) + 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}})) +} + +func TestCoinsGTE(t *testing.T) { + one := NewInt(1) + 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}})) +} + +func TestCoinsLT(t *testing.T) { + one := NewInt(1) + 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}})) +} + +func TestCoinsLTE(t *testing.T) { + one := NewInt(1) + 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}})) +} + func TestPlusCoins(t *testing.T) { one := NewInt(1) zero := NewInt(0) diff --git a/types/config.go b/types/config.go new file mode 100644 index 000000000000..da1a387fe17b --- /dev/null +++ b/types/config.go @@ -0,0 +1,105 @@ +package types + +import ( + "sync" +) + +// Config is the structure that holds the SDK configuration parameters. +// This could be used to initialize certain configuration parameters for the SDK. +type Config struct { + mtx sync.RWMutex + sealed bool + bech32AddressPrefix map[string]string +} + +var ( + // Initializing an instance of Config + sdkConfig = &Config{ + sealed: false, + bech32AddressPrefix: map[string]string{ + "account_addr": Bech32PrefixAccAddr, + "validator_addr": Bech32PrefixValAddr, + "consensus_addr": Bech32PrefixConsAddr, + "account_pub": Bech32PrefixAccPub, + "validator_pub": Bech32PrefixValPub, + "consensus_pub": Bech32PrefixConsPub, + }, + } +) + +// GetConfig returns the config instance for the SDK. +func GetConfig() *Config { + return sdkConfig +} + +func (config *Config) assertNotSealed() { + config.mtx.Lock() + defer config.mtx.Unlock() + + if config.sealed { + panic("Config is sealed") + } +} + +// SetBech32PrefixForAccount builds the Config with Bech32 addressPrefix and publKeyPrefix for accounts +// and returns the config instance +func (config *Config) SetBech32PrefixForAccount(addressPrefix, pubKeyPrefix string) { + config.assertNotSealed() + config.bech32AddressPrefix["account_addr"] = addressPrefix + config.bech32AddressPrefix["account_pub"] = pubKeyPrefix +} + +// SetBech32PrefixForValidator builds the Config with Bech32 addressPrefix and publKeyPrefix for validators +// and returns the config instance +func (config *Config) SetBech32PrefixForValidator(addressPrefix, pubKeyPrefix string) { + config.assertNotSealed() + config.bech32AddressPrefix["validator_addr"] = addressPrefix + config.bech32AddressPrefix["validator_pub"] = pubKeyPrefix +} + +// SetBech32PrefixForConsensusNode builds the Config with Bech32 addressPrefix and publKeyPrefix for consensus nodes +// and returns the config instance +func (config *Config) SetBech32PrefixForConsensusNode(addressPrefix, pubKeyPrefix string) { + config.assertNotSealed() + config.bech32AddressPrefix["consensus_addr"] = addressPrefix + config.bech32AddressPrefix["consensus_pub"] = pubKeyPrefix +} + +// Seal seals the config such that the config state could not be modified further +func (config *Config) Seal() *Config { + config.mtx.Lock() + defer config.mtx.Unlock() + + config.sealed = true + return config +} + +// GetBech32AccountAddrPrefix returns the Bech32 prefix for account address +func (config *Config) GetBech32AccountAddrPrefix() string { + return config.bech32AddressPrefix["account_addr"] +} + +// GetBech32ValidatorAddrPrefix returns the Bech32 prefix for validator address +func (config *Config) GetBech32ValidatorAddrPrefix() string { + return config.bech32AddressPrefix["validator_addr"] +} + +// GetBech32ConsensusAddrPrefix returns the Bech32 prefix for consensus node address +func (config *Config) GetBech32ConsensusAddrPrefix() string { + return config.bech32AddressPrefix["consensus_addr"] +} + +// GetBech32AccountPubPrefix returns the Bech32 prefix for account public key +func (config *Config) GetBech32AccountPubPrefix() string { + return config.bech32AddressPrefix["account_pub"] +} + +// GetBech32ValidatorPubPrefix returns the Bech32 prefix for validator public key +func (config *Config) GetBech32ValidatorPubPrefix() string { + return config.bech32AddressPrefix["validator_pub"] +} + +// GetBech32ConsensusPubPrefix returns the Bech32 prefix for consensus node public key +func (config *Config) GetBech32ConsensusPubPrefix() string { + return config.bech32AddressPrefix["consensus_pub"] +} diff --git a/types/context.go b/types/context.go index f50503333a1c..bfb4c58fed10 100644 --- a/types/context.go +++ b/types/context.go @@ -187,8 +187,16 @@ func (c Context) WithBlockTime(newTime time.Time) Context { return c.WithBlockHeader(newHeader) } +func (c Context) WithProposer(addr ConsAddress) Context { + newHeader := c.BlockHeader() + newHeader.ProposerAddress = addr.Bytes() + return c.WithBlockHeader(newHeader) +} + func (c Context) WithBlockHeight(height int64) Context { - return c.withValue(contextKeyBlockHeight, height) + newHeader := c.BlockHeader() + newHeader.Height = height + return c.withValue(contextKeyBlockHeight, height).withValue(contextKeyBlockHeader, newHeader) } func (c Context) WithConsensusParams(params *abci.ConsensusParams) Context { diff --git a/types/context_test.go b/types/context_test.go index 0026912298d3..0ab6c8dfc79c 100644 --- a/types/context_test.go +++ b/types/context_test.go @@ -164,15 +164,16 @@ func TestContextWithCustom(t *testing.T) { meter := types.NewGasMeter(10000) minFees := types.Coins{types.NewInt64Coin("feeCoin", 1)} - ctx = types.NewContext(nil, header, ischeck, logger). + ctx = types.NewContext(nil, header, ischeck, logger) + require.Equal(t, header, ctx.BlockHeader()) + + ctx = ctx. WithBlockHeight(height). WithChainID(chainid). WithTxBytes(txbytes). WithVoteInfos(voteinfos). WithGasMeter(meter). WithMinimumFees(minFees) - - require.Equal(t, header, ctx.BlockHeader()) require.Equal(t, height, ctx.BlockHeight()) require.Equal(t, chainid, ctx.ChainID()) require.Equal(t, ischeck, ctx.IsCheckTx()) diff --git a/types/decimal.go b/types/decimal.go index 13a8a26c1523..3c0e89f60ba5 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -241,30 +241,45 @@ func (d Dec) Quo(d2 Dec) Dec { return Dec{chopped} } -func (d Dec) String() string { - str := d.ToLeftPaddedWithDecimals(Precision) - placement := len(str) - Precision - if placement < 0 { - panic("too few decimal digits") - } - return str[:placement] + "." + str[placement:] +// quotient +func (d Dec) QuoInt(i Int) Dec { + mul := new(big.Int).Quo(d.Int, i.i) + return Dec{mul} } -// TODO panic if negative or if totalDigits < len(initStr)??? -// evaluate as an integer and return left padded string -func (d Dec) ToLeftPaddedWithDecimals(totalDigits int8) string { - intStr := d.Int.String() - fcode := `%0` + strconv.Itoa(int(totalDigits)) + `s` - return fmt.Sprintf(fcode, intStr) +// is integer, e.g. decimals are zero +func (d Dec) IsInteger() bool { + return new(big.Int).Rem(d.Int, precisionReuse).Sign() == 0 } -// TODO panic if negative or if totalDigits < len(initStr)??? -// evaluate as an integer and return left padded string -func (d Dec) ToLeftPadded(totalDigits int8) string { - chopped := chopPrecisionAndRoundNonMutative(d.Int) - intStr := chopped.String() - fcode := `%0` + strconv.Itoa(int(totalDigits)) + `s` - return fmt.Sprintf(fcode, intStr) +func (d Dec) String() string { + bz, err := d.Int.MarshalText() + if err != nil { + return "" + } + var bzWDec []byte + inputSize := len(bz) + // TODO: Remove trailing zeros + // case 1, purely decimal + if inputSize <= 10 { + bzWDec = make([]byte, 12) + // 0. prefix + bzWDec[0] = byte('0') + bzWDec[1] = byte('.') + // set relevant digits to 0 + for i := 0; i < 10-inputSize; i++ { + bzWDec[i+2] = byte('0') + } + // set last few digits + copy(bzWDec[2+(10-inputSize):], bz) + } 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:]) + } + return string(bzWDec) } // ____ @@ -401,17 +416,13 @@ func (d *Dec) UnmarshalAmino(text string) (err error) { return nil } -// MarshalJSON defines custom encoding scheme +// MarshalJSON marshals the decimal func (d Dec) MarshalJSON() ([]byte, error) { if d.Int == nil { return nilJSON, nil } - bz, err := d.Int.MarshalText() - if err != nil { - return nil, err - } - return json.Marshal(string(bz)) + return json.Marshal(d.String()) } // UnmarshalJSON defines custom decoding scheme @@ -425,7 +436,13 @@ func (d *Dec) UnmarshalJSON(bz []byte) error { if err != nil { return err } - return d.Int.UnmarshalText([]byte(text)) + // TODO: Reuse dec allocation + newDec, err := NewDecFromStr(text) + if err != nil { + return err + } + d.Int = newDec.Int + return nil } //___________________________________________________________________________________ @@ -462,6 +479,6 @@ func MaxDec(d1, d2 Dec) Dec { } // intended to be used with require/assert: require.True(DecEq(...)) -func DecEq(t *testing.T, exp, got Dec) (*testing.T, bool, string, Dec, Dec) { - return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got +func DecEq(t *testing.T, exp, got Dec) (*testing.T, bool, string, string, string) { + return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String() } diff --git a/types/decimal_test.go b/types/decimal_test.go index a6ec0740e819..b3589477134f 100644 --- a/types/decimal_test.go +++ b/types/decimal_test.go @@ -4,6 +4,8 @@ import ( "math/big" "testing" + "github.com/stretchr/testify/assert" + "github.com/cosmos/cosmos-sdk/codec" "github.com/stretchr/testify/require" ) @@ -228,26 +230,46 @@ func TestTruncate(t *testing.T) { } } -func TestToLeftPadded(t *testing.T) { +var cdc = codec.New() + +func TestDecMarshalJSON(t *testing.T) { + decimal := func(i int64) Dec { + d := NewDec(0) + d.Int = new(big.Int).SetInt64(i) + return d + } tests := []struct { - dec Dec - digits int8 - exp string + name string + d Dec + want string + wantErr bool // if wantErr = false, will also attempt unmarshaling }{ - {mustNewDecFromStr(t, "33.3"), 8, "00000033"}, - {mustNewDecFromStr(t, "50"), 8, "00000050"}, - {mustNewDecFromStr(t, "333"), 8, "00000333"}, - {mustNewDecFromStr(t, "333"), 12, "000000000333"}, - {mustNewDecFromStr(t, "0.3333"), 8, "00000000"}, + {"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}, } - for tcIndex, tc := range tests { - res := tc.dec.ToLeftPadded(tc.digits) - require.Equal(t, tc.exp, res, "incorrect left padding, tc %d", tcIndex) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.d.MarshalJSON() + if (err != nil) != tt.wantErr { + t.Errorf("Dec.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + assert.Equal(t, tt.want, string(got), "incorrect marshalled value") + unmarshalledDec := NewDec(0) + unmarshalledDec.UnmarshalJSON(got) + assert.Equal(t, tt.d, unmarshalledDec, "incorrect unmarshalled value") + } + }) } } -var cdc = codec.New() - func TestZeroDeserializationJSON(t *testing.T) { d := Dec{new(big.Int)} err := cdc.UnmarshalJSON([]byte(`"0"`), &d) @@ -283,11 +305,11 @@ func TestSerializationGocodecJSON(t *testing.T) { func TestSerializationGocodecBinary(t *testing.T) { d := mustNewDecFromStr(t, "0.333") - bz, err := cdc.MarshalBinary(d) + bz, err := cdc.MarshalBinaryLengthPrefixed(d) require.NoError(t, err) var d2 Dec - err = cdc.UnmarshalBinary(bz, &d2) + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &d2) require.NoError(t, err) require.True(t, d.Equal(d2), "original: %v, unmarshalled: %v", d, d2) } @@ -301,11 +323,11 @@ type testDEmbedStruct struct { // TODO make work for UnmarshalJSON func TestEmbeddedStructSerializationGocodec(t *testing.T) { obj := testDEmbedStruct{"foo", 10, NewDecWithPrec(1, 3)} - bz, err := cdc.MarshalBinary(obj) + bz, err := cdc.MarshalBinaryLengthPrefixed(obj) require.Nil(t, err) var obj2 testDEmbedStruct - err = cdc.UnmarshalBinary(bz, &obj2) + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &obj2) require.Nil(t, err) require.Equal(t, obj.Field1, obj2.Field1) diff --git a/types/errors.go b/types/errors.go index 266e6d14c331..e05800b539dd 100644 --- a/types/errors.go +++ b/types/errors.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "strings" "github.com/cosmos/cosmos-sdk/codec" cmn "github.com/tendermint/tendermint/libs/common" @@ -286,7 +287,34 @@ func (err *sdkError) QueryResult() abci.ResponseQuery { } } -// nolint +//---------------------------------------- +// REST error utilities + +// appends a message to the head of the given error +func AppendMsgToErr(msg string, err string) string { + msgIdx := strings.Index(err, "message\":\"") + if msgIdx != -1 { + errMsg := err[msgIdx+len("message\":\"") : len(err)-2] + errMsg = fmt.Sprintf("%s; %s", msg, errMsg) + return fmt.Sprintf("%s%s%s", + err[:msgIdx+len("message\":\"")], + errMsg, + err[len(err)-2:], + ) + } + return fmt.Sprintf("%s; %s", msg, err) +} + +// returns the index of the message in the ABCI Log +func mustGetMsgIndex(abciLog string) int { + msgIdx := strings.Index(abciLog, "message\":\"") + if msgIdx == -1 { + panic(fmt.Sprintf("invalid error format: %s", abciLog)) + } + return msgIdx + len("message\":\"") +} + +// parses the error into an object-like struct for exporting type humanReadableError struct { Codespace CodespaceType `json:"codespace"` Code CodeType `json:"code"` diff --git a/types/errors_test.go b/types/errors_test.go index f00b2600ca85..1d63e09908e2 100644 --- a/types/errors_test.go +++ b/types/errors_test.go @@ -1,6 +1,7 @@ package types import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -65,3 +66,28 @@ func TestErrFn(t *testing.T) { require.Equal(t, ABCICodeOK, ToABCICode(CodespaceRoot, CodeOK)) } + +func TestAppendMsgToErr(t *testing.T) { + for i, errFn := range errFns { + err := errFn("") + errMsg := err.Stacktrace().Error() + abciLog := err.ABCILog() + + // plain msg error + msg := AppendMsgToErr("something unexpected happened", errMsg) + require.Equal(t, fmt.Sprintf("something unexpected happened; %s", + errMsg), + msg, + fmt.Sprintf("Should have formatted the error message of ABCI Log. tc #%d", i)) + + // ABCI Log msg error + msg = AppendMsgToErr("something unexpected happened", abciLog) + msgIdx := mustGetMsgIndex(abciLog) + require.Equal(t, fmt.Sprintf("%s%s; %s}", + abciLog[:msgIdx], + "something unexpected happened", + abciLog[msgIdx:len(abciLog)-1]), + msg, + fmt.Sprintf("Should have formatted the error message of ABCI Log. tc #%d", i)) + } +} diff --git a/types/int.go b/types/int.go index 1421a934df66..c2bef7a64fcb 100644 --- a/types/int.go +++ b/types/int.go @@ -2,6 +2,7 @@ package types import ( "encoding/json" + "testing" "math/big" "math/rand" @@ -525,3 +526,10 @@ func (i *Uint) UnmarshalJSON(bz []byte) error { } return unmarshalJSON(i.i, bz) } + +//__________________________________________________________________________ + +// intended to be used with require/assert: require.True(IntEq(...)) +func IntEq(t *testing.T, exp, got Int) (*testing.T, bool, string, string, string) { + return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String() +} diff --git a/types/lib/linear.go b/types/lib/linear.go deleted file mode 100644 index 1c25f4eb43a6..000000000000 --- a/types/lib/linear.go +++ /dev/null @@ -1,254 +0,0 @@ -package lib - -import ( - "fmt" - "strconv" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Linear defines a primitive mapper type -type Linear struct { - cdc *codec.Codec - store sdk.KVStore - keys *LinearKeys -} - -// LinearKeys defines keysions for the key bytes -type LinearKeys struct { - LengthKey []byte - ElemKey []byte - TopKey []byte -} - -// Should never be modified -var cachedDefaultLinearKeys = DefaultLinearKeys() - -// DefaultLinearKeys returns the default setting of LinearOption -func DefaultLinearKeys() *LinearKeys { - keys := LinearKeys{ - LengthKey: []byte{0x00}, - ElemKey: []byte{0x01}, - TopKey: []byte{0x02}, - } - return &keys -} - -// NewLinear constructs new Linear -func NewLinear(cdc *codec.Codec, store sdk.KVStore, keys *LinearKeys) Linear { - if keys == nil { - keys = cachedDefaultLinearKeys - } - if keys.LengthKey == nil || keys.ElemKey == nil || keys.TopKey == nil { - panic("Invalid LinearKeys") - } - return Linear{ - cdc: cdc, - store: store, - keys: keys, - } -} - -// List is a Linear interface that provides list-like functions -// It panics when the element type cannot be (un/)marshalled by the codec -type List interface { - - // Len() returns the length of the list - // The length is only increased by Push() and not decreased - // List dosen't check if an index is in bounds - // The user should check Len() before doing any actions - Len() uint64 - - // Get() returns the element by its index - Get(uint64, interface{}) error - - // Set() stores the element to the given position - // Setting element out of range will break length counting - // Use Push() instead of Set() to append a new element - Set(uint64, interface{}) - - // Delete() deletes the element in the given position - // Other elements' indices are preserved after deletion - // Panics when the index is out of range - Delete(uint64) - - // Push() inserts the element to the end of the list - // It will increase the length when it is called - Push(interface{}) - - // Iterate*() is used to iterate over all existing elements in the list - // Return true in the continuation to break - // The second element of the continuation will indicate the position of the element - // Using it with Get() will return the same one with the provided element - - // CONTRACT: No writes may happen within a domain while iterating over it. - Iterate(interface{}, func(uint64) bool) -} - -// NewList constructs new List -func NewList(cdc *codec.Codec, store sdk.KVStore, keys *LinearKeys) List { - return NewLinear(cdc, store, keys) -} - -// Key for the length of the list -func (m Linear) LengthKey() []byte { - return m.keys.LengthKey -} - -// Key for the elements of the list -func (m Linear) ElemKey(index uint64) []byte { - return append(m.keys.ElemKey, []byte(fmt.Sprintf("%020d", index))...) -} - -// Len implements List -func (m Linear) Len() (res uint64) { - bz := m.store.Get(m.LengthKey()) - if bz == nil { - return 0 - } - m.cdc.MustUnmarshalBinary(bz, &res) - return -} - -// Get implements List -func (m Linear) Get(index uint64, ptr interface{}) error { - bz := m.store.Get(m.ElemKey(index)) - return m.cdc.UnmarshalBinary(bz, ptr) -} - -// Set implements List -func (m Linear) Set(index uint64, value interface{}) { - bz := m.cdc.MustMarshalBinary(value) - m.store.Set(m.ElemKey(index), bz) -} - -// Delete implements List -func (m Linear) Delete(index uint64) { - m.store.Delete(m.ElemKey(index)) -} - -// Push implements List -func (m Linear) Push(value interface{}) { - length := m.Len() - m.Set(length, value) - m.store.Set(m.LengthKey(), m.cdc.MustMarshalBinary(length+1)) -} - -// IterateRead implements List -func (m Linear) Iterate(ptr interface{}, fn func(uint64) bool) { - iter := sdk.KVStorePrefixIterator(m.store, []byte{0x01}) - for ; iter.Valid(); iter.Next() { - v := iter.Value() - m.cdc.MustUnmarshalBinary(v, ptr) - k := iter.Key() - s := string(k[len(k)-20:]) - index, err := strconv.ParseUint(s, 10, 64) - if err != nil { - panic(err) - } - if fn(index) { - break - } - } - - iter.Close() -} - -// Queue is a Linear interface that provides queue-like functions -// It panics when the element type cannot be (un/)marshalled by the codec -type Queue interface { - // Push() inserts the elements to the rear of the queue - Push(interface{}) - - // Popping/Peeking on an empty queue will cause panic - // The user should check IsEmpty() before doing any actions - - // Peek() returns the element at the front of the queue without removing it - Peek(interface{}) error - - // Pop() returns the element at the front of the queue and removes it - Pop() - - // IsEmpty() checks if the queue is empty - IsEmpty() bool - - // Flush() removes elements it processed - // Return true in the continuation to break - // The interface{} is unmarshalled before the continuation is called - // Starts from the top(head) of the queue - // CONTRACT: Pop() or Push() should not be performed while flushing - Flush(interface{}, func() bool) -} - -// NewQueue constructs new Queue -func NewQueue(cdc *codec.Codec, store sdk.KVStore, keys *LinearKeys) Queue { - return NewLinear(cdc, store, keys) -} - -// Key for the top element position in the queue -func (m Linear) TopKey() []byte { - return m.keys.TopKey -} - -func (m Linear) getTop() (res uint64) { - bz := m.store.Get(m.TopKey()) - if bz == nil { - return 0 - } - - m.cdc.MustUnmarshalBinary(bz, &res) - return -} - -func (m Linear) setTop(top uint64) { - bz := m.cdc.MustMarshalBinary(top) - m.store.Set(m.TopKey(), bz) -} - -// Peek implements Queue -func (m Linear) Peek(ptr interface{}) error { - top := m.getTop() - return m.Get(top, ptr) -} - -// Pop implements Queue -func (m Linear) Pop() { - top := m.getTop() - m.Delete(top) - m.setTop(top + 1) -} - -// IsEmpty implements Queue -func (m Linear) IsEmpty() bool { - top := m.getTop() - length := m.Len() - return top >= length -} - -// Flush implements Queue -func (m Linear) Flush(ptr interface{}, fn func() bool) { - top := m.getTop() - length := m.Len() - - var i uint64 - for i = top; i < length; i++ { - err := m.Get(i, ptr) - if err != nil { - // TODO: Handle with #870 - panic(err) - } - m.Delete(i) - if fn() { - break - } - } - m.setTop(i) -} - -func subspace(prefix []byte) (start, end []byte) { - end = make([]byte, len(prefix)) - copy(end, prefix) - end[len(end)-1]++ - return prefix, end -} diff --git a/types/lib/linear_test.go b/types/lib/linear_test.go deleted file mode 100644 index d19c0406136c..000000000000 --- a/types/lib/linear_test.go +++ /dev/null @@ -1,169 +0,0 @@ -package lib - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" - - abci "github.com/tendermint/tendermint/abci/types" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type S struct { - I uint64 - B bool -} - -func defaultComponents(key sdk.StoreKey) (sdk.Context, *codec.Codec) { - db := dbm.NewMemDB() - cms := store.NewCommitMultiStore(db) - cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) - cms.LoadLatestVersion() - ctx := sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) - cdc := codec.New() - return ctx, cdc -} - -func TestNewLinear(t *testing.T) { - cdc := codec.New() - require.NotPanics(t, func() { NewLinear(cdc, nil, nil) }) - require.NotPanics(t, func() { NewLinear(cdc, nil, DefaultLinearKeys()) }) - require.NotPanics(t, func() { NewLinear(cdc, nil, &LinearKeys{[]byte{0xAA}, []byte{0xBB}, []byte{0xCC}}) }) - - require.Panics(t, func() { NewLinear(cdc, nil, &LinearKeys{nil, nil, nil}) }) - require.Panics(t, func() { NewLinear(cdc, nil, &LinearKeys{[]byte{0xAA}, nil, nil}) }) - require.Panics(t, func() { NewLinear(cdc, nil, &LinearKeys{nil, []byte{0xBB}, nil}) }) - require.Panics(t, func() { NewLinear(cdc, nil, &LinearKeys{nil, nil, []byte{0xCC}}) }) -} - -func TestList(t *testing.T) { - key := sdk.NewKVStoreKey("test") - ctx, cdc := defaultComponents(key) - store := ctx.KVStore(key) - lm := NewList(cdc, store, nil) - - val := S{1, true} - var res S - - lm.Push(val) - require.Equal(t, uint64(1), lm.Len()) - lm.Get(uint64(0), &res) - require.Equal(t, val, res) - - val = S{2, false} - lm.Set(uint64(0), val) - lm.Get(uint64(0), &res) - require.Equal(t, val, res) - - val = S{100, false} - lm.Push(val) - require.Equal(t, uint64(2), lm.Len()) - lm.Get(uint64(1), &res) - require.Equal(t, val, res) - - lm.Delete(uint64(1)) - require.Equal(t, uint64(2), lm.Len()) - - lm.Iterate(&res, func(index uint64) (brk bool) { - var temp S - lm.Get(index, &temp) - require.Equal(t, temp, res) - - require.True(t, index != 1) - return - }) - - lm.Iterate(&res, func(index uint64) (brk bool) { - lm.Set(index, S{res.I + 1, !res.B}) - return - }) - - lm.Get(uint64(0), &res) - require.Equal(t, S{3, true}, res) -} - -func TestQueue(t *testing.T) { - key := sdk.NewKVStoreKey("test") - ctx, cdc := defaultComponents(key) - store := ctx.KVStore(key) - - qm := NewQueue(cdc, store, nil) - - val := S{1, true} - var res S - - qm.Push(val) - qm.Peek(&res) - require.Equal(t, val, res) - - qm.Pop() - empty := qm.IsEmpty() - - require.True(t, empty) - require.NotNil(t, qm.Peek(&res)) - - qm.Push(S{1, true}) - qm.Push(S{2, true}) - qm.Push(S{3, true}) - qm.Flush(&res, func() (brk bool) { - if res.I == 3 { - brk = true - } - return - }) - - require.False(t, qm.IsEmpty()) - - qm.Pop() - require.True(t, qm.IsEmpty()) -} - -func TestOptions(t *testing.T) { - key := sdk.NewKVStoreKey("test") - ctx, cdc := defaultComponents(key) - store := ctx.KVStore(key) - - keys := &LinearKeys{ - LengthKey: []byte{0xDE, 0xAD}, - ElemKey: []byte{0xBE, 0xEF}, - TopKey: []byte{0x12, 0x34}, - } - linear := NewLinear(cdc, store, keys) - - for i := 0; i < 10; i++ { - linear.Push(i) - } - - var len uint64 - var top uint64 - var expected int - var actual int - - // Checking keys.LengthKey - err := cdc.UnmarshalBinary(store.Get(keys.LengthKey), &len) - require.Nil(t, err) - require.Equal(t, len, linear.Len()) - - // Checking keys.ElemKey - for i := 0; i < 10; i++ { - linear.Get(uint64(i), &expected) - bz := store.Get(append(keys.ElemKey, []byte(fmt.Sprintf("%020d", i))...)) - err = cdc.UnmarshalBinary(bz, &actual) - require.Nil(t, err) - require.Equal(t, expected, actual) - } - - linear.Pop() - - err = cdc.UnmarshalBinary(store.Get(keys.TopKey), &top) - require.Nil(t, err) - require.Equal(t, top, linear.getTop()) - -} diff --git a/types/stake.go b/types/stake.go index c84ed8d0540f..38f1c0c9baab 100644 --- a/types/stake.go +++ b/types/stake.go @@ -44,6 +44,7 @@ type Validator interface { GetConsAddr() ConsAddress // validation consensus address GetPower() Dec // validation power GetTokens() Dec // validation tokens + GetCommission() Dec // validator commission rate GetDelegatorShares() Dec // Total out standing delegator shares GetBondHeight() int64 // height in which the validator became active } @@ -63,7 +64,11 @@ type ValidatorSet interface { func(index int64, validator Validator) (stop bool)) // iterate through bonded validators by operator address, execute func for each validator - IterateValidatorsBonded(Context, + IterateBondedValidatorsByPower(Context, + func(index int64, validator Validator) (stop bool)) + + // iterate through the consensus validator set of the last block by operator address, execute func for each validator + IterateLastValidators(Context, func(index int64, validator Validator) (stop bool)) Validator(Context, ValAddress) Validator // get a particular validator by operator address @@ -84,9 +89,9 @@ type ValidatorSet interface { // delegation bond for a delegated proof of stake system type Delegation interface { - GetDelegator() AccAddress // delegator AccAddress for the bond - GetValidator() ValAddress // validator operator address - GetShares() Dec // amount of validator's shares held in this delegation + GetDelegatorAddr() AccAddress // delegator AccAddress for the bond + GetValidatorAddr() ValAddress // validator operator address + GetShares() Dec // amount of validator's shares held in this delegation } // properties for the set of all delegations for a particular @@ -110,12 +115,13 @@ type DelegationSet interface { // event hooks for staking validator object type StakingHooks interface { - OnValidatorCreated(ctx Context, address ValAddress) // Must be called when a validator is created - OnValidatorCommissionChange(ctx Context, address ValAddress) // Must be called when a validator's commission is modified - OnValidatorRemoved(ctx Context, address ValAddress) // Must be called when a validator is deleted + 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, address ConsAddress) // Must be called when a validator is bonded - OnValidatorBeginUnbonding(ctx Context, address ConsAddress) // Must be called when a validator begins unbonding + 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 diff --git a/types/store.go b/types/store.go index 7bab93b97e60..8fe0321f5c7c 100644 --- a/types/store.go +++ b/types/store.go @@ -1,6 +1,7 @@ package types import ( + "bytes" "fmt" "io" @@ -127,7 +128,7 @@ type KVStore interface { // Has checks if a key exists. Panics on nil key. Has(key []byte) bool - // Set sets the key. Panics on nil key. + // Set sets the key. Panics on nil key or value. Set(key, value []byte) // Delete deletes the key. Panics on nil key. @@ -176,6 +177,44 @@ func KVStoreReversePrefixIterator(kvs KVStore, prefix []byte) Iterator { return kvs.ReverseIterator(prefix, PrefixEndBytes(prefix)) } +// Compare two KVstores, return either the first key/value pair +// at which they differ and whether or not they are equal, skipping +// value comparison for a set of provided prefixes +func DiffKVStores(a KVStore, b KVStore, prefixesToSkip [][]byte) (kvA cmn.KVPair, kvB cmn.KVPair, count int64, equal bool) { + iterA := a.Iterator(nil, nil) + iterB := b.Iterator(nil, nil) + count = int64(0) + for { + if !iterA.Valid() && !iterB.Valid() { + break + } + var kvA, kvB cmn.KVPair + if iterA.Valid() { + kvA = cmn.KVPair{Key: iterA.Key(), Value: iterA.Value()} + iterA.Next() + } + if iterB.Valid() { + kvB = cmn.KVPair{Key: iterB.Key(), Value: iterB.Value()} + iterB.Next() + } + if !bytes.Equal(kvA.Key, kvB.Key) { + return kvA, kvB, count, false + } + compareValue := true + for _, prefix := range prefixesToSkip { + // Skip value comparison if we matched a prefix + if bytes.Equal(kvA.Key[:len(prefix)], prefix) { + compareValue = false + } + } + if compareValue && !bytes.Equal(kvA.Value, kvB.Value) { + return kvA, kvB, count, false + } + count++ + } + return cmn.KVPair{}, cmn.KVPair{}, count, true +} + // CacheKVStore cache-wraps a KVStore. After calling .Write() on // the CacheKVStore, all previously created CacheKVStores on the // object expire. diff --git a/types/tx_msg.go b/types/tx_msg.go index 9b4aab9370cd..7882b25d3193 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -9,11 +9,11 @@ type Msg interface { // Return the message type. // Must be alphanumeric or empty. - Type() string + Route() string // Returns a human-readable string for the message, intended for utilization // within tags - Name() string + Type() string // ValidateBasic does a simple validation check that // doesn't require access to any other information. @@ -58,8 +58,8 @@ func NewTestMsg(addrs ...AccAddress) *TestMsg { } //nolint -func (msg *TestMsg) Type() string { return "TestMsg" } -func (msg *TestMsg) Name() string { return "Test message" } +func (msg *TestMsg) Route() string { return "TestMsg" } +func (msg *TestMsg) Type() string { return "Test message" } func (msg *TestMsg) GetSignBytes() []byte { bz, err := json.Marshal(msg.signers) if err != nil { diff --git a/types/utils.go b/types/utils.go index b196acb230c0..91cdcf9b0384 100644 --- a/types/utils.go +++ b/types/utils.go @@ -1,7 +1,10 @@ package types import ( + "encoding/binary" "encoding/json" + "time" + tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" tmtypes "github.com/tendermint/tendermint/types" ) @@ -34,6 +37,31 @@ func MustSortJSON(toSortJSON []byte) []byte { return js } +// Uint64ToBigEndian - marshals uint64 to a bigendian byte slice so it can be sorted +func Uint64ToBigEndian(i uint64) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, i) + return b +} + +// Slight modification of the RFC3339Nano but it right pads all zeros and drops the time zone info +const SortableTimeFormat = "2006-01-02T15:04:05.000000000" + +// Formats a time.Time into a []byte that can be sorted +func FormatTimeBytes(t time.Time) []byte { + return []byte(t.UTC().Round(0).Format(SortableTimeFormat)) +} + +// Parses a []byte encoded using FormatTimeKey back into a time.Time +func ParseTimeBytes(bz []byte) (time.Time, error) { + str := string(bz) + t, err := time.Parse(SortableTimeFormat, str) + if err != nil { + return t, err + } + 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. // diff --git a/types/utils_test.go b/types/utils_test.go index 05bc622e72e9..f3930d15211f 100644 --- a/types/utils_test.go +++ b/types/utils_test.go @@ -2,6 +2,7 @@ package types import ( "testing" + "time" "github.com/stretchr/testify/require" ) @@ -20,8 +21,8 @@ func TestSortJSON(t *testing.T) { want: "", wantErr: true}, // genesis.json - {unsortedJSON: `{"consensus_params":{"block_size_params":{"max_bytes":22020096,"max_txs":100000,"max_gas":-1},"tx_size_params":{"max_bytes":10240,"max_gas":-1},"block_gossip_params":{"block_part_size_bytes":65536},"evidence_params":{"max_age":100000}},"validators":[{"pub_key":{"type":"AC26791624DE60","value":"c7UMMAbjFuc5GhGPy0E5q5tefy12p9Tq0imXqdrKXwo="},"power":100,"name":""}],"app_hash":"","genesis_time":"2018-05-11T15:52:25.424795506Z","chain_id":"test-chain-Q6VeoW","app_state":{"accounts":[{"address":"718C9C23F98C9642569742ADDD9F9AB9743FBD5D","coins":[{"denom":"Token","amount":1000},{"denom":"steak","amount":50}]}],"stake":{"pool":{"total_supply":50,"bonded_shares":"0","unbonded_shares":"0","bonded_pool":0,"unbonded_pool":0,"inflation_last_time":0,"inflation":"7/100"},"params":{"inflation_rate_change":"13/100","inflation_max":"1/5","inflation_min":"7/100","goal_bonded":"67/100","max_validators":100,"bond_denom":"steak"},"candidates":null,"bonds":null}}}`, - want: `{"app_hash":"","app_state":{"accounts":[{"address":"718C9C23F98C9642569742ADDD9F9AB9743FBD5D","coins":[{"amount":1000,"denom":"Token"},{"amount":50,"denom":"steak"}]}],"stake":{"bonds":null,"candidates":null,"params":{"bond_denom":"steak","goal_bonded":"67/100","inflation_max":"1/5","inflation_min":"7/100","inflation_rate_change":"13/100","max_validators":100},"pool":{"bonded_pool":0,"bonded_shares":"0","inflation":"7/100","inflation_last_time":0,"total_supply":50,"unbonded_pool":0,"unbonded_shares":"0"}}},"chain_id":"test-chain-Q6VeoW","consensus_params":{"block_gossip_params":{"block_part_size_bytes":65536},"block_size_params":{"max_bytes":22020096,"max_gas":-1,"max_txs":100000},"evidence_params":{"max_age":100000},"tx_size_params":{"max_bytes":10240,"max_gas":-1}},"genesis_time":"2018-05-11T15:52:25.424795506Z","validators":[{"name":"","power":100,"pub_key":{"type":"AC26791624DE60","value":"c7UMMAbjFuc5GhGPy0E5q5tefy12p9Tq0imXqdrKXwo="}}]}`, + {unsortedJSON: `{"consensus_params":{"block_size_params":{"max_bytes":22020096,"max_txs":100000,"max_gas":-1},"tx_size_params":{"max_bytes":10240,"max_gas":-1},"block_gossip_params":{"block_part_size_bytes":65536},"evidence_params":{"max_age":100000}},"validators":[{"pub_key":{"type":"AC26791624DE60","value":"c7UMMAbjFuc5GhGPy0E5q5tefy12p9Tq0imXqdrKXwo="},"power":100,"name":""}],"app_hash":"","genesis_time":"2018-05-11T15:52:25.424795506Z","chain_id":"test-chain-Q6VeoW","app_state":{"accounts":[{"address":"718C9C23F98C9642569742ADDD9F9AB9743FBD5D","coins":[{"denom":"Token","amount":1000},{"denom":"stake","amount":50}]}],"stake":{"pool":{"total_supply":50,"bonded_shares":"0","unbonded_shares":"0","bonded_pool":0,"unbonded_pool":0,"inflation_last_time":0,"inflation":"7/100"},"params":{"inflation_rate_change":"13/100","inflation_max":"1/5","inflation_min":"7/100","goal_bonded":"67/100","max_validators":100,"bond_denom":"stake"},"candidates":null,"bonds":null}}}`, + want: `{"app_hash":"","app_state":{"accounts":[{"address":"718C9C23F98C9642569742ADDD9F9AB9743FBD5D","coins":[{"amount":1000,"denom":"Token"},{"amount":50,"denom":"stake"}]}],"stake":{"bonds":null,"candidates":null,"params":{"bond_denom":"stake","goal_bonded":"67/100","inflation_max":"1/5","inflation_min":"7/100","inflation_rate_change":"13/100","max_validators":100},"pool":{"bonded_pool":0,"bonded_shares":"0","inflation":"7/100","inflation_last_time":0,"total_supply":50,"unbonded_pool":0,"unbonded_shares":"0"}}},"chain_id":"test-chain-Q6VeoW","consensus_params":{"block_gossip_params":{"block_part_size_bytes":65536},"block_size_params":{"max_bytes":22020096,"max_gas":-1,"max_txs":100000},"evidence_params":{"max_age":100000},"tx_size_params":{"max_bytes":10240,"max_gas":-1}},"genesis_time":"2018-05-11T15:52:25.424795506Z","validators":[{"name":"","power":100,"pub_key":{"type":"AC26791624DE60","value":"c7UMMAbjFuc5GhGPy0E5q5tefy12p9Tq0imXqdrKXwo="}}]}`, wantErr: false}, // from the TXSpec: {unsortedJSON: `{"chain_id":"test-chain-1","sequence":1,"fee_bytes":{"amount":[{"amount":5,"denom":"photon"}],"gas":10000},"msg_bytes":{"inputs":[{"address":"696E707574","coins":[{"amount":10,"denom":"atom"}]}],"outputs":[{"address":"6F7574707574","coins":[{"amount":10,"denom":"atom"}]}]},"alt_bytes":null}`, @@ -43,3 +44,23 @@ func TestSortJSON(t *testing.T) { require.Equal(t, string(got), tc.want) } } + +func TestTimeFormatAndParse(t *testing.T) { + cases := []struct { + RFC3339NanoStr string + SDKSortableTimeStr string + Equal bool + }{ + {"2009-11-10T23:00:00Z", "2009-11-10T23:00:00.000000000", true}, + {"2011-01-10T23:10:05.758230235Z", "2011-01-10T23:10:05.758230235", true}, + } + for _, tc := range cases { + timeFromRFC, err := time.Parse(time.RFC3339Nano, tc.RFC3339NanoStr) + require.Nil(t, err) + timeFromSDKFormat, err := time.Parse(SortableTimeFormat, tc.SDKSortableTimeStr) + require.Nil(t, err) + + require.True(t, timeFromRFC.Equal(timeFromSDKFormat)) + require.Equal(t, timeFromRFC.Format(SortableTimeFormat), tc.SDKSortableTimeStr) + } +} diff --git a/x/auth/account_test.go b/x/auth/account_test.go index b7a78e2d2220..e48060fbefc9 100644 --- a/x/auth/account_test.go +++ b/x/auth/account_test.go @@ -93,16 +93,16 @@ func TestBaseAccountMarshal(t *testing.T) { cdc := codec.New() codec.RegisterCrypto(cdc) - b, err := cdc.MarshalBinary(acc) + b, err := cdc.MarshalBinaryLengthPrefixed(acc) require.Nil(t, err) acc2 := BaseAccount{} - err = cdc.UnmarshalBinary(b, &acc2) + err = cdc.UnmarshalBinaryLengthPrefixed(b, &acc2) require.Nil(t, err) require.Equal(t, acc, acc2) // error on bad bytes acc2 = BaseAccount{} - err = cdc.UnmarshalBinary(b[:len(b)/2], &acc2) + err = cdc.UnmarshalBinaryLengthPrefixed(b[:len(b)/2], &acc2) require.NotNil(t, err) } diff --git a/x/auth/ante.go b/x/auth/ante.go index 68f6f65408ee..e88a20a43499 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/hex" "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" @@ -23,7 +22,7 @@ const ( // NewAnteHandler returns an AnteHandler that checks // and increments sequence numbers, checks signatures & account numbers, // and deducts fees from the first signer. -func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { +func NewAnteHandler(am AccountKeeper, fck FeeCollectionKeeper) sdk.AnteHandler { return func( ctx sdk.Context, tx sdk.Tx, simulate bool, ) (newCtx sdk.Context, res sdk.Result, abort bool) { @@ -81,7 +80,7 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { if !res.IsOK() { return newCtx, res, true } - res = validateAccNumAndSequence(signerAccs, stdSigs) + res = validateAccNumAndSequence(ctx, signerAccs, stdSigs) if !res.IsOK() { return newCtx, res, true } @@ -93,7 +92,7 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { if !res.IsOK() { return newCtx, res, true } - fck.addCollectedFees(newCtx, stdTx.Fee.Amount) + fck.AddCollectedFees(newCtx, stdTx.Fee.Amount) } for i := 0; i < len(stdSigs); i++ { @@ -138,7 +137,7 @@ func validateBasic(tx StdTx) (err sdk.Error) { return nil } -func getSignerAccs(ctx sdk.Context, am AccountMapper, addrs []sdk.AccAddress) (accs []Account, res sdk.Result) { +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]) @@ -149,17 +148,23 @@ func getSignerAccs(ctx sdk.Context, am AccountMapper, addrs []sdk.AccAddress) (a return } -func validateAccNumAndSequence(accs []Account, sigs []StdSignature) sdk.Result { +func validateAccNumAndSequence(ctx sdk.Context, accs []Account, sigs []StdSignature) sdk.Result { for i := 0; i < len(accs); i++ { - accnum := accs[i].GetAccountNumber() - seq := accs[i].GetSequence() + // On InitChain, make sure account number == 0 + if ctx.BlockHeight() == 0 && sigs[i].AccountNumber != 0 { + return sdk.ErrInvalidSequence( + fmt.Sprintf("Invalid account number for BlockHeight == 0. Got %d, expected 0", sigs[i].AccountNumber)).Result() + } + // Check account number. - if accnum != sigs[i].AccountNumber { + accnum := accs[i].GetAccountNumber() + if ctx.BlockHeight() != 0 && accnum != sigs[i].AccountNumber { return sdk.ErrInvalidSequence( fmt.Sprintf("Invalid account number. Got %d, expected %d", sigs[i].AccountNumber, accnum)).Result() } // Check sequence number. + seq := accs[i].GetSequence() if seq != sigs[i].Sequence { return sdk.ErrInvalidSequence( fmt.Sprintf("Invalid sequence. Got %d, expected %d", sigs[i].Sequence, seq)).Result() @@ -252,7 +257,7 @@ func adjustFeesByGas(fees sdk.Coins, gas int64) sdk.Coins { } // Deduct the fee from the account. -// We could use the CoinKeeper (in addition to the AccountMapper, +// 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) { coins := acc.GetCoins() @@ -277,7 +282,8 @@ func ensureSufficientMempoolFees(ctx sdk.Context, stdTx StdTx) sdk.Result { // TODO: Make the gasPrice not a constant, and account for tx size. requiredFees := adjustFeesByGas(ctx.MinimumFees(), stdTx.Fee.Gas) - if !ctx.MinimumFees().IsZero() && stdTx.Fee.Amount.IsLT(requiredFees) { + // 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() @@ -287,7 +293,7 @@ func ensureSufficientMempoolFees(ctx sdk.Context, stdTx StdTx) sdk.Result { func setGasMeter(simulate bool, ctx sdk.Context, stdTx StdTx) sdk.Context { // set the gas meter - if simulate { + if simulate || ctx.BlockHeight() == 0 { return ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) } return ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas)) @@ -302,6 +308,3 @@ func getSignBytesList(chainID string, stdTx StdTx, stdSigs []StdSignature) (sign } return } - -// BurnFeeHandler burns all fees (decreasing total supply) -func BurnFeeHandler(_ sdk.Context, _ sdk.Tx, _ sdk.Coins) {} diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index 2a289f317bf8..d29b0bf50de4 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -112,7 +112,7 @@ func TestAnteHandlerSigErrors(t *testing.T) { ms, capKey, capKey2 := setupMultiStore() cdc := codec.New() RegisterBaseAccount(cdc) - mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) @@ -165,10 +165,11 @@ func TestAnteHandlerAccountNumbers(t *testing.T) { ms, capKey, capKey2 := setupMultiStore() cdc := codec.New() RegisterBaseAccount(cdc) - mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + 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) // keys and addresses priv1, addr1 := privAndAddr() @@ -218,16 +219,77 @@ func TestAnteHandlerAccountNumbers(t *testing.T) { checkValidTx(t, anteHandler, ctx, tx, false) } +// 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) + + // keys and addresses + priv1, addr1 := privAndAddr() + priv2, addr2 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc1) + acc2 := mapper.NewAccountWithAddress(ctx, addr2) + acc2.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc2) + + // msg and signatures + var tx sdk.Tx + msg := newTestMsg(addr1) + fee := newStdFee() + + msgs := []sdk.Msg{msg} + + // test good tx from one signer + privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + + // new tx from wrong account number + seqs = []int64{1} + tx = newTestTx(ctx, msgs, privs, []int64{1}, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidSequence) + + // from correct account number + seqs = []int64{1} + tx = newTestTx(ctx, msgs, privs, []int64{0}, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + + // new tx with another signer and incorrect account numbers + msg1 := newTestMsg(addr1, addr2) + msg2 := newTestMsg(addr2, addr1) + msgs = []sdk.Msg{msg1, msg2} + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{1, 0}, []int64{2, 0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidSequence) + + // correct account numbers + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 0}, []int64{2, 0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) +} + // 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 := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + 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) // keys and addresses priv1, addr1 := privAndAddr() @@ -302,7 +364,7 @@ func TestAnteHandlerFees(t *testing.T) { ms, capKey, capKey2 := setupMultiStore() cdc := codec.New() RegisterBaseAccount(cdc) - mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) @@ -344,10 +406,11 @@ func TestAnteHandlerMemoGas(t *testing.T) { ms, capKey, capKey2 := setupMultiStore() cdc := codec.New() RegisterBaseAccount(cdc) - mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + 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) // keys and addresses priv1, addr1 := privAndAddr() @@ -387,10 +450,11 @@ func TestAnteHandlerMultiSigner(t *testing.T) { ms, capKey, capKey2 := setupMultiStore() cdc := codec.New() RegisterBaseAccount(cdc) - mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + 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) // keys and addresses priv1, addr1 := privAndAddr() @@ -438,10 +502,11 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { ms, capKey, capKey2 := setupMultiStore() cdc := codec.New() RegisterBaseAccount(cdc) - mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + 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) // keys and addresses priv1, addr1 := privAndAddr() @@ -519,10 +584,11 @@ func TestAnteHandlerSetPubKey(t *testing.T) { ms, capKey, capKey2 := setupMultiStore() cdc := codec.New() RegisterBaseAccount(cdc) - mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + 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) // keys and addresses priv1, addr1 := privAndAddr() @@ -572,7 +638,7 @@ func TestProcessPubKey(t *testing.T) { ms, capKey, _ := setupMultiStore() cdc := codec.New() RegisterBaseAccount(cdc) - mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) // keys _, addr1 := privAndAddr() diff --git a/x/auth/client/cli/sign.go b/x/auth/client/cli/sign.go index 30704a50027a..f4a4548d4157 100644 --- a/x/auth/client/cli/sign.go +++ b/x/auth/client/cli/sign.go @@ -2,9 +2,9 @@ package cli import ( "fmt" - "io/ioutil" - + "github.com/pkg/errors" "github.com/spf13/viper" + "io/ioutil" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" @@ -13,12 +13,14 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/spf13/cobra" - amino "github.com/tendermint/go-amino" + "github.com/tendermint/go-amino" ) const ( - flagAppend = "append" - flagPrintSigs = "print-sigs" + flagAppend = "append" + flagValidateSigs = "validate-signatures" + flagOffline = "offline" + flagSigOnly = "signature-only" ) // GetSignCommand returns the sign command @@ -27,13 +29,28 @@ func GetSignCommand(codec *amino.Codec, decoder auth.AccountDecoder) *cobra.Comm Use: "sign <file>", Short: "Sign transactions generated offline", Long: `Sign transactions created with the --generate-only flag. -Read a transaction from <file>, sign it, and print its JSON encoding.`, +Read a transaction from <file>, sign it, and print its JSON encoding. + +If the flag --signature-only flag is on, it outputs a JSON representation +of the generated signature only. + +If the flag --validate-signatures is on, then the command would check whether all required +signers have signed the transactions and whether the signatures were collected in the right +order. + +The --offline flag makes sure that the client will not reach out to the local cache. +Thus account number or sequence number lookups will not be performed and it is +recommended to set such parameters manually.`, RunE: makeSignCmd(codec, decoder), Args: cobra.ExactArgs(1), } cmd.Flags().String(client.FlagName, "", "Name of private key with which to sign") - cmd.Flags().Bool(flagAppend, true, "Append the signature to the existing ones. If disabled, old signatures would be overwritten") - cmd.Flags().Bool(flagPrintSigs, false, "Print the addresses that must sign the transaction and those who have already signed it, then exit") + 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.") + 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 local cache.") return cmd } @@ -44,24 +61,45 @@ func makeSignCmd(cdc *amino.Codec, decoder auth.AccountDecoder) func(cmd *cobra. return } - if viper.GetBool(flagPrintSigs) { - printSignatures(stdTx) + if viper.GetBool(flagValidateSigs) { + if !printSignatures(stdTx) { + return fmt.Errorf("signatures validation failed") + } return nil } name := viper.GetString(client.FlagName) + if name == "" { + return errors.New("required flag \"name\" has not been set") + } cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(decoder) txBldr := authtxb.NewTxBuilderFromCLI() - newTx, err := utils.SignStdTx(txBldr, cliCtx, name, stdTx, viper.GetBool(flagAppend)) + // if --signature-only is on, then override --append + generateSignatureOnly := viper.GetBool(flagSigOnly) + appendSig := viper.GetBool(flagAppend) && !generateSignatureOnly + newTx, err := utils.SignStdTx(txBldr, cliCtx, name, stdTx, appendSig, viper.GetBool(flagOffline)) if err != nil { return err } + var json []byte - if cliCtx.Indent { - json, err = cdc.MarshalJSONIndent(newTx, "", " ") - } else { - json, err = cdc.MarshalJSON(newTx) + + switch generateSignatureOnly { + case true: + switch cliCtx.Indent { + case true: + json, err = cdc.MarshalJSONIndent(newTx.Signatures[0], "", " ") + default: + json, err = cdc.MarshalJSON(newTx.Signatures[0]) + } + default: + switch cliCtx.Indent { + case true: + json, err = cdc.MarshalJSONIndent(newTx, "", " ") + default: + json, err = cdc.MarshalJSON(newTx) + } } if err != nil { return err @@ -71,17 +109,31 @@ func makeSignCmd(cdc *amino.Codec, decoder auth.AccountDecoder) func(cmd *cobra. } } -func printSignatures(stdTx auth.StdTx) { +func printSignatures(stdTx auth.StdTx) bool { fmt.Println("Signers:") - for i, signer := range stdTx.GetSigners() { + signers := stdTx.GetSigners() + for i, signer := range signers { fmt.Printf(" %v: %v\n", i, signer.String()) } + + sigs := stdTx.GetSignatures() fmt.Println("") fmt.Println("Signatures:") + success := true + if len(sigs) != len(signers) { + success = false + } for i, sig := range stdTx.GetSignatures() { - fmt.Printf(" %v: %v\n", i, sdk.AccAddress(sig.Address()).String()) + sigAddr := sdk.AccAddress(sig.Address()) + sigSanity := "OK" + if i >= len(signers) || !sigAddr.Equals(signers[i]) { + sigSanity = fmt.Sprintf("ERROR: signature %d does not match its respective signer", i) + success = false + } + fmt.Printf(" %v: %v\t[%s]\n", i, sigAddr.String(), sigSanity) } - return + fmt.Println("") + return success } func readAndUnmarshalStdTx(cdc *amino.Codec, filename string) (stdTx auth.StdTx, err error) { diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index 5d47eb692112..0629cc939e17 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -1,7 +1,6 @@ package rest import ( - "fmt" "net/http" "github.com/cosmos/cosmos-sdk/client/context" @@ -47,7 +46,7 @@ func QueryAccountRequestHandlerFn( res, err := cliCtx.QueryStore(auth.AddressStoreKey(addr), storeName) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("couldn't query account. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -60,7 +59,7 @@ func QueryAccountRequestHandlerFn( // decode the value account, err := decoder(res) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("couldn't parse query result. Result: %s. Error: %s", res, err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -86,7 +85,7 @@ func QueryBalancesRequestHandlerFn( res, err := cliCtx.QueryStore(auth.AddressStoreKey(addr), storeName) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("couldn't query account. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -99,7 +98,7 @@ func QueryBalancesRequestHandlerFn( // decode the value account, err := decoder(res) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("couldn't parse query result. Result: %s. Error: %s", res, err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } diff --git a/x/auth/client/rest/sign.go b/x/auth/client/rest/sign.go index 9e9b92b5ade1..13dd8d20c306 100644 --- a/x/auth/client/rest/sign.go +++ b/x/auth/client/rest/sign.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" "github.com/cosmos/cosmos-sdk/x/auth" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" ) @@ -47,7 +48,13 @@ func SignTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha } signedTx, err := txBldr.SignStdTx(m.LocalAccountName, m.Password, m.Tx, m.AppendSig) - if err != nil { + if keyerror.IsErrKeyNotFound(err) { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } else if keyerror.IsErrWrongPassword(err) { + utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) + return + } else if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } diff --git a/x/auth/client/txbuilder/txbuilder.go b/x/auth/client/txbuilder/txbuilder.go index b488ef359f7b..f516de4513d5 100644 --- a/x/auth/client/txbuilder/txbuilder.go +++ b/x/auth/client/txbuilder/txbuilder.go @@ -125,7 +125,8 @@ func (bldr TxBuilder) Sign(name, passphrase string, msg StdSignMsg) ([]byte, err if err != nil { return nil, err } - return bldr.Codec.MarshalBinary(auth.NewStdTx(msg.Msgs, msg.Fee, []auth.StdSignature{sig}, msg.Memo)) + + return bldr.Codec.MarshalBinaryLengthPrefixed(auth.NewStdTx(msg.Msgs, msg.Fee, []auth.StdSignature{sig}, msg.Memo)) } // BuildAndSign builds a single message to be signed, and signs a transaction @@ -166,7 +167,7 @@ func (bldr TxBuilder) BuildWithPubKey(name string, msgs []sdk.Msg) ([]byte, erro PubKey: info.GetPubKey(), }} - return bldr.Codec.MarshalBinary(auth.NewStdTx(msg.Msgs, msg.Fee, sigs, msg.Memo)) + return bldr.Codec.MarshalBinaryLengthPrefixed(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 diff --git a/x/auth/client/txbuilder/txbuilder_test.go b/x/auth/client/txbuilder/txbuilder_test.go index 996d9b8b1bb4..3ad2ad4123d8 100644 --- a/x/auth/client/txbuilder/txbuilder_test.go +++ b/x/auth/client/txbuilder/txbuilder_test.go @@ -10,6 +10,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/tendermint/tendermint/crypto/ed25519" + stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" ) var ( @@ -46,7 +47,7 @@ func TestTxBuilderBuild(t *testing.T) { SimulateGas: false, ChainID: "test-chain", Memo: "hello", - Fee: "1steak", + Fee: "1" + stakeTypes.DefaultBondDenom, }, defaultMsg, StdSignMsg{ @@ -55,7 +56,7 @@ func TestTxBuilderBuild(t *testing.T) { Sequence: 1, Memo: "hello", Msgs: defaultMsg, - Fee: auth.NewStdFee(100, sdk.NewCoin("steak", sdk.NewInt(1))), + Fee: auth.NewStdFee(100, sdk.NewCoin(stakeTypes.DefaultBondDenom, sdk.NewInt(1))), }, false, }, diff --git a/x/auth/context.go b/x/auth/context.go index 40fb177858bc..28d159c97222 100644 --- a/x/auth/context.go +++ b/x/auth/context.go @@ -8,7 +8,7 @@ import ( Usage: -var accounts types.AccountMapper +var accounts types.AccountKeeper // Fetch all signer accounts. addrs := tx.GetSigners() diff --git a/x/auth/feekeeper.go b/x/auth/feekeeper.go index d2cf7ce62748..0006a918529c 100644 --- a/x/auth/feekeeper.go +++ b/x/auth/feekeeper.go @@ -20,7 +20,6 @@ type FeeCollectionKeeper struct { cdc *codec.Codec } -// NewFeeKeeper returns a new FeeKeeper func NewFeeCollectionKeeper(cdc *codec.Codec, key sdk.StoreKey) FeeCollectionKeeper { return FeeCollectionKeeper{ key: key, @@ -28,7 +27,7 @@ func NewFeeCollectionKeeper(cdc *codec.Codec, key sdk.StoreKey) FeeCollectionKee } } -// Adds to Collected Fee Pool +// retrieves the collected fee pool func (fck FeeCollectionKeeper) GetCollectedFees(ctx sdk.Context) sdk.Coins { store := ctx.KVStore(fck.key) bz := store.Get(collectedFeesKey) @@ -37,26 +36,25 @@ func (fck FeeCollectionKeeper) GetCollectedFees(ctx sdk.Context) sdk.Coins { } feePool := &(sdk.Coins{}) - fck.cdc.MustUnmarshalBinary(bz, feePool) + fck.cdc.MustUnmarshalBinaryLengthPrefixed(bz, feePool) return *feePool } -// Sets to Collected Fee Pool func (fck FeeCollectionKeeper) setCollectedFees(ctx sdk.Context, coins sdk.Coins) { - bz := fck.cdc.MustMarshalBinary(coins) + bz := fck.cdc.MustMarshalBinaryLengthPrefixed(coins) store := ctx.KVStore(fck.key) store.Set(collectedFeesKey, bz) } -// Adds to Collected Fee Pool -func (fck FeeCollectionKeeper) addCollectedFees(ctx sdk.Context, coins sdk.Coins) sdk.Coins { +// add to the fee pool +func (fck FeeCollectionKeeper) AddCollectedFees(ctx sdk.Context, coins sdk.Coins) sdk.Coins { newCoins := fck.GetCollectedFees(ctx).Plus(coins) fck.setCollectedFees(ctx, newCoins) return newCoins } -// Clears the collected Fee Pool +// clear the fee pool func (fck FeeCollectionKeeper) ClearCollectedFees(ctx sdk.Context) { fck.setCollectedFees(ctx, sdk.Coins{}) } diff --git a/x/auth/feekeeper_test.go b/x/auth/feekeeper_test.go index 82bbe9c35da3..d481511617db 100644 --- a/x/auth/feekeeper_test.go +++ b/x/auth/feekeeper_test.go @@ -49,11 +49,11 @@ func TestFeeCollectionKeeperAdd(t *testing.T) { require.True(t, fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) // add oneCoin and check that pool is now oneCoin - fck.addCollectedFees(ctx, oneCoin) + fck.AddCollectedFees(ctx, oneCoin) require.True(t, fck.GetCollectedFees(ctx).IsEqual(oneCoin)) // add oneCoin again and check that pool is now twoCoins - fck.addCollectedFees(ctx, oneCoin) + fck.AddCollectedFees(ctx, oneCoin) require.True(t, fck.GetCollectedFees(ctx).IsEqual(twoCoins)) } diff --git a/x/auth/genesis.go b/x/auth/genesis.go new file mode 100644 index 000000000000..abc4fc3aea8f --- /dev/null +++ b/x/auth/genesis.go @@ -0,0 +1,33 @@ +package auth + +import ( + 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 +} + +// Create a new genesis state +func NewGenesisState(collectedFees sdk.Coins) GenesisState { + return GenesisState{ + CollectedFees: collectedFees, + } +} + +// Return a default genesis state +func DefaultGenesisState() GenesisState { + return NewGenesisState(sdk.Coins{}) +} + +// Init store state from genesis data +func InitGenesis(ctx sdk.Context, keeper FeeCollectionKeeper, data GenesisState) { + keeper.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) +} diff --git a/x/auth/mapper.go b/x/auth/keeper.go similarity index 71% rename from x/auth/mapper.go rename to x/auth/keeper.go index 20d6c2d14347..da5481749fa7 100644 --- a/x/auth/mapper.go +++ b/x/auth/keeper.go @@ -8,9 +8,9 @@ import ( var globalAccountNumberKey = []byte("globalAccountNumber") -// This AccountMapper encodes/decodes accounts using the +// This AccountKeeper encodes/decodes accounts using the // go-amino (binary) encoding/decoding library. -type AccountMapper struct { +type AccountKeeper struct { // The (unexposed) key used to access the store from the Context. key sdk.StoreKey @@ -22,19 +22,19 @@ type AccountMapper struct { cdc *codec.Codec } -// NewAccountMapper returns a new sdk.AccountMapper that +// NewAccountKeeper returns a new sdk.AccountKeeper that // uses go-amino to (binary) encode and decode concrete sdk.Accounts. // nolint -func NewAccountMapper(cdc *codec.Codec, key sdk.StoreKey, proto func() Account) AccountMapper { - return AccountMapper{ +func NewAccountKeeper(cdc *codec.Codec, key sdk.StoreKey, proto func() Account) AccountKeeper { + return AccountKeeper{ key: key, proto: proto, cdc: cdc, } } -// Implaements sdk.AccountMapper. -func (am AccountMapper) NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) Account { +// Implaements sdk.AccountKeeper. +func (am AccountKeeper) NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) Account { acc := am.proto() err := acc.SetAddress(addr) if err != nil { @@ -50,7 +50,7 @@ func (am AccountMapper) NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddre } // New Account -func (am AccountMapper) NewAccount(ctx sdk.Context, acc Account) Account { +func (am AccountKeeper) NewAccount(ctx sdk.Context, acc Account) Account { err := acc.SetAccountNumber(am.GetNextAccountNumber(ctx)) if err != nil { // TODO: Handle with #870 @@ -64,8 +64,8 @@ func AddressStoreKey(addr sdk.AccAddress) []byte { return append([]byte("account:"), addr.Bytes()...) } -// Implements sdk.AccountMapper. -func (am AccountMapper) GetAccount(ctx sdk.Context, addr sdk.AccAddress) Account { +// Implements sdk.AccountKeeper. +func (am AccountKeeper) GetAccount(ctx sdk.Context, addr sdk.AccAddress) Account { store := ctx.KVStore(am.key) bz := store.Get(AddressStoreKey(addr)) if bz == nil { @@ -75,8 +75,8 @@ func (am AccountMapper) GetAccount(ctx sdk.Context, addr sdk.AccAddress) Account return acc } -// Implements sdk.AccountMapper. -func (am AccountMapper) SetAccount(ctx sdk.Context, acc Account) { +// Implements sdk.AccountKeeper. +func (am AccountKeeper) SetAccount(ctx sdk.Context, acc Account) { addr := acc.GetAddress() store := ctx.KVStore(am.key) bz := am.encodeAccount(acc) @@ -84,14 +84,14 @@ func (am AccountMapper) SetAccount(ctx sdk.Context, acc Account) { } // RemoveAccount removes an account for the account mapper store. -func (am AccountMapper) RemoveAccount(ctx sdk.Context, acc Account) { +func (am AccountKeeper) RemoveAccount(ctx sdk.Context, acc Account) { addr := acc.GetAddress() store := ctx.KVStore(am.key) store.Delete(AddressStoreKey(addr)) } -// Implements sdk.AccountMapper. -func (am AccountMapper) IterateAccounts(ctx sdk.Context, process func(Account) (stop bool)) { +// Implements sdk.AccountKeeper. +func (am AccountKeeper) IterateAccounts(ctx sdk.Context, process func(Account) (stop bool)) { store := ctx.KVStore(am.key) iter := sdk.KVStorePrefixIterator(store, []byte("account:")) defer iter.Close() @@ -109,7 +109,7 @@ func (am AccountMapper) IterateAccounts(ctx sdk.Context, process func(Account) ( } // Returns the PubKey of the account at address -func (am AccountMapper) GetPubKey(ctx sdk.Context, addr sdk.AccAddress) (crypto.PubKey, sdk.Error) { +func (am AccountKeeper) GetPubKey(ctx sdk.Context, addr sdk.AccAddress) (crypto.PubKey, sdk.Error) { acc := am.GetAccount(ctx, addr) if acc == nil { return nil, sdk.ErrUnknownAddress(addr.String()) @@ -118,7 +118,7 @@ func (am AccountMapper) GetPubKey(ctx sdk.Context, addr sdk.AccAddress) (crypto. } // Returns the Sequence of the account at address -func (am AccountMapper) GetSequence(ctx sdk.Context, addr sdk.AccAddress) (int64, sdk.Error) { +func (am AccountKeeper) GetSequence(ctx sdk.Context, addr sdk.AccAddress) (int64, sdk.Error) { acc := am.GetAccount(ctx, addr) if acc == nil { return 0, sdk.ErrUnknownAddress(addr.String()) @@ -126,7 +126,7 @@ func (am AccountMapper) GetSequence(ctx sdk.Context, addr sdk.AccAddress) (int64 return acc.GetSequence(), nil } -func (am AccountMapper) setSequence(ctx sdk.Context, addr sdk.AccAddress, newSequence int64) sdk.Error { +func (am AccountKeeper) setSequence(ctx sdk.Context, addr sdk.AccAddress, newSequence int64) sdk.Error { acc := am.GetAccount(ctx, addr) if acc == nil { return sdk.ErrUnknownAddress(addr.String()) @@ -141,20 +141,20 @@ func (am AccountMapper) setSequence(ctx sdk.Context, addr sdk.AccAddress, newSeq } // Returns and increments the global account number counter -func (am AccountMapper) GetNextAccountNumber(ctx sdk.Context) int64 { +func (am AccountKeeper) GetNextAccountNumber(ctx sdk.Context) int64 { var accNumber int64 store := ctx.KVStore(am.key) bz := store.Get(globalAccountNumberKey) if bz == nil { accNumber = 0 } else { - err := am.cdc.UnmarshalBinary(bz, &accNumber) + err := am.cdc.UnmarshalBinaryLengthPrefixed(bz, &accNumber) if err != nil { panic(err) } } - bz = am.cdc.MustMarshalBinary(accNumber + 1) + bz = am.cdc.MustMarshalBinaryLengthPrefixed(accNumber + 1) store.Set(globalAccountNumberKey, bz) return accNumber @@ -163,7 +163,7 @@ func (am AccountMapper) GetNextAccountNumber(ctx sdk.Context) int64 { //---------------------------------------- // misc. -func (am AccountMapper) encodeAccount(acc Account) []byte { +func (am AccountKeeper) encodeAccount(acc Account) []byte { bz, err := am.cdc.MarshalBinaryBare(acc) if err != nil { panic(err) @@ -171,7 +171,7 @@ func (am AccountMapper) encodeAccount(acc Account) []byte { return bz } -func (am AccountMapper) decodeAccount(bz []byte) (acc Account) { +func (am AccountKeeper) decodeAccount(bz []byte) (acc Account) { err := am.cdc.UnmarshalBinaryBare(bz, &acc) if err != nil { panic(err) diff --git a/x/auth/mapper_test.go b/x/auth/keeper_test.go similarity index 55% rename from x/auth/mapper_test.go rename to x/auth/keeper_test.go index e3b737ea3c27..f1f4c7eaf9d9 100644 --- a/x/auth/mapper_test.go +++ b/x/auth/keeper_test.go @@ -3,15 +3,13 @@ package auth import ( "testing" + codec "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" "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) { @@ -32,7 +30,7 @@ func TestAccountMapperGetSet(t *testing.T) { // make context and mapper ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) addr := sdk.AccAddress([]byte("some-address")) @@ -68,7 +66,7 @@ func TestAccountMapperRemoveAccount(t *testing.T) { // make context and mapper ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) addr1 := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) @@ -106,18 +104,101 @@ func BenchmarkAccountMapperGetAccountFound(b *testing.B) { // make context and mapper ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + + // 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) + } + + 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)) + } +} + +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) + + coins := sdk.Coins{ + sdk.NewCoin("LTC", sdk.NewInt(1000)), + sdk.NewCoin("BTC", sdk.NewInt(1000)), + sdk.NewCoin("ETH", sdk.NewInt(1000)), + sdk.NewCoin("XRP", sdk.NewInt(1000)), + sdk.NewCoin("BCH", sdk.NewInt(1000)), + sdk.NewCoin("EOS", sdk.NewInt(1000)), + } // 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.SetCoins(coins) mapper.SetAccount(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)) } } + +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) + + 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) + } +} + +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) + + coins := sdk.Coins{ + sdk.NewCoin("LTC", sdk.NewInt(1000)), + sdk.NewCoin("BTC", sdk.NewInt(1000)), + sdk.NewCoin("ETH", sdk.NewInt(1000)), + sdk.NewCoin("XRP", sdk.NewInt(1000)), + sdk.NewCoin("BCH", sdk.NewInt(1000)), + sdk.NewCoin("EOS", sdk.NewInt(1000)), + } + + 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.SetCoins(coins) + mapper.SetAccount(ctx, acc) + } +} diff --git a/x/auth/simulation/fake.go b/x/auth/simulation/fake.go new file mode 100644 index 000000000000..222b747ec4a0 --- /dev/null +++ b/x/auth/simulation/fake.go @@ -0,0 +1,62 @@ +package simulation + +import ( + "errors" + "fmt" + "math/big" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/mock/simulation" +) + +// SimulateDeductFee +func SimulateDeductFee(m auth.AccountKeeper, f auth.FeeCollectionKeeper) simulation.Operation { + 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) + stored := m.GetAccount(ctx, account.Address) + initCoins := stored.GetCoins() + + if len(initCoins) == 0 { + event(fmt.Sprintf("auth/SimulateDeductFee/false")) + return action, nil, nil + } + + denomIndex := r.Intn(len(initCoins)) + amt, err := randPositiveInt(r, initCoins[denomIndex].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) + } + m.SetAccount(ctx, stored) + if !coins.IsNotNegative() { + panic("setting negative fees") + } + + f.AddCollectedFees(ctx, coins) + + event(fmt.Sprintf("auth/SimulateDeductFee/true")) + + action = "TestDeductFee" + return action, nil, nil + } +} + +func randPositiveInt(r *rand.Rand, max sdk.Int) (sdk.Int, error) { + if !max.GT(sdk.OneInt()) { + return sdk.Int{}, errors.New("max too small") + } + max = max.Sub(sdk.OneInt()) + return sdk.NewIntFromBigInt(new(big.Int).Rand(r, max.BigInt())).Add(sdk.OneInt()), nil +} diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index ca6eb7b422ff..84bf88a4b7e5 100644 --- a/x/auth/stdtx.go +++ b/x/auth/stdtx.go @@ -151,10 +151,11 @@ func DefaultTxDecoder(cdc *codec.Codec) sdk.TxDecoder { // StdTx.Msg is an interface. The concrete types // are registered by MakeTxCodec - err := cdc.UnmarshalBinary(txBytes, &tx) + err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) if err != nil { return nil, sdk.ErrTxDecode("").TraceSDK(err.Error()) } + return tx, nil } } diff --git a/x/bank/app_test.go b/x/bank/app_test.go index 77991f94b78d..906573eae21f 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -102,7 +102,7 @@ func TestMsgSendWithAccounts(t *testing.T) { ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) - res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) + res1 := mapp.AccountKeeper.GetAccount(ctxCheck, addr1) require.NotNil(t, res1) require.Equal(t, acc, res1.(*auth.BaseAccount)) @@ -210,7 +210,7 @@ func TestSengMsgMultipleInOut(t *testing.T) { testCases := []appTestCase{ { msgs: []sdk.Msg{sendMsg3}, - accNums: []int64{0, 2}, + accNums: []int64{0, 0}, accSeqs: []int64{0, 0}, expSimPass: true, expPass: true, @@ -258,7 +258,7 @@ func TestMsgSendDependent(t *testing.T) { }, { msgs: []sdk.Msg{sendMsg4}, - accNums: []int64{1}, + accNums: []int64{0}, accSeqs: []int64{0}, expSimPass: true, expPass: true, diff --git a/x/bank/bench_test.go b/x/bank/bench_test.go index a29a25b99965..bff99da996c8 100644 --- a/x/bank/bench_test.go +++ b/x/bank/bench_test.go @@ -16,7 +16,7 @@ func getBenchmarkMockApp() (*mock.App, error) { mapp := mock.NewApp() RegisterCodec(mapp.Cdc) - bankKeeper := NewBaseKeeper(mapp.AccountMapper) + bankKeeper := NewBaseKeeper(mapp.AccountKeeper) mapp.Router().AddRoute("bank", NewHandler(bankKeeper)) err := mapp.CompleteSetup() diff --git a/x/bank/client/cli/broadcast.go b/x/bank/client/cli/broadcast.go index e7e6bc16d135..dd045439e0d8 100644 --- a/x/bank/client/cli/broadcast.go +++ b/x/bank/client/cli/broadcast.go @@ -25,7 +25,8 @@ in place of an input filename, the command reads from standard input.`, if err != nil { return } - txBytes, err := cliCtx.Codec.MarshalBinary(stdTx) + + txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(stdTx) if err != nil { return } diff --git a/x/bank/client/cli/sendtx.go b/x/bank/client/cli/sendtx.go index 8200563b0e6e..bae2179766b8 100644 --- a/x/bank/client/cli/sendtx.go +++ b/x/bank/client/cli/sendtx.go @@ -59,7 +59,7 @@ func SendTxCmd(cdc *codec.Codec) *cobra.Command { } // ensure account has enough coins - if !account.GetCoins().IsGTE(coins) { + if !account.GetCoins().IsAllGTE(coins) { return errors.Errorf("Address %s doesn't have enough coins to pay for this transaction.", from) } @@ -75,6 +75,8 @@ func SendTxCmd(cdc *codec.Codec) *cobra.Command { cmd.Flags().String(flagTo, "", "Address to send coins") cmd.Flags().String(flagAmount, "", "Amount of coins to send") + cmd.MarkFlagRequired(flagTo) + cmd.MarkFlagRequired(flagAmount) return cmd } diff --git a/x/bank/client/rest/broadcast.go b/x/bank/client/rest/broadcast.go index c52961caf5fe..71696f12b3c3 100644 --- a/x/bank/client/rest/broadcast.go +++ b/x/bank/client/rest/broadcast.go @@ -22,11 +22,12 @@ func BroadcastTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) ht return } - txBytes, err := cliCtx.Codec.MarshalBinary(m.Tx) + txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(m.Tx) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } + res, err := cliCtx.BroadcastTx(txBytes) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index a06cf58460da..7bb2640fddfd 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -56,7 +56,7 @@ func SendRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIC info, err := kb.Get(baseReq.Name) if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } diff --git a/x/bank/handler.go b/x/bank/handler.go index cd60ac835122..e02043d7b4f1 100644 --- a/x/bank/handler.go +++ b/x/bank/handler.go @@ -13,7 +13,7 @@ func NewHandler(k Keeper) sdk.Handler { case MsgIssue: return handleMsgIssue(ctx, k, msg) default: - errMsg := "Unrecognized bank Msg type: %s" + msg.Name() + errMsg := "Unrecognized bank Msg type: %s" + msg.Type() return sdk.ErrUnknownRequest(errMsg).Result() } } diff --git a/x/bank/keeper.go b/x/bank/keeper.go index 2da4eedc8b3f..3a33870e1457 100644 --- a/x/bank/keeper.go +++ b/x/bank/keeper.go @@ -15,41 +15,40 @@ const ( costAddCoins sdk.Gas = 10 ) +//----------------------------------------------------------------------------- +// Keeper + +var _ Keeper = (*BaseKeeper)(nil) + // Keeper defines a module interface that facilitates the transfer of coins // between accounts. type Keeper interface { SendKeeper + SetCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) sdk.Error 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) } -var _ Keeper = (*BaseKeeper)(nil) - // BaseKeeper manages transfers between accounts. It implements the Keeper // interface. type BaseKeeper struct { - am auth.AccountMapper -} + BaseSendKeeper -// NewBaseKeeper returns a new BaseKeeper -func NewBaseKeeper(am auth.AccountMapper) BaseKeeper { - return BaseKeeper{am: am} + ak auth.AccountKeeper } -// GetCoins returns the coins at the addr. -func (keeper BaseKeeper) GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { - return getCoins(ctx, keeper.am, addr) +// NewBaseKeeper returns a new BaseKeeper +func NewBaseKeeper(ak auth.AccountKeeper) BaseKeeper { + return BaseKeeper{ + BaseSendKeeper: NewBaseSendKeeper(ak), + ak: ak, + } } // SetCoins sets the coins at the addr. func (keeper BaseKeeper) SetCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) sdk.Error { - return setCoins(ctx, keeper.am, addr, amt) -} - -// HasCoins returns whether or not an account has at least amt coins. -func (keeper BaseKeeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool { - return hasCoins(ctx, keeper.am, addr, amt) + return setCoins(ctx, keeper.ak, addr, amt) } // SubtractCoins subtracts amt from the coins at the addr. @@ -57,7 +56,7 @@ func (keeper BaseKeeper) SubtractCoins( ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, ) (sdk.Coins, sdk.Tags, sdk.Error) { - return subtractCoins(ctx, keeper.am, addr, amt) + return subtractCoins(ctx, keeper.ak, addr, amt) } // AddCoins adds amt to the coins at the addr. @@ -65,28 +64,17 @@ func (keeper BaseKeeper) AddCoins( ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, ) (sdk.Coins, sdk.Tags, sdk.Error) { - return addCoins(ctx, keeper.am, addr, amt) + return addCoins(ctx, keeper.ak, addr, amt) } -// SendCoins moves coins from one account to another -func (keeper BaseKeeper) SendCoins( - ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins, -) (sdk.Tags, sdk.Error) { - - return sendCoins(ctx, keeper.am, fromAddr, toAddr, amt) -} - -// InputOutputCoins handles a list of inputs and outputs -func (keeper BaseKeeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) { - return inputOutputCoins(ctx, keeper.am, inputs, outputs) -} - -//______________________________________________________________________________________________ +//----------------------------------------------------------------------------- +// Send Keeper // SendKeeper defines a module interface that facilitates the transfer of coins // between accounts without the possibility of creating coins. type SendKeeper interface { ViewKeeper + SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) } @@ -96,22 +84,17 @@ var _ SendKeeper = (*BaseSendKeeper)(nil) // SendKeeper only allows transfers between accounts without the possibility of // creating coins. It implements the SendKeeper interface. type BaseSendKeeper struct { - am auth.AccountMapper -} - -// NewBaseSendKeeper returns a new BaseSendKeeper. -func NewBaseSendKeeper(am auth.AccountMapper) BaseSendKeeper { - return BaseSendKeeper{am: am} -} + BaseViewKeeper -// GetCoins returns the coins at the addr. -func (keeper BaseSendKeeper) GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { - return getCoins(ctx, keeper.am, addr) + ak auth.AccountKeeper } -// HasCoins returns whether or not an account has at least amt coins. -func (keeper BaseSendKeeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool { - return hasCoins(ctx, keeper.am, addr, amt) +// NewBaseSendKeeper returns a new BaseSendKeeper. +func NewBaseSendKeeper(ak auth.AccountKeeper) BaseSendKeeper { + return BaseSendKeeper{ + BaseViewKeeper: NewBaseViewKeeper(ak), + ak: ak, + } } // SendCoins moves coins from one account to another @@ -119,7 +102,7 @@ func (keeper BaseSendKeeper) SendCoins( ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins, ) (sdk.Tags, sdk.Error) { - return sendCoins(ctx, keeper.am, fromAddr, toAddr, amt) + return sendCoins(ctx, keeper.ak, fromAddr, toAddr, amt) } // InputOutputCoins handles a list of inputs and outputs @@ -127,10 +110,13 @@ func (keeper BaseSendKeeper) InputOutputCoins( ctx sdk.Context, inputs []Input, outputs []Output, ) (sdk.Tags, sdk.Error) { - return inputOutputCoins(ctx, keeper.am, inputs, outputs) + return inputOutputCoins(ctx, keeper.ak, inputs, outputs) } -//______________________________________________________________________________________________ +//----------------------------------------------------------------------------- +// View Keeper + +var _ ViewKeeper = (*BaseViewKeeper)(nil) // ViewKeeper defines a module interface that facilitates read only access to // account balances. @@ -139,31 +125,31 @@ type ViewKeeper interface { HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool } -var _ ViewKeeper = (*BaseViewKeeper)(nil) - // BaseViewKeeper implements a read only keeper implementation of ViewKeeper. type BaseViewKeeper struct { - am auth.AccountMapper + ak auth.AccountKeeper } // NewBaseViewKeeper returns a new BaseViewKeeper. -func NewBaseViewKeeper(am auth.AccountMapper) BaseViewKeeper { - return BaseViewKeeper{am: am} +func NewBaseViewKeeper(ak auth.AccountKeeper) BaseViewKeeper { + return BaseViewKeeper{ + ak: ak, + } } // GetCoins returns the coins at the addr. func (keeper BaseViewKeeper) GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { - return getCoins(ctx, keeper.am, addr) + return getCoins(ctx, keeper.ak, addr) } // HasCoins returns whether or not an account has at least amt coins. func (keeper BaseViewKeeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool { - return hasCoins(ctx, keeper.am, addr, amt) + return hasCoins(ctx, keeper.ak, addr, amt) } -//______________________________________________________________________________________________ +//----------------------------------------------------------------------------- -func getCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.AccAddress) sdk.Coins { +func getCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress) sdk.Coins { ctx.GasMeter().ConsumeGas(costGetCoins, "getCoins") acc := am.GetAccount(ctx, addr) if acc == nil { @@ -172,7 +158,7 @@ func getCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.AccAddress) sdk.C return acc.GetCoins() } -func setCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.AccAddress, amt sdk.Coins) sdk.Error { +func setCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) sdk.Error { ctx.GasMeter().ConsumeGas(costSetCoins, "setCoins") acc := am.GetAccount(ctx, addr) if acc == nil { @@ -188,13 +174,13 @@ func setCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.AccAddress, amt s } // HasCoins returns whether or not an account has at least amt coins. -func hasCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.AccAddress, amt sdk.Coins) bool { +func hasCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) bool { ctx.GasMeter().ConsumeGas(costHasCoins, "hasCoins") - return getCoins(ctx, am, addr).IsGTE(amt) + return getCoins(ctx, am, addr).IsAllGTE(amt) } // SubtractCoins subtracts amt from the coins at the addr. -func subtractCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { +func subtractCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { ctx.GasMeter().ConsumeGas(costSubtractCoins, "subtractCoins") oldCoins := getCoins(ctx, am, addr) newCoins := oldCoins.Minus(amt) @@ -207,7 +193,7 @@ func subtractCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.AccAddress, } // AddCoins adds amt to the coins at the addr. -func addCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { +func addCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { ctx.GasMeter().ConsumeGas(costAddCoins, "addCoins") oldCoins := getCoins(ctx, am, addr) newCoins := oldCoins.Plus(amt) @@ -221,7 +207,7 @@ func addCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.AccAddress, amt s // SendCoins moves coins from one account to another // NOTE: Make sure to revert state changes from tx on error -func sendCoins(ctx sdk.Context, am auth.AccountMapper, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) { +func sendCoins(ctx sdk.Context, am auth.AccountKeeper, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) { _, subTags, err := subtractCoins(ctx, am, fromAddr, amt) if err != nil { return nil, err @@ -237,7 +223,7 @@ func sendCoins(ctx sdk.Context, am auth.AccountMapper, fromAddr sdk.AccAddress, // InputOutputCoins handles a list of inputs and outputs // NOTE: Make sure to revert state changes from tx on error -func inputOutputCoins(ctx sdk.Context, am auth.AccountMapper, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) { +func inputOutputCoins(ctx sdk.Context, am auth.AccountKeeper, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) { allTags := sdk.EmptyTags() for _, in := range inputs { diff --git a/x/bank/keeper_test.go b/x/bank/keeper_test.go index c48410c154e3..26c1446d27c7 100644 --- a/x/bank/keeper_test.go +++ b/x/bank/keeper_test.go @@ -33,16 +33,16 @@ func TestKeeper(t *testing.T) { auth.RegisterBaseAccount(cdc) ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - accountMapper := auth.NewAccountMapper(cdc, authKey, auth.ProtoBaseAccount) - bankKeeper := NewBaseKeeper(accountMapper) + accountKeeper := auth.NewAccountKeeper(cdc, authKey, auth.ProtoBaseAccount) + bankKeeper := NewBaseKeeper(accountKeeper) addr := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) addr3 := sdk.AccAddress([]byte("addr3")) - acc := accountMapper.NewAccountWithAddress(ctx, addr) + acc := accountKeeper.NewAccountWithAddress(ctx, addr) // Test GetCoins/SetCoins - accountMapper.SetAccount(ctx, acc) + accountKeeper.SetAccount(ctx, acc) require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) bankKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}) @@ -118,17 +118,17 @@ func TestSendKeeper(t *testing.T) { auth.RegisterBaseAccount(cdc) ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - accountMapper := auth.NewAccountMapper(cdc, authKey, auth.ProtoBaseAccount) - bankKeeper := NewBaseKeeper(accountMapper) - sendKeeper := NewBaseSendKeeper(accountMapper) + accountKeeper := auth.NewAccountKeeper(cdc, authKey, auth.ProtoBaseAccount) + bankKeeper := NewBaseKeeper(accountKeeper) + sendKeeper := NewBaseSendKeeper(accountKeeper) addr := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) addr3 := sdk.AccAddress([]byte("addr3")) - acc := accountMapper.NewAccountWithAddress(ctx, addr) + acc := accountKeeper.NewAccountWithAddress(ctx, addr) // Test GetCoins/SetCoins - accountMapper.SetAccount(ctx, acc) + accountKeeper.SetAccount(ctx, acc) require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) bankKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}) @@ -187,15 +187,15 @@ func TestViewKeeper(t *testing.T) { auth.RegisterBaseAccount(cdc) ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - accountMapper := auth.NewAccountMapper(cdc, authKey, auth.ProtoBaseAccount) - bankKeeper := NewBaseKeeper(accountMapper) - viewKeeper := NewBaseViewKeeper(accountMapper) + accountKeeper := auth.NewAccountKeeper(cdc, authKey, auth.ProtoBaseAccount) + bankKeeper := NewBaseKeeper(accountKeeper) + viewKeeper := NewBaseViewKeeper(accountKeeper) addr := sdk.AccAddress([]byte("addr1")) - acc := accountMapper.NewAccountWithAddress(ctx, addr) + acc := accountKeeper.NewAccountWithAddress(ctx, addr) // Test GetCoins/SetCoins - accountMapper.SetAccount(ctx, acc) + accountKeeper.SetAccount(ctx, acc) require.True(t, viewKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) bankKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}) diff --git a/x/bank/msgs.go b/x/bank/msgs.go index ed2d9a780f17..a1c346a88d80 100644 --- a/x/bank/msgs.go +++ b/x/bank/msgs.go @@ -21,8 +21,8 @@ func NewMsgSend(in []Input, out []Output) MsgSend { // Implements Msg. // nolint -func (msg MsgSend) Type() string { return "bank" } // TODO: "bank/send" -func (msg MsgSend) Name() string { return "send" } +func (msg MsgSend) Route() string { return "bank" } // TODO: "bank/send" +func (msg MsgSend) Type() string { return "send" } // Implements Msg. func (msg MsgSend) ValidateBasic() sdk.Error { @@ -104,8 +104,8 @@ func NewMsgIssue(banker sdk.AccAddress, out []Output) MsgIssue { // Implements Msg. // nolint -func (msg MsgIssue) Type() string { return "bank" } // TODO: "bank/issue" -func (msg MsgIssue) Name() string { return "issue" } +func (msg MsgIssue) Route() string { return "bank" } // TODO: "bank/issue" +func (msg MsgIssue) Type() string { return "issue" } // Implements Msg. func (msg MsgIssue) ValidateBasic() sdk.Error { diff --git a/x/bank/msgs_test.go b/x/bank/msgs_test.go index e082bdac4227..157cc9b5b95b 100644 --- a/x/bank/msgs_test.go +++ b/x/bank/msgs_test.go @@ -11,7 +11,7 @@ import ( func TestNewMsgSend(t *testing.T) {} -func TestMsgSendType(t *testing.T) { +func TestMsgSendRoute(t *testing.T) { // Construct a MsgSend addr1 := sdk.AccAddress([]byte("input")) addr2 := sdk.AccAddress([]byte("output")) @@ -22,7 +22,7 @@ func TestMsgSendType(t *testing.T) { } // TODO some failures for bad result - require.Equal(t, msg.Type(), "bank") + require.Equal(t, msg.Route(), "bank") } func TestInputValidation(t *testing.T) { @@ -231,7 +231,7 @@ func TestNewMsgIssue(t *testing.T) { // TODO } -func TestMsgIssueType(t *testing.T) { +func TestMsgIssueRoute(t *testing.T) { // Construct an MsgIssue addr := sdk.AccAddress([]byte("loan-from-bank")) coins := sdk.Coins{sdk.NewInt64Coin("atom", 10)} @@ -241,7 +241,7 @@ func TestMsgIssueType(t *testing.T) { } // TODO some failures for bad result - require.Equal(t, msg.Type(), "bank") + require.Equal(t, msg.Route(), "bank") } func TestMsgIssueValidation(t *testing.T) { diff --git a/x/bank/simulation/invariants.go b/x/bank/simulation/invariants.go index 20aa9570bf09..f0cd5ba63ce0 100644 --- a/x/bank/simulation/invariants.go +++ b/x/bank/simulation/invariants.go @@ -13,7 +13,7 @@ import ( ) // NonnegativeBalanceInvariant checks that all accounts in the application have non-negative balances -func NonnegativeBalanceInvariant(mapper auth.AccountMapper) simulation.Invariant { +func NonnegativeBalanceInvariant(mapper auth.AccountKeeper) simulation.Invariant { return func(app *baseapp.BaseApp) error { ctx := app.NewContext(false, abci.Header{}) accts := mock.GetAllAccounts(mapper, ctx) @@ -31,7 +31,7 @@ func NonnegativeBalanceInvariant(mapper auth.AccountMapper) simulation.Invariant // TotalCoinsInvariant checks that the sum of the coins across all accounts // is what is expected -func TotalCoinsInvariant(mapper auth.AccountMapper, totalSupplyFn func() sdk.Coins) simulation.Invariant { +func TotalCoinsInvariant(mapper auth.AccountKeeper, totalSupplyFn func() sdk.Coins) simulation.Invariant { return func(app *baseapp.BaseApp) error { ctx := app.NewContext(false, abci.Header{}) totalCoins := sdk.Coins{} diff --git a/x/bank/simulation/msgs.go b/x/bank/simulation/msgs.go index 0a253235e1fb..f33c5e0c99be 100644 --- a/x/bank/simulation/msgs.go +++ b/x/bank/simulation/msgs.go @@ -17,7 +17,7 @@ import ( // SingleInputSendTx tests and runs a single msg send w/ auth, with one input and one output, where both // accounts already exist. -func SingleInputSendTx(mapper auth.AccountMapper) simulation.Operation { +func SingleInputSendTx(mapper auth.AccountKeeper) simulation.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) { fromAcc, action, msg, abort := createSingleInputSendMsg(r, ctx, accs, mapper) if abort { @@ -35,7 +35,7 @@ func SingleInputSendTx(mapper auth.AccountMapper) simulation.Operation { // SingleInputSendMsg tests and runs a single msg send, with one input and one output, where both // accounts already exist. -func SingleInputSendMsg(mapper auth.AccountMapper, bk bank.Keeper) simulation.Operation { +func SingleInputSendMsg(mapper auth.AccountKeeper, bk bank.Keeper) simulation.Operation { handler := bank.NewHandler(bk) return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) { fromAcc, action, msg, abort := createSingleInputSendMsg(r, ctx, accs, mapper) @@ -52,7 +52,7 @@ func SingleInputSendMsg(mapper auth.AccountMapper, bk bank.Keeper) simulation.Op } } -func createSingleInputSendMsg(r *rand.Rand, ctx sdk.Context, accs []simulation.Account, mapper auth.AccountMapper) (fromAcc simulation.Account, action string, msg bank.MsgSend, abort bool) { +func createSingleInputSendMsg(r *rand.Rand, ctx sdk.Context, accs []simulation.Account, mapper auth.AccountKeeper) (fromAcc simulation.Account, action string, msg bank.MsgSend, abort bool) { fromAcc = simulation.RandomAcc(r, accs) toAcc := simulation.RandomAcc(r, accs) // Disallow sending money to yourself @@ -92,7 +92,7 @@ func createSingleInputSendMsg(r *rand.Rand, ctx sdk.Context, accs []simulation.A // Sends and verifies the transition of a msg send. This fails if there are repeated inputs or outputs // pass in handler as nil to handle txs, otherwise handle msgs -func sendAndVerifyMsgSend(app *baseapp.BaseApp, mapper auth.AccountMapper, msg bank.MsgSend, ctx sdk.Context, privkeys []crypto.PrivKey, handler sdk.Handler) error { +func sendAndVerifyMsgSend(app *baseapp.BaseApp, mapper auth.AccountKeeper, msg bank.MsgSend, ctx sdk.Context, privkeys []crypto.PrivKey, handler sdk.Handler) error { initialInputAddrCoins := make([]sdk.Coins, len(msg.Inputs)) initialOutputAddrCoins := make([]sdk.Coins, len(msg.Outputs)) AccountNumbers := make([]int64, len(msg.Inputs)) diff --git a/x/bank/simulation/sim_test.go b/x/bank/simulation/sim_test.go index ce3877a6204c..5141562281dd 100644 --- a/x/bank/simulation/sim_test.go +++ b/x/bank/simulation/sim_test.go @@ -15,7 +15,7 @@ func TestBankWithRandomMessages(t *testing.T) { mapp := mock.NewApp() bank.RegisterCodec(mapp.Cdc) - mapper := mapp.AccountMapper + mapper := mapp.AccountKeeper bankKeeper := bank.NewBaseKeeper(mapper) mapp.Router().AddRoute("bank", bank.NewHandler(bankKeeper)) diff --git a/x/distribution/abci_app.go b/x/distribution/abci_app.go new file mode 100644 index 000000000000..800ede928d88 --- /dev/null +++ b/x/distribution/abci_app.go @@ -0,0 +1,39 @@ +package distribution + +import ( + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/keeper" +) + +// 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) + for _, voteInfo := range req.LastCommitInfo.GetVotes() { + totalPower += voteInfo.Validator.Power + if voteInfo.SignedLastBlock { + sumPrecommitPower += voteInfo.Validator.Power + } + } + + if totalPower == 0 { + return sdk.ZeroDec() + } + return sdk.NewDec(sumPrecommitPower).Quo(sdk.NewDec(totalPower)) +} diff --git a/x/distribution/alias.go b/x/distribution/alias.go new file mode 100644 index 000000000000..f293857abeb9 --- /dev/null +++ b/x/distribution/alias.go @@ -0,0 +1,79 @@ +// nolint +package distribution + +import ( + "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + "github.com/cosmos/cosmos-sdk/x/distribution/tags" + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +type ( + Keeper = keeper.Keeper + Hooks = keeper.Hooks + + DelegatorWithdrawInfo = types.DelegatorWithdrawInfo + DelegationDistInfo = types.DelegationDistInfo + ValidatorDistInfo = types.ValidatorDistInfo + TotalAccum = types.TotalAccum + FeePool = types.FeePool + + MsgSetWithdrawAddress = types.MsgSetWithdrawAddress + MsgWithdrawDelegatorRewardsAll = types.MsgWithdrawDelegatorRewardsAll + MsgWithdrawDelegatorReward = types.MsgWithdrawDelegatorReward + MsgWithdrawValidatorRewardsAll = types.MsgWithdrawValidatorRewardsAll + + GenesisState = types.GenesisState + + // expected keepers + StakeKeeper = types.StakeKeeper + 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 + DefaultGenesisState = types.DefaultGenesisState + DefaultGenesisWithValidators = types.DefaultGenesisWithValidators + + RegisterCodec = types.RegisterCodec + + NewMsgSetWithdrawAddress = types.NewMsgSetWithdrawAddress + NewMsgWithdrawDelegatorRewardsAll = types.NewMsgWithdrawDelegatorRewardsAll + NewMsgWithdrawDelegatorReward = types.NewMsgWithdrawDelegatorReward + NewMsgWithdrawValidatorRewardsAll = types.NewMsgWithdrawValidatorRewardsAll +) + +const ( + DefaultCodespace = types.DefaultCodespace + CodeInvalidInput = types.CodeInvalidInput +) + +var ( + ErrNilDelegatorAddr = types.ErrNilDelegatorAddr + ErrNilWithdrawAddr = types.ErrNilWithdrawAddr + ErrNilValidatorAddr = types.ErrNilValidatorAddr + + ActionModifyWithdrawAddress = tags.ActionModifyWithdrawAddress + ActionWithdrawDelegatorRewardsAll = tags.ActionWithdrawDelegatorRewardsAll + ActionWithdrawDelegatorReward = tags.ActionWithdrawDelegatorReward + ActionWithdrawValidatorRewardsAll = tags.ActionWithdrawValidatorRewardsAll + + TagAction = tags.Action + TagValidator = tags.Validator + TagDelegator = tags.Delegator +) diff --git a/x/distribution/client/cli/tx.go b/x/distribution/client/cli/tx.go new file mode 100644 index 000000000000..b88968cda5cd --- /dev/null +++ b/x/distribution/client/cli/tx.go @@ -0,0 +1,114 @@ +// nolint +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "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" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" + + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +var ( + flagOnlyFromValidator = "only-from-validator" + flagIsValidator = "is-validator" +) + +// command to withdraw rewards +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", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + + onlyFromVal := viper.GetString(flagOnlyFromValidator) + isVal := viper.GetBool(flagIsValidator) + + if onlyFromVal != "" && isVal { + return fmt.Errorf("cannot use --%v, and --%v flags together", + flagOnlyFromValidator, flagIsValidator) + } + + txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + + var msg sdk.Msg + switch { + case isVal: + addr, err := cliCtx.GetFromAddress() + if err != nil { + return err + } + valAddr := sdk.ValAddress(addr.Bytes()) + msg = types.NewMsgWithdrawValidatorRewardsAll(valAddr) + case onlyFromVal != "": + delAddr, err := cliCtx.GetFromAddress() + if err != nil { + return err + } + + valAddr, err := sdk.ValAddressFromBech32(onlyFromVal) + if err != nil { + return err + } + + msg = types.NewMsgWithdrawDelegatorReward(delAddr, valAddr) + default: + delAddr, err := cliCtx.GetFromAddress() + if err != nil { + return err + } + msg = types.NewMsgWithdrawDelegatorRewardsAll(delAddr) + } + + // build and sign the transaction, then broadcast to Tendermint + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + }, + } + cmd.Flags().String(flagOnlyFromValidator, "", "only withdraw from this validator address (in bech)") + cmd.Flags().Bool(flagIsValidator, false, "also withdraw validator's commission") + return cmd +} + +// GetCmdDelegate implements the delegate command. +func GetCmdSetWithdrawAddr(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "set-withdraw-addr [withdraw-addr]", + Short: "change the default withdraw address for rewards associated with an address", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + + delAddr, err := cliCtx.GetFromAddress() + if err != nil { + return err + } + + withdrawAddr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + msg := types.NewMsgSetWithdrawAddress(delAddr, withdrawAddr) + + // build and sign the transaction, then broadcast to Tendermint + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + }, + } + return cmd +} diff --git a/x/distribution/genesis.go b/x/distribution/genesis.go new file mode 100644 index 000000000000..34680891629b --- /dev/null +++ b/x/distribution/genesis.go @@ -0,0 +1,40 @@ +package distribution + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +// InitGenesis sets distribution information for genesis +func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) { + keeper.SetFeePool(ctx, data.FeePool) + keeper.SetCommunityTax(ctx, data.CommunityTax) + keeper.SetBaseProposerReward(ctx, data.BaseProposerReward) + keeper.SetBonusProposerReward(ctx, data.BonusProposerReward) + + for _, vdi := range data.ValidatorDistInfos { + keeper.SetValidatorDistInfo(ctx, vdi) + } + for _, ddi := range data.DelegationDistInfos { + keeper.SetDelegationDistInfo(ctx, ddi) + } + for _, dw := range data.DelegatorWithdrawInfos { + keeper.SetDelegatorWithdrawAddr(ctx, dw.DelegatorAddr, dw.WithdrawAddr) + } + 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 +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) + pp := keeper.GetPreviousProposerConsAddr(ctx) + return NewGenesisState(feePool, communityTax, baseProposerRewards, + bonusProposerRewards, vdis, ddis, dwis, pp) +} diff --git a/x/distribution/handler.go b/x/distribution/handler.go new file mode 100644 index 000000000000..624589cead28 --- /dev/null +++ b/x/distribution/handler.go @@ -0,0 +1,90 @@ +package distribution + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + "github.com/cosmos/cosmos-sdk/x/distribution/tags" + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +func NewHandler(k keeper.Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + // NOTE msg already has validate basic run + switch msg := msg.(type) { + case 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) + 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 + +func handleMsgModifyWithdrawAddress(ctx sdk.Context, msg types.MsgSetWithdrawAddress, k keeper.Keeper) sdk.Result { + + k.SetDelegatorWithdrawAddr(ctx, msg.DelegatorAddr, msg.WithdrawAddr) + + tags := sdk.NewTags( + tags.Action, tags.ActionModifyWithdrawAddress, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + ) + return sdk.Result{ + Tags: tags, + } +} + +func handleMsgWithdrawDelegatorRewardsAll(ctx sdk.Context, msg types.MsgWithdrawDelegatorRewardsAll, k keeper.Keeper) sdk.Result { + + k.WithdrawDelegationRewardsAll(ctx, msg.DelegatorAddr) + + tags := sdk.NewTags( + tags.Action, tags.ActionWithdrawDelegatorRewardsAll, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + ) + return sdk.Result{ + Tags: tags, + } +} + +func handleMsgWithdrawDelegatorReward(ctx sdk.Context, msg types.MsgWithdrawDelegatorReward, k keeper.Keeper) sdk.Result { + + err := k.WithdrawDelegationReward(ctx, msg.DelegatorAddr, msg.ValidatorAddr) + if err != nil { + return err.Result() + } + + tags := sdk.NewTags( + tags.Action, tags.ActionWithdrawDelegatorReward, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.Validator, []byte(msg.ValidatorAddr.String()), + ) + return sdk.Result{ + Tags: tags, + } +} + +func handleMsgWithdrawValidatorRewardsAll(ctx sdk.Context, msg types.MsgWithdrawValidatorRewardsAll, k keeper.Keeper) sdk.Result { + + err := k.WithdrawValidatorRewardsAll(ctx, msg.ValidatorAddr) + if err != nil { + return err.Result() + } + + tags := sdk.NewTags( + tags.Action, tags.ActionWithdrawValidatorRewardsAll, + tags.Validator, []byte(msg.ValidatorAddr.String()), + ) + return sdk.Result{ + Tags: tags, + } +} diff --git a/x/distribution/keeper/allocation.go b/x/distribution/keeper/allocation.go new file mode 100644 index 000000000000..abe6b32dfb47 --- /dev/null +++ b/x/distribution/keeper/allocation.go @@ -0,0 +1,48 @@ +package keeper + +import ( + 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) + + proposerDist := k.GetValidatorDistInfo(ctx, proposerValidator.GetOperator()) + + // get the fees which have been getting collected through all the + // transactions in the block + feesCollected := k.feeCollectionKeeper.GetCollectedFees(ctx) + feesCollectedDec := types.NewDecCoins(feesCollected) + + // allocated rewards to proposer + baseProposerReward := k.GetBaseProposerReward(ctx) + bonusProposerReward := k.GetBonusProposerReward(ctx) + proposerMultiplier := baseProposerReward.Add(bonusProposerReward.Mul(percentVotes)) + proposerReward := feesCollectedDec.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) + + // allocate community funding + communityTax := k.GetCommunityTax(ctx) + communityFunding := feesCollectedDec.MulDec(communityTax) + feePool := k.GetFeePool(ctx) + feePool.CommunityPool = feePool.CommunityPool.Plus(communityFunding) + + // set the global pool within the distribution module + poolReceived := feesCollectedDec.Minus(proposerReward).Minus(communityFunding) + feePool.ValPool = feePool.ValPool.Plus(poolReceived) + + k.SetValidatorDistInfo(ctx, proposerDist) + k.SetFeePool(ctx, feePool) + + // clear the now distributed fees + k.feeCollectionKeeper.ClearCollectedFees(ctx) +} diff --git a/x/distribution/keeper/allocation_test.go b/x/distribution/keeper/allocation_test.go new file mode 100644 index 000000000000..7d05e82b1ca3 --- /dev/null +++ b/x/distribution/keeper/allocation_test.go @@ -0,0 +1,110 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +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 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)) +} diff --git a/x/distribution/keeper/delegation.go b/x/distribution/keeper/delegation.go new file mode 100644 index 000000000000..ac519107daea --- /dev/null +++ b/x/distribution/keeper/delegation.go @@ -0,0 +1,200 @@ +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 +} + +// 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) +} + +// remove a delegator distribution info +func (k Keeper) RemoveDelegationDistInfo(ctx sdk.Context, delAddr sdk.AccAddress, + valOperatorAddr sdk.ValAddress) { + + store := ctx.KVStore(k.storeKey) + store.Delete(GetDelegationDistInfoKey(delAddr, valOperatorAddr)) +} + +//___________________________________________________________________________________________ + +// 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 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 +} + +//___________________________________________________________________________________________ + +// 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) { + + 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) + } +} + +//___________________________________________________________________________________________ + +// 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 { + + if !k.HasDelegationDistInfo(ctx, delAddr, valAddr) { + 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 +} + +//___________________________________________________________________________________________ + +// return all rewards for all delegations of a delegator +func (k Keeper) WithdrawDelegationRewardsAll(ctx sdk.Context, delAddr sdk.AccAddress) { + withdraw := k.withdrawDelegationRewardsAll(ctx, delAddr) + feePool := k.GetFeePool(ctx) + k.WithdrawToDelegator(ctx, feePool, delAddr, withdraw) +} + +func (k Keeper) withdrawDelegationRewardsAll(ctx sdk.Context, + delAddr sdk.AccAddress) types.DecCoins { + + // iterate over all the delegations + withdraw := types.DecCoins{} + 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) + return false + } + k.stakeKeeper.IterateDelegations(ctx, delAddr, operationAtDelegation) + return withdraw +} + +// 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 +} diff --git a/x/distribution/keeper/delegation_test.go b/x/distribution/keeper/delegation_test.go new file mode 100644 index 000000000000..455ad14e8ebf --- /dev/null +++ b/x/distribution/keeper/delegation_test.go @@ -0,0 +1,253 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/stretchr/testify/require" +) + +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 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 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)) +} + +// 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 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)) +} diff --git a/x/distribution/keeper/genesis.go b/x/distribution/keeper/genesis.go new file mode 100644 index 000000000000..8e5a37abe2f4 --- /dev/null +++ b/x/distribution/keeper/genesis.go @@ -0,0 +1,50 @@ +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 new file mode 100644 index 000000000000..a4f4353fa711 --- /dev/null +++ b/x/distribution/keeper/hooks.go @@ -0,0 +1,128 @@ +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) { + + height := ctx.BlockHeight() + vdi := types.ValidatorDistInfo{ + OperatorAddr: valAddr, + FeePoolWithdrawalHeight: height, + DelAccum: types.NewTotalAccum(height), + DelPool: types.DecCoins{}, + ValCommission: types.DecCoins{}, + } + k.SetValidatorDistInfo(ctx, vdi) +} + +// Withdraw all validator rewards +func (k Keeper) onValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { + // This doesn't need to be run at genesis + if ctx.BlockHeight() > 0 { + if err := k.WithdrawValidatorRewardsAll(ctx, valAddr); err != nil { + panic(err) + } + } +} + +// 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) +} + +// 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)) + } +} + +// Withdrawal all validator distribution rewards and cleanup the distribution record +func (k Keeper) onValidatorRemoved(ctx sdk.Context, valAddr sdk.ValAddress) { + k.RemoveValidatorDistInfo(ctx, valAddr) +} + +//_________________________________________________________________________________________ + +// Create a new delegator distribution record +func (k Keeper) onDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, + valAddr sdk.ValAddress) { + + ddi := types.DelegationDistInfo{ + DelegatorAddr: delAddr, + ValOperatorAddr: valAddr, + DelPoolWithdrawalHeight: ctx.BlockHeight(), + } + k.SetDelegationDistInfo(ctx, ddi) +} + +// 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) { + + k.RemoveDelegationDistInfo(ctx, delAddr, valAddr) +} + +//_________________________________________________________________________________________ + +// Wrapper struct +type Hooks struct { + k Keeper +} + +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) +} +func (h Hooks) OnValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { + h.k.onValidatorRemoved(ctx, valAddr) +} +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) OnDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.k.onValidatorModified(ctx, valAddr) + h.k.onDelegationSharesModified(ctx, delAddr, valAddr) +} +func (h Hooks) OnDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.k.onDelegationRemoved(ctx, delAddr, valAddr) +} +func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { + h.k.onValidatorModified(ctx, valAddr) +} +func (h Hooks) OnValidatorBonded(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { + h.k.onValidatorBonded(ctx, valAddr) +} +func (h Hooks) OnValidatorPowerDidChange(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { + h.k.onValidatorPowerDidChange(ctx, valAddr) +} diff --git a/x/distribution/keeper/keeper.go b/x/distribution/keeper/keeper.go new file mode 100644 index 000000000000..a6fed963586a --- /dev/null +++ b/x/distribution/keeper/keeper.go @@ -0,0 +1,157 @@ +package keeper + +import ( + "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/params" +) + +// keeper of the stake store +type Keeper struct { + storeKey sdk.StoreKey + cdc *codec.Codec + paramSpace params.Subspace + bankKeeper types.BankKeeper + stakeKeeper types.StakeKeeper + feeCollectionKeeper types.FeeCollectionKeeper + + // codespace + codespace sdk.CodespaceType +} + +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramSpace params.Subspace, ck types.BankKeeper, + sk types.StakeKeeper, fck types.FeeCollectionKeeper, codespace sdk.CodespaceType) Keeper { + + keeper := Keeper{ + storeKey: key, + cdc: cdc, + paramSpace: paramSpace.WithTypeTable(ParamTypeTable()), + bankKeeper: ck, + stakeKeeper: 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") + } + 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 { + + // withdraw self-delegation + height := ctx.BlockHeight() + totalPower := sdk.NewDecFromInt(k.stakeKeeper.GetLastTotalPower(ctx)) + fp := k.GetFeePool(ctx) + return fp.GetTotalValAccum(height, totalPower) +} + +//______________________________________________________________________ + +// 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") + } + + 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) +} + +//______________________________________________________________________ + +// get context required for withdraw operations +func (k Keeper) GetWithdrawContext(ctx sdk.Context, + valOperatorAddr sdk.ValAddress) types.WithdrawContext { + + 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)) + + return types.NewWithdrawContext( + feePool, height, lastTotalPower, sdk.NewDecFromInt(lastValPower), + validator.GetCommission()) +} + +//______________________________________________________________________ +// PARAM STORE + +// Type declaration for parameters +func ParamTypeTable() params.TypeTable { + return params.NewTypeTable( + ParamStoreKeyCommunityTax, sdk.Dec{}, + ParamStoreKeyBaseProposerReward, sdk.Dec{}, + ParamStoreKeyBonusProposerReward, sdk.Dec{}, + ) +} + +// 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) +} diff --git a/x/distribution/keeper/keeper_test.go b/x/distribution/keeper/keeper_test.go new file mode 100644 index 000000000000..f8eb0925d538 --- /dev/null +++ b/x/distribution/keeper/keeper_test.go @@ -0,0 +1,37 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/stretchr/testify/require" +) + +func TestSetGetPreviousProposerConsAddr(t *testing.T) { + ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, 0) + + 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) + + someDec := sdk.NewDec(333) + keeper.SetCommunityTax(ctx, someDec) + res := keeper.GetCommunityTax(ctx) + require.True(sdk.DecEq(t, someDec, res)) +} + +func TestSetGetFeePool(t *testing.T) { + ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, 0) + + fp := types.InitialFeePool() + fp.TotalValAccum.UpdateHeight = 777 + + keeper.SetFeePool(ctx, fp) + res := keeper.GetFeePool(ctx) + require.Equal(t, fp.TotalValAccum, res.TotalValAccum) +} diff --git a/x/distribution/keeper/key.go b/x/distribution/keeper/key.go new file mode 100644 index 000000000000..443d13079e3c --- /dev/null +++ b/x/distribution/keeper/key.go @@ -0,0 +1,55 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// keys/key-prefixes +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 + + // params store + ParamStoreKeyCommunityTax = []byte("communitytax") + ParamStoreKeyBaseProposerReward = []byte("baseproposerreward") + ParamStoreKeyBonusProposerReward = []byte("bonusproposerreward") +) + +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 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 prefix for a delegator's distributions across all validators +func GetDelegationDistInfosKey(delAddr sdk.AccAddress) []byte { + return append(DelegationDistInfoKey, delAddr.Bytes()...) +} + +// gets the prefix for a delegator's withdraw info +func GetDelegatorWithdrawAddrKey(delAddr sdk.AccAddress) []byte { + return append(DelegatorWithdrawInfoKey, delAddr.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) +} diff --git a/x/distribution/keeper/test_common.go b/x/distribution/keeper/test_common.go new file mode 100644 index 000000000000..660abbd0e048 --- /dev/null +++ b/x/distribution/keeper/test_common.go @@ -0,0 +1,181 @@ +package keeper + +import ( + "testing" + + "github.com/stretchr/testify/require" + + 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/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/distribution/types" +) + +var ( + delPk1 = ed25519.GenPrivKey().PubKey() + delPk2 = ed25519.GenPrivKey().PubKey() + delPk3 = ed25519.GenPrivKey().PubKey() + delAddr1 = sdk.AccAddress(delPk1.Address()) + delAddr2 = sdk.AccAddress(delPk2.Address()) + delAddr3 = sdk.AccAddress(delPk3.Address()) + + valOpPk1 = ed25519.GenPrivKey().PubKey() + valOpPk2 = ed25519.GenPrivKey().PubKey() + valOpPk3 = ed25519.GenPrivKey().PubKey() + valOpAddr1 = sdk.ValAddress(valOpPk1.Address()) + valOpAddr2 = sdk.ValAddress(valOpPk2.Address()) + valOpAddr3 = sdk.ValAddress(valOpPk3.Address()) + valAccAddr1 = sdk.AccAddress(valOpPk1.Address()) // generate acc addresses for these validator keys too + valAccAddr2 = sdk.AccAddress(valOpPk2.Address()) + valAccAddr3 = sdk.AccAddress(valOpPk3.Address()) + + valConsPk1 = ed25519.GenPrivKey().PubKey() + valConsPk2 = ed25519.GenPrivKey().PubKey() + valConsPk3 = ed25519.GenPrivKey().PubKey() + valConsAddr1 = sdk.ConsAddress(valConsPk1.Address()) + valConsAddr2 = sdk.ConsAddress(valConsPk2.Address()) + valConsAddr3 = sdk.ConsAddress(valConsPk3.Address()) + + addrs = []sdk.AccAddress{ + delAddr1, delAddr2, delAddr3, + valAccAddr1, valAccAddr2, valAccAddr3, + } + + emptyDelAddr sdk.AccAddress + emptyValAddr sdk.ValAddress + emptyPubkey crypto.PubKey +) + +// create a codec used only for testing +func MakeTestCodec() *codec.Codec { + var cdc = codec.New() + bank.RegisterCodec(cdc) + stake.RegisterCodec(cdc) + auth.RegisterCodec(cdc) + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + + types.RegisterCodec(cdc) // distr + return cdc +} + +// test input with default values +func CreateTestInputDefault(t *testing.T, isCheckTx bool, initCoins int64) ( + sdk.Context, auth.AccountKeeper, Keeper, stake.Keeper, DummyFeeCollectionKeeper) { + + communityTax := sdk.NewDecWithPrec(2, 2) + return CreateTestInputAdvanced(t, isCheckTx, initCoins, communityTax) +} + +// 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) { + + 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") + + 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(keyAcc, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyFeeCollection, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) + + err := ms.LoadLatestVersion() + require.Nil(t, err) + + cdc := MakeTestCodec() + pk := params.NewKeeper(cdc, keyParams, tkeyParams) + + ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, log.NewNopLogger()) + accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, 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()) + + // fill all the addresses with some coins, set the loose pool tokens simultaneously + for _, addr := range addrs { + pool := sk.GetPool(ctx) + _, _, err := ck.AddCoins(ctx, addr, sdk.Coins{ + {sk.GetParams(ctx).BondDenom, sdk.NewInt(initCoins)}, + }) + require.Nil(t, err) + pool.LooseTokens = pool.LooseTokens.Add(sdk.NewDec(initCoins)) + sk.SetPool(ctx, pool) + } + + fck := DummyFeeCollectionKeeper{} + keeper := NewKeeper(cdc, keyDistr, pk.Subspace(DefaultParamspace), ck, sk, fck, types.DefaultCodespace) + + // set the distribution hooks on staking + sk.SetHooks(keeper.Hooks()) + + // set genesis items required for distribution + keeper.SetFeePool(ctx, types.InitialFeePool()) + keeper.SetCommunityTax(ctx, communityTax) + keeper.SetBaseProposerReward(ctx, sdk.NewDecWithPrec(1, 2)) + keeper.SetBonusProposerReward(ctx, sdk.NewDecWithPrec(4, 2)) + + return ctx, accountKeeper, keeper, sk, fck +} + +//__________________________________________________________________________________ +// fee collection keeper used only for testing +type DummyFeeCollectionKeeper struct{} + +var heldFees sdk.Coins +var _ types.FeeCollectionKeeper = DummyFeeCollectionKeeper{} + +// nolint +func (fck DummyFeeCollectionKeeper) GetCollectedFees(_ sdk.Context) sdk.Coins { + return heldFees +} +func (fck DummyFeeCollectionKeeper) SetCollectedFees(in sdk.Coins) { + heldFees = in +} +func (fck DummyFeeCollectionKeeper) ClearCollectedFees(_ sdk.Context) { + heldFees = sdk.Coins{} +} + +//__________________________________________________________________________________ +// used in simulation + +// iterate over all the validator distribution infos (inefficient, just used to check invariants) +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++ + } +} diff --git a/x/distribution/keeper/validator.go b/x/distribution/keeper/validator.go new file mode 100644 index 000000000000..d2c755cfa6b5 --- /dev/null +++ b/x/distribution/keeper/validator.go @@ -0,0 +1,100 @@ +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)) +} + +// get the validator distribution info +func (k Keeper) GetValidatorDistInfo(ctx sdk.Context, + operatorAddr sdk.ValAddress) (vdi types.ValidatorDistInfo) { + + store := ctx.KVStore(k.storeKey) + + b := store.Get(GetValidatorDistInfoKey(operatorAddr)) + if b == nil { + panic("Stored validator-distribution info should not have been nil") + } + + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &vdi) + return +} + +// 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) +} + +// remove a validator distribution info +func (k Keeper) RemoveValidatorDistInfo(ctx sdk.Context, valAddr sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetValidatorDistInfoKey(valAddr)) +} + +// 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) + } + + // 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 +} + +// 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) + } + + // withdraw self-delegation + accAddr := sdk.AccAddress(operatorAddr.Bytes()) + withdraw := k.withdrawDelegationRewardsAll(ctx, accAddr) + + // withdrawal validator commission rewards + valInfo := k.GetValidatorDistInfo(ctx, operatorAddr) + wc := k.GetWithdrawContext(ctx, operatorAddr) + valInfo, feePool, commission := valInfo.WithdrawCommission(wc) + withdraw = withdraw.Plus(commission) + k.SetValidatorDistInfo(ctx, valInfo) + + 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 +} diff --git a/x/distribution/keeper/validator_test.go b/x/distribution/keeper/validator_test.go new file mode 100644 index 000000000000..62638a4d2289 --- /dev/null +++ b/x/distribution/keeper/validator_test.go @@ -0,0 +1,192 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/stretchr/testify/require" +) + +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 + 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) + + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // 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 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)) + expRes := sdk.NewDec(90). // orig tokens (100 - 10) + Add(feesInNonProposer.Quo(sdk.NewDec(10))). // validator 1 has 1/10 total power + Add(feesInProposer). + 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 new file mode 100644 index 000000000000..75863bfdb406 --- /dev/null +++ b/x/distribution/simulation/invariants.go @@ -0,0 +1,50 @@ +package simulation + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/mock/simulation" + abci "github.com/tendermint/tendermint/abci/types" +) + +// AllInvariants runs all invariants of the distribution module +// Currently: total supply, positive power +func AllInvariants(d distr.Keeper, sk distr.StakeKeeper) simulation.Invariant { + return func(app *baseapp.BaseApp) error { + err := ValAccumInvariants(d, sk)(app) + if err != nil { + return err + } + return nil + } +} + +// ValAccumInvariants checks that the fee pool accum == sum all validators' accum +func ValAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invariant { + + return func(app *baseapp.BaseApp) error { + mockHeader := abci.Header{Height: app.LastBlockHeight() + 1} + ctx := app.NewContext(false, mockHeader) + 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()) + } + + return nil + } +} diff --git a/x/distribution/simulation/msgs.go b/x/distribution/simulation/msgs.go new file mode 100644 index 000000000000..62881c5f958e --- /dev/null +++ b/x/distribution/simulation/msgs.go @@ -0,0 +1,122 @@ +package simulation + +import ( + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/mock/simulation" +) + +// SimulateMsgSetWithdrawAddress +func SimulateMsgSetWithdrawAddress(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) { + + accountOrigin := simulation.RandomAcc(r, accs) + accountDestination := simulation.RandomAcc(r, accs) + msg := distribution.NewMsgSetWithdrawAddress(accountOrigin.Address, accountDestination.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/MsgSetWithdrawAddress/%v", result.IsOK())) + + action = fmt.Sprintf("TestMsgSetWithdrawAddress: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) + return action, nil, nil + } +} + +// 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) + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simulation.Account, event func(string)) ( + action string, fOp []simulation.FutureOperation, err error) { + + delegatorAccount := simulation.RandomAcc(r, accs) + validatorAccount := simulation.RandomAcc(r, accs) + msg := distribution.NewMsgWithdrawDelegatorReward(delegatorAccount.Address, sdk.ValAddress(validatorAccount.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/MsgWithdrawDelegatorReward/%v", result.IsOK())) + + action = fmt.Sprintf("TestMsgWithdrawDelegatorReward: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) + return action, nil, nil + } +} + +// SimulateMsgWithdrawValidatorRewardsAll +func SimulateMsgWithdrawValidatorRewardsAll(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)) + + 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/MsgWithdrawValidatorRewardsAll/%v", result.IsOK())) + + action = fmt.Sprintf("TestMsgWithdrawValidatorRewardsAll: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) + return action, nil, nil + } +} diff --git a/x/distribution/tags/tags.go b/x/distribution/tags/tags.go new file mode 100644 index 000000000000..dd55ba23690b --- /dev/null +++ b/x/distribution/tags/tags.go @@ -0,0 +1,17 @@ +// nolint +package tags + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + ActionModifyWithdrawAddress = []byte("modify-withdraw-address") + ActionWithdrawDelegatorRewardsAll = []byte("withdraw-delegator-rewards-all") + ActionWithdrawDelegatorReward = []byte("withdraw-delegator-reward") + ActionWithdrawValidatorRewardsAll = []byte("withdraw-validator-rewards-all") + + Action = sdk.TagAction + Validator = sdk.TagSrcValidator + Delegator = sdk.TagDelegator +) diff --git a/x/distribution/types/dec_coin.go b/x/distribution/types/dec_coin.go index 59373976de71..5eedad7e36b3 100644 --- a/x/distribution/types/dec_coin.go +++ b/x/distribution/types/dec_coin.go @@ -43,9 +43,11 @@ func (coin DecCoin) Minus(coinB DecCoin) DecCoin { return DecCoin{coin.Denom, coin.Amount.Sub(coinB.Amount)} } -// return the decimal coins with trunctated decimals -func (coin DecCoin) TruncateDecimal() sdk.Coin { - return sdk.NewCoin(coin.Denom, coin.Amount.TruncateInt()) +// 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} } //_______________________________________________________________________ @@ -61,13 +63,16 @@ func NewDecCoins(coins sdk.Coins) DecCoins { return dcs } -// return the coins with trunctated decimals -func (coins DecCoins) TruncateDecimal() sdk.Coins { +// 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 { - out[i] = coin.TruncateDecimal() + truncated, change := coin.TruncateDecimal() + out[i] = truncated + changeSum = changeSum.Plus(DecCoins{change}) } - return out + return out, changeSum } // Plus combines two sets of coins @@ -147,3 +152,27 @@ func (coins DecCoins) QuoDec(d sdk.Dec) DecCoins { } 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) + } + } +} diff --git a/x/distribution/types/delegator_info.go b/x/distribution/types/delegator_info.go index 4de2968904b7..83ca7f8cc95e 100644 --- a/x/distribution/types/delegator_info.go +++ b/x/distribution/types/delegator_info.go @@ -5,43 +5,71 @@ import ( ) // distribution info for a delegation - used to determine entitled rewards -type DelegatorDistInfo struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - ValOperatorAddr sdk.ValAddress `json:"val_operator_addr"` - WithdrawalHeight int64 `json:"withdrawal_height"` // last time this delegation withdrew 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 NewDelegatorDistInfo(delegatorAddr sdk.AccAddress, valOperatorAddr sdk.ValAddress, - currentHeight int64) DelegatorDistInfo { +func NewDelegationDistInfo(delegatorAddr sdk.AccAddress, valOperatorAddr sdk.ValAddress, + currentHeight int64) DelegationDistInfo { - return DelegatorDistInfo{ - DelegatorAddr: delegatorAddr, - ValOperatorAddr: valOperatorAddr, - WithdrawalHeight: currentHeight, + return DelegationDistInfo{ + DelegatorAddr: delegatorAddr, + ValOperatorAddr: valOperatorAddr, + DelPoolWithdrawalHeight: currentHeight, } } -// withdraw rewards from delegator -func (di DelegatorDistInfo) WithdrawRewards(fp FeePool, vi ValidatorDistInfo, - height int64, totalBonded, vdTokens, totalDelShares, delegatorShares, - commissionRate sdk.Dec) (DelegatorDistInfo, ValidatorDistInfo, FeePool, DecCoins) { +// 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 + return delegatorShares.MulInt(sdk.NewInt(blocks)) +} + +// 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) { - vi = vi.UpdateTotalDelAccum(height, totalDelShares) + fp := wc.FeePool + vi = vi.UpdateTotalDelAccum(wc.Height, totalDelShares) if vi.DelAccum.Accum.IsZero() { return di, vi, fp, DecCoins{} } - vi, fp = vi.TakeFeePoolRewards(fp, height, totalBonded, vdTokens, commissionRate) + vi, fp = vi.TakeFeePoolRewards(wc) - blocks := height - di.WithdrawalHeight - di.WithdrawalHeight = height - accum := delegatorShares.MulInt(sdk.NewInt(blocks)) - withdrawalTokens := vi.Pool.MulDec(accum).QuoDec(vi.DelAccum.Accum) - remainingTokens := vi.Pool.Minus(withdrawalTokens) + accum := di.GetDelAccum(wc.Height, delegatorShares) + di.DelPoolWithdrawalHeight = wc.Height + withdrawalTokens := vi.DelPool.MulDec(accum).QuoDec(vi.DelAccum.Accum) + remDelPool := vi.DelPool.Minus(withdrawalTokens) - vi.Pool = remainingTokens + 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/delegator_info_test.go b/x/distribution/types/delegator_info_test.go index 2d9e9bc0fe44..e15ad293038a 100644 --- a/x/distribution/types/delegator_info_test.go +++ b/x/distribution/types/delegator_info_test.go @@ -18,39 +18,43 @@ func TestWithdrawRewards(t *testing.T) { validatorDelShares := sdk.NewDec(10) totalBondedTokens := validatorTokens.Add(sdk.NewDec(90)) // validator-1 is 10% of total power - di1 := NewDelegatorDistInfo(delAddr1, valAddr1, height) + di1 := NewDelegationDistInfo(delAddr1, valAddr1, height) di1Shares := sdk.NewDec(5) // this delegator has half the shares in the validator - di2 := NewDelegatorDistInfo(delAddr2, valAddr1, height) + di2 := NewDelegationDistInfo(delAddr2, valAddr1, height) di2Shares := sdk.NewDec(5) // simulate adding some stake for inflation height = 10 - fp.Pool = DecCoins{NewDecCoin("stake", 1000)} + fp.ValPool = DecCoins{NewDecCoin("stake", 1000)} // withdraw rewards - di1, vi, fp, rewardRecv1 := di1.WithdrawRewards(fp, vi, height, totalBondedTokens, - validatorTokens, validatorDelShares, di1Shares, commissionRate) - - assert.Equal(t, height, di1.WithdrawalHeight) - assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValAccum.Accum)) - assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.Pool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(49), vi.Pool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(2), vi.PoolCommission[0].Amount)) + 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.Pool[0].Amount = fp.Pool[0].Amount.Add(sdk.NewDec(1000)) + fp.ValPool[0].Amount = fp.ValPool[0].Amount.Add(sdk.NewDec(1000)) // withdraw rewards - di2, vi, fp, rewardRecv2 := di2.WithdrawRewards(fp, vi, height, totalBondedTokens, - validatorTokens, validatorDelShares, di2Shares, commissionRate) - - assert.Equal(t, height, di2.WithdrawalHeight) - assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.ValAccum.Accum)) - assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.Pool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(49), vi.Pool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(4), vi.PoolCommission[0].Amount)) + 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/errors.go b/x/distribution/types/errors.go index 57a3dd73e360..605c1b38db33 100644 --- a/x/distribution/types/errors.go +++ b/x/distribution/types/errors.go @@ -8,8 +8,9 @@ import ( type CodeType = sdk.CodeType const ( - DefaultCodespace sdk.CodespaceType = 6 - CodeInvalidInput CodeType = 103 + DefaultCodespace sdk.CodespaceType = 6 + CodeInvalidInput CodeType = 103 + CodeNoDistributionInfo CodeType = 104 ) func ErrNilDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { @@ -21,3 +22,9 @@ func ErrNilWithdrawAddr(codespace sdk.CodespaceType) sdk.Error { func ErrNilValidatorAddr(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "validator address is nil") } +func ErrNoDelegationDistInfo(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeNoDistributionInfo, "no delegation distribution info") +} +func ErrNoValidatorDistInfo(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeNoDistributionInfo, "no validator distribution info") +} diff --git a/x/distribution/types/fee_pool.go b/x/distribution/types/fee_pool.go index 66731cb197a2..c03647330777 100644 --- a/x/distribution/types/fee_pool.go +++ b/x/distribution/types/fee_pool.go @@ -4,51 +4,30 @@ 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 validator 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 -} - -//___________________________________________________________________________________________ - // global fee pool for distribution type FeePool struct { - ValAccum TotalAccum `json:"val_accum"` // total valdator accum held by validators - Pool DecCoins `json:"pool"` // funds for all validators which have yet to be withdrawn + 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.ValAccum = f.ValAccum.UpdateForNewHeight(height, totalBondedTokens) + 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) +} + // zero fee pool func InitialFeePool() FeePool { return FeePool{ - ValAccum: NewTotalAccum(0), - Pool: DecCoins{}, + TotalValAccum: NewTotalAccum(0), + ValPool: DecCoins{}, CommunityPool: DecCoins{}, } } diff --git a/x/distribution/types/fee_pool_test.go b/x/distribution/types/fee_pool_test.go index e39fb09c94c1..73bda52fabf0 100644 --- a/x/distribution/types/fee_pool_test.go +++ b/x/distribution/types/fee_pool_test.go @@ -7,24 +7,13 @@ import ( "github.com/stretchr/testify/require" ) -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)) -} - func TestUpdateTotalValAccum(t *testing.T) { fp := InitialFeePool() fp = fp.UpdateTotalValAccum(5, sdk.NewDec(3)) - require.True(sdk.DecEq(t, sdk.NewDec(15), fp.ValAccum.Accum)) + require.True(sdk.DecEq(t, sdk.NewDec(15), fp.TotalValAccum.Accum)) fp = fp.UpdateTotalValAccum(8, sdk.NewDec(2)) - require.True(sdk.DecEq(t, sdk.NewDec(21), fp.ValAccum.Accum)) + 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 f01d1b781e49..0577af4d33b4 100644 --- a/x/distribution/types/genesis.go +++ b/x/distribution/types/genesis.go @@ -12,22 +12,58 @@ type DelegatorWithdrawInfo struct { // 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"` - DelegatorDistInfos []DelegatorDistInfo `json:"delegator_dist_infos"` + DelegationDistInfos []DelegationDistInfo `json:"delegator_dist_infos"` DelegatorWithdrawInfos []DelegatorWithdrawInfo `json:"delegator_withdraw_infos"` + PreviousProposer sdk.ConsAddress `json:"previous_proposer"` } -func NewGenesisState(feePool FeePool, vdis []ValidatorDistInfo, ddis []DelegatorDistInfo) GenesisState { +func NewGenesisState(feePool FeePool, communityTax, baseProposerReward, bonusProposerReward sdk.Dec, + vdis []ValidatorDistInfo, ddis []DelegationDistInfo, dwis []DelegatorWithdrawInfo, pp sdk.ConsAddress) GenesisState { + return GenesisState{ - FeePool: feePool, - ValidatorDistInfos: vdis, - DelegatorDistInfos: ddis, + FeePool: feePool, + CommunityTax: communityTax, + BaseProposerReward: baseProposerReward, + BonusProposerReward: bonusProposerReward, + ValidatorDistInfos: vdis, + DelegationDistInfos: ddis, + DelegatorWithdrawInfos: dwis, + PreviousProposer: pp, } } // get raw genesis raw message for testing func DefaultGenesisState() GenesisState { return GenesisState{ - FeePool: InitialFeePool(), + 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, } } diff --git a/x/distribution/types/keepers.go b/x/distribution/types/keepers.go index 31a68a5da9a6..2578ea411360 100644 --- a/x/distribution/types/keepers.go +++ b/x/distribution/types/keepers.go @@ -10,6 +10,8 @@ type StakeKeeper interface { Validator(ctx sdk.Context, valAddr sdk.ValAddress) sdk.Validator ValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) sdk.Validator TotalPower(ctx sdk.Context) sdk.Dec + GetLastTotalPower(ctx sdk.Context) sdk.Int + GetLastValidatorPower(ctx sdk.Context, valAddr sdk.ValAddress) sdk.Int } // expected coin keeper diff --git a/x/distribution/types/msg.go b/x/distribution/types/msg.go index f00dceae1f5d..f7c80c2122ce 100644 --- a/x/distribution/types/msg.go +++ b/x/distribution/types/msg.go @@ -6,7 +6,7 @@ import ( ) // name to identify transaction types -const MsgType = "distr" +const MsgRoute = "distr" // Verify interface at compile time var _, _ sdk.Msg = &MsgSetWithdrawAddress{}, &MsgWithdrawDelegatorRewardsAll{} @@ -27,8 +27,8 @@ func NewMsgSetWithdrawAddress(delAddr, withdrawAddr sdk.AccAddress) MsgSetWithdr } } -func (msg MsgSetWithdrawAddress) Type() string { return MsgType } -func (msg MsgSetWithdrawAddress) Name() string { return "set_withdraw_address" } +func (msg MsgSetWithdrawAddress) Route() string { return MsgRoute } +func (msg MsgSetWithdrawAddress) Type() string { return "set_withdraw_address" } // Return address that must sign over msg.GetSignBytes() func (msg MsgSetWithdrawAddress) GetSigners() []sdk.AccAddress { @@ -68,8 +68,8 @@ func NewMsgWithdrawDelegatorRewardsAll(delAddr sdk.AccAddress) MsgWithdrawDelega } } -func (msg MsgWithdrawDelegatorRewardsAll) Type() string { return MsgType } -func (msg MsgWithdrawDelegatorRewardsAll) Name() string { return "withdraw_delegation_rewards_all" } +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 { @@ -108,8 +108,8 @@ func NewMsgWithdrawDelegatorReward(delAddr sdk.AccAddress, valAddr sdk.ValAddres } } -func (msg MsgWithdrawDelegatorReward) Type() string { return MsgType } -func (msg MsgWithdrawDelegatorReward) Name() string { return "withdraw_delegation_reward" } +func (msg MsgWithdrawDelegatorReward) Route() string { return MsgRoute } +func (msg MsgWithdrawDelegatorReward) Type() string { return "withdraw_delegation_reward" } // Return address that must sign over msg.GetSignBytes() func (msg MsgWithdrawDelegatorReward) GetSigners() []sdk.AccAddress { @@ -149,8 +149,8 @@ func NewMsgWithdrawValidatorRewardsAll(valAddr sdk.ValAddress) MsgWithdrawValida } } -func (msg MsgWithdrawValidatorRewardsAll) Type() string { return MsgType } -func (msg MsgWithdrawValidatorRewardsAll) Name() string { return "withdraw_validator_rewards_all" } +func (msg MsgWithdrawValidatorRewardsAll) Route() string { return MsgRoute } +func (msg MsgWithdrawValidatorRewardsAll) Type() string { return "withdraw_validator_rewards_all" } // Return address that must sign over msg.GetSignBytes() func (msg MsgWithdrawValidatorRewardsAll) GetSigners() []sdk.AccAddress { diff --git a/x/distribution/types/test_utils.go b/x/distribution/types/test_common.go similarity index 82% rename from x/distribution/types/test_utils.go rename to x/distribution/types/test_common.go index 3ea78cd792aa..b77efd46c53d 100644 --- a/x/distribution/types/test_utils.go +++ b/x/distribution/types/test_common.go @@ -18,9 +18,9 @@ var ( valPk1 = ed25519.GenPrivKey().PubKey() valPk2 = ed25519.GenPrivKey().PubKey() valPk3 = ed25519.GenPrivKey().PubKey() - valAddr1 = sdk.ValAddress(delPk1.Address()) - valAddr2 = sdk.ValAddress(delPk2.Address()) - valAddr3 = sdk.ValAddress(delPk3.Address()) + valAddr1 = sdk.ValAddress(valPk1.Address()) + valAddr2 = sdk.ValAddress(valPk2.Address()) + valAddr3 = sdk.ValAddress(valPk3.Address()) emptyValAddr sdk.ValAddress emptyPubkey crypto.PubKey diff --git a/x/distribution/types/total_accum.go b/x/distribution/types/total_accum.go new file mode 100644 index 000000000000..0915847a5ef6 --- /dev/null +++ b/x/distribution/types/total_accum.go @@ -0,0 +1,40 @@ +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 new file mode 100644 index 000000000000..81f80a154a0e --- /dev/null +++ b/x/distribution/types/total_accum_test.go @@ -0,0 +1,19 @@ +package types + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +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_info.go b/x/distribution/types/validator_info.go index 18aef8bde6e4..d7739359f2fa 100644 --- a/x/distribution/types/validator_info.go +++ b/x/distribution/types/validator_info.go @@ -4,24 +4,47 @@ 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:"global_withdrawal_height"` // last height this validator withdrew from the global pool - Pool DecCoins `json:"pool"` // rewards owed to delegators, commission has already been charged (includes proposer reward) - PoolCommission DecCoins `json:"pool_commission"` // commission collected by this validator (pending withdrawal) + FeePoolWithdrawalHeight int64 `json:"fee_pool_withdrawal_height"` // last height this validator withdrew from the global pool - DelAccum TotalAccum `json:"del_accum"` // total proposer pool accumulation factor held by delegators + 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, - Pool: DecCoins{}, - PoolCommission: DecCoins{}, - DelAccum: NewTotalAccum(currentHeight), + DelPool: DecCoins{}, + DelAccum: NewTotalAccum(currentHeight), + ValCommission: DecCoins{}, } } @@ -31,42 +54,100 @@ func (vi ValidatorDistInfo) UpdateTotalDelAccum(height int64, totalDelShares sdk return vi } -// move any available accumulated fees in the FeePool to the validator's pool -func (vi ValidatorDistInfo) TakeFeePoolRewards(fp FeePool, height int64, totalBonded, vdTokens, - commissionRate sdk.Dec) (ValidatorDistInfo, FeePool) { +// 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) +} - fp = fp.UpdateTotalValAccum(height, totalBonded) +// 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)) +} - if fp.ValAccum.Accum.IsZero() { +// 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 - blocks := height - vi.FeePoolWithdrawalHeight - vi.FeePoolWithdrawalHeight = height - accum := vdTokens.MulInt(sdk.NewInt(blocks)) - withdrawalTokens := fp.Pool.MulDec(accum).QuoDec(fp.ValAccum.Accum) - remainingTokens := fp.Pool.Minus(withdrawalTokens) + 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) + remValPool := fp.ValPool.Minus(withdrawalTokens) - commission := withdrawalTokens.MulDec(commissionRate) + commission := withdrawalTokens.MulDec(wc.CommissionRate) afterCommission := withdrawalTokens.Minus(commission) - fp.ValAccum.Accum = fp.ValAccum.Accum.Sub(accum) - fp.Pool = remainingTokens - vi.PoolCommission = vi.PoolCommission.Plus(commission) - vi.Pool = vi.Pool.Plus(afterCommission) + 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(fp FeePool, height int64, - totalBonded, vdTokens, commissionRate sdk.Dec) (vio ValidatorDistInfo, fpo FeePool, withdrawn DecCoins) { +func (vi ValidatorDistInfo) WithdrawCommission(wc WithdrawContext) ( + vio ValidatorDistInfo, fpo FeePool, withdrawn DecCoins) { - vi, fp = vi.TakeFeePoolRewards(fp, height, totalBonded, vdTokens, commissionRate) + vi, fp := vi.TakeFeePoolRewards(wc) - withdrawalTokens := vi.PoolCommission - vi.PoolCommission = DecCoins{} // zero + 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 index afa6d8c76363..24c3eaee435f 100644 --- a/x/distribution/types/validator_info_test.go +++ b/x/distribution/types/validator_info_test.go @@ -26,29 +26,32 @@ func TestTakeFeePoolRewards(t *testing.T) { // simulate adding some stake for inflation height = 10 - fp.Pool = DecCoins{NewDecCoin("stake", 1000)} + fp.ValPool = DecCoins{NewDecCoin("stake", 1000)} - vi1, fp = vi1.TakeFeePoolRewards(fp, height, totalBondedTokens, validatorTokens1, commissionRate1) - require.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValAccum.Accum)) - assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.Pool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(100-2), vi1.Pool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(2), vi1.PoolCommission[0].Amount)) + 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(fp, height, totalBondedTokens, validatorTokens2, commissionRate2) - require.True(sdk.DecEq(t, sdk.NewDec(500), fp.ValAccum.Accum)) - assert.True(sdk.DecEq(t, sdk.NewDec(500), fp.Pool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(400-12), vi2.Pool[0].Amount)) - assert.True(sdk.DecEq(t, vi2.PoolCommission[0].Amount, sdk.NewDec(12))) + 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.Pool[0].Amount = fp.Pool[0].Amount.Add(sdk.NewDec(1000)) + fp.ValPool[0].Amount = fp.ValPool[0].Amount.Add(sdk.NewDec(1000)) - vi3, fp = vi3.TakeFeePoolRewards(fp, height, totalBondedTokens, validatorTokens3, commissionRate3) - require.True(sdk.DecEq(t, sdk.NewDec(500), fp.ValAccum.Accum)) - assert.True(sdk.DecEq(t, sdk.NewDec(500), fp.Pool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(1000-40), vi3.Pool[0].Amount)) - assert.True(sdk.DecEq(t, vi3.PoolCommission[0].Amount, sdk.NewDec(40))) + 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) { @@ -63,23 +66,25 @@ func TestWithdrawCommission(t *testing.T) { // simulate adding some stake for inflation height = 10 - fp.Pool = DecCoins{NewDecCoin("stake", 1000)} + fp.ValPool = DecCoins{NewDecCoin("stake", 1000)} // for a more fun staring condition, have an non-withdraw update - vi, fp = vi.TakeFeePoolRewards(fp, height, totalBondedTokens, validatorTokens, commissionRate) - require.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValAccum.Accum)) - assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.Pool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(100-2), vi.Pool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(2), vi.PoolCommission[0].Amount)) + 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.Pool[0].Amount = fp.Pool[0].Amount.Add(sdk.NewDec(1000)) + fp.ValPool[0].Amount = fp.ValPool[0].Amount.Add(sdk.NewDec(1000)) - vi, fp, commissionRecv := vi.WithdrawCommission(fp, height, totalBondedTokens, validatorTokens, commissionRate) - require.True(sdk.DecEq(t, sdk.NewDec(1800), fp.ValAccum.Accum)) - assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.Pool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(200-4), vi.Pool[0].Amount)) - assert.Zero(t, len(vi.PoolCommission)) + 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/tx.go b/x/gov/client/cli/tx.go index 23e95105e0ed..31780f68e74d 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -15,22 +15,23 @@ import ( "io/ioutil" "strings" + "github.com/cosmos/cosmos-sdk/x/gov/client" "github.com/spf13/cobra" "github.com/spf13/viper" ) const ( - flagProposalID = "proposal-id" - flagTitle = "title" - flagDescription = "description" - flagProposalType = "type" - flagDeposit = "deposit" - flagVoter = "voter" - flagOption = "option" - flagDepositer = "depositer" - flagStatus = "status" - flagLatestProposalIDs = "latest" - flagProposal = "proposal" + flagProposalID = "proposal-id" + flagTitle = "title" + flagDescription = "description" + flagProposalType = "type" + flagDeposit = "deposit" + flagVoter = "voter" + flagOption = "option" + flagDepositer = "depositer" + flagStatus = "status" + flagNumLimit = "limit" + flagProposal = "proposal" ) type proposal struct { @@ -115,7 +116,7 @@ $ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome cmd.Flags().String(flagTitle, "", "title of proposal") cmd.Flags().String(flagDescription, "", "description of proposal") - cmd.Flags().String(flagProposalType, "", "proposalType of proposal") + cmd.Flags().String(flagProposalType, "", "proposalType of proposal, types: text/parameter_change/software_upgrade") cmd.Flags().String(flagDeposit, "", "deposit of proposal") cmd.Flags().String(flagProposal, "", "proposal file path (if this path is given, other proposal flags are ignored)") @@ -129,7 +130,7 @@ func parseSubmitProposalFlags() (*proposal, error) { if proposalFile == "" { proposal.Title = viper.GetString(flagTitle) proposal.Description = viper.GetString(flagDescription) - proposal.Type = viper.GetString(flagProposalType) + proposal.Type = client.NormalizeProposalType(viper.GetString(flagProposalType)) proposal.Deposit = viper.GetString(flagDeposit) return proposal, nil } @@ -169,7 +170,7 @@ func GetCmdDeposit(cdc *codec.Codec) *cobra.Command { return err } - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) amount, err := sdk.ParseCoins(viper.GetString(flagDeposit)) if err != nil { @@ -202,7 +203,7 @@ func GetCmdDeposit(cdc *codec.Codec) *cobra.Command { func GetCmdVote(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "vote", - Short: "Vote for an active proposal, options: Yes/No/NoWithVeto/Abstain", + Short: "Vote for an active proposal, options: yes/no/no_with_veto/abstain", RunE: func(cmd *cobra.Command, args []string) error { txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) cliCtx := context.NewCLIContext(). @@ -214,10 +215,10 @@ func GetCmdVote(cdc *codec.Codec) *cobra.Command { return err } - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) option := viper.GetString(flagOption) - byteVoteOption, err := gov.VoteOptionFromString(option) + byteVoteOption, err := gov.VoteOptionFromString(client.NormalizeVoteOption(option)) if err != nil { return err } @@ -243,7 +244,31 @@ func GetCmdVote(cdc *codec.Codec) *cobra.Command { } cmd.Flags().String(flagProposalID, "", "proposalID of proposal voting on") - cmd.Flags().String(flagOption, "", "vote option {Yes, No, NoWithVeto, Abstain}") + cmd.Flags().String(flagOption, "", "vote option {yes, no, no_with_veto, abstain}") + + return cmd +} + +// GetCmdQueryProposal implements the query proposal command. +func GetCmdQueryParams(queryRoute string, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "param [param-type]", + Short: "Query the parameters (voting|tallying|deposit) of the governance process", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + paramType := args[0] + + cliCtx := context.NewCLIContext().WithCodec(cdc) + + res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/params/%s", queryRoute, paramType), nil) + if err != nil { + return err + } + + fmt.Println(string(res)) + return nil + }, + } return cmd } @@ -255,7 +280,7 @@ func GetCmdQueryProposal(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Query details of a single proposal", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) params := gov.QueryProposalParams{ ProposalID: proposalID, @@ -290,10 +315,10 @@ func GetCmdQueryProposals(queryRoute string, cdc *codec.Codec) *cobra.Command { bechDepositerAddr := viper.GetString(flagDepositer) bechVoterAddr := viper.GetString(flagVoter) strProposalStatus := viper.GetString(flagStatus) - latestProposalsIDs := viper.GetInt64(flagLatestProposalIDs) + numLimit := uint64(viper.GetInt64(flagNumLimit)) params := gov.QueryProposalsParams{ - NumLatestProposals: latestProposalsIDs, + Limit: numLimit, } if len(bechDepositerAddr) != 0 { @@ -313,7 +338,7 @@ func GetCmdQueryProposals(queryRoute string, cdc *codec.Codec) *cobra.Command { } if len(strProposalStatus) != 0 { - proposalStatus, err := gov.ProposalStatusFromString(strProposalStatus) + proposalStatus, err := gov.ProposalStatusFromString(client.NormalizeProposalStatus(strProposalStatus)) if err != nil { return err } @@ -351,10 +376,10 @@ func GetCmdQueryProposals(queryRoute string, cdc *codec.Codec) *cobra.Command { }, } - cmd.Flags().String(flagLatestProposalIDs, "", "(optional) limit to latest [number] proposals. Defaults to all proposals") + cmd.Flags().String(flagNumLimit, "", "(optional) limit to latest [number] proposals. Defaults to all proposals") cmd.Flags().String(flagDepositer, "", "(optional) filter by proposals deposited on by depositer") cmd.Flags().String(flagVoter, "", "(optional) filter by proposals voted on by voted") - cmd.Flags().String(flagStatus, "", "(optional) filter proposals by proposal status") + cmd.Flags().String(flagStatus, "", "(optional) filter proposals by proposal status, status: deposit_period/voting_period/passed/rejected") return cmd } @@ -367,7 +392,7 @@ func GetCmdQueryVote(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Query details of a single vote", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) voterAddr, err := sdk.AccAddressFromBech32(viper.GetString(flagVoter)) if err != nil { @@ -406,7 +431,7 @@ func GetCmdQueryVotes(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Query votes on a proposal", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) params := gov.QueryVotesParams{ ProposalID: proposalID, @@ -439,7 +464,7 @@ func GetCmdQueryDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Query details of a deposit", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) depositerAddr, err := sdk.AccAddressFromBech32(viper.GetString(flagDepositer)) if err != nil { @@ -478,7 +503,7 @@ func GetCmdQueryDeposits(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Query deposits on a proposal", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) params := gov.QueryDepositsParams{ ProposalID: proposalID, @@ -503,14 +528,14 @@ func GetCmdQueryDeposits(queryRoute string, cdc *codec.Codec) *cobra.Command { return cmd } -// GetCmdQueryDeposits implements the command to query for proposal deposits. +// GetCmdQueryTally implements the command to query for proposal tally result. func GetCmdQueryTally(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "tally", Short: "Get the tally of a proposal vote", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) params := gov.QueryTallyParams{ ProposalID: proposalID, diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index f66331ad5a18..930a39b025c7 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -10,6 +10,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/gov/client" "github.com/gorilla/mux" "github.com/pkg/errors" ) @@ -17,11 +18,12 @@ import ( // REST Variable names // nolint const ( + RestParamsType = "type" RestProposalID = "proposal-id" RestDepositer = "depositer" RestVoter = "voter" RestProposalStatus = "status" - RestNumLatest = "latest" + RestNumLimit = "limit" storeName = "gov" ) @@ -31,22 +33,26 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), depositHandlerFn(cdc, cliCtx)).Methods("POST") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), voteHandlerFn(cdc, cliCtx)).Methods("POST") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(cdc)).Methods("GET") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositer), queryDepositHandlerFn(cdc)).Methods("GET") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(cdc)).Methods("GET") - - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(cdc)).Methods("GET") - - r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(cdc)).Methods("GET") + r.HandleFunc( + fmt.Sprintf("/gov/parameters/{%s}", RestParamsType), + queryParamsHandlerFn(cdc, cliCtx), + ).Methods("GET") + + 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}/deposits", RestProposalID), queryDepositsHandlerFn(cdc, cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositer), queryDepositHandlerFn(cdc, cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(cdc, cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(cdc, cliCtx)).Methods("GET") } type postProposalReq struct { - BaseReq utils.BaseReq `json:"base_req"` - Title string `json:"title"` // Title of the proposal - Description string `json:"description"` // Description of the proposal - ProposalType gov.ProposalKind `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} - Proposer sdk.AccAddress `json:"proposer"` // Address of the proposer - InitialDeposit sdk.Coins `json:"initial_deposit"` // Coins to add to the proposal's deposit + BaseReq utils.BaseReq `json:"base_req"` + Title string `json:"title"` // Title of the proposal + Description string `json:"description"` // Description of the proposal + ProposalType string `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Proposer sdk.AccAddress `json:"proposer"` // Address of the proposer + InitialDeposit sdk.Coins `json:"initial_deposit"` // Coins to add to the proposal's deposit } type depositReq struct { @@ -58,7 +64,7 @@ type depositReq struct { type voteReq struct { BaseReq utils.BaseReq `json:"base_req"` Voter sdk.AccAddress `json:"voter"` // address of the voter - Option gov.VoteOption `json:"option"` // option from OptionSet chosen by the voter + Option string `json:"option"` // option from OptionSet chosen by the voter } func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { @@ -66,6 +72,7 @@ func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han var req postProposalReq err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -74,8 +81,14 @@ func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han return } + proposalType, err := gov.ProposalTypeFromString(client.NormalizeProposalType(req.ProposalType)) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + // create the message - msg := gov.NewMsgSubmitProposal(req.Title, req.Description, req.ProposalType, req.Proposer, req.InitialDeposit) + msg := gov.NewMsgSubmitProposal(req.Title, req.Description, proposalType, req.Proposer, req.InitialDeposit) err = msg.ValidateBasic() if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) @@ -97,7 +110,7 @@ func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerF return } - proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -136,7 +149,7 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc return } - proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -152,8 +165,14 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc return } + voteOption, err := gov.VoteOptionFromString(client.NormalizeVoteOption(req.Option)) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + // create the message - msg := gov.NewMsgVote(req.Voter, proposalID, req.Option) + msg := gov.NewMsgVote(req.Voter, proposalID, voteOption) err = msg.ValidateBasic() if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) @@ -164,7 +183,22 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc } } -func queryProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { +func queryParamsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + paramType := vars[RestParamsType] + + res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/gov/%s/%s", gov.QueryParams, paramType), nil) + if err != nil { + utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} + +func queryProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -175,13 +209,11 @@ func queryProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := gov.QueryProposalParams{ ProposalID: proposalID, } @@ -198,11 +230,41 @@ func queryProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } -func queryDepositHandlerFn(cdc *codec.Codec) http.HandlerFunc { +func queryDepositsHandlerFn(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 + } + + params := gov.QueryDepositsParams{ + ProposalID: proposalID, + } + + bz, err := cdc.MarshalJSON(params) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, err := cliCtx.QueryWithData("custom/gov/deposits", bz) + 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) strProposalID := vars[RestProposalID] @@ -214,7 +276,7 @@ func queryDepositHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -227,13 +289,10 @@ func queryDepositHandlerFn(cdc *codec.Codec) http.HandlerFunc { depositerAddr, err := sdk.AccAddressFromBech32(bechDepositerAddr) if err != nil { - err := errors.Errorf("'%s' needs to be bech32 encoded", RestDepositer) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := gov.QueryDepositParams{ ProposalID: proposalID, Depositer: depositerAddr, @@ -254,7 +313,7 @@ func queryDepositHandlerFn(cdc *codec.Codec) http.HandlerFunc { var deposit gov.Deposit cdc.UnmarshalJSON(res, &deposit) if deposit.Empty() { - res, err := cliCtx.QueryWithData("custom/gov/proposal", cdc.MustMarshalBinary(gov.QueryProposalParams{params.ProposalID})) + res, err := cliCtx.QueryWithData("custom/gov/proposal", cdc.MustMarshalBinaryLengthPrefixed(gov.QueryProposalParams{params.ProposalID})) if err != nil || len(res) == 0 { err := errors.Errorf("proposalID [%d] does not exist", proposalID) utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) @@ -265,11 +324,11 @@ func queryDepositHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } -func queryVoteHandlerFn(cdc *codec.Codec) http.HandlerFunc { +func queryVoteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -281,7 +340,7 @@ func queryVoteHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -294,13 +353,10 @@ func queryVoteHandlerFn(cdc *codec.Codec) http.HandlerFunc { voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) if err != nil { - err := errors.Errorf("'%s' needs to be bech32 encoded", RestVoter) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := gov.QueryVoteParams{ Voter: voterAddr, ProposalID: proposalID, @@ -335,12 +391,12 @@ func queryVoteHandlerFn(cdc *codec.Codec) http.HandlerFunc { utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // todo: Split this functionality into helper functions to remove the above -func queryVotesOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { +func queryVotesOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -351,13 +407,11 @@ func queryVotesOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := gov.QueryVotesParams{ ProposalID: proposalID, } @@ -373,24 +427,23 @@ func queryVotesOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // todo: Split this functionality into helper functions to remove the above -func queryProposalsWithParameterFn(cdc *codec.Codec) http.HandlerFunc { +func queryProposalsWithParameterFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { bechVoterAddr := r.URL.Query().Get(RestVoter) bechDepositerAddr := r.URL.Query().Get(RestDepositer) strProposalStatus := r.URL.Query().Get(RestProposalStatus) - strNumLatest := r.URL.Query().Get(RestNumLatest) + strNumLimit := r.URL.Query().Get(RestNumLimit) params := gov.QueryProposalsParams{} if len(bechVoterAddr) != 0 { voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) if err != nil { - err := errors.Errorf("'%s' needs to be bech32 encoded", RestVoter) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -400,7 +453,6 @@ func queryProposalsWithParameterFn(cdc *codec.Codec) http.HandlerFunc { if len(bechDepositerAddr) != 0 { depositerAddr, err := sdk.AccAddressFromBech32(bechDepositerAddr) if err != nil { - err := errors.Errorf("'%s' needs to be bech32 encoded", RestDepositer) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -408,20 +460,19 @@ func queryProposalsWithParameterFn(cdc *codec.Codec) http.HandlerFunc { } if len(strProposalStatus) != 0 { - proposalStatus, err := gov.ProposalStatusFromString(strProposalStatus) + proposalStatus, err := gov.ProposalStatusFromString(client.NormalizeProposalStatus(strProposalStatus)) if err != nil { - err := errors.Errorf("'%s' is not a valid Proposal Status", strProposalStatus) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } params.ProposalStatus = proposalStatus } - if len(strNumLatest) != 0 { - numLatest, ok := utils.ParseInt64OrReturnBadRequest(w, strNumLatest) + if len(strNumLimit) != 0 { + numLimit, ok := utils.ParseUint64OrReturnBadRequest(w, strNumLimit) if !ok { return } - params.NumLatestProposals = numLatest + params.Limit = numLimit } bz, err := cdc.MarshalJSON(params) @@ -430,20 +481,18 @@ func queryProposalsWithParameterFn(cdc *codec.Codec) http.HandlerFunc { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - res, err := cliCtx.QueryWithData("custom/gov/proposals", bz) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // todo: Split this functionality into helper functions to remove the above -func queryTallyOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { +func queryTallyOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -456,13 +505,11 @@ func queryTallyOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := gov.QueryTallyParams{ ProposalID: proposalID, } @@ -480,6 +527,6 @@ func queryTallyOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } diff --git a/x/gov/client/utils.go b/x/gov/client/utils.go new file mode 100644 index 000000000000..013f3944a310 --- /dev/null +++ b/x/gov/client/utils.go @@ -0,0 +1,44 @@ +package client + +// NormalizeVoteOption - normalize user specified vote option +func NormalizeVoteOption(option string) string { + switch option { + case "Yes", "yes": + return "Yes" + case "Abstain", "abstain": + return "Abstain" + case "No", "no": + return "No" + case "NoWithVeto", "no_with_veto": + return "NoWithVeto" + } + return "" +} + +//NormalizeProposalType - normalize user specified proposal type +func NormalizeProposalType(proposalType string) string { + switch proposalType { + case "Text", "text": + return "Text" + case "ParameterChange", "parameter_change": + return "ParameterChange" + case "SoftwareUpgrade", "software_upgrade": + return "SoftwareUpgrade" + } + return "" +} + +//NormalizeProposalStatus - normalize user specified proposal status +func NormalizeProposalStatus(status string) string { + switch status { + case "DepositPeriod", "deposit_period": + return "DepositPeriod" + case "VotingPeriod", "voting_period": + return "VotingPeriod" + case "Passed", "passed": + return "Passed" + case "Rejected", "rejected": + return "Rejected" + } + return "" +} diff --git a/x/gov/depositsvotes.go b/x/gov/depositsvotes.go index d1179023fa4b..7a6b043e6367 100644 --- a/x/gov/depositsvotes.go +++ b/x/gov/depositsvotes.go @@ -11,7 +11,7 @@ import ( // Vote type Vote struct { Voter sdk.AccAddress `json:"voter"` // address of the voter - ProposalID int64 `json:"proposal_id"` // proposalID of the proposal + ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal Option VoteOption `json:"option"` // option from OptionSet chosen by the voter } @@ -29,7 +29,7 @@ func (voteA Vote) Empty() bool { // Deposit type Deposit struct { Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer - ProposalID int64 `json:"proposal_id"` // proposalID of the proposal + ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal Amount sdk.Coins `json:"amount"` // Deposit amount } diff --git a/x/gov/endblocker_test.go b/x/gov/endblocker_test.go index 27eff15f6443..47e90375800e 100644 --- a/x/gov/endblocker_test.go +++ b/x/gov/endblocker_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" + stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" abci "github.com/tendermint/tendermint/abci/types" ) @@ -16,35 +17,40 @@ func TestTickExpiredDepositPeriod(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) govHandler := NewHandler(keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() - newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("steak", 5)}) + newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newHeader = ctx.BlockHeader() - newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod) + newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositParams(ctx).MaxDepositPeriod) ctx = ctx.WithBlockHeader(newHeader) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.True(t, inactiveQueue.Valid()) + inactiveQueue.Close() + EndBlocker(ctx, keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() } func TestTickMultipleExpiredDepositPeriod(t *testing.T) { @@ -53,49 +59,54 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) govHandler := NewHandler(keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() - newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("steak", 5)}) + newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(2) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() - newProposalMsg2 := NewMsgSubmitProposal("Test2", "test2", ProposalTypeText, addrs[1], sdk.Coins{sdk.NewInt64Coin("steak", 5)}) + newProposalMsg2 := NewMsgSubmitProposal("Test2", "test2", ProposalTypeText, addrs[1], sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 5)}) res = govHandler(ctx, newProposalMsg2) require.True(t, res.IsOK()) newHeader = ctx.BlockHeader() - newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod).Add(time.Duration(-1) * time.Second) + newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositParams(ctx).MaxDepositPeriod).Add(time.Duration(-1) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.True(t, inactiveQueue.Valid()) + inactiveQueue.Close() EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newHeader = ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(5) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.True(t, inactiveQueue.Valid()) + inactiveQueue.Close() EndBlocker(ctx, keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() } func TestTickPassedDepositPeriod(t *testing.T) { @@ -104,45 +115,39 @@ func TestTickPassedDepositPeriod(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) govHandler := NewHandler(keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) - require.False(t, shouldPopActiveProposalQueue(ctx, keeper)) + inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() + activeQueue := keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, activeQueue.Valid()) + activeQueue.Close() - newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("steak", 5)}) + newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) - var proposalID int64 - keeper.cdc.UnmarshalBinaryBare(res.Data, &proposalID) + var proposalID uint64 + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() - newDepositMsg := NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewInt64Coin("steak", 5)}) + newDepositMsg := NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 5)}) res = govHandler(ctx, newDepositMsg) require.True(t, res.IsOK()) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) - require.NotNil(t, keeper.ActiveProposalQueuePeek(ctx)) - - EndBlocker(ctx, keeper) - - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) - require.NotNil(t, keeper.ActiveProposalQueuePeek(ctx)) - require.False(t, shouldPopActiveProposalQueue(ctx, keeper)) - + activeQueue = keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, activeQueue.Valid()) + activeQueue.Close() } func TestTickPassedVotingPeriod(t *testing.T) { @@ -152,44 +157,49 @@ func TestTickPassedVotingPeriod(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) govHandler := NewHandler(keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) - require.False(t, shouldPopActiveProposalQueue(ctx, keeper)) + inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() + activeQueue := keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, activeQueue.Valid()) + activeQueue.Close() - newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("steak", 5)}) + newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) - var proposalID int64 - keeper.cdc.UnmarshalBinaryBare(res.Data, &proposalID) + var proposalID uint64 + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID) newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - newDepositMsg := NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewInt64Coin("steak", 5)}) + newDepositMsg := NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 5)}) res = govHandler(ctx, newDepositMsg) require.True(t, res.IsOK()) - EndBlocker(ctx, keeper) - newHeader = ctx.BlockHeader() - newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod).Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod) + newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositParams(ctx).MaxDepositPeriod).Add(keeper.GetVotingParams(ctx).VotingPeriod) ctx = ctx.WithBlockHeader(newHeader) - require.True(t, shouldPopActiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() + + activeQueue = keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.True(t, activeQueue.Valid()) + var activeProposalID uint64 + keeper.cdc.UnmarshalBinaryLengthPrefixed(activeQueue.Value(), &activeProposalID) + require.Equal(t, StatusVotingPeriod, keeper.GetProposal(ctx, activeProposalID).GetStatus()) depositsIterator := keeper.GetDeposits(ctx, proposalID) require.True(t, depositsIterator.Valid()) depositsIterator.Close() - require.Equal(t, StatusVotingPeriod, keeper.GetProposal(ctx, proposalID).GetStatus()) + activeQueue.Close() EndBlocker(ctx, keeper) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) - depositsIterator = keeper.GetDeposits(ctx, proposalID) - require.False(t, depositsIterator.Valid()) - depositsIterator.Close() - require.Equal(t, StatusRejected, keeper.GetProposal(ctx, proposalID).GetStatus()) - require.True(t, keeper.GetProposal(ctx, proposalID).GetTallyResult().Equals(EmptyTallyResult())) + activeQueue = keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, activeQueue.Valid()) + activeQueue.Close() } diff --git a/x/gov/errors.go b/x/gov/errors.go index 0825e00f8cc9..e803d080eacd 100644 --- a/x/gov/errors.go +++ b/x/gov/errors.go @@ -26,19 +26,19 @@ const ( //---------------------------------------- // Error constructors -func ErrUnknownProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error { - return sdk.NewError(codespace, CodeUnknownProposal, fmt.Sprintf("Unknown proposal - %d", proposalID)) +func ErrUnknownProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { + return sdk.NewError(codespace, CodeUnknownProposal, fmt.Sprintf("Unknown proposal with id %d", proposalID)) } -func ErrInactiveProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error { - return sdk.NewError(codespace, CodeInactiveProposal, fmt.Sprintf("Inactive proposal - %d", proposalID)) +func ErrInactiveProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { + return sdk.NewError(codespace, CodeInactiveProposal, fmt.Sprintf("Inactive proposal with id %d", proposalID)) } -func ErrAlreadyActiveProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error { +func ErrAlreadyActiveProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { return sdk.NewError(codespace, CodeAlreadyActiveProposal, fmt.Sprintf("Proposal %d has been already active", proposalID)) } -func ErrAlreadyFinishedProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error { +func ErrAlreadyFinishedProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { return sdk.NewError(codespace, CodeAlreadyFinishedProposal, fmt.Sprintf("Proposal %d has already passed its voting period", proposalID)) } diff --git a/x/gov/genesis.go b/x/gov/genesis.go index 58273c8e849b..e134a4a78c91 100644 --- a/x/gov/genesis.go +++ b/x/gov/genesis.go @@ -4,22 +4,38 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" + stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" ) // GenesisState - all staking state that must be provided at genesis type GenesisState struct { - StartingProposalID int64 `json:"starting_proposalID"` - DepositProcedure DepositProcedure `json:"deposit_period"` - VotingProcedure VotingProcedure `json:"voting_period"` - TallyingProcedure TallyingProcedure `json:"tallying_procedure"` + StartingProposalID uint64 `json:"starting_proposal_id"` + Deposits []DepositWithMetadata `json:"deposits"` + Votes []VoteWithMetadata `json:"votes"` + Proposals []Proposal `json:"proposals"` + DepositParams DepositParams `json:"deposit_params"` + VotingParams VotingParams `json:"voting_params"` + TallyParams TallyParams `json:"tally_params"` } -func NewGenesisState(startingProposalID int64, dp DepositProcedure, vp VotingProcedure, tp TallyingProcedure) GenesisState { +// DepositWithMetadata (just for genesis) +type DepositWithMetadata struct { + ProposalID uint64 `json:"proposal_id"` + Deposit Deposit `json:"deposit"` +} + +// VoteWithMetadata (just for genesis) +type VoteWithMetadata struct { + ProposalID uint64 `json:"proposal_id"` + Vote Vote `json:"vote"` +} + +func NewGenesisState(startingProposalID uint64, dp DepositParams, vp VotingParams, tp TallyParams) GenesisState { return GenesisState{ StartingProposalID: startingProposalID, - DepositProcedure: dp, - VotingProcedure: vp, - TallyingProcedure: tp, + DepositParams: dp, + VotingParams: vp, + TallyParams: tp, } } @@ -27,14 +43,14 @@ func NewGenesisState(startingProposalID int64, dp DepositProcedure, vp VotingPro func DefaultGenesisState() GenesisState { return GenesisState{ StartingProposalID: 1, - DepositProcedure: DepositProcedure{ - MinDeposit: sdk.Coins{sdk.NewInt64Coin("steak", 10)}, + DepositParams: DepositParams{ + MinDeposit: sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)}, MaxDepositPeriod: time.Duration(172800) * time.Second, }, - VotingProcedure: VotingProcedure{ + VotingParams: VotingParams{ VotingPeriod: time.Duration(172800) * time.Second, }, - TallyingProcedure: TallyingProcedure{ + TallyParams: TallyParams{ Threshold: sdk.NewDecWithPrec(5, 1), Veto: sdk.NewDecWithPrec(334, 3), GovernancePenalty: sdk.NewDecWithPrec(1, 2), @@ -49,22 +65,52 @@ func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { // TODO: Handle this with #870 panic(err) } - k.setDepositProcedure(ctx, data.DepositProcedure) - k.setVotingProcedure(ctx, data.VotingProcedure) - k.setTallyingProcedure(ctx, data.TallyingProcedure) + k.setDepositParams(ctx, data.DepositParams) + k.setVotingParams(ctx, data.VotingParams) + k.setTallyParams(ctx, data.TallyParams) + for _, deposit := range data.Deposits { + k.setDeposit(ctx, deposit.ProposalID, deposit.Deposit.Depositer, deposit.Deposit) + } + for _, vote := range data.Votes { + k.setVote(ctx, vote.ProposalID, vote.Vote.Voter, vote.Vote) + } + for _, proposal := range data.Proposals { + k.SetProposal(ctx, proposal) + } } -// WriteGenesis - output genesis parameters -func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState { - startingProposalID, _ := k.getNewProposalID(ctx) - depositProcedure := k.GetDepositProcedure(ctx) - votingProcedure := k.GetVotingProcedure(ctx) - tallyingProcedure := k.GetTallyingProcedure(ctx) +// ExportGenesis - output genesis parameters +func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { + startingProposalID, _ := k.peekCurrentProposalID(ctx) + depositParams := k.GetDepositParams(ctx) + votingParams := k.GetVotingParams(ctx) + tallyParams := k.GetTallyParams(ctx) + var deposits []DepositWithMetadata + var votes []VoteWithMetadata + proposals := k.GetProposalsFiltered(ctx, nil, nil, StatusNil, 0) + for _, proposal := range proposals { + proposalID := proposal.GetProposalID() + depositsIterator := k.GetDeposits(ctx, proposalID) + for ; depositsIterator.Valid(); depositsIterator.Next() { + var deposit Deposit + k.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) + deposits = append(deposits, DepositWithMetadata{proposalID, deposit}) + } + votesIterator := k.GetVotes(ctx, proposalID) + for ; votesIterator.Valid(); votesIterator.Next() { + var vote Vote + k.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) + votes = append(votes, VoteWithMetadata{proposalID, vote}) + } + } return GenesisState{ StartingProposalID: startingProposalID, - DepositProcedure: depositProcedure, - VotingProcedure: votingProcedure, - TallyingProcedure: tallyingProcedure, + Deposits: deposits, + Votes: votes, + Proposals: proposals, + DepositParams: depositParams, + VotingParams: votingParams, + TallyParams: tallyParams, } } diff --git a/x/gov/handler.go b/x/gov/handler.go index 3f8cd312b695..180f7a21a280 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -33,7 +33,7 @@ func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitPropos return err.Result() } - proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(proposal.GetProposalID()) + proposalIDBytes := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposal.GetProposalID()) resTags := sdk.NewTags( tags.Action, tags.ActionSubmitProposal, @@ -42,7 +42,7 @@ func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitPropos ) if votingStarted { - resTags.AppendTag(tags.VotingPeriodStart, proposalIDBytes) + resTags = resTags.AppendTag(tags.VotingPeriodStart, proposalIDBytes) } return sdk.Result{ @@ -68,7 +68,7 @@ func handleMsgDeposit(ctx sdk.Context, keeper Keeper, msg MsgDeposit) sdk.Result ) if votingStarted { - resTags.AppendTag(tags.VotingPeriodStart, proposalIDBytes) + resTags = resTags.AppendTag(tags.VotingPeriodStart, proposalIDBytes) } return sdk.Result{ @@ -102,40 +102,35 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { resTags = sdk.NewTags() - // Delete proposals that haven't met minDeposit - for shouldPopInactiveProposalQueue(ctx, keeper) { - inactiveProposal := keeper.InactiveProposalQueuePop(ctx) - if inactiveProposal.GetStatus() != StatusDepositPeriod { - continue - } + inactiveIterator := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + for ; inactiveIterator.Valid(); inactiveIterator.Next() { + var proposalID uint64 + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(inactiveIterator.Value(), &proposalID) + inactiveProposal := keeper.GetProposal(ctx, proposalID) + keeper.DeleteProposal(ctx, proposalID) + keeper.DeleteDeposits(ctx, proposalID) // delete any associated deposits (burned) - proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(inactiveProposal.GetProposalID()) - keeper.DeleteProposal(ctx, inactiveProposal) - resTags.AppendTag(tags.Action, tags.ActionProposalDropped) - resTags.AppendTag(tags.ProposalID, proposalIDBytes) + resTags = resTags.AppendTag(tags.Action, tags.ActionProposalDropped) + resTags = resTags.AppendTag(tags.ProposalID, []byte(string(proposalID))) logger.Info( - fmt.Sprintf("proposal %d (%s) didn't meet minimum deposit of %v steak (had only %v steak); deleted", + fmt.Sprintf("proposal %d (%s) didn't meet minimum deposit of %s (had only %s); deleted", inactiveProposal.GetProposalID(), inactiveProposal.GetTitle(), - keeper.GetDepositProcedure(ctx).MinDeposit.AmountOf("steak"), - inactiveProposal.GetTotalDeposit().AmountOf("steak"), + keeper.GetDepositParams(ctx).MinDeposit, + inactiveProposal.GetTotalDeposit(), ), ) } + inactiveIterator.Close() - // Check if earliest Active Proposal ended voting period yet - for shouldPopActiveProposalQueue(ctx, keeper) { - activeProposal := keeper.ActiveProposalQueuePop(ctx) - - proposalStartTime := activeProposal.GetVotingStartTime() - votingPeriod := keeper.GetVotingProcedure(ctx).VotingPeriod - if ctx.BlockHeader().Time.Before(proposalStartTime.Add(votingPeriod)) { - continue - } - + activeIterator := keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + for ; activeIterator.Valid(); activeIterator.Next() { + var proposalID uint64 + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID) + activeProposal := keeper.GetProposal(ctx, proposalID) passes, tallyResults := tally(ctx, keeper, activeProposal) - proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(activeProposal.GetProposalID()) + var action []byte if passes { keeper.RefundDeposits(ctx, activeProposal.GetProposalID()) @@ -149,37 +144,15 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { activeProposal.SetTallyResult(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)) - resTags.AppendTag(tags.Action, action) - resTags.AppendTag(tags.ProposalID, proposalIDBytes) + resTags = resTags.AppendTag(tags.Action, action) + resTags = resTags.AppendTag(tags.ProposalID, []byte(string(proposalID))) } + activeIterator.Close() return resTags } -func shouldPopInactiveProposalQueue(ctx sdk.Context, keeper Keeper) bool { - depositProcedure := keeper.GetDepositProcedure(ctx) - peekProposal := keeper.InactiveProposalQueuePeek(ctx) - - if peekProposal == nil { - return false - } else if peekProposal.GetStatus() != StatusDepositPeriod { - return true - } else if !ctx.BlockHeader().Time.Before(peekProposal.GetSubmitTime().Add(depositProcedure.MaxDepositPeriod)) { - return true - } - return false -} - -func shouldPopActiveProposalQueue(ctx sdk.Context, keeper Keeper) bool { - votingProcedure := keeper.GetVotingProcedure(ctx) - peekProposal := keeper.ActiveProposalQueuePeek(ctx) - - if peekProposal == nil { - return false - } else if !ctx.BlockHeader().Time.Before(peekProposal.GetVotingStartTime().Add(votingProcedure.VotingPeriod)) { - return true - } - return false -} diff --git a/x/gov/keeper.go b/x/gov/keeper.go index 4b7ec26b5c7c..4f370ef531e6 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -1,10 +1,14 @@ package gov import ( + "time" + codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/params" + + "github.com/tendermint/tendermint/crypto" ) // Parameter store default namestore @@ -14,17 +18,20 @@ const ( // Parameter store key var ( - ParamStoreKeyDepositProcedure = []byte("depositprocedure") - ParamStoreKeyVotingProcedure = []byte("votingprocedure") - ParamStoreKeyTallyingProcedure = []byte("tallyingprocedure") + ParamStoreKeyDepositParams = []byte("depositparams") + ParamStoreKeyVotingParams = []byte("votingparams") + ParamStoreKeyTallyParams = []byte("tallyparams") + + DepositedCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("govDepositedCoins"))) + BurnedDepositCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("govBurnedDepositCoins"))) ) // Type declaration for parameters func ParamTypeTable() params.TypeTable { return params.NewTypeTable( - ParamStoreKeyDepositProcedure, DepositProcedure{}, - ParamStoreKeyVotingProcedure, VotingProcedure{}, - ParamStoreKeyTallyingProcedure, TallyingProcedure{}, + ParamStoreKeyDepositParams, DepositParams{}, + ParamStoreKeyVotingParams, VotingParams{}, + ParamStoreKeyTallyParams, TallyParams{}, ) } @@ -92,13 +99,17 @@ func (keeper Keeper) NewTextProposal(ctx sdk.Context, title string, description TotalDeposit: sdk.Coins{}, SubmitTime: ctx.BlockHeader().Time, } + + depositPeriod := keeper.GetDepositParams(ctx).MaxDepositPeriod + proposal.SetDepositEndTime(proposal.GetSubmitTime().Add(depositPeriod)) + keeper.SetProposal(ctx, proposal) - keeper.InactiveProposalQueuePush(ctx, proposal) + keeper.InsertInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposalID) return proposal } // Get Proposal from store by ProposalID -func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID int64) Proposal { +func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID uint64) Proposal { store := ctx.KVStore(keeper.storeKey) bz := store.Get(KeyProposal(proposalID)) if bz == nil { @@ -106,26 +117,29 @@ func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID int64) Proposal { } var proposal Proposal - keeper.cdc.MustUnmarshalBinary(bz, &proposal) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposal) return proposal } -// Implements sdk.AccountMapper. +// Implements sdk.AccountKeeper. func (keeper Keeper) SetProposal(ctx sdk.Context, proposal Proposal) { store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinary(proposal) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposal) store.Set(KeyProposal(proposal.GetProposalID()), bz) } -// Implements sdk.AccountMapper. -func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposal Proposal) { +// Implements sdk.AccountKeeper. +func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) - store.Delete(KeyProposal(proposal.GetProposalID())) + proposal := keeper.GetProposal(ctx, proposalID) + keeper.RemoveFromInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposalID) + keeper.RemoveFromActiveProposalQueue(ctx, proposal.GetVotingEndTime(), proposalID) + store.Delete(KeyProposal(proposalID)) } // Get Proposal from store by ProposalID -func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositerAddr sdk.AccAddress, status ProposalStatus, numLatest int64) []Proposal { +func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositerAddr sdk.AccAddress, status ProposalStatus, numLatest uint64) []Proposal { maxProposalID, err := keeper.peekCurrentProposalID(ctx) if err != nil { @@ -134,7 +148,7 @@ func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddr matchingProposals := []Proposal{} - if numLatest <= 0 { + if numLatest == 0 { numLatest = maxProposalID } @@ -169,19 +183,19 @@ func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddr return matchingProposals } -func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID int64) sdk.Error { +func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID uint64) sdk.Error { store := ctx.KVStore(keeper.storeKey) bz := store.Get(KeyNextProposalID) if bz != nil { return ErrInvalidGenesis(keeper.codespace, "Initial ProposalID already set") } - bz = keeper.cdc.MustMarshalBinary(proposalID) + bz = keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) store.Set(KeyNextProposalID, bz) return nil } // Get the last used proposal ID -func (keeper Keeper) GetLastProposalID(ctx sdk.Context) (proposalID int64) { +func (keeper Keeper) GetLastProposalID(ctx sdk.Context) (proposalID uint64) { proposalID, err := keeper.peekCurrentProposalID(ctx) if err != nil { return 0 @@ -191,83 +205,87 @@ func (keeper Keeper) GetLastProposalID(ctx sdk.Context) (proposalID int64) { } // Gets the next available ProposalID and increments it -func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID int64, err sdk.Error) { +func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID uint64, err sdk.Error) { store := ctx.KVStore(keeper.storeKey) bz := store.Get(KeyNextProposalID) if bz == nil { - return -1, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") + return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") } - keeper.cdc.MustUnmarshalBinary(bz, &proposalID) - bz = keeper.cdc.MustMarshalBinary(proposalID + 1) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID) + bz = keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID + 1) store.Set(KeyNextProposalID, bz) return proposalID, nil } // Peeks the next available ProposalID without incrementing it -func (keeper Keeper) peekCurrentProposalID(ctx sdk.Context) (proposalID int64, err sdk.Error) { +func (keeper Keeper) peekCurrentProposalID(ctx sdk.Context) (proposalID uint64, err sdk.Error) { store := ctx.KVStore(keeper.storeKey) bz := store.Get(KeyNextProposalID) if bz == nil { - return -1, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") + return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") } - keeper.cdc.MustUnmarshalBinary(bz, &proposalID) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID) return proposalID, nil } func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) { proposal.SetVotingStartTime(ctx.BlockHeader().Time) + votingPeriod := keeper.GetVotingParams(ctx).VotingPeriod + proposal.SetVotingEndTime(proposal.GetVotingStartTime().Add(votingPeriod)) proposal.SetStatus(StatusVotingPeriod) keeper.SetProposal(ctx, proposal) - keeper.ActiveProposalQueuePush(ctx, proposal) + + keeper.RemoveFromInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposal.GetProposalID()) + keeper.InsertActiveProposalQueue(ctx, proposal.GetVotingEndTime(), proposal.GetProposalID()) } // ===================================================== -// Procedures +// Params -// Returns the current Deposit Procedure from the global param store +// Returns the current DepositParams from the global param store // nolint: errcheck -func (keeper Keeper) GetDepositProcedure(ctx sdk.Context) DepositProcedure { - var depositProcedure DepositProcedure - keeper.paramSpace.Get(ctx, ParamStoreKeyDepositProcedure, &depositProcedure) - return depositProcedure +func (keeper Keeper) GetDepositParams(ctx sdk.Context) DepositParams { + var depositParams DepositParams + keeper.paramSpace.Get(ctx, ParamStoreKeyDepositParams, &depositParams) + return depositParams } -// Returns the current Voting Procedure from the global param store +// Returns the current VotingParams from the global param store // nolint: errcheck -func (keeper Keeper) GetVotingProcedure(ctx sdk.Context) VotingProcedure { - var votingProcedure VotingProcedure - keeper.paramSpace.Get(ctx, ParamStoreKeyVotingProcedure, &votingProcedure) - return votingProcedure +func (keeper Keeper) GetVotingParams(ctx sdk.Context) VotingParams { + var votingParams VotingParams + keeper.paramSpace.Get(ctx, ParamStoreKeyVotingParams, &votingParams) + return votingParams } -// Returns the current Tallying Procedure from the global param store +// Returns the current TallyParam from the global param store // nolint: errcheck -func (keeper Keeper) GetTallyingProcedure(ctx sdk.Context) TallyingProcedure { - var tallyingProcedure TallyingProcedure - keeper.paramSpace.Get(ctx, ParamStoreKeyTallyingProcedure, &tallyingProcedure) - return tallyingProcedure +func (keeper Keeper) GetTallyParams(ctx sdk.Context) TallyParams { + var tallyParams TallyParams + keeper.paramSpace.Get(ctx, ParamStoreKeyTallyParams, &tallyParams) + return tallyParams } // nolint: errcheck -func (keeper Keeper) setDepositProcedure(ctx sdk.Context, depositProcedure DepositProcedure) { - keeper.paramSpace.Set(ctx, ParamStoreKeyDepositProcedure, &depositProcedure) +func (keeper Keeper) setDepositParams(ctx sdk.Context, depositParams DepositParams) { + keeper.paramSpace.Set(ctx, ParamStoreKeyDepositParams, &depositParams) } // nolint: errcheck -func (keeper Keeper) setVotingProcedure(ctx sdk.Context, votingProcedure VotingProcedure) { - keeper.paramSpace.Set(ctx, ParamStoreKeyVotingProcedure, &votingProcedure) +func (keeper Keeper) setVotingParams(ctx sdk.Context, votingParams VotingParams) { + keeper.paramSpace.Set(ctx, ParamStoreKeyVotingParams, &votingParams) } // nolint: errcheck -func (keeper Keeper) setTallyingProcedure(ctx sdk.Context, tallyingProcedure TallyingProcedure) { - keeper.paramSpace.Set(ctx, ParamStoreKeyTallyingProcedure, &tallyingProcedure) +func (keeper Keeper) setTallyParams(ctx sdk.Context, tallyParams TallyParams) { + keeper.paramSpace.Set(ctx, ParamStoreKeyTallyParams, &tallyParams) } // ===================================================== // Votes // Adds a vote on a specific proposal -func (keeper Keeper) AddVote(ctx sdk.Context, proposalID int64, voterAddr sdk.AccAddress, option VoteOption) sdk.Error { +func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, option VoteOption) sdk.Error { proposal := keeper.GetProposal(ctx, proposalID) if proposal == nil { return ErrUnknownProposal(keeper.codespace, proposalID) @@ -291,30 +309,30 @@ func (keeper Keeper) AddVote(ctx sdk.Context, proposalID int64, voterAddr sdk.Ac } // Gets the vote of a specific voter on a specific proposal -func (keeper Keeper) GetVote(ctx sdk.Context, proposalID int64, voterAddr sdk.AccAddress) (Vote, bool) { +func (keeper Keeper) GetVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) (Vote, bool) { store := ctx.KVStore(keeper.storeKey) bz := store.Get(KeyVote(proposalID, voterAddr)) if bz == nil { return Vote{}, false } var vote Vote - keeper.cdc.MustUnmarshalBinary(bz, &vote) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &vote) return vote, true } -func (keeper Keeper) setVote(ctx sdk.Context, proposalID int64, voterAddr sdk.AccAddress, vote Vote) { +func (keeper Keeper) setVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, vote Vote) { store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinary(vote) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(vote) store.Set(KeyVote(proposalID, voterAddr), bz) } // Gets all the votes on a specific proposal -func (keeper Keeper) GetVotes(ctx sdk.Context, proposalID int64) sdk.Iterator { +func (keeper Keeper) GetVotes(ctx sdk.Context, proposalID uint64) sdk.Iterator { store := ctx.KVStore(keeper.storeKey) return sdk.KVStorePrefixIterator(store, KeyVotesSubspace(proposalID)) } -func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID int64, voterAddr sdk.AccAddress) { +func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) { store := ctx.KVStore(keeper.storeKey) store.Delete(KeyVote(proposalID, voterAddr)) } @@ -323,26 +341,26 @@ func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID int64, voterAddr sdk // Deposits // Gets the deposit of a specific depositer on a specific proposal -func (keeper Keeper) GetDeposit(ctx sdk.Context, proposalID int64, depositerAddr sdk.AccAddress) (Deposit, bool) { +func (keeper Keeper) GetDeposit(ctx sdk.Context, proposalID uint64, depositerAddr sdk.AccAddress) (Deposit, bool) { store := ctx.KVStore(keeper.storeKey) bz := store.Get(KeyDeposit(proposalID, depositerAddr)) if bz == nil { return Deposit{}, false } var deposit Deposit - keeper.cdc.MustUnmarshalBinary(bz, &deposit) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &deposit) return deposit, true } -func (keeper Keeper) setDeposit(ctx sdk.Context, proposalID int64, depositerAddr sdk.AccAddress, deposit Deposit) { +func (keeper Keeper) setDeposit(ctx sdk.Context, proposalID uint64, depositerAddr sdk.AccAddress, deposit Deposit) { store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinary(deposit) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(deposit) store.Set(KeyDeposit(proposalID, depositerAddr), bz) } // Adds or updates a deposit of a specific depositer on a specific proposal // Activates voting period when appropriate -func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID int64, depositerAddr sdk.AccAddress, depositAmount sdk.Coins) (sdk.Error, bool) { +func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositerAddr sdk.AccAddress, depositAmount sdk.Coins) (sdk.Error, bool) { // Checks to see if proposal exists proposal := keeper.GetProposal(ctx, proposalID) if proposal == nil { @@ -354,8 +372,8 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID int64, depositerAddr return ErrAlreadyFinishedProposal(keeper.codespace, proposalID), false } - // Subtract coins from depositer's account - _, _, err := keeper.ck.SubtractCoins(ctx, depositerAddr, depositAmount) + // Send coins from depositer's account to DepositedCoinsAccAddr account + _, err := keeper.ck.SendCoins(ctx, depositerAddr, DepositedCoinsAccAddr, depositAmount) if err != nil { return err, false } @@ -367,7 +385,7 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID int64, depositerAddr // Check if deposit tipped proposal into voting period // Active voting period if so activatedVotingPeriod := false - if proposal.GetStatus() == StatusDepositPeriod && proposal.GetTotalDeposit().IsGTE(keeper.GetDepositProcedure(ctx).MinDeposit) { + if proposal.GetStatus() == StatusDepositPeriod && proposal.GetTotalDeposit().IsAllGTE(keeper.GetDepositParams(ctx).MinDeposit) { keeper.activateVotingPeriod(ctx, proposal) activatedVotingPeriod = true } @@ -386,21 +404,21 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID int64, depositerAddr } // Gets all the deposits on a specific proposal -func (keeper Keeper) GetDeposits(ctx sdk.Context, proposalID int64) sdk.Iterator { +func (keeper Keeper) GetDeposits(ctx sdk.Context, proposalID uint64) sdk.Iterator { store := ctx.KVStore(keeper.storeKey) return sdk.KVStorePrefixIterator(store, KeyDepositsSubspace(proposalID)) } // Returns and deletes all the deposits on a specific proposal -func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID int64) { +func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) depositsIterator := keeper.GetDeposits(ctx, proposalID) for ; depositsIterator.Valid(); depositsIterator.Next() { deposit := &Deposit{} - keeper.cdc.MustUnmarshalBinary(depositsIterator.Value(), deposit) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit) - _, _, err := keeper.ck.AddCoins(ctx, deposit.Depositer, deposit.Amount) + _, err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, deposit.Depositer, deposit.Amount) if err != nil { panic("should not happen") } @@ -412,11 +430,19 @@ func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID int64) { } // Deletes all the deposits on a specific proposal without refunding them -func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID int64) { +func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) depositsIterator := keeper.GetDeposits(ctx, proposalID) for ; depositsIterator.Valid(); depositsIterator.Next() { + deposit := &Deposit{} + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit) + + _, err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, BurnedDepositCoinsAccAddr, deposit.Amount) + if err != nil { + panic("should not happen") + } + store.Delete(depositsIterator.Key()) } @@ -426,93 +452,40 @@ func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID int64) { // ===================================================== // ProposalQueues -func (keeper Keeper) getActiveProposalQueue(ctx sdk.Context) ProposalQueue { +// Returns an iterator for all the proposals in the Active Queue that expire by endTime +func (keeper Keeper) ActiveProposalQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { store := ctx.KVStore(keeper.storeKey) - bz := store.Get(KeyActiveProposalQueue) - if bz == nil { - return nil - } - - var proposalQueue ProposalQueue - keeper.cdc.MustUnmarshalBinary(bz, &proposalQueue) - - return proposalQueue + return store.Iterator(PrefixActiveProposalQueue, sdk.PrefixEndBytes(PrefixActiveProposalQueueTime(endTime))) } -func (keeper Keeper) setActiveProposalQueue(ctx sdk.Context, proposalQueue ProposalQueue) { +// Inserts a ProposalID into the active proposal queue at endTime +func (keeper Keeper) InsertActiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinary(proposalQueue) - store.Set(KeyActiveProposalQueue, bz) -} - -// Return the Proposal at the front of the ProposalQueue -func (keeper Keeper) ActiveProposalQueuePeek(ctx sdk.Context) Proposal { - proposalQueue := keeper.getActiveProposalQueue(ctx) - if len(proposalQueue) == 0 { - return nil - } - return keeper.GetProposal(ctx, proposalQueue[0]) -} - -// Remove and return a Proposal from the front of the ProposalQueue -func (keeper Keeper) ActiveProposalQueuePop(ctx sdk.Context) Proposal { - proposalQueue := keeper.getActiveProposalQueue(ctx) - if len(proposalQueue) == 0 { - return nil - } - frontElement, proposalQueue := proposalQueue[0], proposalQueue[1:] - keeper.setActiveProposalQueue(ctx, proposalQueue) - return keeper.GetProposal(ctx, frontElement) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) + store.Set(KeyActiveProposalQueueProposal(endTime, proposalID), bz) } -// Add a proposalID to the back of the ProposalQueue -func (keeper Keeper) ActiveProposalQueuePush(ctx sdk.Context, proposal Proposal) { - proposalQueue := append(keeper.getActiveProposalQueue(ctx), proposal.GetProposalID()) - keeper.setActiveProposalQueue(ctx, proposalQueue) -} - -func (keeper Keeper) getInactiveProposalQueue(ctx sdk.Context) ProposalQueue { +// removes a proposalID from the Active Proposal Queue +func (keeper Keeper) RemoveFromActiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) - bz := store.Get(KeyInactiveProposalQueue) - if bz == nil { - return nil - } - - var proposalQueue ProposalQueue - - keeper.cdc.MustUnmarshalBinary(bz, &proposalQueue) - - return proposalQueue + store.Delete(KeyActiveProposalQueueProposal(endTime, proposalID)) } -func (keeper Keeper) setInactiveProposalQueue(ctx sdk.Context, proposalQueue ProposalQueue) { +// Returns an iterator for all the proposals in the Inactive Queue that expire by endTime +func (keeper Keeper) InactiveProposalQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinary(proposalQueue) - store.Set(KeyInactiveProposalQueue, bz) -} - -// Return the Proposal at the front of the ProposalQueue -func (keeper Keeper) InactiveProposalQueuePeek(ctx sdk.Context) Proposal { - proposalQueue := keeper.getInactiveProposalQueue(ctx) - if len(proposalQueue) == 0 { - return nil - } - return keeper.GetProposal(ctx, proposalQueue[0]) + return store.Iterator(PrefixInactiveProposalQueue, sdk.PrefixEndBytes(PrefixInactiveProposalQueueTime(endTime))) } -// Remove and return a Proposal from the front of the ProposalQueue -func (keeper Keeper) InactiveProposalQueuePop(ctx sdk.Context) Proposal { - proposalQueue := keeper.getInactiveProposalQueue(ctx) - if len(proposalQueue) == 0 { - return nil - } - frontElement, proposalQueue := proposalQueue[0], proposalQueue[1:] - keeper.setInactiveProposalQueue(ctx, proposalQueue) - return keeper.GetProposal(ctx, frontElement) +// Inserts a ProposalID into the inactive proposal queue at endTime +func (keeper Keeper) InsertInactiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) + store.Set(KeyInactiveProposalQueueProposal(endTime, proposalID), bz) } -// Add a proposalID to the back of the ProposalQueue -func (keeper Keeper) InactiveProposalQueuePush(ctx sdk.Context, proposal Proposal) { - proposalQueue := append(keeper.getInactiveProposalQueue(ctx), proposal.GetProposalID()) - keeper.setInactiveProposalQueue(ctx, proposalQueue) +// removes a proposalID from the Inactive Proposal Queue +func (keeper Keeper) RemoveFromInactiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + store.Delete(KeyInactiveProposalQueueProposal(endTime, proposalID)) } diff --git a/x/gov/keeper_keys.go b/x/gov/keeper_keys.go index 7b1bf43f2f77..8a3324fd1835 100644 --- a/x/gov/keeper_keys.go +++ b/x/gov/keeper_keys.go @@ -1,7 +1,9 @@ package gov import ( + "bytes" "fmt" + "time" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -10,32 +12,68 @@ import ( // Key for getting a the next available proposalID from the store var ( - KeyNextProposalID = []byte("newProposalID") - KeyActiveProposalQueue = []byte("activeProposalQueue") - KeyInactiveProposalQueue = []byte("inactiveProposalQueue") + KeyDelimiter = []byte(":") + + KeyNextProposalID = []byte("newProposalID") + PrefixActiveProposalQueue = []byte("activeProposalQueue") + PrefixInactiveProposalQueue = []byte("inactiveProposalQueue") ) // Key for getting a specific proposal from the store -func KeyProposal(proposalID int64) []byte { +func KeyProposal(proposalID uint64) []byte { return []byte(fmt.Sprintf("proposals:%d", proposalID)) } // Key for getting a specific deposit from the store -func KeyDeposit(proposalID int64, depositerAddr sdk.AccAddress) []byte { +func KeyDeposit(proposalID uint64, depositerAddr sdk.AccAddress) []byte { return []byte(fmt.Sprintf("deposits:%d:%d", proposalID, depositerAddr)) } // Key for getting a specific vote from the store -func KeyVote(proposalID int64, voterAddr sdk.AccAddress) []byte { +func KeyVote(proposalID uint64, voterAddr sdk.AccAddress) []byte { return []byte(fmt.Sprintf("votes:%d:%d", proposalID, voterAddr)) } // Key for getting all deposits on a proposal from the store -func KeyDepositsSubspace(proposalID int64) []byte { +func KeyDepositsSubspace(proposalID uint64) []byte { return []byte(fmt.Sprintf("deposits:%d:", proposalID)) } // Key for getting all votes on a proposal from the store -func KeyVotesSubspace(proposalID int64) []byte { +func KeyVotesSubspace(proposalID uint64) []byte { return []byte(fmt.Sprintf("votes:%d:", proposalID)) } + +// Returns the key for a proposalID in the activeProposalQueue +func PrefixActiveProposalQueueTime(endTime time.Time) []byte { + return bytes.Join([][]byte{ + PrefixActiveProposalQueue, + sdk.FormatTimeBytes(endTime), + }, KeyDelimiter) +} + +// Returns the key for a proposalID in the activeProposalQueue +func KeyActiveProposalQueueProposal(endTime time.Time, proposalID uint64) []byte { + return bytes.Join([][]byte{ + PrefixActiveProposalQueue, + sdk.FormatTimeBytes(endTime), + sdk.Uint64ToBigEndian(proposalID), + }, KeyDelimiter) +} + +// Returns the key for a proposalID in the activeProposalQueue +func PrefixInactiveProposalQueueTime(endTime time.Time) []byte { + return bytes.Join([][]byte{ + PrefixInactiveProposalQueue, + sdk.FormatTimeBytes(endTime), + }, KeyDelimiter) +} + +// Returns the key for a proposalID in the activeProposalQueue +func KeyInactiveProposalQueueProposal(endTime time.Time, proposalID uint64) []byte { + return bytes.Join([][]byte{ + PrefixInactiveProposalQueue, + sdk.FormatTimeBytes(endTime), + sdk.Uint64ToBigEndian(proposalID), + }, KeyDelimiter) +} diff --git a/x/gov/keeper_test.go b/x/gov/keeper_test.go index 91c41d7d7dde..23199472bafb 100644 --- a/x/gov/keeper_test.go +++ b/x/gov/keeper_test.go @@ -9,6 +9,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" + stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" ) func TestGetSetProposal(t *testing.T) { @@ -36,7 +37,7 @@ func TestIncrementProposalNumber(t *testing.T) { keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposal6 := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - require.Equal(t, int64(6), proposal6.GetProposalID()) + require.Equal(t, uint64(6), proposal6.GetProposalID()) } func TestActivateVotingPeriod(t *testing.T) { @@ -47,12 +48,17 @@ func TestActivateVotingPeriod(t *testing.T) { proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) require.True(t, proposal.GetVotingStartTime().Equal(time.Time{})) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) keeper.activateVotingPeriod(ctx, proposal) require.True(t, proposal.GetVotingStartTime().Equal(ctx.BlockHeader().Time)) - require.Equal(t, proposal.GetProposalID(), keeper.ActiveProposalQueuePeek(ctx).GetProposalID()) + + activeIterator := keeper.ActiveProposalQueueIterator(ctx, proposal.GetVotingEndTime()) + require.True(t, activeIterator.Valid()) + var proposalID uint64 + keeper.cdc.UnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID) + require.Equal(t, proposalID, proposal.GetProposalID()) + activeIterator.Close() } func TestDeposits(t *testing.T) { @@ -64,14 +70,14 @@ func TestDeposits(t *testing.T) { proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() - fourSteak := sdk.Coins{sdk.NewInt64Coin("steak", 4)} - fiveSteak := sdk.Coins{sdk.NewInt64Coin("steak", 5)} + fourSteak := sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 4)} + fiveSteak := sdk.Coins{sdk.NewInt64Coin(stakeTypes.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("steak", 42)})) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin("steak", 42)}, addr0Initial) + // 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, proposal.GetTotalDeposit().IsEqual(sdk.Coins{})) @@ -79,7 +85,6 @@ func TestDeposits(t *testing.T) { deposit, found := keeper.GetDeposit(ctx, proposalID, addrs[1]) require.False(t, found) require.True(t, keeper.GetProposal(ctx, proposalID).GetVotingStartTime().Equal(time.Time{})) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) // Check first deposit err, votingStarted := keeper.AddDeposit(ctx, proposalID, addrs[0], fourSteak) @@ -116,17 +121,15 @@ func TestDeposits(t *testing.T) { // Check that proposal moved to voting period require.True(t, keeper.GetProposal(ctx, proposalID).GetVotingStartTime().Equal(ctx.BlockHeader().Time)) - require.NotNil(t, keeper.ActiveProposalQueuePeek(ctx)) - require.Equal(t, proposalID, keeper.ActiveProposalQueuePeek(ctx).GetProposalID()) // Test deposit iterator depositsIterator := keeper.GetDeposits(ctx, proposalID) require.True(t, depositsIterator.Valid()) - keeper.cdc.MustUnmarshalBinary(depositsIterator.Value(), &deposit) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) require.Equal(t, addrs[0], deposit.Depositer) require.Equal(t, fourSteak.Plus(fiveSteak), deposit.Amount) depositsIterator.Next() - keeper.cdc.MustUnmarshalBinary(depositsIterator.Value(), &deposit) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) require.Equal(t, addrs[1], deposit.Depositer) require.Equal(t, fourSteak, deposit.Amount) depositsIterator.Next() @@ -184,14 +187,14 @@ func TestVotes(t *testing.T) { // Test vote iterator votesIterator := keeper.GetVotes(ctx, proposalID) require.True(t, votesIterator.Valid()) - keeper.cdc.MustUnmarshalBinary(votesIterator.Value(), &vote) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) require.True(t, votesIterator.Valid()) require.Equal(t, addrs[0], vote.Voter) require.Equal(t, proposalID, vote.ProposalID) require.Equal(t, OptionYes, vote.Option) votesIterator.Next() require.True(t, votesIterator.Valid()) - keeper.cdc.MustUnmarshalBinary(votesIterator.Value(), &vote) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) require.True(t, votesIterator.Valid()) require.Equal(t, addrs[1], vote.Voter) require.Equal(t, proposalID, vote.ProposalID) @@ -207,44 +210,21 @@ func TestProposalQueues(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) mapp.InitChainer(ctx, abci.RequestInitChain{}) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) - // create test proposals proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposal2 := keeper.NewTextProposal(ctx, "Test2", "description", ProposalTypeText) - proposal3 := keeper.NewTextProposal(ctx, "Test3", "description", ProposalTypeText) - proposal4 := keeper.NewTextProposal(ctx, "Test4", "description", ProposalTypeText) - - // test pushing to inactive proposal queue - keeper.InactiveProposalQueuePush(ctx, proposal) - keeper.InactiveProposalQueuePush(ctx, proposal2) - keeper.InactiveProposalQueuePush(ctx, proposal3) - keeper.InactiveProposalQueuePush(ctx, proposal4) - - // test peeking and popping from inactive proposal queue - require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal2.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal2.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal3.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal3.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal4.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal4.GetProposalID()) - - // test pushing to active proposal queue - keeper.ActiveProposalQueuePush(ctx, proposal) - keeper.ActiveProposalQueuePush(ctx, proposal2) - keeper.ActiveProposalQueuePush(ctx, proposal3) - keeper.ActiveProposalQueuePush(ctx, proposal4) - - // test peeking and popping from active proposal queue - require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal2.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal2.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal3.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal3.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal4.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal4.GetProposalID()) + + inactiveIterator := keeper.InactiveProposalQueueIterator(ctx, proposal.GetDepositEndTime()) + require.True(t, inactiveIterator.Valid()) + var proposalID uint64 + keeper.cdc.UnmarshalBinaryLengthPrefixed(inactiveIterator.Value(), &proposalID) + require.Equal(t, proposalID, proposal.GetProposalID()) + inactiveIterator.Close() + + keeper.activateVotingPeriod(ctx, proposal) + + activeIterator := keeper.ActiveProposalQueueIterator(ctx, proposal.GetVotingEndTime()) + require.True(t, activeIterator.Valid()) + keeper.cdc.UnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID) + require.Equal(t, proposalID, proposal.GetProposalID()) + activeIterator.Close() } diff --git a/x/gov/msgs.go b/x/gov/msgs.go index a5a68ea21d4b..b8325d81cae6 100644 --- a/x/gov/msgs.go +++ b/x/gov/msgs.go @@ -7,7 +7,7 @@ import ( ) // name to idetify transaction types -const MsgType = "gov" +const MsgRoute = "gov" var _, _, _ sdk.Msg = MsgSubmitProposal{}, MsgDeposit{}, MsgVote{} @@ -31,9 +31,9 @@ func NewMsgSubmitProposal(title string, description string, proposalType Proposa } } -// Implements Msg. -func (msg MsgSubmitProposal) Type() string { return MsgType } -func (msg MsgSubmitProposal) Name() string { return "submit_proposal" } +//nolint +func (msg MsgSubmitProposal) Route() string { return MsgRoute } +func (msg MsgSubmitProposal) Type() string { return "submit_proposal" } // Implements Msg. func (msg MsgSubmitProposal) ValidateBasic() sdk.Error { @@ -84,12 +84,12 @@ func (msg MsgSubmitProposal) GetSigners() []sdk.AccAddress { //----------------------------------------------------------- // MsgDeposit type MsgDeposit struct { - ProposalID int64 `json:"proposalID"` // ID of the proposal - Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer - Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit + ProposalID uint64 `json:"proposal_id"` // ID of the proposal + Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer + Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit } -func NewMsgDeposit(depositer sdk.AccAddress, proposalID int64, amount sdk.Coins) MsgDeposit { +func NewMsgDeposit(depositer sdk.AccAddress, proposalID uint64, amount sdk.Coins) MsgDeposit { return MsgDeposit{ ProposalID: proposalID, Depositer: depositer, @@ -99,8 +99,8 @@ func NewMsgDeposit(depositer sdk.AccAddress, proposalID int64, amount sdk.Coins) // Implements Msg. // nolint -func (msg MsgDeposit) Type() string { return MsgType } -func (msg MsgDeposit) Name() string { return "deposit" } +func (msg MsgDeposit) Route() string { return MsgRoute } +func (msg MsgDeposit) Type() string { return "deposit" } // Implements Msg. func (msg MsgDeposit) ValidateBasic() sdk.Error { @@ -145,12 +145,12 @@ func (msg MsgDeposit) GetSigners() []sdk.AccAddress { //----------------------------------------------------------- // MsgVote type MsgVote struct { - ProposalID int64 // proposalID of the proposal - Voter sdk.AccAddress // address of the voter - Option VoteOption // option from OptionSet chosen by the voter + ProposalID uint64 `json:"proposal_id"` // ID of the proposal + Voter sdk.AccAddress `json:"voter"` // address of the voter + Option VoteOption `json:"option"` // option from OptionSet chosen by the voter } -func NewMsgVote(voter sdk.AccAddress, proposalID int64, option VoteOption) MsgVote { +func NewMsgVote(voter sdk.AccAddress, proposalID uint64, option VoteOption) MsgVote { return MsgVote{ ProposalID: proposalID, Voter: voter, @@ -160,8 +160,8 @@ func NewMsgVote(voter sdk.AccAddress, proposalID int64, option VoteOption) MsgVo // Implements Msg. // nolint -func (msg MsgVote) Type() string { return MsgType } -func (msg MsgVote) Name() string { return "vote" } +func (msg MsgVote) Route() string { return MsgRoute } +func (msg MsgVote) Type() string { return "vote" } // Implements Msg. func (msg MsgVote) ValidateBasic() sdk.Error { diff --git a/x/gov/msgs_test.go b/x/gov/msgs_test.go index 6d2b8bb38804..e488d2ababdc 100644 --- a/x/gov/msgs_test.go +++ b/x/gov/msgs_test.go @@ -7,16 +7,21 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/mock" + stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" ) var ( - coinsPos = sdk.Coins{sdk.NewInt64Coin("steak", 1000)} + coinsPos = sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 1000)} coinsZero = sdk.Coins{} - coinsNeg = sdk.Coins{sdk.NewInt64Coin("steak", -10000)} + coinsNeg = sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, -10000)} coinsPosNotAtoms = sdk.Coins{sdk.NewInt64Coin("foo", 10000)} - coinsMulti = sdk.Coins{sdk.NewInt64Coin("foo", 10000), sdk.NewInt64Coin("steak", 1000)} + coinsMulti = sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 1000), sdk.NewInt64Coin("foo", 10000)} ) +func init() { + coinsMulti.Sort() +} + // test ValidateBasic for MsgCreateValidator func TestMsgSubmitProposal(t *testing.T) { _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) @@ -42,9 +47,9 @@ func TestMsgSubmitProposal(t *testing.T) { for i, tc := range tests { msg := NewMsgSubmitProposal(tc.title, tc.description, tc.proposalType, tc.proposerAddr, tc.initialDeposit) if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test: %v", i) + require.NoError(t, msg.ValidateBasic(), "test: %v", i) } else { - require.NotNil(t, msg.ValidateBasic(), "test: %v", i) + require.Error(t, msg.ValidateBasic(), "test: %v", i) } } } @@ -53,13 +58,12 @@ func TestMsgSubmitProposal(t *testing.T) { func TestMsgDeposit(t *testing.T) { _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) tests := []struct { - proposalID int64 + proposalID uint64 depositerAddr sdk.AccAddress depositAmount sdk.Coins expectPass bool }{ {0, addrs[0], coinsPos, true}, - {-1, addrs[0], coinsPos, false}, {1, sdk.AccAddress{}, coinsPos, false}, {1, addrs[0], coinsZero, true}, {1, addrs[0], coinsNeg, false}, @@ -69,9 +73,9 @@ func TestMsgDeposit(t *testing.T) { for i, tc := range tests { msg := NewMsgDeposit(tc.depositerAddr, tc.proposalID, tc.depositAmount) if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test: %v", i) + require.NoError(t, msg.ValidateBasic(), "test: %v", i) } else { - require.NotNil(t, msg.ValidateBasic(), "test: %v", i) + require.Error(t, msg.ValidateBasic(), "test: %v", i) } } } @@ -80,13 +84,12 @@ func TestMsgDeposit(t *testing.T) { func TestMsgVote(t *testing.T) { _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) tests := []struct { - proposalID int64 + proposalID uint64 voterAddr sdk.AccAddress option VoteOption expectPass bool }{ {0, addrs[0], OptionYes, true}, - {-1, addrs[0], OptionYes, false}, {0, sdk.AccAddress{}, OptionYes, false}, {0, addrs[0], OptionNo, true}, {0, addrs[0], OptionNoWithVeto, true}, diff --git a/x/gov/procedures.go b/x/gov/params.go similarity index 78% rename from x/gov/procedures.go rename to x/gov/params.go index e453add791c9..b9e1efa87805 100644 --- a/x/gov/procedures.go +++ b/x/gov/params.go @@ -6,20 +6,20 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// Procedure around Deposits for governance -type DepositProcedure struct { +// Param around Deposits for governance +type DepositParams struct { MinDeposit sdk.Coins `json:"min_deposit"` // Minimum deposit for a proposal to enter voting period. MaxDepositPeriod time.Duration `json:"max_deposit_period"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months } -// Procedure around Tallying votes in governance -type TallyingProcedure struct { +// Param around Tallying votes in governance +type TallyParams struct { Threshold sdk.Dec `json:"threshold"` // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 Veto sdk.Dec `json:"veto"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 GovernancePenalty sdk.Dec `json:"governance_penalty"` // Penalty if validator does not vote } -// Procedure around Voting in governance -type VotingProcedure struct { +// Param around Voting in governance +type VotingParams struct { VotingPeriod time.Duration `json:"voting_period"` // Length of the voting period. } diff --git a/x/gov/proposals.go b/x/gov/proposals.go index 37e29df704d6..e943fe1169ab 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -13,8 +13,8 @@ import ( //----------------------------------------------------------- // Proposal interface type Proposal interface { - GetProposalID() int64 - SetProposalID(int64) + GetProposalID() uint64 + SetProposalID(uint64) GetTitle() string SetTitle(string) @@ -34,11 +34,17 @@ type Proposal interface { GetSubmitTime() time.Time SetSubmitTime(time.Time) + GetDepositEndTime() time.Time + SetDepositEndTime(time.Time) + GetTotalDeposit() sdk.Coins SetTotalDeposit(sdk.Coins) GetVotingStartTime() time.Time SetVotingStartTime(time.Time) + + GetVotingEndTime() time.Time + SetVotingEndTime(time.Time) } // checks if two proposals are equal @@ -50,8 +56,10 @@ func ProposalEqual(proposalA Proposal, proposalB Proposal) bool { proposalA.GetStatus() == proposalB.GetStatus() && proposalA.GetTallyResult().Equals(proposalB.GetTallyResult()) && proposalA.GetSubmitTime().Equal(proposalB.GetSubmitTime()) && + proposalA.GetDepositEndTime().Equal(proposalB.GetDepositEndTime()) && proposalA.GetTotalDeposit().IsEqual(proposalB.GetTotalDeposit()) && - proposalA.GetVotingStartTime().Equal(proposalB.GetVotingStartTime()) { + proposalA.GetVotingStartTime().Equal(proposalB.GetVotingStartTime()) && + proposalA.GetVotingEndTime().Equal(proposalB.GetVotingEndTime()) { return true } return false @@ -60,7 +68,7 @@ func ProposalEqual(proposalA Proposal, proposalB Proposal) bool { //----------------------------------------------------------- // Text Proposals type TextProposal struct { - ProposalID int64 `json:"proposal_id"` // ID of the proposal + ProposalID uint64 `json:"proposal_id"` // ID of the proposal Title string `json:"title"` // Title of the proposal Description string `json:"description"` // Description of the proposal ProposalType ProposalKind `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} @@ -68,18 +76,20 @@ type TextProposal struct { Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected} TallyResult TallyResult `json:"tally_result"` // Result of Tallys - SubmitTime time.Time `json:"submit_block"` // Height of the block where TxGovSubmitProposal was included - TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit + 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 + TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit - VotingStartTime time.Time `json:"voting_start_block"` // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached + VotingStartTime time.Time `json:"voting_start_time"` // Time of the block where MinDeposit was reached. -1 if MinDeposit is not reached + VotingEndTime time.Time `json:"voting_end_time"` // Time that the VotingPeriod for this proposal will end and votes will be tallied } // Implements Proposal Interface var _ Proposal = (*TextProposal)(nil) // nolint -func (tp TextProposal) GetProposalID() int64 { return tp.ProposalID } -func (tp *TextProposal) SetProposalID(proposalID int64) { tp.ProposalID = proposalID } +func (tp TextProposal) GetProposalID() uint64 { return tp.ProposalID } +func (tp *TextProposal) SetProposalID(proposalID uint64) { tp.ProposalID = proposalID } func (tp TextProposal) GetTitle() string { return tp.Title } func (tp *TextProposal) SetTitle(title string) { tp.Title = title } func (tp TextProposal) GetDescription() string { return tp.Description } @@ -92,16 +102,24 @@ func (tp TextProposal) GetTallyResult() TallyResult { return tp.T 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) GetTotalDeposit() sdk.Coins { return tp.TotalDeposit } -func (tp *TextProposal) SetTotalDeposit(totalDeposit sdk.Coins) { tp.TotalDeposit = totalDeposit } -func (tp TextProposal) GetVotingStartTime() time.Time { return tp.VotingStartTime } +func (tp TextProposal) GetDepositEndTime() time.Time { return tp.DepositEndTime } +func (tp *TextProposal) SetDepositEndTime(depositEndTime time.Time) { + tp.DepositEndTime = depositEndTime +} +func (tp TextProposal) GetTotalDeposit() sdk.Coins { return tp.TotalDeposit } +func (tp *TextProposal) SetTotalDeposit(totalDeposit sdk.Coins) { tp.TotalDeposit = totalDeposit } +func (tp TextProposal) GetVotingStartTime() time.Time { return tp.VotingStartTime } func (tp *TextProposal) SetVotingStartTime(votingStartTime time.Time) { tp.VotingStartTime = votingStartTime } +func (tp TextProposal) GetVotingEndTime() time.Time { return tp.VotingEndTime } +func (tp *TextProposal) SetVotingEndTime(votingEndTime time.Time) { + tp.VotingEndTime = votingEndTime +} //----------------------------------------------------------- // ProposalQueue -type ProposalQueue []int64 +type ProposalQueue []uint64 //----------------------------------------------------------- // ProposalKind diff --git a/x/gov/queryable.go b/x/gov/querier.go similarity index 60% rename from x/gov/queryable.go rename to x/gov/querier.go index 606f73a9ca49..171ab469d551 100644 --- a/x/gov/queryable.go +++ b/x/gov/querier.go @@ -10,6 +10,7 @@ import ( // query endpoints supported by the governance Querier const ( + QueryParams = "params" QueryProposals = "proposals" QueryProposal = "proposal" QueryDeposits = "deposits" @@ -17,11 +18,17 @@ const ( QueryVotes = "votes" QueryVote = "vote" QueryTally = "tally" + + ParamDeposit = "deposit" + ParamVoting = "voting" + ParamTallying = "tallying" ) func NewQuerier(keeper Keeper) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { switch path[0] { + case QueryParams: + return queryParams(ctx, path[1:], req, keeper) case QueryProposals: return queryProposals(ctx, path[1:], req, keeper) case QueryProposal: @@ -42,9 +49,34 @@ func NewQuerier(keeper Keeper) sdk.Querier { } } +func queryParams(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + switch path[0] { + case ParamDeposit: + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, keeper.GetDepositParams(ctx)) + if err2 != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) + } + return bz, nil + case ParamVoting: + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, keeper.GetVotingParams(ctx)) + if err2 != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) + } + return bz, nil + case ParamTallying: + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, keeper.GetTallyParams(ctx)) + if err2 != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) + } + return bz, nil + default: + return res, sdk.ErrUnknownRequest(fmt.Sprintf("%s is not a valid query request path", req.Path)) + } +} + // Params for query 'custom/gov/proposal' type QueryProposalParams struct { - ProposalID int64 + ProposalID uint64 } // nolint: unparam @@ -52,24 +84,24 @@ func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, keeper var params QueryProposalParams err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } proposal := keeper.GetProposal(ctx, params.ProposalID) if proposal == nil { - return []byte{}, ErrUnknownProposal(DefaultCodespace, params.ProposalID) + return nil, ErrUnknownProposal(DefaultCodespace, params.ProposalID) } bz, err2 := codec.MarshalJSONIndent(keeper.cdc, proposal) if err2 != nil { - panic("could not marshal result to JSON") + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) } return bz, nil } // Params for query 'custom/gov/deposit' type QueryDepositParams struct { - ProposalID int64 + ProposalID uint64 Depositer sdk.AccAddress } @@ -78,20 +110,20 @@ func queryDeposit(ctx sdk.Context, path []string, req abci.RequestQuery, keeper var params QueryDepositParams err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } deposit, _ := keeper.GetDeposit(ctx, params.ProposalID, params.Depositer) bz, err2 := codec.MarshalJSONIndent(keeper.cdc, deposit) if err2 != nil { - panic("could not marshal result to JSON") + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) } return bz, nil } // Params for query 'custom/gov/vote' type QueryVoteParams struct { - ProposalID int64 + ProposalID uint64 Voter sdk.AccAddress } @@ -100,48 +132,48 @@ func queryVote(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Kee var params QueryVoteParams err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } vote, _ := keeper.GetVote(ctx, params.ProposalID, params.Voter) bz, err2 := codec.MarshalJSONIndent(keeper.cdc, vote) if err2 != nil { - panic("could not marshal result to JSON") + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) } return bz, nil } // Params for query 'custom/gov/deposits' type QueryDepositsParams struct { - ProposalID int64 + ProposalID uint64 } // nolint: unparam func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { - var params QueryDepositParams + var params QueryDepositsParams err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } var deposits []Deposit depositsIterator := keeper.GetDeposits(ctx, params.ProposalID) for ; depositsIterator.Valid(); depositsIterator.Next() { deposit := Deposit{} - keeper.cdc.MustUnmarshalBinary(depositsIterator.Value(), &deposit) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) deposits = append(deposits, deposit) } bz, err2 := codec.MarshalJSONIndent(keeper.cdc, deposits) if err2 != nil { - panic("could not marshal result to JSON") + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) } return bz, nil } // Params for query 'custom/gov/votes' type QueryVotesParams struct { - ProposalID int64 + ProposalID uint64 } // nolint: unparam @@ -150,30 +182,30 @@ func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } var votes []Vote votesIterator := keeper.GetVotes(ctx, params.ProposalID) for ; votesIterator.Valid(); votesIterator.Next() { vote := Vote{} - keeper.cdc.MustUnmarshalBinary(votesIterator.Value(), &vote) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) votes = append(votes, vote) } bz, err2 := codec.MarshalJSONIndent(keeper.cdc, votes) if err2 != nil { - panic("could not marshal result to JSON") + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) } return bz, nil } // Params for query 'custom/gov/proposals' type QueryProposalsParams struct { - Voter sdk.AccAddress - Depositer sdk.AccAddress - ProposalStatus ProposalStatus - NumLatestProposals int64 + Voter sdk.AccAddress + Depositer sdk.AccAddress + ProposalStatus ProposalStatus + Limit uint64 } // nolint: unparam @@ -181,36 +213,38 @@ func queryProposals(ctx sdk.Context, path []string, req abci.RequestQuery, keepe var params QueryProposalsParams err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } - proposals := keeper.GetProposalsFiltered(ctx, params.Voter, params.Depositer, params.ProposalStatus, params.NumLatestProposals) + proposals := keeper.GetProposalsFiltered(ctx, params.Voter, params.Depositer, params.ProposalStatus, params.Limit) bz, err2 := codec.MarshalJSONIndent(keeper.cdc, proposals) if err2 != nil { - panic("could not marshal result to JSON") + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) } return bz, nil } // Params for query 'custom/gov/tally' type QueryTallyParams struct { - ProposalID int64 + ProposalID uint64 } // nolint: unparam func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { // TODO: Dependant on #1914 - var proposalID int64 - err2 := keeper.cdc.UnmarshalJSON(req.Data, proposalID) + var params QueryTallyParams + err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { - return res, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } + proposalID := params.ProposalID + proposal := keeper.GetProposal(ctx, proposalID) if proposal == nil { - return res, ErrUnknownProposal(DefaultCodespace, proposalID) + return nil, ErrUnknownProposal(DefaultCodespace, proposalID) } var tallyResult TallyResult @@ -225,7 +259,7 @@ func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke bz, err2 := codec.MarshalJSONIndent(keeper.cdc, tallyResult) if err2 != nil { - panic("could not marshal result to JSON") + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) } return bz, nil } diff --git a/x/gov/querier_test.go b/x/gov/querier_test.go new file mode 100644 index 000000000000..eee58d2ac192 --- /dev/null +++ b/x/gov/querier_test.go @@ -0,0 +1,306 @@ +package gov + +import ( + "strings" + "testing" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +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}, "/"), + Data: []byte{}, + } + + bz, err := querier(ctx, []string{QueryParams, ParamDeposit}, query) + require.Nil(t, err) + require.NotNil(t, bz) + + var depositParams DepositParams + err2 := cdc.UnmarshalJSON(bz, &depositParams) + require.Nil(t, err2) + + query = abci.RequestQuery{ + Path: strings.Join([]string{"custom", "gov", QueryParams, ParamVoting}, "/"), + Data: []byte{}, + } + + bz, err = querier(ctx, []string{QueryParams, ParamVoting}, query) + require.Nil(t, err) + require.NotNil(t, bz) + + var votingParams VotingParams + err2 = cdc.UnmarshalJSON(bz, &votingParams) + require.Nil(t, err2) + + query = abci.RequestQuery{ + Path: strings.Join([]string{"custom", "gov", QueryParams, ParamTallying}, "/"), + Data: []byte{}, + } + + bz, err = querier(ctx, []string{QueryParams, ParamTallying}, query) + require.Nil(t, err) + require.NotNil(t, bz) + + var tallyParams TallyParams + err2 = cdc.UnmarshalJSON(bz, &tallyParams) + require.Nil(t, err2) + + return depositParams, votingParams, tallyParams +} + +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}, "/"), + Data: cdc.MustMarshalJSON(QueryProposalParams{ + ProposalID: proposalID, + }), + } + + bz, err := querier(ctx, []string{QueryProposal}, query) + require.Nil(t, err) + require.NotNil(t, bz) + + var proposal Proposal + err2 := cdc.UnmarshalJSON(bz, proposal) + require.Nil(t, err2) + return proposal +} + +func getQueriedProposals(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, depositer, voter sdk.AccAddress, status ProposalStatus, limit uint64) []Proposal { + query := abci.RequestQuery{ + Path: strings.Join([]string{"custom", "gov", QueryProposals}, "/"), + Data: cdc.MustMarshalJSON(QueryProposalsParams{ + Voter: voter, + Depositer: depositer, + ProposalStatus: status, + Limit: limit, + }), + } + + bz, err := querier(ctx, []string{QueryProposal}, query) + require.Nil(t, err) + require.NotNil(t, bz) + + var proposals []Proposal + err2 := cdc.UnmarshalJSON(bz, proposals) + require.Nil(t, err2) + return proposals +} + +func getQueriedDeposit(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64, depositer sdk.AccAddress) Deposit { + query := abci.RequestQuery{ + Path: strings.Join([]string{"custom", "gov", QueryDeposit}, "/"), + Data: cdc.MustMarshalJSON(QueryDepositParams{ + ProposalID: proposalID, + Depositer: depositer, + }), + } + + bz, err := querier(ctx, []string{QueryDeposits}, query) + require.Nil(t, err) + require.NotNil(t, bz) + + var deposit Deposit + err2 := cdc.UnmarshalJSON(bz, deposit) + require.Nil(t, err2) + return deposit +} + +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}, "/"), + Data: cdc.MustMarshalJSON(QueryDepositsParams{ + ProposalID: proposalID, + }), + } + + bz, err := querier(ctx, []string{QueryDeposits}, query) + require.Nil(t, err) + require.NotNil(t, bz) + + var deposits []Deposit + err2 := cdc.UnmarshalJSON(bz, &deposits) + require.Nil(t, err2) + return deposits +} + +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}, "/"), + Data: cdc.MustMarshalJSON(QueryVoteParams{ + ProposalID: proposalID, + Voter: voter, + }), + } + + bz, err := querier(ctx, []string{QueryVote}, query) + require.Nil(t, err) + require.NotNil(t, bz) + + var vote Vote + err2 := cdc.UnmarshalJSON(bz, &vote) + require.Nil(t, err2) + return vote +} + +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}, "/"), + Data: cdc.MustMarshalJSON(QueryVotesParams{ + ProposalID: proposalID, + }), + } + + bz, err := querier(ctx, []string{QueryVotes}, query) + require.Nil(t, err) + require.NotNil(t, bz) + + var votes []Vote + err2 := cdc.UnmarshalJSON(bz, &votes) + require.Nil(t, err2) + return votes +} + +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}, "/"), + Data: cdc.MustMarshalJSON(QueryTallyParams{ + ProposalID: proposalID, + }), + } + + bz, err := querier(ctx, []string{QueryTally}, query) + require.Nil(t, err) + require.NotNil(t, bz) + + var tally TallyResult + err2 := cdc.UnmarshalJSON(bz, &tally) + require.Nil(t, err2) + return tally +} + +func testQueryParams(t *testing.T) { + cdc := codec.New() + mapp, keeper, _, _, _, _ := getMockApp(t, 1000) + querier := NewQuerier(keeper) + ctx := mapp.NewContext(false, abci.Header{}) + + getQueriedParams(t, ctx, cdc, querier) +} + +func testQueries(t *testing.T) { + cdc := codec.New() + mapp, keeper, _, addrs, _, _ := getMockApp(t, 1000) + querier := NewQuerier(keeper) + handler := NewHandler(keeper) + ctx := mapp.NewContext(false, abci.Header{}) + + depositParams, _, _ := getQueriedParams(t, ctx, cdc, querier) + + // addrs[0] proposes (and deposits) proposals #1 and #2 + res := handler(ctx, NewMsgSubmitProposal("title", "description", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("dummycoin", 1)})) + var proposalID1 uint64 + cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID1) + + res = handler(ctx, NewMsgSubmitProposal("title", "description", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("dummycoin", 1)})) + var proposalID2 uint64 + cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID2) + + // addrs[1] proposes (and deposits) proposals #3 + res = handler(ctx, NewMsgSubmitProposal("title", "description", ProposalTypeText, addrs[1], sdk.Coins{sdk.NewInt64Coin("dummycoin", 1)})) + var proposalID3 uint64 + cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID3) + + // addrs[1] deposits on proposals #2 & #3 + res = handler(ctx, NewMsgDeposit(addrs[1], proposalID2, depositParams.MinDeposit)) + res = handler(ctx, NewMsgDeposit(addrs[1], proposalID3, depositParams.MinDeposit)) + + // check deposits on proposal1 match individual deposits + deposits := getQueriedDeposits(t, ctx, cdc, querier, proposalID1) + require.Len(t, deposits, 1) + deposit := getQueriedDeposit(t, ctx, cdc, querier, proposalID1, addrs[0]) + require.Equal(t, deposit, deposits[0]) + + // check deposits on proposal2 match individual deposits + deposits = getQueriedDeposits(t, ctx, cdc, querier, proposalID2) + require.Len(t, deposits, 2) + deposit = getQueriedDeposit(t, ctx, cdc, querier, proposalID2, addrs[0]) + require.True(t, deposit.Equals(deposits[0])) + deposit = getQueriedDeposit(t, ctx, cdc, querier, proposalID2, addrs[1]) + require.True(t, deposit.Equals(deposits[1])) + + // check deposits on proposal3 match individual deposits + deposits = getQueriedDeposits(t, ctx, cdc, querier, proposalID3) + require.Len(t, deposits, 1) + deposit = getQueriedDeposit(t, ctx, cdc, querier, proposalID3, addrs[1]) + require.Equal(t, deposit, deposits[0]) + + // Only proposal #1 should be in Deposit Period + proposals := getQueriedProposals(t, ctx, cdc, querier, nil, nil, StatusDepositPeriod, 0) + require.Len(t, proposals, 1) + require.Equal(t, proposalID1, proposals[0].GetProposalID()) + // Only proposals #2 and #3 should be in Voting Period + proposals = getQueriedProposals(t, ctx, cdc, querier, nil, nil, StatusVotingPeriod, 0) + require.Len(t, proposals, 2) + require.Equal(t, proposalID2, proposals[0].GetProposalID()) + require.Equal(t, proposalID3, proposals[1].GetProposalID()) + + // Addrs[0] votes on proposals #2 & #3 + handler(ctx, NewMsgVote(addrs[0], proposalID2, OptionYes)) + handler(ctx, NewMsgVote(addrs[0], proposalID3, OptionYes)) + + // Addrs[1] votes on proposal #3 + handler(ctx, NewMsgVote(addrs[1], proposalID3, OptionYes)) + + // Test query voted by addrs[0] + proposals = getQueriedProposals(t, ctx, cdc, querier, nil, addrs[0], StatusNil, 0) + require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) + require.Equal(t, proposalID3, (proposals[1]).GetProposalID()) + + // Test query votes on Proposal 2 + votes := getQueriedVotes(t, ctx, cdc, querier, proposalID2) + require.Len(t, votes, 1) + require.Equal(t, addrs[0], votes[0].Voter) + vote := getQueriedVote(t, ctx, cdc, querier, proposalID2, addrs[0]) + require.Equal(t, vote, votes[0]) + + // Test query votes on Proposal 3 + votes = getQueriedVotes(t, ctx, cdc, querier, proposalID3) + require.Len(t, votes, 2) + require.True(t, addrs[0].String() == votes[0].Voter.String()) + require.True(t, addrs[1].String() == votes[0].Voter.String()) + + // Test proposals queries with filters + + // Test query all proposals + proposals = getQueriedProposals(t, ctx, cdc, querier, nil, nil, StatusNil, 0) + require.Equal(t, proposalID1, (proposals[0]).GetProposalID()) + require.Equal(t, proposalID2, (proposals[1]).GetProposalID()) + require.Equal(t, proposalID3, (proposals[2]).GetProposalID()) + + // Test query voted by addrs[1] + proposals = getQueriedProposals(t, ctx, cdc, querier, nil, addrs[1], StatusNil, 0) + require.Equal(t, proposalID3, (proposals[0]).GetProposalID()) + + // Test query deposited by addrs[0] + proposals = getQueriedProposals(t, ctx, cdc, querier, addrs[0], nil, StatusNil, 0) + require.Equal(t, proposalID1, (proposals[0]).GetProposalID()) + + // Test query deposited by addr2 + proposals = getQueriedProposals(t, ctx, cdc, querier, addrs[1], nil, StatusNil, 0) + require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) + require.Equal(t, proposalID3, (proposals[1]).GetProposalID()) + + // Test query voted AND deposited by addr1 + proposals = getQueriedProposals(t, ctx, cdc, querier, addrs[0], addrs[0], StatusNil, 0) + require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) + + // Test Tally Query + tally := getQueriedTally(t, ctx, cdc, querier, proposalID2) + require.True(t, !tally.Equals(EmptyTallyResult())) +} diff --git a/x/gov/simulation/msgs.go b/x/gov/simulation/msgs.go index d5ff1881e470..b69d7c18c6da 100644 --- a/x/gov/simulation/msgs.go +++ b/x/gov/simulation/msgs.go @@ -11,10 +11,11 @@ import ( "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" ) const ( - denom = "steak" + denom = stakeTypes.DefaultBondDenom ) // SimulateSubmittingVotingAndSlashingForProposal simulates creating a msg Submit Proposal @@ -50,7 +51,7 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keepe if err != nil { return "", nil, err } - action, ok := simulateHandleMsgSubmitProposal(msg, sk, handler, ctx, event) + action, ok := simulateHandleMsgSubmitProposal(msg, handler, ctx, event) // don't schedule votes if proposal failed if !ok { return action, nil, nil @@ -64,11 +65,11 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keepe whoVotes := r.Perm(len(accs)) // didntVote := whoVotes[numVotes:] whoVotes = whoVotes[:numVotes] - votingPeriod := k.GetVotingProcedure(ctx).VotingPeriod + votingPeriod := k.GetVotingParams(ctx).VotingPeriod fops := make([]simulation.FutureOperation, numVotes+1) for i := 0; i < numVotes; i++ { whenVote := ctx.BlockHeader().Time.Add(time.Duration(r.Int63n(int64(votingPeriod.Seconds()))) * time.Second) - fops[i] = simulation.FutureOperation{BlockTime: whenVote, Op: operationSimulateMsgVote(k, sk, accs[whoVotes[i]], proposalID)} + fops[i] = simulation.FutureOperation{BlockTime: whenVote, Op: operationSimulateMsgVote(k, accs[whoVotes[i]], proposalID)} } // 3) Make an operation to ensure slashes were done correctly. (Really should be a future invariant) // TODO: Find a way to check if a validator was slashed other than just checking their balance a block @@ -80,7 +81,7 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keepe // SimulateMsgSubmitProposal simulates a msg Submit Proposal // Note: Currently doesn't ensure that the proposal txt is in JSON form -func SimulateMsgSubmitProposal(k gov.Keeper, sk stake.Keeper) simulation.Operation { +func SimulateMsgSubmitProposal(k gov.Keeper) simulation.Operation { handler := gov.NewHandler(k) return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) { sender := simulation.RandomAcc(r, accs) @@ -88,22 +89,15 @@ func SimulateMsgSubmitProposal(k gov.Keeper, sk stake.Keeper) simulation.Operati if err != nil { return "", nil, err } - action, _ = simulateHandleMsgSubmitProposal(msg, sk, handler, ctx, event) + action, _ = simulateHandleMsgSubmitProposal(msg, handler, ctx, event) return action, nil, nil } } -func simulateHandleMsgSubmitProposal(msg gov.MsgSubmitProposal, sk stake.Keeper, handler sdk.Handler, ctx sdk.Context, event func(string)) (action string, ok bool) { - ctx, write := ctx.CacheContext() +func simulateHandleMsgSubmitProposal(msg gov.MsgSubmitProposal, handler sdk.Handler, ctx sdk.Context, event func(string)) (action string, ok bool) { + ctx, _ = ctx.CacheContext() result := handler(ctx, msg) ok = result.IsOK() - if ok { - // Update pool to keep invariants - pool := sk.GetPool(ctx) - pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewDecFromInt(msg.InitialDeposit.AmountOf(denom))) - sk.SetPool(ctx, pool) - write() - } event(fmt.Sprintf("gov/MsgSubmitProposal/%v", ok)) action = fmt.Sprintf("TestMsgSubmitProposal: ok %v, msg %s", ok, msg.GetSignBytes()) return @@ -125,7 +119,7 @@ func simulationCreateMsgSubmitProposal(r *rand.Rand, sender simulation.Account) } // SimulateMsgDeposit -func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation { +func SimulateMsgDeposit(k gov.Keeper) simulation.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { acc := simulation.RandomAcc(r, accs) proposalID, ok := randomProposalID(r, k, ctx) @@ -137,15 +131,8 @@ func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation { if msg.ValidateBasic() != nil { return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } - ctx, write := ctx.CacheContext() + ctx, _ = ctx.CacheContext() result := gov.NewHandler(k)(ctx, msg) - if result.IsOK() { - // Update pool to keep invariants - pool := sk.GetPool(ctx) - pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewDecFromInt(deposit.AmountOf(denom))) - sk.SetPool(ctx, pool) - write() - } event(fmt.Sprintf("gov/MsgDeposit/%v", result.IsOK())) action = fmt.Sprintf("TestMsgDeposit: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil, nil @@ -154,12 +141,12 @@ func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation { // SimulateMsgVote // nolint: unparam -func SimulateMsgVote(k gov.Keeper, sk stake.Keeper) simulation.Operation { - return operationSimulateMsgVote(k, sk, simulation.Account{}, -1) +func SimulateMsgVote(k gov.Keeper) simulation.Operation { + return operationSimulateMsgVote(k, simulation.Account{}, 0) } // nolint: unparam -func operationSimulateMsgVote(k gov.Keeper, sk stake.Keeper, acc simulation.Account, proposalID int64) simulation.Operation { +func operationSimulateMsgVote(k gov.Keeper, acc simulation.Account, proposalID uint64) simulation.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { if acc.Equals(simulation.Account{}) { acc = simulation.RandomAcc(r, accs) @@ -200,12 +187,12 @@ func randomDeposit(r *rand.Rand) sdk.Coins { } // Pick a random proposal ID -func randomProposalID(r *rand.Rand, k gov.Keeper, ctx sdk.Context) (proposalID int64, ok bool) { +func randomProposalID(r *rand.Rand, k gov.Keeper, ctx sdk.Context) (proposalID uint64, ok bool) { lastProposalID := k.GetLastProposalID(ctx) if lastProposalID < 1 { return 0, false } - proposalID = int64(r.Intn(int(lastProposalID))) + proposalID = uint64(r.Intn(int(lastProposalID))) return proposalID, true } diff --git a/x/gov/simulation/sim_test.go b/x/gov/simulation/sim_test.go index a7d4e40d0590..5b6068c577a4 100644 --- a/x/gov/simulation/sim_test.go +++ b/x/gov/simulation/sim_test.go @@ -22,7 +22,7 @@ func TestGovWithRandomMessages(t *testing.T) { bank.RegisterCodec(mapp.Cdc) gov.RegisterCodec(mapp.Cdc) - mapper := mapp.AccountMapper + mapper := mapp.AccountKeeper bankKeeper := bank.NewBaseKeeper(mapper) stakeKey := sdk.NewKVStoreKey("stake") @@ -59,9 +59,9 @@ func TestGovWithRandomMessages(t *testing.T) { simulation.Simulate( t, mapp.BaseApp, appStateFn, []simulation.WeightedOperation{ - {2, SimulateMsgSubmitProposal(govKeeper, stakeKeeper)}, - {3, SimulateMsgDeposit(govKeeper, stakeKeeper)}, - {20, SimulateMsgVote(govKeeper, stakeKeeper)}, + {2, SimulateMsgSubmitProposal(govKeeper)}, + {3, SimulateMsgDeposit(govKeeper)}, + {20, SimulateMsgVote(govKeeper)}, }, []simulation.RandSetup{ setup, }, []simulation.Invariant{ @@ -75,7 +75,7 @@ func TestGovWithRandomMessages(t *testing.T) { t, mapp.BaseApp, appStateFn, []simulation.WeightedOperation{ {10, SimulateSubmittingVotingAndSlashingForProposal(govKeeper, stakeKeeper)}, - {5, SimulateMsgDeposit(govKeeper, stakeKeeper)}, + {5, SimulateMsgDeposit(govKeeper)}, }, []simulation.RandSetup{ setup, }, []simulation.Invariant{ diff --git a/x/gov/tally.go b/x/gov/tally.go index c5751258a00a..d66237762ead 100644 --- a/x/gov/tally.go +++ b/x/gov/tally.go @@ -23,7 +23,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall totalVotingPower := sdk.ZeroDec() currValidators := make(map[string]validatorGovInfo) - keeper.vs.IterateValidatorsBonded(ctx, func(index int64, validator sdk.Validator) (stop bool) { + keeper.vs.IterateBondedValidatorsByPower(ctx, func(index int64, validator sdk.Validator) (stop bool) { currValidators[validator.GetOperator().String()] = validatorGovInfo{ Address: validator.GetOperator(), Power: validator.GetPower(), @@ -39,7 +39,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall defer votesIterator.Close() for ; votesIterator.Valid(); votesIterator.Next() { vote := &Vote{} - keeper.cdc.MustUnmarshalBinary(votesIterator.Value(), vote) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), vote) // if validator, just record it in the map // if delegator tally voting power @@ -50,7 +50,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall } else { keeper.ds.IterateDelegations(ctx, vote.Voter, func(index int64, delegation sdk.Delegation) (stop bool) { - valAddrStr := delegation.GetValidator().String() + valAddrStr := delegation.GetValidatorAddr().String() if val, ok := currValidators[valAddrStr]; ok { val.Minus = val.Minus.Add(delegation.GetShares()) @@ -84,7 +84,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall totalVotingPower = totalVotingPower.Add(votingPower) } - tallyingProcedure := keeper.GetTallyingProcedure(ctx) + tallyParams := keeper.GetTallyParams(ctx) tallyResults = TallyResult{ Yes: results[OptionYes], @@ -98,11 +98,11 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall return false, tallyResults } // If more than 1/3 of voters veto, proposal fails - if results[OptionNoWithVeto].Quo(totalVotingPower).GT(tallyingProcedure.Veto) { + if results[OptionNoWithVeto].Quo(totalVotingPower).GT(tallyParams.Veto) { return false, tallyResults } // If more than 1/2 of non-abstaining voters vote Yes, proposal passes - if results[OptionYes].Quo(totalVotingPower.Sub(results[OptionAbstain])).GT(tallyingProcedure.Threshold) { + if results[OptionYes].Quo(totalVotingPower.Sub(results[OptionAbstain])).GT(tallyParams.Threshold) { return true, tallyResults } // If more than 1/2 of non-abstaining voters vote No, proposal fails diff --git a/x/gov/tally_test.go b/x/gov/tally_test.go index 26b32cc93a72..b2f6aaf32aef 100644 --- a/x/gov/tally_test.go +++ b/x/gov/tally_test.go @@ -11,6 +11,7 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" "github.com/cosmos/cosmos-sdk/x/stake" + stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" ) var ( @@ -25,7 +26,7 @@ func createValidators(t *testing.T, stakeHandler sdk.Handler, ctx sdk.Context, a for i := 0; i < len(addrs); i++ { valCreateMsg := stake.NewMsgCreateValidator( - addrs[i], pubkeys[i], sdk.NewInt64Coin("steak", coinAmt[i]), testDescription, testCommissionMsg, + addrs[i], pubkeys[i], sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, coinAmt[i]), testDescription, testCommissionMsg, ) res := stakeHandler(ctx, valCreateMsg) @@ -289,7 +290,7 @@ func TestTallyDelgatorOverride(t *testing.T) { createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 6, 7}) stake.EndBlocker(ctx, sk) - delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin("steak", 30)) + delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 30)) stakeHandler(ctx, delegator1Msg) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) @@ -326,7 +327,7 @@ func TestTallyDelgatorInherit(t *testing.T) { createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 6, 7}) stake.EndBlocker(ctx, sk) - delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin("steak", 30)) + delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 30)) stakeHandler(ctx, delegator1Msg) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) @@ -361,9 +362,9 @@ func TestTallyDelgatorMultipleOverride(t *testing.T) { createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 6, 7}) stake.EndBlocker(ctx, sk) - delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin("steak", 10)) + 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("steak", 10)) + delegator1Msg2 := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[1]), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)) stakeHandler(ctx, delegator1Msg2) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) @@ -393,24 +394,24 @@ func TestTallyDelgatorMultipleInherit(t *testing.T) { stakeHandler := stake.NewHandler(sk) val1CreateMsg := stake.NewMsgCreateValidator( - sdk.ValAddress(addrs[0]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin("steak", 25), testDescription, testCommissionMsg, + sdk.ValAddress(addrs[0]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 25), testDescription, testCommissionMsg, ) stakeHandler(ctx, val1CreateMsg) val2CreateMsg := stake.NewMsgCreateValidator( - sdk.ValAddress(addrs[1]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin("steak", 6), testDescription, testCommissionMsg, + sdk.ValAddress(addrs[1]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 6), testDescription, testCommissionMsg, ) stakeHandler(ctx, val2CreateMsg) val3CreateMsg := stake.NewMsgCreateValidator( - sdk.ValAddress(addrs[2]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin("steak", 7), testDescription, testCommissionMsg, + sdk.ValAddress(addrs[2]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 7), testDescription, testCommissionMsg, ) stakeHandler(ctx, val3CreateMsg) - delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin("steak", 10)) + 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("steak", 10)) + delegator1Msg2 := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[1]), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)) stakeHandler(ctx, delegator1Msg2) stake.EndBlocker(ctx, sk) @@ -447,10 +448,10 @@ func TestTallyJailedValidator(t *testing.T) { createValidators(t, stakeHandler, ctx, valAddrs, []int64{25, 6, 7}) stake.EndBlocker(ctx, sk) - delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin("steak", 10)) + 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("steak", 10)) + delegator1Msg2 := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[1]), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)) stakeHandler(ctx, delegator1Msg2) val2, found := sk.GetValidator(ctx, sdk.ValAddress(addrs[1])) diff --git a/x/gov/test_common.go b/x/gov/test_common.go index b33f58084186..a0bc80fc2e3e 100644 --- a/x/gov/test_common.go +++ b/x/gov/test_common.go @@ -17,6 +17,7 @@ import ( "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" ) // initialize the mock application for this module @@ -33,18 +34,19 @@ func getMockApp(t *testing.T, numGenAccs int) (*mock.App, Keeper, stake.Keeper, keyGov := sdk.NewKVStoreKey("gov") pk := params.NewKeeper(mapp.Cdc, keyGlobalParams, tkeyGlobalParams) - ck := bank.NewBaseKeeper(mapp.AccountMapper) + ck := bank.NewBaseKeeper(mapp.AccountKeeper) sk := stake.NewKeeper(mapp.Cdc, keyStake, tkeyStake, ck, pk.Subspace(stake.DefaultParamspace), mapp.RegisterCodespace(stake.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.SetEndBlocker(getEndBlocker(keeper)) mapp.SetInitChainer(getInitChainer(mapp, keeper, sk)) require.NoError(t, mapp.CompleteSetup(keyStake, tkeyStake, keyGov, keyGlobalParams, tkeyGlobalParams)) - genAccs, addrs, pubKeys, privKeys := mock.CreateGenAccounts(numGenAccs, sdk.Coins{sdk.NewInt64Coin("steak", 42)}) + genAccs, addrs, pubKeys, privKeys := mock.CreateGenAccounts(numGenAccs, sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 42)}) mock.SetGenesis(mapp, genAccs) diff --git a/x/ibc/app_test.go b/x/ibc/app_test.go index 6806d9779e5d..9b360c7c247a 100644 --- a/x/ibc/app_test.go +++ b/x/ibc/app_test.go @@ -21,7 +21,7 @@ func getMockApp(t *testing.T) *mock.App { RegisterCodec(mapp.Cdc) keyIBC := sdk.NewKVStoreKey("ibc") ibcMapper := NewMapper(mapp.Cdc, keyIBC, mapp.RegisterCodespace(DefaultCodespace)) - bankKeeper := bank.NewBaseKeeper(mapp.AccountMapper) + bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper) mapp.Router().AddRoute("ibc", NewHandler(ibcMapper, bankKeeper)) require.NoError(t, mapp.CompleteSetup(keyIBC)) @@ -49,7 +49,7 @@ func TestIBCMsgs(t *testing.T) { // A checkTx context (true) ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) - res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) + res1 := mapp.AccountKeeper.GetAccount(ctxCheck, addr1) require.Equal(t, acc, res1) packet := IBCPacket{ diff --git a/x/ibc/client/cli/relay.go b/x/ibc/client/cli/relay.go index ab7168aca3ff..43ad783f4935 100644 --- a/x/ibc/client/cli/relay.go +++ b/x/ibc/client/cli/relay.go @@ -117,7 +117,7 @@ OUTER: var processed int64 if processedbz == nil { processed = 0 - } else if err = c.cdc.UnmarshalBinary(processedbz, &processed); err != nil { + } else if err = c.cdc.UnmarshalBinaryLengthPrefixed(processedbz, &processed); err != nil { panic(err) } @@ -131,7 +131,7 @@ OUTER: var egressLength int64 if egressLengthbz == nil { egressLength = 0 - } else if err = c.cdc.UnmarshalBinary(egressLengthbz, &egressLength); err != nil { + } else if err = c.cdc.UnmarshalBinaryLengthPrefixed(egressLengthbz, &egressLength); err != nil { panic(err) } @@ -192,7 +192,7 @@ func (c relayCommander) getSequence(node string) int64 { func (c relayCommander) refine(bz []byte, sequence int64, passphrase string) []byte { var packet ibc.IBCPacket - if err := c.cdc.UnmarshalBinary(bz, &packet); err != nil { + if err := c.cdc.UnmarshalBinaryLengthPrefixed(bz, &packet); err != nil { panic(err) } diff --git a/x/ibc/handler.go b/x/ibc/handler.go index 4a9eebaebc45..c7bffc529348 100644 --- a/x/ibc/handler.go +++ b/x/ibc/handler.go @@ -13,7 +13,7 @@ func NewHandler(ibcm Mapper, ck bank.Keeper) sdk.Handler { case IBCReceiveMsg: return handleIBCReceiveMsg(ctx, ibcm, ck, msg) default: - errMsg := "Unrecognized IBC Msg type: " + msg.Name() + errMsg := "Unrecognized IBC Msg type: " + msg.Type() return sdk.ErrUnknownRequest(errMsg).Result() } } diff --git a/x/ibc/ibc_test.go b/x/ibc/ibc_test.go index c8e7492be7f1..6cd89fded4ea 100644 --- a/x/ibc/ibc_test.go +++ b/x/ibc/ibc_test.go @@ -17,7 +17,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" ) -// AccountMapper(/Keeper) and IBCMapper should use different StoreKey later +// AccountKeeper(/Keeper) and IBCMapper should use different StoreKey later func defaultContext(key sdk.StoreKey) sdk.Context { db := dbm.NewMemDB() @@ -64,7 +64,7 @@ func TestIBC(t *testing.T) { key := sdk.NewKVStoreKey("ibc") ctx := defaultContext(key) - am := auth.NewAccountMapper(cdc, key, auth.ProtoBaseAccount) + am := auth.NewAccountKeeper(cdc, key, auth.ProtoBaseAccount) ck := bank.NewBaseKeeper(am) src := newAddress() diff --git a/x/ibc/mapper.go b/x/ibc/mapper.go index 95c88b62fbc1..957ab191a8a3 100644 --- a/x/ibc/mapper.go +++ b/x/ibc/mapper.go @@ -33,13 +33,13 @@ func (ibcm Mapper) PostIBCPacket(ctx sdk.Context, packet IBCPacket) sdk.Error { // write everything into the state store := ctx.KVStore(ibcm.key) index := ibcm.getEgressLength(store, packet.DestChain) - bz, err := ibcm.cdc.MarshalBinary(packet) + bz, err := ibcm.cdc.MarshalBinaryLengthPrefixed(packet) if err != nil { panic(err) } store.Set(EgressKey(packet.DestChain, index), bz) - bz, err = ibcm.cdc.MarshalBinary(index + 1) + bz, err = ibcm.cdc.MarshalBinaryLengthPrefixed(index + 1) if err != nil { panic(err) } @@ -61,7 +61,7 @@ func (ibcm Mapper) ReceiveIBCPacket(ctx sdk.Context, packet IBCPacket) sdk.Error // Functions for accessing the underlying KVStore. func marshalBinaryPanic(cdc *codec.Codec, value interface{}) []byte { - res, err := cdc.MarshalBinary(value) + res, err := cdc.MarshalBinaryLengthPrefixed(value) if err != nil { panic(err) } @@ -69,7 +69,7 @@ func marshalBinaryPanic(cdc *codec.Codec, value interface{}) []byte { } func unmarshalBinaryPanic(cdc *codec.Codec, bz []byte, ptr interface{}) { - err := cdc.UnmarshalBinary(bz, ptr) + err := cdc.UnmarshalBinaryLengthPrefixed(bz, ptr) if err != nil { panic(err) } diff --git a/x/ibc/types.go b/x/ibc/types.go index 64b7d1f4c945..72dae0d389d0 100644 --- a/x/ibc/types.go +++ b/x/ibc/types.go @@ -71,8 +71,8 @@ type IBCTransferMsg struct { } // nolint -func (msg IBCTransferMsg) Type() string { return "ibc" } -func (msg IBCTransferMsg) Name() string { return "transfer" } +func (msg IBCTransferMsg) Route() string { return "ibc" } +func (msg IBCTransferMsg) Type() string { return "transfer" } // x/bank/tx.go MsgSend.GetSigners() func (msg IBCTransferMsg) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.SrcAddr} } @@ -100,8 +100,8 @@ type IBCReceiveMsg struct { } // nolint -func (msg IBCReceiveMsg) Type() string { return "ibc" } -func (msg IBCReceiveMsg) Name() string { return "receive" } +func (msg IBCReceiveMsg) Route() string { return "ibc" } +func (msg IBCReceiveMsg) Type() string { return "receive" } func (msg IBCReceiveMsg) ValidateBasic() sdk.Error { return msg.IBCPacket.ValidateBasic() } // x/bank/tx.go MsgSend.GetSigners() diff --git a/x/ibc/types_test.go b/x/ibc/types_test.go index e7c51d6b2477..7e0f537d6e24 100644 --- a/x/ibc/types_test.go +++ b/x/ibc/types_test.go @@ -37,7 +37,7 @@ func TestIBCTransferMsg(t *testing.T) { packet := constructIBCPacket(true) msg := IBCTransferMsg{packet} - require.Equal(t, msg.Type(), "ibc") + require.Equal(t, msg.Route(), "ibc") } func TestIBCTransferMsgValidation(t *testing.T) { @@ -69,7 +69,7 @@ func TestIBCReceiveMsg(t *testing.T) { packet := constructIBCPacket(true) msg := IBCReceiveMsg{packet, sdk.AccAddress([]byte("relayer")), 0} - require.Equal(t, msg.Type(), "ibc") + require.Equal(t, msg.Route(), "ibc") } func TestIBCReceiveMsgValidation(t *testing.T) { diff --git a/x/mint/abci_app.go b/x/mint/abci_app.go new file mode 100644 index 000000000000..73491f8081e1 --- /dev/null +++ b/x/mint/abci_app.go @@ -0,0 +1,26 @@ +package mint + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Called every block, process inflation on the first block of every hour +func BeginBlocker(ctx sdk.Context, k Keeper) { + + blockTime := ctx.BlockHeader().Time + minter := k.GetMinter(ctx) + if blockTime.Sub(minter.InflationLastTime) < time.Hour { // only mint on the hour! + return + } + + params := k.GetParams(ctx) + totalSupply := k.sk.TotalPower(ctx) + bondedRatio := k.sk.BondedRatio(ctx) + minter.InflationLastTime = blockTime + minter, mintedCoin := minter.ProcessProvisions(params, totalSupply, bondedRatio) + k.fck.AddCollectedFees(ctx, sdk.Coins{mintedCoin}) + k.sk.InflateSupply(ctx, sdk.NewDecFromInt(mintedCoin.Amount)) + k.SetMinter(ctx, minter) +} diff --git a/x/mint/expected_keepers.go b/x/mint/expected_keepers.go new file mode 100644 index 000000000000..150e155cb3b5 --- /dev/null +++ b/x/mint/expected_keepers.go @@ -0,0 +1,17 @@ +package mint + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// expected stake keeper +type StakeKeeper interface { + TotalPower(ctx sdk.Context) sdk.Dec + BondedRatio(ctx sdk.Context) sdk.Dec + InflateSupply(ctx sdk.Context, newTokens sdk.Dec) +} + +// expected fee collection keeper interface +type FeeCollectionKeeper interface { + AddCollectedFees(sdk.Context, sdk.Coins) sdk.Coins +} diff --git a/x/mint/genesis.go b/x/mint/genesis.go new file mode 100644 index 000000000000..ce375d71e576 --- /dev/null +++ b/x/mint/genesis.go @@ -0,0 +1,55 @@ +package mint + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GenesisState - all distribution state that must be provided at genesis +type GenesisState struct { + Minter Minter `json:"minter"` // minter object + Params Params `json:"params"` // inflation params +} + +func NewGenesisState(minter Minter, params Params) GenesisState { + return GenesisState{ + Minter: minter, + Params: params, + } +} + +// get raw genesis raw message for testing +func DefaultGenesisState() GenesisState { + return GenesisState{ + Minter: InitialMinter(), + Params: DefaultParams(), + } +} + +// new mint genesis +func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { + keeper.SetMinter(ctx, data.Minter) + keeper.SetParams(ctx, data.Params) +} + +// ExportGenesis returns a GenesisState for a given context and keeper. The +// GenesisState will contain the pool, and validator/delegator distribution info's +func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { + + minter := keeper.GetMinter(ctx) + params := keeper.GetParams(ctx) + return NewGenesisState(minter, params) +} + +// ValidateGenesis validates the provided staking genesis state to ensure the +// expected invariants holds. (i.e. params in correct bounds, no duplicate validators) +func ValidateGenesis(data GenesisState) error { + err := validateParams(data.Params) + if err != nil { + return err + } + err = validateMinter(data.Minter) + if err != nil { + return err + } + return nil +} diff --git a/x/mint/keeper.go b/x/mint/keeper.go new file mode 100644 index 000000000000..eba4c3fc1875 --- /dev/null +++ b/x/mint/keeper.go @@ -0,0 +1,85 @@ +package mint + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" +) + +// keeper of the stake store +type Keeper struct { + storeKey sdk.StoreKey + cdc *codec.Codec + paramSpace params.Subspace + sk StakeKeeper + fck FeeCollectionKeeper +} + +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, + paramSpace params.Subspace, sk StakeKeeper, fck FeeCollectionKeeper) Keeper { + + keeper := Keeper{ + storeKey: key, + cdc: cdc, + paramSpace: paramSpace.WithTypeTable(ParamTypeTable()), + sk: sk, + fck: fck, + } + return keeper +} + +//____________________________________________________________________ +// Keys + +var ( + minterKey = []byte{0x00} // the one key to use for the keeper store + + // params store for inflation params + ParamStoreKeyParams = []byte("params") +) + +// ParamTable for stake module +func ParamTypeTable() params.TypeTable { + return params.NewTypeTable( + ParamStoreKeyParams, Params{}, + ) +} + +const ( + // default paramspace for params keeper + DefaultParamspace = "mint" +) + +//______________________________________________________________________ + +// get the minter +func (k Keeper) GetMinter(ctx sdk.Context) (minter Minter) { + store := ctx.KVStore(k.storeKey) + b := store.Get(minterKey) + if b == nil { + panic("Stored fee pool should not have been nil") + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &minter) + return +} + +// set the minter +func (k Keeper) SetMinter(ctx sdk.Context, minter Minter) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinaryLengthPrefixed(minter) + store.Set(minterKey, b) +} + +//______________________________________________________________________ + +// get inflation params from the global param store +func (k Keeper) GetParams(ctx sdk.Context) Params { + var params Params + k.paramSpace.Get(ctx, ParamStoreKeyParams, ¶ms) + return params +} + +// set inflation params from the global param store +func (k Keeper) SetParams(ctx sdk.Context, params Params) { + k.paramSpace.Set(ctx, ParamStoreKeyParams, ¶ms) +} diff --git a/x/mint/minter.go b/x/mint/minter.go new file mode 100644 index 000000000000..135675887bf0 --- /dev/null +++ b/x/mint/minter.go @@ -0,0 +1,72 @@ +package mint + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// current inflation state +type Minter struct { + InflationLastTime time.Time `json:"inflation_last_time"` // block time which the last inflation was processed + Inflation sdk.Dec `json:"inflation"` // current annual inflation rate +} + +// minter object for a new minter +func InitialMinter() Minter { + return Minter{ + InflationLastTime: time.Unix(0, 0), + Inflation: sdk.NewDecWithPrec(13, 2), + } +} + +func validateMinter(minter Minter) error { + if minter.Inflation.LT(sdk.ZeroDec()) { + return fmt.Errorf("mint parameter Inflation should be positive, is %s ", minter.Inflation.String()) + } + if minter.Inflation.GT(sdk.OneDec()) { + return fmt.Errorf("mint parameter Inflation must be <= 1, is %s", minter.Inflation.String()) + } + return nil +} + +var hrsPerYr = sdk.NewDec(8766) // as defined by a julian year of 365.25 days + +// process provisions for an hour period +func (m Minter) ProcessProvisions(params Params, totalSupply, bondedRatio sdk.Dec) ( + minter Minter, provisions sdk.Coin) { + + m.Inflation = m.NextInflation(params, bondedRatio) + provisionsDec := m.Inflation.Mul(totalSupply).Quo(hrsPerYr) + provisions = sdk.NewCoin(params.MintDenom, provisionsDec.TruncateInt()) + + return m, provisions +} + +// get the next inflation rate for the hour +func (m Minter) NextInflation(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) { + + // The target annual inflation rate is recalculated for each previsions cycle. The + // inflation is also subject to a rate change (positive or negative) depending on + // the distance from the desired 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%. + + // (1 - bondedRatio/GoalBonded) * InflationRateChange + inflationRateChangePerYear := sdk.OneDec(). + Sub(bondedRatio.Quo(params.GoalBonded)). + Mul(params.InflationRateChange) + inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYr) + + // increase the new annual inflation for this next cycle + inflation = m.Inflation.Add(inflationRateChange) + if inflation.GT(params.InflationMax) { + inflation = params.InflationMax + } + if inflation.LT(params.InflationMin) { + inflation = params.InflationMin + } + + return inflation +} diff --git a/x/mint/minter_test.go b/x/mint/minter_test.go new file mode 100644 index 000000000000..b022b0ec804c --- /dev/null +++ b/x/mint/minter_test.go @@ -0,0 +1,53 @@ +package mint + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestNextInflation(t *testing.T) { + minter := InitialMinter() + params := DefaultParams() + + // Governing Mechanism: + // inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange + + tests := []struct { + bondedRatio, setInflation, expChange sdk.Dec + }{ + // with 0% bonded atom supply the inflation should increase by InflationRateChange + {sdk.ZeroDec(), sdk.NewDecWithPrec(7, 2), params.InflationRateChange.Quo(hrsPerYr)}, + + // 100% bonded, starting at 20% inflation and being reduced + // (1 - (1/0.67))*(0.13/8667) + {sdk.OneDec(), sdk.NewDecWithPrec(20, 2), + sdk.OneDec().Sub(sdk.OneDec().Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + + // 50% bonded, starting at 10% inflation and being increased + {sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(10, 2), + sdk.OneDec().Sub(sdk.NewDecWithPrec(5, 1).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + + // test 7% minimum stop (testing with 100% bonded) + {sdk.OneDec(), sdk.NewDecWithPrec(7, 2), sdk.ZeroDec()}, + {sdk.OneDec(), sdk.NewDecWithPrec(70001, 6), sdk.NewDecWithPrec(-1, 6)}, + + // test 20% maximum stop (testing with 0% bonded) + {sdk.ZeroDec(), sdk.NewDecWithPrec(20, 2), sdk.ZeroDec()}, + {sdk.ZeroDec(), sdk.NewDecWithPrec(199999, 6), sdk.NewDecWithPrec(1, 6)}, + + // perfect balance shouldn't change inflation + {sdk.NewDecWithPrec(67, 2), sdk.NewDecWithPrec(15, 2), sdk.ZeroDec()}, + } + for i, tc := range tests { + minter.Inflation = tc.setInflation + + inflation := minter.NextInflation(params, tc.bondedRatio) + diffInflation := inflation.Sub(tc.setInflation) + + require.True(t, diffInflation.Equal(tc.expChange), + "Test Index: %v\nDiff: %v\nExpected: %v\n", i, diffInflation, tc.expChange) + } +} diff --git a/x/mint/params.go b/x/mint/params.go new file mode 100644 index 000000000000..47c9c85480f2 --- /dev/null +++ b/x/mint/params.go @@ -0,0 +1,44 @@ +package mint + +import ( + "fmt" + stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// mint parameters +type Params struct { + MintDenom string `json:"mint_denom"` // type of coin to mint + InflationRateChange sdk.Dec `json:"inflation_rate_change"` // maximum annual change in inflation rate + InflationMax sdk.Dec `json:"inflation_max"` // maximum inflation rate + InflationMin sdk.Dec `json:"inflation_min"` // minimum inflation rate + GoalBonded sdk.Dec `json:"goal_bonded"` // goal of percent bonded atoms +} + +// default minting module parameters +func DefaultParams() Params { + return Params{ + MintDenom: stakeTypes.DefaultBondDenom, + InflationRateChange: sdk.NewDecWithPrec(13, 2), + InflationMax: sdk.NewDecWithPrec(20, 2), + InflationMin: sdk.NewDecWithPrec(7, 2), + GoalBonded: sdk.NewDecWithPrec(67, 2), + } +} + +func validateParams(params Params) error { + if params.GoalBonded.LT(sdk.ZeroDec()) { + return fmt.Errorf("mint parameter GoalBonded should be positive, is %s ", params.GoalBonded.String()) + } + if params.GoalBonded.GT(sdk.OneDec()) { + return fmt.Errorf("mint parameter GoalBonded must be <= 1, is %s", params.GoalBonded.String()) + } + if params.InflationMax.LT(params.InflationMin) { + return fmt.Errorf("mint parameter Max inflation must be greater than or equal to min inflation") + } + if params.MintDenom == "" { + return fmt.Errorf("mint parameter MintDenom can't be an empty string") + } + return nil +} diff --git a/x/mock/app.go b/x/mock/app.go index 1fe411fbc555..0b7b0ae16bf0 100644 --- a/x/mock/app.go +++ b/x/mock/app.go @@ -1,9 +1,11 @@ package mock import ( + "bytes" "fmt" "math/rand" "os" + "sort" bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" @@ -29,7 +31,7 @@ type App struct { KeyAccount *sdk.KVStoreKey // TODO: Abstract this out from not needing to be auth specifically - AccountMapper auth.AccountMapper + AccountKeeper auth.AccountKeeper FeeCollectionKeeper auth.FeeCollectionKeeper GenesisAccounts []auth.Account @@ -57,8 +59,8 @@ func NewApp() *App { TotalCoinsSupply: sdk.Coins{}, } - // Define the accountMapper - app.AccountMapper = auth.NewAccountMapper( + // Define the accountKeeper + app.AccountKeeper = auth.NewAccountKeeper( app.Cdc, app.KeyAccount, auth.ProtoBaseAccount, @@ -67,7 +69,7 @@ func NewApp() *App { // Initialize the app. The chainers and blockers can be overwritten before // calling complete setup. app.SetInitChainer(app.InitChainer) - app.SetAnteHandler(auth.NewAnteHandler(app.AccountMapper, app.FeeCollectionKeeper)) + app.SetAnteHandler(auth.NewAnteHandler(app.AccountKeeper, app.FeeCollectionKeeper)) // Not sealing for custom extension @@ -101,31 +103,72 @@ func (app *App) CompleteSetup(newKeys ...sdk.StoreKey) error { func (app *App) InitChainer(ctx sdk.Context, _ abci.RequestInitChain) abci.ResponseInitChain { // Load the genesis accounts for _, genacc := range app.GenesisAccounts { - acc := app.AccountMapper.NewAccountWithAddress(ctx, genacc.GetAddress()) + acc := app.AccountKeeper.NewAccountWithAddress(ctx, genacc.GetAddress()) acc.SetCoins(genacc.GetCoins()) - app.AccountMapper.SetAccount(ctx, acc) + app.AccountKeeper.SetAccount(ctx, acc) } return abci.ResponseInitChain{} } +// Type that combines an Address with the privKey and pubKey to that address +type AddrKeys struct { + Address sdk.AccAddress + PubKey crypto.PubKey + PrivKey crypto.PrivKey +} + +// implement `Interface` in sort package. +type AddrKeysSlice []AddrKeys + +func (b AddrKeysSlice) Len() int { + return len(b) +} + +// Sorts lexographically by Address +func (b AddrKeysSlice) Less(i, j int) bool { + // bytes package already implements Comparable for []byte. + switch bytes.Compare(b[i].Address.Bytes(), b[j].Address.Bytes()) { + case -1: + return true + case 0, 1: + return false + default: + panic("not fail-able with `bytes.Comparable` bounded [-1, 1].") + } +} + +func (b AddrKeysSlice) Swap(i, j int) { + b[j], b[i] = b[i], b[j] +} + // CreateGenAccounts generates genesis accounts loaded with coins, and returns // their addresses, pubkeys, and privkeys. func CreateGenAccounts(numAccs int, genCoins sdk.Coins) (genAccs []auth.Account, addrs []sdk.AccAddress, pubKeys []crypto.PubKey, privKeys []crypto.PrivKey) { + addrKeysSlice := AddrKeysSlice{} + for i := 0; i < numAccs; i++ { privKey := ed25519.GenPrivKey() pubKey := privKey.PubKey() addr := sdk.AccAddress(pubKey.Address()) - genAcc := &auth.BaseAccount{ + addrKeysSlice = append(addrKeysSlice, AddrKeys{ Address: addr, - Coins: genCoins, - } + PubKey: pubKey, + PrivKey: privKey, + }) + } - genAccs = append(genAccs, genAcc) - privKeys = append(privKeys, privKey) - pubKeys = append(pubKeys, pubKey) - addrs = append(addrs, addr) + sort.Sort(addrKeysSlice) + + for i := range addrKeysSlice { + addrs = append(addrs, addrKeysSlice[i].Address) + pubKeys = append(pubKeys, addrKeysSlice[i].PubKey) + privKeys = append(privKeys, addrKeysSlice[i].PrivKey) + genAccs = append(genAccs, &auth.BaseAccount{ + Address: addrKeysSlice[i].Address, + Coins: genCoins, + }) } return @@ -247,8 +290,8 @@ func RandomSetGenesis(r *rand.Rand, app *App, addrs []sdk.AccAddress, denoms []s app.GenesisAccounts = accts } -// GetAllAccounts returns all accounts in the accountMapper. -func GetAllAccounts(mapper auth.AccountMapper, ctx sdk.Context) []auth.Account { +// GetAllAccounts returns all accounts in the accountKeeper. +func GetAllAccounts(mapper auth.AccountKeeper, ctx sdk.Context) []auth.Account { accounts := []auth.Account{} appendAccount := func(acc auth.Account) (stop bool) { accounts = append(accounts, acc) diff --git a/x/mock/app_test.go b/x/mock/app_test.go index d48a6ba14dc1..9e12cac8ecc4 100644 --- a/x/mock/app_test.go +++ b/x/mock/app_test.go @@ -9,7 +9,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" ) -const msgType = "testMsg" +const msgRoute = "testMsg" var ( numAccts = 2 @@ -23,8 +23,8 @@ type testMsg struct { positiveNum int64 } -func (tx testMsg) Type() string { return msgType } -func (tx testMsg) Name() string { return "test" } +func (tx testMsg) Route() string { return msgRoute } +func (tx testMsg) Type() string { return "test" } func (tx testMsg) GetMsg() sdk.Msg { return tx } func (tx testMsg) GetMemo() string { return "" } func (tx testMsg) GetSignBytes() []byte { return nil } @@ -41,7 +41,7 @@ func (tx testMsg) ValidateBasic() sdk.Error { func getMockApp(t *testing.T) *App { mApp := NewApp() - mApp.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) + mApp.Router().AddRoute(msgRoute, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) require.NoError(t, mApp.CompleteSetup()) return mApp @@ -56,7 +56,7 @@ func TestCheckAndDeliverGenTx(t *testing.T) { msg := testMsg{signers: []sdk.AccAddress{addrs[0]}, positiveNum: 1} - acct := mApp.AccountMapper.GetAccount(ctxCheck, addrs[0]) + acct := mApp.AccountKeeper.GetAccount(ctxCheck, addrs[0]) require.Equal(t, accs[0], acct.(*auth.BaseAccount)) SignCheckDeliver( diff --git a/x/mock/simulation/account.go b/x/mock/simulation/account.go new file mode 100644 index 000000000000..37dfbb2cdb1a --- /dev/null +++ b/x/mock/simulation/account.go @@ -0,0 +1,51 @@ +package simulation + +import ( + "math/rand" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Account contains a privkey, pubkey, address tuple +// eventually more useful data can be placed in here. +// (e.g. number of coins) +type Account struct { + PrivKey crypto.PrivKey + PubKey crypto.PubKey + Address sdk.AccAddress +} + +// are two accounts equal +func (acc Account) Equals(acc2 Account) bool { + return acc.Address.Equals(acc2.Address) +} + +// RandomAcc pick a random account from an array +func RandomAcc(r *rand.Rand, accs []Account) Account { + return accs[r.Intn( + len(accs), + )] +} + +// RandomAccounts generates n random accounts +func RandomAccounts(r *rand.Rand, n int) []Account { + accs := make([]Account, n) + for i := 0; i < n; i++ { + // don't need that much entropy for simulation + privkeySeed := make([]byte, 15) + r.Read(privkeySeed) + useSecp := r.Int63()%2 == 0 + if useSecp { + accs[i].PrivKey = secp256k1.GenPrivKeySecp256k1(privkeySeed) + } 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/constants.go b/x/mock/simulation/constants.go deleted file mode 100644 index a96d4541f186..000000000000 --- a/x/mock/simulation/constants.go +++ /dev/null @@ -1,31 +0,0 @@ -package simulation - -const ( - // Fraction of double-signing evidence from a past height - pastEvidenceFraction float64 = 0.5 - - // Minimum time per block - minTimePerBlock int64 = 1000 / 2 - - // Maximum time per block - maxTimePerBlock int64 = 1000 - - // Number of keys - numKeys int = 250 - - // Chance that double-signing evidence is found on a given block - evidenceFraction float64 = 0.5 - - // TODO Remove in favor of binary search for invariant violation - onOperation bool = false -) - -var ( - // Currently there are 3 different liveness types, fully online, spotty connection, offline. - initialLivenessWeightings = []int{40, 5, 5} - livenessTransitionMatrix, _ = CreateTransitionMatrix([][]int{ - {90, 20, 1}, - {10, 50, 5}, - {0, 10, 1000}, - }) -) diff --git a/x/mock/simulation/doc.go b/x/mock/simulation/doc.go index 8b9a3f6932de..ff292bd388fc 100644 --- a/x/mock/simulation/doc.go +++ b/x/mock/simulation/doc.go @@ -2,26 +2,24 @@ Package simulation implements a simulation framework for any state machine built on the SDK which utilizes auth. -It is primarily intended for fuzz testing the integration of modules. -It will test that the provided operations are interoperable, -and that the desired invariants hold. -It can additionally be used to detect what the performance benchmarks in the -system are, by using benchmarking mode and cpu / mem profiling. -If it detects a failure, it provides the entire log of what was ran, +It is primarily intended for fuzz testing the integration of modules. It will +test that the provided operations are interoperable, and that the desired +invariants hold. It can additionally be used to detect what the performance +benchmarks in the system are, by using benchmarking mode and cpu / mem +profiling. If it detects a failure, it provides the entire log of what was ran. -The simulator takes as input: a random seed, the set of operations to run, -the invariants to test, and additional parameters to configure how long to run, -and misc. parameters that affect simulation speed. +The simulator takes as input: a random seed, the set of operations to run, the +invariants to test, and additional parameters to configure how long to run, and +misc. parameters that affect simulation speed. -It is intended that every module provides a list of Operations which will randomly -create and run a message / tx in a manner that is interesting to fuzz, and verify that -the state transition was executed as expected. -Each module should additionally provide methods to assert that the desired invariants hold. +It is intended that every module provides a list of Operations which will +randomly create and run a message / tx in a manner that is interesting to fuzz, +and verify that the state transition was executed as expected. Each module +should additionally provide methods to assert that the desired invariants hold. Then to perform a randomized simulation, select the set of desired operations, -the weightings for each, the invariants you want to test, and how long to run it for. -Then run simulation.Simulate! -The simulator will handle things like ensuring that validators periodically double signing, -or go offline. +the weightings for each, the invariants you want to test, and how long to run +it for. Then run simulation.Simulate! The simulator will handle things like +ensuring that validators periodically double signing, or go offline. */ package simulation diff --git a/x/mock/simulation/event_stats.go b/x/mock/simulation/event_stats.go new file mode 100644 index 000000000000..f54eef4cc99a --- /dev/null +++ b/x/mock/simulation/event_stats.go @@ -0,0 +1,30 @@ +package simulation + +import ( + "fmt" + "sort" +) + +type eventStats map[string]uint + +func newEventStats() eventStats { + events := make(map[string]uint) + return events +} + +func (es eventStats) tally(eventDesc string) { + es[eventDesc]++ +} + +// Pretty-print events as a table +func (es eventStats) Print() { + var keys []string + for key := range es { + keys = append(keys, key) + } + sort.Strings(keys) + fmt.Printf("Event statistics: \n") + for _, key := range keys { + fmt.Printf(" % 60s => %d\n", key, es[key]) + } +} diff --git a/x/mock/simulation/invariants.go b/x/mock/simulation/invariants.go new file mode 100644 index 000000000000..23686005d1cf --- /dev/null +++ b/x/mock/simulation/invariants.go @@ -0,0 +1,30 @@ +package simulation + +import ( + "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/baseapp" +) + +// An Invariant is a function which tests a particular invariant. +// If the invariant has been broken, it should return an error +// containing a descriptive message about what happened. +// The simulator will then halt and print the logs. +type Invariant func(app *baseapp.BaseApp) error + +// group of Invarient +type Invariants []Invariant + +// assertAll asserts the all invariants against application state +func (invs Invariants) assertAll(t *testing.T, app *baseapp.BaseApp, + event string, displayLogs func()) { + + for i := 0; i < len(invs); i++ { + if err := invs[i](app); err != nil { + fmt.Printf("Invariants broken after %s\n%s\n", event, err.Error()) + displayLogs() + t.Fatal() + } + } +} diff --git a/x/mock/simulation/mock_tendermint.go b/x/mock/simulation/mock_tendermint.go new file mode 100644 index 000000000000..2ddac2e79421 --- /dev/null +++ b/x/mock/simulation/mock_tendermint.go @@ -0,0 +1,201 @@ +package simulation + +import ( + "fmt" + "math/rand" + "sort" + "testing" + "time" + + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + tmtypes "github.com/tendermint/tendermint/types" +) + +type mockValidator struct { + val abci.ValidatorUpdate + livenessState int +} + +type mockValidators map[string]mockValidator + +// get mockValidators from abci validators +func newMockValidators(r *rand.Rand, abciVals []abci.ValidatorUpdate, + params Params) mockValidators { + + validators := make(mockValidators) + for _, validator := range abciVals { + str := fmt.Sprintf("%v", validator.PubKey) + liveliness := GetMemberOfInitialState(r, + params.InitialLivenessWeightings) + + validators[str] = mockValidator{ + val: validator, + livenessState: liveliness, + } + } + + return validators +} + +// TODO describe usage +func (vals mockValidators) getKeys() []string { + keys := make([]string, len(vals)) + i := 0 + for key := range vals { + keys[i] = key + i++ + } + sort.Strings(keys) + return keys +} + +//_________________________________________________________________________________ + +// randomProposer picks a random proposer from the current validator set +func (vals mockValidators) randomProposer(r *rand.Rand) cmn.HexBytes { + keys := vals.getKeys() + if len(keys) == 0 { + return nil + } + key := keys[r.Intn(len(keys))] + proposer := vals[key].val + pk, err := tmtypes.PB2TM.PubKey(proposer.PubKey) + if err != nil { + panic(err) + } + return pk.Address() +} + +// updateValidators mimicks Tendermint's update logic +// nolint: unparam +func updateValidators(tb testing.TB, r *rand.Rand, params Params, + current map[string]mockValidator, updates []abci.ValidatorUpdate, + event func(string)) map[string]mockValidator { + + for _, update := range updates { + str := fmt.Sprintf("%v", update.PubKey) + + if update.Power == 0 { + if _, ok := current[str]; !ok { + tb.Fatalf("tried to delete a nonexistent validator") + } + event("endblock/validatorupdates/kicked") + delete(current, str) + + } else if mVal, ok := current[str]; ok { + // validator already exists + mVal.val = update + event("endblock/validatorupdates/updated") + + } else { + // Set this new validator + current[str] = mockValidator{ + update, + GetMemberOfInitialState(r, params.InitialLivenessWeightings), + } + event("endblock/validatorupdates/added") + } + } + + return current +} + +// RandomRequestBeginBlock generates a list of signing validators according to +// the provided list of validators, signing fraction, and evidence fraction +func RandomRequestBeginBlock(r *rand.Rand, params Params, + validators mockValidators, pastTimes []time.Time, + pastVoteInfos [][]abci.VoteInfo, + event func(string), header abci.Header) abci.RequestBeginBlock { + + if len(validators) == 0 { + return abci.RequestBeginBlock{ + Header: header, + } + } + + voteInfos := make([]abci.VoteInfo, len(validators)) + for i, key := range validators.getKeys() { + mVal := validators[key] + mVal.livenessState = params.LivenessTransitionMatrix.NextState(r, mVal.livenessState) + signed := true + + if mVal.livenessState == 1 { + // spotty connection, 50% probability of success + // See https://github.com/golang/go/issues/23804#issuecomment-365370418 + // for reasoning behind computing like this + signed = r.Int63()%2 == 0 + } else if mVal.livenessState == 2 { + // offline + signed = false + } + + if signed { + event("beginblock/signing/signed") + } else { + event("beginblock/signing/missed") + } + + pubkey, err := tmtypes.PB2TM.PubKey(mVal.val.PubKey) + if err != nil { + panic(err) + } + voteInfos[i] = abci.VoteInfo{ + Validator: abci.Validator{ + Address: pubkey.Address(), + Power: mVal.val.Power, + }, + SignedLastBlock: signed, + } + } + + // return if no past times + if len(pastTimes) <= 0 { + return abci.RequestBeginBlock{ + Header: header, + LastCommitInfo: abci.LastCommitInfo{ + Votes: voteInfos, + }, + } + } + + // TODO: Determine capacity before allocation + evidence := make([]abci.Evidence, 0) + for r.Float64() < params.EvidenceFraction { + + height := header.Height + time := header.Time + vals := voteInfos + + if r.Float64() < params.PastEvidenceFraction { + height = int64(r.Intn(int(header.Height) - 1)) + time = pastTimes[height] + vals = pastVoteInfos[height] + } + validator := vals[r.Intn(len(vals))].Validator + + var totalVotingPower int64 + for _, val := range vals { + totalVotingPower += val.Validator.Power + } + + evidence = append(evidence, + abci.Evidence{ + Type: tmtypes.ABCIEvidenceTypeDuplicateVote, + Validator: validator, + Height: height, + Time: time, + TotalVotingPower: totalVotingPower, + }, + ) + event("beginblock/evidence") + } + + return abci.RequestBeginBlock{ + Header: header, + LastCommitInfo: abci.LastCommitInfo{ + Votes: voteInfos, + }, + ByzantineValidators: evidence, + } +} diff --git a/x/mock/simulation/operation.go b/x/mock/simulation/operation.go new file mode 100644 index 000000000000..00237e03ee31 --- /dev/null +++ b/x/mock/simulation/operation.go @@ -0,0 +1,112 @@ +package simulation + +import ( + "math/rand" + "sort" + "time" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Operation runs a state machine transition, and ensures the transition +// happened as expected. The operation could be running and testing a fuzzed +// transaction, or doing the same for a message. +// +// For ease of debugging, an operation returns a descriptive message "action", +// which details what this fuzzed state machine transition actually did. +// +// Operations can optionally provide a list of "FutureOperations" to run later +// These will be ran at the beginning of the corresponding block. +type Operation func(r *rand.Rand, app *baseapp.BaseApp, + ctx sdk.Context, accounts []Account, event func(string)) ( + action string, futureOps []FutureOperation, err error) + +// queue of operations +type OperationQueue map[int][]Operation + +func newOperationQueue() OperationQueue { + operationQueue := make(OperationQueue) + return operationQueue +} + +// adds all future operations into the operation queue. +func queueOperations(queuedOps OperationQueue, + queuedTimeOps []FutureOperation, futureOps []FutureOperation) { + + if futureOps == nil { + return + } + + for _, futureOp := range futureOps { + if futureOp.BlockHeight != 0 { + if val, ok := queuedOps[futureOp.BlockHeight]; ok { + queuedOps[futureOp.BlockHeight] = append(val, futureOp.Op) + } else { + queuedOps[futureOp.BlockHeight] = []Operation{futureOp.Op} + } + continue + } + + // TODO: Replace with proper sorted data structure, so don't have the + // copy entire slice + index := sort.Search( + len(queuedTimeOps), + func(i int) bool { + return queuedTimeOps[i].BlockTime.After(futureOp.BlockTime) + }, + ) + queuedTimeOps = append(queuedTimeOps, FutureOperation{}) + copy(queuedTimeOps[index+1:], queuedTimeOps[index:]) + queuedTimeOps[index] = futureOp + } +} + +//________________________________________________________________________ + +// FutureOperation is an operation which will be ran at the beginning of the +// provided BlockHeight. If both a BlockHeight and BlockTime are specified, it +// will use the BlockHeight. In the (likely) event that multiple operations +// are queued at the same block height, they will execute in a FIFO pattern. +type FutureOperation struct { + BlockHeight int + BlockTime time.Time + Op Operation +} + +//________________________________________________________________________ + +// WeightedOperation is an operation with associated weight. +// This is used to bias the selection operation within the simulator. +type WeightedOperation struct { + Weight int + Op Operation +} + +// WeightedOperations is the group of all weighted operations to simulate. +type WeightedOperations []WeightedOperation + +func (ops WeightedOperations) totalWeight() int { + totalOpWeight := 0 + for _, op := range ops { + totalOpWeight += op.Weight + } + return totalOpWeight +} + +type selectOpFn func(r *rand.Rand) Operation + +func (ops WeightedOperations) getSelectOpFn() selectOpFn { + totalOpWeight := ops.totalWeight() + return func(r *rand.Rand) Operation { + x := r.Intn(totalOpWeight) + for i := 0; i < len(ops); i++ { + if x <= ops[i].Weight { + return ops[i].Op + } + x -= ops[i].Weight + } + // shouldn't happen + return ops[0].Op + } +} diff --git a/x/mock/simulation/params.go b/x/mock/simulation/params.go new file mode 100644 index 000000000000..8499e6c1189b --- /dev/null +++ b/x/mock/simulation/params.go @@ -0,0 +1,69 @@ +package simulation + +import ( + "math/rand" +) + +const ( + // Minimum time per block + minTimePerBlock int64 = 10000 / 2 + + // Maximum time per block + maxTimePerBlock int64 = 10000 + + // TODO Remove in favor of binary search for invariant violation + onOperation bool = false +) + +// TODO explain transitional matrix usage +var ( + // Currently there are 3 different liveness types, + // fully online, spotty connection, offline. + defaultLivenessTransitionMatrix, _ = CreateTransitionMatrix([][]int{ + {90, 20, 1}, + {10, 50, 5}, + {0, 10, 1000}, + }) + + // 3 states: rand in range [0, 4*provided blocksize], + // rand in range [0, 2 * provided blocksize], 0 + defaultBlockSizeTransitionMatrix, _ = CreateTransitionMatrix([][]int{ + {85, 5, 0}, + {15, 92, 1}, + {0, 3, 99}, + }) +) + +// Simulation parameters +type Params struct { + PastEvidenceFraction float64 + NumKeys int + EvidenceFraction float64 + InitialLivenessWeightings []int + LivenessTransitionMatrix TransitionMatrix + BlockSizeTransitionMatrix TransitionMatrix +} + +// Return default simulation parameters +func DefaultParams() Params { + return Params{ + PastEvidenceFraction: 0.5, + NumKeys: 250, + EvidenceFraction: 0.5, + InitialLivenessWeightings: []int{40, 5, 5}, + LivenessTransitionMatrix: defaultLivenessTransitionMatrix, + BlockSizeTransitionMatrix: defaultBlockSizeTransitionMatrix, + } +} + +// Return random simulation parameters +func RandomParams(r *rand.Rand) Params { + return Params{ + PastEvidenceFraction: r.Float64(), + NumKeys: r.Intn(250), + EvidenceFraction: r.Float64(), + InitialLivenessWeightings: []int{r.Intn(80), r.Intn(10), r.Intn(10)}, + LivenessTransitionMatrix: defaultLivenessTransitionMatrix, + BlockSizeTransitionMatrix: defaultBlockSizeTransitionMatrix, + } +} diff --git a/x/mock/simulation/rand_util.go b/x/mock/simulation/rand_util.go new file mode 100644 index 000000000000..c40d2a65ca1d --- /dev/null +++ b/x/mock/simulation/rand_util.go @@ -0,0 +1,64 @@ +package simulation + +import ( + "math/big" + "math/rand" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/mock" +) + +const ( + letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits + letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits +) + +// shamelessly copied from +// https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang#31832326 +// Generate a random string of a particular length +func RandStringOfLength(r *rand.Rand, n int) string { + b := make([]byte, n) + // A src.Int63() generates 63 random bits, enough for letterIdxMax characters! + for i, cache, remain := n-1, r.Int63(), letterIdxMax; i >= 0; { + if remain == 0 { + cache, remain = r.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + return string(b) +} + +// Generate a random amount +func RandomAmount(r *rand.Rand, max sdk.Int) sdk.Int { + return sdk.NewInt(int64(r.Intn(int(max.Int64())))) +} + +// RandomDecAmount generates a random decimal amount +func RandomDecAmount(r *rand.Rand, max sdk.Dec) sdk.Dec { + randInt := big.NewInt(0).Rand(r, max.Int) + return sdk.NewDecFromBigIntWithPrec(randInt, sdk.Precision) +} + +// RandomSetGenesis wraps mock.RandomSetGenesis, but using simulation accounts +func RandomSetGenesis(r *rand.Rand, app *mock.App, accs []Account, denoms []string) { + addrs := make([]sdk.AccAddress, len(accs)) + for i := 0; i < len(accs); i++ { + addrs[i] = accs[i].Address + } + mock.RandomSetGenesis(r, app, addrs, denoms) +} + +// RandTimestamp generates a random timestamp +func RandTimestamp(r *rand.Rand) time.Time { + // json.Marshal breaks for timestamps greater with year greater than 9999 + unixTime := r.Int63n(253373529600) + return time.Unix(unixTime, 0) +} diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go deleted file mode 100644 index 0fc9c21a952d..000000000000 --- a/x/mock/simulation/random_simulate_blocks.go +++ /dev/null @@ -1,426 +0,0 @@ -package simulation - -import ( - "encoding/json" - "fmt" - "math" - "math/rand" - "os" - "os/signal" - "runtime/debug" - "sort" - "strings" - "syscall" - "testing" - "time" - - abci "github.com/tendermint/tendermint/abci/types" - tmtypes "github.com/tendermint/tendermint/types" - - "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Simulate tests application by sending random messages. -func Simulate(t *testing.T, app *baseapp.BaseApp, - appStateFn func(r *rand.Rand, accs []Account) json.RawMessage, - ops []WeightedOperation, setups []RandSetup, - invariants []Invariant, numBlocks int, blockSize int, commit bool) error { - - time := time.Now().UnixNano() - return SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numBlocks, blockSize, commit) -} - -func initChain(r *rand.Rand, accounts []Account, setups []RandSetup, app *baseapp.BaseApp, - appStateFn func(r *rand.Rand, accounts []Account) json.RawMessage) (validators map[string]mockValidator) { - res := app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, accounts)}) - validators = make(map[string]mockValidator) - for _, validator := range res.Validators { - str := fmt.Sprintf("%v", validator.PubKey) - validators[str] = mockValidator{validator, GetMemberOfInitialState(r, initialLivenessWeightings)} - } - - for i := 0; i < len(setups); i++ { - setups[i](r, accounts) - } - - return -} - -func randTimestamp(r *rand.Rand) time.Time { - unixTime := r.Int63n(int64(math.Pow(2, 40))) - return time.Unix(unixTime, 0) -} - -// SimulateFromSeed tests an application by running the provided -// operations, testing the provided invariants, but using the provided seed. -func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, - appStateFn func(r *rand.Rand, accs []Account) json.RawMessage, - seed int64, ops []WeightedOperation, setups []RandSetup, invariants []Invariant, - numBlocks int, blockSize int, commit bool) (simError error) { - - // in case we have to end early, don't os.Exit so that we can run cleanup code. - stopEarly := false - testingMode, t, b := getTestingMode(tb) - fmt.Printf("Starting SimulateFromSeed with randomness created with seed %d\n", int(seed)) - r := rand.New(rand.NewSource(seed)) - timestamp := randTimestamp(r) - fmt.Printf("Starting the simulation from time %v, unixtime %v\n", timestamp.UTC().Format(time.UnixDate), timestamp.Unix()) - timeDiff := maxTimePerBlock - minTimePerBlock - - accs := RandomAccounts(r, numKeys) - - // Setup event stats - events := make(map[string]uint) - event := func(what string) { - events[what]++ - } - - validators := initChain(r, accs, setups, app, appStateFn) - // Second variable to keep pending validator set (delayed one block since TM 0.24) - // Initially this is the same as the initial validator set - nextValidators := validators - - header := abci.Header{Height: 0, Time: timestamp} - opCount := 0 - - // Setup code to catch SIGTERM's - c := make(chan os.Signal) - signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) - go func() { - receivedSignal := <-c - fmt.Printf("Exiting early due to %s, on block %d, operation %d\n", receivedSignal, header.Height, opCount) - simError = fmt.Errorf("Exited due to %s", receivedSignal) - stopEarly = true - }() - - var pastTimes []time.Time - var pastVoteInfos [][]abci.VoteInfo - - request := RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastVoteInfos, event, header) - // These are operations which have been queued by previous operations - operationQueue := make(map[int][]Operation) - timeOperationQueue := []FutureOperation{} - var blockLogBuilders []*strings.Builder - - if testingMode { - blockLogBuilders = make([]*strings.Builder, numBlocks) - } - displayLogs := logPrinter(testingMode, blockLogBuilders) - blockSimulator := createBlockSimulator(testingMode, tb, t, event, invariants, ops, operationQueue, timeOperationQueue, numBlocks, displayLogs) - if !testingMode { - b.ResetTimer() - } else { - // Recover logs in case of panic - defer func() { - if r := recover(); r != nil { - fmt.Println("Panic with err\n", r) - stackTrace := string(debug.Stack()) - fmt.Println(stackTrace) - displayLogs() - simError = fmt.Errorf("Simulation halted due to panic on block %d", header.Height) - } - }() - } - - for i := 0; i < numBlocks && !stopEarly; i++ { - // Log the header time for future lookup - pastTimes = append(pastTimes, header.Time) - pastVoteInfos = append(pastVoteInfos, request.LastCommitInfo.Votes) - - // Run the BeginBlock handler - app.BeginBlock(request) - - if testingMode { - // Make sure invariants hold at beginning of block - assertAllInvariants(t, app, invariants, displayLogs) - } - logWriter := addLogMessage(testingMode, blockLogBuilders, i) - - ctx := app.NewContext(false, header) - thisBlockSize := getBlockSize(r, blockSize) - - // Run queued operations. Ignores blocksize if blocksize is too small - numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), tb, r, app, ctx, accs, logWriter, displayLogs, event) - numQueuedTimeOpsRan := runQueuedTimeOperations(timeOperationQueue, header.Time, tb, r, app, ctx, accs, logWriter, displayLogs, event) - thisBlockSize = thisBlockSize - numQueuedOpsRan - numQueuedTimeOpsRan - operations := blockSimulator(thisBlockSize, r, app, ctx, accs, header, logWriter) - opCount += operations + numQueuedOpsRan + numQueuedTimeOpsRan - - res := app.EndBlock(abci.RequestEndBlock{}) - header.Height++ - header.Time = header.Time.Add(time.Duration(minTimePerBlock) * time.Second).Add(time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second) - logWriter("EndBlock") - - if testingMode { - // Make sure invariants hold at end of block - assertAllInvariants(t, app, invariants, displayLogs) - } - if commit { - app.Commit() - } - - // Generate a random RequestBeginBlock with the current validator set for the next block - request = RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastVoteInfos, event, header) - - // Update the validator set, which will be reflected in the application on the next block - validators = nextValidators - nextValidators = updateValidators(tb, r, validators, res.ValidatorUpdates, event) - } - if stopEarly { - DisplayEvents(events) - return - } - fmt.Printf("\nSimulation complete. Final height (blocks): %d, final time (seconds), : %v, operations ran %d\n", header.Height, header.Time, opCount) - DisplayEvents(events) - return nil -} - -// Returns a function to simulate blocks. Written like this to avoid constant parameters being passed everytime, to minimize -// memory overhead -func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, event func(string), invariants []Invariant, ops []WeightedOperation, operationQueue map[int][]Operation, timeOperationQueue []FutureOperation, totalNumBlocks int, displayLogs func()) func( - blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []Account, header abci.Header, logWriter func(string)) (opCount int) { - totalOpWeight := 0 - for i := 0; i < len(ops); i++ { - totalOpWeight += ops[i].Weight - } - selectOp := func(r *rand.Rand) Operation { - x := r.Intn(totalOpWeight) - for i := 0; i < len(ops); i++ { - if x <= ops[i].Weight { - return ops[i].Op - } - x -= ops[i].Weight - } - // shouldn't happen - return ops[0].Op - } - return func(blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - accounts []Account, header abci.Header, logWriter func(string)) (opCount int) { - for j := 0; j < blocksize; j++ { - logUpdate, futureOps, err := selectOp(r)(r, app, ctx, accounts, event) - if err != nil { - displayLogs() - tb.Fatalf("error on operation %d within block %d, %v", header.Height, opCount, err) - } - logWriter(logUpdate) - - queueOperations(operationQueue, timeOperationQueue, futureOps) - if testingMode { - if onOperation { - assertAllInvariants(t, app, invariants, displayLogs) - } - if opCount%50 == 0 { - fmt.Printf("\rSimulating... block %d/%d, operation %d/%d. ", header.Height, totalNumBlocks, opCount, blocksize) - } - } - opCount++ - } - return opCount - } -} - -func getTestingMode(tb testing.TB) (testingMode bool, t *testing.T, b *testing.B) { - testingMode = false - if _t, ok := tb.(*testing.T); ok { - t = _t - testingMode = true - } else { - b = tb.(*testing.B) - } - return -} - -func getBlockSize(r *rand.Rand, blockSize int) int { - load := r.Float64() - switch { - case load < 0.33: - return 0 - case load < 0.66: - return r.Intn(blockSize * 2) - default: - return r.Intn(blockSize * 4) - } -} - -// adds all future operations into the operation queue. -func queueOperations(queuedOperations map[int][]Operation, queuedTimeOperations []FutureOperation, futureOperations []FutureOperation) { - if futureOperations == nil { - return - } - for _, futureOp := range futureOperations { - if futureOp.BlockHeight != 0 { - if val, ok := queuedOperations[futureOp.BlockHeight]; ok { - queuedOperations[futureOp.BlockHeight] = append(val, futureOp.Op) - } else { - queuedOperations[futureOp.BlockHeight] = []Operation{futureOp.Op} - } - } else { - // TODO: Replace with proper sorted data structure, so don't have the copy entire slice - index := sort.Search(len(queuedTimeOperations), func(i int) bool { return queuedTimeOperations[i].BlockTime.After(futureOp.BlockTime) }) - queuedTimeOperations = append(queuedTimeOperations, FutureOperation{}) - copy(queuedTimeOperations[index+1:], queuedTimeOperations[index:]) - queuedTimeOperations[index] = futureOp - } - } -} - -// nolint: errcheck -func runQueuedOperations(queueOperations map[int][]Operation, height int, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - accounts []Account, logWriter func(string), displayLogs func(), event func(string)) (numOpsRan int) { - if queuedOps, ok := queueOperations[height]; ok { - numOps := len(queuedOps) - for i := 0; i < numOps; i++ { - // For now, queued operations cannot queue more operations. - // If a need arises for us to support queued messages to queue more messages, this can - // be changed. - logUpdate, _, err := queuedOps[i](r, app, ctx, accounts, event) - logWriter(logUpdate) - if err != nil { - displayLogs() - tb.FailNow() - } - } - delete(queueOperations, height) - return numOps - } - return 0 -} - -func runQueuedTimeOperations(queueOperations []FutureOperation, currentTime time.Time, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - accounts []Account, logWriter func(string), displayLogs func(), event func(string)) (numOpsRan int) { - - numOpsRan = 0 - for len(queueOperations) > 0 && currentTime.After(queueOperations[0].BlockTime) { - // For now, queued operations cannot queue more operations. - // If a need arises for us to support queued messages to queue more messages, this can - // be changed. - logUpdate, _, err := queueOperations[0].Op(r, app, ctx, accounts, event) - logWriter(logUpdate) - if err != nil { - displayLogs() - tb.FailNow() - } - queueOperations = queueOperations[1:] - numOpsRan++ - } - return numOpsRan -} - -func getKeys(validators map[string]mockValidator) []string { - keys := make([]string, len(validators)) - i := 0 - for key := range validators { - keys[i] = key - i++ - } - sort.Strings(keys) - return keys -} - -// RandomRequestBeginBlock generates a list of signing validators according to the provided list of validators, signing fraction, and evidence fraction -// nolint: unparam -func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator, livenessTransitions TransitionMatrix, evidenceFraction float64, - pastTimes []time.Time, pastVoteInfos [][]abci.VoteInfo, event func(string), header abci.Header) abci.RequestBeginBlock { - if len(validators) == 0 { - return abci.RequestBeginBlock{Header: header} - } - voteInfos := make([]abci.VoteInfo, len(validators)) - i := 0 - for _, key := range getKeys(validators) { - mVal := validators[key] - mVal.livenessState = livenessTransitions.NextState(r, mVal.livenessState) - signed := true - - if mVal.livenessState == 1 { - // spotty connection, 50% probability of success - // See https://github.com/golang/go/issues/23804#issuecomment-365370418 - // for reasoning behind computing like this - signed = r.Int63()%2 == 0 - } else if mVal.livenessState == 2 { - // offline - signed = false - } - if signed { - event("beginblock/signing/signed") - } else { - event("beginblock/signing/missed") - } - pubkey, err := tmtypes.PB2TM.PubKey(mVal.val.PubKey) - if err != nil { - panic(err) - } - voteInfos[i] = abci.VoteInfo{ - Validator: abci.Validator{ - Address: pubkey.Address(), - }, - SignedLastBlock: signed, - } - i++ - } - // TODO: Determine capacity before allocation - evidence := make([]abci.Evidence, 0) - // Anything but the first block - if len(pastTimes) > 0 { - for r.Float64() < evidenceFraction { - height := header.Height - time := header.Time - vals := voteInfos - if r.Float64() < pastEvidenceFraction { - height = int64(r.Intn(int(header.Height))) - time = pastTimes[height] - vals = pastVoteInfos[height] - } - validator := vals[r.Intn(len(vals))].Validator - var totalVotingPower int64 - for _, val := range vals { - totalVotingPower += val.Validator.Power - } - evidence = append(evidence, abci.Evidence{ - Type: tmtypes.ABCIEvidenceTypeDuplicateVote, - Validator: validator, - Height: height, - Time: time, - TotalVotingPower: totalVotingPower, - }) - event("beginblock/evidence") - } - } - return abci.RequestBeginBlock{ - Header: header, - LastCommitInfo: abci.LastCommitInfo{ - Votes: voteInfos, - }, - ByzantineValidators: evidence, - } -} - -// updateValidators mimicks Tendermint's update logic -// nolint: unparam -func updateValidators(tb testing.TB, r *rand.Rand, current map[string]mockValidator, updates []abci.ValidatorUpdate, event func(string)) map[string]mockValidator { - - for _, update := range updates { - str := fmt.Sprintf("%v", update.PubKey) - switch { - case update.Power == 0: - if _, ok := current[str]; !ok { - tb.Fatalf("tried to delete a nonexistent validator") - } - - event("endblock/validatorupdates/kicked") - delete(current, str) - default: - // Does validator already exist? - if mVal, ok := current[str]; ok { - mVal.val = update - event("endblock/validatorupdates/updated") - } else { - // Set this new validator - current[str] = mockValidator{update, GetMemberOfInitialState(r, initialLivenessWeightings)} - event("endblock/validatorupdates/added") - } - } - } - - return current -} diff --git a/x/mock/simulation/simulate.go b/x/mock/simulation/simulate.go new file mode 100644 index 000000000000..ac0e5d3f7f11 --- /dev/null +++ b/x/mock/simulation/simulate.go @@ -0,0 +1,330 @@ +package simulation + +import ( + "encoding/json" + "fmt" + "math/rand" + "os" + "os/signal" + "runtime/debug" + "strings" + "syscall" + "testing" + "time" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// RandSetup performs the random setup the mock module needs. +type RandSetup func(r *rand.Rand, accounts []Account) + +// AppStateFn returns the app state json bytes +type AppStateFn func(r *rand.Rand, accs []Account) json.RawMessage + +// Simulate tests application by sending random messages. +func Simulate(t *testing.T, app *baseapp.BaseApp, + appStateFn AppStateFn, ops WeightedOperations, setups []RandSetup, + invariants Invariants, numBlocks int, blockSize int, commit bool) error { + + time := time.Now().UnixNano() + return SimulateFromSeed(t, app, appStateFn, time, ops, + setups, invariants, numBlocks, blockSize, commit) +} + +// initialize the chain for the simulation +func initChain(r *rand.Rand, params Params, accounts []Account, + setups []RandSetup, app *baseapp.BaseApp, + appStateFn AppStateFn) mockValidators { + + req := abci.RequestInitChain{ + AppStateBytes: appStateFn(r, accounts), + } + res := app.InitChain(req) + validators := newMockValidators(r, res.Validators, params) + + for i := 0; i < len(setups); i++ { + setups[i](r, accounts) + } + return validators +} + +// SimulateFromSeed tests an application by running the provided +// operations, testing the provided invariants, but using the provided seed. +// TODO split this monster function up +func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, + appStateFn AppStateFn, seed int64, ops WeightedOperations, + setups []RandSetup, invariants Invariants, + numBlocks int, blockSize int, commit bool) (simError error) { + + // in case we have to end early, don't os.Exit so that we can run cleanup code. + stopEarly := false + testingMode, t, b := getTestingMode(tb) + fmt.Printf("Starting SimulateFromSeed with randomness "+ + "created with seed %d\n", int(seed)) + + r := rand.New(rand.NewSource(seed)) + params := RandomParams(r) // := DefaultParams() + fmt.Printf("Randomized simulation params: %+v\n", params) + + timestamp := RandTimestamp(r) + fmt.Printf("Starting the simulation from time %v, unixtime %v\n", + timestamp.UTC().Format(time.UnixDate), timestamp.Unix()) + + timeDiff := maxTimePerBlock - minTimePerBlock + accs := RandomAccounts(r, params.NumKeys) + eventStats := newEventStats() + + // 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, setups, app, appStateFn) + nextValidators := validators + + header := abci.Header{ + Height: 1, + Time: timestamp, + ProposerAddress: validators.randomProposer(r), + } + opCount := 0 + + // Setup code to catch SIGTERM's + c := make(chan os.Signal) + signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) + go func() { + receivedSignal := <-c + fmt.Printf("\nExiting early due to %s, on block %d, operation %d\n", + receivedSignal, header.Height, opCount) + simError = fmt.Errorf("Exited due to %s", receivedSignal) + stopEarly = true + }() + + var pastTimes []time.Time + var pastVoteInfos [][]abci.VoteInfo + + request := RandomRequestBeginBlock(r, params, + validators, pastTimes, pastVoteInfos, eventStats.tally, header) + + // These are operations which have been queued by previous operations + operationQueue := newOperationQueue() + timeOperationQueue := []FutureOperation{} + var blockLogBuilders []*strings.Builder + + if testingMode { + blockLogBuilders = make([]*strings.Builder, numBlocks) + } + displayLogs := logPrinter(testingMode, blockLogBuilders) + blockSimulator := createBlockSimulator( + testingMode, tb, t, params, eventStats.tally, invariants, + ops, operationQueue, timeOperationQueue, + numBlocks, blockSize, displayLogs) + + if !testingMode { + b.ResetTimer() + } else { + // Recover logs in case of panic + defer func() { + if r := recover(); r != nil { + fmt.Println("Panic with err\n", r) + stackTrace := string(debug.Stack()) + fmt.Println(stackTrace) + displayLogs() + simError = fmt.Errorf( + "Simulation halted due to panic on block %d", + header.Height) + } + }() + } + + // TODO split up the contents of this for loop into new functions + for i := 0; i < numBlocks && !stopEarly; i++ { + + // Log the header time for future lookup + pastTimes = append(pastTimes, header.Time) + pastVoteInfos = append(pastVoteInfos, request.LastCommitInfo.Votes) + + // Construct log writer + logWriter := addLogMessage(testingMode, blockLogBuilders, i) + + // Run the BeginBlock handler + logWriter("BeginBlock") + app.BeginBlock(request) + + if testingMode { + invariants.assertAll(t, app, "BeginBlock", displayLogs) + } + + ctx := app.NewContext(false, header) + + // Run queued operations. Ignores blocksize if blocksize is too small + logWriter("Queued operations") + numQueuedOpsRan := runQueuedOperations( + operationQueue, int(header.Height), + tb, r, app, ctx, accs, logWriter, + displayLogs, eventStats.tally) + + numQueuedTimeOpsRan := runQueuedTimeOperations( + timeOperationQueue, header.Time, + tb, r, app, ctx, accs, + logWriter, displayLogs, eventStats.tally) + + if testingMode && onOperation { + invariants.assertAll(t, app, "QueuedOperations", displayLogs) + } + + logWriter("Standard operations") + operations := blockSimulator(r, app, ctx, accs, header, logWriter) + opCount += operations + numQueuedOpsRan + numQueuedTimeOpsRan + if testingMode { + invariants.assertAll(t, app, "StandardOperations", displayLogs) + } + + res := app.EndBlock(abci.RequestEndBlock{}) + header.Height++ + header.Time = header.Time.Add( + time.Duration(minTimePerBlock) * time.Second) + header.Time = header.Time.Add( + time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second) + header.ProposerAddress = validators.randomProposer(r) + logWriter("EndBlock") + + if testingMode { + invariants.assertAll(t, app, "EndBlock", displayLogs) + } + if commit { + app.Commit() + } + + if header.ProposerAddress == nil { + fmt.Printf("\nSimulation stopped early as all validators " + + "have been unbonded, there is nobody left propose a block!\n") + stopEarly = true + break + } + + // Generate a random RequestBeginBlock with the current validator set + // for the next block + request = RandomRequestBeginBlock(r, params, validators, + pastTimes, pastVoteInfos, eventStats.tally, header) + + // Update the validator set, which will be reflected in the application + // on the next block + validators = nextValidators + nextValidators = updateValidators(tb, r, params, + validators, res.ValidatorUpdates, eventStats.tally) + } + + if stopEarly { + eventStats.Print() + return simError + } + fmt.Printf("\nSimulation complete. Final height (blocks): %d, "+ + "final time (seconds), : %v, operations ran %d\n", + header.Height, header.Time, opCount) + + eventStats.Print() + return nil +} + +//______________________________________________________________________________ + +type blockSimFn func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accounts []Account, header abci.Header, logWriter func(string)) (opCount int) + +// Returns a function to simulate blocks. Written like this to avoid constant +// parameters being passed everytime, to minimize memory overhead. +func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, params Params, + event func(string), invariants Invariants, ops WeightedOperations, + operationQueue OperationQueue, timeOperationQueue []FutureOperation, + totalNumBlocks int, avgBlockSize int, displayLogs func()) blockSimFn { + + var lastBlocksizeState = 0 // state for [4 * uniform distribution] + var blocksize int + selectOp := ops.getSelectOpFn() + + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accounts []Account, header abci.Header, logWriter func(string)) (opCount int) { + + fmt.Printf("\rSimulating... block %d/%d, operation %d/%d. ", + header.Height, totalNumBlocks, opCount, blocksize) + lastBlocksizeState, blocksize = getBlockSize(r, params, lastBlocksizeState, avgBlockSize) + + for i := 0; i < blocksize; i++ { + + logUpdate, futureOps, err := selectOp(r)(r, app, ctx, accounts, event) + logWriter(logUpdate) + if err != nil { + displayLogs() + tb.Fatalf("error on operation %d within block %d, %v", + header.Height, opCount, err) + } + + queueOperations(operationQueue, timeOperationQueue, futureOps) + if testingMode { + if onOperation { + eventStr := fmt.Sprintf("operation: %v", logUpdate) + invariants.assertAll(t, app, eventStr, displayLogs) + } + if opCount%50 == 0 { + fmt.Printf("\rSimulating... block %d/%d, operation %d/%d. ", + header.Height, totalNumBlocks, opCount, blocksize) + } + } + opCount++ + } + return opCount + } +} + +// nolint: errcheck +func runQueuedOperations(queueOps map[int][]Operation, + height int, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, + ctx sdk.Context, accounts []Account, logWriter func(string), + displayLogs func(), tallyEvent func(string)) (numOpsRan int) { + + queuedOp, ok := queueOps[height] + if !ok { + return 0 + } + + numOpsRan = len(queuedOp) + for i := 0; i < numOpsRan; i++ { + + // For now, queued operations cannot queue more operations. + // If a need arises for us to support queued messages to queue more messages, this can + // be changed. + logUpdate, _, err := queuedOp[i](r, app, ctx, accounts, tallyEvent) + logWriter(logUpdate) + if err != nil { + displayLogs() + tb.FailNow() + } + } + delete(queueOps, height) + return numOpsRan +} + +func runQueuedTimeOperations(queueOps []FutureOperation, + currentTime time.Time, tb testing.TB, r *rand.Rand, + app *baseapp.BaseApp, ctx sdk.Context, accounts []Account, + logWriter func(string), displayLogs func(), tallyEvent func(string)) (numOpsRan int) { + + numOpsRan = 0 + for len(queueOps) > 0 && currentTime.After(queueOps[0].BlockTime) { + + // For now, queued operations cannot queue more operations. + // If a need arises for us to support queued messages to queue more messages, this can + // be changed. + logUpdate, _, err := queueOps[0].Op(r, app, ctx, accounts, tallyEvent) + logWriter(logUpdate) + if err != nil { + displayLogs() + tb.FailNow() + } + + queueOps = queueOps[1:] + numOpsRan++ + } + return numOpsRan +} diff --git a/x/mock/simulation/transition_matrix.go b/x/mock/simulation/transition_matrix.go index 39bdb1e4f956..f7d713775d38 100644 --- a/x/mock/simulation/transition_matrix.go +++ b/x/mock/simulation/transition_matrix.go @@ -5,12 +5,11 @@ import ( "math/rand" ) -// TransitionMatrix is _almost_ a left stochastic matrix. -// It is technically not one due to not normalizing the column values. -// In the future, if we want to find the steady state distribution, -// it will be quite easy to normalize these values to get a stochastic matrix. -// Floats aren't currently used as the default due to non-determinism across -// architectures +// TransitionMatrix is _almost_ a left stochastic matrix. It is technically +// not one due to not normalizing the column values. In the future, if we want +// to find the steady state distribution, it will be quite easy to normalize +// these values to get a stochastic matrix. Floats aren't currently used as +// the default due to non-determinism across architectures type TransitionMatrix struct { weights [][]int // total in each column @@ -24,7 +23,8 @@ func CreateTransitionMatrix(weights [][]int) (TransitionMatrix, error) { n := len(weights) for i := 0; i < n; i++ { if len(weights[i]) != n { - return TransitionMatrix{}, fmt.Errorf("Transition Matrix: Non-square matrix provided, error on row %d", i) + return TransitionMatrix{}, + fmt.Errorf("Transition Matrix: Non-square matrix provided, error on row %d", i) } } totals := make([]int, n) @@ -36,8 +36,8 @@ func CreateTransitionMatrix(weights [][]int) (TransitionMatrix, error) { return TransitionMatrix{weights, totals, n}, nil } -// NextState returns the next state randomly chosen using r, and the weightings provided -// in the transition matrix. +// NextState returns the next state randomly chosen using r, and the weightings +// provided in the transition matrix. func (t TransitionMatrix) NextState(r *rand.Rand, i int) int { randNum := r.Intn(t.totals[i]) for row := 0; row < t.n; row++ { diff --git a/x/mock/simulation/types.go b/x/mock/simulation/types.go deleted file mode 100644 index 302ebcbd7d7d..000000000000 --- a/x/mock/simulation/types.go +++ /dev/null @@ -1,86 +0,0 @@ -package simulation - -import ( - "math/rand" - "time" - - "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" -) - -type ( - // Operation runs a state machine transition, - // and ensures the transition happened as expected. - // The operation could be running and testing a fuzzed transaction, - // or doing the same for a message. - // - // For ease of debugging, - // an operation returns a descriptive message "action", - // which details what this fuzzed state machine transition actually did. - // - // Operations can optionally provide a list of "FutureOperations" to run later - // These will be ran at the beginning of the corresponding block. - Operation func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - accounts []Account, event func(string), - ) (action string, futureOperations []FutureOperation, err error) - - // RandSetup performs the random setup the mock module needs. - RandSetup func(r *rand.Rand, accounts []Account) - - // An Invariant is a function which tests a particular invariant. - // If the invariant has been broken, it should return an error - // containing a descriptive message about what happened. - // The simulator will then halt and print the logs. - Invariant func(app *baseapp.BaseApp) error - - // Account contains a privkey, pubkey, address tuple - // eventually more useful data can be placed in here. - // (e.g. number of coins) - Account struct { - PrivKey crypto.PrivKey - PubKey crypto.PubKey - Address sdk.AccAddress - } - - mockValidator struct { - val abci.ValidatorUpdate - livenessState int - } - - // FutureOperation is an operation which will be ran at the - // beginning of the provided BlockHeight. - // If both a BlockHeight and BlockTime are specified, it will use the BlockHeight. - // In the (likely) event that multiple operations are queued at the same - // block height, they will execute in a FIFO pattern. - FutureOperation struct { - BlockHeight int - BlockTime time.Time - Op Operation - } - - // WeightedOperation is an operation with associated weight. - // This is used to bias the selection operation within the simulator. - WeightedOperation struct { - Weight int - Op Operation - } -) - -// PeriodicInvariant returns an Invariant function closure that asserts -// a given invariant if the mock application's last block modulo the given -// period is congruent to the given offset. -func PeriodicInvariant(invariant Invariant, period int, offset int) Invariant { - return func(app *baseapp.BaseApp) error { - if int(app.LastBlockHeight())%period == offset { - return invariant(app) - } - return nil - } -} - -// nolint -func (acc Account) Equals(acc2 Account) bool { - return acc.Address.Equals(acc2.Address) -} diff --git a/x/mock/simulation/util.go b/x/mock/simulation/util.go index 54bfabc73f8e..df2b6dae44b8 100644 --- a/x/mock/simulation/util.go +++ b/x/mock/simulation/util.go @@ -4,158 +4,119 @@ import ( "fmt" "math/rand" "os" - "sort" "strings" "testing" "time" - "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/secp256k1" - "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/mock" -) - -// shamelessly copied from https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang#31832326 -// TODO we should probably move this to tendermint/libs/common/random.go - -const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" -const ( - letterIdxBits = 6 // 6 bits to represent a letter index - letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits - letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits ) -// Generate a random string of a particular length -func RandStringOfLength(r *rand.Rand, n int) string { - b := make([]byte, n) - // A src.Int63() generates 63 random bits, enough for letterIdxMax characters! - for i, cache, remain := n-1, r.Int63(), letterIdxMax; i >= 0; { - if remain == 0 { - cache, remain = r.Int63(), letterIdxMax - } - if idx := int(cache & letterIdxMask); idx < len(letterBytes) { - b[i] = letterBytes[idx] - i-- - } - cache >>= letterIdxBits - remain-- +func getTestingMode(tb testing.TB) (testingMode bool, t *testing.T, b *testing.B) { + testingMode = false + if _t, ok := tb.(*testing.T); ok { + t = _t + testingMode = true + } else { + b = tb.(*testing.B) } - return string(b) + return } -// Pretty-print events as a table -func DisplayEvents(events map[string]uint) { - var keys []string - for key := range events { - keys = append(keys, key) - } - sort.Strings(keys) - fmt.Printf("Event statistics: \n") - for _, key := range keys { - fmt.Printf(" % 60s => %d\n", key, events[key]) +// Builds a function to add logs for this particular block +func addLogMessage(testingmode bool, + blockLogBuilders []*strings.Builder, height int) func(string) { + + if !testingmode { + return func(_ string) {} } -} -// RandomAcc pick a random account from an array -func RandomAcc(r *rand.Rand, accs []Account) Account { - return accs[r.Intn( - len(accs), - )] + blockLogBuilders[height] = &strings.Builder{} + return func(x string) { + (*blockLogBuilders[height]).WriteString(x) + (*blockLogBuilders[height]).WriteString("\n") + } } -// Generate a random amount -func RandomAmount(r *rand.Rand, max sdk.Int) sdk.Int { - return sdk.NewInt(int64(r.Intn(int(max.Int64())))) -} +// Creates a function to print out the logs +func logPrinter(testingmode bool, logs []*strings.Builder) func() { + if !testingmode { + return func() {} + } -// RandomAccounts generates n random accounts -func RandomAccounts(r *rand.Rand, n int) []Account { - accs := make([]Account, n) - for i := 0; i < n; i++ { - // don't need that much entropy for simulation - privkeySeed := make([]byte, 15) - r.Read(privkeySeed) - useSecp := r.Int63()%2 == 0 - if useSecp { - accs[i].PrivKey = secp256k1.GenPrivKeySecp256k1(privkeySeed) - } else { - accs[i].PrivKey = ed25519.GenPrivKeyFromSecret(privkeySeed) + return func() { + numLoggers := 0 + for i := 0; i < len(logs); i++ { + // We're passed the last created block + if logs[i] == nil { + numLoggers = i + break + } } - accs[i].PubKey = accs[i].PrivKey.PubKey() - accs[i].Address = sdk.AccAddress(accs[i].PubKey.Address()) - } - return accs -} -// Builds a function to add logs for this particular block -func addLogMessage(testingmode bool, blockLogBuilders []*strings.Builder, height int) func(string) { - if testingmode { - blockLogBuilders[height] = &strings.Builder{} - return func(x string) { - (*blockLogBuilders[height]).WriteString(x) - (*blockLogBuilders[height]).WriteString("\n") + var f *os.File + if numLoggers > 10 { + fileName := fmt.Sprintf("simulation_log_%s.txt", + time.Now().Format("2006-01-02 15:04:05")) + fmt.Printf("Too many logs to display, instead writing to %s\n", + fileName) + f, _ = os.Create(fileName) } - } - return func(x string) {} -} -// assertAllInvariants asserts a list of provided invariants against application state -func assertAllInvariants(t *testing.T, app *baseapp.BaseApp, invariants []Invariant, displayLogs func()) { - for i := 0; i < len(invariants); i++ { - err := invariants[i](app) - if err != nil { - fmt.Println(err.Error()) - displayLogs() - t.Fatal() + for i := 0; i < numLoggers; i++ { + if f == nil { + fmt.Printf("Begin block %d\n", i+1) + fmt.Println((*logs[i]).String()) + continue + } + + _, err := f.WriteString(fmt.Sprintf("Begin block %d\n", i+1)) + if err != nil { + panic("Failed to write logs to file") + } + + _, err = f.WriteString((*logs[i]).String()) + if err != nil { + panic("Failed to write logs to file") + } } } } -// RandomSetGenesis wraps mock.RandomSetGenesis, but using simulation accounts -func RandomSetGenesis(r *rand.Rand, app *mock.App, accs []Account, denoms []string) { - addrs := make([]sdk.AccAddress, len(accs)) - for i := 0; i < len(accs); i++ { - addrs[i] = accs[i].Address +// getBlockSize returns a block size as determined from the transition matrix. +// It targets making average block size the provided parameter. The three +// states it moves between are: +// - "over stuffed" blocks with average size of 2 * avgblocksize, +// - normal sized blocks, hitting avgBlocksize on average, +// - and empty blocks, with no txs / only txs scheduled from the past. +func getBlockSize(r *rand.Rand, params Params, + lastBlockSizeState, avgBlockSize int) (state, blocksize int) { + + // TODO: Make default blocksize transition matrix actually make the average + // blocksize equal to avgBlockSize. + state = params.BlockSizeTransitionMatrix.NextState(r, lastBlockSizeState) + switch state { + case 0: + blocksize = r.Intn(avgBlockSize * 4) + case 1: + blocksize = r.Intn(avgBlockSize * 2) + default: + blocksize = 0 } - mock.RandomSetGenesis(r, app, addrs, denoms) + return state, blocksize } -// Creates a function to print out the logs -func logPrinter(testingmode bool, logs []*strings.Builder) func() { - if testingmode { - return func() { - numLoggers := 0 - for i := 0; i < len(logs); i++ { - // We're passed the last created block - if logs[i] == nil { - numLoggers = i - 1 - break - } - } - var f *os.File - if numLoggers > 10 { - fileName := fmt.Sprintf("simulation_log_%s.txt", time.Now().Format("2006-01-02 15:04:05")) - fmt.Printf("Too many logs to display, instead writing to %s\n", fileName) - f, _ = os.Create(fileName) - } - for i := 0; i < numLoggers; i++ { - if f != nil { - _, err := f.WriteString(fmt.Sprintf("Begin block %d\n", i)) - if err != nil { - panic("Failed to write logs to file") - } - _, err = f.WriteString((*logs[i]).String()) - if err != nil { - panic("Failed to write logs to file") - } - } else { - fmt.Printf("Begin block %d\n", i) - fmt.Println((*logs[i]).String()) - } - } +// PeriodicInvariant returns an Invariant function closure that asserts a given +// invariant if the mock application's last block modulo the given period is +// congruent to the given offset. +// +// NOTE this function is intended to be used manually used while running +// computationally heavy simulations. +// TODO reference this function in the codebase probably through use of a switch +func PeriodicInvariant(invariant Invariant, period int, offset int) Invariant { + return func(app *baseapp.BaseApp) error { + if int(app.LastBlockHeight())%period == offset { + return invariant(app) } + return nil } - return func() {} } diff --git a/x/mock/test_utils.go b/x/mock/test_utils.go index caaca6c9a5cc..4e60478fa4b8 100644 --- a/x/mock/test_utils.go +++ b/x/mock/test_utils.go @@ -41,7 +41,7 @@ func RandFromBigInterval(r *rand.Rand, intervals []BigInterval) sdk.Int { // CheckBalance checks the balance of an account. func CheckBalance(t *testing.T, app *App, addr sdk.AccAddress, exp sdk.Coins) { ctxCheck := app.BaseApp.NewContext(true, abci.Header{}) - res := app.AccountMapper.GetAccount(ctxCheck, addr) + res := app.AccountKeeper.GetAccount(ctxCheck, addr) require.Equal(t, exp, res.GetCoins()) } diff --git a/x/params/doc.go b/x/params/doc.go index 06d6620b2027..bee45e79ec1e 100644 --- a/x/params/doc.go +++ b/x/params/doc.go @@ -3,18 +3,18 @@ package params /* Package params provides a globally available parameter store. -There are two main types, Keeper and Space. Space is an isolated namespace for a +There are two main types, Keeper and Subspace. Subspace is an isolated namespace for a paramstore, where keys are prefixed by preconfigured spacename. Keeper has a -permission to access all existing spaces and create new space. +permission to access all existing spaces. -Space can be used by the individual keepers, who needs a private parameter store -that the other keeper are not able to modify. Keeper can be used by the Governance -keeper, who need to modify any parameter in case of the proposal passes. +Subspace can be used by the individual keepers, who needs a private parameter store +that the other keeper cannot modify. Keeper can be used by the Governance keeper, +who need to modify any parameter in case of the proposal passes. Basic Usage: First, declare parameter space and parameter keys for the module. Then include -params.Store in the keeper. Since we prefix the keys with the spacename, it is +params.Subspace in the keeper. Since we prefix the keys with the spacename, it is recommended to use the same name with the module's. const ( @@ -33,26 +33,29 @@ recommended to use the same name with the module's. ps params.Subspace } -Pass a params.Store to NewKeeper with DefaultParamSpace (or another) +Pass a params.Subspace to NewKeeper with DefaultParamspace (or another) app.myKeeper = mymodule.NewKeeper(app.paramStore.SubStore(mymodule.DefaultParamspace)) Now we can access to the paramstore using Paramstore Keys + var param MyStruct k.ps.Get(KeyParameter1, ¶m) k.ps.Set(KeyParameter2, param) Genesis Usage: -Declare a struct for parameters and make it implement ParamStruct. It will then -be able to be passed to SetFromParamStruct. +Declare a struct for parameters and make it implement params.ParamSet. It will then +be able to be passed to SetParamSet. type MyParams struct { Parameter1 uint64 Parameter2 string } - func (p *MyParams) KeyFieldPairs() params.KeyFieldPairs { + // Implements params.ParamSet + // KeyValuePairs must return the list of (ParamKey, PointerToTheField) + func (p *MyParams) KeyValuePairs() params.KeyValuePairs { return params.KeyFieldPairs { {KeyParameter1, &p.Parameter1}, {KeyParameter2, &p.Parameter2}, @@ -60,26 +63,26 @@ be able to be passed to SetFromParamStruct. } func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { - k.ps.SetFromParamStruct(ctx, &data.params) + k.ps.SetParamSet(ctx, &data.params) } The method is pointer receiver because there could be a case that we read from the store and set the result to the struct. -Master Permission Usage: +Master Keeper Usage: Keepers that require master permission to the paramstore, such as gov, can take -params.Keeper itself to access all substores(using GetSubstore) +params.Keeper itself to access all subspace(using GetSubspace) type MasterKeeper struct { - ps params.Store + pk params.Keeper } func (k MasterKeeper) SetParam(ctx sdk.Context, space string, key string, param interface{}) { - store, ok := k.ps.GetSubstore(space) + space, ok := k.pk.GetSubspace(space) if !ok { return } - store.Set(ctx, key, param) + space.Set(ctx, key, param) } */ diff --git a/x/params/keeper_test.go b/x/params/keeper_test.go index 640661a24527..585db3c410e5 100644 --- a/x/params/keeper_test.go +++ b/x/params/keeper_test.go @@ -65,45 +65,82 @@ func TestKeeper(t *testing.T) { []byte("extra2"), string(""), ) + cdc := codec.New() skey := sdk.NewKVStoreKey("test") tkey := sdk.NewTransientStoreKey("transient_test") ctx := defaultContext(skey, tkey) - store := NewKeeper(codec.New(), skey, tkey).Subspace("test").WithTypeTable(table) + keeper := NewKeeper(cdc, skey, tkey) + space := keeper.Subspace("test").WithTypeTable(table) + store := ctx.KVStore(skey).Prefix([]byte("test/")) + // Set params for i, kv := range kvs { - require.NotPanics(t, func() { store.Set(ctx, []byte(kv.key), kv.param) }, "store.Set panics, tc #%d", i) + require.NotPanics(t, func() { space.Set(ctx, []byte(kv.key), kv.param) }, "space.Set panics, tc #%d", i) } + // Test space.Get for i, kv := range kvs { var param int64 - require.NotPanics(t, func() { store.Get(ctx, []byte(kv.key), ¶m) }, "store.Get panics, tc #%d", i) + require.NotPanics(t, func() { space.Get(ctx, []byte(kv.key), ¶m) }, "space.Get panics, tc #%d", i) require.Equal(t, kv.param, param, "stored param not equal, tc #%d", i) } - cdc := codec.New() + // Test space.GetRaw for i, kv := range kvs { var param int64 - bz := store.GetRaw(ctx, []byte(kv.key)) + bz := space.GetRaw(ctx, []byte(kv.key)) err := cdc.UnmarshalJSON(bz, ¶m) require.Nil(t, err, "err is not nil, tc #%d", i) require.Equal(t, kv.param, param, "stored param not equal, tc #%d", i) } + // Test store.Get equals space.Get + for i, kv := range kvs { + var param int64 + bz := store.Get([]byte(kv.key)) + require.NotNil(t, bz, "KVStore.Get returns nil, tc #%d", i) + err := cdc.UnmarshalJSON(bz, ¶m) + require.NoError(t, err, "UnmarshalJSON returns error, tc #%d", i) + require.Equal(t, kv.param, param, "stored param not equal, tc #%d", i) + } + + // Test invalid space.Get for i, kv := range kvs { var param bool - require.Panics(t, func() { store.Get(ctx, []byte(kv.key), ¶m) }, "invalid store.Get not panics, tc #%d", i) + require.Panics(t, func() { space.Get(ctx, []byte(kv.key), ¶m) }, "invalid space.Get not panics, tc #%d", i) + } + + // Test invalid space.Set + for i, kv := range kvs { + require.Panics(t, func() { space.Set(ctx, []byte(kv.key), true) }, "invalid space.Set not panics, tc #%d", i) } + // Test GetSubspace for i, kv := range kvs { - require.Panics(t, func() { store.Set(ctx, []byte(kv.key), true) }, "invalid store.Set not panics, tc #%d", i) + var gparam, param int64 + gspace, ok := keeper.GetSubspace("test") + require.True(t, ok, "cannot retrieve subspace, tc #%d", i) + + require.NotPanics(t, func() { gspace.Get(ctx, []byte(kv.key), &gparam) }) + require.NotPanics(t, func() { space.Get(ctx, []byte(kv.key), ¶m) }) + require.Equal(t, gparam, param, "GetSubspace().Get not equal with space.Get, tc #%d", i) + + require.NotPanics(t, func() { gspace.Set(ctx, []byte(kv.key), int64(i)) }) + require.NotPanics(t, func() { space.Get(ctx, []byte(kv.key), ¶m) }) + require.Equal(t, int64(i), param, "GetSubspace().Set not equal with space.Get, tc #%d", i) } } -func TestGet(t *testing.T) { +func indirect(ptr interface{}) interface{} { + return reflect.ValueOf(ptr).Elem().Interface() +} + +func TestSubspace(t *testing.T) { + cdc := createTestCodec() key := sdk.NewKVStoreKey("test") tkey := sdk.NewTransientStoreKey("transient_test") ctx := defaultContext(key, tkey) - keeper := NewKeeper(createTestCodec(), key, tkey) + keeper := NewKeeper(cdc, key, tkey) kvs := []struct { key string @@ -140,29 +177,41 @@ func TestGet(t *testing.T) { []byte("struct"), s{}, ) - store := keeper.Subspace("test").WithTypeTable(table) + store := ctx.KVStore(key).Prefix([]byte("test/")) + space := keeper.Subspace("test").WithTypeTable(table) + // Test space.Set, space.Modified for i, kv := range kvs { - require.False(t, store.Modified(ctx, []byte(kv.key)), "store.Modified returns true before setting, tc #%d", i) - require.NotPanics(t, func() { store.Set(ctx, []byte(kv.key), kv.param) }, "store.Set panics, tc #%d", i) - require.True(t, store.Modified(ctx, []byte(kv.key)), "store.Modified returns false after setting, tc #%d", i) + require.False(t, space.Modified(ctx, []byte(kv.key)), "space.Modified returns true before setting, tc #%d", i) + require.NotPanics(t, func() { space.Set(ctx, []byte(kv.key), kv.param) }, "space.Set panics, tc #%d", i) + require.True(t, space.Modified(ctx, []byte(kv.key)), "space.Modified returns false after setting, tc #%d", i) } + // Test space.Get, space.GetIfExists for i, kv := range kvs { - require.NotPanics(t, func() { store.GetIfExists(ctx, []byte("invalid"), kv.ptr) }, "store.GetIfExists panics when no value exists, tc #%d", i) - require.Equal(t, kv.zero, reflect.ValueOf(kv.ptr).Elem().Interface(), "store.GetIfExists unmarshalls when no value exists, tc #%d", i) - require.Panics(t, func() { store.Get(ctx, []byte("invalid"), kv.ptr) }, "invalid store.Get not panics when no value exists, tc #%d", i) - require.Equal(t, kv.zero, reflect.ValueOf(kv.ptr).Elem().Interface(), "invalid store.Get unmarshalls when no value exists, tc #%d", i) + require.NotPanics(t, func() { space.GetIfExists(ctx, []byte("invalid"), kv.ptr) }, "space.GetIfExists panics when no value exists, tc #%d", i) + require.Equal(t, kv.zero, indirect(kv.ptr), "space.GetIfExists unmarshalls when no value exists, tc #%d", i) + require.Panics(t, func() { space.Get(ctx, []byte("invalid"), kv.ptr) }, "invalid space.Get not panics when no value exists, tc #%d", i) + require.Equal(t, kv.zero, indirect(kv.ptr), "invalid space.Get unmarshalls when no value exists, tc #%d", i) + + require.NotPanics(t, func() { space.GetIfExists(ctx, []byte(kv.key), kv.ptr) }, "space.GetIfExists panics, tc #%d", i) + require.Equal(t, kv.param, indirect(kv.ptr), "stored param not equal, tc #%d", i) + require.NotPanics(t, func() { space.Get(ctx, []byte(kv.key), kv.ptr) }, "space.Get panics, tc #%d", i) + require.Equal(t, kv.param, indirect(kv.ptr), "stored param not equal, tc #%d", i) - require.NotPanics(t, func() { store.GetIfExists(ctx, []byte(kv.key), kv.ptr) }, "store.GetIfExists panics, tc #%d", i) - require.Equal(t, kv.param, reflect.ValueOf(kv.ptr).Elem().Interface(), "stored param not equal, tc #%d", i) - require.NotPanics(t, func() { store.Get(ctx, []byte(kv.key), kv.ptr) }, "store.Get panics, tc #%d", i) - require.Equal(t, kv.param, reflect.ValueOf(kv.ptr).Elem().Interface(), "stored param not equal, tc #%d", i) + require.Panics(t, func() { space.Get(ctx, []byte("invalid"), kv.ptr) }, "invalid space.Get not panics when no value exists, tc #%d", i) + require.Equal(t, kv.param, indirect(kv.ptr), "invalid space.Get unmarshalls when no value existt, tc #%d", i) - require.Panics(t, func() { store.Get(ctx, []byte("invalid"), kv.ptr) }, "invalid store.Get not panics when no value exists, tc #%d", i) - require.Equal(t, kv.param, reflect.ValueOf(kv.ptr).Elem().Interface(), "invalid store.Get unmarshalls when no value existt, tc #%d", i) + require.Panics(t, func() { space.Get(ctx, []byte(kv.key), nil) }, "invalid space.Get not panics when the pointer is nil, tc #%d", i) + require.Panics(t, func() { space.Get(ctx, []byte(kv.key), new(invalid)) }, "invalid space.Get not panics when the pointer is different type, tc #%d", i) + } - require.Panics(t, func() { store.Get(ctx, []byte(kv.key), nil) }, "invalid store.Get not panics when the pointer is nil, tc #%d", i) - require.Panics(t, func() { store.Get(ctx, []byte(kv.key), new(invalid)) }, "invalid store.Get not panics when the pointer is different type, tc #%d", i) + // Test store.Get equals space.Get + for i, kv := range kvs { + bz := store.Get([]byte(kv.key)) + require.NotNil(t, bz, "store.Get() returns nil, tc #%d", i) + err := cdc.UnmarshalJSON(bz, kv.ptr) + require.NoError(t, err, "cdc.UnmarshalJSON() returns error, tc #%d", i) + require.Equal(t, kv.param, indirect(kv.ptr), "stored param not equal, tc #%d", i) } } diff --git a/x/params/subspace/subspace.go b/x/params/subspace/subspace.go index 7c9ca5fdaad6..fe5889a908ca 100644 --- a/x/params/subspace/subspace.go +++ b/x/params/subspace/subspace.go @@ -7,10 +7,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// Additional capicity to be allocated for Subspace.name -// So we don't have to allocate extra space each time appending to the key -const extraKeyCap = 20 - // 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 @@ -30,32 +26,35 @@ func NewSubspace(cdc *codec.Codec, key sdk.StoreKey, tkey sdk.StoreKey, name str cdc: cdc, key: key, tkey: tkey, + name: []byte(name), + table: TypeTable{ + m: make(map[string]reflect.Type), + }, } - namebz := []byte(name) - res.name = make([]byte, len(namebz), len(namebz)+extraKeyCap) - copy(res.name, namebz) return } // WithTypeTable initializes TypeTable and returns modified Subspace -func (s Subspace) WithTypeTable(table TypeTable) (res Subspace) { - if table == nil { +func (s Subspace) WithTypeTable(table TypeTable) Subspace { + if table.m == nil { panic("SetTypeTable() called with nil TypeTable") } - if s.table != nil { - panic("SetTypeTable() called on initialized Subspace") + if len(s.table.m) != 0 { + panic("SetTypeTable() called on already initialized Subspace") } - res = Subspace{ - cdc: s.cdc, - key: s.key, - tkey: s.tkey, - name: s.name, - table: table, + for k, v := range table.m { + s.table.m[k] = v } - return + // Allocate additional capicity for Subspace.name + // So we don't have to allocate extra space each time appending to the key + name := s.name + s.name = make([]byte, len(name), len(name)+table.maxKeyLength()) + copy(s.name, name) + + return s } // Returns a KVStore identical with ctx.KVStore(s.key).Prefix() @@ -118,7 +117,7 @@ func (s Subspace) Modified(ctx sdk.Context, key []byte) bool { func (s Subspace) Set(ctx sdk.Context, key []byte, param interface{}) { store := s.kvStore(ctx) - ty, ok := s.table[string(key)] + ty, ok := s.table.m[string(key)] if !ok { panic("Parameter not registered") } diff --git a/x/params/subspace/table.go b/x/params/subspace/table.go index 363d0243d21e..19e41da09005 100644 --- a/x/params/subspace/table.go +++ b/x/params/subspace/table.go @@ -5,7 +5,9 @@ import ( ) // TypeTable subspaces appropriate type for each parameter key -type TypeTable map[string]reflect.Type +type TypeTable struct { + m map[string]reflect.Type +} // Constructs new table func NewTypeTable(keytypes ...interface{}) (res TypeTable) { @@ -13,7 +15,9 @@ func NewTypeTable(keytypes ...interface{}) (res TypeTable) { panic("odd number arguments in NewTypeTypeTable") } - res = make(map[string]reflect.Type) + res = TypeTable{ + m: make(map[string]reflect.Type), + } for i := 0; i < len(keytypes); i += 2 { res = res.RegisterType(keytypes[i].([]byte), keytypes[i+1]) @@ -22,10 +26,27 @@ func NewTypeTable(keytypes ...interface{}) (res TypeTable) { return } +func isAlphaNumeric(key []byte) bool { + for _, b := range key { + if !((48 <= b && b <= 57) || // numeric + (65 <= b && b <= 90) || // upper case + (97 <= b && b <= 122)) { // lower case + return false + } + } + return true +} + // Register single key-type pair func (t TypeTable) RegisterType(key []byte, ty interface{}) TypeTable { + if len(key) == 0 { + panic("cannot register empty key") + } + if !isAlphaNumeric(key) { + panic("non alphanumeric parameter key") + } keystr := string(key) - if _, ok := t[keystr]; ok { + if _, ok := t.m[keystr]; ok { panic("duplicate parameter key") } @@ -36,7 +57,7 @@ func (t TypeTable) RegisterType(key []byte, ty interface{}) TypeTable { rty = rty.Elem() } - t[keystr] = rty + t.m[keystr] = rty return t } @@ -48,3 +69,13 @@ func (t TypeTable) RegisterParamSet(ps ParamSet) TypeTable { } return t } + +func (t TypeTable) maxKeyLength() (res int) { + for k := range t.m { + l := len(k) + if l > res { + res = l + } + } + return +} diff --git a/x/params/subspace/table_test.go b/x/params/subspace/table_test.go new file mode 100644 index 000000000000..8f9142e1eca9 --- /dev/null +++ b/x/params/subspace/table_test.go @@ -0,0 +1,35 @@ +package subspace + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +type testparams struct { + i int64 + b bool +} + +func (tp *testparams) KeyValuePairs() KeyValuePairs { + return KeyValuePairs{ + {[]byte("i"), &tp.i}, + {[]byte("b"), &tp.b}, + } +} + +func TestTypeTable(t *testing.T) { + table := NewTypeTable() + + require.Panics(t, func() { table.RegisterType([]byte(""), nil) }) + require.Panics(t, func() { table.RegisterType([]byte("!@#$%"), nil) }) + require.Panics(t, func() { table.RegisterType([]byte("hello,"), nil) }) + require.Panics(t, func() { table.RegisterType([]byte("hello"), nil) }) + + require.NotPanics(t, func() { table.RegisterType([]byte("hello"), bool(false)) }) + require.NotPanics(t, func() { table.RegisterType([]byte("world"), int64(0)) }) + require.Panics(t, func() { table.RegisterType([]byte("hello"), bool(false)) }) + + require.NotPanics(t, func() { table.RegisterParamSet(&testparams{}) }) + require.Panics(t, func() { table.RegisterParamSet(&testparams{}) }) +} diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index c0ed10747c77..dd96ff51e24f 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -3,15 +3,17 @@ package slashing 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" "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" - "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/ed25519" + stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" ) var ( @@ -31,7 +33,7 @@ func getMockApp(t *testing.T) (*mock.App, stake.Keeper, Keeper) { keyParams := sdk.NewKVStoreKey("params") tkeyParams := sdk.NewTransientStoreKey("transient_params") - bankKeeper := bank.NewBaseKeeper(mapp.AccountMapper) + bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper) paramsKeeper := params.NewKeeper(mapp.Cdc, keyParams, tkeyParams) stakeKeeper := stake.NewKeeper(mapp.Cdc, keyStake, tkeyStake, bankKeeper, paramsKeeper.Subspace(stake.DefaultParamspace), mapp.RegisterCodespace(stake.DefaultCodespace)) @@ -93,8 +95,8 @@ func checkValidatorSigningInfo(t *testing.T, mapp *mock.App, keeper Keeper, func TestSlashingMsgs(t *testing.T) { mapp, stakeKeeper, keeper := getMockApp(t) - genCoin := sdk.NewInt64Coin("steak", 42) - bondCoin := sdk.NewInt64Coin("steak", 10) + genCoin := sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 42) + bondCoin := sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10) acc1 := &auth.BaseAccount{ Address: addr1, diff --git a/x/slashing/client/cli/query.go b/x/slashing/client/cli/query.go index 50ccc6c0eaeb..de4fc5d57d9a 100644 --- a/x/slashing/client/cli/query.go +++ b/x/slashing/client/cli/query.go @@ -34,7 +34,7 @@ func GetCmdQuerySigningInfo(storeName string, cdc *codec.Codec) *cobra.Command { } signingInfo := new(slashing.ValidatorSigningInfo) - cdc.MustUnmarshalBinary(res, signingInfo) + cdc.MustUnmarshalBinaryLengthPrefixed(res, signingInfo) switch viper.Get(cli.OutputFlag) { diff --git a/x/slashing/client/rest/query.go b/x/slashing/client/rest/query.go index e83f1a235dcd..e55b6b9be797 100644 --- a/x/slashing/client/rest/query.go +++ b/x/slashing/client/rest/query.go @@ -1,10 +1,10 @@ package rest import ( - "fmt" "net/http" "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" "github.com/cosmos/cosmos-sdk/x/slashing" @@ -13,7 +13,7 @@ import ( func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) { r.HandleFunc( - "/slashing/signing_info/{validator}", + "/slashing/validators/{validatorPubKey}/signing_info", signingInfoHandlerFn(cliCtx, "slashing", cdc), ).Methods("GET") } @@ -24,10 +24,9 @@ func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *code return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - pk, err := sdk.GetConsPubKeyBech32(vars["validator"]) + pk, err := sdk.GetConsPubKeyBech32(vars["validatorPubKey"]) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -35,27 +34,23 @@ func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *code res, err := cliCtx.QueryStore(key, storeName) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't query signing info. Error: %s", err.Error()))) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - var signingInfo slashing.ValidatorSigningInfo - - err = cdc.UnmarshalBinary(res, &signingInfo) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't decode signing info. Error: %s", err.Error()))) + if len(res) == 0 { + w.WriteHeader(http.StatusNoContent) return } - output, err := cdc.MarshalJSON(signingInfo) + var signingInfo slashing.ValidatorSigningInfo + + err = cdc.UnmarshalBinaryLengthPrefixed(res, &signingInfo) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - w.Write(output) + utils.PostProcessResponse(w, cdc, signingInfo, cliCtx.Indent) } } diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index 972d4351fadb..5f33a1210cb3 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -2,7 +2,6 @@ package rest import ( "bytes" - "fmt" "net/http" "github.com/cosmos/cosmos-sdk/client/context" @@ -17,19 +16,22 @@ import ( func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, kb keys.Keybase) { r.HandleFunc( - "/slashing/unjail", + "/slashing/validators/{validatorAddr}/unjail", unjailRequestHandlerFn(cdc, kb, cliCtx), ).Methods("POST") } // Unjail TX body type UnjailReq struct { - BaseReq utils.BaseReq `json:"base_req"` - ValidatorAddr string `json:"validator_addr"` + BaseReq utils.BaseReq `json:"base_req"` } func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + bech32validator := vars["validatorAddr"] + var req UnjailReq err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { @@ -47,12 +49,9 @@ func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CL return } - valAddr, err := sdk.ValAddressFromBech32(req.ValidatorAddr) + valAddr, err := sdk.ValAddressFromBech32(bech32validator) if err != nil { - utils.WriteErrorResponse( - w, http.StatusInternalServerError, - fmt.Sprintf("failed to decode validator; error: %s", err.Error()), - ) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } diff --git a/x/slashing/genesis.go b/x/slashing/genesis.go index 10af155d6824..1d4a44369151 100644 --- a/x/slashing/genesis.go +++ b/x/slashing/genesis.go @@ -7,13 +7,25 @@ import ( // GenesisState - all slashing state that must be provided at genesis type GenesisState struct { - Params Params + Params Params + SigningInfos map[string]ValidatorSigningInfo + MissedBlocks map[string][]MissedBlock + SlashingPeriods []ValidatorSlashingPeriod +} + +// MissedBlock +type MissedBlock struct { + Index int64 `json:"index"` + Missed bool `json:"missed"` } // HubDefaultGenesisState - default GenesisState used by Cosmos Hub func DefaultGenesisState() GenesisState { return GenesisState{ - Params: DefaultParams(), + Params: DefaultParams(), + SigningInfos: make(map[string]ValidatorSigningInfo), + MissedBlocks: make(map[string][]MissedBlock), + SlashingPeriods: []ValidatorSlashingPeriod{}, } } @@ -24,5 +36,64 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState, sdata types. keeper.addPubkey(ctx, validator.GetConsPubKey()) } + for addr, info := range data.SigningInfos { + address, err := sdk.ConsAddressFromBech32(addr) + if err != nil { + panic(err) + } + keeper.setValidatorSigningInfo(ctx, address, info) + } + + for addr, array := range data.MissedBlocks { + address, err := sdk.ConsAddressFromBech32(addr) + if err != nil { + panic(err) + } + for _, missed := range array { + keeper.setValidatorMissedBlockBitArray(ctx, address, missed.Index, missed.Missed) + } + } + + for _, slashingPeriod := range data.SlashingPeriods { + keeper.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod) + } + keeper.paramspace.SetParamSet(ctx, &data.Params) } + +// ExportGenesis writes the current store values +// to a genesis file, which can be imported again +// with InitGenesis +func ExportGenesis(ctx sdk.Context, keeper Keeper) (data GenesisState) { + var params Params + keeper.paramspace.GetParamSet(ctx, ¶ms) + + signingInfos := make(map[string]ValidatorSigningInfo) + missedBlocks := make(map[string][]MissedBlock) + keeper.iterateValidatorSigningInfos(ctx, func(address sdk.ConsAddress, info ValidatorSigningInfo) (stop bool) { + bechAddr := address.String() + signingInfos[bechAddr] = info + localMissedBlocks := []MissedBlock{} + + keeper.iterateValidatorMissedBlockBitArray(ctx, address, func(index int64, missed bool) (stop bool) { + localMissedBlocks = append(localMissedBlocks, MissedBlock{index, missed}) + return false + }) + missedBlocks[bechAddr] = localMissedBlocks + + 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, + } +} diff --git a/x/slashing/handler.go b/x/slashing/handler.go index 740166d2af62..701577080ec1 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -46,11 +46,7 @@ func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { return ErrValidatorJailed(k.codespace).Result() } - // update the starting height so the validator can't be immediately jailed - // again - info.StartHeight = ctx.BlockHeight() - k.setValidatorSigningInfo(ctx, consAddr, info) - + // unjail the validator k.validatorSet.Unjail(ctx, consAddr) tags := sdk.NewTags("action", []byte("unjail"), "validator", []byte(msg.ValidatorAddr.String())) diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 20395b2e9f92..ef307547c640 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -16,7 +16,7 @@ func TestCannotUnjailUnlessJailed(t *testing.T) { slh := NewHandler(keeper) amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) - msg := newTestMsgCreateValidator(addr, val, amt) + msg := NewTestMsgCreateValidator(addr, val, amt) got := stake.NewHandler(sk)(ctx, msg) require.True(t, got.IsOK()) stake.EndBlocker(ctx, sk) @@ -41,7 +41,7 @@ func TestJailedValidatorDelegations(t *testing.T) { valPubKey, bondAmount := pks[0], sdk.NewInt(amount) valAddr, consAddr := addrs[1], sdk.ConsAddress(addrs[0]) - msgCreateVal := newTestMsgCreateValidator(valAddr, valPubKey, bondAmount) + msgCreateVal := NewTestMsgCreateValidator(valAddr, valPubKey, bondAmount) got := stake.NewHandler(stakeKeeper)(ctx, msgCreateVal) require.True(t, got.IsOK(), "expected create validator msg to be ok, got: %v", got) @@ -53,7 +53,7 @@ func TestJailedValidatorDelegations(t *testing.T) { StartHeight: int64(0), IndexOffset: int64(0), JailedUntil: time.Unix(0, 0), - SignedBlocksCounter: int64(0), + MissedBlocksCounter: int64(0), } slashingKeeper.setValidatorSigningInfo(ctx, consAddr, newInfo) diff --git a/x/slashing/hooks.go b/x/slashing/hooks.go index 701a6b2cde66..e09f6c566e0a 100644 --- a/x/slashing/hooks.go +++ b/x/slashing/hooks.go @@ -1,11 +1,27 @@ package slashing import ( + "time" + + "github.com/tendermint/tendermint/crypto" + sdk "github.com/cosmos/cosmos-sdk/types" ) -// Create a new slashing period when a validator is bonded -func (k Keeper) onValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { +func (k Keeper) onValidatorBonded(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 { + signingInfo := ValidatorSigningInfo{ + StartHeight: ctx.BlockHeight(), + IndexOffset: 0, + JailedUntil: time.Unix(0, 0), + MissedBlocksCounter: 0, + } + k.setValidatorSigningInfo(ctx, address, signingInfo) + } + + // Create a new slashing period when a validator is bonded slashingPeriod := ValidatorSlashingPeriod{ ValidatorAddr: address, StartHeight: ctx.BlockHeight(), @@ -16,12 +32,23 @@ func (k Keeper) onValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { } // Mark the slashing period as having ended when a validator begins unbonding -func (k Keeper) onValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) { +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) { + 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) { + k.deleteAddrPubkeyRelation(ctx, crypto.Address(address)) +} + //_________________________________________________________________________________________ // Wrapper struct @@ -37,19 +64,29 @@ func (k Keeper) Hooks() Hooks { } // Implements sdk.ValidatorHooks -func (h Hooks) OnValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { - h.k.onValidatorBonded(ctx, address) +func (h Hooks) OnValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.k.onValidatorBonded(ctx, consAddr, valAddr) } // Implements sdk.ValidatorHooks -func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) { - h.k.onValidatorBeginUnbonding(ctx, address) +func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.k.onValidatorBeginUnbonding(ctx, consAddr, valAddr) +} + +// 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) } // nolint - unused hooks -func (h Hooks) OnValidatorCreated(_ sdk.Context, _ sdk.ValAddress) {} -func (h Hooks) OnValidatorCommissionChange(_ sdk.Context, _ sdk.ValAddress) {} -func (h Hooks) OnValidatorRemoved(_ sdk.Context, _ sdk.ValAddress) {} +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) {} diff --git a/x/slashing/hooks_test.go b/x/slashing/hooks_test.go index 951e3637f377..eb7406764744 100644 --- a/x/slashing/hooks_test.go +++ b/x/slashing/hooks_test.go @@ -11,7 +11,7 @@ import ( func TestHookOnValidatorBonded(t *testing.T) { ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) addr := sdk.ConsAddress(addrs[0]) - keeper.onValidatorBonded(ctx, addr) + keeper.onValidatorBonded(ctx, addr, nil) period := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, ctx.BlockHeight()) require.Equal(t, ValidatorSlashingPeriod{addr, ctx.BlockHeight(), 0, sdk.ZeroDec()}, period) } @@ -19,8 +19,8 @@ func TestHookOnValidatorBonded(t *testing.T) { func TestHookOnValidatorBeginUnbonding(t *testing.T) { ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) addr := sdk.ConsAddress(addrs[0]) - keeper.onValidatorBonded(ctx, addr) - keeper.onValidatorBeginUnbonding(ctx, addr) + 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 f5fe3bc3626a..fe1531f22818 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -4,13 +4,10 @@ import ( "fmt" "time" - tmtypes "github.com/tendermint/tendermint/types" - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" stake "github.com/cosmos/cosmos-sdk/x/stake/types" - abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" ) @@ -38,6 +35,7 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, vs sdk.ValidatorSet, paramspa } // handle a validator signing two blocks at the same height +// power: power of the double-signing validator at the height of infraction func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractionHeight int64, timestamp time.Time, power int64) { logger := ctx.Logger().With("module", "x/slashing") time := ctx.BlockHeader().Time @@ -48,6 +46,15 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr)) } + // Get validator. + validator := k.validatorSet.ValidatorByConsAddr(ctx, consAddr) + if validator == nil || validator.GetStatus() == sdk.Unbonded { + // Defensive. + // Simulation doesn't take unbonding periods into account, and + // Tendermint might break this assumption at some point. + return + } + // Double sign too old maxEvidenceAge := k.MaxEvidenceAge(ctx) if age > maxEvidenceAge { @@ -71,10 +78,14 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio 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) // Jail validator if not already jailed - validator := k.validatorSet.ValidatorByConsAddr(ctx, consAddr) if !validator.GetJailed() { k.validatorSet.Jail(ctx, consAddr) } @@ -102,8 +113,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p // Will use the 0-value default signing info if not present, except for start height signInfo, found := k.getValidatorSigningInfo(ctx, consAddr) if !found { - // If this validator has never been seen before, construct a new SigningInfo with the correct start height - signInfo = NewValidatorSigningInfo(height, 0, time.Unix(0, 0), 0) + panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr)) } index := signInfo.IndexOffset % k.SignedBlocksWindow(ctx) signInfo.IndexOffset++ @@ -111,24 +121,27 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p // Update signed block bit array & counter // This counter just tracks the sum of the bit array // That way we avoid needing to read/write the whole array each time - previous := k.getValidatorSigningBitArray(ctx, consAddr, index) - if previous == signed { + previous := k.getValidatorMissedBlockBitArray(ctx, consAddr, index) + missed := !signed + switch { + case !previous && missed: + // Array value has changed from not missed to missed, increment counter + k.setValidatorMissedBlockBitArray(ctx, consAddr, index, true) + signInfo.MissedBlocksCounter++ + case previous && !missed: + // Array value has changed from missed to not missed, decrement counter + k.setValidatorMissedBlockBitArray(ctx, consAddr, index, false) + signInfo.MissedBlocksCounter-- + default: // Array value at this index has not changed, no need to update counter - } else if previous && !signed { - // Array value has changed from signed to unsigned, decrement counter - k.setValidatorSigningBitArray(ctx, consAddr, index, false) - signInfo.SignedBlocksCounter-- - } else if !previous && signed { - // Array value has changed from unsigned to signed, increment counter - k.setValidatorSigningBitArray(ctx, consAddr, index, true) - signInfo.SignedBlocksCounter++ } - if !signed { - logger.Info(fmt.Sprintf("Absent validator %s at height %d, %d signed, threshold %d", addr, height, signInfo.SignedBlocksCounter, k.MinSignedPerWindow(ctx))) + if missed { + logger.Info(fmt.Sprintf("Absent validator %s at height %d, %d missed, threshold %d", addr, height, signInfo.MissedBlocksCounter, k.MinSignedPerWindow(ctx))) } minHeight := signInfo.StartHeight + k.SignedBlocksWindow(ctx) - if height > minHeight && signInfo.SignedBlocksCounter < k.MinSignedPerWindow(ctx) { + maxMissed := k.SignedBlocksWindow(ctx) - k.MinSignedPerWindow(ctx) + if height > minHeight && signInfo.MissedBlocksCounter > maxMissed { validator := k.validatorSet.ValidatorByConsAddr(ctx, consAddr) if validator != nil && !validator.GetJailed() { // Downtime confirmed: slash and jail the validator @@ -143,6 +156,10 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, k.SlashFractionDowntime(ctx)) k.validatorSet.Jail(ctx, consAddr) signInfo.JailedUntil = ctx.BlockHeader().Time.Add(k.DowntimeUnbondDuration(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 + k.clearValidatorMissedBlockBitArray(ctx, consAddr) } else { // Validator was (a) not found or (b) already jailed, don't slash logger.Info(fmt.Sprintf("Validator %s would have been slashed for downtime, but was either not found in store or already jailed", @@ -154,19 +171,6 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p k.setValidatorSigningInfo(ctx, consAddr, signInfo) } -// AddValidators adds the validators to the keepers validator addr to pubkey mapping. -func (k Keeper) AddValidators(ctx sdk.Context, vals []abci.ValidatorUpdate) { - for i := 0; i < len(vals); i++ { - val := vals[i] - pubkey, err := tmtypes.PB2TM.PubKey(val.PubKey) - if err != nil { - panic(err) - } - k.addPubkey(ctx, pubkey) - } -} - -// TODO: Make a method to remove the pubkey from the map when a validator is unbonded. func (k Keeper) addPubkey(ctx sdk.Context, pubkey crypto.PubKey) { addr := pubkey.Address() k.setAddrPubkeyRelation(ctx, addr, pubkey) @@ -175,7 +179,7 @@ func (k Keeper) addPubkey(ctx sdk.Context, pubkey crypto.PubKey) { func (k Keeper) getPubkey(ctx sdk.Context, address crypto.Address) (crypto.PubKey, error) { store := ctx.KVStore(k.storeKey) var pubkey crypto.PubKey - err := k.cdc.UnmarshalBinary(store.Get(getAddrPubkeyRelationKey(address)), &pubkey) + err := k.cdc.UnmarshalBinaryLengthPrefixed(store.Get(getAddrPubkeyRelationKey(address)), &pubkey) if err != nil { return nil, fmt.Errorf("address %v not found", address) } @@ -184,7 +188,7 @@ func (k Keeper) getPubkey(ctx sdk.Context, address crypto.Address) (crypto.PubKe func (k Keeper) setAddrPubkeyRelation(ctx sdk.Context, addr crypto.Address, pubkey crypto.PubKey) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(pubkey) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(pubkey) store.Set(getAddrPubkeyRelationKey(addr), bz) } diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 627eff6f2694..94251ded31f5 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -30,13 +30,11 @@ func TestHandleDoubleSign(t *testing.T) { ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) // validator added pre-genesis ctx = ctx.WithBlockHeight(-1) - sk = sk.WithHooks(keeper.Hooks()) amtInt := int64(100) operatorAddr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) - got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(operatorAddr, val, amt)) + got := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, val, amt)) require.True(t, got.IsOK()) - validatorUpdates := stake.EndBlocker(ctx, sk) - keeper.AddValidators(ctx, validatorUpdates) + stake.EndBlocker(ctx, sk) require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(operatorAddr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, operatorAddr).GetPower())) @@ -71,15 +69,13 @@ func TestSlashingPeriodCap(t *testing.T) { // initial setup ctx, ck, sk, _, keeper := createTestInput(t, DefaultParams()) - sk = sk.WithHooks(keeper.Hooks()) 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)) + got := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, valConsPubKey, amt)) require.True(t, got.IsOK()) - validatorUpdates := stake.EndBlocker(ctx, sk) + stake.EndBlocker(ctx, sk) ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) - keeper.AddValidators(ctx, validatorUpdates) require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(operatorAddr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, operatorAddr).GetPower())) @@ -137,25 +133,22 @@ func TestHandleAbsentValidator(t *testing.T) { // initial setup ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) - sk = sk.WithHooks(keeper.Hooks()) amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) sh := stake.NewHandler(sk) slh := NewHandler(keeper) - got := sh(ctx, newTestMsgCreateValidator(addr, val, amt)) + got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) require.True(t, got.IsOK()) - validatorUpdates := stake.EndBlocker(ctx, sk) - keeper.AddValidators(ctx, validatorUpdates) + stake.EndBlocker(ctx, sk) require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower())) + // will exist since the validator has been bonded info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) - require.False(t, found) + require.True(t, found) require.Equal(t, int64(0), info.StartHeight) require.Equal(t, int64(0), info.IndexOffset) - require.Equal(t, int64(0), info.SignedBlocksCounter) - // default time.Time value - var blankTime time.Time - require.Equal(t, blankTime, info.JailedUntil) + require.Equal(t, int64(0), info.MissedBlocksCounter) + require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) height := int64(0) // 1000 first blocks OK @@ -166,7 +159,7 @@ func TestHandleAbsentValidator(t *testing.T) { info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) require.Equal(t, int64(0), info.StartHeight) - require.Equal(t, keeper.SignedBlocksWindow(ctx), info.SignedBlocksCounter) + require.Equal(t, int64(0), info.MissedBlocksCounter) // 500 blocks missed for ; height < keeper.SignedBlocksWindow(ctx)+(keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)); height++ { @@ -176,7 +169,7 @@ func TestHandleAbsentValidator(t *testing.T) { info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) require.Equal(t, int64(0), info.StartHeight) - require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx), info.SignedBlocksCounter) + require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx), info.MissedBlocksCounter) // validator should be bonded still validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) @@ -190,7 +183,8 @@ func TestHandleAbsentValidator(t *testing.T) { info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) require.Equal(t, int64(0), info.StartHeight) - require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-1, info.SignedBlocksCounter) + // counter now reset to zero + require.Equal(t, int64(0), info.MissedBlocksCounter) // end block stake.EndBlocker(ctx, sk) @@ -211,7 +205,7 @@ func TestHandleAbsentValidator(t *testing.T) { info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) require.Equal(t, int64(0), info.StartHeight) - require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-2, info.SignedBlocksCounter) + require.Equal(t, int64(1), info.MissedBlocksCounter) // end block stake.EndBlocker(ctx, sk) @@ -248,12 +242,12 @@ func TestHandleAbsentValidator(t *testing.T) { pool = sk.GetPool(ctx) require.Equal(t, amtInt-slashAmt-secondSlashAmt, pool.BondedTokens.RoundInt64()) - // validator start height should 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, height, info.StartHeight) - // we've missed 2 blocks more than the maximum - require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-2, info.SignedBlocksCounter) + require.Equal(t, int64(0), info.StartHeight) + // we've missed 2 blocks more than the maximum, so the counter was reset to 0 at 1 block more and is now 1 + require.Equal(t, int64(1), info.MissedBlocksCounter) // validator should not be immediately jailed again height++ @@ -294,16 +288,17 @@ func TestHandleNewValidator(t *testing.T) { ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) addr, val, amt := addrs[0], pks[0], int64(100) sh := stake.NewHandler(sk) - got := sh(ctx, newTestMsgCreateValidator(addr, val, sdk.NewInt(amt))) - require.True(t, got.IsOK()) - validatorUpdates := stake.EndBlocker(ctx, sk) - keeper.AddValidators(ctx, validatorUpdates) - require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.SubRaw(amt)}}) - require.Equal(t, sdk.NewDec(amt), sk.Validator(ctx, addr).GetPower()) // 1000 first blocks not a validator ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 1) + // Validator created + got := sh(ctx, NewTestMsgCreateValidator(addr, val, sdk.NewInt(amt))) + require.True(t, got.IsOK()) + stake.EndBlocker(ctx, sk) + require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.SubRaw(amt)}}) + require.Equal(t, sdk.NewDec(amt), sk.Validator(ctx, addr).GetPower()) + // Now a validator, for two blocks keeper.handleValidatorSignature(ctx, val.Address(), 100, true) ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 2) @@ -313,7 +308,7 @@ func TestHandleNewValidator(t *testing.T) { require.True(t, found) require.Equal(t, keeper.SignedBlocksWindow(ctx)+1, info.StartHeight) require.Equal(t, int64(2), info.IndexOffset) - require.Equal(t, int64(1), info.SignedBlocksCounter) + require.Equal(t, int64(1), info.MissedBlocksCounter) require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) // validator should be bonded still, should not have been jailed or slashed @@ -332,10 +327,9 @@ func TestHandleAlreadyJailed(t *testing.T) { amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) sh := stake.NewHandler(sk) - got := sh(ctx, newTestMsgCreateValidator(addr, val, amt)) + got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) require.True(t, got.IsOK()) - validatorUpdates := stake.EndBlocker(ctx, sk) - keeper.AddValidators(ctx, validatorUpdates) + stake.EndBlocker(ctx, sk) // 1000 first blocks OK height := int64(0) @@ -369,3 +363,110 @@ func TestHandleAlreadyJailed(t *testing.T) { require.Equal(t, amtInt-1, validator.GetTokens().RoundInt64()) } + +// Test a validator dipping in and out of the validator set +// Ensure that missed blocks are tracked correctly and that +// the start height of the signing info is reset correctly +func TestValidatorDippingInAndOut(t *testing.T) { + + // initial setup + // keeperTestParams set the SignedBlocksWindow to 1000 and MaxMissedBlocksPerWindow to 500 + ctx, _, sk, _, keeper := createTestInput(t, keeperTestParams()) + params := sk.GetParams(ctx) + params.MaxValidators = 1 + sk.SetParams(ctx, params) + amtInt := int64(100) + addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) + consAddr := sdk.ConsAddress(addr) + sh := stake.NewHandler(sk) + got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) + require.True(t, got.IsOK()) + stake.EndBlocker(ctx, sk) + + // 100 first blocks OK + height := int64(0) + for ; height < int64(100); height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt, true) + } + + // validator kicked out of validator set + newAmt := int64(101) + got = sh(ctx, NewTestMsgCreateValidator(addrs[1], pks[1], sdk.NewInt(newAmt))) + require.True(t, got.IsOK()) + validatorUpdates := stake.EndBlocker(ctx, sk) + require.Equal(t, 2, len(validatorUpdates)) + validator, _ := sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Unbonding, validator.Status) + + // 600 more blocks happened + height = int64(700) + ctx = ctx.WithBlockHeight(height) + + // validator added back in + got = sh(ctx, newTestMsgDelegate(sdk.AccAddress(addrs[2]), addrs[0], sdk.NewInt(2))) + require.True(t, got.IsOK()) + validatorUpdates = stake.EndBlocker(ctx, sk) + require.Equal(t, 2, len(validatorUpdates)) + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Bonded, validator.Status) + newAmt = int64(102) + + // validator misses a block + keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false) + height++ + + // shouldn't be jailed/kicked yet + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Bonded, validator.Status) + + // validator misses 500 more blocks, 501 total + latest := height + for ; height < latest+500; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false) + } + + // should now be jailed & kicked + stake.EndBlocker(ctx, sk) + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Unbonding, validator.Status) + + // check all the signing information + signInfo, found := keeper.getValidatorSigningInfo(ctx, consAddr) + require.True(t, found) + require.Equal(t, int64(0), signInfo.MissedBlocksCounter) + require.Equal(t, int64(0), signInfo.IndexOffset) + // array should be cleared + for offset := int64(0); offset < keeper.SignedBlocksWindow(ctx); offset++ { + missed := keeper.getValidatorMissedBlockBitArray(ctx, consAddr, offset) + require.False(t, missed) + } + + // some blocks pass + height = int64(5000) + ctx = ctx.WithBlockHeight(height) + + // validator rejoins and starts signing again + sk.Unjail(ctx, consAddr) + keeper.handleValidatorSignature(ctx, val.Address(), newAmt, true) + height++ + + // validator should not be kicked since we reset counter/array when it was jailed + stake.EndBlocker(ctx, sk) + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Bonded, validator.Status) + + // validator misses 501 blocks + latest = height + for ; height < latest+501; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false) + } + + // validator should now be jailed & kicked + stake.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 ec453cedca11..750e8825f12b 100644 --- a/x/slashing/keys.go +++ b/x/slashing/keys.go @@ -9,10 +9,10 @@ import ( // key prefix bytes var ( - ValidatorSigningInfoKey = []byte{0x01} // Prefix for signing info - ValidatorSigningBitArrayKey = []byte{0x02} // Prefix for signature bit array - ValidatorSlashingPeriodKey = []byte{0x03} // Prefix for slashing period - AddrPubkeyRelationKey = []byte{0x04} // Prefix for address-pubkey relation + ValidatorSigningInfoKey = []byte{0x01} // Prefix for signing info + ValidatorMissedBlockBitArrayKey = []byte{0x02} // Prefix for missed block bit array + ValidatorSlashingPeriodKey = []byte{0x03} // Prefix for slashing period + AddrPubkeyRelationKey = []byte{0x04} // Prefix for address-pubkey relation ) // stored by *Tendermint* address (not operator address) @@ -20,11 +20,25 @@ func GetValidatorSigningInfoKey(v sdk.ConsAddress) []byte { return append(ValidatorSigningInfoKey, v.Bytes()...) } +// extract the address from a validator signing info key +func GetValidatorSigningInfoAddress(key []byte) (v sdk.ConsAddress) { + addr := key[1:] + if len(addr) != sdk.AddrLen { + panic("unexpected key length") + } + return sdk.ConsAddress(addr) +} + +// stored by *Tendermint* address (not operator address) +func GetValidatorMissedBlockBitArrayPrefixKey(v sdk.ConsAddress) []byte { + return append(ValidatorMissedBlockBitArrayKey, v.Bytes()...) +} + // stored by *Tendermint* address (not operator address) -func GetValidatorSigningBitArrayKey(v sdk.ConsAddress, i int64) []byte { +func GetValidatorMissedBlockBitArrayKey(v sdk.ConsAddress, i int64) []byte { b := make([]byte, 8) binary.LittleEndian.PutUint64(b, uint64(i)) - return append(ValidatorSigningBitArrayKey, append(v.Bytes(), b...)...) + return append(GetValidatorMissedBlockBitArrayPrefixKey(v), b...) } // stored by *Tendermint* address (not operator address) diff --git a/x/slashing/msg.go b/x/slashing/msg.go index 3ba7f9273aa8..51864d4e2656 100644 --- a/x/slashing/msg.go +++ b/x/slashing/msg.go @@ -8,7 +8,7 @@ import ( var cdc = codec.New() // name to identify transaction types -const MsgType = "slashing" +const MsgRoute = "slashing" // verify interface at compile time var _ sdk.Msg = &MsgUnjail{} @@ -25,8 +25,8 @@ func NewMsgUnjail(validatorAddr sdk.ValAddress) MsgUnjail { } //nolint -func (msg MsgUnjail) Type() string { return MsgType } -func (msg MsgUnjail) Name() string { return "unjail" } +func (msg MsgUnjail) Route() string { return MsgRoute } +func (msg MsgUnjail) Type() string { return "unjail" } func (msg MsgUnjail) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{sdk.AccAddress(msg.ValidatorAddr)} } diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go index 1adf49abc12f..291351742f39 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/signing_info.go @@ -15,45 +15,88 @@ func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress found = false return } - k.cdc.MustUnmarshalBinary(bz, &info) + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &info) found = true return } +// Stored by *validator* address (not operator address) +func (k Keeper) iterateValidatorSigningInfos(ctx sdk.Context, handler func(address sdk.ConsAddress, info ValidatorSigningInfo) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, ValidatorSigningInfoKey) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + address := GetValidatorSigningInfoAddress(iter.Key()) + var info ValidatorSigningInfo + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &info) + if handler(address, info) { + break + } + } +} + // Stored by *validator* address (not operator address) func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress, info ValidatorSigningInfo) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(info) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(info) store.Set(GetValidatorSigningInfoKey(address), bz) } // Stored by *validator* address (not operator address) -func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64) (signed bool) { +func (k Keeper) getValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64) (missed bool) { store := ctx.KVStore(k.storeKey) - bz := store.Get(GetValidatorSigningBitArrayKey(address, index)) + bz := store.Get(GetValidatorMissedBlockBitArrayKey(address, index)) if bz == nil { - // lazy: treat empty key as unsigned - signed = false + // lazy: treat empty key as not missed + missed = false return } - k.cdc.MustUnmarshalBinary(bz, &signed) + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &missed) return } // Stored by *validator* address (not operator address) -func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64, signed bool) { +func (k Keeper) iterateValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, handler func(index int64, missed bool) (stop bool)) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(signed) - store.Set(GetValidatorSigningBitArrayKey(address, index), bz) + index := int64(0) + // Array may be sparse + for ; index < k.SignedBlocksWindow(ctx); index++ { + var missed bool + bz := store.Get(GetValidatorMissedBlockBitArrayKey(address, index)) + if bz == nil { + continue + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &missed) + if handler(index, missed) { + break + } + } +} + +// Stored by *validator* address (not operator address) +func (k Keeper) setValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64, missed bool) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(missed) + store.Set(GetValidatorMissedBlockBitArrayKey(address, index), bz) +} + +// Stored by *validator* address (not operator address) +func (k Keeper) clearValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, GetValidatorMissedBlockBitArrayPrefixKey(address)) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + store.Delete(iter.Key()) + } } // Construct a new `ValidatorSigningInfo` struct -func NewValidatorSigningInfo(startHeight int64, indexOffset int64, jailedUntil time.Time, signedBlocksCounter int64) ValidatorSigningInfo { +func NewValidatorSigningInfo(startHeight int64, indexOffset int64, jailedUntil time.Time, missedBlocksCounter int64) ValidatorSigningInfo { return ValidatorSigningInfo{ StartHeight: startHeight, IndexOffset: indexOffset, JailedUntil: jailedUntil, - SignedBlocksCounter: signedBlocksCounter, + MissedBlocksCounter: missedBlocksCounter, } } @@ -62,11 +105,11 @@ 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 - SignedBlocksCounter int64 `json:"signed_blocks_counter"` // signed blocks counter (to avoid scanning the array every time) + 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, signed blocks counter: %d", - i.StartHeight, i.IndexOffset, i.JailedUntil, i.SignedBlocksCounter) + return fmt.Sprintf("Start height: %d, index offset: %d, jailed until: %v, missed blocks counter: %d", + i.StartHeight, i.IndexOffset, i.JailedUntil, i.MissedBlocksCounter) } diff --git a/x/slashing/signing_info_test.go b/x/slashing/signing_info_test.go index 123457d5d582..15863ebc70b1 100644 --- a/x/slashing/signing_info_test.go +++ b/x/slashing/signing_info_test.go @@ -17,7 +17,7 @@ func TestGetSetValidatorSigningInfo(t *testing.T) { StartHeight: int64(4), IndexOffset: int64(3), JailedUntil: time.Unix(2, 0), - SignedBlocksCounter: int64(10), + MissedBlocksCounter: int64(10), } keeper.setValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0]), newInfo) info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0])) @@ -25,14 +25,14 @@ func TestGetSetValidatorSigningInfo(t *testing.T) { require.Equal(t, info.StartHeight, int64(4)) require.Equal(t, info.IndexOffset, int64(3)) require.Equal(t, info.JailedUntil, time.Unix(2, 0).UTC()) - require.Equal(t, info.SignedBlocksCounter, int64(10)) + require.Equal(t, info.MissedBlocksCounter, int64(10)) } -func TestGetSetValidatorSigningBitArray(t *testing.T) { +func TestGetSetValidatorMissedBlockBitArray(t *testing.T) { ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) - signed := keeper.getValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) - require.False(t, signed) // treat empty key as unsigned - keeper.setValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0, true) - signed = keeper.getValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) - require.True(t, signed) // now should be signed + missed := keeper.getValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) + require.False(t, missed) // treat empty key as not missed + keeper.setValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0, true) + missed = keeper.getValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) + require.True(t, missed) // now should be missed } diff --git a/x/slashing/simulation/invariants.go b/x/slashing/simulation/invariants.go index 637a8064bc7b..0aa0ed1e5c8b 100644 --- a/x/slashing/simulation/invariants.go +++ b/x/slashing/simulation/invariants.go @@ -5,10 +5,10 @@ import ( "github.com/cosmos/cosmos-sdk/x/mock/simulation" ) +// TODO Any invariants to check here? // AllInvariants tests all slashing invariants func AllInvariants() simulation.Invariant { - return func(app *baseapp.BaseApp) error { - // TODO Any invariants to check here? + return func(_ *baseapp.BaseApp) error { return nil } } diff --git a/x/slashing/slashing_period.go b/x/slashing/slashing_period.go index 0595d5eeb25c..4caf5d7c9d36 100644 --- a/x/slashing/slashing_period.go +++ b/x/slashing/slashing_period.go @@ -37,6 +37,7 @@ func (k Keeper) capBySlashingPeriod(ctx sdk.Context, address sdk.ConsAddress, fr // 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 @@ -50,6 +51,21 @@ func (k Keeper) getValidatorSlashingPeriodForHeight(ctx sdk.Context, address sdk 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 + } + } +} + // 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 @@ -60,14 +76,14 @@ func (k Keeper) addOrUpdateValidatorSlashingPeriod(ctx sdk.Context, slashingPeri SlashedSoFar: slashingPeriod.SlashedSoFar, } store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(slashingPeriodValue) + 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.MustUnmarshalBinary(value, &slashingPeriodValue) + 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{ diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index af2a3f7d82d1..72ee58e1b26b 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -21,6 +21,7 @@ import ( "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" ) // TODO remove dependencies on staking (should only refer to validator set type from sdk) @@ -68,9 +69,9 @@ 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() - accountMapper := auth.NewAccountMapper(cdc, keyAcc, auth.ProtoBaseAccount) + accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, auth.ProtoBaseAccount) - ck := bank.NewBaseKeeper(accountMapper) + 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() @@ -87,10 +88,11 @@ func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, s } require.Nil(t, err) paramstore := paramsKeeper.Subspace(DefaultParamspace) - keeper := NewKeeper(cdc, keySlashing, sk, paramstore, DefaultCodespace) + keeper := NewKeeper(cdc, keySlashing, &sk, paramstore, DefaultCodespace) + sk.SetHooks(keeper.Hooks()) require.NotPanics(t, func() { - InitGenesis(ctx, keeper, GenesisState{defaults}, genesis) + InitGenesis(ctx, keeper, GenesisState{defaults, nil, nil, nil}, genesis) }) return ctx, ck, sk, paramstore, keeper @@ -111,7 +113,7 @@ func testAddr(addr string) sdk.AccAddress { return res } -func newTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt sdk.Int) stake.MsgCreateValidator { +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{}, @@ -119,7 +121,7 @@ func newTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt DelegatorAddr: sdk.AccAddress(address), ValidatorAddr: address, PubKey: pubKey, - Delegation: sdk.NewCoin("steak", amt), + Delegation: sdk.NewCoin(stakeTypes.DefaultBondDenom, amt), } } @@ -127,6 +129,6 @@ func newTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, delAmoun return stake.MsgDelegate{ DelegatorAddr: delAddr, ValidatorAddr: valAddr, - Delegation: sdk.NewCoin("steak", delAmount), + Delegation: sdk.NewCoin(stakeTypes.DefaultBondDenom, delAmount), } } diff --git a/x/slashing/tick.go b/x/slashing/tick.go index 007d93788772..03bd094afcd7 100644 --- a/x/slashing/tick.go +++ b/x/slashing/tick.go @@ -18,7 +18,7 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, sk Keeper) (tags tags = sdk.NewTags("height", heightBytes) // Iterate over all the validators which *should* have signed this block - // Store whether or not they have actually signed it and slash/unbond any + // store whether or not they have actually signed it and slash/unbond any // which have missed too many blocks in a row (downtime slashing) for _, voteInfo := range req.LastCommitInfo.GetVotes() { sk.handleValidatorSignature(ctx, voteInfo.Validator.Address, voteInfo.Validator.Power, voteInfo.SignedLastBlock) diff --git a/x/slashing/tick_test.go b/x/slashing/tick_test.go index 085ce9eba88c..c6590c94e751 100644 --- a/x/slashing/tick_test.go +++ b/x/slashing/tick_test.go @@ -17,10 +17,9 @@ 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 := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(addr, pk, amt)) require.True(t, got.IsOK()) - validatorUpdates := stake.EndBlocker(ctx, sk) - keeper.AddValidators(ctx, validatorUpdates) + stake.EndBlocker(ctx, sk) require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower())) @@ -45,7 +44,7 @@ func TestBeginBlocker(t *testing.T) { require.Equal(t, ctx.BlockHeight(), info.StartHeight) require.Equal(t, int64(1), info.IndexOffset) require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) - require.Equal(t, int64(1), info.SignedBlocksCounter) + require.Equal(t, int64(0), info.MissedBlocksCounter) height := int64(0) diff --git a/x/stake/app_test.go b/x/stake/app_test.go index ac0bd8fcc587..b24018a6cedf 100644 --- a/x/stake/app_test.go +++ b/x/stake/app_test.go @@ -3,31 +3,15 @@ package stake import ( "testing" + "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/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/params" - "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/ed25519" -) - -var ( - priv1 = ed25519.GenPrivKey() - addr1 = sdk.AccAddress(priv1.PubKey().Address()) - priv2 = ed25519.GenPrivKey() - addr2 = sdk.AccAddress(priv2.PubKey().Address()) - addr3 = sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) - priv4 = ed25519.GenPrivKey() - addr4 = sdk.AccAddress(priv4.PubKey().Address()) - coins = sdk.Coins{sdk.NewCoin("foocoin", sdk.NewInt(10))} - fee = auth.NewStdFee( - 100000, - sdk.Coins{sdk.NewCoin("foocoin", sdk.NewInt(0))}..., - ) - - commissionMsg = NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" ) // getMockApp returns an initialized mock application for this module. @@ -42,7 +26,7 @@ func getMockApp(t *testing.T) (*mock.App, Keeper) { keyParams := sdk.NewKVStoreKey("params") tkeyParams := sdk.NewTransientStoreKey("transient_params") - bankKeeper := bank.NewBaseKeeper(mApp.AccountMapper) + bankKeeper := bank.NewBaseKeeper(mApp.AccountKeeper) pk := params.NewKeeper(mApp.Cdc, keyParams, tkeyParams) keeper := NewKeeper(mApp.Cdc, keyStake, tkeyStake, bankKeeper, pk.Subspace(DefaultParamspace), mApp.RegisterCodespace(DefaultCodespace)) @@ -118,8 +102,8 @@ func checkDelegation( func TestStakeMsgs(t *testing.T) { mApp, keeper := getMockApp(t) - genCoin := sdk.NewInt64Coin("steak", 42) - bondCoin := sdk.NewInt64Coin("steak", 10) + genCoin := sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 42) + bondCoin := sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10) acc1 := &auth.BaseAccount{ Address: addr1, @@ -155,7 +139,7 @@ func TestStakeMsgs(t *testing.T) { addr1, sdk.ValAddress(addr2), priv2.PubKey(), bondCoin, description, commissionMsg, ) - mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsgOnBehalfOf}, []int64{0, 1}, []int64{1, 0}, true, true, priv1, priv2) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsgOnBehalfOf}, []int64{0, 0}, []int64{1, 0}, true, true, priv1, priv2) mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin).Minus(bondCoin)}) mApp.BeginBlock(abci.RequestBeginBlock{}) @@ -179,13 +163,13 @@ func TestStakeMsgs(t *testing.T) { mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin}) delegateMsg := NewMsgDelegate(addr2, sdk.ValAddress(addr1), bondCoin) - mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{delegateMsg}, []int64{1}, []int64{1}, true, true, priv2) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{delegateMsg}, []int64{0}, []int64{1}, true, true, priv2) mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), true, sdk.NewDec(10)) // begin unbonding beginUnbondingMsg := NewMsgBeginUnbonding(addr2, sdk.ValAddress(addr1), sdk.NewDec(10)) - mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{2}, true, true, priv2) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{0}, []int64{2}, true, true, priv2) // delegation should exist anymore checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), false, sdk.Dec{}) diff --git a/x/stake/client/cli/flags.go b/x/stake/client/cli/flags.go index bec76298f3fc..d571bef9eb0c 100644 --- a/x/stake/client/cli/flags.go +++ b/x/stake/client/cli/flags.go @@ -15,7 +15,7 @@ const ( FlagPubKey = "pubkey" FlagAmount = "amount" FlagSharesAmount = "shares-amount" - FlagSharesPercent = "shares-percent" + FlagSharesFraction = "shares-fraction" FlagMoniker = "moniker" FlagIdentity = "identity" @@ -25,15 +25,21 @@ const ( FlagCommissionRate = "commission-rate" FlagCommissionMaxRate = "commission-max-rate" FlagCommissionMaxChangeRate = "commission-max-change-rate" + + FlagGenesisFormat = "genesis-format" + FlagNodeID = "node-id" + FlagIP = "ip" + + FlagOutputDocument = "output-document" // inspired by wget -O ) // common flagsets to add to various functions var ( - fsPk = flag.NewFlagSet("", flag.ContinueOnError) - fsAmount = flag.NewFlagSet("", flag.ContinueOnError) + FsPk = flag.NewFlagSet("", flag.ContinueOnError) + FsAmount = flag.NewFlagSet("", flag.ContinueOnError) fsShares = flag.NewFlagSet("", flag.ContinueOnError) fsDescriptionCreate = flag.NewFlagSet("", flag.ContinueOnError) - fsCommissionCreate = flag.NewFlagSet("", flag.ContinueOnError) + FsCommissionCreate = flag.NewFlagSet("", flag.ContinueOnError) fsCommissionUpdate = flag.NewFlagSet("", flag.ContinueOnError) fsDescriptionEdit = flag.NewFlagSet("", flag.ContinueOnError) fsValidator = flag.NewFlagSet("", flag.ContinueOnError) @@ -42,18 +48,18 @@ var ( ) func init() { - fsPk.String(FlagPubKey, "", "Go-Amino encoded hex PubKey of the validator. For Ed25519 the go-amino prepend hex is 1624de6220") - fsAmount.String(FlagAmount, "", "Amount of coins to bond") + FsPk.String(FlagPubKey, "", "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(FlagSharesPercent, "", "Percent of source-shares to either unbond or redelegate as a positive integer or decimal >0 and <=1") + 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") 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)") + 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") diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index ad8030c9315b..24e44999606b 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -114,6 +114,76 @@ func GetCmdQueryValidators(storeName string, cdc *codec.Codec) *cobra.Command { return cmd } +// GetCmdQueryValidatorUnbondingDelegations implements the query all unbonding delegatations from a validator command. +func GetCmdQueryValidatorUnbondingDelegations(queryRoute 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", queryRoute), + 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(queryRoute 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", queryRoute), + 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{ @@ -139,6 +209,7 @@ func GetCmdQueryDelegation(storeName string, cdc *codec.Codec) *cobra.Command { } // parse out the delegation + delegation, err := types.UnmarshalDelegation(cdc, key, res) if err != nil { return err @@ -215,6 +286,41 @@ func GetCmdQueryDelegations(storeName string, cdc *codec.Codec) *cobra.Command { return cmd } +// GetCmdQueryValidatorDelegations implements the command to query all the +// delegations to a specific validator. +func GetCmdQueryValidatorDelegations(queryRoute 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", queryRoute), 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 { @@ -292,7 +398,7 @@ func GetCmdQueryUnbondingDelegations(storeName string, cdc *codec.Codec) *cobra. return err } - // parse out the validators + // parse out the unbonding delegations var ubds []stake.UnbondingDelegation for _, kv := range resKVs { ubd := types.MustUnmarshalUBD(cdc, kv.Key, kv.Value) diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index 9640c5c05069..a42fcf6d027a 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -87,7 +87,15 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { ) } - if cliCtx.GenerateOnly { + if viper.GetBool(FlagGenesisFormat) { + ip := viper.GetString(FlagIP) + nodeID := viper.GetString(FlagNodeID) + if nodeID != "" && ip != "" { + txBldr = txBldr.WithMemo(fmt.Sprintf("%s@%s:26656", nodeID, ip)) + } + } + + if viper.GetBool(FlagGenesisFormat) || cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, true) } @@ -96,11 +104,15 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { }, } - cmd.Flags().AddFlagSet(fsPk) - cmd.Flags().AddFlagSet(fsAmount) + cmd.Flags().AddFlagSet(FsPk) + cmd.Flags().AddFlagSet(FsAmount) cmd.Flags().AddFlagSet(fsDescriptionCreate) - cmd.Flags().AddFlagSet(fsCommissionCreate) + 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.MarkFlagRequired(client.FlagFrom) return cmd } @@ -193,32 +205,17 @@ func GetCmdDelegate(cdc *codec.Codec) *cobra.Command { }, } - cmd.Flags().AddFlagSet(fsAmount) + cmd.Flags().AddFlagSet(FsAmount) cmd.Flags().AddFlagSet(fsValidator) return cmd } -// GetCmdRedelegate implements the redelegate validator command. +// GetCmdRedelegate the begin redelegation command. func GetCmdRedelegate(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "redelegate", Short: "redelegate illiquid tokens from one validator to another", - } - - cmd.AddCommand( - client.PostCommands( - GetCmdBeginRedelegate(storeName, cdc), - )...) - - return cmd -} - -// GetCmdBeginRedelegate the begin redelegation command. -func GetCmdBeginRedelegate(storeName string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "begin", - Short: "begin redelegation", RunE: func(cmd *cobra.Command, args []string) error { txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) cliCtx := context.NewCLIContext(). @@ -244,9 +241,9 @@ func GetCmdBeginRedelegate(storeName string, cdc *codec.Codec) *cobra.Command { // get the shares amount sharesAmountStr := viper.GetString(FlagSharesAmount) - sharesPercentStr := viper.GetString(FlagSharesPercent) + sharesFractionStr := viper.GetString(FlagSharesFraction) sharesAmount, err := getShares( - storeName, cdc, sharesAmountStr, sharesPercentStr, + storeName, cdc, sharesAmountStr, sharesFractionStr, delAddr, valSrcAddr, ) if err != nil { @@ -273,22 +270,7 @@ func GetCmdBeginRedelegate(storeName string, cdc *codec.Codec) *cobra.Command { func GetCmdUnbond(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "unbond", - Short: "begin or complete unbonding shares from a validator", - } - - cmd.AddCommand( - client.PostCommands( - GetCmdBeginUnbonding(storeName, cdc), - )...) - - return cmd -} - -// GetCmdBeginUnbonding implements the begin unbonding validator command. -func GetCmdBeginUnbonding(storeName string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "begin", - Short: "begin unbonding", + Short: "unbond shares from a validator", RunE: func(cmd *cobra.Command, args []string) error { txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) cliCtx := context.NewCLIContext(). @@ -307,9 +289,9 @@ func GetCmdBeginUnbonding(storeName string, cdc *codec.Codec) *cobra.Command { // get the shares amount sharesAmountStr := viper.GetString(FlagSharesAmount) - sharesPercentStr := viper.GetString(FlagSharesPercent) + sharesFractionStr := viper.GetString(FlagSharesFraction) sharesAmount, err := getShares( - storeName, cdc, sharesAmountStr, sharesPercentStr, + storeName, cdc, sharesAmountStr, sharesFractionStr, delAddr, valAddr, ) if err != nil { diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index a8fbfecf4d76..8c669ee662a4 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -1,15 +1,14 @@ package rest import ( - "fmt" "net/http" "strings" "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" "github.com/cosmos/cosmos-sdk/x/stake/tags" "github.com/gorilla/mux" @@ -19,10 +18,22 @@ const storeName = "stake" func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) { - // Get all delegations (delegation, undelegation and redelegation) from a delegator + // Get all delegations from a delegator r.HandleFunc( - "/stake/delegators/{delegatorAddr}", - delegatorHandlerFn(cliCtx, cdc), + "/stake/delegators/{delegatorAddr}/delegations", + delegatorDelegationsHandlerFn(cliCtx, cdc), + ).Methods("GET") + + // Get all unbonding delegations from a delegator + r.HandleFunc( + "/stake/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 @@ -58,89 +69,78 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Co // Get all validators r.HandleFunc( "/stake/validators", - validatorsHandlerFn(cliCtx), + validatorsHandlerFn(cliCtx, cdc), ).Methods("GET") // Get a single validator info r.HandleFunc( - "/stake/validators/{addr}", + "/stake/validators/{validatorAddr}", validatorHandlerFn(cliCtx, cdc), ).Methods("GET") + // Get all delegations to a validator + r.HandleFunc( + "/stake/validators/{validatorAddr}/delegations", + validatorDelegationsHandlerFn(cliCtx, cdc), + ).Methods("GET") + + // Get all unbonding delegations from a validator + r.HandleFunc( + "/stake/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", - poolHandlerFn(cliCtx), + poolHandlerFn(cliCtx, cdc), ).Methods("GET") // Get the current staking parameter values r.HandleFunc( "/stake/parameters", - paramsHandlerFn(cliCtx), + paramsHandlerFn(cliCtx, cdc), ).Methods("GET") } // HTTP request handler to query a delegator delegations -func delegatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - - vars := mux.Vars(r) - bech32delegator := vars["delegatorAddr"] - - w.Header().Set("Content-Type", "application/json") - - delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - params := stake.QueryDelegatorParams{ - DelegatorAddr: delegatorAddr, - } - - bz, err := cdc.MarshalJSON(params) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - res, err := cliCtx.QueryWithData("custom/stake/delegator", bz) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) +func delegatorDelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { + return queryDelegator(cliCtx, cdc, "custom/stake/delegatorDelegations") +} - return - } +// 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") +} - w.Write(res) - } +// 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") } // HTTP request handler to query all staking txs (msgs) from a delegator func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var output []byte var typesQuerySlice []string vars := mux.Vars(r) delegatorAddr := vars["delegatorAddr"] - w.Header().Set("Content-Type", "application/json") - _, err := sdk.AccAddressFromBech32(delegatorAddr) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } node, err := cliCtx.GetNode() if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("Couldn't get current Node information. Error: %s", err.Error()))) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -182,288 +182,92 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Han for _, action := range actions { foundTxs, errQuery := queryTxs(node, cliCtx, cdc, action, delegatorAddr) if errQuery != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(errQuery.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) } txs = append(txs, foundTxs...) } - output, err = cdc.MarshalJSON(txs) + res, err := cdc.MarshalJSON(txs) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - w.Write(output) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // HTTP request handler to query an unbonding-delegation func unbondingDelegationHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - bech32delegator := vars["delegatorAddr"] - bech32validator := vars["validatorAddr"] - - w.Header().Set("Content-Type", "application/json") - - delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - validatorAddr, err := sdk.ValAddressFromBech32(bech32validator) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - params := stake.QueryBondsParams{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - } - - bz, err := cdc.MarshalJSON(params) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - res, err := cliCtx.QueryWithData("custom/stake/unbondingDelegation", bz) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - - return - } - - w.Write(res) - } + return queryBonds(cliCtx, cdc, "custom/stake/unbondingDelegation") } -// HTTP request handler to query a bonded validator +// HTTP request handler to query a delegation func delegationHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - // read parameters - vars := mux.Vars(r) - bech32delegator := vars["delegatorAddr"] - bech32validator := vars["validatorAddr"] - - w.Header().Set("Content-Type", "application/json") - - delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - validatorAddr, err := sdk.ValAddressFromBech32(bech32validator) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - params := stake.QueryBondsParams{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - } - - bz, err := cdc.MarshalJSON(params) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - res, err := cliCtx.QueryWithData("custom/stake/delegation", bz) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - - return - } - - w.Write(res) - } + return queryBonds(cliCtx, cdc, "custom/stake/delegation") } // HTTP request handler to query all delegator bonded validators func delegatorValidatorsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - // read parameters - vars := mux.Vars(r) - bech32delegator := vars["delegatorAddr"] - - w.Header().Set("Content-Type", "application/json") - - delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - params := stake.QueryDelegatorParams{ - DelegatorAddr: delegatorAddr, - } - - bz, err := cdc.MarshalJSON(params) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - res, err := cliCtx.QueryWithData("custom/stake/delegatorValidators", bz) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - - return - } - - w.Write(res) - } + return queryDelegator(cliCtx, cdc, "custom/stake/delegatorValidators") } // HTTP request handler to get information from a currently bonded validator func delegatorValidatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - - vars := mux.Vars(r) - bech32delegator := vars["delegatorAddr"] - bech32validator := vars["validatorAddr"] - - w.Header().Set("Content-Type", "application/json") - - delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) - validatorAddr, err := sdk.ValAddressFromBech32(bech32validator) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - params := stake.QueryBondsParams{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - } - - bz, err := cdc.MarshalJSON(params) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - res, err := cliCtx.QueryWithData("custom/stake/delegatorValidator", bz) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - - return - } - - w.Write(res) - } + return queryBonds(cliCtx, cdc, "custom/stake/delegatorValidator") } // HTTP request handler to query list of validators -func validatorsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { +func validatorsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - - w.Header().Set("Content-Type", "application/json") - res, err := cliCtx.QueryWithData("custom/stake/validators", nil) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - - w.Header().Set("Content-Type", "application/json") - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // HTTP request handler to query the validator information from a given validator address func validatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - - vars := mux.Vars(r) - bech32validatorAddr := vars["addr"] - - w.Header().Set("Content-Type", "application/json") - - validatorAddr, err := sdk.ValAddressFromBech32(bech32validatorAddr) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - params := stake.QueryValidatorParams{ - ValidatorAddr: validatorAddr, - } - - bz, err := cdc.MarshalJSON(params) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } + return queryValidator(cliCtx, cdc, "custom/stake/validator") +} - res, err := cliCtx.QueryWithData("custom/stake/validator", bz) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) +// 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 - } +// 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") +} - w.Write(res) - } +// 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") } // HTTP request handler to query the pool information -func poolHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { +func poolHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - - w.Header().Set("Content-Type", "application/json") - res, err := cliCtx.QueryWithData("custom/stake/pool", nil) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // HTTP request handler to query the staking params values -func paramsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { +func paramsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - - w.Header().Set("Content-Type", "application/json") - res, err := cliCtx.QueryWithData("custom/stake/parameters", nil) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index 427fb5e19e14..437d40e77814 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -2,7 +2,6 @@ package rest import ( "bytes" - "fmt" "io/ioutil" "net/http" @@ -66,15 +65,13 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte body, err := ioutil.ReadAll(r.Body) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } err = cdc.UnmarshalJSON(body, &req) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -85,8 +82,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte info, err := kb.Get(baseReq.Name) if err != nil { - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } @@ -99,13 +95,13 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte for _, msg := range req.Delegations { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -126,7 +122,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte for _, msg := range req.BeginRedelegates { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -137,18 +133,18 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } shares, err := sdk.NewDecFromStr(msg.SharesAmount) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -165,7 +161,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte for _, msg := range req.BeginUnbondings { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -176,13 +172,13 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } shares, err := sdk.NewDecFromStr(msg.SharesAmount) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -266,12 +262,6 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte results[i] = res } - output, err := codec.MarshalJSONIndent(cdc, results[:]) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(output) + utils.PostProcessResponse(w, cdc, results, cliCtx.Indent) } } diff --git a/x/stake/client/rest/utils.go b/x/stake/client/rest/utils.go index e7b8891eabe5..7f6edc193834 100644 --- a/x/stake/client/rest/utils.go +++ b/x/stake/client/rest/utils.go @@ -2,11 +2,16 @@ package rest import ( "fmt" + "net/http" "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" "github.com/cosmos/cosmos-sdk/x/stake/tags" + "github.com/gorilla/mux" rpcclient "github.com/tendermint/tendermint/rpc/client" ) @@ -43,3 +48,89 @@ func queryTxs(node rpcclient.Client, cliCtx context.CLIContext, cdc *codec.Codec return tx.FormatTxResults(cdc, res.Txs) } + +func queryBonds(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"] + bech32validator := vars["validatorAddr"] + + delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) + validatorAddr, err := sdk.ValAddressFromBech32(bech32validator) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + params := stake.NewQueryBondsParams(delegatorAddr, validatorAddr) + + 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 queryDelegator(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"] + + delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + params := stake.NewQueryDelegatorParams(delegatorAddr) + + 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 queryValidator(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + bech32validatorAddr := vars["validatorAddr"] + + validatorAddr, err := sdk.ValAddressFromBech32(bech32validatorAddr) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + params := stake.NewQueryValidatorParams(validatorAddr) + + 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) + } +} diff --git a/x/stake/genesis.go b/x/stake/genesis.go index 3cd104e667bc..7be8eff3a4fc 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -2,13 +2,13 @@ package stake 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/pkg/errors" ) // InitGenesis sets the pool and parameters for the provided keeper and @@ -26,52 +26,103 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ keeper.SetPool(ctx, data.Pool) keeper.SetParams(ctx, data.Params) - keeper.InitIntraTxCounter(ctx) + keeper.SetIntraTxCounter(ctx, data.IntraTxCounter) + keeper.SetLastTotalPower(ctx, data.LastTotalPower) for i, validator := range data.Validators { - validator.BondIntraTxCounter = int16(i) // set the intra-tx counter to the order the validators are presented - keeper.SetValidator(ctx, validator) - - if validator.Tokens.IsZero() { - return res, errors.Errorf("genesis validator cannot have zero pool shares, validator: %v", validator) - } - if validator.DelegatorShares.IsZero() { - return res, errors.Errorf("genesis validator cannot have zero delegator shares, validator: %v", validator) + // set the intra-tx counter to the order the validators are presented, if necessary + if !data.Exported { + validator.BondIntraTxCounter = int16(i) } + keeper.SetValidator(ctx, validator) // Manually set indices for the first time keeper.SetValidatorByConsAddr(ctx, validator) keeper.SetValidatorByPowerIndex(ctx, validator, data.Pool) + keeper.OnValidatorCreated(ctx, validator.OperatorAddr) + + // Set timeslice if necessary + if validator.Status == sdk.Unbonding { + keeper.InsertValidatorQueue(ctx, validator) + } } - for _, bond := range data.Bonds { - keeper.SetDelegation(ctx, bond) + for _, delegation := range data.Bonds { + keeper.SetDelegation(ctx, delegation) + keeper.OnDelegationCreated(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) + } + + 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) + } + + // don't need to run Tendermint updates if we exported + if data.Exported { + for _, lv := range data.LastValidatorPowers { + keeper.SetLastValidatorPower(ctx, lv.Address, lv.Power) + } + } else { + res = keeper.ApplyAndReturnValidatorSetUpdates(ctx) } - res = keeper.ApplyAndReturnValidatorSetUpdates(ctx) return } -// WriteGenesis returns a GenesisState for a given context and keeper. The +// ExportGenesis returns a GenesisState for a given context and keeper. The // GenesisState will contain the pool, params, validators, and bonds found in // the keeper. -func WriteGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { +func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { pool := keeper.GetPool(ctx) params := keeper.GetParams(ctx) + intraTxCounter := keeper.GetIntraTxCounter(ctx) + lastTotalPower := keeper.GetLastTotalPower(ctx) validators := keeper.GetAllValidators(ctx) bonds := keeper.GetAllDelegations(ctx) + var unbondingDelegations []types.UnbondingDelegation + keeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd types.UnbondingDelegation) (stop bool) { + unbondingDelegations = append(unbondingDelegations, ubd) + return false + }) + var redelegations []types.Redelegation + keeper.IterateRedelegations(ctx, func(_ int64, red types.Redelegation) (stop bool) { + redelegations = append(redelegations, red) + return false + }) + var lastValidatorPowers []types.LastValidatorPower + keeper.IterateLastValidatorPowers(ctx, func(addr sdk.ValAddress, power sdk.Int) (stop bool) { + lastValidatorPowers = append(lastValidatorPowers, types.LastValidatorPower{addr, power}) + return false + }) return types.GenesisState{ - Pool: pool, - Params: params, - Validators: validators, - Bonds: bonds, + Pool: pool, + Params: params, + IntraTxCounter: intraTxCounter, + LastTotalPower: lastTotalPower, + LastValidatorPowers: lastValidatorPowers, + Validators: validators, + Bonds: bonds, + UnbondingDelegations: unbondingDelegations, + Redelegations: redelegations, + Exported: true, } } // WriteValidators returns a slice of bonded genesis validators. func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisValidator) { - keeper.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) { + keeper.IterateLastValidators(ctx, func(_ int64, validator sdk.Validator) (stop bool) { vals = append(vals, tmtypes.GenesisValidator{ PubKey: validator.GetConsPubKey(), Power: validator.GetPower().RoundInt64(), @@ -100,20 +151,9 @@ func ValidateGenesis(data types.GenesisState) error { } func validateParams(params types.Params) error { - if params.GoalBonded.LTE(sdk.ZeroDec()) { - bondedPercent := params.GoalBonded.MulInt(sdk.NewInt(100)).String() - return fmt.Errorf("staking parameter GoalBonded should be positive, instead got %s percent", bondedPercent) - } - if params.GoalBonded.GT(sdk.OneDec()) { - bondedPercent := params.GoalBonded.MulInt(sdk.NewInt(100)).String() - return fmt.Errorf("staking parameter GoalBonded should be less than 100 percent, instead got %s percent", bondedPercent) - } if params.BondDenom == "" { return fmt.Errorf("staking parameter BondDenom can't be an empty string") } - if params.InflationMax.LT(params.InflationMin) { - return fmt.Errorf("staking parameter Max inflation must be greater than or equal to min inflation") - } return nil } @@ -128,11 +168,8 @@ func validateGenesisStateValidators(validators []types.Validator) (err error) { if val.Jailed && val.Status == sdk.Bonded { return fmt.Errorf("validator is bonded and jailed in genesis state: moniker %v, Address %v", val.Description.Moniker, val.ConsAddress()) } - if val.Tokens.IsZero() { - return fmt.Errorf("genesis validator cannot have zero pool shares, validator: %v", val) - } - if val.DelegatorShares.IsZero() { - return fmt.Errorf("genesis validator cannot have zero delegator shares, validator: %v", val) + if val.DelegatorShares.IsZero() && val.Status != sdk.Unbonding { + return fmt.Errorf("bonded/unbonded genesis validator cannot have zero delegator shares, validator: %v", val) } addrMap[strKey] = true } diff --git a/x/stake/genesis_test.go b/x/stake/genesis_test.go index 2bbd8e97a0f5..3f7295a7a033 100644 --- a/x/stake/genesis_test.go +++ b/x/stake/genesis_test.go @@ -22,29 +22,28 @@ func TestInitGenesis(t *testing.T) { pool.BondedTokens = sdk.NewDec(2) params := keeper.GetParams(ctx) + validators := make([]Validator, 2) var delegations []Delegation - validators := []Validator{ - NewValidator(sdk.ValAddress(keep.Addrs[0]), keep.PKs[0], Description{Moniker: "hoop"}), - NewValidator(sdk.ValAddress(keep.Addrs[1]), keep.PKs[1], Description{Moniker: "bloop"}), - } - genesisState := types.NewGenesisState(pool, params, validators, delegations) - _, err := InitGenesis(ctx, keeper, genesisState) - require.Error(t, err) - // initialize the validators + validators[0].OperatorAddr = sdk.ValAddress(keep.Addrs[0]) + validators[0].ConsPubKey = keep.PKs[0] + validators[0].Description = Description{Moniker: "hoop"} validators[0].Status = sdk.Bonded validators[0].Tokens = sdk.OneDec() 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].DelegatorShares = sdk.OneDec() - genesisState = types.NewGenesisState(pool, params, validators, delegations) + genesisState := types.NewGenesisState(pool, params, validators, delegations) vals, err := InitGenesis(ctx, keeper, genesisState) require.NoError(t, err) - actualGenesis := WriteGenesis(ctx, keeper) + actualGenesis := ExportGenesis(ctx, keeper) require.Equal(t, genesisState.Pool, actualGenesis.Pool) require.Equal(t, genesisState.Params, actualGenesis.Params) require.Equal(t, genesisState.Bonds, actualGenesis.Bonds) @@ -121,25 +120,11 @@ func TestValidateGenesis(t *testing.T) { wantErr bool }{ {"default", func(*types.GenesisState) {}, false}, - // validate params - {"200% goalbonded", func(data *types.GenesisState) { (*data).Params.GoalBonded = sdk.OneDec().Add(sdk.OneDec()) }, true}, - {"-67% goalbonded", func(data *types.GenesisState) { (*data).Params.GoalBonded = sdk.OneDec().Neg() }, true}, - {"no bond denom", func(data *types.GenesisState) { (*data).Params.BondDenom = "" }, true}, - {"min inflation > max inflation", func(data *types.GenesisState) { - (*data).Params.InflationMin = (*data).Params.InflationMax.Add(sdk.OneDec()) - }, true}, - {"min inflation = max inflation", func(data *types.GenesisState) { - (*data).Params.InflationMax = (*data).Params.InflationMin - }, false}, // validate genesis validators {"duplicate validator", func(data *types.GenesisState) { (*data).Validators = genValidators1 (*data).Validators = append((*data).Validators, genValidators1[0]) }, true}, - {"no pool shares", func(data *types.GenesisState) { - (*data).Validators = genValidators1 - (*data).Validators[0].Tokens = sdk.ZeroDec() - }, true}, {"no delegator shares", func(data *types.GenesisState) { (*data).Validators = genValidators1 (*data).Validators[0].DelegatorShares = sdk.ZeroDec() diff --git a/x/stake/handler.go b/x/stake/handler.go index 236718289146..83aec00eaca8 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -2,7 +2,6 @@ package stake import ( "bytes" - "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/keeper" @@ -31,10 +30,28 @@ func NewHandler(k keeper.Keeper) sdk.Handler { } } -// Called every block, process inflation, update validator set -func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.ValidatorUpdate) { +// Called every block, update validator set +func EndBlocker(ctx sdk.Context, k keeper.Keeper) (validatorUpdates []abci.ValidatorUpdate) { endBlockerTags := sdk.EmptyTags() + // Reset the intra-transaction counter. + k.SetIntraTxCounter(ctx, 0) + + // Calculate validator set changes. + // + // NOTE: ApplyAndReturnValidatorSetUpdates has to come before + // UnbondAllMatureValidatorQueue. + // This fixes a bug when the unbonding period is instant (is the case in + // some of the tests). The test expected the validator to be completely + // unbonded after the Endblocker (go from Bonded -> Unbonding during + // ApplyAndReturnValidatorSetUpdates and then Unbonding -> Unbonded during + // UnbondAllMatureValidatorQueue). + 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) for _, dvPair := range matureUnbonds { err := k.CompleteUnbonding(ctx, dvPair.DelegatorAddr, dvPair.ValidatorAddr) @@ -48,6 +65,7 @@ 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) @@ -61,23 +79,6 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Valid tags.DstValidator, []byte(dvvTriplet.ValidatorDstAddr.String()), )) } - - pool := k.GetPool(ctx) - - // Process provision inflation - blockTime := ctx.BlockHeader().Time - if blockTime.Sub(pool.InflationLastTime) >= time.Hour { - params := k.GetParams(ctx) - pool.InflationLastTime = blockTime - pool = pool.ProcessProvisions(params) - k.SetPool(ctx, pool) - } - - // reset the intra-transaction counter - k.SetIntraTxCounter(ctx, 0) - - // calculate validator set changes - ValidatorUpdates = k.ApplyAndReturnValidatorSetUpdates(ctx) return } @@ -104,7 +105,7 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description) commission := NewCommissionWithTime( - msg.Commission.Rate, msg.Commission.MaxChangeRate, + msg.Commission.Rate, msg.Commission.MaxRate, msg.Commission.MaxChangeRate, ctx.BlockHeader().Time, ) validator, err := validator.SetInitialCommission(commission) @@ -116,6 +117,8 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k k.SetValidatorByConsAddr(ctx, validator) k.SetNewValidatorByPowerIndex(ctx, validator) + k.OnValidatorCreated(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) @@ -123,10 +126,6 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k return err.Result() } - k.OnValidatorCreated(ctx, validator.OperatorAddr) - accAddr := sdk.AccAddress(validator.OperatorAddr) - k.OnDelegationCreated(ctx, accAddr, validator.OperatorAddr) - tags := sdk.NewTags( tags.Action, tags.ActionCreateValidator, tags.DstValidator, []byte(msg.ValidatorAddr.String()), @@ -160,6 +159,7 @@ func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keepe return err.Result() } validator.Commission = commission + k.OnValidatorModified(ctx, msg.ValidatorAddr) } k.SetValidator(ctx, validator) @@ -195,9 +195,6 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) return err.Result() } - // call the hook if present - k.OnDelegationCreated(ctx, msg.DelegatorAddr, validator.OperatorAddr) - tags := sdk.NewTags( tags.Action, tags.ActionDelegate, tags.Delegator, []byte(msg.DelegatorAddr.String()), @@ -215,7 +212,7 @@ func handleMsgBeginUnbonding(ctx sdk.Context, msg types.MsgBeginUnbonding, k kee return err.Result() } - finishTime := types.MsgCdc.MustMarshalBinary(ubd.MinTime) + finishTime := types.MsgCdc.MustMarshalBinaryLengthPrefixed(ubd.MinTime) tags := sdk.NewTags( tags.Action, tags.ActionBeginUnbonding, @@ -233,7 +230,7 @@ func handleMsgBeginRedelegate(ctx sdk.Context, msg types.MsgBeginRedelegate, k k return err.Result() } - finishTime := types.MsgCdc.MustMarshalBinary(red.MinTime) + finishTime := types.MsgCdc.MustMarshalBinaryLengthPrefixed(red.MinTime) tags := sdk.NewTags( tags.Action, tags.ActionBeginRedelegation, diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 1802f30c1308..fcc268f558f5 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -7,8 +7,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto" - sdk "github.com/cosmos/cosmos-sdk/types" keep "github.com/cosmos/cosmos-sdk/x/stake/keeper" "github.com/cosmos/cosmos-sdk/x/stake/types" @@ -16,31 +14,6 @@ import ( //______________________________________________________________________ -func newTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt int64) MsgCreateValidator { - return types.NewMsgCreateValidator( - address, pubKey, sdk.NewCoin("steak", sdk.NewInt(amt)), Description{}, commissionMsg, - ) -} - -func newTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt int64) MsgDelegate { - return MsgDelegate{ - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - Delegation: sdk.NewCoin("steak", sdk.NewInt(amt)), - } -} - -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("steak", sdk.NewInt(amt)), - } -} - // retrieve params which are instant func setInstantUnbondPeriod(keeper keep.Keeper, ctx sdk.Context) types.Params { params := keeper.GetParams(ctx) @@ -59,7 +32,7 @@ func TestValidatorByPowerIndex(t *testing.T) { _ = setInstantUnbondPeriod(keeper, ctx) // create validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) @@ -83,7 +56,7 @@ func TestValidatorByPowerIndex(t *testing.T) { require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power)) // create a second validator keep it bonded - msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], int64(1000000)) + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr3, keep.PKs[2], int64(1000000)) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) @@ -112,13 +85,6 @@ func TestValidatorByPowerIndex(t *testing.T) { power2 := GetValidatorsByPowerIndexKey(validator, pool) require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power2)) - // inflate a bunch - params := keeper.GetParams(ctx) - for i := 0; i < 200; i++ { - pool = pool.ProcessProvisions(params) - keeper.SetPool(ctx, pool) - } - // now the new record power index should be the same as the original record power3 := GetValidatorsByPowerIndexKey(validator, pool) require.Equal(t, power2, power3) @@ -128,7 +94,7 @@ func TestValidatorByPowerIndex(t *testing.T) { got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) var finishTime time.Time - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) @@ -146,7 +112,7 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) { addr1, addr2 := sdk.ValAddress(keep.Addrs[0]), sdk.ValAddress(keep.Addrs[1]) pk1, pk2 := keep.PKs[0], keep.PKs[1] - msgCreateValidator1 := newTestMsgCreateValidator(addr1, pk1, 10) + msgCreateValidator1 := NewTestMsgCreateValidator(addr1, pk1, 10) got := handleMsgCreateValidator(ctx, msgCreateValidator1, keeper) require.True(t, got.IsOK(), "%v", got) @@ -162,17 +128,17 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) { assert.Equal(t, Description{}, validator.Description) // two validators can't have the same operator address - msgCreateValidator2 := newTestMsgCreateValidator(addr1, pk2, 10) + msgCreateValidator2 := NewTestMsgCreateValidator(addr1, pk2, 10) got = handleMsgCreateValidator(ctx, msgCreateValidator2, keeper) require.False(t, got.IsOK(), "%v", got) // two validators can't have the same pubkey - msgCreateValidator3 := newTestMsgCreateValidator(addr2, pk1, 10) + msgCreateValidator3 := NewTestMsgCreateValidator(addr2, pk1, 10) got = handleMsgCreateValidator(ctx, msgCreateValidator3, keeper) require.False(t, got.IsOK(), "%v", got) // must have different pubkey and operator - msgCreateValidator4 := newTestMsgCreateValidator(addr2, pk2, 10) + msgCreateValidator4 := NewTestMsgCreateValidator(addr2, pk2, 10) got = handleMsgCreateValidator(ctx, msgCreateValidator4, keeper) require.True(t, got.IsOK(), "%v", got) @@ -197,7 +163,7 @@ func TestDuplicatesMsgCreateValidatorOnBehalfOf(t *testing.T) { validatorAddr := sdk.ValAddress(keep.Addrs[0]) delegatorAddr := keep.Addrs[1] pk := keep.PKs[0] - msgCreateValidatorOnBehalfOf := newTestMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr, pk, 10) + msgCreateValidatorOnBehalfOf := NewTestMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr, pk, 10) got := handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper) require.True(t, got.IsOK(), "%v", got) @@ -232,7 +198,7 @@ func TestLegacyValidatorDelegations(t *testing.T) { delAddr := keep.Addrs[1] // create validator - msgCreateVal := newTestMsgCreateValidator(valAddr, valConsPubKey, bondAmount) + msgCreateVal := NewTestMsgCreateValidator(valAddr, valConsPubKey, bondAmount) got := handleMsgCreateValidator(ctx, msgCreateVal, keeper) require.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got) @@ -248,7 +214,7 @@ func TestLegacyValidatorDelegations(t *testing.T) { require.Equal(t, bondAmount, validator.BondedTokens().RoundInt64()) // delegate tokens to the validator - msgDelegate := newTestMsgDelegate(delAddr, valAddr, bondAmount) + msgDelegate := NewTestMsgDelegate(delAddr, valAddr, bondAmount) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) @@ -266,7 +232,7 @@ func TestLegacyValidatorDelegations(t *testing.T) { require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got %v", got) var finishTime time.Time - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) @@ -283,12 +249,12 @@ func TestLegacyValidatorDelegations(t *testing.T) { require.Equal(t, bondAmount, validator.DelegatorShares.RoundInt64()) // verify a delegator cannot create a new delegation to the now jailed validator - msgDelegate = newTestMsgDelegate(delAddr, valAddr, bondAmount) + msgDelegate = NewTestMsgDelegate(delAddr, valAddr, bondAmount) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.False(t, got.IsOK(), "expected delegation to not be ok, got %v", got) // verify the validator can still self-delegate - msgSelfDelegate := newTestMsgDelegate(sdk.AccAddress(valAddr), valAddr, bondAmount) + msgSelfDelegate := NewTestMsgDelegate(sdk.AccAddress(valAddr), valAddr, bondAmount) got = handleMsgDelegate(ctx, msgSelfDelegate, keeper) require.True(t, got.IsOK(), "expected delegation to not be ok, got %v", got) @@ -302,7 +268,7 @@ func TestLegacyValidatorDelegations(t *testing.T) { keeper.Unjail(ctx, valConsAddr) // verify the validator can now accept delegations - msgDelegate = newTestMsgDelegate(delAddr, valAddr, bondAmount) + msgDelegate = NewTestMsgDelegate(delAddr, valAddr, bondAmount) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) @@ -328,7 +294,7 @@ func TestIncrementsMsgDelegate(t *testing.T) { validatorAddr, delegatorAddr := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1] // first create validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], bondAmount) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], bondAmount) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got) @@ -354,7 +320,7 @@ func TestIncrementsMsgDelegate(t *testing.T) { require.Equal(t, bondAmount, pool.BondedTokens.RoundInt64()) // just send the same msgbond multiple times - msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, bondAmount) + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, bondAmount) for i := 0; i < 5; i++ { ctx = ctx.WithBlockHeight(int64(i)) @@ -402,14 +368,14 @@ func TestIncrementsMsgUnbond(t *testing.T) { // create validator, delegate validatorAddr, delegatorAddr := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1] - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) // initial balance amt1 := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(denom) - msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, initBond) + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, initBond) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) @@ -434,7 +400,7 @@ func TestIncrementsMsgUnbond(t *testing.T) { got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) var finishTime time.Time - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) @@ -505,7 +471,7 @@ func TestMultipleMsgCreateValidator(t *testing.T) { // bond them all for i, validatorAddr := range validatorAddrs { - msgCreateValidatorOnBehalfOf := newTestMsgCreateValidatorOnBehalfOf(delegatorAddrs[i], validatorAddr, keep.PKs[i], 10) + msgCreateValidatorOnBehalfOf := NewTestMsgCreateValidatorOnBehalfOf(delegatorAddrs[i], validatorAddr, keep.PKs[i], 10) got := handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) @@ -528,11 +494,12 @@ func TestMultipleMsgCreateValidator(t *testing.T) { got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) var finishTime time.Time - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + // Jump to finishTime for unbonding period and remove from unbonding queue + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) - //Check that the account is unbonded + // Check that the validator is deleted from state validators := keeper.GetValidators(ctx, 100) require.Equal(t, len(validatorAddrs)-(i+1), len(validators), "expected %d validators got %d", len(validatorAddrs)-(i+1), len(validators)) @@ -552,13 +519,13 @@ func TestMultipleMsgDelegate(t *testing.T) { _ = setInstantUnbondPeriod(keeper, ctx) //first make a validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) // delegate multiple parties for i, delegatorAddr := range delegatorAddrs { - msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, 10) got := handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) @@ -574,7 +541,7 @@ func TestMultipleMsgDelegate(t *testing.T) { got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) var finishTime time.Time - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) @@ -590,12 +557,12 @@ func TestJailValidator(t *testing.T) { _ = setInstantUnbondPeriod(keeper, ctx) // create the validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // bond a delegator - msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, 10) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected ok, got %v", got) @@ -604,7 +571,7 @@ func TestJailValidator(t *testing.T) { got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) require.True(t, got.IsOK(), "expected no error: %v", got) var finishTime time.Time - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) @@ -620,7 +587,7 @@ func TestJailValidator(t *testing.T) { msgBeginUnbondingDelegator := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewDec(10)) got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingDelegator, keeper) require.True(t, got.IsOK(), "expected no error") - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) @@ -629,6 +596,56 @@ func TestJailValidator(t *testing.T) { require.True(t, got.IsOK(), "expected ok, got %v", got) } +func TestValidatorQueue(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, delegatorAddr := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1] + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 7 * time.Second + keeper.SetParams(ctx, params) + + // create the validator + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // bond a delegator + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, 10) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected ok, got %v", got) + + 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) + require.True(t, got.IsOK(), "expected no error: %v", got) + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) + origHeader := ctx.BlockHeader() + + validator, found := keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.True(t, validator.GetStatus() == sdk.Unbonding, "%v", validator) + + // should still be unbonding at time 6 seconds later + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 6)) + EndBlocker(ctx, keeper) + validator, found = keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.True(t, validator.GetStatus() == sdk.Unbonding, "%v", validator) + + // should be in unbonded state at time 7 seconds later + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 7)) + EndBlocker(ctx, keeper) + validator, found = keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.True(t, validator.GetStatus() == sdk.Unbonded, "%v", validator) +} + func TestUnbondingPeriod(t *testing.T) { ctx, _, keeper := keep.CreateTestInput(t, false, 1000) validatorAddr := sdk.ValAddress(keep.Addrs[0]) @@ -639,7 +656,7 @@ func TestUnbondingPeriod(t *testing.T) { keeper.SetParams(ctx, params) // create the validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") @@ -677,12 +694,12 @@ func TestUnbondingFromUnbondingValidator(t *testing.T) { validatorAddr, delegatorAddr := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1] // create the validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // bond a delegator - msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, 10) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected ok, got %v", got) @@ -693,7 +710,7 @@ func TestUnbondingFromUnbondingValidator(t *testing.T) { // change the ctx to Block Time one second before the validator would have unbonded var finishTime time.Time - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime.Add(time.Second * -1)) // unbond the delegator from the validator @@ -724,7 +741,7 @@ func TestRedelegationPeriod(t *testing.T) { keeper.SetParams(ctx, params) // create the validators - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) // initial balance amt1 := AccMapper.GetAccount(ctx, sdk.AccAddress(validatorAddr)).GetCoins().AmountOf(denom) @@ -736,7 +753,7 @@ func TestRedelegationPeriod(t *testing.T) { amt2 := AccMapper.GetAccount(ctx, sdk.AccAddress(validatorAddr)).GetCoins().AmountOf(denom) require.Equal(t, amt1.Sub(sdk.NewInt(10)).Int64(), amt2.Int64(), "expected coins to be subtracted") - msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") @@ -783,15 +800,15 @@ func TestTransitiveRedelegation(t *testing.T) { keeper.SetParams(ctx, params) // create the validators - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") - msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") - msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") @@ -813,6 +830,48 @@ func TestTransitiveRedelegation(t *testing.T) { require.True(t, got.IsOK(), "expected no error") } +func TestConflictingRedelegation(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr := sdk.ValAddress(keep.Addrs[0]) + validatorAddr2 := sdk.ValAddress(keep.Addrs[1]) + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 1 + keeper.SetParams(ctx, params) + + // create the validators + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // end block to bond them + EndBlocker(ctx, keeper) + + // begin redelegate + msgBeginRedelegate := NewMsgBeginRedelegate(sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2, 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 + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, !got.IsOK(), "expected an error, msg: %v", msgBeginRedelegate) + + // progress forward in time + ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(10 * time.Second)) + + // complete first redelegation + EndBlocker(ctx, keeper) + + // now should be able to redelegate again + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error") +} + func TestUnbondingWhenExcessValidators(t *testing.T) { ctx, _, keeper := keep.CreateTestInput(t, false, 1000) validatorAddr1 := sdk.ValAddress(keep.Addrs[0]) @@ -826,26 +885,26 @@ func TestUnbondingWhenExcessValidators(t *testing.T) { keeper.SetParams(ctx, params) // add three validators - msgCreateValidator := newTestMsgCreateValidator(validatorAddr1, keep.PKs[0], 50) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr1, keep.PKs[0], 50) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) - require.Equal(t, 1, len(keeper.GetValidatorsBonded(ctx))) + require.Equal(t, 1, len(keeper.GetLastValidators(ctx))) - msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 30) + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 30) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) - require.Equal(t, 2, len(keeper.GetValidatorsBonded(ctx))) + require.Equal(t, 2, len(keeper.GetLastValidators(ctx))) - msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) - require.Equal(t, 2, len(keeper.GetValidatorsBonded(ctx))) + require.Equal(t, 2, len(keeper.GetLastValidators(ctx))) // unbond the valdator-2 msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr2), validatorAddr2, sdk.NewDec(30)) @@ -858,7 +917,7 @@ func TestUnbondingWhenExcessValidators(t *testing.T) { // because there are extra validators waiting to get in, the queued // validator (aka. validator-1) should make it into the bonded group, thus // the total number of validators should stay the same - vals := keeper.GetValidatorsBonded(ctx) + vals := keeper.GetLastValidators(ctx) require.Equal(t, 2, len(vals), "vals %v", vals) val1, found := keeper.GetValidator(ctx, validatorAddr1) require.True(t, found) @@ -870,16 +929,16 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { valA, valB, del := sdk.ValAddress(keep.Addrs[0]), sdk.ValAddress(keep.Addrs[1]), keep.Addrs[2] consAddr0 := sdk.ConsAddress(keep.PKs[0].Address()) - msgCreateValidator := newTestMsgCreateValidator(valA, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(valA, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") - msgCreateValidator = newTestMsgCreateValidator(valB, keep.PKs[1], 10) + msgCreateValidator = NewTestMsgCreateValidator(valB, keep.PKs[1], 10) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // delegate 10 stake - msgDelegate := newTestMsgDelegate(del, valA, 10) + msgDelegate := NewTestMsgDelegate(del, valA, 10) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected no error on runMsgDelegate") @@ -955,7 +1014,7 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { EndBlocker(ctx, keeper) // validator power should have been reduced to zero - // ergo validator should have been removed from the store - _, found = keeper.GetValidator(ctx, valA) - require.False(t, found) + // validator should be in unbonding state + validator, _ = keeper.GetValidator(ctx, valA) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) } diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 40bcae95db20..32f63f5ed47a 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -37,6 +37,21 @@ func (k Keeper) GetAllDelegations(ctx sdk.Context) (delegations []types.Delegati return delegations } +// return all delegations to a specific validator. Useful for querier. +func (k Keeper) GetValidatorDelegations(ctx sdk.Context, valAddr sdk.ValAddress) (delegations []types.Delegation) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, DelegationKey) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) + if delegation.GetValidatorAddr().Equals(valAddr) { + delegations = append(delegations, delegation) + } + } + return delegations +} + // return a given amount of all the delegations from a delegator func (k Keeper) GetDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) (delegations []types.Delegation) { @@ -163,14 +178,14 @@ func (k Keeper) GetUnbondingQueueTimeSlice(ctx sdk.Context, timestamp time.Time) if bz == nil { return []types.DVPair{} } - k.cdc.MustUnmarshalBinary(bz, &dvPairs) + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &dvPairs) return dvPairs } // Sets a specific unbonding queue timeslice. func (k Keeper) SetUnbondingQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []types.DVPair) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(keys) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(keys) store.Set(GetUnbondingDelegationTimeKey(timestamp), bz) } @@ -199,7 +214,7 @@ func (k Keeper) DequeueAllMatureUnbondingQueue(ctx sdk.Context, currTime time.Ti unbondingTimesliceIterator := k.UnbondingQueueIterator(ctx, ctx.BlockHeader().Time) for ; unbondingTimesliceIterator.Valid(); unbondingTimesliceIterator.Next() { timeslice := []types.DVPair{} - k.cdc.MustUnmarshalBinary(unbondingTimesliceIterator.Value(), ×lice) + k.cdc.MustUnmarshalBinaryLengthPrefixed(unbondingTimesliceIterator.Value(), ×lice) matureUnbonds = append(matureUnbonds, timeslice...) store.Delete(unbondingTimesliceIterator.Key()) } @@ -283,6 +298,21 @@ func (k Keeper) SetRedelegation(ctx sdk.Context, red types.Redelegation) { store.Set(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr), []byte{}) } +// 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) + iterator := sdk.KVStorePrefixIterator(store, RedelegationKey) + defer iterator.Close() + + for i := int64(0); iterator.Valid(); iterator.Next() { + red := types.MustUnmarshalRED(k.cdc, iterator.Key(), iterator.Value()) + if stop := fn(i, red); stop { + break + } + i++ + } +} + // remove a redelegation object and associated index func (k Keeper) RemoveRedelegation(ctx sdk.Context, red types.Redelegation) { store := ctx.KVStore(k.storeKey) @@ -300,14 +330,14 @@ func (k Keeper) GetRedelegationQueueTimeSlice(ctx sdk.Context, timestamp time.Ti if bz == nil { return []types.DVVTriplet{} } - k.cdc.MustUnmarshalBinary(bz, &dvvTriplets) + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &dvvTriplets) return dvvTriplets } // Sets a specific redelegation queue timeslice. func (k Keeper) SetRedelegationQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []types.DVVTriplet) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(keys) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(keys) store.Set(GetRedelegationTimeKey(timestamp), bz) } @@ -336,7 +366,7 @@ func (k Keeper) DequeueAllMatureRedelegationQueue(ctx sdk.Context, currTime time redelegationTimesliceIterator := k.RedelegationQueueIterator(ctx, ctx.BlockHeader().Time) for ; redelegationTimesliceIterator.Valid(); redelegationTimesliceIterator.Next() { timeslice := []types.DVVTriplet{} - k.cdc.MustUnmarshalBinary(redelegationTimesliceIterator.Value(), ×lice) + k.cdc.MustUnmarshalBinaryLengthPrefixed(redelegationTimesliceIterator.Value(), ×lice) matureRedelegations = append(matureRedelegations, timeslice...) store.Delete(redelegationTimesliceIterator.Key()) } @@ -349,6 +379,13 @@ func (k Keeper) DequeueAllMatureRedelegationQueue(ctx sdk.Context, currTime time func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Coin, validator types.Validator, subtractAccount bool) (newShares sdk.Dec, err sdk.Error) { + // In some situations, the exchange rate becomes invalid, e.g. if + // validator loses all tokens due to slashing. In this case, + // make all future delegations invalid. + if validator.DelegatorShareExRate().IsZero() { + return sdk.ZeroDec(), types.ErrDelegatorShareExRateInvalid(k.Codespace()) + } + // Get or create the delegator delegation delegation, found := k.GetDelegation(ctx, delAddr, validator.OperatorAddr) if !found { @@ -359,6 +396,13 @@ 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) + } else { + k.OnDelegationCreated(ctx, delAddr, validator.OperatorAddr) + } + if subtractAccount { // Account new shares, save _, _, err = k.bankKeeper.SubtractCoins(ctx, delegation.DelegatorAddr, sdk.Coins{bondAmt}) @@ -373,6 +417,7 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Co delegation.Shares = delegation.Shares.Add(newShares) delegation.Height = ctx.BlockHeight() k.SetDelegation(ctx, delegation) + return newShares, nil } @@ -380,8 +425,6 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Co func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, shares sdk.Dec) (amount sdk.Dec, err sdk.Error) { - k.OnDelegationSharesModified(ctx, delAddr, valAddr) - // check if delegation has any shares in it unbond delegation, found := k.GetDelegation(ctx, delAddr, valAddr) if !found { @@ -389,6 +432,8 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA return } + k.OnDelegationSharesModified(ctx, delAddr, valAddr) + // retrieve the amount to remove if delegation.Shares.LT(shares) { err = types.ErrNotEnoughDelegationShares(k.Codespace(), delegation.Shares.String()) @@ -425,12 +470,11 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA // remove the coins from the validator validator, amount = k.RemoveValidatorTokensAndShares(ctx, validator, shares) - if validator.DelegatorShares.IsZero() && validator.Status != sdk.Bonded { - // if bonded, we must remove in EndBlocker instead + if validator.DelegatorShares.IsZero() && validator.Status == sdk.Unbonded { + // if not unbonded, we must instead remove validator in EndBlocker once it finishes its unbonding period k.RemoveValidator(ctx, validator.OperatorAddr) } - k.OnDelegationSharesModified(ctx, delegation.DelegatorAddr, validator.OperatorAddr) return amount, nil } @@ -447,10 +491,10 @@ func (k Keeper) getBeginInfo(ctx sdk.Context, valSrcAddr sdk.ValAddress) ( // the longest wait - just unbonding period from now minTime = ctx.BlockHeader().Time.Add(k.UnbondingTime(ctx)) - height = ctx.BlockHeader().Height + height = ctx.BlockHeight() return minTime, height, false - case validator.IsUnbonded(ctx): + case validator.Status == sdk.Unbonded: return minTime, height, true case validator.Status == sdk.Unbonding: @@ -481,7 +525,14 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context, return types.UnbondingDelegation{}, err } - balance := sdk.NewCoin(k.BondDenom(ctx), returnAmount.RoundInt()) + 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) // no need to create the ubd object just complete now if completeNow { @@ -502,6 +553,7 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context, } k.SetUnbondingDelegation(ctx, ubd) k.InsertUnbondingQueue(ctx, ubd) + return ubd, nil } @@ -526,6 +578,17 @@ func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAd func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount sdk.Dec) (types.Redelegation, 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()) + } + // check if this is a transitive redelegation if k.HasReceivingRedelegation(ctx, delAddr, valSrcAddr) { return types.Redelegation{}, types.ErrTransitiveRedelegation(k.Codespace()) @@ -536,7 +599,15 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, return types.Redelegation{}, err } - returnCoin := sdk.Coin{k.BondDenom(ctx), returnAmount.RoundInt()} + rounded := returnAmount.TruncateInt() + 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) + dstValidator, found := k.GetValidator(ctx, valDstAddr) if !found { return types.Redelegation{}, types.ErrBadRedelegationDst(k.Codespace()) diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index 0964f10def25..3fa641fd2a18 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -1,6 +1,7 @@ package keeper import ( + "fmt" "testing" "time" @@ -25,9 +26,9 @@ func TestDelegation(t *testing.T) { } keeper.SetPool(ctx, pool) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) - validators[2] = testingUpdateValidator(keeper, ctx, validators[2]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + validators[2] = TestingUpdateValidator(keeper, ctx, validators[2]) // first add a validators[0] to delegate too @@ -104,6 +105,9 @@ func TestDelegation(t *testing.T) { resVal, err = keeper.GetDelegatorValidator(ctx, addrDels[1], addrVals[i]) require.Nil(t, err) require.Equal(t, addrVals[i], resVal.GetOperator()) + + resDels := keeper.GetValidatorDelegations(ctx, addrVals[i]) + require.Len(t, resDels, 2) } // delete a record @@ -138,7 +142,7 @@ func TestUnbondingDelegation(t *testing.T) { ValidatorAddr: addrVals[0], CreationHeight: 0, MinTime: time.Unix(0, 0), - Balance: sdk.NewInt64Coin("steak", 5), + Balance: sdk.NewInt64Coin(types.DefaultBondDenom, 5), } // set and retrieve a record @@ -148,7 +152,7 @@ func TestUnbondingDelegation(t *testing.T) { require.True(t, ubd.Equal(resUnbond)) // modify a records, save, and retrieve - ubd.Balance = sdk.NewInt64Coin("steak", 21) + ubd.Balance = sdk.NewInt64Coin(types.DefaultBondDenom, 21) keeper.SetUnbondingDelegation(ctx, ubd) resUnbonds := keeper.GetUnbondingDelegations(ctx, addrDels[0], 5) @@ -184,7 +188,7 @@ func TestUnbondDelegation(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) require.Equal(t, int64(10), pool.BondedTokens.RoundInt64()) @@ -226,7 +230,7 @@ func TestUndelegateSelfDelegation(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) selfDelegation := types.Delegation{ DelegatorAddr: sdk.AccAddress(addrVals[0].Bytes()), @@ -240,7 +244,7 @@ func TestUndelegateSelfDelegation(t *testing.T) { validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -274,7 +278,7 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) selfDelegation := types.Delegation{ DelegatorAddr: sdk.AccAddress(addrVals[0].Bytes()), @@ -288,7 +292,7 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -350,7 +354,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ @@ -365,7 +369,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -374,12 +378,8 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { } keeper.SetDelegation(ctx, delegation) - header := ctx.BlockHeader() - blockHeight := int64(10) - header.Height = blockHeight - blockTime := time.Unix(333, 0) - header.Time = blockTime - ctx = ctx.WithBlockHeader(header) + ctx = ctx.WithBlockHeight(10) + 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)) @@ -391,17 +391,18 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) - require.Equal(t, blockHeight, validator.UnbondingHeight) + require.Equal(t, ctx.BlockHeight(), validator.UnbondingHeight) params := keeper.GetParams(ctx) - require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + require.True(t, ctx.BlockHeader().Time.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) - // change the context to one which makes the validator considered unbonded - header = ctx.BlockHeader() - blockHeight2 := int64(20) - header.Height = blockHeight2 - blockTime2 := time.Unix(444, 0).Add(params.UnbondingTime) - header.Time = blockTime2 - ctx = ctx.WithBlockHeader(header) + // unbond the validator + ctx = ctx.WithBlockTime(validator.UnbondingMinTime) + keeper.UnbondAllMatureValidatorQueue(ctx) + + // Make sure validator is still in state because there is still an outstanding delegation + validator, found = keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + 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)) @@ -410,6 +411,79 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { // no ubd should have been found, coins should have been returned direcly to account ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) 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)) + require.NoError(t, err) + + // now validator should now be deleted from state + validator, found = keeper.GetValidator(ctx, addrVals[0]) + fmt.Println(validator) + require.False(t, found) +} + +func TestUnbondingAllDelegationFromValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDec(20) + + //create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) + selfDelegation := types.Delegation{ + DelegatorAddr: val0AccAddr, + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + // create a second delegation to this validator + keeper.DeleteValidatorByPowerIndex(ctx, validator, pool) + validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + ctx = ctx.WithBlockHeight(10) + 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)) + require.NoError(t, err) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + // unbond all the remaining delegation + _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(10)) + require.NoError(t, err) + + // validator should still be in state and still be in unbonding state + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, validator.Status, sdk.Unbonding) + + // unbond the validator + ctx = ctx.WithBlockTime(validator.UnbondingMinTime) + keeper.UnbondAllMatureValidatorQueue(ctx) + + // validator should now be deleted from state + _, found = keeper.GetValidator(ctx, addrVals[0]) + require.False(t, found) } // Make sure that that the retrieving the delegations doesn't affect the state @@ -510,6 +584,32 @@ func TestRedelegation(t *testing.T) { 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) + + // create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) + selfDelegation := types.Delegation{ + DelegatorAddr: val0AccAddr, + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + _, err := keeper.BeginRedelegation(ctx, val0AccAddr, addrVals[0], addrVals[0], sdk.NewDec(5)) + require.Error(t, err) + +} + func TestRedelegateSelfDelegation(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 0) @@ -521,7 +621,7 @@ func TestRedelegateSelfDelegation(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ @@ -537,14 +637,14 @@ func TestRedelegateSelfDelegation(t *testing.T) { require.Equal(t, int64(10), issuedShares.RoundInt64()) pool.BondedTokens = pool.BondedTokens.Add(sdk.NewDec(10)) keeper.SetPool(ctx, pool) - validator2 = testingUpdateValidator(keeper, ctx, validator2) + validator2 = TestingUpdateValidator(keeper, ctx, validator2) require.Equal(t, sdk.Bonded, validator2.Status) // create a second delegation to this validator validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -578,7 +678,7 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ @@ -593,7 +693,7 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -608,7 +708,7 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator2 = testingUpdateValidator(keeper, ctx, validator2) + validator2 = TestingUpdateValidator(keeper, ctx, validator2) header := ctx.BlockHeader() blockHeight := int64(10) @@ -662,7 +762,7 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ @@ -678,7 +778,7 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { validator.BondIntraTxCounter = 1 require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -693,15 +793,11 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator2 = testingUpdateValidator(keeper, ctx, validator2) + validator2 = TestingUpdateValidator(keeper, ctx, validator2) require.Equal(t, sdk.Bonded, validator2.Status) - header := ctx.BlockHeader() - blockHeight := int64(10) - header.Height = blockHeight - blockTime := time.Unix(333, 0) - header.Time = blockTime - ctx = ctx.WithBlockHeader(header) + ctx = ctx.WithBlockHeight(10) + 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)) @@ -713,23 +809,18 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) - require.Equal(t, blockHeight, validator.UnbondingHeight) + require.Equal(t, ctx.BlockHeight(), validator.UnbondingHeight) params := keeper.GetParams(ctx) - require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + require.True(t, ctx.BlockHeader().Time.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) - // change the context to one which makes the validator considered unbonded - header = ctx.BlockHeader() - blockHeight2 := int64(20) - header.Height = blockHeight2 - blockTime2 := time.Unix(444, 0).Add(params.UnbondingTime) - header.Time = blockTime2 - ctx = ctx.WithBlockHeader(header) + // unbond the validator + keeper.unbondingToUnbonded(ctx, validator) - // unbond some of the other delegation's shares + // redelegate some of the delegation's shares _, err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDec(6)) require.NoError(t, err) - // no ubd should have been found, coins should have been returned direcly to account - ubd, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) - require.False(t, found, "%v", ubd) + // no red should have been found + red, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.False(t, found, "%v", red) } diff --git a/x/stake/keeper/hooks.go b/x/stake/keeper/hooks.go index 81bf5594ba75..74e830490647 100644 --- a/x/stake/keeper/hooks.go +++ b/x/stake/keeper/hooks.go @@ -6,32 +6,38 @@ import ( ) // Expose the hooks if present -func (k Keeper) OnValidatorCreated(ctx sdk.Context, address sdk.ValAddress) { +func (k Keeper) OnValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { if k.hooks != nil { - k.hooks.OnValidatorCreated(ctx, address) + k.hooks.OnValidatorCreated(ctx, valAddr) } } -func (k Keeper) OnValidatorCommissionChange(ctx sdk.Context, address sdk.ValAddress) { +func (k Keeper) OnValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { if k.hooks != nil { - k.hooks.OnValidatorCommissionChange(ctx, address) + k.hooks.OnValidatorModified(ctx, valAddr) } } -func (k Keeper) OnValidatorRemoved(ctx sdk.Context, address sdk.ValAddress) { +func (k Keeper) OnValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { if k.hooks != nil { - k.hooks.OnValidatorRemoved(ctx, address) + k.hooks.OnValidatorRemoved(ctx, consAddr, valAddr) } } -func (k Keeper) OnValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { +func (k Keeper) OnValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { if k.hooks != nil { - k.hooks.OnValidatorBonded(ctx, address) + k.hooks.OnValidatorBonded(ctx, consAddr, valAddr) } } -func (k Keeper) OnValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) { +func (k Keeper) OnValidatorPowerDidChange(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { if k.hooks != nil { - k.hooks.OnValidatorBeginUnbonding(ctx, address) + 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) } } diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go index 20927869866d..b6c973f98e05 100644 --- a/x/stake/keeper/keeper.go +++ b/x/stake/keeper/keeper.go @@ -36,7 +36,7 @@ func NewKeeper(cdc *codec.Codec, key, tkey sdk.StoreKey, ck bank.Keeper, paramst } // Set the validator hooks -func (k Keeper) WithHooks(sh sdk.StakingHooks) Keeper { +func (k *Keeper) SetHooks(sh sdk.StakingHooks) *Keeper { if k.hooks != nil { panic("cannot set validator hooks twice") } @@ -53,47 +53,103 @@ func (k Keeper) Codespace() sdk.CodespaceType { //_______________________________________________________________________ -// load/save the pool +// load the pool func (k Keeper) GetPool(ctx sdk.Context) (pool types.Pool) { store := ctx.KVStore(k.storeKey) b := store.Get(PoolKey) if b == nil { - panic("Stored pool should not have been nil") + panic("stored pool should not have been nil") } - k.cdc.MustUnmarshalBinary(b, &pool) + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &pool) return } // set the pool func (k Keeper) SetPool(ctx sdk.Context, pool types.Pool) { store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(pool) + b := k.cdc.MustMarshalBinaryLengthPrefixed(pool) store.Set(PoolKey, b) } -//__________________________________________________________________________ +//_______________________________________________________________________ -// get the current in-block validator operation counter -func (k Keeper) InitIntraTxCounter(ctx sdk.Context) { +// Load the last total validator power. +func (k Keeper) GetLastTotalPower(ctx sdk.Context) (power sdk.Int) { store := ctx.KVStore(k.storeKey) - b := store.Get(IntraTxCounterKey) + b := store.Get(LastTotalPowerKey) if b == nil { - k.SetIntraTxCounter(ctx, 0) + return sdk.ZeroInt() } + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &power) + return +} + +// Set the last total validator power. +func (k Keeper) SetLastTotalPower(ctx sdk.Context, power sdk.Int) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinaryLengthPrefixed(power) + store.Set(LastTotalPowerKey, b) +} + +//_______________________________________________________________________ + +// Load the last validator power. +// Returns zero if the operator was not a validator last block. +func (k Keeper) GetLastValidatorPower(ctx sdk.Context, operator sdk.ValAddress) (power sdk.Int) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(GetLastValidatorPowerKey(operator)) + if bz == nil { + return sdk.ZeroInt() + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &power) + return } +// Set the last validator power. +func (k Keeper) SetLastValidatorPower(ctx sdk.Context, operator sdk.ValAddress, power sdk.Int) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(power) + store.Set(GetLastValidatorPowerKey(operator), bz) +} + +// Iterate over last validator powers. +func (k Keeper) IterateLastValidatorPowers(ctx sdk.Context, handler func(operator sdk.ValAddress, power sdk.Int) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, LastValidatorPowerKey) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + addr := sdk.ValAddress(iter.Key()[len(LastValidatorPowerKey):]) + var power sdk.Int + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &power) + if handler(addr, power) { + break + } + } +} + +// Delete the last validator power. +func (k Keeper) DeleteLastValidatorPower(ctx sdk.Context, operator sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetLastValidatorPowerKey(operator)) +} + +//__________________________________________________________________________ + // get the current in-block validator operation counter func (k Keeper) GetIntraTxCounter(ctx sdk.Context) int16 { store := ctx.KVStore(k.storeKey) b := store.Get(IntraTxCounterKey) + if b == nil { + return 0 + } var counter int16 - k.cdc.MustUnmarshalBinary(b, &counter) + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &counter) return counter } // set the current in-block validator operation counter func (k Keeper) SetIntraTxCounter(ctx sdk.Context, counter int16) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(counter) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(counter) store.Set(IntraTxCounterKey, bz) } diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index e84a35c21e06..243fe34ec57c 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -15,20 +15,27 @@ var ( // Keys for store prefixes // TODO DEPRECATED: delete in next release and reorder keys // ParamKey = []byte{0x00} // key for parameters relating to staking - PoolKey = []byte{0x01} // key for the staking pools - ValidatorsKey = []byte{0x02} // prefix for each key to a validator - ValidatorsByConsAddrKey = []byte{0x03} // prefix for each key to a validator index, by pubkey - ValidatorsBondedIndexKey = []byte{0x04} // prefix for each key to a validator index, for bonded validators - ValidatorsByPowerIndexKey = []byte{0x05} // prefix for each key to a validator index, sorted by power - IntraTxCounterKey = []byte{0x06} // key for intra-block tx index - DelegationKey = []byte{0x07} // key for a delegation - UnbondingDelegationKey = []byte{0x08} // key for an unbonding-delegation - UnbondingDelegationByValIndexKey = []byte{0x09} // prefix for each key for an unbonding-delegation, by validator operator - RedelegationKey = []byte{0x0A} // key for a redelegation - RedelegationByValSrcIndexKey = []byte{0x0B} // prefix for each key for an redelegation, by source validator operator - RedelegationByValDstIndexKey = []byte{0x0C} // prefix for each key for an redelegation, by destination validator operator - UnbondingQueueKey = []byte{0x0D} // prefix for the timestamps in unbonding queue - RedelegationQueueKey = []byte{0x0E} // prefix for the timestamps in redelegations queue + PoolKey = []byte{0x01} // key for the staking pools + IntraTxCounterKey = []byte{0x02} // key for intra-block tx index + + // Last* values are const during a block. + LastValidatorPowerKey = []byte{0x11} // prefix for each key to a validator index, for bonded validators + LastTotalPowerKey = []byte{0x12} // prefix for the total power + + ValidatorsKey = []byte{0x21} // prefix for each key to a validator + ValidatorsByConsAddrKey = []byte{0x22} // prefix for each key to a validator index, by pubkey + ValidatorsByPowerIndexKey = []byte{0x23} // prefix for each key to a validator index, sorted by power + + DelegationKey = []byte{0x31} // key for a delegation + UnbondingDelegationKey = []byte{0x32} // key for an unbonding-delegation + UnbondingDelegationByValIndexKey = []byte{0x33} // prefix for each key for an unbonding-delegation, by validator operator + RedelegationKey = []byte{0x34} // key for a redelegation + RedelegationByValSrcIndexKey = []byte{0x35} // prefix for each key for an redelegation, by source validator operator + RedelegationByValDstIndexKey = []byte{0x36} // prefix for each key for an redelegation, by destination validator operator + + UnbondingQueueKey = []byte{0x41} // prefix for the timestamps in unbonding queue + RedelegationQueueKey = []byte{0x42} // prefix for the timestamps in redelegations queue + ValidatorQueueKey = []byte{0x43} // prefix for the timestamps in validator queue ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch @@ -45,9 +52,9 @@ func GetValidatorByConsAddrKey(addr sdk.ConsAddress) []byte { return append(ValidatorsByConsAddrKey, addr.Bytes()...) } -// Get the validator operator address from ValBondedIndexKey -func GetAddressFromValBondedIndexKey(IndexKey []byte) []byte { - return IndexKey[1:] // remove prefix bytes +// Get the validator operator address from LastValidatorPowerKey +func AddressFromLastValidatorPowerKey(key []byte) []byte { + return key[1:] // remove prefix bytes } // get the validator by power index. @@ -60,8 +67,8 @@ func GetValidatorsByPowerIndexKey(validator types.Validator, pool types.Pool) [] } // get the bonded validator index key for an operator address -func GetBondedValidatorIndexKey(operator sdk.ValAddress) []byte { - return append(ValidatorsBondedIndexKey, operator...) +func GetLastValidatorPowerKey(operator sdk.ValAddress) []byte { + return append(LastValidatorPowerKey, operator...) } // get the power ranking of a validator @@ -70,8 +77,15 @@ func GetBondedValidatorIndexKey(operator sdk.ValAddress) []byte { func getValidatorPowerRank(validator types.Validator) []byte { potentialPower := validator.Tokens - powerBytes := []byte(potentialPower.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first) + + // todo: deal with cases above 2**64, ref https://github.com/cosmos/cosmos-sdk/issues/2439#issuecomment-427167556 + tendermintPower := potentialPower.RoundInt64() + tendermintPowerBytes := make([]byte, 8) + binary.BigEndian.PutUint64(tendermintPowerBytes[:], uint64(tendermintPower)) + + powerBytes := tendermintPowerBytes powerBytesLen := len(powerBytes) + // key is of format prefix || powerbytes || heightBytes || counterBytes key := make([]byte, 1+powerBytesLen+8+2) @@ -86,6 +100,12 @@ func getValidatorPowerRank(validator types.Validator) []byte { return key } +// gets the prefix for all unbonding delegations from a delegator +func GetValidatorQueueTimeKey(timestamp time.Time) []byte { + bz := sdk.FormatTimeBytes(timestamp) + return append(ValidatorQueueKey, bz...) +} + //______________________________________________________________________________ // gets the key for delegator bond with validator @@ -140,7 +160,7 @@ func GetUBDsByValIndexKey(valAddr sdk.ValAddress) []byte { // gets the prefix for all unbonding delegations from a delegator func GetUnbondingDelegationTimeKey(timestamp time.Time) []byte { - bz := types.MsgCdc.MustMarshalBinary(timestamp) + bz := sdk.FormatTimeBytes(timestamp) return append(UnbondingQueueKey, bz...) } @@ -214,7 +234,7 @@ func GetREDKeyFromValDstIndexKey(indexKey []byte) []byte { // gets the prefix for all unbonding delegations from a delegator func GetRedelegationTimeKey(timestamp time.Time) []byte { - bz, _ := timestamp.MarshalBinary() + bz := sdk.FormatTimeBytes(timestamp) return append(RedelegationQueueKey, bz...) } diff --git a/x/stake/keeper/key_test.go b/x/stake/keeper/key_test.go index 591e7d1bccc3..385f313c15b5 100644 --- a/x/stake/keeper/key_test.go +++ b/x/stake/keeper/key_test.go @@ -35,10 +35,10 @@ func TestGetValidatorPowerRank(t *testing.T) { validator types.Validator wantHex string }{ - {val1, "05303030303030303030303030ffffffffffffffffffff"}, - {val2, "05303030303030303030303031ffffffffffffffffffff"}, - {val3, "05303030303030303030303130ffffffffffffffffffff"}, - {val4, "0531303939353131363237373736ffffffffffffffffffff"}, + {val1, "230000000000000000ffffffffffffffffffff"}, + {val2, "230000000000000001ffffffffffffffffffff"}, + {val3, "23000000000000000affffffffffffffffffff"}, + {val4, "230000010000000000ffffffffffffffffffff"}, } for i, tt := range tests { got := hex.EncodeToString(getValidatorPowerRank(tt.validator)) @@ -55,11 +55,11 @@ func TestGetREDByValDstIndexKey(t *testing.T) { wantHex string }{ {sdk.AccAddress(addr1), sdk.ValAddress(addr1), sdk.ValAddress(addr1), - "0c63d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"}, + "3663d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"}, {sdk.AccAddress(addr1), sdk.ValAddress(addr2), sdk.ValAddress(addr3), - "0c3ab62f0d93849be495e21e3e9013a517038f45bd63d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f2"}, + "363ab62f0d93849be495e21e3e9013a517038f45bd63d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f2"}, {sdk.AccAddress(addr2), sdk.ValAddress(addr1), sdk.ValAddress(addr3), - "0c3ab62f0d93849be495e21e3e9013a517038f45bd5ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f08609"}, + "363ab62f0d93849be495e21e3e9013a517038f45bd5ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f08609"}, } for i, tt := range tests { got := hex.EncodeToString(GetREDByValDstIndexKey(tt.delAddr, tt.valSrcAddr, tt.valDstAddr)) @@ -76,11 +76,11 @@ func TestGetREDByValSrcIndexKey(t *testing.T) { wantHex string }{ {sdk.AccAddress(addr1), sdk.ValAddress(addr1), sdk.ValAddress(addr1), - "0b63d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"}, + "3563d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"}, {sdk.AccAddress(addr1), sdk.ValAddress(addr2), sdk.ValAddress(addr3), - "0b5ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f086093ab62f0d93849be495e21e3e9013a517038f45bd"}, + "355ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f086093ab62f0d93849be495e21e3e9013a517038f45bd"}, {sdk.AccAddress(addr2), sdk.ValAddress(addr1), sdk.ValAddress(addr3), - "0b63d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f23ab62f0d93849be495e21e3e9013a517038f45bd"}, + "3563d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f23ab62f0d93849be495e21e3e9013a517038f45bd"}, } for i, tt := range tests { got := hex.EncodeToString(GetREDByValSrcIndexKey(tt.delAddr, tt.valSrcAddr, tt.valDstAddr)) diff --git a/x/stake/keeper/params.go b/x/stake/keeper/params.go index 294c6dcd5a34..b62a7688fbb8 100644 --- a/x/stake/keeper/params.go +++ b/x/stake/keeper/params.go @@ -18,30 +18,6 @@ func ParamTypeTable() params.TypeTable { return params.NewTypeTable().RegisterParamSet(&types.Params{}) } -// InflationRateChange - Maximum annual change in inflation rate -func (k Keeper) InflationRateChange(ctx sdk.Context) (res sdk.Dec) { - k.paramstore.Get(ctx, types.KeyInflationRateChange, &res) - return -} - -// InflationMax - Maximum inflation rate -func (k Keeper) InflationMax(ctx sdk.Context) (res sdk.Dec) { - k.paramstore.Get(ctx, types.KeyInflationMax, &res) - return -} - -// InflationMin - Minimum inflation rate -func (k Keeper) InflationMin(ctx sdk.Context) (res sdk.Dec) { - k.paramstore.Get(ctx, types.KeyInflationMin, &res) - return -} - -// GoalBonded - Goal of percent bonded atoms -func (k Keeper) GoalBonded(ctx sdk.Context) (res sdk.Dec) { - k.paramstore.Get(ctx, types.KeyGoalBonded, &res) - return -} - // UnbondingTime func (k Keeper) UnbondingTime(ctx sdk.Context) (res time.Duration) { k.paramstore.Get(ctx, types.KeyUnbondingTime, &res) @@ -62,10 +38,6 @@ func (k Keeper) BondDenom(ctx sdk.Context) (res string) { // Get all parameteras as types.Params func (k Keeper) GetParams(ctx sdk.Context) (res types.Params) { - res.InflationRateChange = k.InflationRateChange(ctx) - res.InflationMax = k.InflationMax(ctx) - res.InflationMin = k.InflationMin(ctx) - res.GoalBonded = k.GoalBonded(ctx) res.UnbondingTime = k.UnbondingTime(ctx) res.MaxValidators = k.MaxValidators(ctx) res.BondDenom = k.BondDenom(ctx) diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go index d702e845dae4..1dea473f892b 100644 --- a/x/stake/keeper/sdk_types.go +++ b/x/stake/keeper/sdk_types.go @@ -28,12 +28,34 @@ func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validato } // iterate through the active validator set and perform the provided function -func (k Keeper) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { +func (k Keeper) IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + maxValidators := k.MaxValidators(ctx) + + iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) + defer iterator.Close() + + i := int64(0) + for ; iterator.Valid() && i < int64(maxValidators); iterator.Next() { + address := iterator.Value() + validator := k.mustGetValidator(ctx, address) + + if validator.Status == sdk.Bonded { + stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? + if stop { + break + } + i++ + } + } +} + +// iterate through the active validator set and perform the provided function +func (k Keeper) IterateLastValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { + iterator := k.LastValidatorsIterator(ctx) i := int64(0) for ; iterator.Valid(); iterator.Next() { - address := GetAddressFromValBondedIndexKey(iterator.Key()) + address := AddressFromLastValidatorPowerKey(iterator.Key()) validator, found := k.GetValidator(ctx, address) if !found { panic(fmt.Sprintf("validator record not found for address: %v\n", address)) @@ -66,12 +88,25 @@ func (k Keeper) ValidatorByConsAddr(ctx sdk.Context, addr sdk.ConsAddress) sdk.V return val } -// total power from the bond +// total power from the bond (not last, but current) func (k Keeper) TotalPower(ctx sdk.Context) sdk.Dec { pool := k.GetPool(ctx) return pool.BondedTokens } +// total power from the bond +func (k Keeper) BondedRatio(ctx sdk.Context) sdk.Dec { + pool := k.GetPool(ctx) + return pool.BondedRatio() +} + +// when minting new tokens +func (k Keeper) InflateSupply(ctx sdk.Context, newTokens sdk.Dec) { + pool := k.GetPool(ctx) + pool.LooseTokens = pool.LooseTokens.Add(newTokens) + k.SetPool(ctx, pool) +} + //__________________________________________________________________________ // Implements DelegationSet diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index dc57dc5dd47b..e7bd72764b5d 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -46,11 +46,12 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh } // should not be slashing unbonded - if validator.IsUnbonded(ctx) { + if validator.Status == sdk.Unbonded { panic(fmt.Sprintf("should not be slashing unbonded validator: %s", validator.GetOperator())) } operatorAddress := validator.GetOperator() + k.OnValidatorModified(ctx, operatorAddress) // Track remaining slash amount for the validator // This will decrease when we slash unbondings and @@ -97,19 +98,16 @@ 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. - // burn validator's tokens and update the validator + // Deduct from validator's bonded tokens and update the validator. + // The deducted tokens are returned to pool.LooseTokens. validator = k.RemoveValidatorTokens(ctx, validator, tokensToBurn) pool := k.GetPool(ctx) + // Burn the slashed tokens, which are now loose. pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn) k.SetPool(ctx, pool) - // remove validator if it has no more tokens - if validator.Tokens.IsZero() && validator.Status != sdk.Bonded { - // if bonded, we must remove in ApplyAndReturnValidatorSetUpdates instead - k.RemoveValidator(ctx, validator.OperatorAddr) - } - // Log that a slash occurred! logger.Info(fmt.Sprintf( "validator %s slashed by slash factor of %s; burned %v tokens", @@ -177,7 +175,7 @@ func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation ty // Burn loose tokens // Ref https://github.com/cosmos/cosmos-sdk/pull/1278#discussion_r198657760 - pool.LooseTokens = pool.LooseTokens.Sub(slashAmount) + pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewDecFromInt(unbondingSlashAmount)) k.SetPool(ctx, pool) } @@ -232,6 +230,7 @@ func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, re if sharesToUnbond.GT(delegation.Shares) { sharesToUnbond = delegation.Shares } + tokensToBurn, err := k.unbond(ctx, redelegation.DelegatorAddr, redelegation.ValidatorDstAddr, sharesToUnbond) if err != nil { panic(fmt.Errorf("error unbonding delegator: %v", err)) diff --git a/x/stake/keeper/slash_test.go b/x/stake/keeper/slash_test.go index edc7ff16b48a..9c23576c34ca 100644 --- a/x/stake/keeper/slash_test.go +++ b/x/stake/keeper/slash_test.go @@ -29,7 +29,7 @@ func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) { validator.BondIntraTxCounter = int16(i) pool.BondedTokens = pool.BondedTokens.Add(sdk.NewDec(amt)) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) keeper.SetValidatorByConsAddr(ctx, validator) } pool = keeper.GetPool(ctx) @@ -348,9 +348,9 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { keeper.ApplyAndReturnValidatorSetUpdates(ctx) // read updated validator // power decreased by 1 again, validator is out of stake - // ergo validator should have been removed from the store - _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.False(t, found) + // validator should be in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) } // tests Slash at a previous height with a redelegation @@ -450,16 +450,16 @@ func TestSlashWithRedelegation(t *testing.T) { // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) // read updated validator - // validator decreased to zero power, should have been removed from the store - _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.False(t, found) + // validator decreased to zero power, should be in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) // slash the validator again, by 100% // no stake remains to be slashed ctx = ctx.WithBlockHeight(12) - // validator no longer in the store - _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.False(t, found) + // validator still in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) // read updating redelegation @@ -472,9 +472,9 @@ func TestSlashWithRedelegation(t *testing.T) { // no more bonded tokens burned require.Equal(t, int64(16), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator - // power still zero, still not in the store - _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.False(t, found) + // power still zero, still in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) } // tests Slash at a previous height with both an unbonding delegation and a redelegation diff --git a/x/stake/keeper/test_common.go b/x/stake/keeper/test_common.go index 10bf2755d1a9..af7e688f3215 100644 --- a/x/stake/keeper/test_common.go +++ b/x/stake/keeper/test_common.go @@ -3,6 +3,7 @@ package keeper import ( "bytes" "encoding/hex" + "math/rand" "strconv" "testing" @@ -73,20 +74,8 @@ func MakeTestCodec() *codec.Codec { return cdc } -// default params without inflation -func ParamsNoInflation() types.Params { - return types.Params{ - InflationRateChange: sdk.ZeroDec(), - InflationMax: sdk.ZeroDec(), - InflationMin: sdk.ZeroDec(), - GoalBonded: sdk.NewDecWithPrec(67, 2), - MaxValidators: 100, - BondDenom: "steak", - } -} - // hogpodge of all sorts of input required for testing -func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, auth.AccountMapper, Keeper) { +func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, auth.AccountKeeper, Keeper) { keyStake := sdk.NewKVStoreKey("stake") tkeyStake := sdk.NewTransientStoreKey("transient_stake") @@ -106,19 +95,18 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, log.NewNopLogger()) cdc := MakeTestCodec() - accountMapper := auth.NewAccountMapper( + accountKeeper := auth.NewAccountKeeper( cdc, // amino codec keyAcc, // target store auth.ProtoBaseAccount, // prototype ) - ck := bank.NewBaseKeeper(accountMapper) + ck := bank.NewBaseKeeper(accountKeeper) pk := params.NewKeeper(cdc, keyParams, tkeyParams) keeper := NewKeeper(cdc, keyStake, tkeyStake, ck, pk.Subspace(DefaultParamspace), types.DefaultCodespace) keeper.SetPool(ctx, types.InitialPool()) keeper.SetParams(ctx, types.DefaultParams()) - keeper.InitIntraTxCounter(ctx) // fill all the addresses with some coins, set the loose pool tokens simultaneously for _, addr := range Addrs { @@ -131,7 +119,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context keeper.SetPool(ctx, pool) } - return ctx, accountMapper, keeper + return ctx, accountKeeper, keeper } func NewPubKey(pk string) (res crypto.PubKey) { @@ -211,7 +199,8 @@ func ValidatorByPowerIndexExists(ctx sdk.Context, keeper Keeper, power []byte) b return store.Has(power) } -func testingUpdateValidator(keeper Keeper, ctx sdk.Context, validator types.Validator) types.Validator { +// update validator for testing +func TestingUpdateValidator(keeper Keeper, ctx sdk.Context, validator types.Validator) types.Validator { pool := keeper.GetPool(ctx) keeper.SetValidator(ctx, validator) keeper.SetValidatorByPowerIndex(ctx, validator, pool) @@ -227,3 +216,17 @@ func validatorByPowerIndexExists(k Keeper, ctx sdk.Context, power []byte) bool { store := ctx.KVStore(k.storeKey) return store.Has(power) } + +// RandomValidator returns a random validator given access to the keeper and ctx +func RandomValidator(r *rand.Rand, keeper Keeper, ctx sdk.Context) types.Validator { + vals := keeper.GetAllValidators(ctx) + i := r.Intn(len(vals)) + return vals[i] +} + +// RandomBondedValidator returns a random bonded validator given access to the keeper and ctx +func RandomBondedValidator(r *rand.Rand, keeper Keeper, ctx sdk.Context) types.Validator { + vals := keeper.GetBondedValidatorsByPower(ctx) + i := r.Intn(len(vals)) + return vals[i] +} diff --git a/x/stake/keeper/val_state_change.go b/x/stake/keeper/val_state_change.go index f9cf6e463fb6..3f98f08c5601 100644 --- a/x/stake/keeper/val_state_change.go +++ b/x/stake/keeper/val_state_change.go @@ -11,7 +11,14 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake/types" ) -// Apply and return accumulated updates to the bonded validator set +// 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 relevant indices. +// It gets called once after genesis, another time maybe after genesis transactions, +// then once at every EndBlock. // // CONTRACT: Only validators with non-zero power or zero-power that were bonded // at the previous block height or were removed from the validator set entirely @@ -20,18 +27,21 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab store := ctx.KVStore(k.storeKey) maxValidators := k.GetParams(ctx).MaxValidators + totalPower := sdk.ZeroInt() - // retrieve last validator set - last := k.retrieveLastValidatorSet(ctx) + // Retrieve the last validator set. + // The persistent set is updated later in this function. + // (see LastValidatorPowerKey). + last := k.getLastValidatorsByAddr(ctx) - // iterate over validators, highest power to lowest + // Iterate over validators, highest power to lowest. iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) count := 0 for ; iterator.Valid() && count < int(maxValidators); iterator.Next() { // fetch the validator - operator := sdk.ValAddress(iterator.Value()) - validator := k.mustGetValidator(ctx, operator) + valAddr := sdk.ValAddress(iterator.Value()) + validator := k.mustGetValidator(ctx, valAddr) if validator.Jailed { panic("should never retrieve a jailed validator from the power store") @@ -57,52 +67,57 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab } // fetch the old power bytes - var operatorBytes [sdk.AddrLen]byte - copy(operatorBytes[:], operator[:]) - oldPowerBytes, found := last[operatorBytes] + var valAddrBytes [sdk.AddrLen]byte + copy(valAddrBytes[:], valAddr[:]) + oldPowerBytes, found := last[valAddrBytes] // calculate the new power bytes - newPowerBytes := validator.ABCIValidatorPowerBytes(k.cdc) - + newPower := validator.BondedTokens().RoundInt64() + newPowerBytes := k.cdc.MustMarshalBinaryLengthPrefixed(sdk.NewInt(newPower)) // update the validator set if power has changed if !found || !bytes.Equal(oldPowerBytes, newPowerBytes) { updates = append(updates, validator.ABCIValidatorUpdate()) + + // 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) + } + + // set validator power on lookup index. + k.SetLastValidatorPower(ctx, valAddr, sdk.NewInt(newPower)) } // validator still in the validator set, so delete from the copy - delete(last, operatorBytes) - - // set the bonded validator index - store.Set(GetBondedValidatorIndexKey(operator), newPowerBytes) + delete(last, valAddrBytes) // keep count count++ - + totalPower = totalPower.Add(sdk.NewInt(newPower)) } // sort the no-longer-bonded validators noLongerBonded := k.sortNoLongerBonded(last) // iterate through the sorted no-longer-bonded validators - for _, operator := range noLongerBonded { + for _, valAddrBytes := range noLongerBonded { // fetch the validator - validator := k.mustGetValidator(ctx, sdk.ValAddress(operator)) + validator := k.mustGetValidator(ctx, sdk.ValAddress(valAddrBytes)) // bonded to unbonding k.bondedToUnbonding(ctx, validator) - // remove validator if it has no more tokens - if validator.Tokens.IsZero() { - k.RemoveValidator(ctx, validator.OperatorAddr) - } - // delete from the bonded validator index - store.Delete(GetBondedValidatorIndexKey(operator)) + k.DeleteLastValidatorPower(ctx, sdk.ValAddress(valAddrBytes)) // update the validator set updates = append(updates, validator.ABCIValidatorUpdateZero()) + } + // set total power on lookup index if there are any updates + if len(updates) > 0 { + k.SetLastTotalPower(ctx, totalPower) } return updates @@ -131,8 +146,9 @@ func (k Keeper) unbondedToBonded(ctx sdk.Context, validator types.Validator) typ return k.bondValidator(ctx, validator) } +// switches a validator from unbonding state to unbonded state func (k Keeper) unbondingToUnbonded(ctx sdk.Context, validator types.Validator) types.Validator { - if validator.Status != sdk.Unbonded { + if validator.Status != sdk.Unbonding { panic(fmt.Sprintf("bad state transition unbondingToBonded, validator: %v\n", validator)) } return k.completeUnbondingValidator(ctx, validator) @@ -175,14 +191,16 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types. validator, pool = validator.UpdateStatus(pool, sdk.Bonded) k.SetPool(ctx, pool) - // save the now bonded validator record to the three referenced stores + // save the now bonded validator record to the two referenced stores k.SetValidator(ctx, validator) - k.SetValidatorByPowerIndex(ctx, validator, pool) + // delete from queue if present + k.DeleteValidatorQueue(ctx, validator) + // call the bond hook if present if k.hooks != nil { - k.hooks.OnValidatorBonded(ctx, validator.ConsAddress()) + k.hooks.OnValidatorBonded(ctx, validator.ConsAddress(), validator.OperatorAddr) } return validator @@ -208,14 +226,16 @@ func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validat validator.UnbondingMinTime = ctx.BlockHeader().Time.Add(params.UnbondingTime) validator.UnbondingHeight = ctx.BlockHeader().Height - // save the now unbonded validator record + // save the now unbonded validator record and power index k.SetValidator(ctx, validator) - k.SetValidatorByPowerIndex(ctx, validator, pool) + // Adds to unbonding validator queue + k.InsertValidatorQueue(ctx, validator) + // call the unbond hook if present if k.hooks != nil { - k.hooks.OnValidatorBeginUnbonding(ctx, validator.ConsAddress()) + k.hooks.OnValidatorBeginUnbonding(ctx, validator.ConsAddress(), validator.OperatorAddr) } return validator @@ -233,17 +253,17 @@ func (k Keeper) completeUnbondingValidator(ctx sdk.Context, validator types.Vali // map of operator addresses to serialized power type validatorsByAddr map[[sdk.AddrLen]byte][]byte -// retrieve the last validator set -func (k Keeper) retrieveLastValidatorSet(ctx sdk.Context) validatorsByAddr { +// get the last validator set +func (k Keeper) getLastValidatorsByAddr(ctx sdk.Context) validatorsByAddr { last := make(validatorsByAddr) store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + iterator := sdk.KVStorePrefixIterator(store, LastValidatorPowerKey) for ; iterator.Valid(); iterator.Next() { - var operator [sdk.AddrLen]byte - copy(operator[:], iterator.Key()[1:]) + var valAddr [sdk.AddrLen]byte + copy(valAddr[:], iterator.Key()[1:]) powerBytes := iterator.Value() - last[operator] = make([]byte, len(powerBytes)) - copy(last[operator][:], powerBytes[:]) + last[valAddr] = make([]byte, len(powerBytes)) + copy(last[valAddr][:], powerBytes[:]) } return last } @@ -254,10 +274,10 @@ func (k Keeper) sortNoLongerBonded(last validatorsByAddr) [][]byte { // sort the map keys for determinism noLongerBonded := make([][]byte, len(last)) index := 0 - for operatorBytes := range last { - operator := make([]byte, sdk.AddrLen) - copy(operator[:], operatorBytes[:]) - noLongerBonded[index] = operator + for valAddrBytes := range last { + valAddr := make([]byte, sdk.AddrLen) + copy(valAddr[:], valAddrBytes[:]) + noLongerBonded[index] = valAddr index++ } // sorted by address - order doesn't matter diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 741b787d09f6..c7919537cf9c 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -1,8 +1,10 @@ package keeper import ( + "bytes" "container/list" "fmt" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" @@ -154,6 +156,7 @@ 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 { + pool := k.GetPool(ctx) k.DeleteValidatorByPowerIndex(ctx, validator, pool) validator, pool = validator.RemoveTokens(pool, tokensToRemove) @@ -188,6 +191,9 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { if !found { return } + if validator.Status != sdk.Unbonded { + panic("Cannot call RemoveValidator on bonded or unbonding validators") + } // delete the old validator record store := ctx.KVStore(k.storeKey) @@ -196,6 +202,11 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { store.Delete(GetValidatorByConsAddrKey(sdk.ConsAddress(validator.ConsPubKey.Address()))) store.Delete(GetValidatorsByPowerIndexKey(validator, pool)) + // call hook if present + if k.hooks != nil { + k.hooks.OnValidatorRemoved(ctx, validator.ConsAddress(), validator.OperatorAddr) + } + } //___________________________________________________________________________ @@ -234,24 +245,24 @@ func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve uint16) (validators [ } // get the group of the bonded validators -func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validator) { +func (k Keeper) GetLastValidators(ctx sdk.Context) (validators []types.Validator) { store := ctx.KVStore(k.storeKey) // add the actual validator power sorted store maxValidators := k.MaxValidators(ctx) validators = make([]types.Validator, maxValidators) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + iterator := sdk.KVStorePrefixIterator(store, LastValidatorPowerKey) defer iterator.Close() i := 0 for ; iterator.Valid(); iterator.Next() { // sanity check - if i > int(maxValidators-1) { - panic("maxValidators is less than the number of records in ValidatorsBonded Store, store should have been updated") + if i >= int(maxValidators) { + panic("more validators than maxValidators found") } - address := GetAddressFromValBondedIndexKey(iterator.Key()) + address := AddressFromLastValidatorPowerKey(iterator.Key()) validator := k.mustGetValidator(ctx, address) validators[i] = validator @@ -260,7 +271,14 @@ func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validat return validators[:i] // trim } -// get the group of bonded validators sorted by power-rank +// returns an iterator for the consensus validators in the last block +func (k Keeper) LastValidatorsIterator(ctx sdk.Context) (iterator sdk.Iterator) { + store := ctx.KVStore(k.storeKey) + iterator = sdk.KVStorePrefixIterator(store, LastValidatorPowerKey) + return iterator +} + +// get the current group of bonded validators sorted by power-rank func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator { store := ctx.KVStore(k.storeKey) maxValidators := k.MaxValidators(ctx) @@ -281,3 +299,104 @@ func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator { } return validators[:i] // trim } + +// returns an iterator for the current validator power store +func (k Keeper) ValidatorsPowerStoreIterator(ctx sdk.Context) (iterator sdk.Iterator) { + store := ctx.KVStore(k.storeKey) + iterator = sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) + return iterator +} + +// gets a specific validator queue timeslice. A timeslice is a slice of ValAddresses corresponding to unbonding validators +// that expire at a certain time. +func (k Keeper) GetValidatorQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (valAddrs []sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(GetValidatorQueueTimeKey(timestamp)) + if bz == nil { + return []sdk.ValAddress{} + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &valAddrs) + return valAddrs +} + +// Sets a specific validator queue timeslice. +func (k Keeper) SetValidatorQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(keys) + store.Set(GetValidatorQueueTimeKey(timestamp), bz) +} + +// Deletes a specific validator queue timeslice. +func (k Keeper) DeleteValidatorQueueTimeSlice(ctx sdk.Context, timestamp time.Time) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetValidatorQueueTimeKey(timestamp)) +} + +// 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) + if len(timeSlice) == 0 { + k.SetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime, []sdk.ValAddress{val.OperatorAddr}) + } else { + timeSlice = append(timeSlice, val.OperatorAddr) + k.SetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime, timeSlice) + } +} + +// Delete a validator address from the validator queue +func (k Keeper) DeleteValidatorQueue(ctx sdk.Context, val types.Validator) { + timeSlice := k.GetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime) + newTimeSlice := []sdk.ValAddress{} + for _, addr := range timeSlice { + if !bytes.Equal(addr, val.OperatorAddr) { + newTimeSlice = append(newTimeSlice, addr) + } + } + if len(newTimeSlice) == 0 { + k.DeleteValidatorQueueTimeSlice(ctx, val.UnbondingMinTime) + } else { + k.SetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime, 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))) +} + +// Returns a concatenated list of all the timeslices before currTime, and deletes the timeslices from the queue +func (k Keeper) GetAllMatureValidatorQueue(ctx sdk.Context, currTime time.Time) (matureValsAddrs []sdk.ValAddress) { + // gets an iterator for all timeslices from time 0 until the current Blockheader time + validatorTimesliceIterator := k.ValidatorQueueIterator(ctx, ctx.BlockHeader().Time) + for ; validatorTimesliceIterator.Valid(); validatorTimesliceIterator.Next() { + timeslice := []sdk.ValAddress{} + k.cdc.MustUnmarshalBinaryLengthPrefixed(validatorTimesliceIterator.Value(), ×lice) + matureValsAddrs = append(matureValsAddrs, timeslice...) + } + return matureValsAddrs +} + +// Unbonds all the unbonding validators that have finished their unbonding period +func (k Keeper) UnbondAllMatureValidatorQueue(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + validatorTimesliceIterator := k.ValidatorQueueIterator(ctx, ctx.BlockHeader().Time) + for ; validatorTimesliceIterator.Valid(); validatorTimesliceIterator.Next() { + timeslice := []sdk.ValAddress{} + k.cdc.MustUnmarshalBinaryLengthPrefixed(validatorTimesliceIterator.Value(), ×lice) + for _, valAddr := range timeslice { + val, found := k.GetValidator(ctx, valAddr) + if !found { + continue + } + if val.GetStatus() != sdk.Unbonding { + panic("unexpected validator in unbonding queue, status was not unbonding") + } + k.unbondingToUnbonded(ctx, val) + if val.GetDelegatorShares().IsZero() { + k.RemoveValidator(ctx, val.OperatorAddr) + } + } + store.Delete(validatorTimesliceIterator.Key()) + } +} diff --git a/x/stake/keeper/validator_test.go b/x/stake/keeper/validator_test.go index 9b4dc3c55ef1..fe806335f093 100644 --- a/x/stake/keeper/validator_test.go +++ b/x/stake/keeper/validator_test.go @@ -49,7 +49,7 @@ func TestSetValidator(t *testing.T) { assert.True(ValEq(t, validator, resVal)) require.True(t, found) - resVals := keeper.GetValidatorsBonded(ctx) + resVals := keeper.GetLastValidators(ctx) require.Equal(t, 1, len(resVals)) assert.True(ValEq(t, validator, resVals[0])) @@ -84,7 +84,7 @@ func TestUpdateValidatorByPowerIndex(t *testing.T) { require.Equal(t, sdk.Unbonded, validator.Status) require.Equal(t, int64(100), validator.Tokens.RoundInt64()) keeper.SetPool(ctx, pool) - testingUpdateValidator(keeper, ctx, validator) + TestingUpdateValidator(keeper, ctx, validator) 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) @@ -98,7 +98,7 @@ func TestUpdateValidatorByPowerIndex(t *testing.T) { validator, pool, burned := validator.RemoveDelShares(pool, delSharesCreated.Quo(sdk.NewDec(2))) require.Equal(t, int64(50), burned.RoundInt64()) keeper.SetPool(ctx, pool) // update the pool - testingUpdateValidator(keeper, ctx, validator) // update the validator, possibly kicking it out + TestingUpdateValidator(keeper, ctx, validator) // update the validator, possibly kicking it out require.False(t, validatorByPowerIndexExists(keeper, ctx, power)) pool = keeper.GetPool(ctx) @@ -135,7 +135,7 @@ func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) { val, pool, _ = val.AddTokensFromDel(pool, sdk.NewInt(int64((i+1)*10))) keeper.SetPool(ctx, pool) - val = testingUpdateValidator(keeper, ctx, val) + val = TestingUpdateValidator(keeper, ctx, val) validators[i] = val } @@ -146,7 +146,7 @@ func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) { keeper.DeleteValidatorByPowerIndex(ctx, nextCliffVal, pool) nextCliffVal, pool, _ = nextCliffVal.RemoveDelShares(pool, sdk.NewDec(21)) keeper.SetPool(ctx, pool) - nextCliffVal = testingUpdateValidator(keeper, ctx, nextCliffVal) + nextCliffVal = TestingUpdateValidator(keeper, ctx, nextCliffVal) expectedValStatus := map[int]sdk.BondStatus{ 9: sdk.Bonded, 8: sdk.Bonded, 7: sdk.Bonded, 5: sdk.Bonded, 4: sdk.Bonded, @@ -178,7 +178,7 @@ func TestSlashToZeroPowerRemoved(t *testing.T) { require.Equal(t, int64(100), validator.Tokens.RoundInt64()) keeper.SetPool(ctx, pool) keeper.SetValidatorByConsAddr(ctx, validator) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) require.Equal(t, int64(100), validator.Tokens.RoundInt64(), "\nvalidator %v\npool %v", validator, pool) // slash the validator by 100% @@ -186,12 +186,12 @@ func TestSlashToZeroPowerRemoved(t *testing.T) { keeper.Slash(ctx, consAddr0, 0, 100, sdk.OneDec()) // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) - // validator should have been deleted - _, found := keeper.GetValidator(ctx, addrVals[0]) - require.False(t, found) + // validator should be unbonding + validator, _ = keeper.GetValidator(ctx, addrVals[0]) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) } -// This function tests UpdateValidator, GetValidator, GetValidatorsBonded, RemoveValidator +// This function tests UpdateValidator, GetValidator, GetLastValidators, RemoveValidator func TestValidatorBasics(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) pool := keeper.GetPool(ctx) @@ -213,7 +213,7 @@ func TestValidatorBasics(t *testing.T) { // check the empty keeper first _, found := keeper.GetValidator(ctx, addrVals[0]) require.False(t, found) - resVals := keeper.GetValidatorsBonded(ctx) + resVals := keeper.GetLastValidators(ctx) require.Zero(t, len(resVals)) resVals = keeper.GetValidators(ctx, 2) @@ -223,7 +223,7 @@ func TestValidatorBasics(t *testing.T) { assert.True(sdk.DecEq(t, sdk.ZeroDec(), pool.BondedTokens)) // set and retrieve a record - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) keeper.SetValidatorByConsAddr(ctx, validators[0]) resVal, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) @@ -237,7 +237,7 @@ func TestValidatorBasics(t *testing.T) { require.True(t, found) assert.True(ValEq(t, validators[0], resVal)) - resVals = keeper.GetValidatorsBonded(ctx) + resVals = keeper.GetLastValidators(ctx) require.Equal(t, 1, len(resVals)) assert.True(ValEq(t, validators[0], resVals[0])) assert.Equal(t, sdk.Bonded, validators[0].Status) @@ -250,18 +250,18 @@ func TestValidatorBasics(t *testing.T) { validators[0].Status = sdk.Bonded validators[0].Tokens = sdk.NewDec(10) validators[0].DelegatorShares = sdk.NewDec(10) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) resVal, found = keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) assert.True(ValEq(t, validators[0], resVal)) - resVals = keeper.GetValidatorsBonded(ctx) + resVals = keeper.GetLastValidators(ctx) require.Equal(t, 1, len(resVals)) assert.True(ValEq(t, validators[0], resVals[0])) // add other validators - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) - validators[2] = testingUpdateValidator(keeper, ctx, validators[2]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + validators[2] = TestingUpdateValidator(keeper, ctx, validators[2]) resVal, found = keeper.GetValidator(ctx, addrVals[1]) require.True(t, found) assert.True(ValEq(t, validators[1], resVal)) @@ -269,14 +269,16 @@ func TestValidatorBasics(t *testing.T) { require.True(t, found) assert.True(ValEq(t, validators[2], resVal)) - resVals = keeper.GetValidatorsBonded(ctx) + resVals = keeper.GetLastValidators(ctx) require.Equal(t, 3, len(resVals)) assert.True(ValEq(t, validators[0], resVals[0])) // order doesn't matter here assert.True(ValEq(t, validators[1], resVals[1])) assert.True(ValEq(t, validators[2], resVals[2])) // remove a record - keeper.RemoveValidator(ctx, validators[1].OperatorAddr) + validators[1].Status = sdk.Unbonded // First must set to Unbonded. + keeper.SetValidator(ctx, validators[1]) // ... + keeper.RemoveValidator(ctx, validators[1].OperatorAddr) // Now it can be removed. _, found = keeper.GetValidator(ctx, addrVals[1]) require.False(t, found) } @@ -294,7 +296,7 @@ func GetValidatorSortingUnmixed(t *testing.T) { validators[i].Status = sdk.Bonded validators[i].Tokens = sdk.NewDec(amt) validators[i].DelegatorShares = sdk.NewDec(amt) - testingUpdateValidator(keeper, ctx, validators[i]) + TestingUpdateValidator(keeper, ctx, validators[i]) } // first make sure everything made it in to the gotValidator group @@ -313,14 +315,14 @@ func GetValidatorSortingUnmixed(t *testing.T) { // test a basic increase in voting power validators[3].Tokens = sdk.NewDec(500) - testingUpdateValidator(keeper, ctx, validators[3]) + TestingUpdateValidator(keeper, ctx, validators[3]) 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) - testingUpdateValidator(keeper, ctx, validators[3]) + TestingUpdateValidator(keeper, ctx, validators[3]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) assert.True(ValEq(t, validators[3], resValidators[0])) @@ -329,7 +331,7 @@ func GetValidatorSortingUnmixed(t *testing.T) { // test equal voting power, different age validators[3].Tokens = sdk.NewDec(200) ctx = ctx.WithBlockHeight(10) - testingUpdateValidator(keeper, ctx, validators[3]) + TestingUpdateValidator(keeper, ctx, validators[3]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) assert.True(ValEq(t, validators[3], resValidators[0])) @@ -339,7 +341,7 @@ func GetValidatorSortingUnmixed(t *testing.T) { // no change in voting power - no change in sort ctx = ctx.WithBlockHeight(20) - testingUpdateValidator(keeper, ctx, validators[4]) + TestingUpdateValidator(keeper, ctx, validators[4]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) assert.True(ValEq(t, validators[3], resValidators[0])) @@ -348,11 +350,11 @@ func GetValidatorSortingUnmixed(t *testing.T) { // 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) - testingUpdateValidator(keeper, ctx, validators[3]) + TestingUpdateValidator(keeper, ctx, validators[3]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) ctx = ctx.WithBlockHeight(30) - testingUpdateValidator(keeper, ctx, validators[4]) + TestingUpdateValidator(keeper, ctx, validators[4]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n, "%v", resValidators) assert.True(ValEq(t, validators[3], resValidators[0])) @@ -390,7 +392,7 @@ func GetValidatorSortingMixed(t *testing.T) { validators[4].Tokens = sdk.NewDec(amts[4]) for i := range amts { - testingUpdateValidator(keeper, ctx, validators[i]) + TestingUpdateValidator(keeper, ctx, validators[i]) } val0, found := keeper.GetValidator(ctx, sdk.ValAddress(Addrs[0])) require.True(t, found) @@ -444,7 +446,7 @@ func TestGetValidatorsEdgeCases(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) validators[i].BondIntraTxCounter = int16(i) keeper.SetPool(ctx, pool) - validators[i] = testingUpdateValidator(keeper, ctx, validators[i]) + validators[i] = TestingUpdateValidator(keeper, ctx, validators[i]) } for i := range amts { @@ -460,7 +462,7 @@ func TestGetValidatorsEdgeCases(t *testing.T) { keeper.DeleteValidatorByPowerIndex(ctx, validators[0], pool) validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewInt(500)) keeper.SetPool(ctx, pool) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, nMax, uint16(len(resValidators))) assert.True(ValEq(t, validators[0], resValidators[0])) @@ -478,7 +480,7 @@ func TestGetValidatorsEdgeCases(t *testing.T) { keeper.DeleteValidatorByPowerIndex(ctx, validators[3], pool) validators[3], pool, _ = validators[3].AddTokensFromDel(pool, sdk.NewInt(1)) keeper.SetPool(ctx, pool) - validators[3] = testingUpdateValidator(keeper, ctx, validators[3]) + validators[3] = TestingUpdateValidator(keeper, ctx, validators[3]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, nMax, uint16(len(resValidators))) assert.True(ValEq(t, validators[0], resValidators[0])) @@ -488,7 +490,7 @@ func TestGetValidatorsEdgeCases(t *testing.T) { keeper.DeleteValidatorByPowerIndex(ctx, validators[3], pool) validators[3], pool, _ = validators[3].RemoveDelShares(pool, sdk.NewDec(201)) keeper.SetPool(ctx, pool) - validators[3] = testingUpdateValidator(keeper, ctx, validators[3]) + validators[3] = TestingUpdateValidator(keeper, ctx, validators[3]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, nMax, uint16(len(resValidators))) assert.True(ValEq(t, validators[0], resValidators[0])) @@ -498,7 +500,7 @@ func TestGetValidatorsEdgeCases(t *testing.T) { keeper.DeleteValidatorByPowerIndex(ctx, validators[3], pool) validators[3], pool, _ = validators[3].AddTokensFromDel(pool, sdk.NewInt(200)) keeper.SetPool(ctx, pool) - validators[3] = testingUpdateValidator(keeper, ctx, validators[3]) + validators[3] = TestingUpdateValidator(keeper, ctx, validators[3]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, nMax, uint16(len(resValidators))) assert.True(ValEq(t, validators[0], resValidators[0])) @@ -531,13 +533,13 @@ func TestValidatorBondHeight(t *testing.T) { validators[2], pool, _ = validators[2].AddTokensFromDel(pool, sdk.NewInt(100)) keeper.SetPool(ctx, pool) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) //////////////////////////////////////// // If two validators both increase to the same voting power in the same block, // the one with the first transaction should become bonded - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) - validators[2] = testingUpdateValidator(keeper, ctx, validators[2]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + validators[2] = TestingUpdateValidator(keeper, ctx, validators[2]) pool = keeper.GetPool(ctx) @@ -551,10 +553,10 @@ func TestValidatorBondHeight(t *testing.T) { validators[1], pool, _ = validators[1].AddTokensFromDel(pool, sdk.NewInt(50)) validators[2], pool, _ = validators[2].AddTokensFromDel(pool, sdk.NewInt(50)) keeper.SetPool(ctx, pool) - validators[2] = testingUpdateValidator(keeper, ctx, validators[2]) + validators[2] = TestingUpdateValidator(keeper, ctx, validators[2]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, params.MaxValidators, uint16(len(resValidators))) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) assert.True(ValEq(t, validators[0], resValidators[0])) assert.True(ValEq(t, validators[2], resValidators[1])) } @@ -575,7 +577,7 @@ func TestFullValidatorSetPowerChange(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) validators[i].BondIntraTxCounter = int16(i) keeper.SetPool(ctx, pool) - testingUpdateValidator(keeper, ctx, validators[i]) + TestingUpdateValidator(keeper, ctx, validators[i]) } for i := range amts { var found bool @@ -596,7 +598,7 @@ func TestFullValidatorSetPowerChange(t *testing.T) { pool := keeper.GetPool(ctx) validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewInt(600)) keeper.SetPool(ctx, pool) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) resValidators = keeper.GetBondedValidatorsByPower(ctx) assert.Equal(t, max, len(resValidators)) assert.True(ValEq(t, validators[0], resValidators[0])) @@ -647,14 +649,14 @@ func TestApplyAndReturnValidatorSetUpdatesIdentical(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // test identical, // tendermintUpdate set: {} -> {} - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) } @@ -669,15 +671,15 @@ func TestApplyAndReturnValidatorSetUpdatesSingleValueChange(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // test single value change // tendermintUpdate set: {} -> {c1'} validators[0].Status = sdk.Bonded validators[0].Tokens = sdk.NewDec(600) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) @@ -696,8 +698,8 @@ func TestApplyAndReturnValidatorSetUpdatesMultipleValueChange(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // test multiple value change @@ -706,8 +708,8 @@ func TestApplyAndReturnValidatorSetUpdatesMultipleValueChange(t *testing.T) { validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewInt(190)) validators[1], pool, _ = validators[1].AddTokensFromDel(pool, sdk.NewInt(80)) keeper.SetPool(ctx, pool) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 2, len(updates)) @@ -726,8 +728,8 @@ func TestApplyAndReturnValidatorSetUpdatesInserted(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // test validtor added at the beginning @@ -775,13 +777,13 @@ func TestApplyAndReturnValidatorSetUpdatesWithCliffValidator(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // test validator added at the end but not inserted in the valset // tendermintUpdate set: {} -> {} - testingUpdateValidator(keeper, ctx, validators[2]) + TestingUpdateValidator(keeper, ctx, validators[2]) updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 0, len(updates)) @@ -813,8 +815,8 @@ func TestApplyAndReturnValidatorSetUpdatesPowerDecrease(t *testing.T) { validators[i].BondIntraTxCounter = int16(i) keeper.SetPool(ctx, pool) } - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // check initial power @@ -827,8 +829,8 @@ func TestApplyAndReturnValidatorSetUpdatesPowerDecrease(t *testing.T) { validators[0], pool, _ = validators[0].RemoveDelShares(pool, sdk.NewDec(20)) validators[1], pool, _ = validators[1].RemoveDelShares(pool, sdk.NewDec(30)) keeper.SetPool(ctx, pool) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) // power has changed require.Equal(t, sdk.NewDec(80).RoundInt64(), validators[0].GetPower().RoundInt64()) diff --git a/x/stake/querier/queryable.go b/x/stake/querier/queryable.go index 8537f2bd49e7..13ff97ef3f5e 100644 --- a/x/stake/querier/queryable.go +++ b/x/stake/querier/queryable.go @@ -1,8 +1,6 @@ package querier import ( - "fmt" - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" keep "github.com/cosmos/cosmos-sdk/x/stake/keeper" @@ -12,15 +10,21 @@ import ( // query endpoints supported by the staking Querier const ( - QueryValidators = "validators" - QueryValidator = "validator" - QueryDelegator = "delegator" - QueryDelegation = "delegation" - QueryUnbondingDelegation = "unbondingDelegation" - QueryDelegatorValidators = "delegatorValidators" - QueryDelegatorValidator = "delegatorValidator" - QueryPool = "pool" - QueryParameters = "parameters" + QueryValidators = "validators" + QueryValidator = "validator" + QueryDelegatorDelegations = "delegatorDelegations" + QueryDelegatorUnbondingDelegations = "delegatorUnbondingDelegations" + QueryDelegatorRedelegations = "delegatorRedelegations" + QueryValidatorDelegations = "validatorDelegations" + QueryValidatorUnbondingDelegations = "validatorUnbondingDelegations" + QueryValidatorRedelegations = "validatorRedelegations" + QueryDelegator = "delegator" + QueryDelegation = "delegation" + QueryUnbondingDelegation = "unbondingDelegation" + QueryDelegatorValidators = "delegatorValidators" + QueryDelegatorValidator = "delegatorValidator" + QueryPool = "pool" + QueryParameters = "parameters" ) // creates a querier for staking REST endpoints @@ -31,12 +35,22 @@ func NewQuerier(k keep.Keeper, cdc *codec.Codec) sdk.Querier { return queryValidators(ctx, cdc, k) case QueryValidator: return queryValidator(ctx, cdc, req, k) - case QueryDelegator: - return queryDelegator(ctx, cdc, req, k) + case QueryValidatorDelegations: + 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: return queryUnbondingDelegation(ctx, cdc, req, k) + case QueryDelegatorDelegations: + return queryDelegatorDelegations(ctx, cdc, req, k) + case QueryDelegatorUnbondingDelegations: + return queryDelegatorUnbondingDelegations(ctx, cdc, req, k) + case QueryDelegatorRedelegations: + return queryDelegatorRedelegations(ctx, cdc, req, k) case QueryDelegatorValidators: return queryDelegatorValidators(ctx, cdc, req, k) case QueryDelegatorValidator: @@ -52,7 +66,9 @@ func NewQuerier(k keep.Keeper, cdc *codec.Codec) sdk.Querier { } // defines the params for the following queries: -// - 'custom/stake/delegator' +// - 'custom/stake/delegatorDelegations' +// - 'custom/stake/delegatorUnbondingDelegations' +// - 'custom/stake/delegatorRedelegations' // - 'custom/stake/delegatorValidators' type QueryDelegatorParams struct { DelegatorAddr sdk.AccAddress @@ -60,6 +76,9 @@ type QueryDelegatorParams struct { // defines the params for the following queries: // - 'custom/stake/validator' +// - 'custom/stake/validatorDelegations' +// - 'custom/stake/validatorUnbondingDelegations' +// - 'custom/stake/validatorRedelegations' type QueryValidatorParams struct { ValidatorAddr sdk.ValAddress } @@ -73,13 +92,35 @@ type QueryBondsParams struct { ValidatorAddr sdk.ValAddress } +// creates a new QueryDelegatorParams +func NewQueryDelegatorParams(delegatorAddr sdk.AccAddress) QueryDelegatorParams { + return QueryDelegatorParams{ + DelegatorAddr: delegatorAddr, + } +} + +// creates a new QueryValidatorParams +func NewQueryValidatorParams(validatorAddr sdk.ValAddress) QueryValidatorParams { + return QueryValidatorParams{ + ValidatorAddr: validatorAddr, + } +} + +// creates a new QueryBondsParams +func NewQueryBondsParams(delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) QueryBondsParams { + return QueryBondsParams{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + } +} + 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) res, errRes := codec.MarshalJSONIndent(cdc, validators) if err != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } @@ -89,7 +130,7 @@ func queryValidator(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress(fmt.Sprintf("incorrectly formatted request address: %s", err.Error())) + return []byte{}, sdk.ErrUnknownAddress("") } validator, found := k.GetValidator(ctx, params.ValidatorAddr) @@ -99,30 +140,109 @@ func queryValidator(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k res, errRes = codec.MarshalJSONIndent(cdc, validator) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) + } + return res, nil +} + +func queryValidatorDelegations(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("") + } + + delegations := k.GetValidatorDelegations(ctx, params.ValidatorAddr) + + res, errRes = codec.MarshalJSONIndent(cdc, delegations) + if errRes != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) + } + return res, nil +} + +func queryValidatorUnbondingDelegations(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("") + } + + unbonds := k.GetUnbondingDelegationsFromValidator(ctx, params.ValidatorAddr) + + res, errRes = codec.MarshalJSONIndent(cdc, unbonds) + if errRes != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) + } + 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 queryDelegator(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) (res []byte, err sdk.Error) { +func queryDelegatorDelegations(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(fmt.Sprintf("incorrectly formatted request address: %s", errRes.Error())) + return []byte{}, sdk.ErrUnknownAddress("") } + delegations := k.GetAllDelegatorDelegations(ctx, params.DelegatorAddr) + + res, errRes = codec.MarshalJSONIndent(cdc, delegations) + if errRes != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) + } + return res, nil +} + +func queryDelegatorUnbondingDelegations(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("") + } + unbondingDelegations := k.GetAllUnbondingDelegations(ctx, params.DelegatorAddr) - redelegations := k.GetAllRedelegations(ctx, params.DelegatorAddr) - summary := types.DelegationSummary{ - Delegations: delegations, - UnbondingDelegations: unbondingDelegations, - Redelegations: redelegations, + res, errRes = codec.MarshalJSONIndent(cdc, unbondingDelegations) + if errRes != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) + } + 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("") } - res, errRes = codec.MarshalJSONIndent(cdc, summary) + redelegations := k.GetAllRedelegations(ctx, params.DelegatorAddr) + + res, errRes = codec.MarshalJSONIndent(cdc, redelegations) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } @@ -134,14 +254,14 @@ func queryDelegatorValidators(ctx sdk.Context, cdc *codec.Codec, req abci.Reques errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress(fmt.Sprintf("incorrectly formatted request address: %s", errRes.Error())) + return []byte{}, sdk.ErrUnknownAddress("") } validators := k.GetDelegatorValidators(ctx, params.DelegatorAddr, stakeParams.MaxValidators) res, errRes = codec.MarshalJSONIndent(cdc, validators) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } @@ -151,7 +271,7 @@ func queryDelegatorValidator(ctx sdk.Context, cdc *codec.Codec, req abci.Request errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request address: %s", errRes.Error())) + return []byte{}, sdk.ErrUnknownAddress("") } validator, err := k.GetDelegatorValidator(ctx, params.DelegatorAddr, params.ValidatorAddr) @@ -161,7 +281,7 @@ func queryDelegatorValidator(ctx sdk.Context, cdc *codec.Codec, req abci.Request res, errRes = codec.MarshalJSONIndent(cdc, validator) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } @@ -171,7 +291,7 @@ func queryDelegation(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request address: %s", errRes.Error())) + return []byte{}, sdk.ErrUnknownAddress("") } delegation, found := k.GetDelegation(ctx, params.DelegatorAddr, params.ValidatorAddr) @@ -181,7 +301,7 @@ func queryDelegation(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k res, errRes = codec.MarshalJSONIndent(cdc, delegation) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } @@ -191,7 +311,7 @@ func queryUnbondingDelegation(ctx sdk.Context, cdc *codec.Codec, req abci.Reques errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request address: %s", errRes.Error())) + return []byte{}, sdk.ErrUnknownAddress("") } unbond, found := k.GetUnbondingDelegation(ctx, params.DelegatorAddr, params.ValidatorAddr) @@ -201,7 +321,7 @@ func queryUnbondingDelegation(ctx sdk.Context, cdc *codec.Codec, req abci.Reques res, errRes = codec.MarshalJSONIndent(cdc, unbond) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } @@ -211,7 +331,7 @@ func queryPool(ctx sdk.Context, cdc *codec.Codec, k keep.Keeper) (res []byte, er res, errRes := codec.MarshalJSONIndent(cdc, pool) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } @@ -221,7 +341,7 @@ func queryParameters(ctx sdk.Context, cdc *codec.Codec, k keep.Keeper) (res []by res, errRes := codec.MarshalJSONIndent(cdc, params) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } diff --git a/x/stake/querier/queryable_test.go b/x/stake/querier/queryable_test.go index d950f90bf91c..eda5c6952525 100644 --- a/x/stake/querier/queryable_test.go +++ b/x/stake/querier/queryable_test.go @@ -17,23 +17,79 @@ var ( pk1, pk2 = keep.PKs[0], keep.PKs[1] ) -func newTestDelegatorQuery(delegatorAddr sdk.AccAddress) QueryDelegatorParams { - return QueryDelegatorParams{ - DelegatorAddr: delegatorAddr, +func TestNewQuerier(t *testing.T) { + cdc := codec.New() + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + pool := keeper.GetPool(ctx) + // Create Validators + amts := []sdk.Int{sdk.NewInt(9), sdk.NewInt(8)} + var validators [2]types.Validator + for i, amt := range amts { + validators[i] = types.NewValidator(sdk.ValAddress(keep.Addrs[i]), keep.PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + validators[i].BondIntraTxCounter = int16(i) + keeper.SetValidator(ctx, validators[i]) + keeper.SetValidatorByPowerIndex(ctx, validators[i], pool) } -} + keeper.SetPool(ctx, pool) -func newTestValidatorQuery(validatorAddr sdk.ValAddress) QueryValidatorParams { - return QueryValidatorParams{ - ValidatorAddr: validatorAddr, + query := abci.RequestQuery{ + Path: "", + Data: []byte{}, } -} -func newTestBondQuery(delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) QueryBondsParams { - return QueryBondsParams{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - } + querier := NewQuerier(keeper, cdc) + + bz, err := querier(ctx, []string{"other"}, query) + require.NotNil(t, err) + require.Nil(t, bz) + + _, err = querier(ctx, []string{"validators"}, query) + require.Nil(t, err) + + _, err = querier(ctx, []string{"pool"}, query) + require.Nil(t, err) + + _, err = querier(ctx, []string{"parameters"}, query) + require.Nil(t, err) + + queryValParams := NewQueryValidatorParams(addrVal1) + bz, errRes := cdc.MarshalJSON(queryValParams) + require.Nil(t, errRes) + + query.Path = "/custom/stake/validator" + query.Data = bz + + _, err = querier(ctx, []string{"validator"}, query) + require.Nil(t, err) + + _, err = querier(ctx, []string{"validatorDelegations"}, query) + require.Nil(t, err) + + _, 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.Data = bz + + _, err = querier(ctx, []string{"delegatorDelegations"}, query) + require.Nil(t, err) + + _, err = querier(ctx, []string{"delegatorUnbondingDelegations"}, query) + require.Nil(t, err) + + _, err = querier(ctx, []string{"delegatorRedelegations"}, query) + require.Nil(t, err) + + _, err = querier(ctx, []string{"delegatorValidators"}, query) + require.Nil(t, err) } func TestQueryParametersPool(t *testing.T) { @@ -88,7 +144,7 @@ func TestQueryValidators(t *testing.T) { require.ElementsMatch(t, queriedValidators, validatorsResp) // Query each validator - queryParams := newTestValidatorQuery(addrVal1) + queryParams := NewQueryValidatorParams(addrVal1) bz, errRes := cdc.MarshalJSON(queryParams) require.Nil(t, errRes) @@ -117,13 +173,18 @@ func TestQueryDelegation(t *testing.T) { pool := keeper.GetPool(ctx) keeper.SetValidatorByPowerIndex(ctx, val1, pool) - keeper.Delegate(ctx, addrAcc2, sdk.NewCoin("steak", sdk.NewInt(20)), val1, true) + val2 := types.NewValidator(addrVal2, pk2, types.Description{}) + keeper.SetValidator(ctx, val2) + pool = keeper.GetPool(ctx) + keeper.SetValidatorByPowerIndex(ctx, val2, pool) + + keeper.Delegate(ctx, addrAcc2, sdk.NewCoin(types.DefaultBondDenom, sdk.NewInt(20)), val1, true) // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) // Query Delegator bonded validators - queryParams := newTestDelegatorQuery(addrAcc2) + queryParams := NewQueryDelegatorParams(addrAcc2) bz, errRes := cdc.MarshalJSON(queryParams) require.Nil(t, errRes) @@ -144,8 +205,14 @@ func TestQueryDelegation(t *testing.T) { require.Equal(t, len(delValidators), len(validatorsResp)) require.ElementsMatch(t, delValidators, validatorsResp) + // error unknown request + query.Data = bz[:len(bz)-1] + + _, err = queryDelegatorValidators(ctx, cdc, query, keeper) + require.NotNil(t, err) + // Query bonded validator - queryBondParams := newTestBondQuery(addrAcc2, addrVal1) + queryBondParams := NewQueryBondsParams(addrAcc2, addrVal1) bz, errRes = cdc.MarshalJSON(queryBondParams) require.Nil(t, errRes) @@ -163,6 +230,12 @@ func TestQueryDelegation(t *testing.T) { require.Equal(t, delValidators[0], validator) + // error unknown request + query.Data = bz[:len(bz)-1] + + _, err = queryDelegatorValidator(ctx, cdc, query, keeper) + require.NotNil(t, err) + // Query delegation query = abci.RequestQuery{ @@ -182,9 +255,54 @@ func TestQueryDelegation(t *testing.T) { require.Equal(t, delegation, delegationRes) + // Query Delegator Delegations + + query = abci.RequestQuery{ + Path: "/custom/stake/delegatorDelegations", + Data: bz, + } + + res, err = queryDelegatorDelegations(ctx, cdc, query, keeper) + require.Nil(t, err) + + var delegatorDelegations []types.Delegation + errRes = cdc.UnmarshalJSON(res, &delegatorDelegations) + require.Nil(t, errRes) + require.Len(t, delegatorDelegations, 1) + require.Equal(t, delegation, delegatorDelegations[0]) + + // error unknown request + query.Data = bz[:len(bz)-1] + + _, err = queryDelegation(ctx, cdc, query, keeper) + require.NotNil(t, err) + + // Query validator delegations + + bz, errRes = cdc.MarshalJSON(NewQueryValidatorParams(addrVal1)) + require.Nil(t, errRes) + + query = abci.RequestQuery{ + Path: "custom/stake/validatorDelegations", + Data: bz, + } + + res, err = queryValidatorDelegations(ctx, cdc, query, keeper) + require.Nil(t, err) + + var delegationsRes []types.Delegation + errRes = cdc.UnmarshalJSON(res, &delegationsRes) + require.Nil(t, errRes) + + require.Equal(t, delegationsRes[0], delegation) + // Query unbonging delegation keeper.BeginUnbonding(ctx, addrAcc2, val1.OperatorAddr, sdk.NewDec(10)) + queryBondParams = NewQueryBondsParams(addrAcc2, addrVal1) + bz, errRes = cdc.MarshalJSON(queryBondParams) + require.Nil(t, errRes) + query = abci.RequestQuery{ Path: "/custom/stake/unbondingDelegation", Data: bz, @@ -202,19 +320,87 @@ func TestQueryDelegation(t *testing.T) { require.Equal(t, unbond, unbondRes) - // Query Delegator Summary + // error unknown request + query.Data = bz[:len(bz)-1] + + _, err = queryUnbondingDelegation(ctx, cdc, query, keeper) + require.NotNil(t, err) + + // Query Delegator Delegations + + query = abci.RequestQuery{ + Path: "/custom/stake/delegatorUnbondingDelegations", + Data: bz, + } + + res, err = queryDelegatorUnbondingDelegations(ctx, cdc, query, keeper) + require.Nil(t, err) + + var delegatorUbds []types.UnbondingDelegation + errRes = cdc.UnmarshalJSON(res, &delegatorUbds) + require.Nil(t, errRes) + require.Equal(t, unbond, delegatorUbds[0]) + + // error unknown request + query.Data = bz[:len(bz)-1] + + _, err = queryDelegatorUnbondingDelegations(ctx, cdc, query, keeper) + require.NotNil(t, err) +} + +func TestQueryRedelegations(t *testing.T) { + cdc := codec.New() + ctx, _, keeper := keep.CreateTestInput(t, false, 10000) + + // Create Validators and Delegation + val1 := types.NewValidator(addrVal1, pk1, types.Description{}) + val2 := types.NewValidator(addrVal2, pk2, types.Description{}) + keeper.SetValidator(ctx, val1) + keeper.SetValidator(ctx, val2) + + keeper.Delegate(ctx, addrAcc2, sdk.NewCoin(types.DefaultBondDenom, sdk.NewInt(100)), val1, true) + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + + keeper.BeginRedelegation(ctx, addrAcc2, val1.GetOperator(), val2.GetOperator(), sdk.NewDec(20)) + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + + redelegation, found := keeper.GetRedelegation(ctx, addrAcc2, val1.OperatorAddr, val2.OperatorAddr) + require.True(t, found) + + // delegator redelegations + queryDelegatorParams := NewQueryDelegatorParams(addrAcc2) + bz, errRes := cdc.MarshalJSON(queryDelegatorParams) + require.Nil(t, errRes) + + query := abci.RequestQuery{ + Path: "/custom/stake/delegatorRedelegations", + Data: bz, + } + + res, err := queryDelegatorRedelegations(ctx, cdc, query, keeper) + require.Nil(t, err) + + var redsRes []types.Redelegation + errRes = cdc.UnmarshalJSON(res, &redsRes) + require.Nil(t, errRes) + + require.Equal(t, redelegation, redsRes[0]) + + // validator redelegations + queryValidatorParams := NewQueryValidatorParams(val1.GetOperator()) + bz, errRes = cdc.MarshalJSON(queryValidatorParams) + require.Nil(t, errRes) query = abci.RequestQuery{ - Path: "/custom/stake/delegator", + Path: "/custom/stake/validatorRedelegations", Data: bz, } - res, err = queryDelegator(ctx, cdc, query, keeper) + res, err = queryValidatorRedelegations(ctx, cdc, query, keeper) require.Nil(t, err) - var summary types.DelegationSummary - errRes = cdc.UnmarshalJSON(res, &summary) + errRes = cdc.UnmarshalJSON(res, &redsRes) require.Nil(t, errRes) - require.Equal(t, unbond, summary.UnbondingDelegations[0]) + require.Equal(t, redelegation, redsRes[0]) } diff --git a/x/stake/simulation/invariants.go b/x/stake/simulation/invariants.go index 2866f6292fef..439f40de3b05 100644 --- a/x/stake/simulation/invariants.go +++ b/x/stake/simulation/invariants.go @@ -1,29 +1,38 @@ package simulation import ( + "bytes" "fmt" "github.com/cosmos/cosmos-sdk/baseapp" 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/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" abci "github.com/tendermint/tendermint/abci/types" ) // AllInvariants runs all invariants of the stake module. // Currently: total supply, positive power -func AllInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) simulation.Invariant { +func AllInvariants(ck bank.Keeper, k stake.Keeper, + f auth.FeeCollectionKeeper, d distribution.Keeper, + am auth.AccountKeeper) simulation.Invariant { + return func(app *baseapp.BaseApp) error { - err := SupplyInvariants(ck, k, am)(app) + err := SupplyInvariants(ck, k, f, d, am)(app) if err != nil { return err } + err = PositivePowerInvariant(k)(app) if err != nil { return err } + err = ValidatorSetInvariant(k)(app) return err } @@ -31,19 +40,20 @@ func AllInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) simula // SupplyInvariants checks that the total supply reflects all held loose tokens, bonded tokens, and unbonding delegations // nolint: unparam -func SupplyInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) simulation.Invariant { +func SupplyInvariants(ck bank.Keeper, k stake.Keeper, + f auth.FeeCollectionKeeper, d distribution.Keeper, am auth.AccountKeeper) simulation.Invariant { return func(app *baseapp.BaseApp) error { ctx := app.NewContext(false, abci.Header{}) pool := k.GetPool(ctx) - loose := sdk.ZeroInt() + loose := sdk.ZeroDec() bonded := sdk.ZeroDec() am.IterateAccounts(ctx, func(acc auth.Account) bool { - loose = loose.Add(acc.GetCoins().AmountOf("steak")) + loose = loose.Add(sdk.NewDecFromInt(acc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom))) return false }) k.IterateUnbondingDelegations(ctx, func(_ int64, ubd stake.UnbondingDelegation) bool { - loose = loose.Add(ubd.Balance.Amount) + loose = loose.Add(sdk.NewDecFromInt(ubd.Balance.Amount)) return false }) k.IterateValidators(ctx, func(_ int64, validator sdk.Validator) bool { @@ -51,41 +61,73 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) sim case sdk.Bonded: bonded = bonded.Add(validator.GetPower()) case sdk.Unbonding: - loose = loose.Add(validator.GetTokens().RoundInt()) + loose = loose.Add(validator.GetTokens()) case sdk.Unbonded: - loose = loose.Add(validator.GetTokens().RoundInt()) + loose = loose.Add(validator.GetTokens()) } return false }) - // Loose tokens should equal coin supply plus unbonding delegations plus tokens on unbonded validators - if pool.LooseTokens.RoundInt64() != loose.Int64() { - return fmt.Errorf("expected loose tokens to equal total steak held by accounts - pool.LooseTokens: %v, sum of account tokens: %v", pool.LooseTokens.RoundInt64(), loose.Int64()) + feePool := d.GetFeePool(ctx) + + // add outstanding fees + loose = loose.Add(sdk.NewDecFromInt(f.GetCollectedFees(ctx).AmountOf(stakeTypes.DefaultBondDenom))) + + // add community pool + loose = loose.Add(feePool.CommunityPool.AmountOf(stakeTypes.DefaultBondDenom)) + + // add validator distribution pool + loose = loose.Add(feePool.ValPool.AmountOf(stakeTypes.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 + }, + ) + + // Loose 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) } // Bonded tokens should equal sum of tokens with bonded validators - if pool.BondedTokens.RoundInt64() != bonded.RoundInt64() { - return fmt.Errorf("expected bonded tokens to equal total steak held by bonded validators - pool.BondedTokens: %v, sum of bonded validator tokens: %v", pool.BondedTokens.RoundInt64(), bonded.RoundInt64()) + if !pool.BondedTokens.Equal(bonded) { + return fmt.Errorf("bonded token invariance:\n\tpool.BondedTokens: %v"+ + "\n\tsum of account tokens: %v", pool.BondedTokens, bonded) } - // TODO Inflation check on total supply return nil } } -// PositivePowerInvariant checks that all stored validators have > 0 power +// PositivePowerInvariant checks that all stored validators have > 0 power. func PositivePowerInvariant(k stake.Keeper) simulation.Invariant { return func(app *baseapp.BaseApp) error { ctx := app.NewContext(false, abci.Header{}) - var err error - k.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) bool { - if !validator.GetPower().GT(sdk.ZeroDec()) { - err = fmt.Errorf("validator with non-positive power stored. (pubkey %v)", validator.GetConsPubKey()) - return true + + iterator := k.ValidatorsPowerStoreIterator(ctx) + pool := k.GetPool(ctx) + + for ; iterator.Valid(); iterator.Next() { + validator, found := k.GetValidator(ctx, iterator.Value()) + if !found { + panic(fmt.Sprintf("validator record not found for address: %X\n", iterator.Value())) } - return false - }) - return err + + powerKey := keeper.GetValidatorsByPowerIndexKey(validator, pool) + + if !bytes.Equal(iterator.Key(), powerKey) { + return fmt.Errorf("power store invariance:\n\tvalidator.Power: %v"+ + "\n\tkey should be: %v\n\tkey in store: %v", validator.GetPower(), powerKey, iterator.Key()) + } + } + iterator.Close() + return nil } } diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index 9c2b1b98d7d7..dda344ffbd78 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -10,24 +10,27 @@ import ( "github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/mock/simulation" "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/stake/keeper" abci "github.com/tendermint/tendermint/abci/types" ) // SimulateMsgCreateValidator -func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation.Operation { +func SimulateMsgCreateValidator(m auth.AccountKeeper, k stake.Keeper) simulation.Operation { handler := stake.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) { + 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{ Moniker: simulation.RandStringOfLength(r, 10), } - maxCommission := sdk.NewInt(10) + maxCommission := sdk.NewDecWithPrec(r.Int63n(1000), 3) commission := stake.NewCommissionMsg( - sdk.NewDecWithPrec(simulation.RandomAmount(r, maxCommission).Int64(), 1), - sdk.NewDecWithPrec(simulation.RandomAmount(r, maxCommission).Int64(), 1), - sdk.NewDecWithPrec(simulation.RandomAmount(r, maxCommission).Int64(), 1), + simulation.RandomDecAmount(r, maxCommission), + maxCommission, + simulation.RandomDecAmount(r, maxCommission), ) acc := simulation.RandomAcc(r, accs) @@ -71,7 +74,9 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation // SimulateMsgEditValidator func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { handler := stake.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) { + 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{ Moniker: simulation.RandStringOfLength(r, 10), @@ -80,11 +85,10 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { Details: simulation.RandStringOfLength(r, 10), } - maxCommission := sdk.NewInt(10) - newCommissionRate := sdk.NewDecWithPrec(simulation.RandomAmount(r, maxCommission).Int64(), 1) + val := keeper.RandomValidator(r, k, ctx) + address := val.GetOperator() + newCommissionRate := simulation.RandomDecAmount(r, val.Commission.MaxRate) - acc := simulation.RandomAcc(r, accs) - address := sdk.ValAddress(acc.Address) msg := stake.MsgEditValidator{ Description: description, ValidatorAddr: address, @@ -107,13 +111,15 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { } // SimulateMsgDelegate -func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation { +func SimulateMsgDelegate(m auth.AccountKeeper, k stake.Keeper) simulation.Operation { handler := stake.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) { + 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 - validatorAcc := simulation.RandomAcc(r, accs) - validatorAddress := sdk.ValAddress(validatorAcc.Address) + val := keeper.RandomValidator(r, k, ctx) + validatorAddress := val.GetOperator() delegatorAcc := simulation.RandomAcc(r, accs) delegatorAddress := delegatorAcc.Address amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) @@ -143,29 +149,32 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operat } // SimulateMsgBeginUnbonding -func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation.Operation { +func SimulateMsgBeginUnbonding(m auth.AccountKeeper, k stake.Keeper) simulation.Operation { handler := stake.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) { + 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 - validatorAcc := simulation.RandomAcc(r, accs) - validatorAddress := sdk.ValAddress(validatorAcc.Address) delegatorAcc := simulation.RandomAcc(r, accs) delegatorAddress := delegatorAcc.Address - amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) - if amount.GT(sdk.ZeroInt()) { - amount = simulation.RandomAmount(r, amount) + delegations := k.GetAllDelegatorDelegations(ctx, delegatorAddress) + if len(delegations) == 0 { + return "no-operation", nil, nil } - if amount.Equal(sdk.ZeroInt()) { + delegation := delegations[r.Intn(len(delegations))] + + numShares := simulation.RandomDecAmount(r, delegation.Shares) + if numShares.Equal(sdk.ZeroDec()) { return "no-operation", nil, nil } msg := stake.MsgBeginUnbonding{ DelegatorAddr: delegatorAddress, - ValidatorAddr: validatorAddress, - SharesAmount: sdk.NewDecFromInt(amount), + ValidatorAddr: delegation.ValidatorAddr, + SharesAmount: numShares, } if msg.ValidateBasic() != nil { - return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s, got error %v", + msg.GetSignBytes(), msg.ValidateBasic()) } ctx, write := ctx.CacheContext() result := handler(ctx, msg) @@ -179,15 +188,17 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. } // SimulateMsgBeginRedelegate -func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation { +func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k stake.Keeper) simulation.Operation { handler := stake.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) { + 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 - sourceValidatorAcc := simulation.RandomAcc(r, accs) - sourceValidatorAddress := sdk.ValAddress(sourceValidatorAcc.Address) - destValidatorAcc := simulation.RandomAcc(r, accs) - destValidatorAddress := sdk.ValAddress(destValidatorAcc.Address) + srcVal := keeper.RandomValidator(r, k, ctx) + srcValidatorAddress := srcVal.GetOperator() + destVal := keeper.RandomValidator(r, k, ctx) + destValidatorAddress := destVal.GetOperator() delegatorAcc := simulation.RandomAcc(r, accs) delegatorAddress := delegatorAcc.Address // TODO @@ -200,7 +211,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation } msg := stake.MsgBeginRedelegate{ DelegatorAddr: delegatorAddress, - ValidatorSrcAddr: sourceValidatorAddress, + ValidatorSrcAddr: srcValidatorAddress, ValidatorDstAddr: destValidatorAddress, SharesAmount: sdk.NewDecFromInt(amount), } @@ -224,16 +235,14 @@ func Setup(mapp *mock.App, k stake.Keeper) simulation.RandSetup { return func(r *rand.Rand, accs []simulation.Account) { ctx := mapp.NewContext(false, abci.Header{}) gen := stake.DefaultGenesisState() - gen.Params.InflationMax = sdk.NewDec(0) - gen.Params.InflationMin = sdk.NewDec(0) stake.InitGenesis(ctx, k, gen) params := k.GetParams(ctx) denom := params.BondDenom loose := sdk.ZeroInt() - mapp.AccountMapper.IterateAccounts(ctx, func(acc auth.Account) bool { + mapp.AccountKeeper.IterateAccounts(ctx, func(acc auth.Account) bool { balance := simulation.RandomAmount(r, sdk.NewInt(1000000)) acc.SetCoins(acc.GetCoins().Plus(sdk.Coins{sdk.NewCoin(denom, balance)})) - mapp.AccountMapper.SetAccount(ctx, acc) + mapp.AccountKeeper.SetAccount(ctx, acc) loose = loose.Add(balance) return false }) diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go index 6aa780113939..51f633eb1a35 100644 --- a/x/stake/simulation/sim_test.go +++ b/x/stake/simulation/sim_test.go @@ -8,7 +8,9 @@ import ( abci "github.com/tendermint/tendermint/abci/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/distribution" "github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/mock/simulation" "github.com/cosmos/cosmos-sdk/x/params" @@ -20,15 +22,19 @@ func TestStakeWithRandomMessages(t *testing.T) { mapp := mock.NewApp() bank.RegisterCodec(mapp.Cdc) - mapper := mapp.AccountMapper + mapper := mapp.AccountKeeper bankKeeper := bank.NewBaseKeeper(mapper) + feeKey := sdk.NewKVStoreKey("fee") stakeKey := sdk.NewKVStoreKey("stake") stakeTKey := sdk.NewTransientStoreKey("transient_stake") paramsKey := sdk.NewKVStoreKey("params") paramsTKey := sdk.NewTransientStoreKey("transient_params") + distrKey := sdk.NewKVStoreKey("distr") - paramstore := params.NewKeeper(mapp.Cdc, paramsKey, paramsTKey).Subspace(stake.DefaultParamspace) - stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, stakeTKey, bankKeeper, paramstore, stake.DefaultCodespace) + feeCollectionKeeper := auth.NewFeeCollectionKeeper(mapp.Cdc, feeKey) + paramstore := params.NewKeeper(mapp.Cdc, paramsKey, paramsTKey) + stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, stakeTKey, bankKeeper, paramstore.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) + distrKeeper := distribution.NewKeeper(mapp.Cdc, distrKey, paramstore.Subspace(distribution.DefaultParamspace), bankKeeper, stakeKeeper, feeCollectionKeeper, distribution.DefaultCodespace) mapp.Router().AddRoute("stake", stake.NewHandler(stakeKeeper)) mapp.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { validatorUpdates := stake.EndBlocker(ctx, stakeKeeper) @@ -58,7 +64,7 @@ func TestStakeWithRandomMessages(t *testing.T) { }, []simulation.RandSetup{ Setup(mapp, stakeKeeper), }, []simulation.Invariant{ - AllInvariants(bankKeeper, stakeKeeper, mapp.AccountMapper), + AllInvariants(bankKeeper, stakeKeeper, feeCollectionKeeper, distrKeeper, mapp.AccountKeeper), }, 10, 100, false, ) diff --git a/x/stake/stake.go b/x/stake/stake.go index 0baa468ba6d2..a922d9d72e67 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -14,7 +14,6 @@ type ( Description = types.Description Commission = types.Commission Delegation = types.Delegation - DelegationSummary = types.DelegationSummary UnbondingDelegation = types.UnbondingDelegation Redelegation = types.Redelegation Params = types.Params @@ -39,12 +38,13 @@ var ( GetDelegationKey = keeper.GetDelegationKey GetDelegationsKey = keeper.GetDelegationsKey PoolKey = keeper.PoolKey + IntraTxCounterKey = keeper.IntraTxCounterKey + LastValidatorPowerKey = keeper.LastValidatorPowerKey + LastTotalPowerKey = keeper.LastTotalPowerKey ValidatorsKey = keeper.ValidatorsKey ValidatorsByConsAddrKey = keeper.ValidatorsByConsAddrKey - ValidatorsBondedIndexKey = keeper.ValidatorsBondedIndexKey ValidatorsByPowerIndexKey = keeper.ValidatorsByPowerIndexKey DelegationKey = keeper.DelegationKey - IntraTxCounterKey = keeper.IntraTxCounterKey GetUBDKey = keeper.GetUBDKey GetUBDByValIndexKey = keeper.GetUBDByValIndexKey GetUBDsKey = keeper.GetUBDsKey @@ -56,14 +56,15 @@ var ( GetREDsFromValSrcIndexKey = keeper.GetREDsFromValSrcIndexKey GetREDsToValDstIndexKey = keeper.GetREDsToValDstIndexKey GetREDsByDelToValDstIndexKey = keeper.GetREDsByDelToValDstIndexKey + TestingUpdateValidator = keeper.TestingUpdateValidator + UnbondingQueueKey = keeper.UnbondingQueueKey + RedelegationQueueKey = keeper.RedelegationQueueKey + ValidatorQueueKey = keeper.ValidatorQueueKey - DefaultParamspace = keeper.DefaultParamspace - KeyInflationRateChange = types.KeyInflationRateChange - KeyInflationMax = types.KeyInflationMax - KeyGoalBonded = types.KeyGoalBonded - KeyUnbondingTime = types.KeyUnbondingTime - KeyMaxValidators = types.KeyMaxValidators - KeyBondDenom = types.KeyBondDenom + DefaultParamspace = keeper.DefaultParamspace + KeyUnbondingTime = types.KeyUnbondingTime + KeyMaxValidators = types.KeyMaxValidators + KeyBondDenom = types.KeyBondDenom DefaultParams = types.DefaultParams InitialPool = types.InitialPool @@ -83,19 +84,26 @@ var ( NewMsgBeginUnbonding = types.NewMsgBeginUnbonding NewMsgBeginRedelegate = types.NewMsgBeginRedelegate - NewQuerier = querier.NewQuerier + NewQuerier = querier.NewQuerier + NewQueryDelegatorParams = querier.NewQueryDelegatorParams + NewQueryValidatorParams = querier.NewQueryValidatorParams + NewQueryBondsParams = querier.NewQueryBondsParams ) const ( - QueryValidators = querier.QueryValidators - QueryValidator = querier.QueryValidator - QueryDelegator = querier.QueryDelegator - QueryDelegation = querier.QueryDelegation - QueryUnbondingDelegation = querier.QueryUnbondingDelegation - QueryDelegatorValidators = querier.QueryDelegatorValidators - QueryDelegatorValidator = querier.QueryDelegatorValidator - QueryPool = querier.QueryPool - QueryParameters = querier.QueryParameters + QueryValidators = querier.QueryValidators + QueryValidator = querier.QueryValidator + QueryValidatorUnbondingDelegations = querier.QueryValidatorUnbondingDelegations + QueryValidatorRedelegations = querier.QueryValidatorRedelegations + QueryDelegation = querier.QueryDelegation + QueryUnbondingDelegation = querier.QueryUnbondingDelegation + QueryDelegatorDelegations = querier.QueryDelegatorDelegations + QueryDelegatorUnbondingDelegations = querier.QueryDelegatorUnbondingDelegations + QueryDelegatorRedelegations = querier.QueryDelegatorRedelegations + QueryDelegatorValidators = querier.QueryDelegatorValidators + QueryDelegatorValidator = querier.QueryDelegatorValidator + QueryPool = querier.QueryPool + QueryParameters = querier.QueryParameters ) const ( diff --git a/x/stake/test_common.go b/x/stake/test_common.go new file mode 100644 index 000000000000..88077d18bcda --- /dev/null +++ b/x/stake/test_common.go @@ -0,0 +1,62 @@ +package stake + +import ( + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +var ( + priv1 = ed25519.GenPrivKey() + addr1 = sdk.AccAddress(priv1.PubKey().Address()) + priv2 = ed25519.GenPrivKey() + addr2 = sdk.AccAddress(priv2.PubKey().Address()) + addr3 = sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) + priv4 = ed25519.GenPrivKey() + addr4 = sdk.AccAddress(priv4.PubKey().Address()) + coins = sdk.Coins{sdk.NewCoin("foocoin", sdk.NewInt(10))} + fee = auth.NewStdFee( + 100000, + sdk.Coins{sdk.NewCoin("foocoin", sdk.NewInt(0))}..., + ) + + commissionMsg = NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) +) + +func NewTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt int64) MsgCreateValidator { + return types.NewMsgCreateValidator( + address, pubKey, sdk.NewCoin(types.DefaultBondDenom, sdk.NewInt(amt)), Description{}, commissionMsg, + ) +} + +func NewTestMsgCreateValidatorWithCommission(address sdk.ValAddress, pubKey crypto.PubKey, + amt int64, commissionRate sdk.Dec) MsgCreateValidator { + + commission := NewCommissionMsg(commissionRate, sdk.OneDec(), sdk.ZeroDec()) + + return types.NewMsgCreateValidator( + address, pubKey, sdk.NewCoin(types.DefaultBondDenom, sdk.NewInt(amt)), Description{}, commission, + ) +} + +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)), + } +} + +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)), + } +} diff --git a/x/stake/types/commission.go b/x/stake/types/commission.go index b76971faa001..730bb6741394 100644 --- a/x/stake/types/commission.go +++ b/x/stake/types/commission.go @@ -11,7 +11,7 @@ type ( // Commission defines a commission parameters for a given validator. Commission struct { Rate sdk.Dec `json:"rate"` // the commission rate charged to delegators - MaxRate sdk.Dec `json:"max_rate"` // maximum commission rate which validator can ever charge + MaxRate sdk.Dec `json:"max_rate"` // maximum commission rate which this validator can ever charge MaxChangeRate sdk.Dec `json:"max_change_rate"` // maximum daily increase of the validator commission UpdateTime time.Time `json:"update_time"` // the last time the commission rate was changed } diff --git a/x/stake/types/delegation.go b/x/stake/types/delegation.go index d389115217d0..e7315542741d 100644 --- a/x/stake/types/delegation.go +++ b/x/stake/types/delegation.go @@ -41,20 +41,13 @@ type delegationValue struct { Height int64 } -// aggregates of all delegations, unbondings and redelegations -type DelegationSummary struct { - Delegations []Delegation `json:"delegations"` - UnbondingDelegations []UnbondingDelegation `json:"unbonding_delegations"` - Redelegations []Redelegation `json:"redelegations"` -} - // return the delegation without fields contained within the key for the store func MustMarshalDelegation(cdc *codec.Codec, delegation Delegation) []byte { val := delegationValue{ delegation.Shares, delegation.Height, } - return cdc.MustMarshalBinary(val) + return cdc.MustMarshalBinaryLengthPrefixed(val) } // return the delegation without fields contained within the key for the store @@ -69,7 +62,7 @@ func MustUnmarshalDelegation(cdc *codec.Codec, key, value []byte) 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.UnmarshalBinary(value, &storeValue) + err = cdc.UnmarshalBinaryLengthPrefixed(value, &storeValue) if err != nil { err = fmt.Errorf("%v: %v", ErrNoDelegation(DefaultCodespace).Data(), err) return @@ -104,9 +97,9 @@ func (d Delegation) Equal(d2 Delegation) bool { var _ sdk.Delegation = Delegation{} // nolint - for sdk.Delegation -func (d Delegation) GetDelegator() sdk.AccAddress { return d.DelegatorAddr } -func (d Delegation) GetValidator() sdk.ValAddress { return d.ValidatorAddr } -func (d Delegation) GetShares() sdk.Dec { return d.Shares } +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 @@ -115,7 +108,7 @@ 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", d.Shares.String()) + resp += fmt.Sprintf("Shares: %s\n", d.Shares.String()) resp += fmt.Sprintf("Height: %d", d.Height) return resp, nil @@ -146,7 +139,7 @@ func MustMarshalUBD(cdc *codec.Codec, ubd UnbondingDelegation) []byte { ubd.InitialBalance, ubd.Balance, } - return cdc.MustMarshalBinary(val) + return cdc.MustMarshalBinaryLengthPrefixed(val) } // unmarshal a unbonding delegation from a store key and value @@ -161,7 +154,7 @@ func MustUnmarshalUBD(cdc *codec.Codec, key, value []byte) UnbondingDelegation { // 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.UnmarshalBinary(value, &storeValue) + err = cdc.UnmarshalBinaryLengthPrefixed(value, &storeValue) if err != nil { return } @@ -186,8 +179,8 @@ func UnmarshalUBD(cdc *codec.Codec, key, value []byte) (ubd UnbondingDelegation, // nolint func (d UnbondingDelegation) Equal(d2 UnbondingDelegation) bool { - bz1 := MsgCdc.MustMarshalBinary(&d) - bz2 := MsgCdc.MustMarshalBinary(&d2) + bz1 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d) + bz2 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d2) return bytes.Equal(bz1, bz2) } @@ -238,7 +231,7 @@ func MustMarshalRED(cdc *codec.Codec, red Redelegation) []byte { red.SharesSrc, red.SharesDst, } - return cdc.MustMarshalBinary(val) + return cdc.MustMarshalBinaryLengthPrefixed(val) } // unmarshal a redelegation from a store key and value @@ -253,7 +246,7 @@ func MustUnmarshalRED(cdc *codec.Codec, key, value []byte) Redelegation { // 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.UnmarshalBinary(value, &storeValue) + err = cdc.UnmarshalBinaryLengthPrefixed(value, &storeValue) if err != nil { return } @@ -282,8 +275,8 @@ func UnmarshalRED(cdc *codec.Codec, key, value []byte) (red Redelegation, err er // nolint func (d Redelegation) Equal(d2 Redelegation) bool { - bz1 := MsgCdc.MustMarshalBinary(&d) - bz2 := MsgCdc.MustMarshalBinary(&d2) + bz1 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d) + bz2 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d2) return bytes.Equal(bz1, bz2) } @@ -297,7 +290,7 @@ func (d Redelegation) HumanReadableString() (string, error) { 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", d.SharesSrc.String()) + 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/errors.go b/x/stake/types/errors.go index 84a7e5ae6826..1a6ed6a64169 100644 --- a/x/stake/types/errors.go +++ b/x/stake/types/errors.go @@ -155,6 +155,10 @@ func ErrNoRedelegation(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "no redelegation found") } +func ErrSelfRedelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "cannot redelegate to the same validator") +} + func ErrBadRedelegationDst(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "redelegation validator not found") } @@ -164,6 +168,16 @@ 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") +} + func ErrBothShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "both shares amount and shares percent provided") } diff --git a/x/stake/types/genesis.go b/x/stake/types/genesis.go index d08c6b899341..1085e8f97087 100644 --- a/x/stake/types/genesis.go +++ b/x/stake/types/genesis.go @@ -1,11 +1,27 @@ package types +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + // GenesisState - all staking state that must be provided at genesis type GenesisState struct { - Pool Pool `json:"pool"` - Params Params `json:"params"` - Validators []Validator `json:"validators"` - Bonds []Delegation `json:"bonds"` + Pool Pool `json:"pool"` + Params Params `json:"params"` + IntraTxCounter int16 `json:"intra_tx_counter"` + LastTotalPower sdk.Int `json:"last_total_power"` + LastValidatorPowers []LastValidatorPower `json:"last_validator_powers"` + Validators []Validator `json:"validators"` + Bonds []Delegation `json:"bonds"` + UnbondingDelegations []UnbondingDelegation `json:"unbonding_delegations"` + Redelegations []Redelegation `json:"redelegations"` + Exported bool `json:"exported"` +} + +// Last validator power, needed for validator set update logic +type LastValidatorPower struct { + Address sdk.ValAddress + Power sdk.Int } func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []Delegation) GenesisState { diff --git a/x/stake/types/inflation_test.go b/x/stake/types/inflation_test.go deleted file mode 100644 index 159ecb4c4e14..000000000000 --- a/x/stake/types/inflation_test.go +++ /dev/null @@ -1,145 +0,0 @@ -package types - -import ( - "math/rand" - "testing" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -//changing the int in NewSource will allow you to test different, deterministic, sets of operations -var r = rand.New(rand.NewSource(6595)) - -func TestGetInflation(t *testing.T) { - pool := InitialPool() - params := DefaultParams() - - // Governing Mechanism: - // BondedRatio = BondedTokens / TotalSupply - // inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange - - tests := []struct { - name string - setBondedTokens, setLooseTokens, - setInflation, expectedChange sdk.Dec - }{ - // with 0% bonded atom supply the inflation should increase by InflationRateChange - {"test 1", sdk.ZeroDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(7, 2), params.InflationRateChange.Quo(hrsPerYrDec)}, - - // 100% bonded, starting at 20% inflation and being reduced - // (1 - (1/0.67))*(0.13/8667) - {"test 2", sdk.OneDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(20, 2), - sdk.OneDec().Sub(sdk.OneDec().Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrDec)}, - - // 50% bonded, starting at 10% inflation and being increased - {"test 3", sdk.OneDec(), sdk.OneDec(), sdk.NewDecWithPrec(10, 2), - sdk.OneDec().Sub(sdk.NewDecWithPrec(5, 1).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrDec)}, - - // test 7% minimum stop (testing with 100% bonded) - {"test 4", sdk.OneDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(7, 2), sdk.ZeroDec()}, - {"test 5", sdk.OneDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(70001, 6), sdk.NewDecWithPrec(-1, 6)}, - - // test 20% maximum stop (testing with 0% bonded) - {"test 6", sdk.ZeroDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(20, 2), sdk.ZeroDec()}, - {"test 7", sdk.ZeroDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(199999, 6), sdk.NewDecWithPrec(1, 6)}, - - // perfect balance shouldn't change inflation - {"test 8", sdk.NewDec(67), sdk.NewDec(33), sdk.NewDecWithPrec(15, 2), sdk.ZeroDec()}, - } - for _, tc := range tests { - pool.BondedTokens, pool.LooseTokens = tc.setBondedTokens, tc.setLooseTokens - pool.Inflation = tc.setInflation - - inflation := pool.NextInflation(params) - diffInflation := inflation.Sub(tc.setInflation) - - require.True(t, diffInflation.Equal(tc.expectedChange), - "Name: %v\nDiff: %v\nExpected: %v\n", tc.name, diffInflation, tc.expectedChange) - } -} - -// Test that provisions are correctly added to the pool and validators each hour for 1 year -func TestProcessProvisions(t *testing.T) { - pool := InitialPool() - params := DefaultParams() - - var ( - initialTotalTokens int64 = 550000000 - cumulativeExpProvs = sdk.ZeroDec() - ) - pool.LooseTokens = sdk.NewDec(initialTotalTokens) - - // process the provisions for a year - for hr := 0; hr < 100; hr++ { - var expProvisions sdk.Dec - _, expProvisions, pool = updateProvisions(t, pool, params, hr) - cumulativeExpProvs = cumulativeExpProvs.Add(expProvisions) - } - - //get the pool and do the final value checks from checkFinalPoolValues - checkFinalPoolValues(t, pool, sdk.NewDec(initialTotalTokens), cumulativeExpProvs) -} - -//_________________________________________________________________________________________ -////////////////////////////////HELPER FUNCTIONS BELOW///////////////////////////////////// - -// Final check on the global pool values for what the total tokens accumulated from each hour of provisions -func checkFinalPoolValues(t *testing.T, pool Pool, initialTotalTokens, cumulativeExpProvs sdk.Dec) { - calculatedTotalTokens := initialTotalTokens.Add(cumulativeExpProvs) - require.True(sdk.DecEq(t, calculatedTotalTokens, pool.TokenSupply())) -} - -// Processes provisions are added to the pool correctly every hour -// Returns expected Provisions, expected Inflation, and pool, to help with cumulative calculations back in main Tests -func updateProvisions(t *testing.T, pool Pool, params Params, hr int) (sdk.Dec, sdk.Dec, Pool) { - - expInflation := pool.NextInflation(params) - expProvisions := expInflation.Mul(pool.TokenSupply()).Quo(hrsPerYrDec) - startTotalSupply := pool.TokenSupply() - pool = pool.ProcessProvisions(params) - - //check provisions were added to pool - require.True(sdk.DecEq(t, startTotalSupply.Add(expProvisions), pool.TokenSupply())) - - return expInflation, expProvisions, pool -} - -// Checks that The inflation will correctly increase or decrease after an update to the pool -func checkInflation(t *testing.T, pool Pool, previousInflation, updatedInflation sdk.Dec, msg string) { - inflationChange := updatedInflation.Sub(previousInflation) - - switch { - //BELOW 67% - Rate of change positive and increasing, while we are between 7% <= and < 20% inflation - case pool.BondedRatio().LT(sdk.NewDecWithPrec(67, 2)) && updatedInflation.LT(sdk.NewDecWithPrec(20, 2)): - require.Equal(t, true, inflationChange.GT(sdk.ZeroDec()), msg) - - //BELOW 67% - Rate of change should be 0 while inflation continually stays at 20% until we reach 67% bonded ratio - case pool.BondedRatio().LT(sdk.NewDecWithPrec(67, 2)) && updatedInflation.Equal(sdk.NewDecWithPrec(20, 2)): - if previousInflation.Equal(sdk.NewDecWithPrec(20, 2)) { - require.Equal(t, true, inflationChange.IsZero(), msg) - - //This else statement covers the one off case where we first hit 20%, but we still needed a positive ROC to get to 67% bonded ratio (i.e. we went from 19.99999% to 20%) - } else { - require.Equal(t, true, inflationChange.GT(sdk.ZeroDec()), msg) - } - - //ABOVE 67% - Rate of change should be negative while the bond is above 67, and should stay negative until we reach inflation of 7% - case pool.BondedRatio().GT(sdk.NewDecWithPrec(67, 2)) && - updatedInflation.LT(sdk.NewDecWithPrec(20, 2)) && updatedInflation.GT(sdk.NewDecWithPrec(7, 2)): - require.Equal(t, true, inflationChange.LT(sdk.ZeroDec()), msg) - - //ABOVE 67% - Rate of change should be 0 while inflation continually stays at 7%. - case pool.BondedRatio().GT(sdk.NewDecWithPrec(67, 2)) && - updatedInflation.Equal(sdk.NewDecWithPrec(7, 2)): - - if previousInflation.Equal(sdk.NewDecWithPrec(7, 2)) { - require.Equal(t, true, inflationChange.IsZero(), msg) - - //This else statement covers the one off case where we first hit 7%, but we still needed a negative ROC to continue to get down to 67%. (i.e. we went from 7.00001% to 7%) - } else { - require.Equal(t, true, inflationChange.LT(sdk.ZeroDec()), msg) - } - } -} diff --git a/x/stake/types/msg.go b/x/stake/types/msg.go index da46415d254f..63d0afb5fbd1 100644 --- a/x/stake/types/msg.go +++ b/x/stake/types/msg.go @@ -7,8 +7,8 @@ import ( "github.com/tendermint/tendermint/crypto" ) -// name to identify transaction types -const MsgType = "stake" +// name to identify transaction routes +const MsgRoute = "stake" // Verify interface at compile time var _, _, _ sdk.Msg = &MsgCreateValidator{}, &MsgEditValidator{}, &MsgDelegate{} @@ -48,8 +48,8 @@ func NewMsgCreateValidatorOnBehalfOf(delAddr sdk.AccAddress, valAddr sdk.ValAddr } //nolint -func (msg MsgCreateValidator) Type() string { return MsgType } -func (msg MsgCreateValidator) Name() string { return "create_validator" } +func (msg MsgCreateValidator) Route() string { return MsgRoute } +func (msg MsgCreateValidator) Type() string { return "create_validator" } // Return address(es) that must sign over msg.GetSignBytes() func (msg MsgCreateValidator) GetSigners() []sdk.AccAddress { @@ -68,6 +68,7 @@ func (msg MsgCreateValidator) GetSigners() []sdk.AccAddress { 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"` @@ -129,8 +130,8 @@ func NewMsgEditValidator(valAddr sdk.ValAddress, description Description, newRat } //nolint -func (msg MsgEditValidator) Type() string { return MsgType } -func (msg MsgEditValidator) Name() string { return "edit_validator" } +func (msg MsgEditValidator) Route() string { return MsgRoute } +func (msg MsgEditValidator) Type() string { return "edit_validator" } func (msg MsgEditValidator) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{sdk.AccAddress(msg.ValidatorAddr)} } @@ -181,8 +182,8 @@ func NewMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, delegation s } //nolint -func (msg MsgDelegate) Type() string { return MsgType } -func (msg MsgDelegate) Name() string { return "delegate" } +func (msg MsgDelegate) Route() string { return MsgRoute } +func (msg MsgDelegate) Type() string { return "delegate" } func (msg MsgDelegate) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.DelegatorAddr} } @@ -232,8 +233,8 @@ func NewMsgBeginRedelegate(delAddr sdk.AccAddress, valSrcAddr, } //nolint -func (msg MsgBeginRedelegate) Type() string { return MsgType } -func (msg MsgBeginRedelegate) Name() string { return "begin_redelegate" } +func (msg MsgBeginRedelegate) Route() string { return MsgRoute } +func (msg MsgBeginRedelegate) Type() string { return "begin_redelegate" } func (msg MsgBeginRedelegate) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.DelegatorAddr} } @@ -292,8 +293,8 @@ func NewMsgBeginUnbonding(delAddr sdk.AccAddress, valAddr sdk.ValAddress, shares } //nolint -func (msg MsgBeginUnbonding) Type() string { return MsgType } -func (msg MsgBeginUnbonding) Name() string { return "begin_unbonding" } +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} } // get the bytes for the message signer to sign on diff --git a/x/stake/types/msg_test.go b/x/stake/types/msg_test.go index 7c1800b8e42b..7e719f3eef8d 100644 --- a/x/stake/types/msg_test.go +++ b/x/stake/types/msg_test.go @@ -10,9 +10,9 @@ import ( ) var ( - coinPos = sdk.NewInt64Coin("steak", 1000) - coinZero = sdk.NewInt64Coin("steak", 0) - coinNeg = sdk.NewInt64Coin("steak", -10000) + coinPos = sdk.NewInt64Coin(DefaultBondDenom, 1000) + coinZero = sdk.NewInt64Coin(DefaultBondDenom, 0) + coinNeg = sdk.NewInt64Coin(DefaultBondDenom, -10000) ) // test ValidateBasic for MsgCreateValidator diff --git a/x/stake/types/params.go b/x/stake/types/params.go index abc1db4d56a2..4e9aba5ab020 100644 --- a/x/stake/types/params.go +++ b/x/stake/types/params.go @@ -6,7 +6,6 @@ import ( "time" "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" ) @@ -20,28 +19,22 @@ const ( // if this is 1, the validator set at the end of a block will sign the block after the next. // Constant as this should not change without a hard fork. ValidatorUpdateDelay int64 = 1 + + // Default bondable coin denomination + DefaultBondDenom = "STAKE" ) // nolint - Keys for parameter access var ( - KeyInflationRateChange = []byte("InflationRateChange") - KeyInflationMax = []byte("InflationMax") - KeyInflationMin = []byte("InflationMin") - KeyGoalBonded = []byte("GoalBonded") - KeyUnbondingTime = []byte("UnbondingTime") - KeyMaxValidators = []byte("MaxValidators") - KeyBondDenom = []byte("BondDenom") + KeyUnbondingTime = []byte("UnbondingTime") + KeyMaxValidators = []byte("MaxValidators") + KeyBondDenom = []byte("BondDenom") ) var _ params.ParamSet = (*Params)(nil) // Params defines the high level settings for staking type Params struct { - InflationRateChange sdk.Dec `json:"inflation_rate_change"` // maximum annual change in inflation rate - InflationMax sdk.Dec `json:"inflation_max"` // maximum inflation rate - InflationMin sdk.Dec `json:"inflation_min"` // minimum inflation rate - GoalBonded sdk.Dec `json:"goal_bonded"` // Goal of percent bonded atoms - UnbondingTime time.Duration `json:"unbonding_time"` MaxValidators uint16 `json:"max_validators"` // maximum number of validators @@ -51,10 +44,6 @@ type Params struct { // Implements params.ParamSet func (p *Params) KeyValuePairs() params.KeyValuePairs { return params.KeyValuePairs{ - {KeyInflationRateChange, &p.InflationRateChange}, - {KeyInflationMax, &p.InflationMax}, - {KeyInflationMin, &p.InflationMin}, - {KeyGoalBonded, &p.GoalBonded}, {KeyUnbondingTime, &p.UnbondingTime}, {KeyMaxValidators, &p.MaxValidators}, {KeyBondDenom, &p.BondDenom}, @@ -63,21 +52,17 @@ func (p *Params) KeyValuePairs() params.KeyValuePairs { // Equal returns a boolean determining if two Param types are identical. func (p Params) Equal(p2 Params) bool { - bz1 := MsgCdc.MustMarshalBinary(&p) - bz2 := MsgCdc.MustMarshalBinary(&p2) + 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{ - InflationRateChange: sdk.NewDecWithPrec(13, 2), - InflationMax: sdk.NewDecWithPrec(20, 2), - InflationMin: sdk.NewDecWithPrec(7, 2), - GoalBonded: sdk.NewDecWithPrec(67, 2), - UnbondingTime: defaultUnbondingTime, - MaxValidators: 100, - BondDenom: "steak", + UnbondingTime: defaultUnbondingTime, + MaxValidators: 100, + BondDenom: DefaultBondDenom, } } @@ -85,13 +70,9 @@ func DefaultParams() Params { // parameters. func (p Params) HumanReadableString() string { - resp := "Pool \n" - resp += fmt.Sprintf("Maximum Annual Inflation Rate Change: %s\n", p.InflationRateChange) - resp += fmt.Sprintf("Max Inflation Rate: %s\n", p.InflationMax) - resp += fmt.Sprintf("Min Inflation Tate: %s\n", p.InflationMin) - resp += fmt.Sprintf("Bonded Token Goal (%s): %s\n", "s", p.GoalBonded) + resp := "Params \n" resp += fmt.Sprintf("Unbonding Time: %s\n", p.UnbondingTime) - resp += fmt.Sprintf("Max Validators: %d: \n", p.MaxValidators) + resp += fmt.Sprintf("Max Validators: %d\n", p.MaxValidators) resp += fmt.Sprintf("Bonded Coin Denomination: %s\n", p.BondDenom) return resp } @@ -107,7 +88,7 @@ func MustUnmarshalParams(cdc *codec.Codec, value []byte) Params { // unmarshal the current staking params value from store key func UnmarshalParams(cdc *codec.Codec, value []byte) (params Params, err error) { - err = cdc.UnmarshalBinary(value, ¶ms) + err = cdc.UnmarshalBinaryLengthPrefixed(value, ¶ms) if err != nil { return } diff --git a/x/stake/types/pool.go b/x/stake/types/pool.go index c7cb6974868e..4b227aa8051e 100644 --- a/x/stake/types/pool.go +++ b/x/stake/types/pool.go @@ -3,7 +3,6 @@ package types import ( "bytes" "fmt" - "time" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -11,33 +10,22 @@ 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 - InflationLastTime time.Time `json:"inflation_last_time"` // block which the last inflation was processed - Inflation sdk.Dec `json:"inflation"` // current annual inflation rate - - DateLastCommissionReset int64 `json:"date_last_commission_reset"` // unix timestamp for last commission accounting reset (daily) - - // Fee Related - PrevBondedShares sdk.Dec `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calculations + LooseTokens sdk.Dec `json:"loose_tokens"` // tokens which are not bonded in a validator + BondedTokens sdk.Dec `json:"bonded_tokens"` // reserve of bonded tokens } // nolint func (p Pool) Equal(p2 Pool) bool { - bz1 := MsgCdc.MustMarshalBinary(&p) - bz2 := MsgCdc.MustMarshalBinary(&p2) + bz1 := MsgCdc.MustMarshalBinaryLengthPrefixed(&p) + bz2 := MsgCdc.MustMarshalBinaryLengthPrefixed(&p2) return bytes.Equal(bz1, bz2) } // initial pool for testing func InitialPool() Pool { return Pool{ - LooseTokens: sdk.ZeroDec(), - BondedTokens: sdk.ZeroDec(), - InflationLastTime: time.Unix(0, 0), - Inflation: sdk.NewDecWithPrec(7, 2), - DateLastCommissionReset: 0, - PrevBondedShares: sdk.ZeroDec(), + LooseTokens: sdk.ZeroDec(), + BondedTokens: sdk.ZeroDec(), } } @@ -79,52 +67,6 @@ func (p Pool) bondedTokensToLoose(bondedTokens sdk.Dec) Pool { return p } -//_______________________________________________________________________ -// Inflation - -const precision = 10000 // increased to this precision for accuracy -var hrsPerYrDec = sdk.NewDec(8766) // as defined by a julian year of 365.25 days - -// process provisions for an hour period -func (p Pool) ProcessProvisions(params Params) Pool { - p.Inflation = p.NextInflation(params) - provisions := p.Inflation. - Mul(p.TokenSupply()). - Quo(hrsPerYrDec) - - // TODO add to the fees provisions - p.LooseTokens = p.LooseTokens.Add(provisions) - return p -} - -// get the next inflation rate for the hour -func (p Pool) NextInflation(params Params) (inflation sdk.Dec) { - - // The target annual inflation rate is recalculated for each previsions cycle. The - // inflation is also subject to a rate change (positive or negative) depending on - // the distance from the desired 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%. - - // (1 - bondedRatio/GoalBonded) * InflationRateChange - inflationRateChangePerYear := sdk.OneDec(). - Sub(p.BondedRatio(). - Quo(params.GoalBonded)). - Mul(params.InflationRateChange) - inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYrDec) - - // increase the new annual inflation for this next cycle - inflation = p.Inflation.Add(inflationRateChange) - if inflation.GT(params.InflationMax) { - inflation = params.InflationMax - } - if inflation.LT(params.InflationMin) { - inflation = params.InflationMin - } - - return inflation -} - // HumanReadableString returns a human readable string representation of a // pool. func (p Pool) HumanReadableString() string { @@ -134,10 +76,6 @@ func (p Pool) HumanReadableString() string { 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()) - resp += fmt.Sprintf("Previous Inflation Block: %s\n", p.InflationLastTime) - resp += fmt.Sprintf("Inflation: %v\n", p.Inflation) - resp += fmt.Sprintf("Date of Last Commission Reset: %d\n", p.DateLastCommissionReset) - resp += fmt.Sprintf("Previous Bonded Shares: %v\n", p.PrevBondedShares) return resp } @@ -152,7 +90,7 @@ func MustUnmarshalPool(cdc *codec.Codec, value []byte) Pool { // unmarshal the current pool value from store key func UnmarshalPool(cdc *codec.Codec, value []byte) (pool Pool, err error) { - err = cdc.UnmarshalBinary(value, &pool) + err = cdc.UnmarshalBinaryLengthPrefixed(value, &pool) if err != nil { return } diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index dbd4e2a54000..ca56e0ea4aae 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -87,7 +87,7 @@ func MustMarshalValidator(cdc *codec.Codec, validator Validator) []byte { UnbondingMinTime: validator.UnbondingMinTime, Commission: validator.Commission, } - return cdc.MustMarshalBinary(val) + return cdc.MustMarshalBinaryLengthPrefixed(val) } // unmarshal a redelegation from a store key and value @@ -106,7 +106,7 @@ func UnmarshalValidator(cdc *codec.Codec, operatorAddr, value []byte) (validator return } var storeValue validatorValue - err = cdc.UnmarshalBinary(value, &storeValue) + err = cdc.UnmarshalBinaryLengthPrefixed(value, &storeValue) if err != nil { return } @@ -314,12 +314,6 @@ func (v Validator) ABCIValidatorUpdate() abci.ValidatorUpdate { } } -// ABCIValidatorPowerBytes -func (v Validator) ABCIValidatorPowerBytes(cdc *codec.Codec) []byte { - power := v.BondedTokens().RoundInt64() - return cdc.MustMarshalBinary(power) -} - // ABCIValidatorUpdateZero returns an abci.ValidatorUpdate from a staked validator type // with zero power used for validator updates. func (v Validator) ABCIValidatorUpdateZero() abci.ValidatorUpdate { @@ -398,6 +392,9 @@ func (v Validator) AddTokensFromDel(pool Pool, amount sdk.Int) (Validator, Pool, pool = pool.looseTokensToBonded(amountDec) } + if exRate.IsZero() { + panic("zero exRate should not happen") + } v.Tokens = v.Tokens.Add(amountDec) issuedShares := amountDec.Quo(exRate) v.DelegatorShares = v.DelegatorShares.Add(issuedShares) @@ -435,21 +432,6 @@ func (v Validator) BondedTokens() sdk.Dec { return sdk.ZeroDec() } -// TODO remove this once the validator queue logic is implemented -// Returns if the validator should be considered unbonded -func (v Validator) IsUnbonded(ctx sdk.Context) bool { - switch v.Status { - case sdk.Unbonded: - return true - case sdk.Unbonding: - ctxTime := ctx.BlockHeader().Time - if ctxTime.After(v.UnbondingMinTime) { - return true - } - } - return false -} - //______________________________________________________________________ // ensure fulfills the sdk validator types diff --git a/x/stake/types/validator_test.go b/x/stake/types/validator_test.go index 36ac0da57611..b01b2b7444fc 100644 --- a/x/stake/types/validator_test.go +++ b/x/stake/types/validator_test.go @@ -3,7 +3,6 @@ package types import ( "fmt" "testing" - "time" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -185,10 +184,8 @@ func TestRemoveDelShares(t *testing.T) { DelegatorShares: delShares, } pool := Pool{ - BondedTokens: sdk.NewDec(248305), - LooseTokens: sdk.NewDec(232147), - InflationLastTime: time.Unix(0, 0), - Inflation: sdk.NewDecWithPrec(7, 2), + BondedTokens: sdk.NewDec(248305), + LooseTokens: sdk.NewDec(232147), } shares := sdk.NewDec(29) _, newPool, tokens := validator.RemoveDelShares(pool, shares) @@ -238,10 +235,8 @@ func TestPossibleOverflow(t *testing.T) { DelegatorShares: delShares, } pool := Pool{ - LooseTokens: sdk.NewDec(100), - BondedTokens: poolTokens, - InflationLastTime: time.Unix(0, 0), - Inflation: sdk.NewDecWithPrec(7, 2), + LooseTokens: sdk.NewDec(100), + BondedTokens: poolTokens, } tokens := int64(71) msg := fmt.Sprintf("validator %#v", validator)