diff --git a/.circleci/config.yml b/.circleci/config.yml index 269d4ca94..a62c61fd3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -154,6 +154,7 @@ jobs: command: | export PATH="$GOBIN:$PATH" make install + export VERSION="$(git describe --tags --long | sed 's/v\(.*\)/\1/')" for pkg in $(go list github.com/cosmos/cosmos-sdk/... | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test | grep -v '/simulation' | circleci tests split --split-by=timings); do id=$(basename "$pkg") GOCACHE=off go test -timeout 8m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log" diff --git a/Gopkg.lock b/Gopkg.lock index 6c36fb53e..5160d752e 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -34,11 +34,11 @@ [[projects]] branch = "master" - digest = "1:2c00f064ba355903866cbfbf3f7f4c0fe64af6638cc7d1b8bdcf3181bc67f1d8" + digest = "1:c0decf632843204d2b8781de7b26e7038584e2dcccc7e2f401e88ae85b1df2b7" name = "github.com/btcsuite/btcd" packages = ["btcec"] pruneopts = "UT" - revision = "cff30e1d23fc9e800b2b5b4b41ef1817dda07e9f" + revision = "2a560b2036bee5e3679ec2133eb6520b2f195213" [[projects]] digest = "1:386de157f7d19259a7f9c81f26ce011223ce0f090353c1152ffdf730d7d10ac2" @@ -230,6 +230,14 @@ revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" version = "v1.0.1" +[[projects]] + digest = "1:78bbb1ba5b7c3f2ed0ea1eab57bdd3859aec7e177811563edc41198a760b06af" + name = "github.com/mitchellh/go-homedir" + packages = ["."] + pruneopts = "UT" + revision = "ae18d6b8b3205b561c79e8e5f69bff09736185f4" + version = "v1.0.0" + [[projects]] digest = "1:645110e089152bd0f4a011a2648fbb0e4df5977be73ca605781157ac297f50c4" name = "github.com/mitchellh/mapstructure" @@ -294,7 +302,7 @@ [[projects]] branch = "master" - digest = "1:8c49953a1414305f2ff5465147ee576dd705487c35b15918fcd4efdc0cb7a290" + digest = "1:ef1dd9945e58ee9b635273d28c0ef3fa3742a7dedc038ebe207fd63e6ce000ef" name = "github.com/prometheus/procfs" packages = [ ".", @@ -303,7 +311,7 @@ "xfs", ] pruneopts = "UT" - revision = "05ee40e3a273f7245e8777337fc7b46e533a9a92" + revision = "418d78d0b9a7b7de3a6bbc8a23def624cc977bb2" [[projects]] digest = "1:c4556a44e350b50a490544d9b06e9fba9c286c21d6c0e47f54f3a9214597298c" @@ -423,12 +431,12 @@ version = "v0.12.0" [[projects]] - digest = "1:e99ef92d64f2391efbbfb15310df635f96247532bbac2676ea43e466d706401d" + digest = "1:53397098d6acb7613358683cc84ae59281a60c6033f0bff62fa8d3f279c6c430" name = "github.com/tendermint/iavl" packages = ["."] pruneopts = "UT" - revision = "e5726c0066ccdd299a2ec9262f93c7896cdfcd87" - version = "v0.10.0" + revision = "3acc91fb8811db2c5409a855ae1f8e441fe98e2d" + version = "v0.11.0" [[projects]] digest = "1:f4fcc1a4dbe079b200556ca26c1ff1dacf23712125b9c265d8f02c0dbc318f39" @@ -499,14 +507,6 @@ revision = "d419fffe18531317c28c29a292ad7d253f6cafdf" version = "v0.24.0" -[[projects]] - digest = "1:bf6d9a827ea3cad964c2f863302e4f6823170d0b5ed16f72cf1184a7c615067e" - name = "github.com/tendermint/tmlibs" - packages = ["cli"] - pruneopts = "UT" - revision = "49596e0a1f48866603813df843c9409fc19805c6" - version = "v0.9.0" - [[projects]] digest = "1:7886f86064faff6f8d08a3eb0e8c773648ff5a2e27730831e2bfbf07467f6666" name = "github.com/zondax/ledger-goclient" @@ -536,7 +536,7 @@ "salsa20/salsa", ] pruneopts = "UT" - revision = "0709b304e793a5edb4a2c0145f281ecdc20838a4" + revision = "0e37d006457bf46f9e6692014ba72ef82c33022c" [[projects]] digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1" @@ -556,14 +556,14 @@ [[projects]] branch = "master" - digest = "1:e54041967955fe3ddc2f5d75060301f2c7ef1ee8fae9a76e9e238b8bff82eb12" + digest = "1:68023dc297a659d5eb2dafd62eda811456b338c5b3ec3c27da79e8a47d3f456a" name = "golang.org/x/sys" packages = [ "cpu", "unix", ] pruneopts = "UT" - revision = "8cf3aee429924738c56c34bb819c4ea8273fc434" + revision = "2f1df4e56cdeb503a08d8577e6f1a7eb12efab82" [[projects]] digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" @@ -590,11 +590,11 @@ [[projects]] branch = "master" - digest = "1:077c1c599507b3b3e9156d17d36e1e61928ee9b53a5b420f10f28ebd4a0b275c" + digest = "1:56b0bca90b7e5d1facf5fbdacba23e4e0ce069d25381b8e2f70ef1e7ebfb9c1a" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] pruneopts = "UT" - revision = "11092d34479b07829b72e10713b159248caf5dad" + revision = "0e822944c569bf5c9afd034adaa56208bd2906ac" [[projects]] digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74" @@ -648,6 +648,8 @@ "github.com/golang/protobuf/proto", "github.com/gorilla/mux", "github.com/mattn/go-isatty", + "github.com/mitchellh/go-homedir", + "github.com/pelletier/go-toml", "github.com/pkg/errors", "github.com/spf13/cobra", "github.com/spf13/pflag", @@ -675,6 +677,7 @@ "github.com/tendermint/tendermint/libs/db", "github.com/tendermint/tendermint/libs/log", "github.com/tendermint/tendermint/lite", + "github.com/tendermint/tendermint/lite/errors", "github.com/tendermint/tendermint/lite/proxy", "github.com/tendermint/tendermint/node", "github.com/tendermint/tendermint/p2p", @@ -686,10 +689,8 @@ "github.com/tendermint/tendermint/rpc/lib/server", "github.com/tendermint/tendermint/types", "github.com/tendermint/tendermint/version", - "github.com/tendermint/tmlibs/cli", "github.com/zondax/ledger-goclient", "golang.org/x/crypto/blowfish", - "golang.org/x/crypto/ripemd160", ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 6df6665ad..ca9cac0d8 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -53,7 +53,7 @@ [[override]] name = "github.com/tendermint/iavl" - version = "=v0.10.0" + version = "=v0.11.0" [[override]] name = "github.com/tendermint/tendermint" @@ -70,3 +70,7 @@ [prune] go-tests = true unused-packages = true + +[[constraint]] + name = "github.com/mitchellh/go-homedir" + version = "1.0.0" diff --git a/Makefile b/Makefile index 51a1a46e2..3e0420072 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ PACKAGES_NOSIMULATION=$(shell go list ./... | grep -v '/simulation') PACKAGES_SIMTEST=$(shell go list ./... | grep '/simulation') -COMMIT_HASH := $(shell git rev-parse --short HEAD) +VERSION := $(shell git describe --tags --long | sed 's/v\(.*\)/\1/') BUILD_TAGS = netgo ledger -BUILD_FLAGS = -tags "${BUILD_TAGS}" -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}" +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) @@ -142,10 +142,10 @@ test_examples: @go test -count 1 -p 1 `go list github.com/cosmos/cosmos-sdk/examples/democoin/cli_test` -tags=cli_test test_unit: - @go test $(PACKAGES_NOSIMULATION) + @VERSION=$(VERSION) go test $(PACKAGES_NOSIMULATION) test_race: - @go test -race $(PACKAGES_NOSIMULATION) + @VERSION=$(VERSION) go test -race $(PACKAGES_NOSIMULATION) test_sim_modules: @echo "Running individual module simulations..." @@ -157,11 +157,11 @@ 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=150 -v -timeout 24h + @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=400 -SimulationBlockSize=200 -SimulationCommit=true -v -timeout 24h -test_sim_gaia_slow: - @echo "Running full Gaia simulation. This may take awhile!" - @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 -SimulationVerbose=true -v -timeout 24h +test_sim_gaia_full: + @echo "Running full multi-seed Gaia simulation. This may take awhile!" + @sh scripts/multisim.sh SIM_NUM_BLOCKS ?= 210 SIM_BLOCK_SIZE ?= 200 @@ -175,7 +175,7 @@ test_sim_gaia_profile: @go test -benchmem -run=^$$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$$ -SimulationEnabled=true -SimulationNumBlocks=$(SIM_NUM_BLOCKS) -SimulationBlockSize=$(SIM_BLOCK_SIZE) -SimulationCommit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out test_cover: - @bash tests/test_cover.sh + @export VERSION=$(VERSION); bash tests/test_cover.sh test_lint: gometalinter.v2 --config=tools/gometalinter.json ./... diff --git a/PENDING.md b/PENDING.md index 30077cd27..b60b7e2c9 100644 --- a/PENDING.md +++ b/PENDING.md @@ -4,6 +4,7 @@ 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 @@ -17,6 +18,8 @@ BREAKING CHANGES 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. * Gaia * Make the transient store key use a distinct store key. [#2013](https://github.com/cosmos/cosmos-sdk/pull/2013) @@ -33,6 +36,9 @@ BREAKING CHANGES renamed for accounts and validator operators: * `cosmosaccaddr` / `cosmosaccpub` => `cosmos` / `cosmospub` * `cosmosvaladdr` / `cosmosvalpub` => `cosmosvaloper` / `cosmosvaloperpub` + * [x/stake] [#1013] TendermintUpdates now uses transient store + * [x/gov] [#2195] Governance uses BFT Time + * [x/gov] \#2256 Removed slashing for governance non-voting validators * SDK * [core] \#2219 Update to Tendermint 0.24.0 @@ -42,14 +48,24 @@ BREAKING CHANGES * [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] [\#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 * Tendermint - FEATURES * Gaia REST API (`gaiacli advanced rest-server`) @@ -64,21 +80,32 @@ FEATURES * [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) Setting the --gas flag value to 0 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=0. + * [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`. * 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. * 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/stake] [\#1672](https://github.com/cosmos/cosmos-sdk/issues/1672) Implement + basis for the validator commission model. * Tendermint @@ -99,14 +126,27 @@ IMPROVEMENTS * [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) - * [genesis] \#2229 Ensure that there are no duplicate accounts in the genesis state. + * [genesis] \#2229 Ensure that there are no duplicate accounts or validators in the genesis state. + * 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`. + * 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 Update IAVL dependency to v0.10.0 + * [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 + * [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`. * Tendermint @@ -119,6 +159,8 @@ BUG FIXES * [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) * 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) @@ -127,5 +169,6 @@ BUG FIXES 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. * Tendermint diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index b6059a12b..34c27c1c6 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -14,10 +14,10 @@ import ( 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/version" - "github.com/cosmos/cosmos-sdk/wire" ) // Key to store the header in the DB itself. @@ -68,6 +68,9 @@ type BaseApp struct { deliverState *state // for DeliverTx voteInfos []abci.VoteInfo // absent validators from begin block + // minimum fees for spam prevention + minimumFees sdk.Coins + // flag for sealing sealed bool } @@ -120,13 +123,20 @@ func (app *BaseApp) RegisterCodespace(codespace sdk.CodespaceType) sdk.Codespace return app.codespacer.RegisterNext(codespace) } -// Mount a store to the provided key in the BaseApp multistore +// Mount IAVL stores to the provided keys in the BaseApp multistore func (app *BaseApp) MountStoresIAVL(keys ...*sdk.KVStoreKey) { for _, key := range keys { app.MountStore(key, sdk.StoreTypeIAVL) } } +// Mount stores to the provided keys in the BaseApp multistore +func (app *BaseApp) MountStoresTransient(keys ...*sdk.TransientStoreKey) { + for _, key := range keys { + app.MountStore(key, sdk.StoreTypeTransient) + } +} + // Mount a store to the provided key in the BaseApp multistore, using a specified DB func (app *BaseApp) MountStoreWithDB(key sdk.StoreKey, typ sdk.StoreType, db dbm.DB) { app.cms.MountStoreWithDB(key, typ, db) @@ -181,10 +191,13 @@ func (app *BaseApp) initFromStore(mainKey sdk.StoreKey) error { return nil } +// SetMinimumFees sets the minimum fees. +func (app *BaseApp) SetMinimumFees(fees sdk.Coins) { app.minimumFees = fees } + // NewContext returns a new Context with the correct store, the given header, and nil txBytes. func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context { if isCheckTx { - return sdk.NewContext(app.checkState.ms, header, true, app.Logger) + return sdk.NewContext(app.checkState.ms, header, true, app.Logger).WithMinimumFees(app.minimumFees) } return sdk.NewContext(app.deliverState.ms, header, false, app.Logger) } @@ -202,7 +215,7 @@ func (app *BaseApp) setCheckState(header abci.Header) { ms := app.cms.CacheMultiStore() app.checkState = &state{ ms: ms, - ctx: sdk.NewContext(ms, header, true, app.Logger), + ctx: sdk.NewContext(ms, header, true, app.Logger).WithMinimumFees(app.minimumFees), } } @@ -324,7 +337,7 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abc } // Encode with json - value := wire.Cdc.MustMarshalBinary(result) + value := codec.Cdc.MustMarshalBinary(result) return abci.ResponseQuery{ Code: uint32(sdk.ABCICodeOK), Value: value, @@ -379,7 +392,8 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res sdk.ErrUnknownRequest(fmt.Sprintf("no custom querier found for route %s", path[1])).QueryResult() } - ctx := sdk.NewContext(app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger) + 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) @@ -525,6 +539,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()))) // 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 b87162786..0137a3f7d 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -14,8 +14,8 @@ import ( dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" ) var ( @@ -34,14 +34,14 @@ func defaultLogger() log.Logger { func newBaseApp(name string, options ...func(*BaseApp)) *BaseApp { logger := defaultLogger() db := dbm.NewMemDB() - codec := wire.NewCodec() + codec := codec.New() registerTestCodec(codec) return NewBaseApp(name, logger, db, testTxDecoder(codec), options...) } -func registerTestCodec(cdc *wire.Codec) { +func registerTestCodec(cdc *codec.Codec) { // register Tx, Msg - sdk.RegisterWire(cdc) + sdk.RegisterCodec(cdc) // register test types cdc.RegisterConcrete(&txTest{}, "cosmos-sdk/baseapp/txTest", nil) @@ -302,6 +302,7 @@ type msgCounter struct { // Implements Msg func (msg msgCounter) Type() string { return typeMsgCounter } +func (msg msgCounter) Name() string { return "counter1" } func (msg msgCounter) GetSignBytes() []byte { return nil } func (msg msgCounter) GetSigners() []sdk.AccAddress { return nil } func (msg msgCounter) ValidateBasic() sdk.Error { @@ -340,6 +341,7 @@ type msgCounter2 struct { // Implements Msg func (msg msgCounter2) Type() string { return typeMsgCounter2 } +func (msg msgCounter2) Name() string { return "counter2" } func (msg msgCounter2) GetSignBytes() []byte { return nil } func (msg msgCounter2) GetSigners() []sdk.AccAddress { return nil } func (msg msgCounter2) ValidateBasic() sdk.Error { @@ -350,7 +352,7 @@ func (msg msgCounter2) ValidateBasic() sdk.Error { } // amino decode -func testTxDecoder(cdc *wire.Codec) sdk.TxDecoder { +func testTxDecoder(cdc *codec.Codec) sdk.TxDecoder { return func(txBytes []byte) (sdk.Tx, sdk.Error) { var tx txTest if len(txBytes) == 0 { @@ -448,7 +450,7 @@ func TestCheckTx(t *testing.T) { app.InitChain(abci.RequestInitChain{}) // Create same codec used in txDecoder - codec := wire.NewCodec() + codec := codec.New() registerTestCodec(codec) for i := int64(0); i < nTxs; i++ { @@ -489,7 +491,7 @@ func TestDeliverTx(t *testing.T) { app := setupBaseApp(t, anteOpt, routerOpt) // Create same codec used in txDecoder - codec := wire.NewCodec() + codec := codec.New() registerTestCodec(codec) nBlocks := 3 @@ -532,7 +534,7 @@ func TestMultiMsgDeliverTx(t *testing.T) { app := setupBaseApp(t, anteOpt, routerOpt) // Create same codec used in txDecoder - codec := wire.NewCodec() + codec := codec.New() registerTestCodec(codec) // run a multi-msg tx @@ -613,8 +615,8 @@ func TestSimulateTx(t *testing.T) { app.InitChain(abci.RequestInitChain{}) // Create same codec used in txDecoder - codec := wire.NewCodec() - registerTestCodec(codec) + cdc := codec.New() + registerTestCodec(cdc) nBlocks := 3 for blockN := 0; blockN < nBlocks; blockN++ { @@ -634,7 +636,7 @@ func TestSimulateTx(t *testing.T) { require.Equal(t, gasConsumed, result.GasUsed) // simulate by calling Query with encoded tx - txBytes, err := codec.MarshalBinary(tx) + txBytes, err := cdc.MarshalBinary(tx) require.Nil(t, err) query := abci.RequestQuery{ Path: "/app/simulate", @@ -644,7 +646,7 @@ func TestSimulateTx(t *testing.T) { require.True(t, queryResult.IsOK(), queryResult.Log) var res sdk.Result - wire.Cdc.MustUnmarshalBinary(queryResult.Value, &res) + codec.Cdc.MustUnmarshalBinary(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) @@ -721,7 +723,7 @@ func TestRunInvalidTransaction(t *testing.T) { tx.Msgs = append(tx.Msgs, msgNoDecode{}) // new codec so we can encode the tx, but we shouldn't be able to decode - newCdc := wire.NewCodec() + newCdc := codec.New() registerTestCodec(newCdc) newCdc.RegisterConcrete(&msgNoDecode{}, "cosmos-sdk/baseapp/msgNoDecode", nil) @@ -819,92 +821,3 @@ func TestTxGasLimits(t *testing.T) { } } } - -//------------------------------------------------------------------------------------------- -// Queries - -// Test that we can only query from the latest committed state. -func TestQuery(t *testing.T) { - key, value := []byte("hello"), []byte("goodbye") - anteOpt := func(bapp *BaseApp) { - bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) { - store := ctx.KVStore(capKey1) - store.Set(key, value) - return - }) - } - - routerOpt := func(bapp *BaseApp) { - bapp.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - store := ctx.KVStore(capKey1) - store.Set(key, value) - return sdk.Result{} - }) - } - - app := setupBaseApp(t, anteOpt, routerOpt) - - app.InitChain(abci.RequestInitChain{}) - - // NOTE: "/store/key1" tells us KVStore - // and the final "/key" says to use the data as the - // key in the given KVStore ... - query := abci.RequestQuery{ - Path: "/store/key1/key", - Data: key, - } - tx := newTxCounter(0, 0) - - // query is empty before we do anything - res := app.Query(query) - require.Equal(t, 0, len(res.Value)) - - // query is still empty after a CheckTx - resTx := app.Check(tx) - require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx)) - res = app.Query(query) - require.Equal(t, 0, len(res.Value)) - - // query is still empty after a DeliverTx before we commit - app.BeginBlock(abci.RequestBeginBlock{}) - resTx = app.Deliver(tx) - require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx)) - res = app.Query(query) - require.Equal(t, 0, len(res.Value)) - - // query returns correct value after Commit - app.Commit() - res = app.Query(query) - require.Equal(t, value, res.Value) -} - -// Test p2p filter queries -func TestP2PQuery(t *testing.T) { - addrPeerFilterOpt := func(bapp *BaseApp) { - bapp.SetAddrPeerFilter(func(addrport string) abci.ResponseQuery { - require.Equal(t, "1.1.1.1:8000", addrport) - return abci.ResponseQuery{Code: uint32(3)} - }) - } - - pubkeyPeerFilterOpt := func(bapp *BaseApp) { - bapp.SetPubKeyPeerFilter(func(pubkey string) abci.ResponseQuery { - require.Equal(t, "testpubkey", pubkey) - return abci.ResponseQuery{Code: uint32(4)} - }) - } - - app := setupBaseApp(t, addrPeerFilterOpt, pubkeyPeerFilterOpt) - - addrQuery := abci.RequestQuery{ - Path: "/p2p/filter/addr/1.1.1.1:8000", - } - res := app.Query(addrQuery) - require.Equal(t, uint32(3), res.Code) - - pubkeyQuery := abci.RequestQuery{ - Path: "/p2p/filter/pubkey/testpubkey", - } - res = app.Query(pubkeyQuery) - require.Equal(t, uint32(4), res.Code) -} diff --git a/baseapp/options.go b/baseapp/options.go index 0a404217a..a6460248d 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -1,9 +1,12 @@ +// nolint: golint package baseapp import ( "fmt" + "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" + dbm "github.com/tendermint/tendermint/libs/db" ) // File for storing in-package BaseApp optional functions, @@ -20,9 +23,100 @@ func SetPruning(pruning string) func(*BaseApp) { case "syncable": pruningEnum = sdk.PruneSyncable default: - panic(fmt.Sprintf("Invalid pruning strategy: %s", pruning)) + panic(fmt.Sprintf("invalid pruning strategy: %s", pruning)) } return func(bap *BaseApp) { bap.cms.SetPruning(pruningEnum) } } + +// SetMinimumFees returns an option that sets the minimum fees on the app. +func SetMinimumFees(minFees string) func(*BaseApp) { + fees, err := sdk.ParseCoins(minFees) + if err != nil { + panic(fmt.Sprintf("invalid minimum fees: %v", err)) + } + return func(bap *BaseApp) { bap.SetMinimumFees(fees) } +} + +func (app *BaseApp) SetName(name string) { + if app.sealed { + panic("SetName() on sealed BaseApp") + } + app.name = name +} + +func (app *BaseApp) SetDB(db dbm.DB) { + if app.sealed { + panic("SetDB() on sealed BaseApp") + } + app.db = db +} + +func (app *BaseApp) SetCMS(cms store.CommitMultiStore) { + if app.sealed { + panic("SetEndBlocker() on sealed BaseApp") + } + app.cms = cms +} + +func (app *BaseApp) SetInitChainer(initChainer sdk.InitChainer) { + if app.sealed { + panic("SetInitChainer() on sealed BaseApp") + } + app.initChainer = initChainer +} + +func (app *BaseApp) SetBeginBlocker(beginBlocker sdk.BeginBlocker) { + if app.sealed { + panic("SetBeginBlocker() on sealed BaseApp") + } + app.beginBlocker = beginBlocker +} + +func (app *BaseApp) SetEndBlocker(endBlocker sdk.EndBlocker) { + if app.sealed { + panic("SetEndBlocker() on sealed BaseApp") + } + app.endBlocker = endBlocker +} + +func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) { + if app.sealed { + panic("SetAnteHandler() on sealed BaseApp") + } + app.anteHandler = ah +} + +func (app *BaseApp) SetAddrPeerFilter(pf sdk.PeerFilter) { + if app.sealed { + panic("SetAddrPeerFilter() on sealed BaseApp") + } + app.addrPeerFilter = pf +} + +func (app *BaseApp) SetPubKeyPeerFilter(pf sdk.PeerFilter) { + if app.sealed { + panic("SetPubKeyPeerFilter() on sealed BaseApp") + } + app.pubkeyPeerFilter = pf +} + +func (app *BaseApp) Router() Router { + if app.sealed { + panic("Router() on sealed BaseApp") + } + return app.router +} + +func (app *BaseApp) QueryRouter() QueryRouter { + return app.queryRouter +} + +func (app *BaseApp) Seal() { app.sealed = true } +func (app *BaseApp) IsSealed() bool { return app.sealed } +func (app *BaseApp) enforceSeal() { + if !app.sealed { + panic("enforceSeal() on BaseApp but not sealed") + } +} diff --git a/baseapp/query_test.go b/baseapp/query_test.go new file mode 100644 index 000000000..579874e21 --- /dev/null +++ b/baseapp/query_test.go @@ -0,0 +1,96 @@ +package baseapp + +import ( + "fmt" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +// Test that we can only query from the latest committed state. +func TestQuery(t *testing.T) { + key, value := []byte("hello"), []byte("goodbye") + anteOpt := func(bapp *BaseApp) { + bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) { + store := ctx.KVStore(capKey1) + store.Set(key, value) + return + }) + } + + routerOpt := func(bapp *BaseApp) { + bapp.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + store := ctx.KVStore(capKey1) + store.Set(key, value) + return sdk.Result{} + }) + } + + app := setupBaseApp(t, anteOpt, routerOpt) + + app.InitChain(abci.RequestInitChain{}) + + // NOTE: "/store/key1" tells us KVStore + // and the final "/key" says to use the data as the + // key in the given KVStore ... + query := abci.RequestQuery{ + Path: "/store/key1/key", + Data: key, + } + tx := newTxCounter(0, 0) + + // query is empty before we do anything + res := app.Query(query) + require.Equal(t, 0, len(res.Value)) + + // query is still empty after a CheckTx + resTx := app.Check(tx) + require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx)) + res = app.Query(query) + require.Equal(t, 0, len(res.Value)) + + // query is still empty after a DeliverTx before we commit + app.BeginBlock(abci.RequestBeginBlock{}) + resTx = app.Deliver(tx) + require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx)) + res = app.Query(query) + require.Equal(t, 0, len(res.Value)) + + // query returns correct value after Commit + app.Commit() + res = app.Query(query) + require.Equal(t, value, res.Value) +} + +// Test p2p filter queries +func TestP2PQuery(t *testing.T) { + addrPeerFilterOpt := func(bapp *BaseApp) { + bapp.SetAddrPeerFilter(func(addrport string) abci.ResponseQuery { + require.Equal(t, "1.1.1.1:8000", addrport) + return abci.ResponseQuery{Code: uint32(3)} + }) + } + + pubkeyPeerFilterOpt := func(bapp *BaseApp) { + bapp.SetPubKeyPeerFilter(func(pubkey string) abci.ResponseQuery { + require.Equal(t, "testpubkey", pubkey) + return abci.ResponseQuery{Code: uint32(4)} + }) + } + + app := setupBaseApp(t, addrPeerFilterOpt, pubkeyPeerFilterOpt) + + addrQuery := abci.RequestQuery{ + Path: "/p2p/filter/addr/1.1.1.1:8000", + } + res := app.Query(addrQuery) + require.Equal(t, uint32(3), res.Code) + + pubkeyQuery := abci.RequestQuery{ + Path: "/p2p/filter/pubkey/testpubkey", + } + res = app.Query(pubkeyQuery) + require.Equal(t, uint32(4), res.Code) +} diff --git a/baseapp/setters.go b/baseapp/setters.go deleted file mode 100644 index 28782c96d..000000000 --- a/baseapp/setters.go +++ /dev/null @@ -1,80 +0,0 @@ -package baseapp - -import ( - dbm "github.com/tendermint/tendermint/libs/db" - - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// nolint - Setter functions -func (app *BaseApp) SetName(name string) { - if app.sealed { - panic("SetName() on sealed BaseApp") - } - app.name = name -} -func (app *BaseApp) SetDB(db dbm.DB) { - if app.sealed { - panic("SetDB() on sealed BaseApp") - } - app.db = db -} -func (app *BaseApp) SetCMS(cms store.CommitMultiStore) { - if app.sealed { - panic("SetEndBlocker() on sealed BaseApp") - } - app.cms = cms -} -func (app *BaseApp) SetInitChainer(initChainer sdk.InitChainer) { - if app.sealed { - panic("SetInitChainer() on sealed BaseApp") - } - app.initChainer = initChainer -} -func (app *BaseApp) SetBeginBlocker(beginBlocker sdk.BeginBlocker) { - if app.sealed { - panic("SetBeginBlocker() on sealed BaseApp") - } - app.beginBlocker = beginBlocker -} -func (app *BaseApp) SetEndBlocker(endBlocker sdk.EndBlocker) { - if app.sealed { - panic("SetEndBlocker() on sealed BaseApp") - } - app.endBlocker = endBlocker -} -func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) { - if app.sealed { - panic("SetAnteHandler() on sealed BaseApp") - } - app.anteHandler = ah -} -func (app *BaseApp) SetAddrPeerFilter(pf sdk.PeerFilter) { - if app.sealed { - panic("SetAddrPeerFilter() on sealed BaseApp") - } - app.addrPeerFilter = pf -} -func (app *BaseApp) SetPubKeyPeerFilter(pf sdk.PeerFilter) { - if app.sealed { - panic("SetPubKeyPeerFilter() on sealed BaseApp") - } - app.pubkeyPeerFilter = pf -} -func (app *BaseApp) Router() Router { - if app.sealed { - panic("Router() on sealed BaseApp") - } - return app.router -} -func (app *BaseApp) QueryRouter() QueryRouter { - return app.queryRouter -} -func (app *BaseApp) Seal() { app.sealed = true } -func (app *BaseApp) IsSealed() bool { return app.sealed } -func (app *BaseApp) enforceSeal() { - if !app.sealed { - panic("enforceSeal() on BaseApp but not sealed") - } -} diff --git a/client/config.go b/client/config.go new file mode 100644 index 000000000..fcb252375 --- /dev/null +++ b/client/config.go @@ -0,0 +1,131 @@ +package client + +import ( + "github.com/spf13/cobra" + "github.com/mitchellh/go-homedir" + "bufio" + "path" + "os" + "io/ioutil" + "github.com/pelletier/go-toml" + "fmt" + "github.com/cosmos/cosmos-sdk/types" +) + +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"` +} + +// ConfigCmd returns a CLI command to interactively create a +// Gaia CLI config file. +func ConfigCmd() *cobra.Command { + cfg := &cobra.Command{ + Use: "config", + Short: "Interactively creates a Gaia CLI config file", + RunE: runConfigCmd, + } + + return cfg +} + +func runConfigCmd(cmd *cobra.Command, args [] string) error { + home, err := homedir.Dir() + if err != nil { + return err + } + + 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() + if err != nil { + fmt.Println("Couldn't populate ChainID, so using an empty one.") + } + + cfg := &cliConfig{ + Home: gaiaCLIHome, + ChainID: chainID, + TrustNode: trustNode, + Encoding: encoding, + Output: output, + Node: node, + Trace: false, + } + + return createGaiaCLIConfig(cfg) +} + +func handleGaiaCLIHome(dir string, stdin *bufio.Reader) (string, error) { + dirName := ".gaiacli" + home, err := GetString(fmt.Sprintf("Where is your gaiacli home directory? (Default: ~/%s)", dirName), stdin) + if err != nil { + return "", err + } + + if home == "" { + home = path.Join(dir, dirName) + } + + return home, nil +} + +func handleNode(stdin *bufio.Reader) (string, error) { + defaultNode := "tcp://localhost:26657" + node, err := GetString(fmt.Sprintf("Where is your validator node running? (Default: %s)", defaultNode), stdin) + if err != nil { + return "", err + } + + if node == "" { + node = defaultNode + } + + return node, nil +} + +func handleTrustNode(stdin *bufio.Reader) (bool, error) { + return GetConfirmation("Do you trust this node?", stdin) +} + +func createGaiaCLIConfig(cfg *cliConfig) error { + cfgPath := path.Join(cfg.Home, "config") + err := os.MkdirAll(cfgPath, os.ModePerm) + if err != nil { + return err + } + + data, err := toml.Marshal(*cfg) + if err != nil { + return err + } + + cfgFile := path.Join(cfgPath, "config.toml") + if info, err := os.Stat(cfgFile); err == nil && !info.IsDir() { + err = os.Rename(cfgFile, path.Join(cfgPath, "config.toml-old")) + if err != nil { + return err + } + } + + return ioutil.WriteFile(cfgFile, data, os.ModePerm) +} diff --git a/client/context/broadcast.go b/client/context/broadcast.go new file mode 100644 index 000000000..4e295777c --- /dev/null +++ b/client/context/broadcast.go @@ -0,0 +1,158 @@ +package context + +import ( + "fmt" + "io" + + "github.com/pkg/errors" + + abci "github.com/tendermint/tendermint/abci/types" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +// TODO: This should get deleted eventually, and perhaps +// ctypes.ResultBroadcastTx be stripped of unused fields, and +// ctypes.ResultBroadcastTxCommit returned for tendermint RPC BroadcastTxSync. +// +// The motivation is that we want a unified type to return, and the better +// option is the one that can hold CheckTx/DeliverTx responses optionally. +func resultBroadcastTxToCommit(res *ctypes.ResultBroadcastTx) *ctypes.ResultBroadcastTxCommit { + return &ctypes.ResultBroadcastTxCommit{ + Hash: res.Hash, + // NOTE: other fields are unused for async. + } +} + +// BroadcastTx broadcasts a transactions either synchronously or asynchronously +// based on the context parameters. The result of the broadcast is parsed into +// an intermediate structure which is logged if the context has a logger +// defined. +func (ctx CLIContext) BroadcastTx(txBytes []byte) (*ctypes.ResultBroadcastTxCommit, error) { + if ctx.Async { + res, err := ctx.broadcastTxAsync(txBytes) + if err != nil { + return nil, err + } + + resCommit := resultBroadcastTxToCommit(res) + return resCommit, err + } + + return ctx.broadcastTxCommit(txBytes) +} + +// BroadcastTxAndAwaitCommit broadcasts transaction bytes to a Tendermint node +// and waits for a commit. +func (ctx CLIContext) BroadcastTxAndAwaitCommit(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) { + node, err := ctx.GetNode() + if err != nil { + return nil, err + } + + res, err := node.BroadcastTxCommit(tx) + if err != nil { + return res, err + } + + if !res.CheckTx.IsOK() { + return res, errors.Errorf("checkTx failed: (%d) %s", + res.CheckTx.Code, + res.CheckTx.Log) + } + + if !res.DeliverTx.IsOK() { + return res, errors.Errorf("deliverTx failed: (%d) %s", + res.DeliverTx.Code, + res.DeliverTx.Log) + } + + return res, err +} + +// BroadcastTxAsync broadcasts transaction bytes to a Tendermint node +// asynchronously. +func (ctx CLIContext) BroadcastTxAsync(tx []byte) (*ctypes.ResultBroadcastTx, error) { + node, err := ctx.GetNode() + if err != nil { + return nil, err + } + + res, err := node.BroadcastTxAsync(tx) + if err != nil { + return res, err + } + + return res, err +} + +func (ctx CLIContext) broadcastTxAsync(txBytes []byte) (*ctypes.ResultBroadcastTx, error) { + res, err := ctx.BroadcastTxAsync(txBytes) + if err != nil { + return res, err + } + + if ctx.Logger != nil { + if ctx.JSON { + type toJSON struct { + TxHash string + } + + resJSON := toJSON{res.Hash.String()} + bz, err := ctx.Codec.MarshalJSON(resJSON) + if err != nil { + return res, err + } + + ctx.Logger.Write(bz) + io.WriteString(ctx.Logger, "\n") + } else { + io.WriteString(ctx.Logger, fmt.Sprintf("async tx sent (tx hash: %s)\n", res.Hash)) + } + } + + return res, nil +} + +func (ctx CLIContext) broadcastTxCommit(txBytes []byte) (*ctypes.ResultBroadcastTxCommit, error) { + res, err := ctx.BroadcastTxAndAwaitCommit(txBytes) + if err != nil { + return res, err + } + + if ctx.JSON { + // Since JSON is intended for automated scripts, always include response in + // JSON mode. + type toJSON struct { + Height int64 + TxHash string + Response abci.ResponseDeliverTx + } + + if ctx.Logger != nil { + resJSON := toJSON{res.Height, res.Hash.String(), res.DeliverTx} + bz, err := ctx.Codec.MarshalJSON(resJSON) + if err != nil { + return res, err + } + + ctx.Logger.Write(bz) + io.WriteString(ctx.Logger, "\n") + } + + return res, nil + } + + if ctx.Logger != nil { + resStr := fmt.Sprintf("Committed at block %d (tx hash: %s)\n", res.Height, res.Hash.String()) + + if ctx.PrintResponse { + resStr = fmt.Sprintf("Committed at block %d (tx hash: %s, response: %+v)\n", + res.Height, res.Hash.String(), res.DeliverTx, + ) + } + + io.WriteString(ctx.Logger, resStr) + } + + return res, nil +} diff --git a/client/context/context.go b/client/context/context.go index dfd18a581..589137407 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -6,16 +6,20 @@ import ( "io" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/spf13/viper" + "github.com/cosmos/cosmos-sdk/client/keys" + cskeys "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/types" "github.com/tendermint/tendermint/libs/cli" "github.com/tendermint/tendermint/libs/log" tmlite "github.com/tendermint/tendermint/lite" tmliteProxy "github.com/tendermint/tendermint/lite/proxy" rpcclient "github.com/tendermint/tendermint/rpc/client" + "os" ) const ctxAccStoreName = "acc" @@ -23,24 +27,24 @@ const ctxAccStoreName = "acc" // CLIContext implements a typical CLI context created in SDK modules for // transaction handling and queries. type CLIContext struct { - Codec *wire.Codec - AccDecoder auth.AccountDecoder - Client rpcclient.Client - Logger io.Writer - Height int64 - Gas int64 - GasAdjustment float64 - NodeURI string - FromAddressName string - AccountStore string - TrustNode bool - UseLedger bool - Async bool - JSON bool - PrintResponse bool - Verifier tmlite.Verifier - DryRun bool - GenerateOnly bool + Codec *codec.Codec + AccDecoder auth.AccountDecoder + Client rpcclient.Client + Logger io.Writer + Height int64 + NodeURI string + From string + AccountStore string + TrustNode bool + UseLedger bool + Async bool + JSON bool + PrintResponse bool + Verifier tmlite.Verifier + DryRun bool + GenerateOnly bool + fromAddress types.AccAddress + fromName string } // NewCLIContext returns a new initialized CLIContext with parameters from the @@ -53,59 +57,103 @@ func NewCLIContext() CLIContext { rpc = rpcclient.NewHTTP(nodeURI, "/websocket") } + from := viper.GetString(client.FlagFrom) + fromAddress, fromName := fromFields(from) + return CLIContext{ - Client: rpc, - NodeURI: nodeURI, - AccountStore: ctxAccStoreName, - FromAddressName: viper.GetString(client.FlagFrom), - Height: viper.GetInt64(client.FlagHeight), - Gas: viper.GetInt64(client.FlagGas), - GasAdjustment: viper.GetFloat64(client.FlagGasAdjustment), - TrustNode: viper.GetBool(client.FlagTrustNode), - UseLedger: viper.GetBool(client.FlagUseLedger), - Async: viper.GetBool(client.FlagAsync), - JSON: viper.GetBool(client.FlagJson), - PrintResponse: viper.GetBool(client.FlagPrintResponse), - Verifier: createVerifier(), - DryRun: viper.GetBool(client.FlagDryRun), - GenerateOnly: viper.GetBool(client.FlagGenerateOnly), + Client: rpc, + NodeURI: nodeURI, + AccountStore: ctxAccStoreName, + From: viper.GetString(client.FlagFrom), + Height: viper.GetInt64(client.FlagHeight), + TrustNode: viper.GetBool(client.FlagTrustNode), + UseLedger: viper.GetBool(client.FlagUseLedger), + Async: viper.GetBool(client.FlagAsync), + JSON: viper.GetBool(client.FlagJson), + PrintResponse: viper.GetBool(client.FlagPrintResponse), + Verifier: createVerifier(), + DryRun: viper.GetBool(client.FlagDryRun), + GenerateOnly: viper.GetBool(client.FlagGenerateOnly), + fromAddress: fromAddress, + fromName: fromName, } } func createVerifier() tmlite.Verifier { + trustNodeDefined := viper.IsSet(client.FlagTrustNode) + if !trustNodeDefined { + return nil + } + trustNode := viper.GetBool(client.FlagTrustNode) if trustNode { return nil } + chainID := viper.GetString(client.FlagChainID) home := viper.GetString(cli.HomeFlag) nodeURI := viper.GetString(client.FlagNode) var errMsg bytes.Buffer if chainID == "" { - errMsg.WriteString("chain-id ") + errMsg.WriteString("--chain-id ") } if home == "" { - errMsg.WriteString("home ") + errMsg.WriteString("--home ") } if nodeURI == "" { - errMsg.WriteString("node ") + errMsg.WriteString("--node ") } - // errMsg is not empty if errMsg.Len() != 0 { - panic(fmt.Errorf("can't create certifier for distrust mode, empty values from these options: %s", errMsg.String())) + fmt.Printf("Must specify these options: %s when --trust-node is false\n", errMsg.String()) + os.Exit(1) } node := rpcclient.NewHTTP(nodeURI, "/websocket") // TODO Utilize ctx.Logger correctly verifier, err := tmliteProxy.NewVerifier(chainID, home, node, log.NewNopLogger()) + if err != nil { - panic(err) + fmt.Printf("Create verifier failed: %s\n", err.Error()) + fmt.Printf("Please check network connection and verify the address of the node to connect to\n") + os.Exit(1) } + return verifier } +func fromFields(from string) (fromAddr types.AccAddress, fromName string) { + if from == "" { + return nil, "" + } + + keybase, err := keys.GetKeyBase() + if err != nil { + fmt.Println("no keybase found") + os.Exit(1) + } + + var info cskeys.Info + if addr, err := types.AccAddressFromBech32(from); err == nil { + info, err = keybase.GetByAddress(addr) + if err != nil { + fmt.Printf("could not find key %s\n", from) + os.Exit(1) + } + } else { + info, err = keybase.Get(from) + if err != nil { + fmt.Printf("could not find key %s\n", from) + os.Exit(1) + } + } + + fromAddr = info.GetAddress() + fromName = info.GetName() + return +} + // WithCodec returns a copy of the context with an updated codec. -func (ctx CLIContext) WithCodec(cdc *wire.Codec) CLIContext { +func (ctx CLIContext) WithCodec(cdc *codec.Codec) CLIContext { ctx.Codec = cdc return ctx } @@ -129,10 +177,9 @@ func (ctx CLIContext) WithAccountStore(accountStore string) CLIContext { return ctx } -// WithFromAddressName returns a copy of the context with an updated from -// address. -func (ctx CLIContext) WithFromAddressName(addrName string) CLIContext { - ctx.FromAddressName = addrName +// WithFrom returns a copy of the context with an updated from address or name. +func (ctx CLIContext) WithFrom(from string) CLIContext { + ctx.From = from return ctx } @@ -167,9 +214,3 @@ func (ctx CLIContext) WithVerifier(verifier tmlite.Verifier) CLIContext { ctx.Verifier = verifier return ctx } - -// WithGasAdjustment returns a copy of the context with an updated GasAdjustment flag. -func (ctx CLIContext) WithGasAdjustment(adjustment float64) CLIContext { - ctx.GasAdjustment = adjustment - return ctx -} diff --git a/client/context/errors.go b/client/context/errors.go index 9c611494a..de96aaa18 100644 --- a/client/context/errors.go +++ b/client/context/errors.go @@ -11,3 +11,11 @@ func ErrInvalidAccount(addr sdk.AccAddress) error { return errors.Errorf(`No account with address %s was found in the state. Are you sure there has been a transaction involving it?`, addr) } + +// ErrVerifyCommit returns a common error reflecting that the blockchain commit at a given +// height can't be verified. The reason is that the base checkpoint of the certifier is +// newer than the given height +func ErrVerifyCommit(height int64) error { + return errors.Errorf(`The height of base truststore in gaia-lite is higher than height %d. +Can't verify blockchain proof at this height. Please set --trust-node to true and try again`, height) +} diff --git a/client/context/query.go b/client/context/query.go index d108f80fa..4cd3c427f 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -2,9 +2,7 @@ package context import ( "fmt" - "io" - "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -12,13 +10,14 @@ import ( "strings" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store" - "github.com/cosmos/cosmos-sdk/wire" abci "github.com/tendermint/tendermint/abci/types" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/lite" + tmliteErr "github.com/tendermint/tendermint/lite/errors" tmliteProxy "github.com/tendermint/tendermint/lite/proxy" rpcclient "github.com/tendermint/tendermint/rpc/client" - ctypes "github.com/tendermint/tendermint/rpc/core/types" ) // GetNode returns an RPC client. If the context's client is not defined, an @@ -82,22 +81,13 @@ func (ctx CLIContext) GetAccount(address []byte) (auth.Account, error) { } // GetFromAddress returns the from address from the context's name. -func (ctx CLIContext) GetFromAddress() (from sdk.AccAddress, err error) { - if ctx.FromAddressName == "" { - return nil, errors.Errorf("must provide a from address name") - } +func (ctx CLIContext) GetFromAddress() (sdk.AccAddress, error) { + return ctx.fromAddress, nil +} - keybase, err := keys.GetKeyBase() - if err != nil { - return nil, err - } - - info, err := keybase.Get(ctx.FromAddressName) - if err != nil { - return nil, errors.Errorf("no key for: %s", ctx.FromAddressName) - } - - return sdk.AccAddress(info.GetPubKey().Address()), nil +// GetFromName returns the key name for the current context. +func (ctx CLIContext) GetFromName() (string, error) { + return ctx.fromName, nil } // GetAccountNumber returns the next account number for the given account @@ -122,49 +112,6 @@ func (ctx CLIContext) GetAccountSequence(address []byte) (int64, error) { return account.GetSequence(), nil } -// BroadcastTx broadcasts transaction bytes to a Tendermint node. -func (ctx CLIContext) BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) { - node, err := ctx.GetNode() - if err != nil { - return nil, err - } - - res, err := node.BroadcastTxCommit(tx) - if err != nil { - return res, err - } - - if !res.CheckTx.IsOK() { - return res, errors.Errorf("checkTx failed: (%d) %s", - res.CheckTx.Code, - res.CheckTx.Log) - } - - if !res.DeliverTx.IsOK() { - return res, errors.Errorf("deliverTx failed: (%d) %s", - res.DeliverTx.Code, - res.DeliverTx.Log) - } - - return res, err -} - -// BroadcastTxAsync broadcasts transaction bytes to a Tendermint node -// asynchronously. -func (ctx CLIContext) BroadcastTxAsync(tx []byte) (*ctypes.ResultBroadcastTx, error) { - node, err := ctx.GetNode() - if err != nil { - return nil, err - } - - res, err := node.BroadcastTxAsync(tx) - if err != nil { - return res, err - } - - return res, err -} - // EnsureAccountExists ensures that an account exists for a given context. An // error is returned if it does not. func (ctx CLIContext) EnsureAccountExists() error { @@ -201,92 +148,6 @@ func (ctx CLIContext) EnsureAccountExistsFromAddr(addr sdk.AccAddress) error { return nil } -// EnsureBroadcastTx broadcasts a transactions either synchronously or -// asynchronously based on the context parameters. The result of the broadcast -// is parsed into an intermediate structure which is logged if the context has -// a logger defined. -func (ctx CLIContext) EnsureBroadcastTx(txBytes []byte) error { - if ctx.Async { - return ctx.ensureBroadcastTxAsync(txBytes) - } - - return ctx.ensureBroadcastTx(txBytes) -} - -func (ctx CLIContext) ensureBroadcastTxAsync(txBytes []byte) error { - res, err := ctx.BroadcastTxAsync(txBytes) - if err != nil { - return err - } - - if ctx.JSON { - type toJSON struct { - TxHash string - } - - if ctx.Logger != nil { - resJSON := toJSON{res.Hash.String()} - bz, err := ctx.Codec.MarshalJSON(resJSON) - if err != nil { - return err - } - - ctx.Logger.Write(bz) - io.WriteString(ctx.Logger, "\n") - } - } else { - if ctx.Logger != nil { - io.WriteString(ctx.Logger, fmt.Sprintf("Async tx sent (tx hash: %s)\n", res.Hash)) - } - } - - return nil -} - -func (ctx CLIContext) ensureBroadcastTx(txBytes []byte) error { - res, err := ctx.BroadcastTx(txBytes) - if err != nil { - return err - } - - if ctx.JSON { - // since JSON is intended for automated scripts, always include - // response in JSON mode. - type toJSON struct { - Height int64 - TxHash string - Response abci.ResponseDeliverTx - } - - if ctx.Logger != nil { - resJSON := toJSON{res.Height, res.Hash.String(), res.DeliverTx} - bz, err := ctx.Codec.MarshalJSON(resJSON) - if err != nil { - return err - } - - ctx.Logger.Write(bz) - io.WriteString(ctx.Logger, "\n") - } - - return nil - } - - if ctx.Logger != nil { - resStr := fmt.Sprintf("Committed at block %d (tx hash: %s)\n", res.Height, res.Hash.String()) - - if ctx.PrintResponse { - resStr = fmt.Sprintf("Committed at block %d (tx hash: %s, response: %+v)\n", - res.Height, res.Hash.String(), res.DeliverTx, - ) - } - - io.WriteString(ctx.Logger, resStr) - } - - return nil -} - // query performs a query from a Tendermint node with the provided store name // and path. func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err error) { @@ -310,7 +171,7 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) } - // Data from trusted node or subspace query doesn't need verification + // data from trusted node or subspace query doesn't need verification if ctx.TrustNode || !isQueryStoreWithProof(path) { return resp.Value, nil } @@ -323,42 +184,52 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro return resp.Value, nil } -// verifyProof perform response proof verification -// nolint: unparam -func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error { +// Verify verifies the consensus proof at given height. +func (ctx CLIContext) Verify(height int64) (lite.Commit, error) { + check, err := tmliteProxy.GetCertifiedCommit(height, ctx.Client, ctx.Verifier) + switch { + case tmliteErr.IsCommitNotFoundErr(err): + return lite.Commit{}, ErrVerifyCommit(height) + case err != nil: + return lite.Commit{}, err + } + return check, nil +} + +// verifyProof perform response proof verification. +func (ctx CLIContext) verifyProof(_ string, resp abci.ResponseQuery) error { if ctx.Verifier == nil { - return fmt.Errorf("missing valid verifier to verify data from untrusted node") + return fmt.Errorf("missing valid certifier to verify data from distrusted node") } - node, err := ctx.GetNode() - if err != nil { - return err - } - - // AppHash for height H is in header H+1 - commit, err := tmliteProxy.GetCertifiedCommit(resp.Height+1, node, ctx.Verifier) + // the AppHash for height H is in header H+1 + commit, err := ctx.Verify(resp.Height + 1) if err != nil { return err } var multiStoreProof store.MultiStoreProof - cdc := wire.NewCodec() + cdc := codec.New() + err = cdc.UnmarshalBinary(resp.Proof, &multiStoreProof) if err != nil { return errors.Wrap(err, "failed to unmarshalBinary rangeProof") } - // Verify the substore commit hash against trusted appHash - substoreCommitHash, err := store.VerifyMultiStoreCommitInfo(multiStoreProof.StoreName, - multiStoreProof.StoreInfos, commit.Header.AppHash) + // 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") } + err = store.VerifyRangeProof(resp.Key, resp.Value, substoreCommitHash, &multiStoreProof.RangeProof) if err != nil { return errors.Wrap(err, "failed in the range proof verification") } + return nil } @@ -370,11 +241,12 @@ func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([ } // isQueryStoreWithProof expects a format like /// -// queryType can be app or store +// queryType can be app or store. func isQueryStoreWithProof(path string) bool { if !strings.HasPrefix(path, "/") { return false } + paths := strings.SplitN(path[1:], "/", 3) if len(paths) != 3 { return false @@ -383,5 +255,6 @@ func isQueryStoreWithProof(path string) bool { if store.RequireProof("/" + paths[2]) { return true } + return false } diff --git a/client/flags.go b/client/flags.go index 97fce42a5..ff9354937 100644 --- a/client/flags.go +++ b/client/flags.go @@ -1,6 +1,12 @@ package client -import "github.com/spf13/cobra" +import ( + "fmt" + "strconv" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) // nolint const ( @@ -9,6 +15,7 @@ const ( // occur between the tx simulation and the actual run. DefaultGasAdjustment = 1.0 DefaultGasLimit = 200000 + GasFlagSimulate = "simulate" FlagUseLedger = "ledger" FlagChainID = "chain-id" @@ -32,17 +39,23 @@ const ( // LineBreak can be included in a command list to provide a blank line // to help with readability -var LineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}} +var ( + LineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}} + GasFlagVar = GasSetting{Gas: DefaultGasLimit} +) // GetCommands adds common flags to query commands func GetCommands(cmds ...*cobra.Command) []*cobra.Command { for _, c := range cmds { - // TODO: make this default false when we support proofs - c.Flags().Bool(FlagTrustNode, true, "Don't verify proofs for responses") + c.Flags().Bool(FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device") c.Flags().String(FlagChainID, "", "Chain ID of tendermint node") c.Flags().String(FlagNode, "tcp://localhost:26657", ": to tendermint rpc interface for this chain") c.Flags().Int64(FlagHeight, 0, "block height to query, omit to get most recent provable block") + viper.BindPFlag(FlagTrustNode, c.Flags().Lookup(FlagTrustNode)) + viper.BindPFlag(FlagUseLedger, c.Flags().Lookup(FlagUseLedger)) + viper.BindPFlag(FlagChainID, c.Flags().Lookup(FlagChainID)) + viper.BindPFlag(FlagNode, c.Flags().Lookup(FlagNode)) } return cmds } @@ -50,7 +63,7 @@ func GetCommands(cmds ...*cobra.Command) []*cobra.Command { // PostCommands adds common flags for commands to post tx func PostCommands(cmds ...*cobra.Command) []*cobra.Command { for _, c := range cmds { - c.Flags().String(FlagFrom, "", "Name of private key with which to sign") + c.Flags().String(FlagFrom, "", "Name or address of private key with which to sign") c.Flags().Int64(FlagAccountNumber, 0, "AccountNumber number to sign the tx") c.Flags().Int64(FlagSequence, 0, "Sequence number to sign the tx") c.Flags().String(FlagMemo, "", "Memo to send along with transaction") @@ -58,14 +71,61 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { c.Flags().String(FlagChainID, "", "Chain ID of tendermint node") c.Flags().String(FlagNode, "tcp://localhost:26657", ": to tendermint rpc interface for this chain") c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device") - c.Flags().Int64(FlagGas, DefaultGasLimit, "gas limit to set per-transaction; set to 0 to calculate required gas automatically") c.Flags().Float64(FlagGasAdjustment, DefaultGasAdjustment, "adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored ") c.Flags().Bool(FlagAsync, false, "broadcast transactions asynchronously") c.Flags().Bool(FlagJson, false, "return output in json format") c.Flags().Bool(FlagPrintResponse, true, "return tx response (only works with async = false)") - c.Flags().Bool(FlagTrustNode, true, "Don't verify proofs for query responses") + c.Flags().Bool(FlagTrustNode, true, "Trust connected full node (don't verify proofs for responses)") c.Flags().Bool(FlagDryRun, false, "ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it") c.Flags().Bool(FlagGenerateOnly, false, "build an unsigned transaction and write it to STDOUT") + // --gas can accept integers and "simulate" + c.Flags().Var(&GasFlagVar, "gas", fmt.Sprintf( + "gas limit to set per-transaction; set to %q to calculate required gas automatically (default %d)", GasFlagSimulate, DefaultGasLimit)) + viper.BindPFlag(FlagTrustNode, c.Flags().Lookup(FlagTrustNode)) + viper.BindPFlag(FlagUseLedger, c.Flags().Lookup(FlagUseLedger)) + viper.BindPFlag(FlagChainID, c.Flags().Lookup(FlagChainID)) + viper.BindPFlag(FlagNode, c.Flags().Lookup(FlagNode)) } return cmds } + +// Gas flag parsing functions + +// GasSetting encapsulates the possible values passed through the --gas flag. +type GasSetting struct { + Simulate bool + Gas int64 +} + +// Type returns the flag's value type. +func (v *GasSetting) Type() string { return "string" } + +// Set parses and sets the value of the --gas flag. +func (v *GasSetting) Set(s string) (err error) { + v.Simulate, v.Gas, err = ReadGasFlag(s) + return +} + +func (v *GasSetting) String() string { + if v.Simulate { + return GasFlagSimulate + } + return strconv.FormatInt(v.Gas, 10) +} + +// ParseGasFlag parses the value of the --gas flag. +func ReadGasFlag(s string) (simulate bool, gas int64, err error) { + switch s { + case "": + gas = DefaultGasLimit + case GasFlagSimulate: + simulate = true + default: + gas, err = strconv.ParseInt(s, 10, 64) + if err != nil { + err = fmt.Errorf("gas must be either integer or %q", GasFlagSimulate) + return + } + } + return +} diff --git a/client/input.go b/client/input.go index b10f65ce6..a456f1b92 100644 --- a/client/input.go +++ b/client/input.go @@ -100,6 +100,19 @@ func GetConfirmation(prompt string, buf *bufio.Reader) (bool, error) { } } +// GetString simply returns the trimmed string output of a given reader. +func GetString(prompt string, buf *bufio.Reader) (string, error) { + if inputIsTty() && prompt != "" { + PrintPrefixed(prompt) + } + + out, err := readLineFromBuf(buf) + if err != nil { + return "", err + } + return strings.TrimSpace(out), nil +} + // inputIsTty returns true iff we have an interactive prompt, // where we can disable echo and request to repeat the password. // If false, we can optimize for piped input from another command @@ -117,3 +130,8 @@ func readLineFromBuf(buf *bufio.Reader) (string, error) { } return strings.TrimSpace(pass), nil } + +// PrintPrefixed prints a string with > prefixed for use in prompts. +func PrintPrefixed(msg string) { + fmt.Printf("> %s\n", msg) +} diff --git a/client/keys/add.go b/client/keys/add.go index c42172819..be2825a55 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -46,7 +46,6 @@ phrase, otherwise, a new key will be generated.`, return cmd } -// nolint: gocyclo // TODO remove the above when addressing #1446 func runAddCmd(cmd *cobra.Command, args []string) error { var kb keys.Keybase diff --git a/client/keys/wire.go b/client/keys/codec.go similarity index 70% rename from client/keys/wire.go rename to client/keys/codec.go index a163f995a..6bbb16850 100644 --- a/client/keys/wire.go +++ b/client/keys/codec.go @@ -1,14 +1,14 @@ package keys import ( - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" ) -var cdc *wire.Codec +var cdc *codec.Codec func init() { - cdc = wire.NewCodec() - wire.RegisterCrypto(cdc) + cdc = codec.New() + codec.RegisterCrypto(cdc) } // marshal keys diff --git a/client/keys/show.go b/client/keys/show.go index 9710cac11..82c6f9883 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -5,13 +5,11 @@ import ( "fmt" "net/http" - "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/gorilla/mux" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/tmlibs/cli" + "github.com/tendermint/tendermint/libs/cli" ) const ( @@ -19,7 +17,7 @@ const ( FlagAddress = "address" // FlagPublicKey represents the user's public key on the command line. FlagPublicKey = "pubkey" - // FlagBechPrefix defines a desired Bech32 prefix encoding for a key + // FlagBechPrefix defines a desired Bech32 prefix encoding for a key. FlagBechPrefix = "bech" ) @@ -29,39 +27,7 @@ func showKeysCmd() *cobra.Command { Short: "Show key info for the given name", Long: `Return public details of one local key.`, Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - name := args[0] - info, err := getKey(name) - if err != nil { - return err - } - - showAddress := viper.GetBool(FlagAddress) - showPublicKey := viper.GetBool(FlagPublicKey) - outputSet := cmd.Flag(cli.OutputFlag).Changed - - if showAddress && showPublicKey { - return errors.New("cannot use both --address and --pubkey at once") - } - if outputSet && (showAddress || showPublicKey) { - return errors.New("cannot use --output with --address or --pubkey") - } - - bechKeyOut, err := getBechKeyOut(viper.GetString(FlagBechPrefix)) - if err != nil { - return err - } - - switch { - case showAddress: - printKeyAddress(info, bechKeyOut) - case showPublicKey: - printPubKey(info, bechKeyOut) - default: - printKeyInfo(info, bechKeyOut) - } - return nil - }, + RunE: runShowCmd, } cmd.Flags().String(FlagBechPrefix, "acc", "The Bech32 prefix encoding for a key (acc|val|cons)") @@ -71,6 +37,43 @@ func showKeysCmd() *cobra.Command { return cmd } +func runShowCmd(cmd *cobra.Command, args []string) error { + name := args[0] + + info, err := GetKeyInfo(name) + if err != nil { + return err + } + + isShowAddr := viper.GetBool(FlagAddress) + isShowPubKey := viper.GetBool(FlagPublicKey) + isOutputSet := cmd.Flag(cli.OutputFlag).Changed + + if isShowAddr && isShowPubKey { + return errors.New("cannot use both --address and --pubkey at once") + } + + if isOutputSet && (isShowAddr || isShowPubKey) { + return errors.New("cannot use --output with --address or --pubkey") + } + + bechKeyOut, err := getBechKeyOut(viper.GetString(FlagBechPrefix)) + if err != nil { + return err + } + + switch { + case isShowAddr: + printKeyAddress(info, bechKeyOut) + case isShowPubKey: + printPubKey(info, bechKeyOut) + default: + printKeyInfo(info, bechKeyOut) + } + + return nil +} + func getBechKeyOut(bechPrefix string) (bechKeyOutFn, error) { switch bechPrefix { case "acc": @@ -84,15 +87,6 @@ func getBechKeyOut(bechPrefix string) (bechKeyOutFn, error) { return nil, fmt.Errorf("invalid Bech32 prefix encoding provided: %s", bechPrefix) } -func getKey(name string) (keys.Info, error) { - kb, err := GetKeyBase() - if err != nil { - return nil, err - } - - return kb.Get(name) -} - /////////////////////////// // REST @@ -113,7 +107,7 @@ func GetKeyRequestHandler(w http.ResponseWriter, r *http.Request) { return } - info, err := getKey(name) + 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 { diff --git a/client/lcd/certificates.go b/client/lcd/certificates.go new file mode 100644 index 000000000..f47f2397c --- /dev/null +++ b/client/lcd/certificates.go @@ -0,0 +1,174 @@ +package lcd + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "math/big" + "net" + "os" + "strings" + "time" +) + +// default: 30 days +const defaultValidFor = 30 * 24 * time.Hour + +func generateSelfSignedCert(host string) (certBytes []byte, priv *ecdsa.PrivateKey, err error) { + priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + notBefore := time.Now() + notAfter := notBefore.Add(defaultValidFor) + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + err = fmt.Errorf("failed to generate serial number: %s", err) + return + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"Gaia Lite"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: true, + } + hosts := strings.Split(host, ",") + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + + certBytes, err = x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + err = fmt.Errorf("couldn't create certificate: %s", err) + return + } + return +} + +func writeCertAndPrivKey(certBytes []byte, priv *ecdsa.PrivateKey) (certFile string, keyFile string, err error) { + if priv == nil { + err = errors.New("private key is nil") + return + } + certFile, err = writeCertificateFile(certBytes) + if err != nil { + return + } + keyFile, err = writeKeyFile(priv) + return +} + +func writeCertificateFile(certBytes []byte) (filename string, err error) { + f, err := ioutil.TempFile("", "cert_") + if err != nil { + return + } + defer f.Close() + filename = f.Name() + if err := pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}); err != nil { + return filename, fmt.Errorf("failed to write data to %s: %s", filename, err) + } + return +} + +func writeKeyFile(priv *ecdsa.PrivateKey) (filename string, err error) { + f, err := ioutil.TempFile("", "key_") + if err != nil { + return + } + defer f.Close() + filename = f.Name() + block, err := pemBlockForKey(priv) + if err != nil { + return + } + if err := pem.Encode(f, block); err != nil { + return filename, fmt.Errorf("failed to write data to %s: %s", filename, err) + } + return +} + +func pemBlockForKey(priv *ecdsa.PrivateKey) (*pem.Block, error) { + b, err := x509.MarshalECPrivateKey(priv) + if err != nil { + return nil, fmt.Errorf("unable to marshal ECDSA private key: %v", err) + } + return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil + +} + +func genCertKeyFilesAndReturnFingerprint(sslHosts string) (certFile, keyFile string, fingerprint string, err error) { + certBytes, priv, err := generateSelfSignedCert(sslHosts) + if err != nil { + return + } + certFile, keyFile, err = writeCertAndPrivKey(certBytes, priv) + cleanupFunc := func() { + os.Remove(certFile) + os.Remove(keyFile) + } + // Either of the files could have been written already, + // thus clean up regardless of the error. + if err != nil { + defer cleanupFunc() + return + } + fingerprint, err = fingerprintForCertificate(certBytes) + if err != nil { + defer cleanupFunc() + return + } + return +} + +func fingerprintForCertificate(certBytes []byte) (string, error) { + cert, err := x509.ParseCertificate(certBytes) + if err != nil { + return "", err + } + h := sha256.New() + h.Write(cert.Raw) + fingerprintBytes := h.Sum(nil) + var buf bytes.Buffer + for i, b := range fingerprintBytes { + if i > 0 { + fmt.Fprintf(&buf, ":") + } + fmt.Fprintf(&buf, "%02X", b) + } + return fmt.Sprintf("SHA256 Fingerprint=%s", buf.String()), nil +} + +func fingerprintFromFile(certFile string) (string, error) { + f, err := os.Open(certFile) + if err != nil { + return "", err + } + defer f.Close() + data, err := ioutil.ReadAll(f) + if err != nil { + return "", err + } + block, _ := pem.Decode(data) + if block == nil { + return "", fmt.Errorf("couldn't find PEM data in %s", certFile) + } + return fingerprintForCertificate(block.Bytes) +} diff --git a/client/lcd/certificates_test.go b/client/lcd/certificates_test.go new file mode 100644 index 000000000..14bddfa0f --- /dev/null +++ b/client/lcd/certificates_test.go @@ -0,0 +1,93 @@ +package lcd + +import ( + "crypto/ecdsa" + "crypto/x509" + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGenerateSelfSignedCert(t *testing.T) { + host := "127.0.0.1,localhost,::1" + certBytes, _, err := generateSelfSignedCert(host) + require.Nil(t, err) + cert, err := x509.ParseCertificate(certBytes) + require.Nil(t, err) + require.Equal(t, 2, len(cert.IPAddresses)) + require.Equal(t, 1, len(cert.DNSNames)) + require.True(t, cert.IsCA) +} + +func TestWriteCertAndPrivKey(t *testing.T) { + expectedPerm := "-rw-------" + derBytes, priv, err := generateSelfSignedCert("localhost") + require.Nil(t, err) + type args struct { + certBytes []byte + priv *ecdsa.PrivateKey + } + tests := []struct { + name string + args args + wantErr bool + }{ + {"valid certificate", args{derBytes, priv}, false}, + {"garbage", args{[]byte("some garbage"), nil}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotCertFile, gotKeyFile, err := writeCertAndPrivKey(tt.args.certBytes, tt.args.priv) + defer os.Remove(gotCertFile) + defer os.Remove(gotKeyFile) + if tt.wantErr { + require.NotNil(t, err) + return + } + require.Nil(t, err) + info, err := os.Stat(gotCertFile) + require.Nil(t, err) + require.True(t, info.Mode().IsRegular()) + require.Equal(t, expectedPerm, info.Mode().String()) + info, err = os.Stat(gotKeyFile) + require.Nil(t, err) + require.True(t, info.Mode().IsRegular()) + require.Equal(t, expectedPerm, info.Mode().String()) + }) + } +} + +func TestFingerprintFromFile(t *testing.T) { + cert := `-----BEGIN CERTIFICATE----- +MIIBbDCCARGgAwIBAgIQSuFKYv/22v+cxtVgMUrQADAKBggqhkjOPQQDAjASMRAw +DgYDVQQKEwdBY21lIENvMB4XDTE4MDkyMDIzNDQyNloXDTE5MDkyMDIzNDQyNlow +EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDIo +ujAesRczcPVAWiLhpeV1B7hS/RI2LJaGj3QjyJ8hiUthJTPIamr8m7LuS/U5fS0o +hY297YeTIGo9YkxClICjSTBHMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr +BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MA8GA1UdEQQIMAaHBH8AAAEwCgYIKoZI +zj0EAwIDSQAwRgIhAKnwbhX9FrGG1otCVLwhClQ3RaLxnNpCgIGTqSimb34cAiEA +stMN+IqMCKWlZyGqxGIiyksMLMEU3lRqKNQn2EoAZJY= +-----END CERTIFICATE-----` + wantFingerprint := `SHA256 Fingerprint=0B:ED:9A:AA:A2:D1:7E:B2:53:56:F6:FC:C0:E6:1A:69:70:21:A2:B0:90:FC:AF:BB:EF:AE:2C:78:52:AB:68:40` + certFile, err := ioutil.TempFile("", "test_cert_") + require.Nil(t, err) + _, err = certFile.Write([]byte(cert)) + require.Nil(t, err) + err = certFile.Close() + require.Nil(t, err) + defer os.Remove(certFile.Name()) + fingerprint, err := fingerprintFromFile(certFile.Name()) + require.Nil(t, err) + require.Equal(t, wantFingerprint, fingerprint) + + // test failure + emptyFile, err := ioutil.TempFile("", "test_cert_") + require.Nil(t, err) + err = emptyFile.Close() + require.Nil(t, err) + defer os.Remove(emptyFile.Name()) + _, err = fingerprintFromFile(emptyFile.Name()) + require.NotNil(t, err) +} diff --git a/client/lcd/wire.go b/client/lcd/codec.go similarity index 100% rename from client/lcd/wire.go rename to client/lcd/codec.go diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 7fc98aef6..1da8edcc8 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "os" "regexp" "testing" "time" @@ -22,19 +23,20 @@ import ( 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/codec" tests "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" + version "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/x/auth" authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" - "github.com/cosmos/cosmos-sdk/x/stake/client/rest" ) func init() { cryptoKeys.BcryptSecurityParameter = 1 + version.Version = os.Getenv("VERSION") } func TestKeys(t *testing.T) { @@ -61,7 +63,7 @@ func TestKeys(t *testing.T) { require.Equal(t, http.StatusOK, res.StatusCode, body) var resp keys.KeyOutput - err = wire.Cdc.UnmarshalJSON([]byte(body), &resp) + err = codec.Cdc.UnmarshalJSON([]byte(body), &resp) require.Nil(t, err, body) addr2Bech32 := resp.Address @@ -118,6 +120,11 @@ func TestKeys(t *testing.T) { } func TestVersion(t *testing.T) { + // skip the test if the VERSION environment variable has not been set + if version.Version == "" { + t.SkipNow() + } + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) defer cleanup() @@ -125,16 +132,16 @@ func TestVersion(t *testing.T) { res, body := Request(t, port, "GET", "/version", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - reg, err := regexp.Compile(`\d+\.\d+\.\d+(-dev)?`) + reg, err := regexp.Compile(`\d+\.\d+\.\d+.*`) require.Nil(t, err) match := reg.MatchString(body) - require.True(t, match, body) + require.True(t, match, body, fmt.Sprintf("%s", body)) // node info res, body = Request(t, port, "GET", "/node_version", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - reg, err = regexp.Compile(`\d+\.\d+\.\d+(-dev)?`) + reg, err = regexp.Compile(`\d+\.\d+\.\d+.*`) require.Nil(t, err) match = reg.MatchString(body) require.True(t, match, body) @@ -178,10 +185,10 @@ func TestBlock(t *testing.T) { // -- - res, body = Request(t, port, "GET", "/blocks/1", nil) + res, body = Request(t, port, "GET", "/blocks/2", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - err = wire.Cdc.UnmarshalJSON([]byte(body), &resultBlock) + err = codec.Cdc.UnmarshalJSON([]byte(body), &resultBlock) require.Nil(t, err, "Couldn't parse block") require.NotEqual(t, ctypes.ResultBlock{}, resultBlock) @@ -211,7 +218,7 @@ func TestValidators(t *testing.T) { // -- - res, body = Request(t, port, "GET", "/validatorsets/1", nil) + res, body = Request(t, port, "GET", "/validatorsets/2", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) err = cdc.UnmarshalJSON([]byte(body), &resultVals) @@ -267,25 +274,33 @@ func TestCoinSend(t *testing.T) { require.Equal(t, int64(1), mycoins.Amount.Int64()) // test failure with too little gas - res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 100, 0, "") + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, "100", 0, "") + require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) + + // test failure with negative gas + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, "-200", 0, "") + require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) + + // test failure with 0 gas + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, "0", 0, "") require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) // test failure with wrong adjustment - res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 0, 0.1, "") + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, "simulate", 0.1, "") require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) // run simulation and test success with estimated gas - res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 0, 0, "?simulate=true") + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, "", 0, "?simulate=true") require.Equal(t, http.StatusOK, res.StatusCode, body) var responseBody struct { GasEstimate int64 `json:"gas_estimate"` } require.Nil(t, json.Unmarshal([]byte(body), &responseBody)) - res, body, _ = doSendWithGas(t, port, seed, name, password, addr, responseBody.GasEstimate, 0, "") + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, fmt.Sprintf("%v", responseBody.GasEstimate), 0, "") require.Equal(t, http.StatusOK, res.StatusCode, body) } -func TestIBCTransfer(t *testing.T) { +func DisabledTestIBCTransfer(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKeyBase(t)) cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) @@ -322,7 +337,7 @@ func TestCoinSendGenerateSignAndBroadcast(t *testing.T) { acc := getAccount(t, port, addr) // generate TX - res, body, _ := doSendWithGas(t, port, seed, name, password, addr, 0, 0, "?generate_only=true") + res, body, _ := doSendWithGas(t, port, seed, name, password, addr, "simulate", 0, "?generate_only=true") require.Equal(t, http.StatusOK, res.StatusCode, body) var msg auth.StdTx require.Nil(t, cdc.UnmarshalJSON([]byte(body), &msg)) @@ -478,12 +493,12 @@ func TestValidatorsQuery(t *testing.T) { // make sure all the validators were found (order unknown because sorted by operator addr) foundVal := false - pkBech := sdk.MustBech32ifyConsPub(pks[0]) - if validators[0].ConsPubKey == pkBech { + + if validators[0].ConsPubKey == pks[0] { foundVal = true } - require.True(t, foundVal, "pkBech %v, operator %v", pkBech, validators[0].OperatorAddr) + require.True(t, foundVal, "pk %v, operator %v", pks[0], validators[0].OperatorAddr) } func TestValidatorQuery(t *testing.T) { @@ -502,6 +517,7 @@ func TestBonding(t *testing.T) { cleanup, pks, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) defer cleanup() + amt := sdk.NewDec(60) validator1Operator := sdk.ValAddress(pks[0].Address()) validator := getValidator(t, port, validator1Operator) @@ -519,18 +535,18 @@ func TestBonding(t *testing.T) { // query validator bond := getDelegation(t, port, addr, validator1Operator) - require.Equal(t, "60.0000000000", bond.Shares) + require.Equal(t, amt, bond.Shares) summary := getDelegationSummary(t, port, addr) require.Len(t, summary.Delegations, 1, "Delegation summary holds all delegations") - require.Equal(t, "60.0000000000", summary.Delegations[0].Shares) + require.Equal(t, amt, summary.Delegations[0].Shares) require.Len(t, summary.UnbondingDelegations, 0, "Delegation summary holds all unbonding-delegations") bondedValidators := getDelegatorValidators(t, port, addr) require.Len(t, bondedValidators, 1) require.Equal(t, validator1Operator, bondedValidators[0].OperatorAddr) - require.Equal(t, validator.DelegatorShares.Add(sdk.NewDec(60)).String(), bondedValidators[0].DelegatorShares.String()) + require.Equal(t, validator.DelegatorShares.Add(amt).String(), bondedValidators[0].DelegatorShares.String()) bondedValidator := getDelegatorValidator(t, port, addr, validator1Operator) require.Equal(t, validator1Operator, bondedValidator.OperatorAddr) @@ -550,9 +566,8 @@ func TestBonding(t *testing.T) { coins = acc.GetCoins() require.Equal(t, int64(40), coins.AmountOf("steak").Int64()) - unbondings := getUndelegations(t, port, addr, validator1Operator) - require.Len(t, unbondings, 1, "Unbondings holds all unbonding-delegations") - require.Equal(t, "60", unbondings[0].Balance.Amount.String()) + unbonding := getUndelegation(t, port, addr, validator1Operator) + require.Equal(t, "60", unbonding.Balance.Amount.String()) summary = getDelegationSummary(t, port, addr) @@ -792,7 +807,7 @@ func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account { return acc } -func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.AccAddress, gas int64, gasAdjustment float64, queryStr string) (res *http.Response, body string, receiveAddr sdk.AccAddress) { +func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.AccAddress, gas string, gasAdjustment float64, queryStr string) (res *http.Response, body string, receiveAddr sdk.AccAddress) { // create receive address kb := client.MockKeyBase() @@ -811,33 +826,35 @@ func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.Acc } gasStr := "" - if gas > 0 { + if len(gas) != 0 { gasStr = fmt.Sprintf(` - "gas":"%v", + "gas":%q, `, gas) } gasAdjustmentStr := "" if gasAdjustment > 0 { - gasStr = fmt.Sprintf(` + gasAdjustmentStr = fmt.Sprintf(` "gas_adjustment":"%v", `, gasAdjustment) } jsonStr := []byte(fmt.Sprintf(`{ - %v%v - "name":"%s", - "password":"%s", - "account_number":"%d", - "sequence":"%d", "amount":[%s], - "chain_id":"%s" - }`, gasStr, gasAdjustmentStr, name, password, accnum, sequence, coinbz, chainID)) + "base_req": { + %v%v + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d" + } + }`, coinbz, gasStr, gasAdjustmentStr, name, password, chainID, accnum, sequence)) res, body = Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send%v", receiveAddr, queryStr), jsonStr) return } func doSend(t *testing.T, port, seed, name, password string, addr sdk.AccAddress) (receiveAddr sdk.AccAddress, resultTx ctypes.ResultBroadcastTxCommit) { - res, body, receiveAddr := doSendWithGas(t, port, seed, name, password, addr, 0, 0, "") + res, body, receiveAddr := doSendWithGas(t, port, seed, name, password, addr, "", 0, "") require.Equal(t, http.StatusOK, res.StatusCode, body) err := cdc.UnmarshalJSON([]byte(body), &resultTx) @@ -862,18 +879,20 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Acc // send jsonStr := []byte(fmt.Sprintf(`{ - "name":"%s", - "password": "%s", - "account_number":"%d", - "sequence": "%d", - "src_chain_id": "%s", "amount":[ { "denom": "%s", "amount": "1" } - ] - }`, name, password, accnum, sequence, chainID, "steak")) + ], + "base_req": { + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d" + } + }`, "steak", 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) @@ -887,43 +906,43 @@ 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) require.Equal(t, http.StatusOK, res.StatusCode, body) + var signingInfo slashing.ValidatorSigningInfo err := cdc.UnmarshalJSON([]byte(body), &signingInfo) require.Nil(t, err) + return signingInfo } // ============= Stake Module ================ -func getDelegation(t *testing.T, port string, delAddr sdk.AccAddress, valAddr sdk.ValAddress) rest.DelegationWithoutRat { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/delegations/%s", delAddr, valAddr), nil) +func getDelegation(t *testing.T, port string, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) stake.Delegation { + res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/delegations/%s", delegatorAddr, validatorAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var bond rest.DelegationWithoutRat - + var bond stake.Delegation err := cdc.UnmarshalJSON([]byte(body), &bond) require.Nil(t, err) return bond } -func getUndelegations(t *testing.T, port string, delAddr sdk.AccAddress, valAddr sdk.ValAddress) []stake.UnbondingDelegation { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/unbonding_delegations/%s", delAddr, valAddr), nil) +func getUndelegation(t *testing.T, port string, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) stake.UnbondingDelegation { + res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/unbonding_delegations/%s", delegatorAddr, validatorAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var unbondings []stake.UnbondingDelegation - + var unbondings stake.UnbondingDelegation err := cdc.UnmarshalJSON([]byte(body), &unbondings) require.Nil(t, err) return unbondings } -func getDelegationSummary(t *testing.T, port string, delegatorAddr sdk.AccAddress) rest.DelegationSummary { +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) require.Equal(t, http.StatusOK, res.StatusCode, body) - var summary rest.DelegationSummary + var summary stake.DelegationSummary err := cdc.UnmarshalJSON([]byte(body), &summary) require.Nil(t, err) @@ -950,11 +969,11 @@ func getBondingTxs(t *testing.T, port string, delegatorAddr sdk.AccAddress, quer return txs } -func getDelegatorValidators(t *testing.T, port string, delegatorAddr sdk.AccAddress) []stake.BechValidator { +func getDelegatorValidators(t *testing.T, port string, delegatorAddr sdk.AccAddress) []stake.Validator { res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/validators", delegatorAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var bondedValidators []stake.BechValidator + var bondedValidators []stake.Validator err := cdc.UnmarshalJSON([]byte(body), &bondedValidators) require.Nil(t, err) @@ -962,11 +981,11 @@ func getDelegatorValidators(t *testing.T, port string, delegatorAddr sdk.AccAddr return bondedValidators } -func getDelegatorValidator(t *testing.T, port string, delAddr sdk.AccAddress, valAddr sdk.ValAddress) stake.BechValidator { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/validators/%s", delAddr, valAddr), nil) +func getDelegatorValidator(t *testing.T, port string, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) stake.Validator { + res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/validators/%s", delegatorAddr, validatorAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var bondedValidator stake.BechValidator + var bondedValidator stake.Validator err := cdc.UnmarshalJSON([]byte(body), &bondedValidator) require.Nil(t, err) @@ -982,11 +1001,6 @@ func doDelegate(t *testing.T, port, seed, name, password string, chainID := viper.GetString(client.FlagChainID) jsonStr := []byte(fmt.Sprintf(`{ - "name": "%s", - "password": "%s", - "account_number": "%d", - "sequence": "%d", - "chain_id": "%s", "delegations": [ { "delegator_addr": "%s", @@ -997,8 +1011,15 @@ func doDelegate(t *testing.T, port, seed, name, password string, "begin_unbondings": [], "complete_unbondings": [], "begin_redelegates": [], - "complete_redelegates": [] - }`, name, password, accnum, sequence, chainID, delAddr, valAddr, "steak", amount)) + "complete_redelegates": [], + "base_req": { + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d" + } + }`, delAddr, valAddr, "steak", 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) @@ -1019,11 +1040,6 @@ func doBeginUnbonding(t *testing.T, port, seed, name, password string, chainID := viper.GetString(client.FlagChainID) jsonStr := []byte(fmt.Sprintf(`{ - "name": "%s", - "password": "%s", - "account_number": "%d", - "sequence": "%d", - "chain_id": "%s", "delegations": [], "begin_unbondings": [ { @@ -1034,8 +1050,15 @@ func doBeginUnbonding(t *testing.T, port, seed, name, password string, ], "complete_unbondings": [], "begin_redelegates": [], - "complete_redelegates": [] - }`, name, password, accnum, sequence, chainID, delAddr, valAddr, amount)) + "complete_redelegates": [], + "base_req": { + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d" + } + }`, delAddr, valAddr, 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) @@ -1057,11 +1080,6 @@ func doBeginRedelegation(t *testing.T, port, seed, name, password string, chainID := viper.GetString(client.FlagChainID) jsonStr := []byte(fmt.Sprintf(`{ - "name": "%s", - "password": "%s", - "account_number": "%d", - "sequence": "%d", - "chain_id": "%s", "delegations": [], "begin_unbondings": [], "complete_unbondings": [], @@ -1073,8 +1091,15 @@ func doBeginRedelegation(t *testing.T, port, seed, name, password string, "shares": "30" } ], - "complete_redelegates": [] - }`, name, password, accnum, sequence, chainID, delAddr, valSrcAddr, valDstAddr)) + "complete_redelegates": [], + "base_req": { + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d" + } + }`, delAddr, valSrcAddr, valDstAddr, 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) @@ -1086,21 +1111,25 @@ func doBeginRedelegation(t *testing.T, port, seed, name, password string, return results[0] } -func getValidators(t *testing.T, port string) []stake.BechValidator { +func getValidators(t *testing.T, port string) []stake.Validator { res, body := Request(t, port, "GET", "/stake/validators", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var validators []stake.BechValidator + + var validators []stake.Validator err := cdc.UnmarshalJSON([]byte(body), &validators) require.Nil(t, err) + return validators } -func getValidator(t *testing.T, port string, valAddr sdk.ValAddress) stake.BechValidator { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/validators/%s", valAddr.String()), nil) +func getValidator(t *testing.T, port string, validatorAddr sdk.ValAddress) stake.Validator { + res, body := Request(t, port, "GET", fmt.Sprintf("/stake/validators/%s", validatorAddr.String()), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var validator stake.BechValidator + + var validator stake.Validator err := cdc.UnmarshalJSON([]byte(body), &validator) require.Nil(t, err) + return validator } @@ -1276,7 +1305,6 @@ func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.Ac } }`, proposerAddr, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/votes", proposalID), jsonStr) - fmt.Println(res) require.Equal(t, http.StatusOK, res.StatusCode, body) var results ctypes.ResultBroadcastTxCommit diff --git a/client/lcd/root.go b/client/lcd/root.go index f18ee5dfd..84d3e806c 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -1,6 +1,8 @@ package lcd import ( + "errors" + "net" "net/http" "os" @@ -9,11 +11,10 @@ import ( "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" 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" - ibc "github.com/cosmos/cosmos-sdk/x/ibc/client/rest" slashing "github.com/cosmos/cosmos-sdk/x/slashing/client/rest" stake "github.com/cosmos/cosmos-sdk/x/stake/client/rest" "github.com/gorilla/mux" @@ -24,35 +25,84 @@ import ( tmserver "github.com/tendermint/tendermint/rpc/lib/server" ) +const ( + flagListenAddr = "laddr" + flagCORS = "cors" + flagMaxOpenConnections = "max-open" + flagInsecure = "insecure" + flagSSLHosts = "ssl-hosts" + flagSSLCertFile = "ssl-certfile" + flagSSLKeyFile = "ssl-keyfile" +) + // ServeCommand will generate a long-running rest server // (aka Light Client Daemon) that exposes functionality similar // to the cli, but over rest -func ServeCommand(cdc *wire.Codec) *cobra.Command { - flagListenAddr := "laddr" - flagCORS := "cors" - flagMaxOpenConnections := "max-open" +func ServeCommand(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "rest-server", Short: "Start LCD (light-client daemon), a local REST server", - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) (err error) { listenAddr := viper.GetString(flagListenAddr) handler := createHandler(cdc) 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() {} - listener, err := tmserver.StartHTTPServer( - listenAddr, handler, logger, - tmserver.Config{MaxOpenConnections: maxOpen}, - ) - if err != nil { - return err + var listener net.Listener + var fingerprint string + if viper.GetBool(flagInsecure) { + 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 + err = validateCertKeyFiles(certFile, keyFile) + if err != nil { + return err + } + // cert/key pair is provided, read the fingerprint + fingerprint, err = fingerprintFromFile(certFile) + if err != nil { + return err + } + } else { + // if certificate is not supplied, generate a self-signed one + certFile, keyFile, fingerprint, err = genCertKeyFilesAndReturnFingerprint(sslHosts) + if err != nil { + return err + } + cleanupFunc = func() { + os.Remove(certFile) + os.Remove(keyFile) + } + defer cleanupFunc() + } + listener, err = tmserver.StartHTTPAndTLSServer( + listenAddr, handler, + certFile, keyFile, + logger, + tmserver.Config{MaxOpenConnections: maxOpen}, + ) + if err != nil { + return + } + logger.Info(fingerprint) } - logger.Info("REST server started") // wait forever and cleanup cmn.TrapSignal(func() { + defer cleanupFunc() err := listener.Close() logger.Error("error closing listener", "err", err) }) @@ -62,16 +112,23 @@ func ServeCommand(cdc *wire.Codec) *cobra.Command { } cmd.Flags().String(flagListenAddr, "tcp://localhost:1317", "The address for the server to listen on") + cmd.Flags().Bool(flagInsecure, false, "Do not set up SSL/TLS layer") + cmd.Flags().String(flagSSLHosts, "", "Comma-separated hostnames and IPs to generate a certificate for") + cmd.Flags().String(flagSSLCertFile, "", "Path to a SSL certificate file. If not supplied, a self-signed certificate will be generated.") + cmd.Flags().String(flagSSLKeyFile, "", "Path to a key file; ignored if a certificate file is not supplied.") cmd.Flags().String(flagCORS, "", "Set the domains that can make CORS requests (* for all)") - cmd.Flags().String(client.FlagChainID, "", "The chain ID to connect to") + cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") cmd.Flags().String(client.FlagNode, "tcp://localhost:26657", "Address of the node to connect to") cmd.Flags().Int(flagMaxOpenConnections, 1000, "The number of maximum open connections") - cmd.Flags().Bool(client.FlagTrustNode, false, "Whether trust connected full node") + cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) + viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) + viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) return cmd } -func createHandler(cdc *wire.Codec) http.Handler { +func createHandler(cdc *codec.Codec) http.Handler { r := mux.NewRouter() kb, err := keys.GetKeyBase() //XXX @@ -90,10 +147,22 @@ func createHandler(cdc *wire.Codec) http.Handler { tx.RegisterRoutes(cliCtx, r, cdc) auth.RegisterRoutes(cliCtx, r, cdc, "acc") bank.RegisterRoutes(cliCtx, r, cdc, kb) - ibc.RegisterRoutes(cliCtx, r, cdc, kb) stake.RegisterRoutes(cliCtx, r, cdc, kb) slashing.RegisterRoutes(cliCtx, r, cdc, kb) gov.RegisterRoutes(cliCtx, r, cdc) return r } + +func validateCertKeyFiles(certFile, keyFile string) error { + if keyFile == "" { + return errors.New("a key file is required") + } + if _, err := os.Stat(certFile); err != nil { + return err + } + if _, err := os.Stat(keyFile); err != nil { + return err + } + return nil +} diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index af2055de8..d41eeaec2 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -15,11 +15,11 @@ import ( "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" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/spf13/viper" @@ -177,7 +177,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress genesisState.StakeData.Pool.LooseTokens = genesisState.StakeData.Pool.LooseTokens.Add(sdk.NewDec(100)) } - appState, err := wire.MarshalJSONIndent(cdc, genesisState) + appState, err := codec.MarshalJSONIndent(cdc, genesisState) require.NoError(t, err) genDoc.AppState = appState @@ -187,6 +187,10 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress // XXX: Need to set this so LCD knows the tendermint node address! viper.Set(client.FlagNode, config.RPC.ListenAddress) viper.Set(client.FlagChainID, genDoc.ChainID) + viper.Set(client.FlagTrustNode, false) + dir, err := ioutil.TempDir("", "lcd_test") + require.NoError(t, err) + viper.Set(cli.HomeFlag, dir) node, err := startTM(config, logger, genDoc, privVal, app) require.NoError(t, err) @@ -251,7 +255,7 @@ func startTM( // startLCD starts the LCD. // // NOTE: This causes the thread to block. -func startLCD(logger log.Logger, listenAddr string, cdc *wire.Codec) (net.Listener, error) { +func startLCD(logger log.Logger, listenAddr string, cdc *codec.Codec) (net.Listener, error) { return tmrpc.StartHTTPServer(listenAddr, createHandler(cdc), logger, tmrpc.Config{}) } diff --git a/client/rpc/block.go b/client/rpc/block.go index d16f38342..44def2d32 100644 --- a/client/rpc/block.go +++ b/client/rpc/block.go @@ -10,6 +10,8 @@ import ( "github.com/gorilla/mux" "github.com/spf13/cobra" + "github.com/spf13/viper" + tmliteProxy "github.com/tendermint/tendermint/lite/proxy" ) //BlockCommand returns the verified block data for a given heights @@ -21,8 +23,10 @@ func BlockCommand() *cobra.Command { RunE: printBlock, } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") - // TODO: change this to false when we can - cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses") + viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) + cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) + cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") return cmd } @@ -41,8 +45,25 @@ func getBlock(cliCtx context.CLIContext, height *int64) ([]byte, error) { return nil, err } + if !cliCtx.TrustNode { + check, err := cliCtx.Certify(res.Block.Height) + if err != nil { + return nil, err + } + + err = tmliteProxy.ValidateBlockMeta(res.BlockMeta, check) + if err != nil { + return nil, err + } + + err = tmliteProxy.ValidateBlock(res.Block, check) + if err != nil { + return nil, err + } + } + // TODO move maarshalling into cmd/rest functions - // output, err := tmwire.MarshalJSON(res) + // output, err := tmcodec.MarshalJSON(res) output, err := cdc.MarshalJSON(res) if err != nil { return nil, err diff --git a/client/rpc/wire.go b/client/rpc/codec.go similarity index 100% rename from client/rpc/wire.go rename to client/rpc/codec.go diff --git a/client/rpc/root.go b/client/rpc/root.go index a8171a293..74c5a69a2 100644 --- a/client/rpc/root.go +++ b/client/rpc/root.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/spf13/viper" ) const ( @@ -40,6 +41,9 @@ func initClientCommand() *cobra.Command { cmd.Flags().String(flagGenesis, "", "Genesis file to verify header validity") cmd.Flags().String(flagCommit, "", "File with trusted and signed header") cmd.Flags().String(flagValHash, "", "Hash of trusted validator set (hex-encoded)") + viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) + viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) + return cmd } diff --git a/client/rpc/status.go b/client/rpc/status.go index ea090d32f..6c4270175 100644 --- a/client/rpc/status.go +++ b/client/rpc/status.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/spf13/viper" ) func statusCommand() *cobra.Command { @@ -20,6 +21,7 @@ func statusCommand() *cobra.Command { } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") + viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) return cmd } diff --git a/client/rpc/validators.go b/client/rpc/validators.go index cb002d3ea..4802e785b 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -1,6 +1,7 @@ package rpc import ( + "bytes" "fmt" "net/http" "strconv" @@ -12,6 +13,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" tmtypes "github.com/tendermint/tendermint/types" + "github.com/spf13/viper" ) // TODO these next two functions feel kinda hacky based on their placement @@ -19,14 +21,17 @@ import ( //ValidatorCommand returns the validator set for a given height func ValidatorCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "validator-set [height]", + Use: "tendermint-validator-set [height]", Short: "Get the full tendermint validator set at given height", Args: cobra.MaximumNArgs(1), RunE: printValidators, } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") - // TODO: change this to false when we can - cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses") + viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) + cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) + cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") + viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) return cmd } @@ -70,6 +75,17 @@ func getValidators(cliCtx context.CLIContext, height *int64) ([]byte, error) { return nil, err } + if !cliCtx.TrustNode { + check, err := cliCtx.Certify(validatorsRes.BlockHeight) + if err != nil { + return nil, err + } + + if !bytes.Equal(check.ValidatorsHash(), tmtypes.NewValidatorSet(validatorsRes.Validators).Hash()) { + return nil, fmt.Errorf("got invalid validatorset") + } + } + outputValidatorsRes := ResultValidatorsOutput{ BlockHeight: validatorsRes.BlockHeight, Validators: make([]ValidatorOutput, len(validatorsRes.Validators)), diff --git a/client/tx/broadcast.go b/client/tx/broadcast.go index 89ad48f43..a49431825 100644 --- a/client/tx/broadcast.go +++ b/client/tx/broadcast.go @@ -25,7 +25,7 @@ func BroadcastTxRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } - res, err := cliCtx.BroadcastTx([]byte(m.TxBytes)) + res, err := cliCtx.BroadcastTxAndAwaitCommit([]byte(m.TxBytes)) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) diff --git a/client/tx/query.go b/client/tx/query.go index f95776b10..f1d13b608 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -4,25 +4,23 @@ import ( "encoding/hex" "fmt" "net/http" - "strconv" - "github.com/tendermint/tendermint/libs/common" "github.com/gorilla/mux" "github.com/spf13/cobra" - "github.com/spf13/viper" abci "github.com/tendermint/tendermint/abci/types" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/spf13/viper" ) // QueryTxCmd implements the default command for a tx query. -func QueryTxCmd(cdc *wire.Codec) *cobra.Command { +func QueryTxCmd(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "tx [hash]", Short: "Matches this txhash over all committed blocks", @@ -30,11 +28,10 @@ func QueryTxCmd(cdc *wire.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { // find the key to look up the account hashHexStr := args[0] - trustNode := viper.GetBool(client.FlagTrustNode) cliCtx := context.NewCLIContext().WithCodec(cdc) - output, err := queryTx(cdc, cliCtx, hashHexStr, trustNode) + output, err := queryTx(cdc, cliCtx, hashHexStr) if err != nil { return err } @@ -45,13 +42,15 @@ func QueryTxCmd(cdc *wire.Codec) *cobra.Command { } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") - - // TODO: change this to false when we can - cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses") + viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) + cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") + viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) + cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) return cmd } -func queryTx(cdc *wire.Codec, cliCtx context.CLIContext, hashHexStr string, trustNode bool) ([]byte, error) { +func queryTx(cdc *codec.Codec, cliCtx context.CLIContext, hashHexStr string) ([]byte, error) { hash, err := hex.DecodeString(hashHexStr) if err != nil { return nil, err @@ -62,21 +61,41 @@ func queryTx(cdc *wire.Codec, cliCtx context.CLIContext, hashHexStr string, trus return nil, err } - res, err := node.Tx(hash, !trustNode) + res, err := node.Tx(hash, !cliCtx.TrustNode) if err != nil { return nil, err } + if !cliCtx.TrustNode { + err := ValidateTxResult(cliCtx, res) + if err != nil { + return nil, err + } + } + info, err := formatTxResult(cdc, res) if err != nil { return nil, err } - return wire.MarshalJSONIndent(cdc, info) + return codec.MarshalJSONIndent(cdc, info) } -func formatTxResult(cdc *wire.Codec, res *ctypes.ResultTx) (Info, error) { - // TODO: verify the proof if requested +// ValidateTxResult performs transaction verification +func ValidateTxResult(cliCtx context.CLIContext, res *ctypes.ResultTx) error { + check, err := cliCtx.Certify(res.Height) + if err != nil { + return err + } + + err = res.Proof.Validate(check.Header.DataHash) + if err != nil { + return err + } + return nil +} + +func formatTxResult(cdc *codec.Codec, res *ctypes.ResultTx) (Info, error) { tx, err := parseTx(cdc, res.Tx) if err != nil { return Info{}, err @@ -98,7 +117,7 @@ type Info struct { Result abci.ResponseDeliverTx `json:"result"` } -func parseTx(cdc *wire.Codec, txBytes []byte) (sdk.Tx, error) { +func parseTx(cdc *codec.Codec, txBytes []byte) (sdk.Tx, error) { var tx auth.StdTx err := cdc.UnmarshalBinary(txBytes, &tx) @@ -112,17 +131,12 @@ func parseTx(cdc *wire.Codec, txBytes []byte) (sdk.Tx, error) { // REST // transaction query REST handler -func QueryTxRequestHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc { +func QueryTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) hashHexStr := vars["hash"] - trustNode, err := strconv.ParseBool(r.FormValue("trust_node")) - // trustNode defaults to true - if err != nil { - trustNode = true - } - output, err := queryTx(cdc, cliCtx, hashHexStr, trustNode) + output, err := queryTx(cdc, cliCtx, hashHexStr) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) diff --git a/client/tx/root.go b/client/tx/root.go index 7e18d5aae..19376a25b 100644 --- a/client/tx/root.go +++ b/client/tx/root.go @@ -5,11 +5,11 @@ import ( "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" ) // AddCommands adds a number of tx-query related subcommands -func AddCommands(cmd *cobra.Command, cdc *wire.Codec) { +func AddCommands(cmd *cobra.Command, cdc *codec.Codec) { cmd.AddCommand( SearchTxCmd(cdc), QueryTxCmd(cdc), @@ -17,7 +17,7 @@ func AddCommands(cmd *cobra.Command, cdc *wire.Codec) { } // register REST routes -func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec) { +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) { r.HandleFunc("/txs/{hash}", QueryTxRequestHandlerFn(cdc, cliCtx)).Methods("GET") r.HandleFunc("/txs", SearchTxRequestHandlerFn(cliCtx, cdc)).Methods("GET") // r.HandleFunc("/txs/sign", SignTxRequstHandler).Methods("POST") diff --git a/client/tx/search.go b/client/tx/search.go index 06b3c0972..8d83c44b3 100644 --- a/client/tx/search.go +++ b/client/tx/search.go @@ -9,8 +9,8 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -24,7 +24,7 @@ const ( ) // default client command to search through tagged transactions -func SearchTxCmd(cdc *wire.Codec) *cobra.Command { +func SearchTxCmd(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "txs", Short: "Search for all transactions that match the given tags.", @@ -62,15 +62,17 @@ $ gaiacli tendermint txs --tag test1,test2 --any } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") - - // TODO: change this to false once proofs built in - cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses") + viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) + cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") + viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) + cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) cmd.Flags().StringSlice(flagTags, nil, "Comma-separated list of tags that must match") cmd.Flags().Bool(flagAny, false, "Return transactions that match ANY tag, rather than ALL") return cmd } -func searchTxs(cliCtx context.CLIContext, cdc *wire.Codec, tags []string) ([]Info, error) { +func searchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string) ([]Info, error) { if len(tags) == 0 { return nil, errors.New("must declare at least one tag to search") } @@ -84,7 +86,7 @@ func searchTxs(cliCtx context.CLIContext, cdc *wire.Codec, tags []string) ([]Inf return nil, err } - prove := !viper.GetBool(client.FlagTrustNode) + prove := !cliCtx.TrustNode // TODO: take these as args page := 0 @@ -94,6 +96,15 @@ func searchTxs(cliCtx context.CLIContext, cdc *wire.Codec, tags []string) ([]Inf return nil, err } + if prove { + for _, tx := range res.Txs { + err := ValidateTxResult(cliCtx, tx) + if err != nil { + return nil, err + } + } + } + info, err := FormatTxResults(cdc, res.Txs) if err != nil { return nil, err @@ -103,7 +114,7 @@ func searchTxs(cliCtx context.CLIContext, cdc *wire.Codec, tags []string) ([]Inf } // parse the indexed txs into an array of Info -func FormatTxResults(cdc *wire.Codec, res []*ctypes.ResultTx) ([]Info, error) { +func FormatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]Info, error) { var err error out := make([]Info, len(res)) for i := range res { @@ -119,7 +130,7 @@ func FormatTxResults(cdc *wire.Codec, res []*ctypes.ResultTx) ([]Info, error) { // REST // Search Tx REST Handler -func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { +func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { tag := r.FormValue("tag") if tag == "" { diff --git a/client/tx/sign.go b/client/tx/sign.go deleted file mode 100644 index 786c7fa0b..000000000 --- a/client/tx/sign.go +++ /dev/null @@ -1,48 +0,0 @@ -package tx - -import ( - "encoding/json" - "net/http" - - keybase "github.com/cosmos/cosmos-sdk/client/keys" - keys "github.com/cosmos/cosmos-sdk/crypto/keys" -) - -// REST request body for signed txs -// TODO does this need to be exposed? -type SignTxBody struct { - Name string `json:"name"` - Password string `json:"password"` - TxBytes string `json:"tx"` -} - -// sign transaction REST Handler -func SignTxRequstHandler(w http.ResponseWriter, r *http.Request) { - var kb keys.Keybase - var m SignTxBody - - decoder := json.NewDecoder(r.Body) - err := decoder.Decode(&m) - if err != nil { - w.WriteHeader(400) - w.Write([]byte(err.Error())) - return - } - - kb, err = keybase.GetKeyBase() - if err != nil { - w.WriteHeader(500) - w.Write([]byte(err.Error())) - return - } - - //TODO check if account exists - sig, _, err := kb.Sign(m.Name, m.Password, []byte(m.TxBytes)) - if err != nil { - w.WriteHeader(403) - w.Write([]byte(err.Error())) - return - } - - w.Write(sig) -} diff --git a/client/utils/rest.go b/client/utils/rest.go index e0fae753d..5e3deb71d 100644 --- a/client/utils/rest.go +++ b/client/utils/rest.go @@ -2,10 +2,15 @@ package utils import ( "fmt" + "io/ioutil" "net/http" "net/url" "strconv" + "strings" + client "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" auth "github.com/cosmos/cosmos-sdk/x/auth" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" @@ -16,6 +21,9 @@ const ( queryArgGenerateOnly = "generate_only" ) +//---------------------------------------- +// Basic HTTP utilities + // WriteErrorResponse prepares and writes a HTTP error // given a status code and an error message. func WriteErrorResponse(w http.ResponseWriter, status int, msg string) { @@ -30,24 +38,45 @@ func WriteSimulationResponse(w http.ResponseWriter, gas int64) { w.Write([]byte(fmt.Sprintf(`{"gas_estimate":%v}`, gas))) } -// HasDryRunArg returns true if the request's URL query contains -// the dry run argument and its value is set to "true". -func HasDryRunArg(r *http.Request) bool { return urlQueryHasArg(r.URL, queryArgDryRun) } +// HasDryRunArg returns true if the request's URL query contains the dry run +// argument and its value is set to "true". +func HasDryRunArg(r *http.Request) bool { + return urlQueryHasArg(r.URL, queryArgDryRun) +} -// HasGenerateOnlyArg returns whether a URL's query "generate-only" parameter is set to "true". -func HasGenerateOnlyArg(r *http.Request) bool { return urlQueryHasArg(r.URL, queryArgGenerateOnly) } +// HasGenerateOnlyArg returns whether a URL's query "generate-only" parameter +// is set to "true". +func HasGenerateOnlyArg(r *http.Request) bool { + return urlQueryHasArg(r.URL, queryArgGenerateOnly) +} -// ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a default -// value if the string is empty. Write +// ParseInt64OrReturnBadRequest converts s to a int64 value. +func ParseInt64OrReturnBadRequest(w http.ResponseWriter, s string) (n int64, ok bool) { + var err error + + n, err = strconv.ParseInt(s, 10, 64) + if err != nil { + err := fmt.Errorf("'%s' is not a valid int64", 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) { if len(s) == 0 { return defaultIfEmpty, true } + n, err := strconv.ParseFloat(s, 64) if err != nil { WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return n, false } + return n, true } @@ -58,13 +87,164 @@ func WriteGenerateStdTxResponse(w http.ResponseWriter, txBldr authtxb.TxBuilder, WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } + output, err := txBldr.Codec.MarshalJSON(auth.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo)) if err != nil { WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } + w.Write(output) return } func urlQueryHasArg(url *url.URL, arg string) bool { return url.Query().Get(arg) == "true" } + +//---------------------------------------- +// Building / Sending utilities + +// BaseReq defines a structure that can be embedded in other request structures +// that all share common "base" fields. +type BaseReq struct { + Name string `json:"name"` + Password string `json:"password"` + ChainID string `json:"chain_id"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` + Gas string `json:"gas"` + GasAdjustment string `json:"gas_adjustment"` +} + +// Sanitize performs basic sanitization on a BaseReq object. +func (br BaseReq) Sanitize() BaseReq { + return BaseReq{ + Name: strings.TrimSpace(br.Name), + Password: strings.TrimSpace(br.Password), + ChainID: strings.TrimSpace(br.ChainID), + Gas: strings.TrimSpace(br.Gas), + GasAdjustment: strings.TrimSpace(br.GasAdjustment), + AccountNumber: br.AccountNumber, + Sequence: br.Sequence, + } +} + +/* +ReadRESTReq is a simple convenience wrapper that reads the body and +unmarshals to the req interface. + + Usage: + type SomeReq struct { + BaseReq `json:"base_req"` + CustomField string `json:"custom_field"` + } + + req := new(SomeReq) + err := ReadRESTReq(w, r, cdc, req) +*/ +func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req interface{}) error { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return err + } + + err = cdc.UnmarshalJSON(body, req) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return err + } + + return nil +} + +// ValidateBasic performs basic validation of a BaseReq. If custom validation +// logic is needed, the implementing request handler should perform those +// checks manually. +func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool { + switch { + case len(br.Name) == 0: + WriteErrorResponse(w, http.StatusUnauthorized, "name required but not specified") + return false + + case len(br.Password) == 0: + WriteErrorResponse(w, http.StatusUnauthorized, "password required but not specified") + return false + + case len(br.ChainID) == 0: + WriteErrorResponse(w, http.StatusUnauthorized, "chainID required but not specified") + return false + } + + return true +} + +// CompleteAndBroadcastTxREST implements a utility function that facilitates +// sending a series of messages in a signed transaction given a TxBuilder and a +// QueryContext. It ensures that the account exists, has a proper number and +// sequence set. In addition, it builds and signs a transaction with the +// supplied messages. Finally, it broadcasts the signed transaction to a node. +// +// NOTE: Also see CompleteAndBroadcastTxCli. +// NOTE: Also see x/stake/client/rest/tx.go delegationsRequestHandlerFn. +func CompleteAndBroadcastTxREST(w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext, baseReq BaseReq, msgs []sdk.Msg, cdc *codec.Codec) { + simulateGas, gas, err := client.ReadGasFlag(baseReq.Gas) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + adjustment, ok := ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) + if !ok { + return + } + + txBldr := authtxb.TxBuilder{ + Codec: cdc, + Gas: gas, + GasAdjustment: adjustment, + SimulateGas: simulateGas, + ChainID: baseReq.ChainID, + AccountNumber: baseReq.AccountNumber, + Sequence: baseReq.Sequence, + } + + if HasDryRunArg(r) || txBldr.SimulateGas { + newBldr, err := EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, msgs) + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + if HasDryRunArg(r) { + WriteSimulationResponse(w, newBldr.Gas) + return + } + + txBldr = newBldr + } + + if HasGenerateOnlyArg(r) { + WriteGenerateStdTxResponse(w, txBldr, msgs) + return + } + + txBytes, err := txBldr.BuildAndSign(baseReq.Name, baseReq.Password, msgs) + if err != nil { + WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) + return + } + + res, err := cliCtx.BroadcastTx(txBytes) + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + output, err := codec.MarshalJSONIndent(cdc, res) + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + w.Write(output) +} diff --git a/client/utils/utils.go b/client/utils/utils.go index 54d9dd584..9e72c552d 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -8,25 +8,33 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" - auth "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" - amino "github.com/tendermint/go-amino" + "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/libs/common" ) -// SendTx implements a auxiliary handler that facilitates sending a series of -// messages in a signed transaction given a TxBuilder and a QueryContext. It -// ensures that the account exists, has a proper number and sequence set. In -// addition, it builds and signs a transaction with the supplied messages. -// Finally, it broadcasts the signed transaction to a node. -func SendTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error { - txBldr, err := prepareTxContext(txBldr, cliCtx) +// CompleteAndBroadcastTxCli implements a utility function that +// facilitates sending a series of messages in a signed +// transaction given a TxBuilder and a QueryContext. It ensures +// that the account exists, has a proper number and sequence +// set. In addition, it builds and signs a transaction with the +// supplied messages. Finally, it broadcasts the signed +// transaction to a node. +// NOTE: Also see CompleteAndBroadcastTxREST. +func CompleteAndBroadcastTxCli(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error { + txBldr, err := prepareTxBuilder(txBldr, cliCtx) if err != nil { return err } - autogas := cliCtx.DryRun || (cliCtx.Gas == 0) - if autogas { - txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, cliCtx.FromAddressName, msgs) + + name, err := cliCtx.GetFromName() + if err != nil { + return err + } + + if txBldr.SimulateGas || cliCtx.DryRun { + txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs) if err != nil { return err } @@ -36,34 +44,25 @@ func SendTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) return nil } - passphrase, err := keys.GetPassphrase(cliCtx.FromAddressName) + passphrase, err := keys.GetPassphrase(name) if err != nil { return err } // build and sign the transaction - txBytes, err := txBldr.BuildAndSign(cliCtx.FromAddressName, passphrase, msgs) + txBytes, err := txBldr.BuildAndSign(name, passphrase, msgs) if err != nil { return err } // broadcast to a Tendermint node - return cliCtx.EnsureBroadcastTx(txBytes) -} - -// SimulateMsgs simulates the transaction and returns the gas estimate and the adjusted value. -func SimulateMsgs(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, msgs []sdk.Msg, gas int64) (estimated, adjusted int64, err error) { - txBytes, err := txBldr.WithGas(gas).BuildWithPubKey(name, msgs) - if err != nil { - return - } - estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, cliCtx.GasAdjustment) - return + _, err = cliCtx.BroadcastTx(txBytes) + return err } // EnrichCtxWithGas calculates the gas estimate that would be consumed by the // transaction and set the transaction's respective value accordingly. func EnrichCtxWithGas(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, msgs []sdk.Msg) (authtxb.TxBuilder, error) { - _, adjusted, err := SimulateMsgs(txBldr, cliCtx, name, msgs, 0) + _, adjusted, err := simulateMsgs(txBldr, cliCtx, name, msgs) if err != nil { return txBldr, err } @@ -143,6 +142,17 @@ func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, return txBldr.SignStdTx(name, passphrase, stdTx, appendSig) } +// nolint +// SimulateMsgs simulates the transaction and returns the gas estimate and the adjusted value. +func simulateMsgs(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, msgs []sdk.Msg) (estimated, adjusted int64, err error) { + txBytes, err := txBldr.BuildWithPubKey(name, msgs) + if err != nil { + return + } + estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, txBldr.GasAdjustment) + return +} + func adjustGasEstimate(estimate int64, adjustment float64) int64 { return int64(adjustment * float64(estimate)) } @@ -155,7 +165,7 @@ func parseQueryResponse(cdc *amino.Codec, rawRes []byte) (int64, error) { return simulationResult.GasUsed, nil } -func prepareTxContext(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (authtxb.TxBuilder, error) { +func prepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (authtxb.TxBuilder, error) { if err := cliCtx.EnsureAccountExists(); err != nil { return txBldr, err } @@ -190,12 +200,18 @@ func prepareTxContext(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (auth // buildUnsignedStdTx builds a StdTx as per the parameters passed in the // contexts. Gas is automatically estimated if gas wanted is set to 0. func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) { - txBldr, err = prepareTxContext(txBldr, cliCtx) + txBldr, err = prepareTxBuilder(txBldr, cliCtx) if err != nil { return } - if txBldr.Gas == 0 { - txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, cliCtx.FromAddressName, msgs) + if txBldr.SimulateGas { + var name string + name, err = cliCtx.GetFromName() + if err != nil { + return + } + + txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs) if err != nil { return } diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 14ed1b2c1..31b912a9d 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -12,12 +12,11 @@ import ( tmtypes "github.com/tendermint/tendermint/types" bam "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/gov" - "github.com/cosmos/cosmos-sdk/x/ibc" "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" @@ -36,13 +35,13 @@ var ( // Extended ABCI application type GaiaApp struct { *bam.BaseApp - cdc *wire.Codec + cdc *codec.Codec // keys to access the substores keyMain *sdk.KVStoreKey keyAccount *sdk.KVStoreKey - keyIBC *sdk.KVStoreKey keyStake *sdk.KVStoreKey + tkeyStake *sdk.TransientStoreKey keySlashing *sdk.KVStoreKey keyGov *sdk.KVStoreKey keyFeeCollection *sdk.KVStoreKey @@ -53,7 +52,6 @@ type GaiaApp struct { accountMapper auth.AccountMapper feeCollectionKeeper auth.FeeCollectionKeeper bankKeeper bank.Keeper - ibcMapper ibc.Mapper stakeKeeper stake.Keeper slashingKeeper slashing.Keeper govKeeper gov.Keeper @@ -72,8 +70,8 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio cdc: cdc, keyMain: sdk.NewKVStoreKey("main"), keyAccount: sdk.NewKVStoreKey("acc"), - keyIBC: sdk.NewKVStoreKey("ibc"), keyStake: sdk.NewKVStoreKey("stake"), + tkeyStake: sdk.NewTransientStoreKey("transient_stake"), keySlashing: sdk.NewKVStoreKey("slashing"), keyGov: sdk.NewKVStoreKey("gov"), keyFeeCollection: sdk.NewKVStoreKey("fee"), @@ -89,33 +87,33 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio ) // add handlers - app.bankKeeper = bank.NewKeeper(app.accountMapper) - app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace)) + app.bankKeeper = bank.NewBaseKeeper(app.accountMapper) app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams) - app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.bankKeeper, app.RegisterCodespace(stake.DefaultCodespace)) + app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.tkeyStake, app.bankKeeper, app.RegisterCodespace(stake.DefaultCodespace)) app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace)) - app.stakeKeeper = app.stakeKeeper.WithValidatorHooks(app.slashingKeeper.ValidatorHooks()) + app.stakeKeeper = app.stakeKeeper.WithHooks(app.slashingKeeper.Hooks()) app.govKeeper = gov.NewKeeper(app.cdc, app.keyGov, app.paramsKeeper.Setter(), app.bankKeeper, app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace)) app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.keyFeeCollection) // register message routes app.Router(). AddRoute("bank", bank.NewHandler(app.bankKeeper)). - AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.bankKeeper)). AddRoute("stake", stake.NewHandler(app.stakeKeeper)). AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)). AddRoute("gov", gov.NewHandler(app.govKeeper)) app.QueryRouter(). - AddRoute("gov", gov.NewQuerier(app.govKeeper)) + AddRoute("gov", gov.NewQuerier(app.govKeeper)). + AddRoute("stake", stake.NewQuerier(app.stakeKeeper, app.cdc)) // initialize BaseApp app.SetInitChainer(app.initChainer) app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) - app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams) - app.MountStore(app.tkeyParams, sdk.StoreTypeTransient) + 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()) @@ -125,16 +123,15 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio } // custom tx codec -func MakeCodec() *wire.Codec { - var cdc = wire.NewCodec() - ibc.RegisterWire(cdc) - bank.RegisterWire(cdc) - stake.RegisterWire(cdc) - slashing.RegisterWire(cdc) - gov.RegisterWire(cdc) - auth.RegisterWire(cdc) - sdk.RegisterWire(cdc) - wire.RegisterCrypto(cdc) +func MakeCodec() *codec.Codec { + var cdc = codec.New() + bank.RegisterCodec(cdc) + stake.RegisterCodec(cdc) + slashing.RegisterCodec(cdc) + gov.RegisterCodec(cdc) + auth.RegisterCodec(cdc) + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) return cdc } @@ -219,7 +216,7 @@ func (app *GaiaApp) ExportAppStateAndValidators() (appState json.RawMessage, val StakeData: stake.WriteGenesis(ctx, app.stakeKeeper), GovData: gov.WriteGenesis(ctx, app.govKeeper), } - appState, err = wire.MarshalJSONIndent(app.cdc, genState) + appState, err = codec.MarshalJSONIndent(app.cdc, genState) if err != nil { return nil, nil, err } diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index dd53f495c..bcd16a176 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -4,7 +4,7 @@ import ( "os" "testing" - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/stake" "github.com/stretchr/testify/require" @@ -25,7 +25,7 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error { StakeData: stake.DefaultGenesisState(), } - stateBytes, err := wire.MarshalJSONIndent(gapp.cdc, genesisState) + stateBytes, err := codec.MarshalJSONIndent(gapp.cdc, genesisState) if err != nil { return err } diff --git a/cmd/gaia/app/benchmarks/txsize_test.go b/cmd/gaia/app/benchmarks/txsize_test.go new file mode 100644 index 000000000..83e51c041 --- /dev/null +++ b/cmd/gaia/app/benchmarks/txsize_test.go @@ -0,0 +1,35 @@ +package app + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/tendermint/tendermint/crypto/secp256k1" +) + +// This will fail half the time with the second output being 173 +// This is due to secp256k1 signatures not being constant size. +// This will be resolved when updating to tendermint v0.24.0 +// nolint: vet +func ExampleTxSendSize() { + cdc := app.MakeCodec() + priv1 := secp256k1.GenPrivKeySecp256k1([]byte{0}) + addr1 := sdk.AccAddress(priv1.PubKey().Address()) + priv2 := secp256k1.GenPrivKeySecp256k1([]byte{1}) + addr2 := sdk.AccAddress(priv2.PubKey().Address()) + coins := []sdk.Coin{sdk.NewCoin("denom", sdk.NewInt(10))} + msg1 := bank.MsgSend{ + Inputs: []bank.Input{bank.NewInput(addr1, coins)}, + Outputs: []bank.Output{bank.NewOutput(addr2, coins)}, + } + sig, _ := priv1.Sign(msg1.GetSignBytes()) + sigs := []auth.StdSignature{auth.StdSignature{nil, sig, 0, 0}} + tx := auth.NewStdTx([]sdk.Msg{msg1}, auth.NewStdFee(0, coins...), sigs, "") + fmt.Println(len(cdc.MustMarshalBinaryBare([]sdk.Msg{msg1}))) + fmt.Println(len(cdc.MustMarshalBinaryBare(tx))) + // output: 80 + // 173 +} diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 4967361b1..84f0ea7fa 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -6,13 +6,14 @@ import ( "fmt" "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/wire" "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/spf13/pflag" @@ -91,7 +92,7 @@ type GaiaGenTx struct { // GaiaAppGenTx generates a Gaia genesis transaction. func GaiaAppGenTx( - cdc *wire.Codec, pk crypto.PubKey, genTxConfig config.GenTx, + 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)") @@ -135,7 +136,7 @@ func GaiaAppGenTx( } // Generate a gaia genesis transaction without flags -func GaiaAppGenTxNF(cdc *wire.Codec, pk crypto.PubKey, addr sdk.AccAddress, name string) ( +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 @@ -144,7 +145,7 @@ func GaiaAppGenTxNF(cdc *wire.Codec, pk crypto.PubKey, addr sdk.AccAddress, name Address: addr, PubKey: sdk.MustBech32ifyConsPub(pk), } - bz, err = wire.MarshalJSONIndent(cdc, gaiaGenTx) + bz, err = codec.MarshalJSONIndent(cdc, gaiaGenTx) if err != nil { return } @@ -159,7 +160,7 @@ func GaiaAppGenTxNF(cdc *wire.Codec, pk crypto.PubKey, addr sdk.AccAddress, name // Create the core parameters for genesis initialization for gaia // note that the pubkey input is this machines pubkey -func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState GenesisState, err error) { +func GaiaAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (genesisState GenesisState, err error) { if len(appGenTxs) == 0 { err = errors.New("must provide at least genesis transaction") @@ -233,7 +234,7 @@ func genesisAccountFromGenTx(genTx GaiaGenTx) GenesisAccount { } // GaiaValidateGenesisState ensures that the genesis state obeys the expected invariants -// TODO: No validators are both bonded and revoked (#2088) +// TODO: No validators are both bonded and jailed (#2088) // TODO: Error if there is a duplicate validator (#1708) // TODO: Ensure all state machine parameters are in genesis (#1704) func GaiaValidateGenesisState(genesisState GenesisState) (err error) { @@ -241,6 +242,26 @@ func GaiaValidateGenesisState(genesisState GenesisState) (err error) { if err != nil { return } + err = validateGenesisStateValidators(genesisState.StakeData.Validators) + if err != nil { + return + } + return +} + +func validateGenesisStateValidators(validators []stakeTypes.Validator) (err error) { + addrMap := make(map[string]bool, len(validators)) + for i := 0; i < len(validators); i++ { + val := validators[i] + strKey := string(val.ConsPubKey.Bytes()) + if _, ok := addrMap[strKey]; ok { + return fmt.Errorf("Duplicate validator in genesis state: moniker %v, Address %v", val.Description.Moniker, val.ConsAddress()) + } + 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()) + } + addrMap[strKey] = true + } return } @@ -259,13 +280,13 @@ func validateGenesisStateAccounts(accs []GenesisAccount) (err error) { } // GaiaAppGenState but with JSON -func GaiaAppGenStateJSON(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { +func GaiaAppGenStateJSON(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { // create the final app state genesisState, err := GaiaAppGenState(cdc, appGenTxs) if err != nil { return nil, err } - appState, err = wire.MarshalJSONIndent(cdc, genesisState) + appState, err = codec.MarshalJSONIndent(cdc, genesisState) return } diff --git a/cmd/gaia/app/genesis_test.go b/cmd/gaia/app/genesis_test.go index 755a90ebe..9cbd1f49f 100644 --- a/cmd/gaia/app/genesis_test.go +++ b/cmd/gaia/app/genesis_test.go @@ -6,11 +6,25 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/gov" - stake "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/stake" + stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" ) +var ( + pk1 = ed25519.GenPrivKey().PubKey() + pk2 = ed25519.GenPrivKey().PubKey() + pk3 = ed25519.GenPrivKey().PubKey() + addr1 = sdk.ValAddress(pk1.Address()) + addr2 = sdk.ValAddress(pk2.Address()) + addr3 = sdk.ValAddress(pk3.Address()) + + emptyAddr sdk.ValAddress + emptyPubkey crypto.PubKey +) + func makeGenesisState(genTxs []GaiaGenTx) GenesisState { // start with the default staking genesis state stakeData := stake.DefaultGenesisState() @@ -63,13 +77,27 @@ func TestGaiaAppGenState(t *testing.T) { func TestGaiaGenesisValidation(t *testing.T) { genTxs := make([]GaiaGenTx, 2) - privKey := ed25519.GenPrivKey() - pubKey := privKey.PubKey() - addr := pubKey.Address() + addr := pk1.Address() // Test duplicate accounts fails genTxs[0] = GaiaGenTx{"", sdk.AccAddress(addr), ""} genTxs[1] = GaiaGenTx{"", sdk.AccAddress(addr), ""} genesisState := makeGenesisState(genTxs) err := GaiaValidateGenesisState(genesisState) require.NotNil(t, err) + // Test bonded + jailed validator fails + genesisState = makeGenesisState(genTxs[:1]) + val1 := stakeTypes.NewValidator(addr1, pk1, stakeTypes.Description{Moniker: "test #2"}) + val1.Jailed = true + val1.Status = sdk.Bonded + genesisState.StakeData.Validators = append(genesisState.StakeData.Validators, val1) + err = GaiaValidateGenesisState(genesisState) + require.NotNil(t, err) + // Test duplicate validator fails + val1.Jailed = false + genesisState = makeGenesisState(genTxs[:1]) + 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) } diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 97de59a23..6097b5a08 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -10,11 +10,9 @@ import ( "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" - "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation" "github.com/cosmos/cosmos-sdk/x/gov" @@ -43,14 +41,14 @@ func init() { flag.BoolVar(&commit, "SimulationCommit", false, "Have the simulation commit") } -func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage { +func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { var genesisAccounts []GenesisAccount // Randomly generate some genesis accounts for _, acc := range accs { coins := sdk.Coins{sdk.Coin{"steak", sdk.NewInt(100)}} genesisAccounts = append(genesisAccounts, GenesisAccount{ - Address: acc, + Address: acc.Address, Coins: coins, }) } @@ -62,10 +60,10 @@ func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json // XXX Try different numbers of initially bonded validators numInitiallyBonded := int64(50) for i := 0; i < int(numInitiallyBonded); i++ { - validator := stake.NewValidator(sdk.ValAddress(accs[i]), keys[i].PubKey(), stake.Description{}) + 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], sdk.ValAddress(accs[i]), sdk.NewDec(100), 0} + delegation := stake.Delegation{accs[i].Address, sdk.ValAddress(accs[i].Address), sdk.NewDec(100), 0} validators = append(validators, validator) delegations = append(delegations, delegation) } @@ -90,30 +88,28 @@ func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json return appState } -func testAndRunTxs(app *GaiaApp) []simulation.Operation { - return []simulation.Operation{ - banksim.SimulateSingleInputMsgSend(app.accountMapper), - govsim.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, app.stakeKeeper), - govsim.SimulateMsgDeposit(app.govKeeper, app.stakeKeeper), - stakesim.SimulateMsgCreateValidator(app.accountMapper, app.stakeKeeper), - stakesim.SimulateMsgEditValidator(app.stakeKeeper), - stakesim.SimulateMsgDelegate(app.accountMapper, app.stakeKeeper), - stakesim.SimulateMsgBeginUnbonding(app.accountMapper, app.stakeKeeper), - stakesim.SimulateMsgCompleteUnbonding(app.stakeKeeper), - stakesim.SimulateMsgBeginRedelegate(app.accountMapper, app.stakeKeeper), - stakesim.SimulateMsgCompleteRedelegate(app.stakeKeeper), - slashingsim.SimulateMsgUnjail(app.slashingKeeper), +func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation { + return []simulation.WeightedOperation{ + {100, banksim.SimulateSingleInputMsgSend(app.accountMapper)}, + {5, govsim.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, app.stakeKeeper)}, + {100, govsim.SimulateMsgDeposit(app.govKeeper, app.stakeKeeper)}, + {100, stakesim.SimulateMsgCreateValidator(app.accountMapper, app.stakeKeeper)}, + {5, stakesim.SimulateMsgEditValidator(app.stakeKeeper)}, + {100, stakesim.SimulateMsgDelegate(app.accountMapper, app.stakeKeeper)}, + {100, stakesim.SimulateMsgBeginUnbonding(app.accountMapper, app.stakeKeeper)}, + {100, stakesim.SimulateMsgCompleteUnbonding(app.stakeKeeper)}, + {100, stakesim.SimulateMsgBeginRedelegate(app.accountMapper, app.stakeKeeper)}, + {100, stakesim.SimulateMsgCompleteRedelegate(app.stakeKeeper)}, + {100, slashingsim.SimulateMsgUnjail(app.slashingKeeper)}, } } func invariants(app *GaiaApp) []simulation.Invariant { return []simulation.Invariant{ - func(t *testing.T, baseapp *baseapp.BaseApp, log string) { - banksim.NonnegativeBalanceInvariant(app.accountMapper)(t, baseapp, log) - govsim.AllInvariants()(t, baseapp, log) - stakesim.AllInvariants(app.bankKeeper, app.stakeKeeper, app.accountMapper)(t, baseapp, log) - slashingsim.AllInvariants()(t, baseapp, log) - }, + banksim.NonnegativeBalanceInvariant(app.accountMapper), + govsim.AllInvariants(), + stakesim.AllInvariants(app.bankKeeper, app.stakeKeeper, app.accountMapper), + slashingsim.AllInvariants(), } } @@ -134,7 +130,7 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { // Run randomized simulation // TODO parameterize numbers, save for a later PR - simulation.SimulateFromSeed( + err := simulation.SimulateFromSeed( b, app.BaseApp, appStateFn, seed, testAndRunTxs(app), []simulation.RandSetup{}, @@ -143,6 +139,10 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { blockSize, commit, ) + if err != nil { + fmt.Println(err) + b.Fail() + } if commit { fmt.Println("GoLevelDB Stats") fmt.Println(db.Stats()["leveldb.stats"]) @@ -167,7 +167,7 @@ func TestFullGaiaSimulation(t *testing.T) { require.Equal(t, "GaiaApp", app.Name()) // Run randomized simulation - simulation.SimulateFromSeed( + err := simulation.SimulateFromSeed( t, app.BaseApp, appStateFn, seed, testAndRunTxs(app), []simulation.RandSetup{}, @@ -179,6 +179,7 @@ func TestFullGaiaSimulation(t *testing.T) { if commit { fmt.Println("Database Size", db.Stats()["database.size"]) } + require.Nil(t, err) } // TODO: Make another test for the fuzzer itself, which just has noOp txs @@ -207,9 +208,9 @@ func TestAppStateDeterminism(t *testing.T) { []simulation.Invariant{}, 50, 100, - false, + true, ) - app.Commit() + //app.Commit() appHash := app.LastCommitID().Hash appHashList[j] = appHash } diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 032482e7b..5b204f4df 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "os" "testing" + "path" "github.com/stretchr/testify/require" @@ -18,10 +19,10 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/stake" @@ -36,6 +37,72 @@ func init() { gaiadHome, gaiacliHome = getTestingHomeDirs() } +func TestGaiaCLIMinimumFees(t *testing.T) { + chainID, servAddr, port := initializeFixtures(t) + flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) + + // start gaiad server with minimum fees + proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v --minimum_fees=2feeToken", gaiadHome, servAddr)) + + defer proc.Stop(false) + tests.WaitForTMStart(port) + tests.WaitForNextNBlocksTM(2, port) + + fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) + 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()) + + success := executeWrite(t, fmt.Sprintf( + "gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + require.False(t, success) + tests.WaitForNextNBlocksTM(2, port) + +} + +func TestGaiaCLIFeesDeduction(t *testing.T) { + chainID, servAddr, port := initializeFixtures(t) + flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) + + // start gaiad server with minimum fees + proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v --minimum_fees=1fooToken", gaiadHome, servAddr)) + + defer proc.Stop(false) + tests.WaitForTMStart(port) + tests.WaitForNextNBlocksTM(2, port) + + fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) + 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(1000), fooAcc.GetCoins().AmountOf("fooToken").Int64()) + + // test simulation + success := executeWrite(t, fmt.Sprintf( + "gaiacli tx send %v --amount=1000fooToken --to=%s --from=foo --fee=1fooToken --dry-run", flags, barAddr), app.DefaultKeyPass) + require.True(t, success) + tests.WaitForNextNBlocksTM(2, port) + // ensure state didn't change + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) + require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf("fooToken").Int64()) + + // insufficient funds (coins + fees) + success = executeWrite(t, fmt.Sprintf( + "gaiacli tx send %v --amount=1000fooToken --to=%s --from=foo --fee=1fooToken", flags, barAddr), app.DefaultKeyPass) + require.False(t, success) + tests.WaitForNextNBlocksTM(2, port) + // ensure state didn't change + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) + require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf("fooToken").Int64()) + + // test success (transfer = coins + fees) + success = executeWrite(t, fmt.Sprintf( + "gaiacli tx send %v --fee=300fooToken --amount=500fooToken --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + require.True(t, success) + tests.WaitForNextNBlocksTM(2, port) +} + func TestGaiaCLISend(t *testing.T) { chainID, servAddr, port := initializeFixtures(t) flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) @@ -50,40 +117,40 @@ func TestGaiaCLISend(t *testing.T) { fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) require.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak").Int64()) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) // Test --dry-run - success := executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo --dry-run", flags, barAddr), app.DefaultKeyPass) + success := executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo --dry-run", flags, barAddr), app.DefaultKeyPass) require.True(t, success) // Check state didn't change - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) // test autosequencing - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) require.Equal(t, int64(20), barAcc.GetCoins().AmountOf("steak").Int64()) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(30), fooAcc.GetCoins().AmountOf("steak").Int64()) // test memo - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo --memo 'testmemo'", flags, barAddr), app.DefaultKeyPass) + executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo --memo 'testmemo'", flags, barAddr), app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) require.Equal(t, int64(30), barAcc.GetCoins().AmountOf("steak").Int64()) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(20), fooAcc.GetCoins().AmountOf("steak").Int64()) } @@ -101,19 +168,27 @@ func TestGaiaCLIGasAuto(t *testing.T) { fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) // Test failure with auto gas disabled and very little gas set by hand - success := executeWrite(t, fmt.Sprintf("gaiacli 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=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) require.False(t, success) tests.WaitForNextNBlocksTM(2, port) // Check state didn't change - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").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) + 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) + require.False(t, success) + // Enable auto gas - success, stdout, _ := executeWriteRetStdStreams(t, fmt.Sprintf("gaiacli send %v --json --gas=0 --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + success, stdout, _ := executeWriteRetStdStreams(t, fmt.Sprintf("gaiacli tx send %v --json --gas=simulate --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) require.True(t, success) // check that gas wanted == gas used cdc := app.MakeCodec() @@ -126,7 +201,7 @@ func TestGaiaCLIGasAuto(t *testing.T) { require.Equal(t, jsonOutput.Response.GasWanted, jsonOutput.Response.GasUsed) tests.WaitForNextNBlocksTM(2, port) // Check state has changed accordingly - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) } @@ -145,12 +220,12 @@ 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 send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) require.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak").Int64()) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) defaultParams := stake.DefaultParams() @@ -159,11 +234,14 @@ func TestGaiaCLICreateValidator(t *testing.T) { initialPool = initialPool.ProcessProvisions(defaultParams) // provisions are added to the pool every hour // create validator - cvStr := fmt.Sprintf("gaiacli stake create-validator %v", flags) + cvStr := fmt.Sprintf("gaiacli tx 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(" --moniker=%v", "bar-vally") + cvStr += fmt.Sprintf(" --commission-rate=%v", "0.05") + cvStr += fmt.Sprintf(" --commission-max-rate=%v", "0.20") + cvStr += fmt.Sprintf(" --commission-max-change-rate=%v", "0.10") initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewDec(1)) @@ -184,15 +262,15 @@ func TestGaiaCLICreateValidator(t *testing.T) { executeWrite(t, cvStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) require.Equal(t, int64(8), barAcc.GetCoins().AmountOf("steak").Int64(), "%v", barAcc) - validator := executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %s --output=json %v", sdk.ValAddress(barAddr), flags)) + validator := executeGetValidator(t, fmt.Sprintf("gaiacli query 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)) // unbond a single share - unbondStr := fmt.Sprintf("gaiacli stake unbond begin %v", flags) + unbondStr := fmt.Sprintf("gaiacli tx 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") @@ -202,16 +280,16 @@ func TestGaiaCLICreateValidator(t *testing.T) { tests.WaitForNextNBlocksTM(2, port) /* // this won't be what we expect because we've only started unbonding, haven't completed - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) + barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %v %v", barCech, flags)) require.Equal(t, int64(9), barAcc.GetCoins().AmountOf("steak").Int64(), "%v", barAcc) */ - validator = executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %s --output=json %v", sdk.ValAddress(barAddr), flags)) + validator = executeGetValidator(t, fmt.Sprintf("gaiacli query validator %s --output=json %v", sdk.ValAddress(barAddr), flags)) require.Equal(t, "1.0000000000", validator.Tokens.String()) - params := executeGetParams(t, fmt.Sprintf("gaiacli stake parameters --output=json %v", flags)) + params := executeGetParams(t, fmt.Sprintf("gaiacli query parameters --output=json %v", flags)) require.True(t, defaultParams.Equal(params)) - pool := executeGetPool(t, fmt.Sprintf("gaiacli stake pool --output=json %v", flags)) + 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) require.Equal(t, initialPool.BondedTokens, pool.BondedTokens) @@ -230,14 +308,14 @@ func TestGaiaCLISubmitProposal(t *testing.T) { fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) - proposalsQuery := tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals %v", flags), "") + proposalsQuery := tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals %v", flags), "") require.Equal(t, "No matching proposals found", proposalsQuery) // submit a test proposal - spStr := fmt.Sprintf("gaiacli gov submit-proposal %v", flags) + spStr := fmt.Sprintf("gaiacli tx submit-proposal %v", flags) spStr += fmt.Sprintf(" --from=%s", "foo") spStr += fmt.Sprintf(" --deposit=%s", "5steak") spStr += fmt.Sprintf(" --type=%s", "Text") @@ -261,17 +339,17 @@ func TestGaiaCLISubmitProposal(t *testing.T) { executeWrite(t, spStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(45), fooAcc.GetCoins().AmountOf("steak").Int64()) - proposal1 := executeGetProposal(t, fmt.Sprintf("gaiacli gov query-proposal --proposal-id=1 --output=json %v", flags)) + 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, gov.StatusDepositPeriod, proposal1.GetStatus()) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals %v", flags), "") + proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals %v", flags), "") require.Equal(t, " 1 - Test", proposalsQuery) - depositStr := fmt.Sprintf("gaiacli gov deposit %v", flags) + depositStr := fmt.Sprintf("gaiacli tx deposit %v", flags) depositStr += fmt.Sprintf(" --from=%s", "foo") depositStr += fmt.Sprintf(" --deposit=%s", "10steak") depositStr += fmt.Sprintf(" --proposal-id=%s", "1") @@ -289,13 +367,13 @@ func TestGaiaCLISubmitProposal(t *testing.T) { executeWrite(t, depositStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + 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 gov query-proposal --proposal-id=1 --output=json %v", flags)) + 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, gov.StatusVotingPeriod, proposal1.GetStatus()) - voteStr := fmt.Sprintf("gaiacli gov vote %v", flags) + voteStr := fmt.Sprintf("gaiacli tx vote %v", flags) voteStr += fmt.Sprintf(" --from=%s", "foo") voteStr += fmt.Sprintf(" --proposal-id=%s", "1") voteStr += fmt.Sprintf(" --option=%s", "Yes") @@ -313,23 +391,23 @@ func TestGaiaCLISubmitProposal(t *testing.T) { executeWrite(t, voteStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - vote := executeGetVote(t, fmt.Sprintf("gaiacli gov query-vote --proposal-id=1 --voter=%s --output=json %v", fooAddr, flags)) + 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) require.Equal(t, gov.OptionYes, vote.Option) - votes := executeGetVotes(t, fmt.Sprintf("gaiacli gov query-votes --proposal-id=1 --output=json %v", flags)) + votes := executeGetVotes(t, fmt.Sprintf("gaiacli query votes --proposal-id=1 --output=json %v", flags)) require.Len(t, votes, 1) require.Equal(t, int64(1), votes[0].ProposalID) require.Equal(t, gov.OptionYes, votes[0].Option) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals --status=DepositPeriod %v", flags), "") + proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --status=DepositPeriod %v", flags), "") require.Equal(t, "No matching proposals found", proposalsQuery) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals --status=VotingPeriod %v", flags), "") + proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --status=VotingPeriod %v", flags), "") require.Equal(t, " 1 - Test", proposalsQuery) // submit a second test proposal - spStr = fmt.Sprintf("gaiacli gov submit-proposal %v", flags) + spStr = fmt.Sprintf("gaiacli tx submit-proposal %v", flags) spStr += fmt.Sprintf(" --from=%s", "foo") spStr += fmt.Sprintf(" --deposit=%s", "5steak") spStr += fmt.Sprintf(" --type=%s", "Text") @@ -339,7 +417,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { executeWrite(t, spStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals --latest=1 %v", flags), "") + proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --latest=1 %v", flags), "") require.Equal(t, " 2 - Apples", proposalsQuery) } @@ -359,7 +437,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test generate sendTx with default gas success, stdout, stderr := executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli send %v --amount=10steak --to=%s --from=foo --generate-only", + "gaiacli tx send %v --amount=10steak --to=%s --from=foo --generate-only", flags, barAddr), []string{}...) require.True(t, success) require.Empty(t, stderr) @@ -370,7 +448,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test generate sendTx with --gas=$amount success, stdout, stderr = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli send %v --amount=10steak --to=%s --from=foo --gas=100 --generate-only", + "gaiacli tx send %v --amount=10steak --to=%s --from=foo --gas=100 --generate-only", flags, barAddr), []string{}...) require.True(t, success) require.Empty(t, stderr) @@ -381,7 +459,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test generate sendTx, estimate gas success, stdout, stderr = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli send %v --amount=10steak --to=%s --from=foo --gas=0 --generate-only", + "gaiacli tx send %v --amount=10steak --to=%s --from=foo --gas=simulate --generate-only", flags, barAddr), []string{}...) require.True(t, success) require.NotEmpty(t, stderr) @@ -395,13 +473,13 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test sign --print-sigs success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli sign %v --print-sigs %v", flags, unsignedTxFile.Name())) + "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) // Test sign success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli sign %v --name=foo %v", flags, unsignedTxFile.Name()), app.DefaultKeyPass) + "gaiacli tx sign %v --name=foo %v", flags, unsignedTxFile.Name()), app.DefaultKeyPass) require.True(t, success) msg = unmarshalStdTx(t, stdout) require.Equal(t, len(msg.Msgs), 1) @@ -414,15 +492,15 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test sign --print-signatures success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli sign %v --print-sigs %v", flags, signedTxFile.Name())) + "gaiacli tx sign %v --print-sigs %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) // Test broadcast - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) - success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf("gaiacli 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 @@ -432,12 +510,54 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { require.Equal(t, msg.Fee.Gas, result.Response.GasWanted) tests.WaitForNextNBlocksTM(2, port) - barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) require.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak").Int64()) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) } +func TestGaiaCLIConfig(t *testing.T) { + require.NoError(t, os.RemoveAll(gaiacliHome)) + require.NoError(t, os.RemoveAll(gaiadHome)) + 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)) + 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" +trace = false +trust_node = true +`, chainID, gaiacliHome, node) + require.Equal(t, expectedConfig, string(config)) + // ensure a backup gets created + executeWrite(t, "gaiacli config", gaiacliHome, node, "y", "y") + configBackup, err := ioutil.ReadFile(path.Join(gaiacliHome, "config", "config.toml-old")) + require.NoError(t, err) + require.Equal(t, expectedConfig, string(configBackup)) + + require.NoError(t, os.RemoveAll(gaiadHome)) + executeWrite(t, "gaiacli config", gaiacliHome, node, "y") + + // ensure it works without an initialized gaiad state + expectedConfig = fmt.Sprintf(`chain_id = "" +encoding = "btc" +home = "%s" +node = "%s" +output = "text" +trace = false +trust_node = true +`, gaiacliHome, node) + config, err = ioutil.ReadFile(path.Join(gaiacliHome, "config", "config.toml")) + require.NoError(t, err) + require.Equal(t, expectedConfig, string(config)) +} + //___________________________________________________________________________________ // helper methods @@ -541,8 +661,8 @@ func executeGetAccount(t *testing.T, cmdStr string) auth.BaseAccount { require.NoError(t, err, "out %v, err %v", out, err) value := initRes["value"] var acc auth.BaseAccount - cdc := wire.NewCodec() - wire.RegisterCrypto(cdc) + cdc := codec.New() + codec.RegisterCrypto(cdc) err = cdc.UnmarshalJSON(value, &acc) require.NoError(t, err, "value %v, err %v", string(value), err) return acc diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index c216bb2eb..2f66caadd 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -14,11 +14,20 @@ import ( 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" - ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/client/cli" slashingcmd "github.com/cosmos/cosmos-sdk/x/slashing/client/cli" stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "path" + "os" + "github.com/spf13/viper" +) + +const ( + storeAcc = "acc" + storeGov = "gov" + storeSlashing = "slashing" + storeStake = "stake" ) // rootCmd is the entry point for this binary @@ -36,105 +45,76 @@ func main() { // 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 state commands - tendermintCmd := &cobra.Command{ - Use: "tendermint", - Short: "Tendermint state querying subcommands", + //Add query commands + queryCmd := &cobra.Command{ + Use: "query", + Aliases: []string{"q"}, + Short: "Querying subcommands", } - tendermintCmd.AddCommand( + queryCmd.AddCommand( rpc.BlockCommand(), rpc.ValidatorCommand(), ) - tx.AddCommands(tendermintCmd, cdc) + 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 IBC commands - ibcCmd := &cobra.Command{ - Use: "ibc", - Short: "Inter-Blockchain Communication subcommands", + //Add query commands + txCmd := &cobra.Command{ + Use: "tx", + Short: "Transactions subcommands", } - ibcCmd.AddCommand( + + //Add auth and bank commands + txCmd.AddCommand( client.PostCommands( - ibccmd.IBCTransferCmd(cdc), - ibccmd.IBCRelayCmd(cdc), + bankcmd.GetBroadcastCommand(cdc), + authcmd.GetSignCommand(cdc, authcmd.GetAccountDecoder(cdc)), )...) + txCmd.AddCommand(client.LineBreak) - rootCmd.AddCommand( - tendermintCmd, - ibcCmd, - lcd.ServeCommand(cdc), - client.LineBreak, - ) - - //Add stake commands - stakeCmd := &cobra.Command{ - Use: "stake", - Short: "Stake and validation subcommands", - } - stakeCmd.AddCommand( - client.GetCommands( - stakecmd.GetCmdQueryValidator("stake", cdc), - stakecmd.GetCmdQueryValidators("stake", cdc), - stakecmd.GetCmdQueryDelegation("stake", cdc), - stakecmd.GetCmdQueryDelegations("stake", cdc), - stakecmd.GetCmdQueryParams("stake", cdc), - stakecmd.GetCmdQueryPool("stake", cdc), - stakecmd.GetCmdQueryUnbondingDelegation("stake", cdc), - stakecmd.GetCmdQueryUnbondingDelegations("stake", cdc), - stakecmd.GetCmdQueryRedelegation("stake", cdc), - stakecmd.GetCmdQueryRedelegations("stake", cdc), - slashingcmd.GetCmdQuerySigningInfo("slashing", cdc), - )...) - stakeCmd.AddCommand( + txCmd.AddCommand( client.PostCommands( stakecmd.GetCmdCreateValidator(cdc), stakecmd.GetCmdEditValidator(cdc), stakecmd.GetCmdDelegate(cdc), - stakecmd.GetCmdUnbond("stake", cdc), - stakecmd.GetCmdRedelegate("stake", cdc), - slashingcmd.GetCmdUnjail(cdc), - )...) - rootCmd.AddCommand( - stakeCmd, - ) - - //Add stake commands - govCmd := &cobra.Command{ - Use: "gov", - Short: "Governance and voting subcommands", - } - govCmd.AddCommand( - client.GetCommands( - govcmd.GetCmdQueryProposal("gov", cdc), - govcmd.GetCmdQueryVote("gov", cdc), - govcmd.GetCmdQueryVotes("gov", cdc), - govcmd.GetCmdQueryProposals("gov", cdc), - )...) - govCmd.AddCommand( - client.PostCommands( - govcmd.GetCmdSubmitProposal(cdc), govcmd.GetCmdDeposit(cdc), + stakecmd.GetCmdRedelegate(storeStake, cdc), + bankcmd.SendTxCmd(cdc), + govcmd.GetCmdSubmitProposal(cdc), + stakecmd.GetCmdUnbond(storeStake, cdc), + slashingcmd.GetCmdUnjail(cdc), govcmd.GetCmdVote(cdc), )...) rootCmd.AddCommand( - govCmd, + queryCmd, + txCmd, + lcd.ServeCommand(cdc), + client.LineBreak, ) - //Add auth and bank commands - rootCmd.AddCommand( - client.GetCommands( - authcmd.GetAccountCmd("acc", cdc, authcmd.GetAccountDecoder(cdc)), - authcmd.GetSignCommand(cdc, authcmd.GetAccountDecoder(cdc)), - )...) - rootCmd.AddCommand( - client.PostCommands( - bankcmd.SendTxCmd(cdc), - bankcmd.GetBroadcastCommand(cdc), - )...) - // add proxy, version and key info rootCmd.AddCommand( keys.Commands(), @@ -144,9 +124,35 @@ func main() { // prepare and add flags executor := cli.PrepareMainCmd(rootCmd, "GA", app.DefaultCLIHome) - err := executor.Execute() + err := initConfig(rootCmd) + if err != nil { + panic(err) + } + + err = executor.Execute() if err != nil { // handle with #870 panic(err) } } + +func initConfig(cmd *cobra.Command) error { + home, err := cmd.PersistentFlags().GetString(cli.HomeFlag) + if err != nil { + return err + } + + cfgFile := path.Join(home, "config", "config.toml") + if _, err := os.Stat(cfgFile); err == nil { + viper.SetConfigFile(cfgFile) + + if err := viper.ReadInConfig(); err != nil { + return err + } + } + + if err := viper.BindPFlag(cli.EncodingFlag, cmd.PersistentFlags().Lookup(cli.EncodingFlag)); err != nil { + return err + } + return viper.BindPFlag(cli.OutputFlag, cmd.PersistentFlags().Lookup(cli.OutputFlag)) +} diff --git a/cmd/gaia/cmd/gaiad/main.go b/cmd/gaia/cmd/gaiad/main.go index aa5978407..0b5f0e505 100644 --- a/cmd/gaia/cmd/gaiad/main.go +++ b/cmd/gaia/cmd/gaiad/main.go @@ -43,7 +43,10 @@ func main() { } func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application { - return app.NewGaiaApp(logger, db, traceStore, baseapp.SetPruning(viper.GetString("pruning"))) + return app.NewGaiaApp(logger, db, traceStore, + baseapp.SetPruning(viper.GetString("pruning")), + baseapp.SetMinimumFees(viper.GetString("minimum_fees")), + ) } func exportAppStateAndTMValidators( diff --git a/cmd/gaia/cmd/gaiadebug/hack.go b/cmd/gaia/cmd/gaiadebug/hack.go index a321a5082..182e789be 100644 --- a/cmd/gaia/cmd/gaiadebug/hack.go +++ b/cmd/gaia/cmd/gaiadebug/hack.go @@ -21,10 +21,9 @@ import ( bam "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/cosmos/cosmos-sdk/x/ibc" "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" @@ -65,7 +64,7 @@ func runHackCmd(cmd *cobra.Command, args []string) error { // The following powerKey was there, but the corresponding "trouble" validator did not exist. // So here we do a binary search on the past states to find when the powerKey first showed up ... - // operator of the validator the bonds, gets revoked, later unbonds, and then later is still found in the bypower store + // operator of the validator the bonds, gets jailed, later unbonds, and then later is still found in the bypower store trouble := hexToBytes("D3DC0FF59F7C3B548B7AFA365561B87FD0208AF8") // this is his "bypower" key powerKey := hexToBytes("05303030303030303030303033FFFFFFFFFFFF4C0C0000FFFED3DC0FF59F7C3B548B7AFA365561B87FD0208AF8") @@ -127,13 +126,13 @@ var ( // Extended ABCI application type GaiaApp struct { *bam.BaseApp - cdc *wire.Codec + cdc *codec.Codec // keys to access the substores keyMain *sdk.KVStoreKey keyAccount *sdk.KVStoreKey - keyIBC *sdk.KVStoreKey keyStake *sdk.KVStoreKey + tkeyStake *sdk.TransientStoreKey keySlashing *sdk.KVStoreKey keyParams *sdk.KVStoreKey @@ -141,7 +140,6 @@ type GaiaApp struct { accountMapper auth.AccountMapper feeCollectionKeeper auth.FeeCollectionKeeper bankKeeper bank.Keeper - ibcMapper ibc.Mapper stakeKeeper stake.Keeper slashingKeeper slashing.Keeper paramsKeeper params.Keeper @@ -159,8 +157,8 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp cdc: cdc, keyMain: sdk.NewKVStoreKey("main"), keyAccount: sdk.NewKVStoreKey("acc"), - keyIBC: sdk.NewKVStoreKey("ibc"), keyStake: sdk.NewKVStoreKey("stake"), + tkeyStake: sdk.NewTransientStoreKey("transient_stake"), keySlashing: sdk.NewKVStoreKey("slashing"), keyParams: sdk.NewKVStoreKey("params"), } @@ -173,16 +171,14 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp ) // add handlers - app.bankKeeper = bank.NewKeeper(app.accountMapper) - app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace)) + app.bankKeeper = bank.NewBaseKeeper(app.accountMapper) app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams) - app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.bankKeeper, app.RegisterCodespace(stake.DefaultCodespace)) + app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.tkeyStake, app.bankKeeper, app.RegisterCodespace(stake.DefaultCodespace)) app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace)) // register message routes app.Router(). AddRoute("bank", bank.NewHandler(app.bankKeeper)). - AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.bankKeeper)). AddRoute("stake", stake.NewHandler(app.stakeKeeper)) // initialize BaseApp @@ -190,7 +186,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) - app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing) + app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyStake, app.keySlashing) err := app.LoadLatestVersion(app.keyMain) if err != nil { cmn.Exit(err.Error()) @@ -202,15 +198,14 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp } // custom tx codec -func MakeCodec() *wire.Codec { - var cdc = wire.NewCodec() - ibc.RegisterWire(cdc) - bank.RegisterWire(cdc) - stake.RegisterWire(cdc) - slashing.RegisterWire(cdc) - auth.RegisterWire(cdc) - sdk.RegisterWire(cdc) - wire.RegisterCrypto(cdc) +func MakeCodec() *codec.Codec { + var cdc = codec.New() + bank.RegisterCodec(cdc) + stake.RegisterCodec(cdc) + slashing.RegisterCodec(cdc) + auth.RegisterCodec(cdc) + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) cdc.Seal() return cdc } diff --git a/cmd/gaia/testnets/STATUS.md b/cmd/gaia/testnets/STATUS.md index b972eb802..4db429784 100644 --- a/cmd/gaia/testnets/STATUS.md +++ b/cmd/gaia/testnets/STATUS.md @@ -34,7 +34,7 @@ See [testnets repo](https://github.com/cosmos/testnets). ## *June 13, 2018, 17:00 EST* - Gaia-6002 is making blocks! - Gaia-6002 is live and making blocks -- Absent validators have been slashed and revoked +- Absent validators have been slashed and jailed - Currently live with 17 validators ## *June 13, 2018, 4:30 EST* - New Testnet Gaia-6002 diff --git a/wire/wire.go b/codec/codec.go similarity index 93% rename from wire/wire.go rename to codec/codec.go index 683149c99..175eb89c1 100644 --- a/wire/wire.go +++ b/codec/codec.go @@ -1,4 +1,4 @@ -package wire +package codec import ( "bytes" @@ -11,7 +11,7 @@ import ( // amino codec to marshal/unmarshal type Codec = amino.Codec -func NewCodec() *Codec { +func New() *Codec { cdc := amino.NewCodec() return cdc } @@ -42,7 +42,7 @@ func MarshalJSONIndent(cdc *Codec, obj interface{}) ([]byte, error) { var Cdc *Codec func init() { - cdc := NewCodec() + cdc := New() RegisterCrypto(cdc) Cdc = cdc.Seal() } diff --git a/crypto/keys/wire.go b/crypto/keys/codec.go similarity index 100% rename from crypto/keys/wire.go rename to crypto/keys/codec.go diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index 0bf7ad990..c9bfd0812 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -14,6 +14,7 @@ import ( "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/crypto/secp256k1" dbm "github.com/tendermint/tendermint/libs/db" + "github.com/cosmos/cosmos-sdk/types" ) var _ Keybase = dbKeybase{} @@ -41,6 +42,8 @@ const ( French // Italian is currently not supported. Italian + addressSuffix = "address" + infoSuffix = "info" ) var ( @@ -179,11 +182,16 @@ func (kb dbKeybase) List() ([]Info, error) { iter := kb.db.Iterator(nil, nil) defer iter.Close() for ; iter.Valid(); iter.Next() { - info, err := readInfo(iter.Value()) - if err != nil { - return nil, err + key := string(iter.Key()) + + // need to include only keys in storage that have an info suffix + if strings.HasSuffix(key, infoSuffix) { + info, err := readInfo(iter.Value()) + if err != nil { + return nil, err + } + res = append(res, info) } - res = append(res, info) } return res, nil } @@ -197,6 +205,15 @@ func (kb dbKeybase) Get(name string) (Info, error) { return readInfo(bs) } +func (kb dbKeybase) GetByAddress(address types.AccAddress) (Info, error) { + ik := kb.db.Get(addrKey(address)) + if len(ik) == 0 { + return nil, fmt.Errorf("key with address %s not found", address) + } + bs := kb.db.Get(ik) + return readInfo(bs) +} + // Sign signs the msg with the named key. // It returns an error if the key doesn't exist or the decryption fails. func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) { @@ -347,6 +364,7 @@ func (kb dbKeybase) Delete(name, passphrase string) error { if err != nil { return err } + kb.db.DeleteSync(addrKey(linfo.GetAddress())) kb.db.DeleteSync(infoKey(name)) return nil case ledgerInfo: @@ -354,9 +372,11 @@ func (kb dbKeybase) Delete(name, passphrase string) error { if passphrase != "yes" { return fmt.Errorf("enter 'yes' exactly to delete the key - this cannot be undone") } + kb.db.DeleteSync(addrKey(info.GetAddress())) kb.db.DeleteSync(infoKey(name)) return nil } + return nil } @@ -413,9 +433,16 @@ func (kb dbKeybase) writeOfflineKey(pub tmcrypto.PubKey, name string) Info { func (kb dbKeybase) writeInfo(info Info, name string) { // write the info by key - kb.db.SetSync(infoKey(name), writeInfo(info)) + key := infoKey(name) + kb.db.SetSync(key, writeInfo(info)) + // store a pointer to the infokey by address for fast lookup + kb.db.SetSync(addrKey(info.GetAddress()), key) +} + +func addrKey(address types.AccAddress) []byte { + return []byte(fmt.Sprintf("%s.%s", address.String(), addressSuffix)) } func infoKey(name string) []byte { - return []byte(fmt.Sprintf("%s.info", name)) + return []byte(fmt.Sprintf("%s.%s", name, infoSuffix)) } diff --git a/crypto/keys/keybase_test.go b/crypto/keys/keybase_test.go index 652a36bcb..2a602461a 100644 --- a/crypto/keys/keybase_test.go +++ b/crypto/keys/keybase_test.go @@ -11,6 +11,7 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" dbm "github.com/tendermint/tendermint/libs/db" + "github.com/cosmos/cosmos-sdk/types" ) func init() { @@ -20,8 +21,9 @@ func init() { // TestKeyManagement makes sure we can manipulate these keys well func TestKeyManagement(t *testing.T) { // make the storage with reasonable defaults + db := dbm.NewMemDB() cstore := New( - dbm.NewMemDB(), + db, ) algo := Secp256k1 @@ -51,6 +53,12 @@ func TestKeyManagement(t *testing.T) { require.NoError(t, err) _, err = cstore.Get(n3) require.NotNil(t, err) + _, err = cstore.GetByAddress(accAddr(i2)) + require.NoError(t, err) + addr, err := types.AccAddressFromBech32("cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t") + require.NoError(t, err) + _, err = cstore.GetByAddress(addr) + require.NotNil(t, err) // list shows them in order keyS, err := cstore.List() @@ -92,6 +100,11 @@ func TestKeyManagement(t *testing.T) { keyS, err = cstore.List() require.NoError(t, err) require.Equal(t, 1, len(keyS)) + + // addr cache gets nuked + err = cstore.Delete(n2, p2) + require.NoError(t, err) + require.False(t, db.Has(addrKey(i2.GetAddress()))) } // TestSignVerify does some detailed checks on how we sign and validate @@ -387,3 +400,7 @@ func ExampleNew() { // Carl // signed by Bob } + +func accAddr(info Info) types.AccAddress { + return (types.AccAddress)(info.GetPubKey().Address()) +} \ No newline at end of file diff --git a/crypto/keys/types.go b/crypto/keys/types.go index a68626489..c5e97d5fb 100644 --- a/crypto/keys/types.go +++ b/crypto/keys/types.go @@ -5,14 +5,15 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/types" ) // Keybase exposes operations on a generic keystore type Keybase interface { - // CRUD on the keystore List() ([]Info, error) Get(name string) (Info, error) + GetByAddress(address types.AccAddress) (Info, error) Delete(name, passphrase string) error // Sign some bytes, looking up the private key to use @@ -73,6 +74,8 @@ type Info interface { GetName() string // Public key GetPubKey() crypto.PubKey + // Address + GetAddress() types.AccAddress } var _ Info = &localInfo{} @@ -106,6 +109,10 @@ func (i localInfo) GetPubKey() crypto.PubKey { return i.PubKey } +func (i localInfo) GetAddress() types.AccAddress { + return i.PubKey.Address().Bytes() +} + // ledgerInfo is the public information about a Ledger key type ledgerInfo struct { Name string `json:"name"` @@ -133,6 +140,10 @@ func (i ledgerInfo) GetPubKey() crypto.PubKey { return i.PubKey } +func (i ledgerInfo) GetAddress() types.AccAddress { + return i.PubKey.Address().Bytes() +} + // offlineInfo is the public information about an offline key type offlineInfo struct { Name string `json:"name"` @@ -158,6 +169,10 @@ func (i offlineInfo) GetPubKey() crypto.PubKey { return i.PubKey } +func (i offlineInfo) GetAddress() types.AccAddress { + return i.PubKey.Address().Bytes() +} + // encoding info func writeInfo(i Info) []byte { return cdc.MustMarshalBinary(i) diff --git a/docs/config.js b/docs/.vuepress/config.js similarity index 93% rename from docs/config.js rename to docs/.vuepress/config.js index 93426bcc5..5a30b4423 100644 --- a/docs/config.js +++ b/docs/.vuepress/config.js @@ -24,8 +24,8 @@ module.exports = { children: [ "/getting-started/voyager", "/getting-started/installation", - "/getting-started/full-node", - "/getting-started/create-testnet" + "/getting-started/join-testnet", + "/getting-started/networks" ] }, { @@ -57,7 +57,9 @@ module.exports = { { title: "Lotion JS", collapsable: false, - children: [["/lotion/overview", "Overview"], "/lotion/building-an-app"] + children: [ + ["/lotion/overview", "Overview"] + ] }, { title: "Validators", diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js new file mode 100644 index 000000000..f24006cba --- /dev/null +++ b/docs/.vuepress/enhanceApp.js @@ -0,0 +1,3 @@ +export default ({ router }) => { + router.addRoutes([{ path: "/testnet/", redirect: "/" }]) +} diff --git a/docs/.vuepress/override.styl b/docs/.vuepress/override.styl new file mode 100644 index 000000000..39bdc860d --- /dev/null +++ b/docs/.vuepress/override.styl @@ -0,0 +1,4 @@ +$accentColor = #304DE9 +$textColor = #15192C +$borderColor = #eaecef +$codeBgColor = #282c34 diff --git a/docs/DOCS_README.md b/docs/DOCS_README.md index da11b2c01..30cb5d9bc 100644 --- a/docs/DOCS_README.md +++ b/docs/DOCS_README.md @@ -1,16 +1,94 @@ -# Documentation Maintenance Overview +# Docs Build Workflow -The documentation found in this directory is hosted at: +The documentation for the Cosmos SDK is hosted at: -- https://cosmos.network/docs/ +- https://cosmos.network/docs/ and +- https://cosmos-staging.interblock.io/docs/ -and built using [VuePress](https://vuepress.vuejs.org/) from the Cosmos website repo: +built from the files in this (`/docs`) directory for +[master](https://github.com/cosmos/cosmos-sdk/tree/master/docs) +and [develop](https://github.com/cosmos/cosmos-sdk/tree/develop/docs), +respectively. -- https://github.com/cosmos/cosmos.network +## How It Works -Under the hood, Jenkins listens for changes (on develop or master) in ./docs then rebuilds -either the staging or production site depending on which branch the changes were made. +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. -To update the Table of Contents (layout of the documentation sidebar), edit the -`config.js` in this directory, while the `README.md` is the landing page for the -website documentation. +## README + +The [README.md](./README.md) is also the landing page for the documentation +on the website. + +## 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 + +**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 + +Where is the other file, relative to the current one? + +- works both on GitHub and for the VuePress build +- confusing / annoying to have things like: `../../../../myfile.md` +- requires more updates when files are re-shuffled + +### Absolute + +Where is the other file, given the root of the repo? + +- works on GitHub, doesn't work for the VuePress build +- 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 + +The full GitHub URL to a file or directory. Used occasionally when it makes sense +to send users to the GitHub. + +## Building Locally + +To build and serve the documentation locally, run: + +``` +npm install -g vuepress +``` + +then change the following line in the `config.js`: + +``` +base: "/docs/", +``` + +to: + +``` +base: "/", +``` + +Finally, go up one directory to the root of the repo and run: + +``` +# from root of repo +vuepress build docs +cd dist/docs +python -m SimpleHTTPServer 8080 +``` + +then navigate to localhost:8080 in your browser. + +## 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). diff --git a/docs/README.md b/docs/README.md index 9921cd757..e721ad1a1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,3 +7,8 @@ Cosmos is a decentralized network of independent parallel blockchains, each powe 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. + +## Edit the Documentation + +See [this file](./DOCS_README.md) for details of the build process and +considerations when making changes. diff --git a/docs/architecture/decision-records/README.md b/docs/architecture/decision-records/README.md new file mode 100644 index 000000000..7c669566d --- /dev/null +++ b/docs/architecture/decision-records/README.md @@ -0,0 +1,22 @@ +# 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 new file mode 100644 index 000000000..0e9f25990 --- /dev/null +++ b/docs/architecture/decision-records/adr-001-message-counter.md @@ -0,0 +1,54 @@ +# 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 new file mode 100644 index 000000000..6153a9d1e --- /dev/null +++ b/docs/architecture/decision-records/adr-template.md @@ -0,0 +1,32 @@ +# 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/ledger.md b/docs/clients/ledger.md index 26b0215b3..e4bf9ac15 100644 --- a/docs/clients/ledger.md +++ b/docs/clients/ledger.md @@ -23,7 +23,7 @@ NAME: TYPE: ADDRESS: PUBKEY: This key will only be accessible while the Ledger is plugged in and unlocked. To send some coins with this key, run the following: ```bash -$ gaiacli send --from {{ .Key.Name }} --to {{ .Destination.AccAddr }} --chain-id=gaia-7000 +$ gaiacli tx send --from {{ .Key.Name }} --to {{ .Destination.AccAddr }} --chain-id=gaia-7000 ``` You will be asked to review and confirm the transaction on the Ledger. Once you do this you should see the result in the console! Now you can use your Ledger to manage your Atoms and Stake! diff --git a/docs/clients/service-providers.md b/docs/clients/service-providers.md new file mode 100644 index 000000000..79fe2a667 --- /dev/null +++ b/docs/clients/service-providers.md @@ -0,0 +1,111 @@ +# Integrate a Cosmos-SDK based blockchain as a Service Provider + +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. + +This section does not concern wallet builders that want to provide [Light-Client](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/light) functionalities. Service providers are expected to act as trusted point of contact to the blockchain for their end-users. + +## High-level description of the architecture + +There are three main pieces to consider: + +- Full-nodes: To interact with the blockchain. +- Rest Server: This acts as a relayer for HTTP calls. +- Rest API: Define available endpoints for the Rest Server. + +## Running a Full-Node + +### Installation and configuration + +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). + +Then, you can start [running a full-node](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/getting-started/full-node.md). + +### Command-Line interface + +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): + +```bash +gaiacli keys add +``` + +You will be asked to create a passwords (at least 8 characters) for this key-pair. The command returns 4 informations: + +- `NAME`: Name of your key +- `ADDRESS`: Your address. Used to receive funds. +- `PUBKEY`: Your public key. Useful for validators. +- `Seed phrase`: 12-words phrase. **Save this seed phrase somewhere safe**. It is used to recover your private key in case you forget the password. + +You can see all your available keys by typing: + +```bash +gaiacli keys list +``` + +#### Checking your balance + +After receiving tokens to your address, you can view your account's balance by typing: + +```bash +gaiacli account +``` + +*Note: When you query an account balance with zero tokens, you will get this error: No account with address was found in the state. This is expected! We're working on improving our error messages.* + +#### Sending coins via the CLI + +Here is the command to send coins via the CLI: + +```bash +gaiacli tx send --amount=10faucetToken --chain-id= --name= --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. +- `--to`: Address of the recipient. + +#### Help + +If you need to do something else, the best command you can run is: + +```bash +gaiacli +``` + +It will display all the available commands. For each command, you can use the `--help` flag to get further information. + +## 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. + +To start the Rest server: + +```bash +gaiacli rest-server --trust-node=false --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 . + +### 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) + +## 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 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 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. diff --git a/docs/getting-started/create-testnet.md b/docs/getting-started/create-testnet.md deleted file mode 100644 index 74fd1d5ef..000000000 --- a/docs/getting-started/create-testnet.md +++ /dev/null @@ -1,27 +0,0 @@ -## Create your Own Testnet - -To create your own testnet, first each validator will need to install gaiad and run gen-tx - -```bash -gaiad init gen-tx --name -``` - -This populations `$HOME/.gaiad/gen-tx/` with a json file. - -Now these json files need to be aggregated together via Github, a Google form, pastebin or other methods. - -Place all files on one computer in `$HOME/.gaiad/gen-tx/` - -```bash -gaiad init --with-txs -o --chain= -``` - -This will generate a `genesis.json` in `$HOME/.gaiad/config/genesis.json` distribute this file to all validators on your testnet. - -### Export state - -To export state and reload (useful for testing purposes): - -``` -gaiad export > genesis.json; cp genesis.json ~/.gaiad/config/genesis.json; gaiad start -``` diff --git a/docs/getting-started/full-node.md b/docs/getting-started/join-testnet.md similarity index 92% rename from docs/getting-started/full-node.md rename to docs/getting-started/join-testnet.md index 87c28581b..66ec97cad 100644 --- a/docs/getting-started/full-node.md +++ b/docs/getting-started/join-testnet.md @@ -29,6 +29,19 @@ You can edit this `name` later, in the `~/.gaiad/config/config.toml` file: moniker = "" ``` +You can edit the `~/.gaiad/config/gaiad.toml` file in order to enable the anti spam mechanism and reject incoming transactions with less than a minimum fee: + +``` +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base config options ##### + +# Validators reject any tx from the mempool with less than the minimum fee per gas. +minimum_fees = "" +``` + + Your full node has been initialized! Please skip to [Genesis & Seeds](#genesis-seeds). ## Upgrading From Previous Testnet diff --git a/docs/getting-started/networks.md b/docs/getting-started/networks.md new file mode 100644 index 000000000..abd2999ba --- /dev/null +++ b/docs/getting-started/networks.md @@ -0,0 +1,209 @@ +# Networks + +There are a variety of ways to setup either local or remote networks with automation, detailed below. +All the required files are found in the [networks directory](https://github.com/cosmos/cosmos-sdk/tree/develop/networks) and additionally the `local` or `remote` sub-directories. + +## Local Testnet + +From the [networks/local directory](https://github.com/cosmos/cosmos-sdk/tree/develop/networks/local): + +### Requirements + +- [Install gaia](https://cosmos.network/docs/getting-started/installation.html) +- [Install docker](https://docs.docker.com/engine/installation/) +- [Install docker-compose](https://docs.docker.com/compose/install/) + +### Build + +Build the `gaiad` binary and the `tendermint/gaiadnode` docker image. + +Note the binary will be mounted into the container so it can be updated without +rebuilding the image. + +``` +cd $GOPATH/src/github.com/cosmos/cosmos-sdk + +# Build the linux binary in ./build +make build-linux + +# Build tendermint/gaiadnode image +make build-docker-gaiadnode +``` + +### Run a testnet + +To start a 4 node testnet run: + +``` +make localnet-start +``` + +This command creates a 4-node network using the gaiadnode image. +The ports for each node are found in this table: + +| Node ID | P2P Port | RPC Port | +| --------|-------|------| +| `gaianode0` | `26656` | `26657` | +| `gaianode1` | `26659` | `26660` | +| `gaianode2` | `26661` | `26662` | +| `gaianode3` | `26663` | `26664` | + +To update the binary, just rebuild it and restart the nodes: + +``` +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: + +```tree -L 2 build/ +build/ +├── gaiacli +├── gaiad +├── gentxs +│   ├── node0.json +│   ├── node1.json +│   ├── node2.json +│   └── node3.json +├── node0 +│   ├── gaiacli +│   │   ├── key_seed.json +│   │   └── keys +│   └── gaiad +│   ├── ${LOG:-gaiad.log} +│   ├── config +│   └── data +├── node1 +│   ├── gaiacli +│   │   └── key_seed.json +│   └── gaiad +│   ├── ${LOG:-gaiad.log} +│   ├── config +│   └── data +├── node2 +│   ├── gaiacli +│   │   └── key_seed.json +│   └── gaiad +│   ├── ${LOG:-gaiad.log} +│   ├── config +│   └── data +└── node3 + ├── gaiacli + │   └── key_seed.json + └── gaiad + ├── ${LOG:-gaiad.log} + ├── config + └── data +``` + +Each `./build/nodeN` directory is mounted to the `/gaiad` directory in each container. + +### Logging + +Logs are saved under each `./build/nodeN/gaiad/gaia.log`. Watch them stream in with, for example: + +``` +tail -f build/node0/gaiad/gaia.log +``` + +### 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: + +``` +# Run with custom binary +BINARY=gaiafoo make localnet-start +``` + +## Remote Testnet + +The following should be run from the [networks directory](https://github.com/cosmos/cosmos-sdk/tree/develop/networks). + +### Terraform & Ansible + +Automated deployments are done using [Terraform](https://www.terraform.io/) to create servers on AWS then +[Ansible](http://www.ansible.com/) to create and manage testnets on those servers. + +### Prerequisites + +- Install [Terraform](https://www.terraform.io/downloads.html) and [Ansible](http://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) on a Linux machine. +- Create an [AWS API token](https://docs.aws.amazon.com/general/latest/gr/managing-aws-access-keys.html) with EC2 create capability. +- Create SSH keys + +``` +export AWS_ACCESS_KEY_ID="2345234jk2lh4234" +export AWS_SECRET_ACCESS_KEY="234jhkg234h52kh4g5khg34" +export TESTNET_NAME="remotenet" +export CLUSTER_NAME= "remotenetvalidators" +export SSH_PRIVATE_FILE="$HOME/.ssh/id_rsa" +export SSH_PUBLIC_FILE="$HOME/.ssh/id_rsa.pub" +``` + +These will be used by both `terraform` and `ansible`. + +### Create a remote network + +``` +SERVERS=1 REGION_LIMIT=1 make validators-start +``` + +The testnet name is what's going to be used in --chain-id, while the cluster name is the administrative tag in AWS for the servers. The code will create SERVERS amount of servers in each availability zone up to the number of REGION_LIMITs, starting at us-east-2. (us-east-1 is excluded.) The below BaSH script does the same, but sometimes it's more comfortable for input. + +``` +./new-testnet.sh "$TESTNET_NAME" "$CLUSTER_NAME" 1 1 +``` + +### Quickly see the /status endpoint + +``` +make validators-status +``` + +### Delete servers + +``` +make validators-stop +``` + +### Logging + +You can ship logs to Logz.io, an Elastic stack (Elastic search, Logstash and Kibana) service provider. You can set up your nodes to log there automatically. Create an account and get your API key from the notes on [this page](https://app.logz.io/#/dashboard/data-sources/Filebeat), then: + +``` +yum install systemd-devel || echo "This will only work on RHEL-based systems." +apt-get install libsystemd-dev || echo "This will only work on Debian-based systems." + +go get github.com/mheese/journalbeat +ansible-playbook -i inventory/digital_ocean.py -l remotenet logzio.yml -e LOGZIO_TOKEN=ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 +``` + +### Monitoring + +You can install the DataDog agent with: + +``` +make datadog-install +``` + +### Single-node testnet + +To create a single node testnet: + +``` +cd $GOPATH/src/github.com/cosmos/cosmos-sdk + +# Clear the build folder +rm -rf ./build + +# Build binary +make build-linux + +# Create configuration +docker run -v `pwd`/build:/gaiad tendermint/gaiadnode testnet -o . --v 1 + +# Run the node +docker run -v `pwd`/build:/gaiad tendermint/gaiadnode +``` diff --git a/docs/light/api.md b/docs/light/api.md index 6c7f9de17..7fbf9fbe1 100644 --- a/docs/light/api.md +++ b/docs/light/api.md @@ -763,7 +763,7 @@ The GovernanceAPI exposes all functionality needed for casting votes on plain te "chain_id": "string", "account_number": 0, "sequence": 0, - "gas": 0 + "gas": "simulate" }, "depositer": "string", "amount": 0, @@ -866,7 +866,7 @@ The GovernanceAPI exposes all functionality needed for casting votes on plain te "chain_id": "string", "account_number": 0, "sequence": 0, - "gas": 0 + "gas": "simulate" }, // A cosmos address "voter": "string", diff --git a/docs/light/getting_started.md b/docs/light/getting_started.md index 5f11956c0..21497477a 100644 --- a/docs/light/getting_started.md +++ b/docs/light/getting_started.md @@ -1,6 +1,6 @@ # Getting Started -To start a rest server, we need to specify the following parameters: +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 | @@ -12,9 +12,25 @@ To start a rest server, we need to specify the following parameters: Sample command: ```bash -gaiacli light-client --chain-id=test --laddr=tcp://localhost:1317 --node tcp://localhost:46657 --trust-node=false +gaiacli rest-server --chain-id=test \ + --laddr=tcp://localhost:1317 \ + --node tcp://localhost:46657 \ + --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 \ + --laddr=tcp://localhost:1317 \ + --node tcp://localhost:46657 \ + --trust-node=false \ + --certfile=mycert.pem --keyfile=mykey.key +``` + +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 diff --git a/docs/lotion/building-an-app.md b/docs/lotion/building-an-app.md deleted file mode 100644 index 93b3363c5..000000000 --- a/docs/lotion/building-an-app.md +++ /dev/null @@ -1,46 +0,0 @@ -# Building an App - -::: tip -Lotion requires __node v7.6.0__ or higher, and a mac or linux machine. -::: - -## Installation -``` -$ 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 } -``` - -## Learn More - -You can learn more about Lotion JS by visiting Lotion on [Github](https://github.com/keppel/lotion). diff --git a/docs/lotion/overview.md b/docs/lotion/overview.md index 03c79c571..b28d2c75b 100644 --- a/docs/lotion/overview.md +++ b/docs/lotion/overview.md @@ -1,5 +1,54 @@ -# Lotion JS Overview +# Overview -Lotion is a new way to create blockchain apps in JavaScript, which aims to make writing new blockchains fast and fun. It builds on top of Tendermint using the ABCI protocol. Lotion lets you write secure, scalable applications that can easily interoperate with other blockchains on the Cosmos Network using IBC. +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/resources/whitepaper-zh-CN.md b/docs/resources/whitepaper-zh-CN.md index 540ab6647..126deabd4 100644 --- a/docs/resources/whitepaper-zh-CN.md +++ b/docs/resources/whitepaper-zh-CN.md @@ -1,726 +1,1013 @@ # Cosmos +**A Network of Distributed Ledgers** -分布式账本网络 +**分布式账本网络** -Jae Kwon
-Ethan Buchman +Jae Kwon jae@tendermint.com
+Ethan Buchman ethan@tendermint.com -加入我们的 [Matrix](https://riot.im/app/#/room/#cosmos:matrix.org)一起讨论吧! +讨论[请加入Telegram](https://t.me/cosmosproject)! -_注意:我们会对内容进行定期更新,您可以随时进行查阅,谢谢!_ +_注意:如果你能在github上阅读,我们仍然定时更新这个文档,请定期检查更新!_ -\[[toc]] +## 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)(加密货币),Zerocash [\[2\]](2)(私有加密货币),也看到了大众化智能合约平台,比如以太坊 [\[3\]](3),此外还有其他无数针对 EVM(以太坊虚拟机)的分布式应用,如 Augur(预测市场)以及 The DAO [\[4\]](4)(投资俱乐部)。 -但是,到目前为止,这些区块链已经暴露了各种缺陷,包括总能量低效、功能不佳或受限、并且缺乏成熟的管理机制。为了扩大比特币交易吞吐量,已经研发了许多诸如隔离见证(Segregated-Witness) [\[5\]](5)和 BitcoinNG [\[6\]](6)这样的解决方案,但是这些垂直扩展方案都因单一物理机容量而受到限制,不然就得损害其可审核性这一特性。闪电网络 [\[7\]](7)可以通过让部分交易完全记录在账本外,来帮助扩大比特币交易额,这个方法非常适合微支付以及隐私保护支付轨道,但是可能无法满足更广泛的扩展需求。 +开源的生态系统、去中心化的文件共享、以及公共的加密货币,这一系列技术的成功使人们启发和理解,去中心化的互联网协议是可以从根本上改善社会经济基础架构的。我们已经见识过个有专长的区块链应用,诸如比特币[\[1\]][1](加密货币),ZCASH [\[2\]][2] (隐私加密货币),也看到了例如以太坊 [\[3\]][3] 的大众智能合约平台,还有无数基于 EVM (以太坊虚拟机)开发的分布式应用,例如 Augur(预测市场)和 TheDAO [\[4\]][4] (投资俱乐部) -理想的解决方案是在允许多个平行区块链互相操作的同时,保留安全特性。不过事实证明,采用工作量证明很难做到这一点,但也并非不可能。例如合并挖矿可以在完成工作的同时,让母链得以在子链上重复使用。不过这样还是需要通过每个节点,依次对交易进行验证,而且如果母链上大多数哈希力没有积极地对子链进行合并挖矿,那么就很容易遭到攻击。关于 [可替代区块链网络架构的学术回顾](http://vukolic.com/iNetSec_2015.pdf)将在辅助材料中呈现,我们会在 [相关作品](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#related-work)中对更多提议及其缺点进行概括。 -这里我们要介绍的是 Cosmos,这是一个全新区块链网络架构,能够解决所有问题。Cosmos 是一个涵盖众多独立区块链的网络,叫做"空间"。空间在 Tendermint Core [\[8\]](8)支持下运行,是一个类似实用拜占庭容错的安全共识引擎,兼具高性能、一致性等特点,而且在其严格的分叉责任制保证下,能够防止怀有恶意的参与者做出不当操作。Tendermint Core 的拜占庭容错共识算法,非常适合用来扩展权益证明机制下的公共区块链。 +然而,迄今为止,这些区块链已经暴露了各种缺陷,包括总体能效低下,性能不佳或受到限制和缺乏成熟的治理机制。为了扩大比特币交易吞吐量,已经研发了许多诸如隔离见证 [\[5\]][5](Segregated-Witness)和BitcoinNG [\[6\]][6](一种新的可扩展协议)这样的解决方案,但这些垂直扩展解决方案仍然受到单一物理机容量的限制,以确保完整的可审计性。闪电网络 [\[7\]][7] 可以通过部分交易完全记录在主链账本外来扩展比特币的交易容量,这种方法十分适用于微支付和隐私保护支付通道,但是无法适用于更通用的扩展需求。 -Cosmos 上的第一个空间叫做"Cosmos Hub"(Cosmos 中心)。Cosmos 中心是一种多资产权益证明加密货币网络,它通过简单的管理机制来实现网络的改动与更新。此外,Cosmos 中心还可以通过连接其他空间来实现扩展。 -Cosmos 网络的中心及各个空间可以通过区块链间通信(IBC)协议进行沟通,这种协议就是针对区块链的虚拟用户数据报协议(UDP)或者传输控制协议(TCP)。代币可以安全快速地从一个空间传递到另一个空间,两者之间无需体现汇兑流动性。相反,空间内部所有代币的转移都会通过 Cosmos 中心,它会记录每个空间所持有的代币总量。这个中心会将每个空间与其他故障空间隔离开。因为每个人都将新空间连接到 Cosmos 中心,所以空间今后也可以兼容新的区块链技术。 +理想的解决方案是允许多个并行的区块链交互操作的同时保持其安全特性。事实证明,采用工作量证明很难做到这一点,但也并非不可能。例如合并挖矿,允许在工作完成的同时,确保母链在子链上被重复使用,但交易必须通过每个节点依次进行验证,而且如果母链上的大多数哈希算力没有积极地对子链进行合并挖矿,那么就容易遭受到攻击。关于[可替代区块链网络架构的学术回顾](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 共识协议及其用来创建应用程序的界面进行介绍。更多细节,详见 [附录](#appendix)。 + +在这一部分我们将阐述Tendermint共识协议和用于建立其应用程序的接口。 更多信息,请参见[附录](#附录) ### 验证人 -在经典拜占庭容错(BFT)算法中,每个节点都同样重要。在 Tendermint 网络里,节点的投票权不能为负,而拥有投票权的节点被称作"验证人"。验证人通过传播加密签名或选票,来参与共识协议并商定下一区块。 -验证人的投票权是一开始就确定好的,或者根据应用程序由区块链来决定是否有改变。比如,在 Cosmos 中心这种权益证明类应用程序中,投票权可能就是通过绑定为保证金的代币数量来确定的。 -_注意:像 ⅔ 和 ⅓ 这样的分数指的是占总投票权的分数,而不是总验证人,除非所有验证人拥有相同币种。而+⅔ 的意思是"超过 ⅔ ",⅓+则是"⅓ 或者更多"的意思。_ +在经典的拜占庭容错算法中,每个节点有相同的权重。在 Tendermint,节点有着不同数量(非负)的 _投票权_,而那些拥有相当数量投票权的节点称之为 _验证人_。验证人通过广播加密签名、投票或者对下一个区块表决同意来参与共识协议。 + +验证者的投票权是一开始就确定好了,或者根据应用程序由区块链来决定修改投票权。例如,在像Cosmos 枢纽的权益证明应用里,投票权可由绑定为押金的代币数量来决定。 + + +注意:像⅔和⅓这样的分数指的是占总投票权的分数,而不是总验证人,除非所有验证人拥有相同权重。而>⅔ 的意思是"超过⅔ ",≥⅓则是"⅓或者更多"的意思。 ### 共识 -Tendermint 是部分同步运作的拜占庭容错共识协议,这种协议源自 DLS 共识算法 [\[20\]](20)。Tendermint 的特点就在于其简易性、高性能以及分叉责任制。协议要求有固定且熟知的一组验证人,其中每个验证人通过公钥进行身份验证。这些验证人会尝试在某个区块上同时达成共识(这里的区块是指一份交易列表)。每个区块的共识轮流进行,每一轮都会有个领头人,或者提议人,由他们来发起区块。之后验证人分阶段对是否接受该区块,或者是否进入下一轮做出投票。每轮的提议人会从验证人顺序列表中按照其选票比例来选择确定。 -该协议全部细节请参考 [此处](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm)。 +Tendermint 是部分同步运作的拜占庭容错共识协议,这种协议源自DLS共识算法 [\[20\]][20]。Tendermint以简易性、高性能以及[分叉问责制](#fork-accountability)而著称。协议要求这组验证人固定且被熟知,并且每个验证人都有其公钥验证身份。这些验证人试图同时在一个区块上达成共识,这些区块是一系列的交易记录。每个区块的共识轮流进行,每一轮都会有个领头人,或者提议人,由他们来发起区块。之后验证人分阶段对是否接受该区块,或者是否进入下一轮做出投票。每轮的提议人会从验证人顺序列表中按照其投票权比例来选择确定。 -Tendermint 采用由绝对多数的选票(+⅔)选定的最优拜占庭容错算法,以及一套锁定机制来确保安全性。对此他们保证: -- 想要违背安全必须有超过 ⅓ 的选票出现拜占庭问题,并且提交超过两个值。 -- 如果有任何验证组引起了安全问题,或者说是企图这么做,那么就会被协议发现,一方面针对有冲突的区块进行投票,同时广播那些有问题的选票。 +更多协议的全部细节,请点击[这里](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm). -除了其超强安全保障外,Tendermint 还具备其他功效。以商品型云平台为例,Tendermint 共识以分布在五大洲七个数据中心的 64 位节点为基准,其每秒可以处理成千上万笔交易,提交顺序延迟时间为 1-2 秒。而值得关注的是,即使是在极其恶劣的敌对环境中,比如验证人崩溃了或者是遇到蓄谋已久的恶意选票,也能维持这种每秒千笔交易的高绩效。详见下图。 + +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 轻客戸端只需和验证组的变化保持一致,然后简单地验证最新区块中预先提交的+⅔,来确定最新情况。 -这种简单的轻客戸端证明机制也可以实现 [区块链之间的通信](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc)。 +Tendermint 共识算法的主要好处是具有安全简易的客户端,使其成为手机和物联网用例的理想选择。比特币轻客户端必须同步运行区块头组成的链,并且找到工作量证明最多的那一条链,而Tendermint轻客戸端只需和验证组的变化保持一致,然后简单地验证最新区块中预先提交的>⅔,来确定最新情况。 + +这种简单的轻客戸端证明机制也可以实现[区块链之间的通信](#inter-blockchain-communication-ibc)。 ### 防止攻击 -Tendermint 有各种各样的防御措施来防止攻击,比如 [远程无利害关系双重花费](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#preventing-long-range-attacks)及 [审查制度](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#overcoming-forks-and-censorship-attacks)。这个在 [附录](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#appendix)中会进行完整讨论。 -### TMSP +Tendermint 有各种各样的防御措施来防止一些明显的攻击,比如[远程无利害关系双花攻击](#preventing-long-range-attacks) 及[审查制度](#overcoming-forks-and-censorship-attacks)。 这些在[附录](#appendix)中有更详细的讨论。 -Tendermint 共识算法是在叫做 Tendermint Core 的程序中实现的。这个程序是一种与应用程序无关的"共识引擎",可以让任何命中注定的黑匣子软件变为分散复制的区块链。就像 Apache 网页服务器或者 Nginx 是通过通用网关接口(CGI)或快速通用网关接口(FastCGI)来连接 Wordpress(一款博客系统)应用程序一样,Tendermint Core 通过 Tendermint Socket 协议(TMSP)来连接区块链应用程序。因此,TMSP 允许区块链应用程序用任何语言进行编程,而不仅仅是共识引擎写入的程序语言。此外,TMSP 也让交换任何现有区块链堆栈的共识层成为可能。 +### ABCI -我们将其与知名加密货币比特币进行了类比。在比特币这种加密币区块链中,每个节点都维持着完整的审核过的 UTXO(未使用交易输出)数据库。如果您想要在 TMSP 基础上,创建出类似比特币的系统,那么 Tendermint Core 可以做到: -- 在节点间共享区块及交易 -- 创建规范或不可改变的交易顺序(区块链) +Tendermint共识算法是在叫做 Tendermint Core 的程序中实现的。这个程序是独立于应用的“共识引擎”,可以将任何已经确定的黑盒应用转变为分布式、可复制的区块链。Tendermint Core 可以通过应用区块链接口(ABCI) [\[17\]][17]与其他区块链应用连接。而且,应用区块链接口(ABCI) 接口允许区块链应用以任何语言编程实现,而不仅仅是写这个共识引擎所使用的语言。此外,应用区块链接口(ABCI) 也让交换任何现有区块链栈的共识层成为可能。 -同时,TMSP 应用程序会负责: -- 维护 UTXO 数据库 -- 验证交易的加密签名 -- 防止出现不存在的交易花费 -- 允许客户访问 UTXO 数据库 +我们将其与知名加密货币比特币进行了类比。在比特币这种加密币区块链中,每个节点都维持着完整的审核过的 UTXO(未使用交易输出)数据库。如果您想要在应用区块链接口(ABCI)基础上,创建出类似比特币的系统,那么 Tendermint Core 可以做到: + +* 在节点间共享区块及交易 +* 创建规范或不可改变的交易顺序(区块链) + + +同时,ABCI应用也可以做到: + +* 维护 UTXO 数据库 +* 验证交易的加密签名 +* 防止出现不存在的余额被交易 +* 允许客户访问UTXO数据库 + -Tendermint 能够通过为应用程序与共识的形成过程,提供简单的应用程序界面(API),来分解区块设计。 ## Cosmos 概述 -Cosmos 是一种独立平行的区块链网络,其中每条区块链通过 Tendermint [1](https://github.com/tendermint/tendermint)这样的经典拜占庭容错共识算法来运行。 -网络中第一条区块链将会是 Cosmos 中心。Cosmos 中心通过全新区块链间通信协议来连接其他众多区块链(或将其称之为空间)。中心可以追踪无数代币种类,并且在各个连接的空间里记录代币总数。代币可以安全快速地从一个空间传递到另一个空间,两者之间无需体现汇兑流动性,因为所有空间之间的代币传输都会经过 Cosmos 中心。 +Cosmos是一个独立平行的区块链网络,其中每条区块链通过 Tendermint[1](http://github.com/tendermint/tendermint)这样的经典拜占庭容错共识算法来运行。 -这一架构解决了当今区块链领域面临的许多问题,包括应用程序互操作性、可扩展性、以及无缝更新性。比如,从 Bitcoind、Go-Ethereum、CryptoNote、ZCash 或其他区块链系统中衍生出来的空间,都可以接入 Cosmos 中心。这些空间允许 Cosmos 实现无限扩展,从而满足全球交易的需求。此外,空间也完全适用于分布式交易所,反之交易所也支持空间运行。 -Cosmos 不仅仅是单一的分布式账本,而 Cosmos 中心也不是封闭式花园或宇宙中心。我们正在为分布式账本的开放网络设计一套协议,这套协议会按照加密学、稳健经济学、共识理论、透明性及可追究制的原则,成为未来金融系统的全新基础。 +网络中第一条区块链将会是 Cosmos 枢纽。Cosmos 枢纽通过全新的区块链间通信协议来连接其他众多区块链(或将其称之为 _分区_)。Cosmos 枢纽可以追踪无数代币的种类,并且在各个连接的分区里记录各种代币总数。代币可以安全快速地从一个分区转移到另一个分区,两者之间无需体现汇兑流动性,因为所有分区之间的代币传输都会经过 Cosmos 枢纽。 -### Tendermint 拜占庭容错股份授权证明机制(Tendermint-BFT DPoS) -Cosmos 中心是 Cosmos 网络中第一个公共区块链,通过 Tendermint 拜占庭共识算法运行。这个 Tendermint 开源项目于 2014 年开始,旨在解决比特币工作量证明算法的速度、可扩展性以及环境问题。通过采用并提高已经过验证的拜占庭算法(1988 年在麻省理工学院开发),Tendermint 成为了首个在概念上演示加密货币权益证明的团队,这种机制可以解决 NXT 和 BitShares 这些第一代权益证明加密币面临的"无利害关系"(nothing-at-stake)的问题。 +这一架构解决了当今区块链领域面临的许多问题,包括应用程序互操作性、可扩展性、以及可无缝升级的能力。比如,从Bitcoind、Go-Ethereum、CryptoNote、ZCash或其他区块链系统中衍生出来的分区,都能被锚定接入 Cosmos 枢纽。这些分区允许 Cosmos 实现无限扩展,从而满足全球交易的需求。此外,分区也完全适用于分布式交易所,反之交易所也支持分区运行。 -如今,实际上所有比特币移动钱包都要使用可靠的服务器来进行交易验证。这是因为工作量证明机制需要在交易被认定为无法逆转前进行多次确认。而在 CoinBase 之类的服务中也已经出现重复花费攻击。 -和其他区块链共识系统不同,Tendermint 提供的是即时、可证明安全的移动客户端支付验证方式。因为 Tendermint 的设计完全不支持分叉,所以移动钱包就可以实时接收交易确认,从而在智能手机上真正实现去信任的支付方式。这一点也大大影响了物联网应用程序。 -Cosmos 中的验证人(其扮演的角色类似比特币矿工,但是与之不同的是,他们采用加密签名来进行投票)必须是专门用来提交区块的安全机器。非验证人可以将权益代币(也叫做"atom")委托给任何验证人来赚取一定的区块费用以及 atom 奖励,但是如果验证人被黑客攻击或者违反协议规定,那么就会面临被惩罚(削减)的风险。Tendermint 拜占庭共识的可证明安全机制,以及利益相关方(验证人和委托人)的抵押品保证,为节点甚至是轻客户端提供了可证明、可计量的安全性。 +Cosmos 不仅仅是单一的分布式账本,而 Cosmos 枢纽也不是封闭式庭院或宇宙的中心。我们正在为分布式账本的开放网络设计一套协议,这套协议将基于密码学、稳健经济学、共识理论、透明性及可追责制的原则,成为未来金融系统的全新基础。 -### 管理 +### Tendermint-拜占庭容错 -分布式公共账本应该要有一套章程与管理体系。比特币依靠比特币基金会(在一定程度上)及挖矿来协调更新,但是这个过程很缓慢。以太坊在采用硬分叉措施解决 The DAO 黑客事件后,分裂成了 ETH 和 ETC,这主要是因为之前设定社会契约或机制来进行这类决定。 -Cosmos 中心的验证人与委托人可以对提案进行投票,从而自动改变预先设置好的系统参数(比如区块容量限制),协调更新,并对人们看得懂的章程进行修订投票,从而管理 Cosmos 中心。这个章程允许权益相关者聚集到一起,来解决盗窃及漏洞等相关问题(比如 The DAO 事件),并快速得出明确的解决方案。 +Cosmos 枢纽是 Cosmos 网络中第一条公共区块链,通过 Tendermint 的拜占庭共识算法运行。Tendermint 开源项目创立于2014年,旨在解决比特币工作量证明共识算法的速度、可扩展性以及造成的环境问题。通过采用并提高已经过验证的拜占庭算法(1988年在麻省理工学院开发)[\[20\]][20],Tendermint 成为了首个在概念论证了权益证明加密货币的团队,这种机制可以解决 NXT 和 BitShares 这些第一代权益证明加密币面临的"无利害关系"的问题。 -每个空间也具备自己的一套章程及管理机制。比如,Cosmos 中心的章程会强制实现中心的不可改变性(不能重新执行,除了 Cosmos 中心节点实现的漏洞),而每个空间则可自行设置与盗窃及漏洞相关的重新执行政策。 -Cosmos 网络能够在政策不同的区块间实现互操作性,这一点可以让客户在无需许可的环境下进行实验,为客户带去了终极自由及潜力。 +如今,实际上所有比特币移动钱包都要使用可靠的服务器来进行交易验证。这是因为工作量证明机制需要在交易被认定为无法逆转前进行多次确认。而在 Coinbase 之类的服务中也已经出现双重支付攻击。 -## 中心与空间 -这里我们将描述一个全新的去中心化与可扩展性模型。Cosmos 网络通过 Tendermint 机制来运行众多区块链。虽然现存提案的目标是创建一个包含全球所有交易顺序的"单一区块链",Cosmos 允许众多区块链在相互运行的同时,维持互操作性。 +和其他区块链共识系统不同,Tendermint 提供的是即时、可证明安全的移动客户端支付验证方式。因为 Tendermint 被设计为完全不分叉,所以移动钱包就可以实时接收交易确认,从而在智能手机上真正实现去信任的支付方式。这一点也大大影响了物联网应用程序。 -在这个基础上,Cosmos 中心负责管理众多独立区块链(称之为"空间",有时也叫做"碎片",根据数据库扩展技术"分片"得出)。中心上的空间会源源不断地提交最新区块,这一点可以让中心跟上每个空间状态的变化。同样地,每个空间也会和中心的状态保持一致(不过空间之间不会同彼此的步伐保持一致,除非间接通过中心来实现)。之后信息包就会从一个空间传递到另一个空间,并通过发布梅克尔证明(Merkle-proof)来说明信息已经被传送或接收。这种机制叫做"区块链间通信",或者简称为"IBC"机制。 + +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) -任何区块都可以自行成为中心,从而形成非循环图,但是有一点需要阐明,那就是我们只会对简单配置(只有一个中心)以及许多没有中心的空间进行描述。 -### 中心(Hub) +任何分区都可以自行成为枢纽来建立非循环图表,但为了清楚起见,我们只描述这种只有一个枢纽和许多非枢纽的分区这样简单的配置 -Cosmos 中心区块链承载的是多资产分布式账本,其中代币可以由个体用户或空间本身持有。这些代币能够通过特殊的 IBC 包裹,即"代币包"(coin packet)从一个空间转移到另一个空间。中心负责保持空间中各类代币全球总量不变。IBC 代币宝交易必须由发送人、中心及接收人的区块链执行。 -因为 Cosmos 中心在整个系统中扮演着中央代币账本的角色,其安全性极其重要。虽然每个空间可能都是一个 Tendermint 区块链——只需通过 4 个,或者在无需拜占庭容错共识的情况下更少的验证人来保证安全),但是 Cosmos 中心必须通过全球去中心化验证组来保证安全,而且这个验证组要能够承受最严重的攻击,比如大陆网络分割或者由国家发起的攻击。 +### 枢纽 -### 空间(Zones) -Cosmos 空间是独立的区块链,能够和 Cosmos 中心进行 IBC 信息交换。从 Cosmos 中心的角度看,空间是一种多资产、多签名的动态会员制账户,它可以通过 IBC 包裹进行代币发送与接收。就像加密币账户一样,空间不能转移超出其持有量的代币,不过可以从其他拥有代币的人那里接收代币。空间可能会被指定为一种或多种代币的"来源",从而赋予其增加代币供应量的权力。 +Cosmos枢纽是承载多种分布式账本资产的区块链,其中代币可以由个人或分区自己持有。这些代币能够通过特殊的IBC数据包,即"代币数据包"(coin packet)从一个分区转移到另一个分区。枢纽负责保持各个分区中各类代币总量不变。IBC代币数据包交易必须由发送人、枢纽及区块接受者执行。 -Cosmos 中心的 Atom 或可作为空间(连接到中心)验证人的筹码。虽然在 Tendermint 分叉责任制下,空间出现重复花费攻击会导致 atom 数量减少,但是如果空间中有超过 ⅔ 的选票都出现拜占庭问题的话,那这个空间就可以提交无效状态。Cosmos 中心不会验证或执行提交到其他空间的交易,因此将代币传送到可靠空间就是用户的责任了。未来 Cosmos 中心的管理系统可能会通过改善提案,来解决空间故障问题。比如,在检测到袭击时,可以将有些空间(或全部空间)发起的代币转移输出压制下来,实现紧急断路(即暂时中止代币转移)。 -## 区块链间通信(IBC) -现在我们来介绍下中心与空间之前通信的方法。假如现在有三个区块链,分别是"空间 1"、"空间 2"以及"中心",我们想要"空间 1"生成一个包裹,通过"中心"发送给"空间 2"。为了让包裹从一个区块链转移到另一个区块链,需要在接收方区块链上发布一个证明,来明确发送方已经发起了一个包裹到指定地点。接收方要验证的这个证明,必须和发送方区块头保持一致。这种机制就类似与侧链采用的机制,它需要两个相互作用的链,通过双向传送存在证明数据元(交易),来"知晓"另一方的情况。 +因为Cosmos枢纽在整个系统中扮演着中央代币账本的角色,其安全性极其重要。虽然每个分区可能都是一个Tendermint区块链——只需通过4个,(或者在无需拜占庭容错共识的情况下更少的验证人来保证安全),但是Cosmos枢纽必须通过全球去中心化验证组来保证安全,而且这个验证组要能够承受最严重的攻击,比如区域网络分裂或者由国家发起的攻击。 -IBC 协议可以自然定义为两种交易的使用:一种是 IBCBlockCommitTx 交易,这种交易可以让区块链向任何观察员证明其最新区块哈希值;另一种是 IBCPacketTx 交易,这种交易则可以证明某个包裹确实由发送者的应用程序,通过梅克尔证明机制(Merkle-proof)传送到了最新区块的哈希值上。 +### 分区 -通过将 IBC 机制分裂成两个单独的交易,即 IBCBlockCommitTx 交易与 IBCPacketTx 交易,我们可以让接收链的本地费用市场机制,来决定承认哪个包裹,与此同时还能确保发送方的完全自由,让其自行决定能够传出的包裹数量。 + +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_](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#ibcblockcommittx)_ 以及 _ [_IBCPacketTx_](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#ibcpacketcommit)_。_ -## 用例 + +在上述案例中,为了更新"枢纽"上"分区1"的区块哈希(或者说"分区2"上"枢纽"的区块哈希),必须将IBCBlockCommitTx交易的"分区1"区块哈希值发布到"枢纽"上(或者将该交易的"枢纽"区块哈希值发布到"分区2"中)。 + +_关于两种IBC交易类型,详细请参见 [IBCBlockCommitTx](#ibcblockcommittx) 和 [IBCPacketTx](#ibcpacketcommit) + +## 用例 ### 分布式交易所 -比特币借助批量复制的分布式账本技术来保证安全,同样的,我们也可以用这种方式,在区块链上运行,从而降低交易所受内外部攻击的可能性。我们称之为分布式交易所。 +比特币借助大量复制来增加分布式账本的安全性。用类似的方式,我们可以在区块链上运行交易所,来降低其受内部及外部攻击的可能性。我们称之为去中心化交易所。 -如今,加密币社区认为去中心化交易所是基于"原子交叉链"交易(AXC 交易)的交易所。通过这类交易,不同链上的两位用户可以发起两笔传输交易,要么在两个账本上一起执行,要么两个账本都不执行(即原子级)。比如,两位用户可以通过 AXC 交易来进行比特币和以太币之间的交易(或不同账本上的任意两种代币),即使比特币和以太坊之间并没有相互连接。在 AXC 交易模式下的交易所,其好处在于用户双方都不需要相信彼此,也不用相信交易匹配服务。其坏处就是,双方都得在线才能进行交易。 +现今,加密货币社区认为的去中心化交易所基于"跨链原子事务"交易( AXC 交易)。通过AXC交易,两条不同链上的两个用户可以发起两笔转账交易,交易在两个账本上要么一起提交执行,或者两个账本都不执行(即交易的原子性)。比如,两位用户可以通过AXC交易来实现比特币和以太币之间的交易(或是在不同账本上的任意两种代币),即使比特币和以太坊的区块链之间并没有彼此连接。AXC 交易模式下的交易所用户双方不需要彼此信任,也不用依赖交易匹配服务。其弊端是,交易双方必须同时在线才能进行交易。 -另一种去中心化交易所是在交易所的区块链上运行批量复制的分布式账本。这种交易所的用户可以提交一份限价订单,在关机状态下执行交易。区块链会代表交易者匹配并完成交易。 +另一种去中心化交易所是进行大量复制的具有独立区块链的分布式交易所。该种交易所的用户可以提交限价订单并关闭他们的计算机,交易可以在用户离线状态下执行。区块链将会代表交易者去完成匹配和交易。 -去中心化交易所可以创建一份大范围限价订单簿,以此来吸引其他交易者。在交易所界,流动性需求越来越高,因此交易所业务界的网络效应也愈发强烈(或者说至少产生了"胜者得益"效应)。目前加密币交易所排名第一的是 Poloniex,其 24 小时交易额为 2000 万美元,而 Bitfinex 以 24 小时 500 万位列第二。在这种强大的网络效应背景下,基于 AXC 的去中心化交易所的交易额是不可能超过中心化交易所的。去中心化交易所要想和中心化交易所一争高下,那么就需要支持大范围限价订单簿的运行。而只有基于区块链的去中心化交易所可以实现这一点。 +一个中心化的交易所可以构建一个有大交易量的限价交易的买卖盘账目,以此来吸引更多的交易者。在交易所领域,流动性会引发更多流动性,因此在交易所业务中,其具有的网络效应也愈发明显(或者说至少产生了"赢家通吃"效应)。目前加密货币交易所 Poloniex 以每24小时2,000万美元的交易量排名第一, Bitfinex 则每24小时500万美元的交易额位列第二。在这种强大的网络效应之下,基于AXC的去中心化交易所的成交量是不太可能超过中心化交易所。去中心化交易所要想和中心化交易所一争高下,就需要支持以限价订单构成的具有深度的交易买卖盘账目的运行。而只有基于区块链的去中心化交易所可以实现这一点。 -Tendermint 的快速交易执行是另一大优势。Cosmos 的空间可以在不牺牲一致性的前提下,通过优先完善快速交易,来实现交易的快速完成——针对双向订单交易,及 IBC(跨区块链通信)代币与其他空间的交易。 +Tendermint提供的快速交易执行是另一大优势。Cosmos的内部网络可以在不牺牲一致性的前提下优先快速的确定最终性,来实现交易的快速完成 —— 同时针对交易订单交易,以及IBC(跨区块链通信)代币与其他网络的交易。 -根据如今加密币交易所的情况,Cosmos 的一项重大应用就是分布式交易所(也就是 Cosmos DEX)。其交易吞吐能力及提交延时情况可以和那些中心化交易所媲美。交易者可以在离线的状态下提交限价订单。并且,在 Tendermint,Cosmos 中心以及 IBC 的应用下,交易者可以快速地完成资金在交易所及其他空间的转出转入。 +综上,根据现有加密货币交易所的情况,Cosmos的一项重大应用就是去中心化交易所(称为 Cosmos DEX)。其交易吞吐能量和委托延时可以与那些中心化交易所媲美。交易者可以在各方离线的状态下提交限价订单。并且,基于Tendermint,Cosmos枢纽以及IBC的情况下,交易者可以快速地完成在交易所及其他网络的资金进出。 -### 和其他加密货币挂钩 +### 作为其他加密货币的纽带 -享有特权的空间可以作为和其他加密货币挂钩的代币来源。这种挂钩类似 Cosmos 中心与空间之间的关系,两者都必须及时更新彼此最新的区块链,从而验证代币已经从一方转移到另一方的证明。Cosmos 网络上挂钩的空间要和中心以及其他加密货币保持一致。这种间接挂钩的空间可以维持简单的中心逻辑,并且不用了解其他区块链共识战略(比如比特币工作量证明挖矿机制)。 +特权分区可以作为和其他加密货币挂钩的代币来源。这种挂钩类似Cosmos枢纽与分区之间的关系,两者都必须及时更新彼此最新的区块链,从而验证代币已经从一方转移到另一方。Cosmos网络上挂钩的”桥接分区“要和中心以及其他加密币保持同步。这种间接通过”桥接分区“可以保持枢纽逻辑的简洁。并且不必要了解其他的链上共识战略,如比特币工作量证明挖矿机制。 -比如,设置有特定验证组的 Cosmos 空间(可能和中心里的验证组一样)可以作为与以太币挂钩的空间,其中基于 Tendermint Socket 协议(TMSP)的应用(即"挂钩空间"里的)有办法和外部以太坊区块链上的(即"起始点")挂钩合约交换 IBC 信息。通过这一合约,持币人可以先将以太币发送到以太坊的挂钩合约中,然后再将以太币传送到挂钩空间。挂钩合约接收到以太币后,除非同时从挂钩空间处接收到正确的 IBC 包裹,否则这些以太币是无法提取的。而当挂钩空间接收到 IBC 包裹,并证明以太币已被特定以太坊账户的挂钩合约接收后,挂钩空间就会生成存有余额的相关账户。之后,挂钩空间上的以太币(即"已挂钩的以太币")就可以转进或转出中心了,完成传送到特定以太坊提取地址的交易后,再彻底删除。IBC 包裹可以证明挂钩空间上的交易,这个包裹可以公布到以太坊挂钩合约中,来开放以太币的提取权。 +#### 向Cosmos枢纽发送代币 -当然,这类挂钩合约也存在风险,比如会出现恶劣的验证组。如果拜占庭投票权超过 ⅓,就会造成分叉,即从以太坊挂钩合约中提取以太币的同时,还能保持挂钩空间中的挂钩以太币不变。更有甚者,如果拜占庭投票权超过 ⅔,可能会有人直接对将以太币发送到挂钩合约中(通过脱离原始挂钩空间的挂钩逻辑)的人下手,盗取他们的以太币。 +每个挂钩桥接分区的验证人都会在基于Tendermint公式的区块链之上,运行带有特殊的ABCI桥接应用程序,但同时也会运行一个原有区块链的“全节点”。 -如果将这个挂钩方法完全设计成责任制,那么就有可能解决这一问题。比如,中心及起始点的全部 IBC 包裹可能需要先通过挂钩空间的认可,即让中心或起始点中的钩挂合约对挂钩空间的所有状态转变进行有效验证。中心及起始点要允许挂钩空间的验证人提供抵押品,而挂钩合约的代币转出需要有所延迟(并且抵押品解绑时间也要足够长),从而让单独的审计人有时间发起挑战。我们会以未来 Cosmos 改善提议的形式公开这一系统的设计说明及实现方式,以待 Cosmos 中心的管理系统审批通过。 +在原有区块链挖出新区块时,桥接分区验证人员将通过签署和分享起始点区块链的提示,各自局部视角可以达成一致。当一个桥接分区收到原有区块链的支付时(如在以太坊或比特币等PoW机制的链上有足够数目的确认),则在该桥接分区上创建具有该对应账户的余额。 -虽然现在的社会政治环境还不够成熟,不过我们可以做一些延伸,比如让负责国家国币的一些机构(尤其是其银行)组成一个验证组,来实现空间同国家法定货币的挂钩。当然这必须布置好额外的预防措施,只接受法律系统下的货币,从而加强可靠的公证人或大型机构对银行活动的审计。 +就以太坊而言,桥接分区可以和Cosmos枢纽共享相同的验证人。以太坊方面(原本区块链),一个桥接合约将允许以太拥有者通过将以太币发送到以太坊的桥接分区的桥接合约上。一旦挂桥接合约接收到以太币,以太币就不能被撤回,除非从桥接分区接收到对应的IBC数据包。桥接合约跟随桥接分区的验证组,它可能与Cosmos枢纽的验证人组相同。 -这一整合或可让空间中所有拥有银行账户的人,将自己银行账户里的美元传输到空间账户中,或者完整的转入中心或其他空间里。 +就比特币而言,概念是相似,除了代替一个桥接合约,每个UTXO将由一个门限多重签名P2SH数据库限制。由于P2SH系统的限制,签名者不能与Cosmos枢纽的验证人组相同。 -这么看来,Cosmos 中心就是法定货币和加密货币无缝对接的导管,从而解决困扰交易所至今的交互局限问题。 +#### 从Cosmos枢纽提出代币 + +桥接分区上的以太币(“桥接以太币”)可以在枢纽间转进,转出,完成传送到特定以太坊提取地址后,转出的“桥接以太币”被彻底删除。一个IBC消息可以证明桥接分区上的交易,这个消息将被公布到以太坊桥接合约中,以便以太币被取出。 + +就比特币而言,严谨的交易脚本系统让IBC币的镜像转换机制很难实现。每个UTXO都有自己的特定的脚本,所以当比特币履约签名者发生变化时,每个UTXO都必须迁移到新的UTXO。一个解决方案是根据需要,压缩和解压缩UTXO-set,以保持UTXO的总数量下降。 + +#### 挂钩区完全责任制 + +这类挂钩合约存在风险的风险是,可能会出现恶意的验证人组。如果拜占庭投票权超过⅓,就会造成分叉,即从以太坊桥接合约中提取以太币的同时,还能保持桥接分区中的挂钩以太币不变。甚至,如果拜占庭投票权超过⅔,可能会有人直接通过脱离原始桥接分区的桥接逻辑,对发送以太币发到桥接合约中的帐户下手,盗取以太币。 + +如果将这个桥接方法完全设计成责任制,就有可能解决这一问题。比如,枢纽及起始点的全部IBC包裹可能需要先通过桥接分区的认可,即让枢纽或起始点中的桥接合约对桥接分区的所有状态转换进行有效验证。枢纽及起始点要允许桥接分区的验证人提供抵押物,而侨界合约的代币转出需要延时(且抵押品解绑时间也要足够长),从而让单独的审计人有时间发起任何的质询。我们会把这一系统的设计说明以及执行方式开放,作为未来Cosmos改善的提议,以待Cosmos枢纽的管理系统审批通过。 ### 以太坊的扩展 -扩展问题一直是以太坊的一个公开问题。目前以太坊节点会处理每笔交易,并且存储所有状态。 -因为 Tendermint 提交区块的速度比以太坊工作量证明要快,所以由 Tendermint 共识推动且用于挂钩以太币运行的 EVM(以太坊虚拟机)空间能够强以太坊区块链的性能。此外,虽然 Cosmos 中心及 IBC 包裹技术不能实现每秒合约逻辑的任意执行,但是它可以用来协调不同空间里以太坊合约间的代币变动,通过碎片化方式为以代币为中心的以太坊奠定基础。 +众所周知,扩展问题是一直困扰着以太坊的问题。目前以太坊节点会处理节点上每笔交易,并且存储所有的状态[参考](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 直接成为去中心化交易所,当然这里假设的是订单通过其他平台进行匹配。还有一种替代方案,即让空间作为分布式容错交易所(包含订单簿),这可以算是对现有中心化加密币交易所的严格改进——现有交易所时不时会受到攻击。 +### 多用一体化 -空间也可以作为区块链版的企业及政府系统,其原本由一个或多个组织运行的特定服务,现在作为 TMSP 应用在某个空间上运行,从而在不放弃对底层服务控制的前提下,维持公共 Cosmos 网络的安全性及交互性。所以,Cosmos 或可为那些既想使用区块链技术,又不愿将控制权彻底放给分布式第三方的人,提供最佳的运行环境。 +Cosmos分区可以运行任意的应用逻辑,应用在分区创建时设定好,可通过管理者可以不断更新。这种灵活度使得Cosmos分区可以作为其他加密币的挂钩载体,比如以太坊或比特币,并且它还能和这些区块链的衍生品挂钩,利用同样的代码库,而在验证程序及初始分配有所区分。这样就允许多种现有加密币框架得以运行,如以太坊、Zerocash、比特币、CryptoNote等等,将其同Tendermint Core结合,成为通用网络中性能更优的共识引擎,为平台之间提供更多的交互机会。此外,作为多资产区块链,每笔交易都有可能包含多个输入输出项,其中每个输入项都可以是任意代币,使Cosmos直接成为去中心化交易所,当然这里假设交易订单通过其他平台进行匹配。替代方案是,让分区作为分布式容错交易所(包含买卖盘账目),这算是对中心化加密币交易所之上的严格改进——现行交易所在过去时常发生被攻击的事件。 + +分区也可以作为区块链版的企业及政府系统,其原本由一个或多个组织运行的特定服务,现在作为ABCI应用在某个分区上运行,从而在不放弃对底层服务控制的前提下,维持公共Cosmos网络的安全性及交互性。所以,Cosmos或可为那些既想使用区块链技术,又不愿彻底放弃控制权给分布式第三方的人,提供绝佳的运行环境。 ### 缓解网络分区问题 -有人认为像 Tendermint 这种支持一致性的共识算法有一个重大问题,那就是网络分割会导致没有一个分区拥有超过 ⅔ 的投票权(比如超过 ⅓ 在线下),而任何这类网络分割都将中止整个共识。而 Cosmos 架构可以缓解这个问题,它可以使用全球中心,但是空间实行地区自治,然后让每个空间的投票权按照正常的地理位置进行分布。比如,某个一般范例就有可能是针对个别城市或地区的,让他们在运行自己空间的同时,还能共享共同的中心(比如 Cosmos 中心),并且可以在因网络分区导致的中断期间,继续维持地区自治活动。请注意,这样一来在设计稳健的联邦式容错系统过程中,就可以真正地去考虑地理、政治及网络拓扑的特征了。 +有人认为像Tendermint这种支持一致性的共识算法有一个重大问题,就是网络分区会导致没有任何一个分区会拥有超过⅔的投票权(比如超过⅓投票权在线下),这会中断共识。而Cosmos架构可以缓解这个问题,它可以使用全球中心,同时,各分区实行地区自治,然后让每个分区的投票权按照正常的地域位置进行分配。如,一般范例就有可能是针对个别城市或地区的,让他们各自运行自己分区的同时,还能共享共同的枢纽(比如Cosmos枢纽),并且在临时的网络分区导致的中断期间,也可以继续维持地区自治活动。注意,这样一来在设计稳健的联邦式容错系统过程中,就可以去考虑真实的地理、政治及网络拓扑的特征了。 ### 联邦式名称解析系统 -NameCoin 是首批试图通过比特币区块链解决名称解析问题的区块链之一。不幸的是,这个方案存在一些不足。 +NameCoin是首批试图通过比特币技术解决名称解析问题的区块链之一。不过,这个方案存在一些不足。 -比如,我们可以通过 Namecoin 来验证*@satoshi*(中本聪)这个号是在过去某个时间点用特定公钥进行注册的。但是,该公约是否更新过我们就不得而知了,除非将该名称最后一次更新以来的全部区块都下载下来。这一点是因为比特币 UTXO 交易模式中梅克尔式模型的局限性所导致的,这类模型中只有交易(而非可变的应用程序状态)会以梅克尔形式加入到区块哈希中。它会让我们证明之后名称更新的存在,而非不存在。因此,我们必须依靠完整节点才能明确这个名称的最近价值,否则就要投入巨大成本来下载整个区块链。 +例如,我们可以通过Namecoin来验证@satoshi(聪)这个号是在过去某个时间点用特定公钥进行注册的。但是,该公钥是否更新过我们就不得而知了,除非将该名称最后一次更新之前的所有全部下载。这一点是比特币UTXO交易模式中默克尔化模型的局限性导致的,这类模型中只有交易(而非可变的应用状态)会以默克尔化加入到区块哈希中。它让我们得在之后用更新证明名称的存在,而非不存在。因此,我们必须依靠全节点才能明确这个名称的最近的值,或者花费大量资源下载整个区块链。 -即使在 NameCoin 运用了默克尔化的搜索树,其工作量证明的独立性还是会导致轻客戸端的验证出现问题。轻客戸端必须下载区块链中所有区块头的完整复件(或者至少是自其最后的名称更新后的所有区块头)。这意味着带宽需要会随着时间直线扩展。 [\[21\]](21)此外,在工作量证明制区块链上的名称更改需要等额外的工作量证明验证区块才能进行,这个在比特币上可能要花上一个小时。 +即使在NameCoin上运用默克尔化的搜索树,其工作量证明的独立性还是会导致轻客戸端的验证出现问题。轻客戸端必须下载区块链中所有区块头的完整备份(或者至少是自其最后的名称更新的所有区块头)。这意味着带宽需要随着时间做线性的扩展。 [21]此外,在工作量证明制度使区块链上的名称更改需要等额外的工作量证明验证确认才能进行,它在比特币上可能要花费一个小时。 -有了 Tendermint,我们只需用到由法定数量验证人签署(通过投票权)的区块哈希,以及与名称相关的当前价值的默克尔证明。这点让简易、快速、安全的轻客戸端名称价值验证成为可能。 -在 Cosmos 中,我们可以借助这个概念对其进行延伸。Cosmos 中的每个名称注册空间都能有一个相关的最高级别域名(TLD),比如".com"或者".org"等,每个名称注册空间都有其本身的管理和登记规则。 +有了Tendermint,我们只需用到由法定数量验证人签署(通过投票权)的区块哈希,以及与名称相关的当前值的默克尔证明。这点让简易、快速、安全的轻客戸端名称值验证成为可能。 -## 发行与激励 +在Cosmos中,我们可以利用这个概念并延伸。每一个在Cosmos上的名称注册都能有一个相关的最高级别域名(TLD),比如".com"或者".org"等,而且每个名称注册分区都有自己的管理和登记规则。 + +## 发行与激励 ### Atom 代币 -Cosmos Hub(Cosmos 中心)是多资产分布式账本,不过它也有本地代币,叫做 Atom。Atom 是 Cosmos Hub 唯一的权益代币。Atom 是持有人投票、验证或委托给其他验证人的许可证,就像以太坊的以太币以太币一样,Atom 也可以用来支付交易费以减少电子垃圾。额外的通胀 Atom 和区块交易费用就作为验证人及委托人(委托给其他验证人)的奖励。 +Cosmos 枢纽是多资产分布式账本,它有自己的代币,是 Atom 。Atom 是 Cosmos 枢纽唯一的权益代币。Atom是持有人投票、验证或委托给其他验证人的许可证明,就像以太坊上的以太币一样,Atom也可以用来支付交易费以减少电子垃圾。额外的通胀 Atom 和区块交易费用就作为激励分给验证人及委托验证人。 -BurnAtomTx 交易可以用来恢复储蓄池中任意比例的代币。 +`BurnAtomTx`交易可以用来恢复储蓄池中任意比例的代币。 #### 众筹 -创世块上的 Atom 代币及验证人的初次分布会是 Cosmos 众销资助人占 75%,预售资助人 5%,Cosmos 公司占 20%。从创世块开始,总 Atom 总量的 1/3 将作为奖励发放给每年绑定的验证人以及委托人。 +创世块上的 Atom 代币及验证人的初次分发是 Cosmos 众售参与者持有75%,预售参与者持有5%,Cosmos网络基金会持有10%,ALL IN BITS, 集团持有10%。从创世块开始,Atom 总量的1/3将作为奖励发放给每年担保持有的验证人以及委托人。 -额外细节详见 [Crowdfund Plan](https://github.com/cosmos/cosmos/blob/master/PLAN.md)。 +更多细节见 [Cosmos Plan](https://github.com/cosmos/cosmos/blob/master/PLAN.md) -#### 归属 +### 验证人的数量限制 -为了防止那些炒股诈骗的投机者借众筹来进行短期牟利,创世块的 Atom 必须有所归属才能用于转移。每个账户将在为期两年的时间里以每小时恒速授予 Atom,这个速率由创世块 Atom 总量除以(2 \* 365 \* 24)小时得出。通胀区块获得的 Atom 奖励是预先授予的,可以立即进行转移,因此第一年绑定的验证人及委托人可以挣取比其创世块 Atom 一半还多的奖励。 +与比特币或其他工作量证明区块链不同的是, 由于通信的复杂性增加, Tendermint 区块链会随着验证人的增加而变慢。幸运的是, 我们可以支持足够多的验证人来实现可靠的全球化分布式区块链, 并具有非常快的交易确认时间。 而且随着带宽、存储和并行计算容量的增加, 我们将来能够支持更多的验证人。 -### 验证人的数量上限 +在创世日, 验证人的最大数量将设置为 100, 这个数字将以13% 的速度增长10年, 最终达到300位。 -Tendermint 区块链和比特币之类的工作量证明区块链不同,由于通信复杂度提升,验证人增加,所以速度会更慢。所幸的是,我们可以支持足够多的验证人来实现全球稳健的分布式区块链,使其拥有较短交易验证时间,此外,在提升带宽、内存以及平行电脑计算能力的提升下,在未来支持更多验证人的参与。 +``` +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 +... +``` -在创世块诞生那天,验证人数量最多将设置为 100,之后十年的增长率将在 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 - ... -### 成为创世日后首个验证人 +Atom 持有者可以通过签署和提交 `BondTx` 交易成为验证人。抵押的 atom 数量不能为零。任何人任何时候都成为验证人, 除非当前验证人组的数量超过了最大值。在这种情况下, 只有当持有 atom 的数量大于现有验证人中持有有效 atom 数量的最少者, 该交易才有效, 其中有效 atom 包括受委托的 atom。当一个新的验证人以这种方式替换现有的验证人时, 现有的验证人将离线, 其所有的 atom 和受委托的 atom 进入解绑状态。 -如果 Atom 持有人还没有成为验证人,那么可以通过签署提交 BondTx 交易来成为验证人,其中作为抵押品的 Atom 数量不能为零。任何人在任何时候都可以作为验证人,除非当前验证组的数量超过了最大值。这样的话,除非 Atom 数量比最小验证人持有的有效 Atom(包括受委托的 Atom)还要多,那么交易才算有效。如果新验证人通过这种方式取代了现有验证人,那么现有验证人就被中止活动,所有 Atom 和受委托的 Atom 都会进入解绑状态。 +### 对验证人的惩罚 -### 针对验证人的惩罚 -针对验证人必须有一定的惩罚机制,防止他们有意无意地偏离已批准的协议。有些证据可以立即采纳,比如在同样高度和回合的双重签名,或者违反"预投票锁定"的(这一规则在 Tendermint 共识协议中有列出)。这类证据将导致验证人损失良好信誉,而且其绑定的 Atom 还有储备池内一定比例的代币份额——合起来称作其"权益"——也会减少。 +对于任何有意或无意的偏离认可协议的验证人, 必须对其施加一定的惩罚。有些证据立即可予受理, 比如在同样高度和回合的双重签名, 或违反 "预投票锁定" (Tendermint 协商一致议定书的规则)。这样的证据将导致验证人失去其良好的声誉, 其绑定的 atom 以及在储备池中的比例份额 – 统称为 “权益” – 将被大幅削减。 -有时因为地区网络中断、电力故障或者其他原因,验证人会无法连通。如果在过去随便什么时间点的 ValidatorTimeoutWindow 区块中,验证人在区块链中提交的投票没有超过 ValidatorTimeoutMaxAbsent 次,那么验证人将会被中止活动,并且从权益中共损失一定的验证人超时罚款(ValidatorTimeoutPenalty ,默认为 1%)。有些劣行表露的没那么明显,这样的话,验证人就可以在带外协调,强制叫停这类恶意验证人,如果有绝对多数制共识的话。 +有时, 由于区域网络中断、电源故障或其他原因, 验证人将不可用。如果在过去任意时间点的 `ValidatorTimeoutWindow` 块中, 验证人的提交投票不包括在区块链中超过 `ValidatorTimeoutMaxAbsent` 次, 该验证人将离线, 并减少 `ValidatorTimeoutPenalty` (默认 1%) 的权益。 -如果 Cosmos 中心因为超过 ⅓ 的投票权在线下合并而出现了中止情况,或者说超过 ⅓ 的投票权合并来审查进入区块链的恶意行为,这时候中心就必须借助硬分叉重组协议来恢复。(详见"分叉与审查攻击") + +一些 "恶意" 行为在区块链上并没有产生明显的证据。在这些情况下, 如果存在多数的协商一致, 则验证人可以在带外协调,强制将这些恶意验证人超时。 + + +如果 Cosmos 枢纽 因为超过⅓的投票权离线而出现了中止情况,或者说超过⅓的投票权审查到进入区块链的恶意行为,这时候枢纽就必须借助硬分叉重组协议来恢复。(详见“分叉与审查攻击”) ### 交易费用 -Cosmos Hub 验证人可以接受任何中共类的代币或组合作为处理交易的费用。每个验证人可以主观设置任意兑换率,并且选择它想要进行的交易,只要没有超过区块 Gas 限制(BlockGasLimit)。收集起来的费用剪去下面列出的任意税费后,会再次根据权益相关人绑定的 Atom 比例进行分配,周期是就是每次验证人支付的时间(ValidatorPayoutPeriod,默认为 1 小时)。 -在所有交易费用中,储存税(ReserveTax,默认为 2%)将存入储备池来增加储备量,来提高 Cosmos 网络的安全性及价值。普通税(CommonsTax,默认为 3%)合并到普通商品的资金中。这些资金将进入托管人地址(CustodianAddress)根据管理熊进行分配。将投票权委托给其他验证人的 Atom 持有人会支付一定佣金给委托方,而这笔费用可以由每个验证人进行设置。 +Cosmos 枢纽验证人可以接受任何种类的代币或组合作为处理交易的费用。每个验证人可自行设置兑换率, 并选择其想要的交易, 只要不超过 `BlockGasLimit`, 每隔 `ValidatorPayoutPeriod` (默认为1小时) 时间会根据权益相关人绑定的 Atom 比例进行分配。 + + +在所收取的交易费用中, `ReserveTax` (默认 2%) 将存入储备池来增加储备量, 增加 Cosmos 枢纽的安全性和价值。这些资金也可以按照治理系统的决策进行分配。 + + +将投票权委托给其他验证人的 Atom 持有人会支付一定佣金给委托方,而这笔费用可以由每个验证人进行设置。 ### 激励黑客 -Cosmos Hub 的安全是一组函数,涉及底层验证人的安全以及委托人的委托选择。为了鼓励发现并及时报告缺陷,Cosmos Hub 允许黑客通过 ReportHackTx 交易来"邀功",主要就是说明,"这个加点已被攻击,请将奖金发到这个地址"。通过这类功绩,验证人和委托人的行为将被中止,而黑客赏金地址可以收到每个人 Atom 中攻击奖励比率(HackRewardRatio,默认为 5%)。而验证人必须通过使用备份密钥来恢复剩余的 Atom。 +Cosmos 枢纽的安全取决于底层验证人的安全性和委托人的委托选择。为了鼓励发现和早期报告发现的漏洞, Cosmos 枢纽鼓励黑客通过 `ReportHackTx` 交易发布成功的漏洞, 说, "这个验证人被入侵了,请把赏金发送到这个地址"。这种情况下, 验证人和委托人将挂起闲置, 每个人 `HackPunishmentRatio` (默认 5%) 的 atom 将被削减, `HackRewardRatio` (默认 5%) 的 atom 将发送到黑客的赏金地址作为奖励。验证人必须使用其备份密钥来恢复剩余的 atom。 -为了防止这个特征被滥用于转移未授权的 Atom,ReportHackTx(黑客报告交易)前后验证人和委托人手中的两类 Atom 的比例(授权的与未授权的)将保持不变,而黑客的赏金将包含未授权的 Atom,如果有的话。 +为了防止这一特性被滥用于转移未授权的 atom, 在 `ReportHackTx` 前后,Atom的比例(授权的与未授权的) 将保持不变, 而黑客的赏金将包括一些未授权的 atom (如果有的话)。 -## 管理 +### 治理规范 -Cosmos Hub 通过分布式组织来运行,这类组织要求有一套完备的管理机制,从而协调区块链上的各类变动,比如系统变量参数,以及软件更新、规章更改等。 +Cosmos 枢纽是由一个分布式组织管理的, 需要一个明确的治理机制, 以协调对区块链的各种变化, 如系统的参数变量, 以及软件升级和宪法修订. -所有验证人对所有提案的投票负责。如果没能及时对提案做出投票,那么验证人就会在一段时间内自动失去活动权利,这段时间叫做缺席惩罚期(AbsenteeismPenaltyPeriod,默认为一周)。 +所有验证人负责对所有提案进行表决。如果未能及时对提案进行表决, 将导致验证人被自动停用一段时间。 这段时间被称为 `AbsenteeismPenaltyPeriod` (默认1周)。 -委托人自动继承委托验证人的投票权。这一投票可能会被手动覆盖掉。而未绑定的 Atom 是没有投票权的。 -每个提案都需要一定的保证金,即最低提案保证金(MinimumProposalDeposit )代币,这个可以是代币组合也可以是更多代币包括 Atom。对每一个提案,投票人可能会投票来取走保证金呢。如果超过一半的投票人选择取走保证金(比如,由于提案是垃圾信息之类),那么保证金就会进去储备池,除非有任何 Atom 被燃烧。 +委托人自动继承其委托的验证人的投票权。这一投票可以被手动覆盖掉。而未绑定的 Atom 是没有投票权的。 -对于每一个提案,投票人可能会投以下选项: -- 同意 -- 强烈同意 -- 反对 -- 强烈反对 -- 弃权 +每个提案都需要 `MinimumProposalDeposit` 代币的保证金, 这可能是一个或多个代币 (包括atom) 的组合。对于每项提案, 投票者可以投票表决取走保证金。如果超过半数的投票者选择取走保证金 (例如, 因为提案是垃圾信息), 那么保证金就会存入储备池, 除了被燃烧的 atoms。 -决定采纳(或不采纳)提案需要严格的多数投"同意"或"强烈同意"(或者"反对"及"强烈反对"),但是超过 1/3 的人投"强烈反对"或"强烈支持"的话就可以否决大多数人的决定。如果大多数人的票都被否决,那么每个人都会得到惩罚,即损失否决惩罚费用块那一部分钱( VetoPenaltyFeeBlocks,默认是一天的区块值 ,税费除外),而否决大多数决定的那一方也会受到额外的惩罚,即损失否决惩罚 Atom(VetoPenaltyAtoms,默认为 0.1%)。 +对于每项提案, 投票人可以选择下列方案: -### 参数改变提案 +* 同意 +* 强烈同意 +* 反对 +* 强烈反对 +* 弃权 -这里定义的任何参数都可以发生改变,主要在参数改变提案(ParameterChangeProposal)的接受范围内。 +决定采纳(或不采纳)提案需要严格的多数投“同意”或“强烈同意”(或者“反对”及“强烈反对”),但是超过1/3的人投“强烈反对”或“强烈支持”的话就可以否决大多数人的决定。如果大多数人票被否决,那么他们每个人都会失去 `VetoPenaltyFeeBlocks` (默认是一天的区块值 ,税费除外) 作为惩罚,而否决大多数决定的那一方还将额外失去 `VetoPenaltyAtoms` 默认为0.1%)的 Atom 作为惩罚。 + +### 参数变更提案 + + +这里定义的任何参数都可在 `ParameterChangeProposal` 通过后改变。 + +### 赏金提案 + + +通过 `BountyProposal` 后, Atom 可以增发和预留储备池资金作为赏金。 ### 文本提案 -所有其他提案,比如用来更新协议的提案,都会通过通用的文本提案(TextProposal)来协调。 -## 路线图 +所有其他提案,比如用来更新协议的提案,都会通过通用的 TextProposal 来协调。 -详见 [计划](https://github.com/cosmos/cosmos/blob/master/PLAN.md)。 -## 相关工作 +## 路线图 -过去几年,关于区块链共识及可扩展性已经有过多次创新,这一部分将挑选一些重要的创新进行简短分析。 + +详见[计划](https://github.com/cosmos/cosmos/blob/master/PLAN.md). + +## 相关工作 + +过去几年涌现了很多区块链共识及扩展性方面的创新。在这一部分中将挑选一些重要的创新进行简单分析。 ### 共识系统 #### 经典拜占庭容错 -二十世纪八十年代早期的共识机制中就已经出现了恶意参与者,当时 Leslie Lamport 杜撰了"拜占庭容错"这个词,用来指那些图谋不轨参与者做出的恣意妄为的行径,这个词和"死机故障"相对,死机故障就只是处理过程崩溃而已。早期针对同步网络也探索出了一些解决方案,其信息滞后有一个上限,尽管实际使用是在高度受控的环境下进行的(比如飞机控制器以及原子钟同步的数据中心)。直到九十年代后期,实用拜占庭容错(PBFT)才被引用,作为有效的、部分同步的共识算法,以容忍处理过程中 ⅓ 的恣意行为。PBFT 成为标准算法,繁殖了各种衍生品,包括最近 IBM 用于超级账本中的。 +二十世纪八十年代早期就开始研究存在恶意参与者的共识机制,当时Leslie Lamport创造了”拜占庭容错”这个词,用来指那些图谋不轨参与者做出的恶意的行为,与”死机故障”不同,后者只是处理过程崩溃而已。早期针对同步网络也探索出了一些解决方案,网络信息滞后有一个上限,但实际使用是在高度受控的环境下进行,比如精密飞行仪器以及使用原子钟同步的数据中心。直到九十年代后期,实用拜占庭容错( Practical Byzantine Fault Tolerance ,PBFT)[\[11\]][11]才作为有效的、部分同步的共识算法被逐步推广。它可以容忍⅓参与者有恶意行为。PBFT成为标准算法,催生了各种版本,包括最近由IBM提出并使用于Hyperledger超级账本中的算法。 -和 PBFT 相比,Tendermint 共识的主要好处在于其有一套经过改善且简化了的底层结构,其中有些是拥抱区块链范例的结果。Tendermint 区块必须按顺序提交,这一点可以消除复杂性,节省与 PBFT 浏览变化相关的通信开支。在 Cosmos 和众多加密币中,如果区块 N 本身没有提交,那么就无需让区块 N+i(i*>=1*)来提交。如果是带宽导致了区块 N 未提交到 Cosmos 空间,那么它就不会帮助使用带宽将选票共享给区块 N+i。如果是网络分区或者线下节点导致的区块 N 未提交,那么 N+i 就无论如何也不会提交。 +和PBFT相比,Tendermint共识的主要好处在于其改善且简化了的底层结构,其中有些是遵循了区块链典范的结果。Tendermint中,区块必须按顺序提交,这就消除复杂性,节省PBFT中状态变化相关的通信开支。在Cosmos和众多加密币中,如果区块N本身没有提交,那么就不能让它之后的区块N+i(i>=1)提交。如果是通信带宽限制导致了区块N未提交到Cosmos Zone上,那么将通信带宽用于分享选票给区块N+i是一种浪费。如果由于网络分区或者节点掉线导致的区块N未提交,那么N+i就无论如何也不能提交。 -此外,区块内交易的批量处理可以对应用程序的状态进行默克尔哈希,而不是用 PBFT 检查机制进行周期消化。这可以实现轻客戸端更快的证明交易提交,以及更快的跨区块链通信。 +此外,将交易打包成块可以用默克尔哈希纪录应用程序的状态,而不是用PBFT检查机制进行定时摘要。这可以让轻客戸端更快的提交交易证明,以及更快的跨链通信。 -Tendermint Core 中有很多优化项和特点都超过了 PBFT 特定的性能。比如,验证人发起的区块被分割成部分,默克尔化然后散布开来,这种方式可以提高其广播性能(关于启发请查看 LibSwift [\[19\]](19))。而且,Tendermint Core 不会对点对点连接做任何假设,只要点对点网络仍有微小连接,那么它就能正常运行。 +Tendermint Core中也优化了很多PBFT特性以外的功能。比如,验证人提交的区块被分割多个部分,对其默克尔化后,然后在节点间广播。这种方式可以提高其广播性能(具体请查看LibSwift [19])。而且,Tendermint Core不会对点对点连接做任何假设,只要点对点间的网络不断开,那么它就能正常运行。 -#### BitShare 委托权益 +#### BitShare委托权益 -BitShares [\[12\]](12)不是第一个部署权益证明机制(PoS)的区块链,但是其对 PoS 区块链的研究与采纳做出了巨大的贡献,尤其是这些被认为是"受委托的" PoS。在 BitShares 中,股权持有者选择"见证"以及"委托",其中"见证"负责下单并提交交易,而"委托"负责协调软件更新与参数变化。尽管 BitShare 在理想环境下的性能很高(100k tx/s,1 秒的滞后),但是它也有可能受到恶意见证施加的重复使用攻击,这类攻击会导致区块链出现分叉而不用受到任何经济惩罚——它的惩罚来自于"没有任何权益"。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 中的"仲裁集"被定义为一组节点,其中每一节点包含(是一个超集合)至少一个仲裁集碎片,这样就可以达成协议。 +Stellar [\[13\]][13]是以Ripple推行的解决方案为基础,它优化了联邦拜占庭协议模型,其中参与共识过程的并不构成一个固定的全局过程。 相反,每个进程节点组织一个或多个“仲裁片”,每个“仲裁片”构成一组可信进程。 Stellar中的“法定人数”被定义为一组包含至少个个节点的一个仲裁片。从而可达成一致。 -机制的安全性依靠假设实现,即假设任意两个仲裁集的交集并非为空,而实现节点的可用性则需要至少一个仲裁集碎片来完整组成正确节点,这个在使用或大或小的仲裁集碎片间可能会产生一组权衡,因为要在不对信任施加重要假设的情况下来进行平衡是很困难的。最终,节点必须以某种办法来选择充足的仲裁集碎片,从而保证有足够多的容错(或任何"完整无损的节点",大部分文章里得出的结果就依靠这个来决定)。此外,唯一用来确保这类配置的战略是等级制的,且和 BGP 协议相似(边界网关协议),可用于互联网高层 ISP 来创建全球路由表,或者是用在浏览器中管理 TSL(传输层安全)证书,不过这两者都因其不安全性而臭名昭著。 +恒星机制的安全性依赖于任何两个仲裁的交集都是非空的假设,同时,节点的可用性要求至少一个“仲裁片”完全由诚实节点组成。这就需要在“法定人数”的大小上作出妥协:人数过少难以获得共识,人数过多则难以信任所有人。可能难以平衡而不对信任做出重大假设。 除此之外,节点必须维护一定数量的仲裁片以获得足够的容错能力(或者任何“完整节点”,大部分结果的依赖),并且提供一种层级化的配置策略,类似于边界网关协议(Border Gateway Protocol,BGP)。BGP被互联网服务供应商(Internet Service Provider,ISP)用来建立全球路由表,也被浏览器用来管理传输层安全协议(Transport Layer Security,TLS)证书。他们都因为不安全而臭名昭着。 -Stellar 文章中对基于 Tendermint 的权益证明系统的批判可以通过以下代币战略加以缓和,其中有一种新的代币叫做"atom",可以通过发布这一代币来代表未来的费用及奖励。所以,Tendermint 权益证明机制的好处就是其简易性,在相对简易的同时,提供足够多且可证明的安全保障。 + +对于Stellar论文中基于Tendermint的PoS批评可以通过本文描述的代币策略来缓解。本文提出了名为 _atom_ 的新的代币,它代表未来交易过程中产生的费用和奖励。 基于Tendermint PoS的优势在于其原理相对简单,同时仍可充分保证和证明安全性。 #### BitcoinNG -BitcoinNG 是针对比特币提出来的一种改善,或将允许垂直扩展形式,比如增加区块容量,并且不会带来负面经济影响(一般这类变动都会带来这类影响),比如越小的矿工受到的影响越大。这一改善可以通过将是首项选择从交易广播中分离来实现,即由"微区块"中的工作量证明先选择群首,之后可以对要提交的交易进行广播,直到新的微区块被发现。这个过程会减少赢得工作量证明比赛所必须的带宽需求,让小矿工可以更公平的参与竞争,然后让最后找到微区块的矿工来定期提交交易。 +BitcoinNG是对比特币的改进,允许垂直扩展,比如增加块的大小而避免带来负面的经济后果,例如对矿工造成的影响严重不成比例。 这种改进是通过将leader选举与交易广播分开来实现的:leader首先通过“微块micro-blocks”的PoW选举,然后leader能够广播交易直至下一个新的“微块”。 这减少了赢得PoW比赛所需的带宽要求,使矿主竞争更公平,并通过允许最后一名矿工提交微块以加快交易提交频率。 #### Casper -Casper [\[16\]](16)是针对以太坊提出的一个权益证明共识算法。其最初运行模式是"赌注共识",理念就是让验证人反复对其认为会提交到区块链的区块下赌注(根据它之前打赌的经验来),最后得出结论。 [链接](https://blog.ethereum.org/2015/12/28/understanding-serenity-part-2-casper/)。这是 Casper 团队活跃中的研究领域,其挑战就是搭建一套进化稳定的打赌机制。和 Tendermint 相比,Casper 主要的优势就在于提供了"优于一致性的实用性"——其共识无需超过 ⅔ 的仲裁选票——可能其代价就是速度慢以及实现复杂。 +Casper [\[16\]][16]是以太坊提出的PoS共识算法。 它的主要操作模式是“预测押注”的一致。 通过让验证者基于他们目前看到的其他投注来迭代地下注他们认为哪个块将提交入区块链。最终性可以立即实现。 + +[链接](https://blog.ethereum.org/2015/12/28/understanding-serenity-part-2-casper/). 这是Casper团队研究的一个热点领域。 挑战在于构建一个可以证明是一个演化稳定策略的投注机制。 与Tendermint相比,Casper的主要优势可能在于提供“可用性超越一致性” — 达成共识不需要超过50%的投票权 — 可能是以提交速度或实现复杂性为代价的。 ### 水平扩展 -#### Interledger 协议 +#### Interledger协议 -Interledger 协议 [\[14\]](14)严格来说不算是可扩展解决方案。它通过一个松散耦合的双边关系网络,为不同账本系统提供了特别的互操作性。比如闪电网络,ILP 的目的就是促进支付,不过是以不同账本类型的支付为主,并且延展了原子交易机制,将哈希锁以及公证人的仲裁集(也叫做原子传输协议)都包括进去。用于维持账本间交易原子数的后面这种机制和 Tendermint 的轻客戸端 SPV 机制相似,因此就可以对 ILP 和 Cosmos/IBC 之间的区别加以解释了,具体如下: +Interledger协议(The Interledger Protocol,ILP)[\[14\]][14]不是一种严格的扩展方案。 它通过一个松散耦合的双边关系网络,提供一种指定的跨不同账本系统交互操作。像闪电网络一样,ILP的目的是实现支付,但是它特别关注跨账本类型的支付,并扩展原子事务的处理机制,使得事务的处理不仅支持哈希锁,而且还包括法定人数的公证人(称为原子运输协议)。 后者在账本间交易中实施原子性的机制与Tendermint的轻客户SPV机制类似,因此比较ILP和Cosmos / IBC之间的区别是有必要的,具体见下文。 -1. 在 ILP 中连接器的公证人不支持成员变动,并且不允许在公证人间进行灵活的权重。而 IBC 是专门为区块链设计的,其验证人可以有不同的权重,而且成员也可以根据区块链进程进行变动。 -2. 在闪电网络中,ILP 付款接收人必须在线来发送确认函给发送者。而在 IBC 代币传输过程中,接收人区块链的验证组会负责提供确认,而非接收人。 -3. 两者最大的不同就是 ILP 的连接器不充当支付状态的权威方,而在 Cosmos 中,一个中心的验证人就是 IBC 代币传输状态以及每个空间所持代币总量(不是空间每个账户所持代币总量)的权威见证人。这是一个根本性创新,允许代币在空间里进行安全且非对称的传输,而在 Cosmos 中,充当 ILP 连接器的角色是一个持久且安全的区块链账本——Cosmos 中心(Cosmos Hub)。 -4. ILP 账本间支付需要由一份交易订单簿做支持,因为其不包括代币从一个账本到另一个账本的非对称传输,而只有价值或市场等值在传输。 +1.ILP不支持连接器公证员的变更,也不允许公证员之间有灵活的权重。 另一方面,IBC是专门为区块链设计的,验证人可以拥有不同的权重,并且随着区块链的发展,成员可以随时更改。 + +2.与闪电网络一样,ILP中的接收人必须在线才能向发起人发送确认。 在IBC代币传输中,接收者所在区块链的验证人集合负责提供确认,而不是接收用户本人。 + +3.最大的不同在于ILP连接器不需要负责对支付状态保持权威性,然而在Cosmos中,hub的验证人负责IBC代币传输状态和每个zone内持有代币数量的权威性。允许从zone之间的安全地不对称地交换代币是本质的创新。在cosmos中的ILP连接器可以看作是一个持久和最安全的区块链分类账:cosmos hub。 + +4.ILP内的跨账本支付需要一个交易所的指令集的支持。因为不存在从一个分类账到另一个分类账不对称的代币转移,只能做到市场等价物的转移。 #### 侧链 -侧链 [\[15\]](15)是针对比特币网络扩展提出的一个机制,通过与比特币区块链"挂钩"的可替代区块链实现。侧链可以让比特币有效从区块链转移到侧链及后台,还可以在侧链上进行新功能测试。在 Cosmos Hub 中,侧链和比特币是彼此的轻客戸端,使用 SPV(安全协议验证工具)证明来决定什么时候转移代币到侧链及后台。当然,因为比特币采用工作量证明机制,所以以比特币为中心的侧链也遇到很多工作量证明作为共识算法而带来的问题与风险。此外,这个是比特币多数派解决方案,它并不支持本地代币以及空间内网络拓扑学,而 Cosmos 可以。也就是说,这种双向挂钩的核心机制在原则上是和 Cosmos 网络运用的机制一样的。 +Sidechains [\[15\]][15]是一种通过使用与比特币区块链“双向挂钩”替代区块链来扩展比特币网络性能的机制。(双向挂钩相当于桥接,在cosmos中被称为“桥接”与市场挂钩区分)。侧链使得比特币可以方便的在比特币区块链和侧链间移动,并允许在侧链上实验新功能。在Cosmos Hub中,侧链和比特币是彼此的轻客户端,在比特币区块链和侧链间移动时使用SPV证明。当然,由于比特币使用PoW,以比特币为中心的侧链遭受许多由于PoW作为共识机制的引起的问题和风险。而且,这是一个比特币利益最大化的解决方案,并不像Cosmos那样本地支持各种代币和zone间网络拓扑结构。但是,双向挂钩的核心机制原则上与Cosmos所采用的机制相同。 -#### 以太坊在可扩展方面的努力 +#### 以太坊扩展性的努力 -以太坊目前正在研究不同的方法来实现以太坊区块链状态的碎片化,从而解决可扩展问题。这些努力的目标就是在共享状态空间中维持当前以太坊虚拟机提供的抽象层。他们同时开展了多项研究。 [\[18\]](18) [\[22\]](22) -##### Cosmos vs 以太坊 2.0 Mauve +以太坊目前正在研究许多不同的战略,将以太坊区块链的状态分区化,以解决可扩展性的需求。 这些努力的目标是在共享状态空间之上,维持当前以太坊虚拟机提供抽象层。 目前,多项研究工作正在进行。[\[18\]][18] [\[22\]][22] -Cosmos 和以太坊 2.0 Mauve [\[22\]](22)有不同的设计目标。 +##### Cosmos vs Ethereum 2.0 Mauve -- Cosmos 专注代币。而 Mauve 是和扩展普通计算相关。 -- Cosmos 不一定是以太坊虚拟机,因此即使是不同的虚拟机也可以进行交互。 -- Cosmos 可以让空间创建者决定谁来验证空间。 -- 任何人都可以在 Cosmos 开创新空间(除非管理有其他决定)。 -- 中心会隔离空间故障,这样全球代币的不变性就得到了维护。 -### 普通扩展 +Cosmos 和 Ethereum 2.0 Mauve [\[22\]][[22] 有不同的设计理念。 + +* Cosmos是针对代币儿 Mauve 是关于扩大计算能力。 +* Cosmos不仅限于 EVM, 所以即使不同的VM也可以交互。 +* Cosmos 让zone的创建者决定验证人。 +* 任何人都可以在Cosmos中建立新的zone(除非管理者另做决定)。 +* hub与zone的失效隔离,所以全局的代币不变量可以保持。 + +### 普遍扩展 #### 闪电网络 -闪电网络是一种代币传输网络,在比特币区块链(及其他公共区块链)上一层运行,能够执行众多改善交易吞吐量的指令,通过将大多数交易从共式账本移出,转入所谓的"支付信道"内。这个举措通过链上的加密货币脚本或可实现,它可以让参与者进入双方有状态的合约,其中状态可以通过共享数字签名进行更新,并且最后将证据公布到区块链后可以关闭合约,这个机制最初是通过跨链原子掉期而普及的。通过开放多方支付信道,闪电网络的参与者可以成为他人支付的路由焦点,带领全面连接的支付信道网络,代价就是绑定在支付信道上的资本。 -虽然闪电网络可以轻松在多个单独区块链间延伸以通过交易市场进行价值传输,但是它无法用来进行区块链间的非对称代币传输。Cosmos 网络的主要好处就是能够进行这类直接代币传输。也就是说,我们可以期待支付信道与闪电网络在我们的代币传输机制下投入广泛应用,一方面为了节省成本,另一方面也可以保证隐私。 +闪电网络被设计成一种代币传输网络,在比特币区块链(及其他公有区块链)上一层运行,通过把大部分交易从共识分类账之外转移到所谓的“ 付款渠道“。 这通过链上加密货币脚本来实现,这些脚本使双方能够进入双方持有的状态化合同,通过共享数字签名来更新状态,并且在合同结束后最终通过在区块链上发布证据,这种机制首先受到跨链原子互换交易的欢迎。 通过与多方开通支付渠道,闪电网络的参与者可以成为集中点,为其他人的支付提供路由,从而导致支付渠道网络的完全联通,其代价是绑定在支付渠道上的资金。 -#### 隔离见证 +虽然闪电网络也可以轻松地跨越多个独立的区块链,并借助交易市场实现价值转移,但它不能实现从一个区块链到另一个区块链的非对称代币交易。 这里描述的Cosmos网络的主要优点是实现直接的代币交换。 也就是说,我们希望支付渠道和闪电网络将会与我们的代币传输机制一起被广泛采用,从而节省成本和保护隐私。 -隔离见证是一项比特币改善提议( [连接](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)),其目的是将每个区块的交易吞吐量提高 2-3 倍,并且同时保证区块快速同步新节点。这个方案的出色就在于它可以在比特币当前协议的限制下运行,并且允许进行软分叉更新(也就是其他旧版本的客户端软件在更新后可以继续运行)。Tendermint 是一种新的协议,它没有设计限制,因此可以有不同的扩展选择。首先,Tendermint 采用拜占庭容错循环制算法,这个是在加密签字而非挖矿的基础上进行,可以简单地通过多平行区块链进行水平扩展,而定期频繁的区块提交还可以进行垂直扩展。 +#### 隔离验证人 -## 附录 + +隔离见证是一个比特币改进建议BIP,[链接](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki) 旨在将每块交易吞吐量提高2倍或3倍,同时使新节点能更快同步区块。 这个解决方案的亮点在于它如何在比特币当前协议的局限下,允许软分叉升级(例如,具有旧版本软件的客户端将在升级后仍可继续运行)。 Tendermint作为一个新的协议没有设计的限制,所以它具有不同的扩展优先级。 TendermintBFT的循环算法,主要基于加密签名而不是采矿,这种算法允许通过多个并行区块链进行水平扩展,而更常规、更频繁的区块提交也允许垂直扩展。 + + + +## 附录 ### 分叉问责制 -如果遇到超过容错能力以及共识出错的情况下,设计周到的共识协议应该要对此提供一定的保障。这个在经济系统中尤为必要,经济系统中的拜占庭行为可以获取大量资金奖励。而针对上述情况有一个最为重要的保证就是分叉问责制,这个形式下,导致共识出错的进程(也就是导致协议客户端开始接受不同值——即分叉出现)可以被识别出并根据协议规定进行生发,或者也有可能受到法律制裁。当法律系统变得不可靠或者调用代价过大,那么验证人可能要强制支付安全保证金来参与,而一旦检测到恶意行为,那么这些保证金就会被吊销或者减少。 [\[10\]](10) +经过精心设计的共识协议应该能够在超出容错能力或者共识出错的情况下为系统提供一定的保障。这在能够通过拜占庭行为获取实质经济回报的金融系统中显得尤为必要。_分叉问责制_ 就属于这种非常重要的保障机制,这种制度会使一个导致共识出错的进程(例如使协议客户端开始接受不同值——即分叉)被识别出来,并且根据协议规则对其进行惩罚,甚至将其移送至司法系统处置。但当司法系统不可靠或者诉讼费极其昂贵时,为了让验证人们都参与到这个机制当中,该制度会强制要求他们建立安全保证金,一旦检测到恶意行为,那么这些保证金将会被罚没或者削减[\[10\]][10]. -注意这个和比特币有很大区别,由于网络同步及其发现哈希冲突的概率特性,比特币的分叉是定期出现的。在很多案例中,很难将恶意分叉与同步导致的分叉区分开来,所以比特币无法可靠地实施分叉问责制,除了让矿工为孤行区块挖矿支付隐形机会成本。 -### Tendermint 共识 -我们将投票阶段分为预投票及预提交阶段。一个选票可以用于特定区块或*Nil。*我们把同一轮超过 ⅔ 的单个区块的预投票总和称为*Polka*,把同一轮超过 ⅔ 的单个区块的预提交总和称为*Commit*。如果同一轮针对 Nil 的预提交超过 ⅔,那么它们就进入下一轮。 +注意这与比特币有所不同,由于网络非同步与局部哈希碰撞的概率特性,比特币的分叉是定期出现的。因为在很多情况下,恶意分叉与非同步引起的分叉是无法分辨的,除非让矿工为挖到的孤立区块支付隐性的机会成本,否则比特币就很难准确地执行分叉问责制。 -注意,协议中严格的决定论会招致较弱的同步假设,因为默认的首项必须被扣除且略过。因此,验证人在对 Nil 进行预投票前会等候一段时间(即*TimeoutPropose* ,超时提议),而*TimeoutPropose* 的值会随着每一轮的进行而增加。每一轮剩下的进程是完全同步的,在过程中只有验证人收到超过 ⅔ 的网络投票才会进入下一步。在实践中,这么做将需要超级强大的对手无限阻挠较弱的同步假设(导致共识无法提交区块),而且通过使用每个验证人的*TimeoutPropose* 随机值还可加大其难度。 +### Tendermint共识 -另一套约束,或者锁定规则会确保最终在每个高度只提交一个区块,任何试图提交超过一个区块到指定高度的恶意行为都会被识别出来。首先,每个区块的预提交必须正当,并且以 Polka 的形式提交。如果验证人已经在*R_1*轮预提交了一个区块,那么我们就认为它们被锁定在了这个区块,然后用于验证*R_2*轮新预提交动作的 Polka 必须进入 R_polka 轮,其中 `R_1 < R_polka <= R_2`。第二,验证人必须提出并且/或者预投票它们被锁定的区块。这两步合起来,就可以确保验证人不会在没有充足证据证明正当性的前提下进行预提交,并且保证已经完成预提交的验证人不能再为其他东西的预提交贡献证明。这样不但可以保证安全,还能保证共识算法的活跃。 -关于这一协议的全部细节参考 [这里](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm)。 +我们把投票阶段分为 _预投票_ 和 _预提交_ 两个阶段。一个投票可以是用于特定区块,也可以用于 _Nil_。我们把同一轮超过⅔的单个区块的预投票总和称为 _Polka_ ,把同一轮超过⅔的单个区块的预提交总和称为 _Commit_。如果在同一轮中对Nil的预提交超过⅔,他们就进入到下一轮中。 -### Tendermint 轻客戸端 -本来需要同步所有区块头,现在在 Tendermint-PoS 里取消了这一需求,因为我们有替代链(分叉),也就是说可以削减超过 ⅓ 的绑定权益。当然,削减也是需要有人共享分叉证据的,所以轻客戸端就要存储任何它见证的区块哈希提交。此外,轻客戸端可以定期同步验证组的变动,以避免出现 [远距离攻击](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#preventing-long-range-attacks)(除了其他可能实现的解决方案)。 +注意到协议中的严格的确定性会引发一个弱同步假设,因为错误的发起者必须被检测到并将其略过。验证人们在为Nil预投票之前会等待一段时间,称之为超时提议,这个超时提议的hi等待时间也会随着每一轮的进行而递增。每一轮的进行都是完全不同步的,在这个过程中,只有当验证人收听到超过⅔的网络投票才能进入到下一轮中。实际上,它需要极其强大的阻碍才能阻挠这个弱同步假设(导致无达成共识,无法提交区块),而且通过每个验证人超时提议(TimeoutPropose)的随机值还可更加提高这么做的难度。 -秉着和以太坊类似的想法,Tendermint 能够让应用程序在每个区块中嵌入全球梅克尔根哈希,可以进行简单且可验证的状态查询,比如查询账户余额、合约存放价值,或者未使用交易输入的存在等,具体由应用程序的特性决定。 +另一个附加的约束条件,或者叫锁定条例,它能确保网络最终在每个高度只提交一个区块。任何试图在给定高度提交超过一个区块的恶意行为都会被识别出来。首先,一个区块的预提交必须被认为是正当的,并且以Polka的形式提交。如果验证人已经准备在R_1轮中预提交一个区块,我们称他们锁定了这个区块,并且然后用于验证R_2轮新预提交动作的Polka必须进入R_polka轮,其中 R_1 < R_polka <= R_2。其次,验证人们必须为他们锁定的区块提议并且(或者)预投票。这两个条件共同作用,确保了验证人在对其正当性没有充分论证的情况下不能进行预提交操作,并且保证已经完成预提交的验证人不能再为其他东西的预提交贡献证明投票。这样不但可以保证共识算法的安全性,还能保证它的活跃度。 -### 预防远距离攻击 -假设有一套充足的可复原的广播网络集合以及一组静态验证组,那么任何区块链分叉都可以被检测到,而且发起攻击的验证人提交的保证金会被扣除。这一创新是由 Vitalik Buterin 于 2014 年首次提出的,可以解决其他权益证明加密货币"没有任何权益"的问题(详见 [相关工作](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#related-work)部分。)但是,由于验证组必须要可以更改,所以可能在很长一段时内最初的验证人可能会解除绑定,因此就可以自由从创世区块中创建新链,并且不需要任何费用,因为他们不再有锁定的保证金。这类攻击被称为远距离攻击(LRA),和短距离攻击相反,短距离攻击是当前处于绑定中的验证人导致的分叉,并且会因此受到惩罚(假设有类似 Tendermint 共识这样的分叉问责制拜占庭容错算法)。长距离攻击经常被认为是对权益证明机制的重要打击。 +关于这一协议的全部细节参考[这里](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm). -所幸的是,LRA 可以通过以下方法得以缓解。第一,针对解绑(来恢复抵押的保证金并且不再挣取参与共识的费用)的验证人,保证金在一定时间内必须是不可转移的,可以称作"解绑期",可能长达数周或数月。第二,针对轻客戸端的安全,其首次连接到网络必须根据可信源验证最近的一个或者最好是多个区块哈希。这种情况有时也被称作"薄弱主观性"。最后,为了保证安全,必须频繁同步最新验证组(每次间隔时间和解绑期一样)。这一点可以保证轻客戸端在验证人解绑资金(从而没有任何权益)前知道验证组的变化情况,否则解绑的验证人就会通过实施远距离攻击来欺骗客户端,在其绑定的高度创建新区块起始返回点(假设它可以控制足够多的早期私钥)。 +### Tendermint轻客户端 -注意,用这种方式解决 LRA 问题需要对工作量证明模型原始的安全问题进行彻底检查。在 PoW 中,轻客戸端被认为可以在任何时候从可靠的创世区块同步到当前高度,这个过程可以简单的在每个区块头上处理工作量证明来完成。但是,为了解决 LRA,我们需要轻客戸端上线定期追踪验证组的变动,并且其首次上线必须尤其要注意根据可靠源来验证其从网络收集的情报。当然,后面这个要求和比特币的类似,在比特币中,协议和软件也必须从可靠源获得。 +因为生成侧链(一个分叉)意味着至少⅓的担保权益被罚没,Tendermint权益证明(Tendermint-PoS)取消了同步所有区块头的要求。当然,罚没保证金也是需要有人共享分叉证据的,所以轻客戸端就要存储任何它见证的区块哈希提交。另外,轻客戸端可以定期地与验证人组的改变保持同步,以避免出现远程攻击(但是其他解决方案也具有可能性)。 -上述预防 LRA 的方法正好适合 Tendermint 驱动下的区块链验证人及全部节点,因为这些节点的任务就是保持连接到网络。这个方法也适合那些想要经常进行网络同步的轻客戸端。但是,对不希望频繁接入互联网或区块链网络的轻客戸端来说,还有另一种方法可以用来解决 LRA 问题。非验证人的代币持有者额可以在很长的解绑期内(比如比验证人的解绑期久)发布代币作为抵押品,并且为轻客戸端提供第二级解决方案来证实当前及过去区块哈希的有效性。就算这些节点没有计入区块链共识的安全性时,他们还是可以为轻客戸端提供强大的保障。如果历史区块哈希查询在以太坊中得到过支持,那么任何人都可以用特定的智能合约来绑定他们的代币,并且提供付费证明服务,从而有效地针对轻客戸端 LRA 安全问题开发出一个市场。 -### 克服分叉及审查攻击 +与以太坊类似,Tendermint能够让应用程序在每个区块中嵌入一个全局默克尔根的哈希值,可以简单方便的验证的状态查询,比如查询账户余额、在智能合约中的值,或者未使用交易输出(UTXO)的存在,具体由应用程序的特性决定。 -由于区块提交的定义如此,所以任何超过 ⅓ 的投票联合都可以通过下线或者不广播选票来中止区块链运行。这样的联合也可以通过拒绝包含这类交易的区块来检查特定交易,尽管这或将导致大多数区块提案被拒绝,从而减缓区块提交速率,降低实用性及价值。恶意联合或许会陆陆续续地广播选票,以阻挠区块链的区块提交,将其逼停,或者就是参与到这些攻击的组合攻击中。最后,它会通过双重签名或者违反锁定规则来造成区块链分叉。 +### 远程攻击的防御 -如果一个全球活跃的对手参与进来,那么就会使网络分割,导致验证组子集出现拉低速率。这不单单是 Tendermint 面临的限制,也是所有共识协议(其网络可能由活跃对手控制)的限制。 -对这几类攻击,验证人子集应该在外部进行协调,签署重组提议来选择分叉(及牵扯到的任何证据)以及验证人的初始子集。签署这份重组提议的验证人会放弃他们在其他分叉上的所有抵押品。客户端要验证重组提议上的签名,验证任何证据并且做出判断或者给端用户提示来做决定。比如,一个手机钱包 APP 可能会提示用户安全警告,而电冰箱可能会接受签署超过 ½ 的初始验证人提出的重组提议。 +假设有一个足够有弹性的广播网络采集与一组静态验证人组存在,那么任何区块链分叉都可以被检测到,而且发起攻击的验证人提交的保证金会被罚没。这个由Vitalik Buterin在2014年首次提出的新方法,解决了其他权益证明加密货币的"没有任何相关权益"问题(详见 [相关工作部分](#related-work))。但由于验证人组必须是能够变化的,在较长的时间段内最初的一些验证人会解除j押金绑定,这就使他们可以自由地从创世区块中创建新链,并且因为他们不再有被锁定的保证金,他们将不需要为这个行为支付任何费用。这类攻击被称为远程攻击(LRA),与短程攻击相比,后者对处在押金绑定中的验证人发起的分叉是可以对其进行惩罚的(假设有类似Tendermint共识这样的分叉问责制拜占庭容错算法)。所以远程攻击经常被认为是对权益证明机制的一种危险打击。 -任何非同步的拜占庭容错算反都不能进入共识,如果超过 ⅓ 的投票不诚实的话,而且出现分叉就说明已经有超过 ⅓ 的投票权因为无正当理由的双重签名或改锁而失信。因此,签署重组提议是一个协调性问题,无法通过任何非同步协议(即自动的,不对底层网络可靠性做假设的)来解决。目前,我们认为重组提议的协调问题,可以通过互联网媒体的社会共识来实现人为协调。验证人必须确保在签订重组提议前不会出现网络分割,以此来避免有两个相冲突的重组提议被强叔的情况出现。 -加入外部条件媒介以及协议足够稳健的话,那么分叉的问题就没有检查攻击问题来的严重。 +幸运的是,远程攻击(LRA)可以用以下的途径来缓解。第一,对于解除绑定的验证人而言(取回抵押保证金并且不再从参与共识中获取费用),保证金在一定时间内不能转移,也可以称其为"解绑周期",这个周期可能长达数周或数月。第二,对于轻客户端的安全性而言,其首次连接到网络时必须根据可信源验证最新的一个区块哈希或者多个最好的区块哈希。这种情况有时被称为"弱主观性"。最后,为了保证安全,必须与最新的验证人组进行频繁的同步,时长与解绑周期一致。这样就确保了轻客戸端在因验证人解绑资金而失去任何权益之前,知道验证人组的变化情况,否则解绑的验证人就会在其绑定的高度后面开始创建新区块来实施远程攻击,以此来欺骗客户端(假设它可以控制足够多的早期私钥)。 -分叉和检查需要有超过 ⅓ 的拜占庭投票权,此外超过 ⅔ 的投票权联合可能会恣意提交无效状态。这个是任何拜占庭容错共识系统的特点。和双重签名不同,双重签名会利用简单的可验证的证据来创建分叉,而要检测无效状态的提交则需要非验证对等节点来验证整个区块,也就是说它们会保留一份本地状态副本,执行每笔交易,然后独立计算状态根。一旦检测出来,处理这类故障的唯一方法就是社会共识。比如,如果比特币出现问题,那么无论是软件漏洞造成的分叉,还是矿工拜占庭行为提交的无效状态(正如 2015 年 7 月),连接紧密的商户、开发者、矿工以及其他组织组成的社区按照所需分工来参与修复网络。此外,因为 Tendermint 区块链的验证人或可进行身份认证,所以如果有这个想法,那么无效状态的提交也可能会受到法律或其他外部法律体系的惩罚。 +注意到用这样的方式来对抗远程攻击(LRA)需要对工作量证明(proof-of-work)的原始安全模块进行彻底检查。在工作量证明中(PoW),一个轻客户端可以通过在每一个区块头中运行工作量证明,以此简便地在任何时候与可信任的创始区块的当前高度进行同步。但是,为了对抗远程攻击(LRA),我们需要轻客戸端定期上线追踪验证人组的变动,其中在首次上线时必须格外仔细地根据可靠源来验证从网络采集的信息。诚然,后面这个要求和比特币类似,其协议和软件也必须从可靠源获得。 -### TMSP 具体说明 -TMSP(Tendermint Socket 协议)由三个主要信息类型组成,这三类信息从核心传递到应用程序上,然后应用程序用相关回复信息做出应答。 +以上这些为了防止远程攻击的方法,比较好地适用于由Tendermint驱动下的区块链验证人节点以及全节点,因为这些节点需要保持与网络的连接。这些方法同样适用于希望频繁地与网络同步的轻客户端。但是,对于那些不希望频繁接入互联网或者区块链网络的轻客户端来说,还有另一种方法可以解决远程攻击的问题。非验证人节点可以在很长的解绑期内(比如比验证人的解绑期更久)使用代币作为保证金,并且为轻客戸端提供二级证明当前有效性以及过去区块哈希的解决方案。虽然这些代币对于区块链共识的安全性并没有价值,不过他们还是可以为轻客戸端提供强大的保障。如果在以太坊中支持历史区块哈希查询,那么任何人都可以用特定的智能合约来绑定他们的代币,并且提供付费证明服务,从而有效地针对轻客戸端LRA安全问题开发出一个市场。 -AppendTx 信息是应用程序的主力。区块链上的每笔交易都通过这个信息来传递。应用程序需要借助 AppendTx 信息在当前状态、应用程序协议以及交易加密证书中进行验证,验证每笔接收的交易。验证过的交易之后需要更新应用状态——通过绑定一个值到关键价值存储库中,或者更新 UTXO 数据库。 +### 克服分叉与审查攻击 -CheckTx 信息和 AppendTx 信息类似,但是只针对交易验证。Tendermint Core 的内存池会先用 CheckTx 检查交易有效性,并且只会将有效的交易分程传递给对等节点。应用程序可能会检查交易中的递增新鲜值,如果新鲜值过期就会借助 CheckTx 返回一个错误。 +由于提交区块流程的定义,任何联合后不少于⅓的投票权的节点都可以通过下线或者不广播选票来中止区块链运行。这样的联合也可以通过拒绝包含这些交易的区块来审查特定的交易,尽管这将导致大多数区块提案被拒绝,致使区块提交速率减缓,降低了它的实用性与价值。恶意的联合或许仍然会陆陆续续地广播选票,用阻挠区块链的区块提交来将其逼停,或者使用任何这些攻击的组合攻击。最终,它会通过双重签名或者违反锁定规则来造成区块链分叉。 -Commit 信息是用来计算当前应用状态上的加密提交项的——之后会存入下一区块头。这包含一些方便的特性。状态更新的矛盾性将以区块链分叉的形式出现,从而捕捉整个阶段的程序错误。这也简化了安全轻客戸端的开发,因为梅克尔哈希证明可以通过检查区块哈希来加以验证,而区块哈希是通过验证人仲裁集加以签署的(通过投票权)。 +如果一个全球活跃的作恶者也参与进来,就会用可能出现错误的验证组人子集导致速度降低的方法来分割网络。这不只是Tendermint面临的局限性,更确切地说是所有被活跃敌对者控制了网络的共识协议所面临的局限性1。 -额外的 TMSP 信息允许应用程序追踪改变验证组,并让应用程序接收高度、提交的选票之类的区块信息。 -TMSP 的要求/回应是简单的 Protobuf 信息。请参考这里的 [模式文件](https://github.com/tendermint/abci/blob/master/types/types.proto)。 +对于这些类型的攻击,验证人的子集应该通过外部的方式进行协调,以签署选择一个分叉(及关系到的所有证据)与带有签名的验证人的初始子集的重组提案。签署了这样一份重组提案的验证者,将放弃在所有其他分叉上属于他们的保证金。客户端应在重组提案中验证签名以及任何相关的证据,并作出判断或提示终端用户作出决定。例如,一个手机钱包app应该在可能接受任何由一半以上的初始验证人们通过投票权利签署的重组提案时,给予用户安全警告提示。 -#### AppendTx(附加交易) +当多于⅓的投票权益是不诚实的时候,一个非同步的拜占庭容错算法步能够达成共识,然而,分叉假设不少于⅓的投票权益已经因不正当的双重签名或者锁定改变而成为不诚实的。因此,签署重组提案是一个协调问题,任何非同步协议都无法解决这个问题(也就是自动的,并且不考虑底层网络的可靠性)。目前,我们通过互联网媒体的社会共识,把重组提案的协调问题留给了用户去协调。验证人必须在签署重组提案之前就确保没有出现网络分割的问题,以此来避免签署两个相冲突的重组提议的情况发生。 -- **命令行参数** : - - Data (\[]byte): 所需交易的字节 -- **返回** : - - Code (uint32): 回复代码 - - Data (\[]byte): 结果字节,如果有的话 - - Log (string): 调试或出错信息 -- **使用** : - 附加并运行一笔交易。如果交易有效,那返回 CodeType.OK -#### CheckTx(检查交易) +假设外部协调媒介和协议是可靠的,对于分叉的担心会比审查攻击要少许多。 -- **命令行参数** : - - Data (\[]byte): 所需交易的字节 -- **返回** : - - Code (uint32): 回复代码 - - Data (\[]byte): 结果字节,如果有的话 - - Log (string): 调试或出错信息 -- **使用** : -验证一笔交易。这个信息不应该改变状态。交易在广播给内存池层对等节点前,首先通过 CheckTx 运行。你可以发起半状态化 CheckTx,并在 Commit or BeginBlock 上清算状态,以允许执行同一区块中相关的交易序列。 +除了需要大于⅓的拜占庭投票权益才能启动的分叉和审查制度以外,超过⅔的联合投票权益可能会提交任意、无效的状态。这是任何拜占庭容错算法的共识系统所特有的问题。与利用简单可验证证明来创建分叉的双重签名不同,检测无效状态的提交需要非验证节点来验证整个区块,这意味着非验证节点会保留一份本地的状态副本并执行每一笔交易,然后为他们自己独立计算出状态的根源。一旦检测出来,处理这类故障的唯一方法就是社会共识。打一个比方,在比特币出现问题的情况下,无论是由于软件漏洞造成的分叉(正如2013年3月),还是由于矿工拜占庭行为提交的无效状态(正如2015年7月),由商户、开发者、矿工和其他组织组成的联系紧密的社区所建立起来的社会共识会让他们按照分工来参与到修复网络的工作当中去。此外,由于Tendermint区块链的验证人身份是可识别的,那么如果需要的话,无效状态的提交实际上是可以被法律或其他外部法律体系惩治的。 -#### Commit(提交) +### ABCI说明 -- **返回** : - - Data (\[]byte): 梅克尔根哈希 - - Log (string): 调试或出错信息 -- **使用** : - 返回应用程序状态梅克尔根哈希。 -#### Query(查询) +ABCI由3种主要的信息类型组成,这三类信息从共识引擎传递到应用程序上,然后应用程序用相应回复信息做出应答。 -- **命令行参数** : - - Data (\[]byte): 查询需要的字节 -- **返回** : - - Code (uint32): 回复代码 - - Data (\[]byte): 查询回复字节 - - Log (string): 调试或出错信息 -#### Flush(划掉) +`AppendTx` 信息是应用程序的主要传递媒介。区块链中的每一笔交易都通过这个信息来传递。应用程序需要验证每笔交易,这将通过接收针对当前状态、应用协议和交易密码证书的AppendTx信息来实现。验证过的交易将需要通过添加数值到键值存储或者更新UTXO数据库的方式来更新应用状态。 -- **使用** : -划掉回复队列。使用 types.Application 的应用程序无需实施这条信息——这个由项目进行处理。 +`CheckTx`信息与AppendTx信息类似,但它只是为了交易验证。Tendermint Core的内存池会先用CheckTx验证交易有效性,并且只会将有效的交易传递给其他的节点。应用程序会检查交易序列号,如果序列号过期就会根据CheckTx返回一个错误。 -#### Info(信息) +`Commit`信息是用来计算之后会存入到下一区块头中的当前应用状态的加密提交项。这具有便利的特性。状态的前后矛盾性将会像引起程序错误,从而导致区块链分叉。这也简化了安全轻客戸端的开发,因为默克尔哈希证明可以通过检查区块哈希来加以验证,而区块哈希是由规定人数的验证人们签署的(通过投票权益)。 -- **返回** : - - Data (\[]byte): 信息的字节 -- **使用** : -返回关于应用程序状态的信息。Application specific. -#### SetOption(设置选项) +此外,ABCI信息允许应用程序保持追踪验证人组的改变,并让应用程序接收诸如高度和提交选票之类的区块信息。 -- **命令行参数** : - - Key (string): 设置密钥 - - Value (string): 密钥设置的值 -- **返回** : - - Log (string): 调试或出错信息 -- **使用** : - 设置应用选项。比如,针对内存池的连接可以将密钥设置为"mode"(模式),价值为"mempool"(内存池)。或者针对共识连接,将密钥设置为"mode",价值设置为"consensus"(共识)。其他选项根据可具体应用进行专门设置。 -#### InitChain(初始链) +ABCI请求/响应是简单的Protobuf信息。请参考这里的[模式文件](https://github.com/tendermint/abci/blob/master/types/types.proto)。 -- **命令行参数** : - - Validators (\[]Validator): 初始创世验证人 -- **使用** : - 在创世块生成后进行调用 +##### AppendTx + * __命令行参数__: + * `Data ([]byte)`: 交易请求信息 + * __Returns__: + * `Code (uint32)`: 回复代码 + * `Data ([]byte)`: 结果字节,如果有的话 + * `Log (string)`: 错误信息 + * __使用__: + 提交并执行一笔交易,如果交易有效,那返回CodeType.OK -#### BeginBlock(起始块) +##### CheckTx + * __命令行参数__: + * `Data ([]byte)`: 交易请求信息 + * __Returns__: + * `Code (uint32)`: 回复代码 + * `Data ([]byte)`: 结果字节,如果有的话 + * `Log (string)`: 错误信息 + * __使用__: + 验证一笔交易。这个信息不应该改变应用状态。交易在广播给其他节点前,首先通过CheckTx运行。你可以发起半状态化CheckTx,并在Commit or BeginBlock上清算状态,以允许序列执行同一区块中相关的交易。 + +##### Commit + * __返回值__: + * `Data ([]byte)`: 默克尔根值 + * `Log (string)`: 调试或出错信息 + * __使用__: + 返回当前应用状态。 -- **命令行参数** : - - Height (uint64): 区块刚开始的高度 -- **使用** : - 为新区块的开始提供信号。在任何附加交易(AppendTxs)前进行调用。 +##### Query + * __命令行参数__: + * `Data ([]byte)`: 请求数据 + * __返回值__: + * `Code (uint32)`: 回复代码 + * `Data ([]byte)`: 查询回复字节 + * `Log (string)`: 调试或出错信息 -#### EndBlock(结束区块) +##### Flush + * __使用__: + 刷新回复队列。应用types.Application的应用程序无需实施这条信息——这个由项目进行处理。 -- **命令行参数** : - - Height (uint64): 结束时的区块高度 -- **返回** : - - Validators (\[]Validator): 具有新选票的变动后的验证人(归零就去除) -- **使用** : - 为区块结束提供信号。在每次提交前所有交易后调用。 +##### Info + * __返回值__: + * `Data ([]byte)`: 信息字节串 + * __使用__: + 返回关于应用程序状态的信息. 应用指定。 -更多细节请参考 [TMSP 知识库](https://github.com/tendermint/abci)。 +##### SetOption + * __参数__: + * `Key (string)`: 设置参数 + * `Value (string)`: 参数值 + * __返回值__: + * `Log (string)`: Debug or error message + * __使用__: + 比如,针对内存池的连接可以将键设置为"mode"(模式),值为"mempool"(内存池)。或者针对共识连接,将键设置为"mode",值设置为"consensus"(共识)。其他选项根据可具体应用进行专门设置。 + +##### InitChain + * __参数__: + * `Validators ([]Validator)`: 初始化创世验证人 + * __使用__: + 在创世区块创建时调用 -### IBC 包交付确认 +##### BeginBlock + * __参数__: -发送人可能会需要接收链提供包交付确认,这个原因有很多。比如,发送人可能不了解目的链的状态,如果有问题的话也不得而知。或者,发送者可能会想要向包强加一次超时(借助 MaxHeight 即最大值包域),而目的链可能会遇到拒绝服务攻击,比如接受包数量突然暴增。 -在这些案例中,发送人可以通过在 AckPending 上设置初始包状态来要求提供交付确认。接着就由接收链通过在应用程序的梅克尔哈希中包括一个缩写的 IBCPacket 来确认交付。 + * * `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 是公布在"Hub"(中心)上用来证明"Zone1"(空间 1)上的 IBCPacket 的存在的。假如 IBCPacketTx 的值如下: -- FromChainID: "Zone1" -- FromBlockHeight: 100 (假如) -- Packet: an IBCPacket: - - Header: an IBCPacketHeader: - - SrcChainID: "Zone1" - - DstChainID: "Zone2" - - Number: 200 (假如) - - Status: AckPending - - Type: "coin" - - MaxHeight: 350 (假如"Hub"目前的高度是 300) - - Payload: <The bytes of a "coin" payload>(一个"代币"的有效负荷字节) +首先,一个`IBCBlockCommit`和`IBCPacketTx`是被上传到“枢纽”上用来证明"分区1"上的`IBCPacket`的存在的。假设`IBCPacketTx`的值如下: -第二步,IBCBlockCommit 和 IBCPacketTx 被公布到"Zone2"(空间 2)来证明 IBCPacket 存在于"Hub"上。假如 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: "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>(一个"代币"相同的有效负荷字节) -接下来,"Zone2"必须将缩写的包放入其应用程序哈希中来显示 AckSent 的最新状态。 +- `FromChainID`: "Zone1" +- `FromBlockHeight`: 100 (假设) +- `Packet`: an `IBCPacket`: + - `Header`: an `IBCPacketHeader`: + - `SrcChainID`: "Zone1" + - `DstChainID`: "Zone2" + - `Number`: 200 (假设) + - `Status`: `AckPending` + - `Type`: "coin" + - `MaxHeight`: 350 (假设"枢纽"目前的高度是300) + - `Payload`: <一个"代币"的有效负荷字节> -IBCBlockCommitand 和 IBCPacketTx 会公布到"Hub"上来证明缩写的 IBCPacket 存在于"Zone2"上。假如 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>(相同"代币"有效负荷的哈希字节) +其次,一个`IBCBlockCommit` 和 `IBCPacketTx`被传输都“分区2”上用来证明`IBCPacket`在“枢纽”上的存在。假设`IBCPacketTx`的值如下: -最后,"Hub"必须更新从 AckPending 到 AckReceived 的包的状态。这个最新状态的证据要回到"Zone2"。假如 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: 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>(相同"代币"有效负荷的哈希字节) -同时,"Zone1"可能会积极地假设"代币"包的交付是成功的,除非"Hub"上有证据给出相反的证明。在上述例子中,如果"Hub"没有从"Zone2"接收到区块 350 的 AckSent 状态,那么它就会自动将这个设置到 Timeout(超时)。这个超时证据可以贴回到"Zone1"上,然后就可以返回任意代币。 +- `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+的。 +Tendermint/Cosmos生态支持的两种默克尔树:简单树和IAVL+树。 -#### 简易版梅克尔树 +#### 简易版默克尔树 -简易版梅克尔树针对的元素的静态列表。如果项的数目不是 2 的次方,那么有些树叶就会在不同的层。简易树试图让树的两侧在同一高度,但是左边可能会稍大一点。这种梅克尔树就是用于一个区块交易的梅克尔化的,而顶层元素就是应用状态根。 - * - / \ - / \ - / \ - / \ - * * - / \ / \ - / \ / \ - / \ / \ - * * * h6 - / \ / \ / \ - h0 h1 h2 h3 h4 h5 +简易版默克尔树针对基础的静态列表。如果项目的数量不是2的次方,那么有些树叶就会在不同的层上。简易树试图让树的两侧在同一高度,但是左边可能会稍大一点。这种默克尔树就是用于一个区块交易的默克尔化的,而顶层元素就是应用状态的根。 - A SimpleTree with 7 elements +``` + * + / \ + / \ + / \ + / \ + * * + / \ / \ + / \ / \ + / \ / \ + * * * h6 + / \ / \ / \ + h0 h1 h2 h3 h4 h5 -#### IAVL+梅克尔树 + A SimpleTree with 7 elements +``` -IAVL+数据结构的目的是永久储存应用状态中的密钥-值,这样具有决定功能的梅克尔根哈希就可以有效进行运算了。这个树的平衡通过 [AVL 算法](http://en.wikipedia.org/wiki/AVL_tree)的一个变量来达到,所有运行都是 O(log(n))。 +#### IAVL+树 -在 AVL 树中,任意节点两个子树的高度至少有一处不同。无论什么时候出现更新来打破这种情况,这个树都会通过创造 O(log(n))新节点(指向旧树上未修改的节点)来再次达到平衡。在初始 AVL 算法中,内部节点也可以维持密钥-值。AVL+算法(注意这里有个"+"号)对 AVL 算法进行了修改,来维持所有树叶节点上的值,同时还只需采用分支-节点来存储密钥。这一点在简化算法的同时,还能维持较短的梅克尔哈希轨迹。 +IAVL+数据结构的目的是永久储存应用状态中的密钥对,这样就可以对确定的默克尔根哈希进行高效的运算。这个树的平衡通过 AVL算法的变体来实现,所有运行都是O(log(n))。 -AVL+树类似于以太坊的 [Patricia tries](https://en.wikipedia.org/wiki/Radix_tree)(帕氏树)。其中也有一定的折中。密钥不需要在嵌入到 IAVL+树前生成哈希,而这个就为密钥空间里提供了较快的命令迭代,或许为很多应用程序都带去了好处。逻辑实现很简单,只需要两种节点——内部节点和树叶节点。作为一个平衡的二进制树,梅克尔证明算是短的。而另一方面,IAVL+树有取决于命令的更新。 +在AVL树中,任意节点的两个子树的高度至多有一处不同。无论在什么时候这种情况都是与更新相违背的,这个树都会通过创造O(log(n))新节点(指向旧树上未修改的节点)来再次达到平衡。在初始的AVL算法中,内部节点也可以保留密钥值对。AVL+算法(注意这里有个"+"号)对AVL算法进行了修改,来维持所有数值都在树叶节点上,同时还只需采用分支-节点来存储密钥。这样在维持较短的默克尔哈希轨迹对的同时,还简化了算法。 + +AVL+树类似于以太坊的[帕氏树](http://en.wikipedia.org/wiki/Radix_tree)。其中也有一定的折中。密钥不需要在嵌入到IAVL+树之前生成哈希,所以这就为密钥空间提供了较快的命令迭代,这会为很多应用程序带来好处。逻辑实现更简单,只需要内部节点和树叶节点这两种节点类型。作为一个平衡的二叉树,其默克尔证明平均更短。而另一方面,IAVL+树的默克尔根有取决于命令的更新。 +我们将支持额外有效的默克尔树,比如当二元变量可用时的以太坊帕氏树。 -我们将支持额外有效的梅克尔树,比如以太坊的帕氏树,同时实现二进制变量的可用性。 ### 交易类型 -在标准实现中,交易通过 TMSP 界面流入 Cosmos Hub 的应用程序。 -Cosmos Hub 将接受几类主要交易,包括 SendTx,BondTx,UnbondTx,ReportHackTx,SlashTx,BurnAtomTx,ProposalCreateTx,以及 ProposalVoteTx(即发送交易、绑定交易、解绑交易、攻击报告交易、削减交易、Atom 燃烧交易,创建提议交易),这些都不需要加以寿命,会在之后文章更新中进行归档。这里我们主要列举两个主要的 IBC 交易类型:IBCBlockCommitTx 以及 IBCPacketTx(即 IBC 区块提交交易以及 IBC 包交易) +在标准的执行中,交易通过ABCI界面涌入Cosmos Hub的应用程序中。 + +Cosmos Hub将接收几类主要的交易类型,包括`SendTx`, `BondTx`, `UnbondTx`, `ReportHackTx`, `SlashTx`, `BurnAtomTx`, +`ProposalCreateTx`,以及`ProposalVoteTx`(发送交易、绑定交易、解绑交易、攻击报告交易、削减交易、Atom燃烧交易,创建提案交易、以及提案投票交易),这些都不需要加以说明,会在未来版本的文档中加以备案。这里我们主要列举两个主要的IBC交易类型: `IBCBlockCommitTx` 以及`IBCPacketTx`(即IBC区块提交交易以及IBC数据包交易) + +#### IBCBlockCommitTx -#### IBCBlockCommitTx(IBC 区块提交交易) IBCBlockCommitTx 交易主要由这些组成: -- ChainID (string): 区块链 ID -- BlockHash (\[]byte): 区块哈希字节,就是梅克尔根(包括应用程序哈希) -- BlockPartsHeader (PartSetHeader): 区块部分设置的头字节,只用于验证投票签名 -- BlockHeight (int): 提交高度 -- BlockRound (int): 提交回合 -- Commit (\[]Vote): 超过 ⅔ 的 Tendermint 预提交投票,以组成区块提交项 -- ValidatorsHash (\[]byte): 新验证组的梅克尔树根哈希 -- ValidatorsHashProof (SimpleProof): 简易版梅克尔树证明,在区块哈希中证明验证人哈希 -- AppHash (\[]byte): IAVL 树,应用程序状态的梅克尔树根哈希 -- AppHashProof (SimpleProof): 简易版梅克尔树证明,在区块哈希中验证应用程序哈希 +- `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(IBC 包交易) +#### IBCPacketTx -IBCPacket 由下列项组成: -- Header (IBCPacketHeader): 包头 -- Payload (\[]byte): 包有效负荷字节。_可选择_。 -- PayloadHash (\[]byte): 包字节哈希。_可选择。_ +`IBCPacket` 由下列项组成: -有效负荷或有效负荷哈希必须存在一个。IBCPacket 的哈希就是两个项的简易版梅克尔根,即头和有效负荷。没有完整有效负荷的 IBCPacket 被称作缩写版包。 +- `Header (IBCPacketHeader)`: 数据包头 +- `Payload ([]byte)`: 数据包有效负荷字节。可选择。 +- `PayloadHash ([]byte)`: 数据包字节哈希。可选择。 -IBCPacketHeader 由下列项组成: -- SrcChainID (string): 源区块链 ID -- DstChainID (string): 目标区块链 ID -- Number (int): 所有包特定数量 -- Status (enum): 可以是 AckPending,AckSent,AckReceived,NoAck,或 Timeout 任意一个 -- Type (string): 种类根据应用程序决定。Cosmos 保留"coin"(币)包种类。 -- MaxHeight (int): 如果状态不是这个高度给出的 NoAckWanted 或者 AckReceived ,那么状态就算超时。_可选择。_ -IBCPacketTx 交易有下列项组成: +有效负荷`Payload`或有效负荷哈希`PayloadHash`必须存在一个。`IBCPacket` 的哈希就是两个项的简易版默克尔根,即头和有效负荷。没有完整有效负荷的`IBCPacket` 被称作 _缩写版包_ 。 -- 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"。 -PacketProof 必须有正确的梅克尔证明路径,如下: +`IBCPacketHeader`由下列项组成: - IBC/// +`SrcChainID (string)`: 源区块链 ID +DstChainID (string)`: 目标区块链ID +Number (int)`: 所有数据包的唯一数字 +Status (enum)`:可以是AckPending,AckSent,AckReceived,NoAck,或Timeout任意一个 +Type (string)`: 种类根据应用程序决定。Cosmos保留"coin"(币)包种类。 +`MaxHeight (int)`: 如果状态不是这个高度给出的`NoAckWanted` 或者`AckReceived` ,那么状态就算超时。可选择。 -当"Zone1"想要向"Zone2"通过"Hub"发送证明,那么 IBCPacket 的数据是相同的,无论这个包是在"Zone1"、 "Hub"还是"Zone2"上梅克尔化的。唯一可变的域只有追踪交付的 Status (状态),如下所示。 +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 -感谢朋友及同行在概念成型与检查方面提供的帮助,以及对我们同 Tendermint 及 Cosmos 工作的支持。 +`IBCPacketTx` 交易有下列项组成: +- `FromChainID (string)`: 提供给这个数据包的区块链ID,不是源所必须的 +- `FromBlockHeight (int)`: 区块链高度,其中接下来的包会包含在(默克尔化的)源链的区块哈希中 +- `Packet (IBCPacket)`:数据包, 其状态可能是`AckPending`, `AckSent`, `AckReceived`, `NoAck`, 或者 `Timeout`其中的一个 +- `PacketProof (IAVLProof)`: IAVL树默克尔证明,用于验证在给定高度下的源链应用哈希中的数据包哈希 -- [SkuChain](http://www.skuchain.com/)的 [Zaki Manian](https://github.com/zmanian)在 格式和措辞方面提供了很多支持,尤其是 TMSP 部分。 -- Althea and Dustin Byington 的 [Jehan Trembac](https://github.com/jtremback) [k](https://github.com/jtremback) 在初始迭代方面帮助。 -- [Honey Badger](https://eprint.iacr.org/2016/199)的 [Andrew Miller](https://soc1024.com/) 对共识部分给予的反馈。 -- [Greg Slepak](https://fixingtao.com/)对共识及措辞给予的反馈。 -- 同时还要感谢 [Bill Gleim](https://github.com/gleim)和 [Seunghwan Han](http://www.seunghwanhan.com/)的各种支持与贡献。 -- \*\*此处还有您及您的组织对本文的贡献。 -## 引用 +通过"Hub",将数据包从"Zone1"发送到"Zone2"的序列,描述在{Figure X}函数中。首先,一个`IBCPacketTx`会向"Hub"证明数据包是包含在"Zone1"的应用程序状态中。然后,另一个`IBCPacketTx` 会向"Zone2"证明数据包包含在"Hub"的应用程序状态中。在这个过程中,`IBCPacketTx` 的字段是相同的:`SrcChainID`永远是"Zone1",而`DstChainID` 永远是"Zone2"。 -- [1] Bitcoin: -- [2] ZeroCash: -- [3] Ethereum: -- [4] TheDAO: -- [5] Segregated Witness: -- [6] BitcoinNG: -- [7] Lightning Network: -- [8] Tendermint: -- [9] FLP Impossibility: -- [10] Slasher: -- [11] PBFT: -- [12] BitShares: -- [13] Stellar: -- [14] Interledger: -- [15] Sidechains: -- [16] Casper: -- [17] TMSP: -- [18] Ethereum Sharding: -- [19] LibSwift: -- [20] DLS: -- [21] Thin Client Security: -- [22] Ethereum 2.0 Mauve Paper: +The `PacketProof` must have the correct Merkle-proof path, as follows: + +`PacketProof` 必须有正确的默克尔证明路径,如下: + +``` +IBC/// + +``` + + +当“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 index 62707c080..f77608072 100644 --- a/docs/resources/whitepaper.md +++ b/docs/resources/whitepaper.md @@ -1003,7 +1003,7 @@ 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 revoked, or slashed, +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 diff --git a/docs/sdk/clients.md b/docs/sdk/clients.md index 4d02d3c90..5e5a9c7ac 100644 --- a/docs/sdk/clients.md +++ b/docs/sdk/clients.md @@ -98,7 +98,7 @@ When you query an account balance with zero tokens, you will get this error: `No The following command could be used to send coins from one account to another: ```bash -gaiacli send \ +gaiacli tx send \ --amount=10faucetToken \ --chain-id= \ --name= \ @@ -111,7 +111,7 @@ The `--amount` flag accepts the format `--amount=`. ::: tip Note You may want to cap the maximum gas that can be consumed by the transaction via the `--gas` flag. -If set to 0, the gas limit will be automatically estimated. +If you pass `--gas=simulate`, the gas limit will be automatically estimated. Gas estimate might be inaccurate as state changes could occur in between the end of the simulation and the actual execution of a transaction, thus an adjustment is applied on top of the original estimate in order to ensure the transaction is broadcasted successfully. The adjustment can be controlled via the `--gas-adjustment` flag, whose default value is 1.0. ::: @@ -131,7 +131,7 @@ gaiacli account --block= You can simulate a transaction without actually broadcasting it by appending the `--dry-run` flag to the command line: ```bash -gaiacli send \ +gaiacli tx send \ --amount=10faucetToken \ --chain-id= \ --name= \ @@ -142,7 +142,7 @@ gaiacli send \ Furthermore, you can build a transaction and print its JSON format to STDOUT by appending `--generate-only` to the list of the command line arguments: ```bash -gaiacli send \ +gaiacli tx send \ --amount=10faucetToken \ --chain-id= \ --name= \ @@ -153,7 +153,7 @@ gaiacli send \ You can now sign the transaction file generated through the `--generate-only` flag by providing your key to the following command: ```bash -gaiacli sign \ +gaiacli tx sign \ --chain-id= \ --name= unsignedSendTx.json > signedSendTx.json @@ -162,7 +162,7 @@ gaiacli sign \ You can broadcast the signed transaction to a node by providing the JSON file to the following command: ``` -gaiacli broadcast --node= signedSendTx.json +gaiacli tx broadcast --node= signedSendTx.json ``` ### Staking @@ -180,13 +180,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 stake validators +gaiacli query validators ``` If you want to get the information of a single validator you can check it with: ```bash -gaiacli stake validator +gaiacli query validator ``` #### Bond Tokens @@ -194,7 +194,7 @@ gaiacli stake validator On the testnet, we delegate `steak` instead of `atom`. Here's how you can bond tokens to a testnet validator (*i.e.* delegate): ```bash -gaiacli stake delegate \ +gaiacli tx delegate \ --amount=10steak \ --validator=$(gaiad tendermint show-validator) \ --name= \ @@ -212,7 +212,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 stake delegation \ +gaiacli query delegation \ --address-delegator= \ --validator= ``` @@ -220,7 +220,7 @@ gaiacli stake delegation \ Or if you want to check all your current delegations with disctinct validators: ```bash -gaiacli stake delegations +gaiacli query delegations ``` You can also get previous delegation(s) status by adding the `--height` flag. @@ -230,17 +230,17 @@ You can also get previous delegation(s) status by adding the `--height` flag. If for any reason the validator misbehaves, or you just want to unbond a certain amount of tokens, use this following command. You can unbond a specific `shares-amount` (eg:`12.1`\) or a `shares-percent` (eg:`25`) with the corresponding flags. ```bash -gaiacli stake unbond begin \ +gaiacli tx unbond begin \ --validator= \ --shares-percent=100 \ --from= \ --chain-id= ``` -Later you must complete the unbonding process by using the `gaiacli stake unbond complete` command: +Later you must complete the unbonding process by using the `gaiacli tx unbond complete` command: ```bash -gaiacli stake unbond complete \ +gaiacli tx unbond complete \ --validator= \ --from= \ --chain-id= @@ -251,7 +251,7 @@ gaiacli stake unbond complete \ Once you begin an unbonding-delegation, you can see it's information by using the following command: ```bash -gaiacli stake unbonding-delegation \ +gaiacli query unbonding-delegation \ --address-delegator= \ --validator= \ ``` @@ -259,7 +259,7 @@ gaiacli stake unbonding-delegation \ Or if you want to check all your current unbonding-delegations with disctinct validators: ```bash -gaiacli stake unbonding-delegations +gaiacli query unbonding-delegations ``` You can also get previous unbonding-delegation(s) status by adding the `--height` flag. @@ -269,7 +269,7 @@ You can also get previous unbonding-delegation(s) status by adding the `--height A redelegation is a type delegation that allows you to bond illiquid tokens from one validator to another: ```bash -gaiacli stake redelegate begin \ +gaiacli tx redelegate begin \ --address-validator-source= \ --address-validator-dest= \ --shares-percent=50 \ @@ -279,10 +279,10 @@ gaiacli stake redelegate begin \ Here you can also redelegate a specific `shares-amount` or a `shares-percent` with the corresponding flags. -Later you must complete the redelegation process by using the `gaiacli stake redelegate complete` command: +Later you must complete the redelegation process by using the `gaiacli tx redelegate complete` command: ```bash -gaiacli stake unbond complete \ +gaiacli tx unbond complete \ --validator= \ --from= \ --chain-id= @@ -293,7 +293,7 @@ gaiacli stake unbond complete \ Once you begin an redelegation, you can see it's information by using the following command: ```bash -gaiacli stake redelegation \ +gaiacli query redelegation \ --address-delegator= \ --address-validator-source= \ --address-validator-dest= \ @@ -302,7 +302,7 @@ gaiacli stake redelegation \ Or if you want to check all your current unbonding-delegations with disctinct validators: ```bash -gaiacli stake redelegations +gaiacli query redelegations ``` You can also get previous redelegation(s) status by adding the `--height` flag. @@ -331,7 +331,7 @@ 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 gov submit-proposal \ +gaiacli tx submit-proposal \ --title= \ --description=<description> \ --type=<Text/ParameterChange/SoftwareUpgrade> \ @@ -346,14 +346,14 @@ gaiacli gov submit-proposal \ Once created, you can now query information of the proposal: ```bash -gaiacli gov query-proposal \ +gaiacli query proposal \ --proposal-id=<proposal_id> ``` Or query all available proposals: ```bash -gaiacli gov query-proposals +gaiacli query proposals ``` You can also query proposals filtered by `voter` or `depositer` by using the corresponding flags. @@ -363,7 +363,7 @@ 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 gov deposit \ +gaiacli tx deposit \ --proposal-id=<proposal_id> \ --depositer=<account_cosmos> \ --deposit=<200steak> \ @@ -378,7 +378,7 @@ gaiacli gov deposit \ After a proposal's deposit reaches the `MinDeposit` value, the voting period opens. Bonded `Atom` holders can then cast vote on it: ```bash -gaiacli gov vote \ +gaiacli tx vote \ --proposal-id=<proposal_id> \ --voter=<account_cosmos> \ --option=<Yes/No/NoWithVeto/Abstain> \ @@ -391,7 +391,7 @@ gaiacli gov vote \ Check the vote with the option you just submitted: ```bash -gaiacli gov query-vote \ +gaiacli query vote \ --proposal-id=<proposal_id> \ --voter=<account_cosmos> ``` @@ -401,7 +401,7 @@ gaiacli gov query-vote \ You can get the current parameters that define high level settings for staking: ``` -gaiacli stake parameters +gaiacli query parameters ``` With the above command you will get the values for: @@ -420,7 +420,7 @@ All this values can be updated though a `governance` process by submitting a par A staking `Pool` defines the dynamic parameters of the current state. You can query them with the following command: ``` -gaiacli stake pool +gaiacli query pool ``` With the `pool` command you will get the values for: diff --git a/docs/sdk/core/app2.md b/docs/sdk/core/app2.md index 9a689cca6..6477617fb 100644 --- a/docs/sdk/core/app2.md +++ b/docs/sdk/core/app2.md @@ -139,8 +139,8 @@ Amino can also be used for persistent storage of interfaces. To use Amino, simply create a codec, and then register types: ``` -func NewCodec() *wire.Codec { - cdc := wire.NewCodec() +func NewCodec() *codec.Codec { + cdc := codec.New() cdc.RegisterInterface((*sdk.Msg)(nil), nil) cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil) cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil) @@ -175,7 +175,7 @@ func (tx app2Tx) GetMsgs() []sdk.Msg { } // Amino decode app2Tx. Capable of decoding both MsgSend and MsgIssue -func tx2Decoder(cdc *wire.Codec) sdk.TxDecoder { +func tx2Decoder(cdc *codec.Codec) sdk.TxDecoder { return func(txBytes []byte) (sdk.Tx, sdk.Error) { var tx app2Tx err := cdc.UnmarshalBinary(txBytes, &tx) diff --git a/docs/sdk/core/app3.md b/docs/sdk/core/app3.md index fea42d449..b84313adc 100644 --- a/docs/sdk/core/app3.md +++ b/docs/sdk/core/app3.md @@ -285,7 +285,7 @@ it can't increment sequence numbers, change PubKeys, or otherwise. A `bank.Keeper` is easily instantiated from an `AccountMapper`: ```go -bankKeeper = bank.NewKeeper(accountMapper) +bankKeeper = bank.NewBaseKeeper(accountMapper) ``` We can then use it within a handler, instead of working directly with the @@ -336,7 +336,7 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Set various mappers/keepers to interact easily with underlying stores accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount) - bankKeeper := bank.NewKeeper(accountMapper) + bankKeeper := bank.NewBaseKeeper(accountMapper) feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees) app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper)) diff --git a/docs/sdk/core/examples/app1.go b/docs/sdk/core/examples/app1.go index ba33c6120..137983455 100644 --- a/docs/sdk/core/examples/app1.go +++ b/docs/sdk/core/examples/app1.go @@ -57,6 +57,7 @@ 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" } // Implements Msg. Ensure the addresses are good and the // amount is positive. diff --git a/docs/sdk/core/examples/app2.go b/docs/sdk/core/examples/app2.go index 5f23abe07..3ff9d1dea 100644 --- a/docs/sdk/core/examples/app2.go +++ b/docs/sdk/core/examples/app2.go @@ -13,8 +13,8 @@ import ( "github.com/tendermint/tendermint/libs/log" bapp "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" ) const ( @@ -25,8 +25,8 @@ var ( issuer = ed25519.GenPrivKey().PubKey().Address() ) -func NewCodec() *wire.Codec { - cdc := wire.NewCodec() +func NewCodec() *codec.Codec { + cdc := codec.New() cdc.RegisterInterface((*sdk.Msg)(nil), nil) cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil) cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil) @@ -77,6 +77,7 @@ type MsgIssue struct { // Implements Msg. func (msg MsgIssue) Type() string { return "issue" } +func (msg MsgIssue) Name() string { return "issue" } // Implements Msg. Ensures addresses are valid and Coin is positive func (msg MsgIssue) ValidateBasic() sdk.Error { @@ -196,7 +197,7 @@ func (tx app2Tx) GetSignature() []byte { } // Amino decode app2Tx. Capable of decoding both MsgSend and MsgIssue -func tx2Decoder(cdc *wire.Codec) sdk.TxDecoder { +func tx2Decoder(cdc *codec.Codec) sdk.TxDecoder { return func(txBytes []byte) (sdk.Tx, sdk.Error) { var tx app2Tx err := cdc.UnmarshalBinary(txBytes, &tx) diff --git a/docs/sdk/core/examples/app3.go b/docs/sdk/core/examples/app3.go index a423e3743..9a101c281 100644 --- a/docs/sdk/core/examples/app3.go +++ b/docs/sdk/core/examples/app3.go @@ -7,8 +7,8 @@ import ( "github.com/tendermint/tendermint/libs/log" bapp "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" ) @@ -31,7 +31,7 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Set various mappers/keepers to interact easily with underlying stores accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount) - bankKeeper := bank.NewKeeper(accountMapper) + bankKeeper := bank.NewBaseKeeper(accountMapper) feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees) app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper)) @@ -51,12 +51,12 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { } // Update codec from app2 to register imported modules -func UpdatedCodec() *wire.Codec { - cdc := wire.NewCodec() +func UpdatedCodec() *codec.Codec { + cdc := codec.New() cdc.RegisterInterface((*sdk.Msg)(nil), nil) cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil) cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil) - auth.RegisterWire(cdc) + auth.RegisterCodec(cdc) cryptoAmino.RegisterAmino(cdc) return cdc } diff --git a/docs/sdk/core/examples/app4.go b/docs/sdk/core/examples/app4.go index be4e5f4f4..e4fa515ee 100644 --- a/docs/sdk/core/examples/app4.go +++ b/docs/sdk/core/examples/app4.go @@ -7,8 +7,8 @@ import ( "github.com/tendermint/tendermint/libs/log" bapp "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" ) @@ -29,7 +29,7 @@ func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Set various mappers/keepers to interact easily with underlying stores accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount) - bankKeeper := bank.NewKeeper(accountMapper) + bankKeeper := bank.NewBaseKeeper(accountMapper) // TODO keyFees := sdk.NewKVStoreKey("fee") @@ -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 *wire.Codec, accountMapper auth.AccountMapper) sdk.InitChainer { +func NewInitChainer(cdc *codec.Codec, accountMapper auth.AccountMapper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { stateJSON := req.AppStateBytes diff --git a/docs/sdk/sdk-by-examples/simple-governance/app-codec.md b/docs/sdk/sdk-by-examples/simple-governance/app-codec.md index 9d35b4363..d994ba19c 100644 --- a/docs/sdk/sdk-by-examples/simple-governance/app-codec.md +++ b/docs/sdk/sdk-by-examples/simple-governance/app-codec.md @@ -5,13 +5,13 @@ Finally, we need to define the `MakeCodec()` function and register the concrete types and interface from the various modules. ```go -func MakeCodec() *wire.Codec { - var cdc = wire.NewCodec() - wire.RegisterCrypto(cdc) // Register crypto. - sdk.RegisterWire(cdc) // Register Msgs - bank.RegisterWire(cdc) - simplestake.RegisterWire(cdc) - simpleGov.RegisterWire(cdc) +func MakeCodec() *codec.Codec { + var cdc = codec.New() + codec.RegisterCrypto(cdc) // Register crypto. + sdk.RegisterCodec(cdc) // Register Msgs + bank.RegisterCodec(cdc) + simplestake.RegisterCodec(cdc) + simpleGov.RegisterCodec(cdc) // Register AppAccount cdc.RegisterInterface((*auth.Account)(nil), nil) diff --git a/docs/sdk/sdk-by-examples/simple-governance/app-constructor.md b/docs/sdk/sdk-by-examples/simple-governance/app-constructor.md index 76c8a7a14..149e97277 100644 --- a/docs/sdk/sdk-by-examples/simple-governance/app-constructor.md +++ b/docs/sdk/sdk-by-examples/simple-governance/app-constructor.md @@ -33,7 +33,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.NewKeeper(app.accountMapper) +app.bankKeeper = bank.NewBaseKeeper(app.accountMapper) 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)) ``` diff --git a/docs/sdk/sdk-by-examples/simple-governance/app-rest.md b/docs/sdk/sdk-by-examples/simple-governance/app-rest.md index 7e8469ad6..828d52c85 100644 --- a/docs/sdk/sdk-by-examples/simple-governance/app-rest.md +++ b/docs/sdk/sdk-by-examples/simple-governance/app-rest.md @@ -13,7 +13,7 @@ var SimpleGovAppInit = server.AppInit{ } // SimpleGovAppGenState sets up the app_state and appends the simpleGov app state -func SimpleGovAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { +func SimpleGovAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { appState, err = server.SimpleAppGenState(cdc, appGenTxs) if err != nil { return diff --git a/docs/sdk/sdk-by-examples/simple-governance/app-structure.md b/docs/sdk/sdk-by-examples/simple-governance/app-structure.md index 7cec5aa5c..ce9ba5fef 100644 --- a/docs/sdk/sdk-by-examples/simple-governance/app-structure.md +++ b/docs/sdk/sdk-by-examples/simple-governance/app-structure.md @@ -28,7 +28,7 @@ Then, let us define the structure of our application. // Extended ABCI application type SimpleGovApp struct { *bam.BaseApp - cdc *wire.Codec + cdc *codec.Codec // keys to access the substores capKeyMainStore *sdk.KVStoreKey diff --git a/docs/sdk/sdk-by-examples/simple-governance/intro.md b/docs/sdk/sdk-by-examples/simple-governance/intro.md index ab4b5b6a5..fe1c7639e 100644 --- a/docs/sdk/sdk-by-examples/simple-governance/intro.md +++ b/docs/sdk/sdk-by-examples/simple-governance/intro.md @@ -28,7 +28,7 @@ Before getting in the bulk of the code, we will start by some introductory conte + [Types](module-types.md) + [Keeper](module-keeper.md) + [Handler](module-handler.md) - + [Wire](module-wire.md) + + [Wire](module-codec.md) + [Errors](module-errors.md) + Command-Line Interface and Rest API * [Command-Line Interface](module-cli.md) diff --git a/docs/sdk/sdk-by-examples/simple-governance/module-cli.md b/docs/sdk/sdk-by-examples/simple-governance/module-cli.md index b7e3143e3..4cdf331a9 100644 --- a/docs/sdk/sdk-by-examples/simple-governance/module-cli.md +++ b/docs/sdk/sdk-by-examples/simple-governance/module-cli.md @@ -14,7 +14,7 @@ The CLI builds on top of [Cobra](https://github.com/spf13/cobra). Here is the sc ) // Main command function. One function for each command. - func Command(codec *wire.Codec) *cobra.Command { + func Command(codec *codec.Codec) *cobra.Command { // Create the command to return command := &cobra.Command{ Use: "actual command", diff --git a/docs/sdk/sdk-by-examples/simple-governance/module-codec.md b/docs/sdk/sdk-by-examples/simple-governance/module-codec.md new file mode 100644 index 000000000..68b03b936 --- /dev/null +++ b/docs/sdk/sdk-by-examples/simple-governance/module-codec.md @@ -0,0 +1,13 @@ +## Codec + +**File: [`x/simple_governance/codec.go`](https://github.com/cosmos/cosmos-sdk/blob/fedekunze/module_tutorial/examples/simpleGov/x/simple_governance/codec.go)** + +The `codec.go` file allows developers to register the concrete message types of their module into the codec. In our case, we have two messages to declare: + +```go +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(SubmitProposalMsg{}, "simple_governance/SubmitProposalMsg", nil) + cdc.RegisterConcrete(VoteMsg{}, "simple_governance/VoteMsg", nil) +} +``` +Don't forget to call this function in `app.go` (see [Application - Bridging it all together](app-structure.md)) for more). \ No newline at end of file diff --git a/docs/sdk/sdk-by-examples/simple-governance/module-init.md b/docs/sdk/sdk-by-examples/simple-governance/module-init.md index d018b512f..33bdfd500 100644 --- a/docs/sdk/sdk-by-examples/simple-governance/module-init.md +++ b/docs/sdk/sdk-by-examples/simple-governance/module-init.md @@ -7,7 +7,7 @@ cd x/ mkdir simple_governance cd simple_governance mkdir -p client/cli client/rest -touch client/cli/simple_governance.go client/rest/simple_governance.go errors.go handler.go handler_test.go keeper_keys.go keeper_test.go keeper.go test_common.go test_types.go types.go wire.go +touch client/cli/simple_governance.go client/rest/simple_governance.go errors.go handler.go handler_test.go keeper_keys.go keeper_test.go keeper.go test_common.go test_types.go types.go codec.go ``` Let us start by adding the files we will need. Your module's folder should look something like that: @@ -25,7 +25,7 @@ x ├─── keeper_keys.go ├─── keeper.go ├─── types.go - └─── wire.go + └─── codec.go ``` Let us go into the detail of each of these files. \ No newline at end of file diff --git a/docs/sdk/sdk-by-examples/simple-governance/module-keeper.md b/docs/sdk/sdk-by-examples/simple-governance/module-keeper.md index 851593a78..99c7cd566 100644 --- a/docs/sdk/sdk-by-examples/simple-governance/module-keeper.md +++ b/docs/sdk/sdk-by-examples/simple-governance/module-keeper.md @@ -47,7 +47,7 @@ With all that in mind, we can define the structure of our `Keeper`: ```go type Keeper struct { SimpleGov sdk.StoreKey // Key to our module's store - cdc *wire.Codec // Codec to encore/decode structs + cdc *codec.Codec // Codec to encore/decode structs ck bank.Keeper // Needed to handle deposits. This module onlyl requires read/writes to Atom balance sm stake.Keeper // Needed to compute voting power. This module only needs read access to the staking store. codespace sdk.CodespaceType // Reserves space for error codes diff --git a/docs/sdk/sdk-by-examples/simple-governance/module-wire.md b/docs/sdk/sdk-by-examples/simple-governance/module-wire.md deleted file mode 100644 index f889db91a..000000000 --- a/docs/sdk/sdk-by-examples/simple-governance/module-wire.md +++ /dev/null @@ -1,13 +0,0 @@ -## Wire - -**File: [`x/simple_governance/wire.go`](https://github.com/cosmos/cosmos-sdk/blob/fedekunze/module_tutorial/examples/simpleGov/x/simple_governance/wire.go)** - -The `wire.go` file allows developers to register the concrete message types of their module into the codec. In our case, we have two messages to declare: - -```go -func RegisterWire(cdc *wire.Codec) { - cdc.RegisterConcrete(SubmitProposalMsg{}, "simple_governance/SubmitProposalMsg", nil) - cdc.RegisterConcrete(VoteMsg{}, "simple_governance/VoteMsg", nil) -} -``` -Don't forget to call this function in `app.go` (see [Application - Bridging it all together](app-structure.md)) for more). \ No newline at end of file diff --git a/docs/spec/bank/WIP_keeper.md b/docs/spec/bank/WIP_keeper.md new file mode 100644 index 000000000..9667fc5dd --- /dev/null +++ b/docs/spec/bank/WIP_keeper.md @@ -0,0 +1,25 @@ +WORK IN PROGRESS +See PR comments here https://github.com/cosmos/cosmos-sdk/pull/2072 + +# Keeper + +## Denom Metadata + +The BankKeeper contains a store that stores the metadata of different token denoms. Denoms are referred to by their name, same as the `denom` field in sdk.Coin. The different attributes of a denom are stored in the denom metadata store under the key `[denom name]:[attribute name]`. The default attributes in the store are explained below. However, this can be extended by the developer or through SoftwareUpgrade proposals. + +### Decimals `int8` + +- `Base Unit` = The common standard for the default "standard" size of a token. Examples: 1 Bitcoin or 1 Ether. +- `Smallest Unit` = The smallest possible denomination of a token. A fraction of the base unit. Examples: 1 satoshi or 1 wei. + +All amounts throughout the SDK are denominated in the smallest unit of a token, so that all amounts can be expressed as integers. However, UIs typically want to display token values in the base unit, so the Decimals metadata field standardizes the number of digits that come after the decimal place in the base unit. + +`1 [Base Unit] = 10^(N) [Smallest Unit]` + +### TotalSupply `sdk.Integer` + +The TotalSupply of a denom is the total amount of a token that exists (known to the chain) across all accounts and modules. It is denominated in the `smallest unit` of a denom. It can be changed by the Keeper functions `MintCoins` and `BurnCoins`. `AddCoins` and `SubtractCoins` are used when adding or subtracting coins for an account, but not removing them from total supply (for example, when moving the coins to the control of the staking module). + +### Aliases `[]string` + +Aliases is an array of strings that are "alternative names" for a token. As an example, while the Ether's denom name might be `ether`, a possible alias could be `ETH`. This field can be useful for UIs and clients. It is intended that this field can be modified by a governance mechanism. diff --git a/docs/spec/bank/state.md b/docs/spec/bank/state.md deleted file mode 100644 index 8c2c47fd4..000000000 --- a/docs/spec/bank/state.md +++ /dev/null @@ -1,2 +0,0 @@ -- SDK related specifications (ie. how multistore, signatures, etc. work). -- Basecoin (SendTx) diff --git a/docs/spec/governance/state.md b/docs/spec/governance/state.md index 728ded371..5b577cec3 100644 --- a/docs/spec/governance/state.md +++ b/docs/spec/governance/state.md @@ -13,13 +13,13 @@ 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 int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months + MaxDepositPeriod time.Time // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months } ``` ```go type VotingProcedure struct { - VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks + VotingPeriod time.Time // Length of the voting period. Initial value: 2 weeks } ``` @@ -28,7 +28,6 @@ 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 - GracePeriod int64 // If validator entered validator set in this period of blocks before vote ended, governance penalty does not apply } ``` @@ -97,10 +96,10 @@ 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 - SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included + SubmitTime time.Time // Time of the block where TxGovSubmitProposal was included Submitter sdk.Address // Address of the submitter - VotingStartBlock int64 // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached + VotingStartTime time.Time // Time of the block where MinDeposit was reached. time.Time{} if MinDeposit is not reached CurrentStatus ProposalStatus // Current status of the proposal YesVotes sdk.Dec @@ -137,7 +136,7 @@ For pseudocode purposes, here are the two function we will use to read or write * `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 - `CurrentBlock == VotingStartBlock + activeProcedure.VotingPeriod`. If it is, + `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. @@ -159,7 +158,7 @@ And the pseudocode for the `ProposalProcessingQueue`: proposal = load(Governance, <proposalID|'proposal'>) // proposal is a const key votingProcedure = load(GlobalParams, 'VotingProcedure') - if (CurrentBlock == proposal.VotingStartBlock + votingProcedure.VotingPeriod && proposal.CurrentStatus == ProposalStatusActive) + if (CurrentTime == proposal.VotingStartTime + votingProcedure.VotingPeriod && proposal.CurrentStatus == ProposalStatusActive) // End of voting period, tally @@ -192,14 +191,10 @@ And the pseudocode for the `ProposalProcessingQueue`: tallyingProcedure = load(GlobalParams, 'TallyingProcedure') - // Slash validators that did not vote, or update tally if they voted + // Update tally if validator voted they voted for each validator in validators - if (validator.bondHeight < CurrentBlock - tallyingProcedure.GracePeriod) - // only slash if validator entered validator set before grace period - if (!tmpValMap(validator).HasVoted) - slash validator by tallyingProcedure.GovernancePenalty - else - proposal.updateTally(tmpValMap(validator).Vote, (validator.TotalShares - tmpValMap(validator).Minus)) + if tmpValMap(validator).HasVoted + proposal.updateTally(tmpValMap(validator).Vote, (validator.TotalShares - tmpValMap(validator).Minus)) diff --git a/docs/spec/ibc/appendices.md b/docs/spec/ibc/appendices.md index 751924f46..a3d04960d 100644 --- a/docs/spec/ibc/appendices.md +++ b/docs/spec/ibc/appendices.md @@ -8,7 +8,7 @@ The specification has focused on semantics and functionality of the IBC protocol In defining a standard binary encoding for all the "universal" components, we wish to make use of a standardized library, with efficient serialization and support in multiple languages. We considered two main formats: Ethereum's RLP[[6](./references.md#6)] and Google's Protobuf[[7](./references.md#7)]. We decided for protobuf, as it is more widely supported, is more expressive for different data types, and supports code generation for very efficient (de)serialization codecs. It does have a learning curve and more setup to generate the code from the type specifications, but the ibc data types should not change often and this code generation setup only needs to happen once per language (and can be exposed in a common repo), so this is not a strong counter-argument. Efficiency, expressiveness, and wider support rule in its favor. It is also widely used in gRPC and in many microservice architectures. -The tendermint-specific data structures are encoded with go-wire[[8](./references.md#8)], the native binary encoding used inside of tendermint. Most blockchains define their own formats, and until some universal format for headers and signatures among blockchains emerge, it seems very premature to enforce any encoding here. These are defined as arbitrary byte slices in the protocol, to be parsed in an consensus engine-dependent manner. +The tendermint-specific data structures are encoded with go-amino[[8](./references.md#8)], the native binary encoding used inside of tendermint. Most blockchains define their own formats, and until some universal format for headers and signatures among blockchains emerge, it seems very premature to enforce any encoding here. These are defined as arbitrary byte slices in the protocol, to be parsed in an consensus engine-dependent manner. For the following appendixes, the data structure specifications will be in proto3[[9](./references.md#9)] format. @@ -61,7 +61,7 @@ The IBC protocol does not handle these kinds of errors. They must be handled ind **TODO: clean this all up** -This is a mess now, we need to figure out what formats we use, define go-wire, etc. or just point to the source???? Will do more later, need help here from the tendermint core team. +This is a mess now, we need to figure out what formats we use, define go-amino, etc. or just point to the source???? Will do more later, need help here from the tendermint core team. In order to prove a merkle root, we must fully define the headers, signatures, and validator information returned from the Tendermint consensus engine, as well as the rules by which to verify a header. We also define here the messages used for creating and removing connections to other blockchains as well as how to handle forks. diff --git a/docs/spec/ibc/channels-and-packets.md b/docs/spec/ibc/channels-and-packets.md index 0951ca657..56ea93b5a 100644 --- a/docs/spec/ibc/channels-and-packets.md +++ b/docs/spec/ibc/channels-and-packets.md @@ -28,7 +28,7 @@ Every transaction on the same chain already has a well-defined causality relatio For example, an application may wish to allow a single tokenized asset to be transferred between and held on multiple blockchains while preserving fungibility and conservation of supply. The application can mint asset vouchers on chain `B` when a particular IBC packet is committed to chain `B`, and require outgoing sends of that packet on chain `A` to escrow an equal amount of the asset on chain `A` until the vouchers are later redeemed back to chain `A` with an IBC packet in the reverse direction. This ordering guarantee along with correct application logic can ensure that total supply is preserved across both chains and that any vouchers minted on chain `B` can later be redeemed back to chain `A`. -This section provides definitions for packets and channels, a high-level specification of the queue interface, and a list of the necessary proofs. To implement wire-compatible IBC, chain `A` and chain `B` must also use a common encoding format. An example binary encoding format can be found in [Appendix C](appendices.md#appendix-c-merkle-proof-formats). +This section provides definitions for packets and channels, a high-level specification of the queue interface, and a list of the necessary proofs. To implement amino-compatible IBC, chain `A` and chain `B` must also use a common encoding format. An example binary encoding format can be found in [Appendix C](appendices.md#appendix-c-merkle-proof-formats). ### 3.2 Definitions diff --git a/docs/spec/ibc/conclusion.md b/docs/spec/ibc/conclusion.md index f85ae8599..0c4fae18e 100644 --- a/docs/spec/ibc/conclusion.md +++ b/docs/spec/ibc/conclusion.md @@ -6,4 +6,4 @@ We have demonstrated a secure, performant, and flexible protocol for cross-block This document defines solely a message queue protocol - not the application-level semantics which must sit on top of it to enable asset transfer between two chains. We will shortly release a separate paper on Cosmos IBC that defines the application logic used for direct value transfer as well as routing over the Cosmos hub. That paper builds upon the IBC protocol defined here and provides a first example of how to reason about application logic and global invariants in the context of IBC. -There is a reference implementation of the Cosmos IBC protocol as part of the Cosmos SDK, written in Golang and released under the Apache license. To facilitate implementations in other langauages which are wire-compatible with the Cosmos implementation, the following appendices define exact message and binary encoding formats. +There is a reference implementation of the Cosmos IBC protocol as part of the Cosmos SDK, written in Golang and released under the Apache license. To facilitate implementations in other langauages which are amino-compatible with the Cosmos implementation, the following appendices define exact message and binary encoding formats. diff --git a/docs/spec/ibc/references.md b/docs/spec/ibc/references.md index ba9fbee69..715086e1b 100644 --- a/docs/spec/ibc/references.md +++ b/docs/spec/ibc/references.md @@ -24,7 +24,7 @@ [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) ##### 8: -[https://github.com/tendermint/go-wire](https://github.com/tendermint/go-wire) +[https://github.com/tendermint/go-amino](https://github.com/tendermint/go-amino) ##### 9: [https://developers.google.com/protocol-buffers/docs/proto3](https://developers.google.com/protocol-buffers/docs/proto3) diff --git a/docs/spec/staking/end_block.md b/docs/spec/staking/end_block.md index c2fe143ba..a29469261 100644 --- a/docs/spec/staking/end_block.md +++ b/docs/spec/staking/end_block.md @@ -12,7 +12,7 @@ the changes cleared ```golang EndBlock() ValidatorSetChanges - vsc = GetTendermintUpdates() + vsc = GetValidTendermintUpdates() ClearTendermintUpdates() return vsc ``` diff --git a/docs/spec/staking/hooks.md b/docs/spec/staking/hooks.md new file mode 100644 index 000000000..bcc496e3d --- /dev/null +++ b/docs/spec/staking/hooks.md @@ -0,0 +1,19 @@ +## Receiver Hooks + +The staking module allow for the following hooks to be registered with staking events: + +``` 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 + + OnValidatorBonded(ctx Context, address ConsAddress) // called when a validator is bonded + OnValidatorBeginUnbonding(ctx Context, address ConsAddress) // called when a validator begins unbonding + + OnDelegationCreated(ctx Context, delAddr AccAddress, valAddr ValAddress) // called when a delegation is created + OnDelegationSharesModified(ctx Context, delAddr AccAddress, valAddr ValAddress) // called when a delegation's shares are modified + OnDelegationRemoved(ctx Context, delAddr AccAddress, valAddr ValAddress) // called when a delegation is removed +} +``` diff --git a/docs/spec/staking/state.md b/docs/spec/staking/state.md index f2d6f9854..9454aca7d 100644 --- a/docs/spec/staking/state.md +++ b/docs/spec/staking/state.md @@ -43,12 +43,13 @@ type Params struct { Validators are identified according to the `OperatorAddr`, an SDK validator address for the operator of the validator. -Validators also have a `ConsPubKey`, the public key of the validator. - -Validators are indexed in the store using the following maps: +Validators also have a `ConsPubKey`, the public key of the validator used in +Tendermint consensus. The validator can be retrieved from it's `ConsPubKey` +once it can be converted into the corresponding `ConsAddr`. Validators are +indexed in the store using the following maps: - Validators: `0x02 | OperatorAddr -> amino(validator)` -- ValidatorsByPubKey: `0x03 | ConsPubKey -> OperatorAddr` +- ValidatorsByConsAddr: `0x03 | ConsAddr -> OperatorAddr` - ValidatorsByPower: `0x05 | power | blockHeight | blockTx -> OperatorAddr` `Validators` is the primary index - it ensures that each operator can have only one @@ -69,7 +70,7 @@ validator. ```golang type Validator struct { - ConsensusPubKey crypto.PubKey // Tendermint consensus pubkey of validator + ConsPubKey crypto.PubKey // Tendermint consensus pubkey of validator Jailed bool // has the validator been jailed? Status sdk.BondStatus // validator status (bonded/unbonding/unbonded) diff --git a/docs/spec/staking/transactions.md b/docs/spec/staking/transactions.md index ee2b976ae..6ed90343b 100644 --- a/docs/spec/staking/transactions.md +++ b/docs/spec/staking/transactions.md @@ -1,86 +1,107 @@ -## Transaction Overview +# Transaction Overview In this section we describe the processing of the transactions and the corresponding updates to the state. Transactions: - - TxCreateValidator - - TxEditValidator - - TxDelegation - - TxStartUnbonding - - TxCompleteUnbonding - - TxRedelegate - - TxCompleteRedelegation + +* TxCreateValidator +* TxEditValidator +* TxDelegation +* TxStartUnbonding +* TxCompleteUnbonding +* TxRedelegate +* TxCompleteRedelegation Other important state changes: - - Update Validators + +* Update Validators Other notes: - - `tx` denotes a reference to the transaction being processed - - `sender` denotes the address of the sender of the transaction - - `getXxx`, `setXxx`, and `removeXxx` functions are used to retrieve and - modify objects from the store - - `sdk.Dec` refers to a decimal type specified by the SDK. -### TxCreateValidator +* `tx` denotes a reference to the transaction being processed +* `sender` denotes the address of the sender of the transaction +* `getXxx`, `setXxx`, and `removeXxx` functions are used to retrieve and + modify objects from the store +* `sdk.Dec` refers to a decimal type specified by the SDK. - - triggers: `distribution.CreateValidatorDistribution` +## TxCreateValidator + +* triggers: `distribution.CreateValidatorDistribution` A validator is created using the `TxCreateValidator` transaction. ```golang type TxCreateValidator struct { - Operator sdk.Address - ConsensusPubKey crypto.PubKey - GovernancePubKey crypto.PubKey - SelfDelegation coin.Coin + Description Description + Commission Commission - Description Description - Commission sdk.Dec - CommissionMax sdk.Dec - CommissionMaxChange sdk.Dec + DelegatorAddr sdk.AccAddress + ValidatorAddr sdk.ValAddress + PubKey crypto.PubKey + Delegation sdk.Coin } - createValidator(tx TxCreateValidator): - validator = getValidator(tx.Operator) - if validator != nil return // only one validator per address + ok := validatorExists(tx.ValidatorAddr) + if ok return err // only one validator per address - validator = NewValidator(operatorAddr, ConsensusPubKey, GovernancePubKey, Description) - init validator poolShares, delegatorShares set to 0 - init validator commision fields from tx - validator.PoolShares = 0 + ok := validatorByPubKeyExists(tx.PubKey) + if ok return err // only one validator per public key + err := validateDenom(tx.Delegation.Denom) + if err != nil return err // denomination must be valid + + validator := NewValidator(tx.ValidatorAddr, tx.PubKey, tx.Description) + + err := setInitialCommission(validator, tx.Commission, blockTime) + if err != nil return err // must be able to set initial commission correctly + + // set the validator and public key setValidator(validator) + setValidatorByPubKeyIndex(validator) - txDelegate = TxDelegate(tx.Operator, tx.Operator, tx.SelfDelegation) - delegate(txDelegate, validator) // see delegate function in [TxDelegate](TxDelegate) - return + // delegate coins from tx.DelegatorAddr to the validator + err := delegate(tx.DelegatorAddr, tx.Delegation, validator) + if err != nil return err // must be able to set delegation correctly + + tags := createTags(tx) + return tags ``` -### TxEditValidator +## TxEditValidator -If either the `Description` (excluding `DateBonded` which is constant), -`Commission`, or the `GovernancePubKey` need to be updated, the -`TxEditCandidacy` transaction should be sent from the operator account: +If either the `Description`, `Commission`, or the `ValidatorAddr` need to be +updated, the `TxEditCandidacy` transaction should be sent from the operator +account: ```golang type TxEditCandidacy struct { - GovernancePubKey crypto.PubKey - Commission sdk.Dec - Description Description + Description Description + ValidatorAddr sdk.ValAddress + CommissionRate sdk.Dec } editCandidacy(tx TxEditCandidacy): - validator = getValidator(tx.ValidatorAddr) + validator, ok := getValidator(tx.ValidatorAddr) + if !ok return err // validator must exist - if tx.Commission > CommissionMax || tx.Commission < 0 then fail - if rateChange(tx.Commission) > CommissionMaxChange then fail - validator.Commission = tx.Commission + // Attempt to update the validator's description. The description provided + // must be valid. + description, err := updateDescription(validator, tx.Description) + if err != nil return err - if tx.GovernancePubKey != nil validator.GovernancePubKey = tx.GovernancePubKey - if tx.Description != nil validator.Description = tx.Description + // a validator is not required to update it's commission rate + if tx.CommissionRate != nil { + // Attempt to update a validator's commission rate. The rate provided + // must be valid. It's rate can only be updated once a day. + err := updateValidatorCommission(validator, tx.CommissionRate) + if err != nil return err + } - setValidator(store, validator) - return + // set the validator and public key + setValidator(validator) + + tags := createTags(tx) + return tags ``` ### TxDelegate @@ -100,7 +121,7 @@ type TxDelegate struct { delegate(tx TxDelegate): pool = getPool() - if validator.Status == Revoked return + if validator.Status == Jailed return delegation = getDelegatorBond(DelegatorAddr, ValidatorAddr) if delegation == nil then delegation = NewDelegation(DelegatorAddr, ValidatorAddr) @@ -141,7 +162,7 @@ startUnbonding(tx TxStartUnbonding): revokeCandidacy = false if bond.Shares.IsZero() { - if bond.DelegatorAddr == validator.Operator && validator.Revoked == false + if bond.DelegatorAddr == validator.Operator && validator.Jailed == false revokeCandidacy = true removeDelegation( bond) @@ -157,7 +178,7 @@ startUnbonding(tx TxStartUnbonding): setUnbondingDelegation(unbondingDelegation) if revokeCandidacy - validator.Revoked = true + validator.Jailed = true validator = updateValidator(validator) @@ -279,9 +300,9 @@ updateBondedValidators(newValidator Validator) (updatedVal Validator) else validator = getValidator(operatorAddr) - // if not previously a validator (and unrevoked), + // if not previously a validator (and unjailed), // kick the cliff validator / bond this new validator - if validator.Status() != Bonded && !validator.Revoked { + if validator.Status() != Bonded && !validator.Jailed { kickCliffValidator = true validator = bondValidator(ctx, store, validator) diff --git a/docs/validators/validator-setup.md b/docs/validators/validator-setup.md index 8a842ea20..43a3b0672 100644 --- a/docs/validators/validator-setup.md +++ b/docs/validators/validator-setup.md @@ -22,22 +22,29 @@ Your `cosmosvalconspub` can be used to create a new validator by staking tokens. gaiad tendermint show-validator ``` -Next, craft your `gaiacli stake create-validator` command: +Next, craft your `gaiacli tx 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 stake create-validator \ +gaiacli tx create-validator \ --amount=5steak \ --pubkey=$(gaiad tendermint show-validator) \ --address-validator=<account_cosmosval> --moniker="choose a moniker" \ --chain-id=<chain_id> \ - --name=<key_name> + --name=<key_name> \ + --commission-rate="0.10" \ + --commission-max-rate="0.20" \ + --commission-max-change-rate="0.01" ``` +__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. + ### 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). @@ -45,22 +52,30 @@ 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 stake edit-validator +gaiacli tx edit-validator --validator=<account_cosmos> --moniker="choose a moniker" \ --website="https://cosmos.network" \ --identity=6A0D65E29A4CBC8E --details="To infinity and beyond!" --chain-id=<chain_id> \ - --name=<key_name> + --name=<key_name> \ + --commission-rate="0.10" ``` +__Note__: The `commission-rate` value must adhere to the following invariants: + +- Must be between 0 and the validator's `commission-max-rate` +- Must not exceed the validator's `commission-max-change-rate` which is maximum + % point change rate **per day**. In other words, a validator can only change + its commission once per day and within `commission-max-change-rate` bounds. + ### View Validator Description View the validator's information with this command: ```bash -gaiacli stake validator <account_cosmos> +gaiacli query validator <account_cosmos> ``` ### Track Validator Signing Information @@ -68,7 +83,7 @@ gaiacli stake 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 stake signing-information <validator-pubkey>\ +gaiacli query signing-information <validator-pubkey>\ --chain-id=<chain_id> ``` @@ -77,7 +92,7 @@ gaiacli stake signing-information <validator-pubkey>\ 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). ```bash -gaiacli stake unjail \ +gaiacli tx unjail \ --from=<key_name> \ --chain-id=<chain_id> --validator=<account_cosmosval> \ @@ -89,7 +104,7 @@ gaiacli stake unjail \ Your validator is active if the following command returns anything: ```bash -gaiacli tendermint validator-set | grep "$(gaiad tendermint show-validator)" +gaiacli query tendermint-validator-set | grep "$(gaiad tendermint show-validator)" ``` You should also be able to see your validator on the [Explorer](https://explorecosmos.network/validators). You are looking for the `bech32` encoded `address` in the `~/.gaiad/config/priv_validator.json` file. @@ -113,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 stake unjail <cosmos> --chain-id=<chain_id> --name=<name> +gaiacli tx unjail <cosmos> --chain-id=<chain_id> --name=<name> ``` ::: danger Warning diff --git a/examples/README.md b/examples/README.md index 11aef9d28..5d872a7c9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -253,7 +253,7 @@ first time. Accounts are serialized and stored in a Merkle tree under the key ``base/a/<address>``, where ``<address>`` is the address of the account. -Typically, the address of the account is the 20-byte ``RIPEMD160`` hash +Typically, the address of the account is the first 20-bytes of the ``sha256`` hash of the public key, but other formats are acceptable as well, as defined in the `Tendermint crypto library <https://github.com/tendermint/tendermint/tree/master/crypto>`__. The Merkle tree diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index 001d05d3a..657cbcf0f 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -4,9 +4,9 @@ import ( "encoding/json" bam "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/examples/basecoin/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/ibc" @@ -27,7 +27,7 @@ const ( // integral app types. type BasecoinApp struct { *bam.BaseApp - cdc *wire.Codec + cdc *codec.Codec // keys to access the multistore keyMain *sdk.KVStoreKey @@ -67,7 +67,7 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.Ba return &types.AppAccount{} }, ) - app.bankKeeper = bank.NewKeeper(app.accountMapper) + app.bankKeeper = bank.NewBaseKeeper(app.accountMapper) app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace)) // register message routes @@ -93,16 +93,16 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.Ba return app } -// MakeCodec creates a new wire codec and registers all the necessary types +// MakeCodec creates a new codec codec and registers all the necessary types // with the codec. -func MakeCodec() *wire.Codec { - cdc := wire.NewCodec() +func MakeCodec() *codec.Codec { + cdc := codec.New() - wire.RegisterCrypto(cdc) - sdk.RegisterWire(cdc) - bank.RegisterWire(cdc) - ibc.RegisterWire(cdc) - auth.RegisterWire(cdc) + codec.RegisterCrypto(cdc) + sdk.RegisterCodec(cdc) + bank.RegisterCodec(cdc) + ibc.RegisterCodec(cdc) + auth.RegisterCodec(cdc) // register custom type cdc.RegisterConcrete(&types.AppAccount{}, "basecoin/Account", nil) @@ -173,7 +173,7 @@ func (app *BasecoinApp) ExportAppStateAndValidators() (appState json.RawMessage, app.accountMapper.IterateAccounts(ctx, appendAccountsFn) genState := types.GenesisState{Accounts: accounts} - appState, err = wire.MarshalJSONIndent(app.cdc, genState) + appState, err = codec.MarshalJSONIndent(app.cdc, genState) if err != nil { return nil, nil, err } diff --git a/examples/basecoin/app/app_test.go b/examples/basecoin/app/app_test.go index 1cf74e7f8..da544f480 100644 --- a/examples/basecoin/app/app_test.go +++ b/examples/basecoin/app/app_test.go @@ -4,9 +4,9 @@ import ( "os" "testing" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/examples/basecoin/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -22,7 +22,7 @@ func setGenesis(baseApp *BasecoinApp, accounts ...*types.AppAccount) (types.Gene } genesisState := types.GenesisState{Accounts: genAccts} - stateBytes, err := wire.MarshalJSONIndent(baseApp.cdc, genesisState) + stateBytes, err := codec.MarshalJSONIndent(baseApp.cdc, genesisState) if err != nil { return types.GenesisState{}, err } @@ -67,7 +67,7 @@ func TestGenesis(t *testing.T) { // reload app and ensure the account is still there baseApp = NewBasecoinApp(logger, db) - stateBytes, err := wire.MarshalJSONIndent(baseApp.cdc, genState) + stateBytes, err := codec.MarshalJSONIndent(baseApp.cdc, genState) require.Nil(t, err) // initialize the chain with the expected genesis state diff --git a/examples/basecoin/types/account.go b/examples/basecoin/types/account.go index 45774ae43..04d3e371e 100644 --- a/examples/basecoin/types/account.go +++ b/examples/basecoin/types/account.go @@ -1,8 +1,8 @@ package types import ( + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" ) @@ -30,7 +30,7 @@ func NewAppAccount(name string, baseAcct auth.BaseAccount) *AppAccount { // GetAccountDecoder returns the AccountDecoder function for the custom // AppAccount. -func GetAccountDecoder(cdc *wire.Codec) auth.AccountDecoder { +func GetAccountDecoder(cdc *codec.Codec) auth.AccountDecoder { return func(accBytes []byte) (auth.Account, error) { if len(accBytes) == 0 { return nil, sdk.ErrTxDecode("accBytes are empty") diff --git a/examples/democoin/app/app.go b/examples/democoin/app/app.go index a7078a3b1..0449f6251 100644 --- a/examples/democoin/app/app.go +++ b/examples/democoin/app/app.go @@ -10,8 +10,8 @@ import ( tmtypes "github.com/tendermint/tendermint/types" bam "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/ibc" @@ -30,7 +30,7 @@ const ( // Extended ABCI application type DemocoinApp struct { *bam.BaseApp - cdc *wire.Codec + cdc *codec.Codec // keys to access the substores capKeyMainStore *sdk.KVStoreKey @@ -75,7 +75,7 @@ func NewDemocoinApp(logger log.Logger, db dbm.DB) *DemocoinApp { ) // Add handlers. - app.bankKeeper = bank.NewKeeper(app.accountMapper) + app.bankKeeper = bank.NewBaseKeeper(app.accountMapper) 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)) @@ -103,15 +103,15 @@ func NewDemocoinApp(logger log.Logger, db dbm.DB) *DemocoinApp { } // custom tx codec -func MakeCodec() *wire.Codec { - var cdc = wire.NewCodec() - wire.RegisterCrypto(cdc) // Register crypto. - sdk.RegisterWire(cdc) // Register Msgs - cool.RegisterWire(cdc) - pow.RegisterWire(cdc) - bank.RegisterWire(cdc) - ibc.RegisterWire(cdc) - simplestake.RegisterWire(cdc) +func MakeCodec() *codec.Codec { + var cdc = codec.New() + codec.RegisterCrypto(cdc) // Register crypto. + sdk.RegisterCodec(cdc) // Register Msgs + cool.RegisterCodec(cdc) + pow.RegisterCodec(cdc) + bank.RegisterCodec(cdc) + ibc.RegisterCodec(cdc) + simplestake.RegisterCodec(cdc) // Register AppAccount cdc.RegisterInterface((*auth.Account)(nil), nil) @@ -182,7 +182,7 @@ func (app *DemocoinApp) ExportAppStateAndValidators() (appState json.RawMessage, POWGenesis: pow.WriteGenesis(ctx, app.powKeeper), CoolGenesis: cool.WriteGenesis(ctx, app.coolKeeper), } - appState, err = wire.MarshalJSONIndent(app.cdc, genState) + appState, err = codec.MarshalJSONIndent(app.cdc, genState) if err != nil { return nil, nil, err } diff --git a/examples/democoin/app/app_test.go b/examples/democoin/app/app_test.go index 731a21c5f..fc00651b8 100644 --- a/examples/democoin/app/app_test.go +++ b/examples/democoin/app/app_test.go @@ -4,10 +4,10 @@ import ( "os" "testing" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/examples/democoin/types" "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -27,7 +27,7 @@ func setGenesis(bapp *DemocoinApp, trend string, accs ...auth.BaseAccount) error CoolGenesis: cool.Genesis{trend}, } - stateBytes, err := wire.MarshalJSONIndent(bapp.cdc, genesisState) + stateBytes, err := codec.MarshalJSONIndent(bapp.cdc, genesisState) if err != nil { return err } diff --git a/examples/democoin/cmd/democoind/main.go b/examples/democoin/cmd/democoind/main.go index 528cafe1c..e22b0fc3e 100644 --- a/examples/democoin/cmd/democoind/main.go +++ b/examples/democoin/cmd/democoind/main.go @@ -13,9 +13,9 @@ import ( "github.com/tendermint/tendermint/libs/log" tmtypes "github.com/tendermint/tendermint/types" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/examples/democoin/app" "github.com/cosmos/cosmos-sdk/server" - "github.com/cosmos/cosmos-sdk/wire" ) // init parameters @@ -25,7 +25,7 @@ var CoolAppInit = server.AppInit{ } // coolGenAppParams sets up the app_state and appends the cool app state -func CoolAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { +func CoolAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { appState, err = server.SimpleAppGenState(cdc, appGenTxs) if err != nil { return diff --git a/examples/democoin/mock/validator.go b/examples/democoin/mock/validator.go index 9f84786ad..a54cdfedf 100644 --- a/examples/democoin/mock/validator.go +++ b/examples/democoin/mock/validator.go @@ -24,7 +24,12 @@ func (v Validator) GetOperator() sdk.ValAddress { } // Implements sdk.Validator -func (v Validator) GetPubKey() crypto.PubKey { +func (v Validator) GetConsPubKey() crypto.PubKey { + return nil +} + +// Implements sdk.Validator +func (v Validator) GetConsAddr() sdk.ConsAddress { return nil } @@ -88,7 +93,12 @@ func (vs *ValidatorSet) Validator(ctx sdk.Context, addr sdk.ValAddress) sdk.Vali } // ValidatorByPubKey implements sdk.ValidatorSet -func (vs *ValidatorSet) ValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) sdk.Validator { +func (vs *ValidatorSet) ValidatorByConsPubKey(_ sdk.Context, _ crypto.PubKey) sdk.Validator { + panic("not implemented") +} + +// ValidatorByPubKey implements sdk.ValidatorSet +func (vs *ValidatorSet) ValidatorByConsAddr(_ sdk.Context, _ sdk.ConsAddress) sdk.Validator { panic("not implemented") } @@ -122,21 +132,21 @@ func (vs *ValidatorSet) RemoveValidator(addr sdk.AccAddress) { } // Implements sdk.ValidatorSet -func (vs *ValidatorSet) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, power int64, amt sdk.Dec) { +func (vs *ValidatorSet) Slash(_ sdk.Context, _ sdk.ConsAddress, _ int64, _ int64, _ sdk.Dec) { panic("not implemented") } // Implements sdk.ValidatorSet -func (vs *ValidatorSet) Jail(ctx sdk.Context, pubkey crypto.PubKey) { +func (vs *ValidatorSet) Jail(_ sdk.Context, _ sdk.ConsAddress) { panic("not implemented") } // Implements sdk.ValidatorSet -func (vs *ValidatorSet) Unjail(ctx sdk.Context, pubkey crypto.PubKey) { +func (vs *ValidatorSet) Unjail(_ sdk.Context, _ sdk.ConsAddress) { panic("not implemented") } // Implements sdk.ValidatorSet -func (vs *ValidatorSet) Delegation(ctx sdk.Context, addrDel sdk.AccAddress, addrVal sdk.ValAddress) sdk.Delegation { +func (vs *ValidatorSet) Delegation(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) sdk.Delegation { panic("not implemented") } diff --git a/examples/democoin/types/account.go b/examples/democoin/types/account.go index 8eb9b0ae4..ad57c944d 100644 --- a/examples/democoin/types/account.go +++ b/examples/democoin/types/account.go @@ -1,8 +1,8 @@ package types import ( + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool" @@ -31,7 +31,7 @@ func (acc AppAccount) GetName() string { return acc.Name } func (acc *AppAccount) SetName(name string) { acc.Name = name } // Get the AccountDecoder function for the custom AppAccount -func GetAccountDecoder(cdc *wire.Codec) auth.AccountDecoder { +func GetAccountDecoder(cdc *codec.Codec) auth.AccountDecoder { return func(accBytes []byte) (res auth.Account, err error) { if len(accBytes) == 0 { return nil, sdk.ErrTxDecode("accBytes are empty") diff --git a/examples/democoin/x/assoc/validator_set.go b/examples/democoin/x/assoc/validator_set.go index 7515e1ad6..69b35501f 100644 --- a/examples/democoin/x/assoc/validator_set.go +++ b/examples/democoin/x/assoc/validator_set.go @@ -3,8 +3,8 @@ package assoc import ( "bytes" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" ) // ValidatorSet defines @@ -12,7 +12,7 @@ type ValidatorSet struct { sdk.ValidatorSet store sdk.KVStore - cdc *wire.Codec + cdc *codec.Codec maxAssoc int addrLen int @@ -21,7 +21,7 @@ type ValidatorSet struct { var _ sdk.ValidatorSet = ValidatorSet{} // NewValidatorSet returns new ValidatorSet with underlying ValidatorSet -func NewValidatorSet(cdc *wire.Codec, store sdk.KVStore, valset sdk.ValidatorSet, maxAssoc int, addrLen int) ValidatorSet { +func NewValidatorSet(cdc *codec.Codec, store sdk.KVStore, valset sdk.ValidatorSet, maxAssoc int, addrLen int) ValidatorSet { if maxAssoc < 0 || addrLen < 0 { panic("Cannot use negative integer for NewValidatorSet") } diff --git a/examples/democoin/x/assoc/validator_set_test.go b/examples/democoin/x/assoc/validator_set_test.go index eac23b25e..9fc6526f8 100644 --- a/examples/democoin/x/assoc/validator_set_test.go +++ b/examples/democoin/x/assoc/validator_set_test.go @@ -9,10 +9,10 @@ import ( abci "github.com/tendermint/tendermint/abci/types" dbm "github.com/tendermint/tendermint/libs/db" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/examples/democoin/mock" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" ) func defaultContext(key sdk.StoreKey) sdk.Context { @@ -36,7 +36,7 @@ func TestValidatorSet(t *testing.T) { {addr2, sdk.NewDec(2)}, }} - valset := NewValidatorSet(wire.NewCodec(), ctx.KVStore(key).Prefix([]byte("assoc")), base, 1, 5) + valset := NewValidatorSet(codec.New(), ctx.KVStore(key).Prefix([]byte("assoc")), base, 1, 5) require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1)) require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2)) diff --git a/examples/democoin/x/cool/app_test.go b/examples/democoin/x/cool/app_test.go index 9e1aa776e..35a656ceb 100644 --- a/examples/democoin/x/cool/app_test.go +++ b/examples/democoin/x/cool/app_test.go @@ -47,15 +47,15 @@ var ( func getMockApp(t *testing.T) *mock.App { mapp := mock.NewApp() - RegisterWire(mapp.Cdc) + RegisterCodec(mapp.Cdc) keyCool := sdk.NewKVStoreKey("cool") - bankKeeper := bank.NewKeeper(mapp.AccountMapper) + bankKeeper := bank.NewBaseKeeper(mapp.AccountMapper) keeper := NewKeeper(keyCool, bankKeeper, mapp.RegisterCodespace(DefaultCodespace)) mapp.Router().AddRoute("cool", NewHandler(keeper)) mapp.SetInitChainer(getInitChainer(mapp, keeper, "ice-cold")) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyCool})) + require.NoError(t, mapp.CompleteSetup(keyCool)) return mapp } diff --git a/examples/democoin/x/cool/client/cli/tx.go b/examples/democoin/x/cool/client/cli/tx.go index 1e41ff5b2..db6394e88 100644 --- a/examples/democoin/x/cool/client/cli/tx.go +++ b/examples/democoin/x/cool/client/cli/tx.go @@ -7,15 +7,15 @@ 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" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" ) // QuizTxCmd invokes the coolness quiz transaction. -func QuizTxCmd(cdc *wire.Codec) *cobra.Command { +func QuizTxCmd(cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "cool [answer]", Short: "What's cooler than being cool?", @@ -34,13 +34,13 @@ func QuizTxCmd(cdc *wire.Codec) *cobra.Command { msg := cool.NewMsgQuiz(from, args[0]) - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } } // SetTrendTxCmd sends a new cool trend transaction. -func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command { +func SetTrendTxCmd(cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "setcool [answer]", Short: "You're so cool, tell us what is cool!", @@ -59,7 +59,7 @@ func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command { msg := cool.NewMsgSetTrend(from, args[0]) - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } } diff --git a/examples/democoin/x/cool/wire.go b/examples/democoin/x/cool/codec.go similarity index 53% rename from examples/democoin/x/cool/wire.go rename to examples/democoin/x/cool/codec.go index 63666888a..491c00617 100644 --- a/examples/democoin/x/cool/wire.go +++ b/examples/democoin/x/cool/codec.go @@ -1,11 +1,11 @@ package cool import ( - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" ) -// Register concrete types on wire codec -func RegisterWire(cdc *wire.Codec) { +// Register concrete types on codec codec +func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgQuiz{}, "cool/Quiz", nil) cdc.RegisterConcrete(MsgSetTrend{}, "cool/SetTrend", nil) } diff --git a/examples/democoin/x/cool/keeper_test.go b/examples/democoin/x/cool/keeper_test.go index ab59ea610..e3af7790e 100644 --- a/examples/democoin/x/cool/keeper_test.go +++ b/examples/democoin/x/cool/keeper_test.go @@ -8,9 +8,9 @@ import ( abci "github.com/tendermint/tendermint/abci/types" dbm "github.com/tendermint/tendermint/libs/db" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" auth "github.com/cosmos/cosmos-sdk/x/auth" bank "github.com/cosmos/cosmos-sdk/x/bank" ) @@ -26,12 +26,12 @@ func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) { func TestCoolKeeper(t *testing.T) { ms, capKey := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() auth.RegisterBaseAccount(cdc) am := auth.NewAccountMapper(cdc, capKey, auth.ProtoBaseAccount) ctx := sdk.NewContext(ms, abci.Header{}, false, nil) - ck := bank.NewKeeper(am) + ck := bank.NewBaseKeeper(am) keeper := NewKeeper(capKey, ck, DefaultCodespace) err := InitGenesis(ctx, keeper, Genesis{"icy"}) diff --git a/examples/democoin/x/cool/types.go b/examples/democoin/x/cool/types.go index d335f9c91..f04811b14 100644 --- a/examples/democoin/x/cool/types.go +++ b/examples/democoin/x/cool/types.go @@ -33,6 +33,7 @@ var _ sdk.Msg = MsgSetTrend{} // nolint func (msg MsgSetTrend) Type() string { return "cool" } +func (msg MsgSetTrend) Name() 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,6 +84,7 @@ var _ sdk.Msg = MsgQuiz{} // nolint func (msg MsgQuiz) Type() string { return "cool" } +func (msg MsgQuiz) Name() 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/examples/democoin/x/oracle/README.md index 0cfcb820d..b840dc0e8 100644 --- a/examples/democoin/x/oracle/README.md +++ b/examples/democoin/x/oracle/README.md @@ -38,7 +38,7 @@ func NewHandler(keeper Keeper) sdk.Handler { In the previous example, the keeper has an `oracle.Keeper`. `oracle.Keeper`s are generated by `NewKeeper`. ```go -func NewKeeper(key sdk.StoreKey, cdc *wire.Codec, valset sdk.ValidatorSet, supermaj sdk.Dec, timeout int64) Keeper { +func NewKeeper(key sdk.StoreKey, cdc *codec.Codec, valset sdk.ValidatorSet, supermaj sdk.Dec, timeout int64) Keeper { return Keeper { cdc: cdc, key: key, diff --git a/examples/democoin/x/oracle/keeper.go b/examples/democoin/x/oracle/keeper.go index 0406f560a..e55cd7083 100644 --- a/examples/democoin/x/oracle/keeper.go +++ b/examples/democoin/x/oracle/keeper.go @@ -1,7 +1,7 @@ package oracle import ( - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -9,7 +9,7 @@ import ( // Keeper of the oracle store type Keeper struct { key sdk.StoreKey - cdc *wire.Codec + cdc *codec.Codec valset sdk.ValidatorSet @@ -18,7 +18,7 @@ type Keeper struct { } // NewKeeper constructs a new keeper -func NewKeeper(key sdk.StoreKey, cdc *wire.Codec, valset sdk.ValidatorSet, supermaj sdk.Dec, timeout int64) Keeper { +func NewKeeper(key sdk.StoreKey, cdc *codec.Codec, valset sdk.ValidatorSet, supermaj sdk.Dec, timeout int64) Keeper { if timeout < 0 { panic("Timeout should not be negative") } diff --git a/examples/democoin/x/oracle/keeper_keys.go b/examples/democoin/x/oracle/keeper_keys.go index f657e8027..9b71aeaa1 100644 --- a/examples/democoin/x/oracle/keeper_keys.go +++ b/examples/democoin/x/oracle/keeper_keys.go @@ -1,23 +1,23 @@ package oracle import ( + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" ) // GetInfoKey returns the key for OracleInfo -func GetInfoKey(p Payload, cdc *wire.Codec) []byte { +func GetInfoKey(p Payload, cdc *codec.Codec) []byte { bz := cdc.MustMarshalBinary(p) return append([]byte{0x00}, bz...) } // GetSignPrefix returns the prefix for signs -func GetSignPrefix(p Payload, cdc *wire.Codec) []byte { +func GetSignPrefix(p Payload, cdc *codec.Codec) []byte { bz := cdc.MustMarshalBinary(p) return append([]byte{0x01}, bz...) } // GetSignKey returns the key for sign -func GetSignKey(p Payload, signer sdk.AccAddress, cdc *wire.Codec) []byte { +func GetSignKey(p Payload, signer sdk.AccAddress, cdc *codec.Codec) []byte { return append(GetSignPrefix(p, cdc), signer...) } diff --git a/examples/democoin/x/oracle/oracle_test.go b/examples/democoin/x/oracle/oracle_test.go index f4971c8b1..0b921e9d9 100644 --- a/examples/democoin/x/oracle/oracle_test.go +++ b/examples/democoin/x/oracle/oracle_test.go @@ -9,10 +9,10 @@ import ( abci "github.com/tendermint/tendermint/abci/types" dbm "github.com/tendermint/tendermint/libs/db" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/examples/democoin/mock" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" ) func defaultContext(keys ...sdk.StoreKey) sdk.Context { @@ -34,13 +34,16 @@ type seqOracle struct { func (o seqOracle) Type() string { return "seq" } +func (o seqOracle) Name() string { + return "seq" +} func (o seqOracle) ValidateBasic() sdk.Error { return nil } -func makeCodec() *wire.Codec { - var cdc = wire.NewCodec() +func makeCodec() *codec.Codec { + var cdc = codec.New() cdc.RegisterInterface((*sdk.Msg)(nil), nil) cdc.RegisterConcrete(Msg{}, "test/Oracle", nil) @@ -79,7 +82,7 @@ func getSequence(ctx sdk.Context, key sdk.StoreKey) int { if seqbz == nil { seq = 0 } else { - wire.NewCodec().MustUnmarshalBinary(seqbz, &seq) + codec.New().MustUnmarshalBinary(seqbz, &seq) } return seq @@ -93,7 +96,7 @@ func handleSeqOracle(ctx sdk.Context, key sdk.StoreKey, o seqOracle) sdk.Error { return sdk.NewError(sdk.CodespaceRoot, 1, "") } - bz := wire.NewCodec().MustMarshalBinary(seq + 1) + bz := codec.New().MustMarshalBinary(seq + 1) store.Set([]byte("seq"), bz) return nil diff --git a/examples/democoin/x/oracle/types.go b/examples/democoin/x/oracle/types.go index ab4b04a42..5e47597b8 100644 --- a/examples/democoin/x/oracle/types.go +++ b/examples/democoin/x/oracle/types.go @@ -29,5 +29,6 @@ func (msg Msg) GetSigners() []sdk.AccAddress { // Payload defines inner data for actual execution type Payload interface { Type() string + Name() string ValidateBasic() sdk.Error } diff --git a/examples/democoin/x/pow/app_test.go b/examples/democoin/x/pow/app_test.go index 2aa4ea9b0..6e6f07f77 100644 --- a/examples/democoin/x/pow/app_test.go +++ b/examples/democoin/x/pow/app_test.go @@ -23,16 +23,16 @@ var ( func getMockApp(t *testing.T) *mock.App { mapp := mock.NewApp() - RegisterWire(mapp.Cdc) + RegisterCodec(mapp.Cdc) keyPOW := sdk.NewKVStoreKey("pow") - bankKeeper := bank.NewKeeper(mapp.AccountMapper) + bankKeeper := bank.NewBaseKeeper(mapp.AccountMapper) config := Config{"pow", 1} keeper := NewKeeper(keyPOW, config, bankKeeper, mapp.RegisterCodespace(DefaultCodespace)) mapp.Router().AddRoute("pow", keeper.Handler) mapp.SetInitChainer(getInitChainer(mapp, keeper)) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyPOW})) + require.NoError(t, mapp.CompleteSetup(keyPOW)) mapp.Seal() diff --git a/examples/democoin/x/pow/client/cli/tx.go b/examples/democoin/x/pow/client/cli/tx.go index 15102b128..4f5dacb1a 100644 --- a/examples/democoin/x/pow/client/cli/tx.go +++ b/examples/democoin/x/pow/client/cli/tx.go @@ -6,9 +6,9 @@ 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" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" @@ -16,7 +16,7 @@ import ( ) // command to mine some pow! -func MineCmd(cdc *wire.Codec) *cobra.Command { +func MineCmd(cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "mine [difficulty] [count] [nonce] [solution]", Short: "Mine some coins with proof-of-work!", @@ -53,7 +53,7 @@ func MineCmd(cdc *wire.Codec) *cobra.Command { // Build and sign the transaction, then broadcast to a Tendermint // node. - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } } diff --git a/examples/democoin/x/pow/codec.go b/examples/democoin/x/pow/codec.go new file mode 100644 index 000000000..8f4296f17 --- /dev/null +++ b/examples/democoin/x/pow/codec.go @@ -0,0 +1,10 @@ +package pow + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +// Register concrete types on codec codec +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(MsgMine{}, "pow/Mine", nil) +} diff --git a/examples/democoin/x/pow/handler_test.go b/examples/democoin/x/pow/handler_test.go index a203d2776..8166ddfc5 100644 --- a/examples/democoin/x/pow/handler_test.go +++ b/examples/democoin/x/pow/handler_test.go @@ -8,21 +8,21 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" + codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" auth "github.com/cosmos/cosmos-sdk/x/auth" bank "github.com/cosmos/cosmos-sdk/x/bank" ) func TestPowHandler(t *testing.T) { ms, capKey := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() auth.RegisterBaseAccount(cdc) am := auth.NewAccountMapper(cdc, capKey, auth.ProtoBaseAccount) ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) config := NewConfig("pow", int64(1)) - ck := bank.NewKeeper(am) + ck := bank.NewBaseKeeper(am) keeper := NewKeeper(capKey, config, ck, DefaultCodespace) handler := keeper.Handler diff --git a/examples/democoin/x/pow/keeper_test.go b/examples/democoin/x/pow/keeper_test.go index a6802cb21..dbd974c4d 100644 --- a/examples/democoin/x/pow/keeper_test.go +++ b/examples/democoin/x/pow/keeper_test.go @@ -9,9 +9,9 @@ import ( 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/wire" auth "github.com/cosmos/cosmos-sdk/x/auth" bank "github.com/cosmos/cosmos-sdk/x/bank" ) @@ -29,13 +29,13 @@ func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) { func TestPowKeeperGetSet(t *testing.T) { ms, capKey := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() auth.RegisterBaseAccount(cdc) am := auth.NewAccountMapper(cdc, capKey, auth.ProtoBaseAccount) ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) config := NewConfig("pow", int64(1)) - ck := bank.NewKeeper(am) + ck := bank.NewBaseKeeper(am) keeper := NewKeeper(capKey, config, ck, DefaultCodespace) err := InitGenesis(ctx, keeper, Genesis{uint64(1), uint64(0)}) diff --git a/examples/democoin/x/pow/types.go b/examples/democoin/x/pow/types.go index 2247a1e88..4f808cbed 100644 --- a/examples/democoin/x/pow/types.go +++ b/examples/democoin/x/pow/types.go @@ -32,6 +32,7 @@ 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) 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/wire.go b/examples/democoin/x/pow/wire.go deleted file mode 100644 index 3d7f61486..000000000 --- a/examples/democoin/x/pow/wire.go +++ /dev/null @@ -1,10 +0,0 @@ -package pow - -import ( - "github.com/cosmos/cosmos-sdk/wire" -) - -// Register concrete types on wire codec -func RegisterWire(cdc *wire.Codec) { - cdc.RegisterConcrete(MsgMine{}, "pow/Mine", nil) -} diff --git a/examples/democoin/x/simplestake/client/cli/commands.go b/examples/democoin/x/simplestake/client/cli/commands.go index 9f6eb40f7..5d2790e70 100644 --- a/examples/democoin/x/simplestake/client/cli/commands.go +++ b/examples/democoin/x/simplestake/client/cli/commands.go @@ -7,9 +7,9 @@ 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" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" @@ -25,7 +25,7 @@ const ( ) // simple bond tx -func BondTxCmd(cdc *wire.Codec) *cobra.Command { +func BondTxCmd(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "bond", Short: "Bond to a validator", @@ -68,7 +68,7 @@ func BondTxCmd(cdc *wire.Codec) *cobra.Command { // Build and sign the transaction, then broadcast to a Tendermint // node. - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -79,7 +79,7 @@ func BondTxCmd(cdc *wire.Codec) *cobra.Command { } // simple unbond tx -func UnbondTxCmd(cdc *wire.Codec) *cobra.Command { +func UnbondTxCmd(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "unbond", Short: "Unbond from a validator", @@ -98,7 +98,7 @@ func UnbondTxCmd(cdc *wire.Codec) *cobra.Command { // Build and sign the transaction, then broadcast to a Tendermint // node. - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/examples/democoin/x/simplestake/wire.go b/examples/democoin/x/simplestake/codec.go similarity index 57% rename from examples/democoin/x/simplestake/wire.go rename to examples/democoin/x/simplestake/codec.go index 4ef971b44..7813fd642 100644 --- a/examples/democoin/x/simplestake/wire.go +++ b/examples/democoin/x/simplestake/codec.go @@ -1,11 +1,11 @@ package simplestake import ( - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" ) -// Register concrete types on wire codec -func RegisterWire(cdc *wire.Codec) { +// Register concrete types on codec codec +func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgBond{}, "simplestake/BondMsg", nil) cdc.RegisterConcrete(MsgUnbond{}, "simplestake/UnbondMsg", nil) } diff --git a/examples/democoin/x/simplestake/keeper.go b/examples/democoin/x/simplestake/keeper.go index eb3f34079..7bdc17937 100644 --- a/examples/democoin/x/simplestake/keeper.go +++ b/examples/democoin/x/simplestake/keeper.go @@ -3,8 +3,8 @@ package simplestake import ( "github.com/tendermint/tendermint/crypto" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/bank" ) @@ -17,13 +17,13 @@ type Keeper struct { ck bank.Keeper key sdk.StoreKey - cdc *wire.Codec + cdc *codec.Codec codespace sdk.CodespaceType } func NewKeeper(key sdk.StoreKey, bankKeeper bank.Keeper, codespace sdk.CodespaceType) Keeper { - cdc := wire.NewCodec() - wire.RegisterCrypto(cdc) + cdc := codec.New() + codec.RegisterCrypto(cdc) return Keeper{ key: key, cdc: cdc, diff --git a/examples/democoin/x/simplestake/keeper_test.go b/examples/democoin/x/simplestake/keeper_test.go index 393bc1b33..68f28bd91 100644 --- a/examples/democoin/x/simplestake/keeper_test.go +++ b/examples/democoin/x/simplestake/keeper_test.go @@ -12,9 +12,9 @@ import ( 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/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" ) @@ -32,11 +32,11 @@ func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) { func TestKeeperGetSet(t *testing.T) { ms, authKey, capKey := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() auth.RegisterBaseAccount(cdc) accountMapper := auth.NewAccountMapper(cdc, authKey, auth.ProtoBaseAccount) - stakeKeeper := NewKeeper(capKey, bank.NewKeeper(accountMapper), DefaultCodespace) + stakeKeeper := NewKeeper(capKey, bank.NewBaseKeeper(accountMapper), DefaultCodespace) ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) addr := sdk.AccAddress([]byte("some-address")) @@ -60,13 +60,13 @@ func TestKeeperGetSet(t *testing.T) { func TestBonding(t *testing.T) { ms, authKey, capKey := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() auth.RegisterBaseAccount(cdc) ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) accountMapper := auth.NewAccountMapper(cdc, authKey, auth.ProtoBaseAccount) - bankKeeper := bank.NewKeeper(accountMapper) + bankKeeper := bank.NewBaseKeeper(accountMapper) stakeKeeper := NewKeeper(capKey, bankKeeper, DefaultCodespace) addr := sdk.AccAddress([]byte("some-address")) privKey := ed25519.GenPrivKey() diff --git a/examples/democoin/x/simplestake/msgs.go b/examples/democoin/x/simplestake/msgs.go index 9f4c4f5f6..aea984d18 100644 --- a/examples/democoin/x/simplestake/msgs.go +++ b/examples/democoin/x/simplestake/msgs.go @@ -27,6 +27,7 @@ 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) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Address} } // basic validation of the bond message @@ -66,6 +67,7 @@ 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) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Address} } func (msg MsgUnbond) ValidateBasic() sdk.Error { return nil } diff --git a/examples/kvstore/kvstore b/examples/kvstore/kvstore index 5dd8b5eea..6de98cc56 100755 Binary files a/examples/kvstore/kvstore and b/examples/kvstore/kvstore differ diff --git a/examples/kvstore/tx.go b/examples/kvstore/tx.go index 0d8312fab..bb3075ae4 100644 --- a/examples/kvstore/tx.go +++ b/examples/kvstore/tx.go @@ -18,6 +18,10 @@ func (tx kvstoreTx) Type() string { return "kvstore" } +func (tx kvstoreTx) Name() string { + return "kvstore" +} + func (tx kvstoreTx) GetMsgs() []sdk.Msg { return []sdk.Msg{tx} } diff --git a/networks/README.md b/networks/README.md index 322d37340..221f6e385 100644 --- a/networks/README.md +++ b/networks/README.md @@ -1,65 +1,6 @@ -# Terraform & Ansible +# Networks -Automated deployments are done using [Terraform](https://www.terraform.io/) to create servers on AWS then -[Ansible](http://www.ansible.com/) to create and manage testnets on those servers. +Here contains the files required for automated deployment of either local or remote testnets. -## Prerequisites - -- Install [Terraform](https://www.terraform.io/downloads.html) and [Ansible](http://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) on a Linux machine. -- Create an [AWS API token](https://docs.aws.amazon.com/general/latest/gr/managing-aws-access-keys.html) with EC2 create capability. -- Create SSH keys - -``` -export AWS_ACCESS_KEY_ID="2345234jk2lh4234" -export AWS_SECRET_ACCESS_KEY="234jhkg234h52kh4g5khg34" -export TESTNET_NAME="remotenet" -export CLUSTER_NAME= "remotenetvalidators" -export SSH_PRIVATE_FILE="$HOME/.ssh/id_rsa" -export SSH_PUBLIC_FILE="$HOME/.ssh/id_rsa.pub" -``` - -These will be used by both `terraform` and `ansible`. - -## Create a remote network - -``` -SERVERS=1 REGION_LIMIT=1 make validators-start -``` - -The testnet name is what's going to be used in --chain-id, while the cluster name is the administrative tag in AWS for the servers. The code will create SERVERS amount of servers in each availability zone up to the number of REGION_LIMITs, starting at us-east-2. (us-east-1 is excluded.) The below BaSH script does the same, but sometimes it's more comfortable for input. - -``` -./new-testnet.sh "$TESTNET_NAME" "$CLUSTER_NAME" 1 1 -``` - -## Quickly see the /status endpoint - -``` -make validators-status -``` - -## Delete servers - -``` -make validators-stop -``` - -## Logging - -You can ship logs to Logz.io, an Elastic stack (Elastic search, Logstash and Kibana) service provider. You can set up your nodes to log there automatically. Create an account and get your API key from the notes on [this page](https://app.logz.io/#/dashboard/data-sources/Filebeat), then: - -``` -yum install systemd-devel || echo "This will only work on RHEL-based systems." -apt-get install libsystemd-dev || echo "This will only work on Debian-based systems." - -go get github.com/mheese/journalbeat -ansible-playbook -i inventory/digital_ocean.py -l remotenet logzio.yml -e LOGZIO_TOKEN=ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 -``` - -## Monitoring - -You can install the DataDog agent with: - -``` -make datadog-install -``` +Doing so is best accomplished using the `make` targets. For more information, see the +[networks documentation](/docs/getting-started/networks.md) diff --git a/networks/local/README.md b/networks/local/README.md deleted file mode 100644 index ef5e27803..000000000 --- a/networks/local/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# Local Cluster with Docker Compose - -## Requirements - -- [Install gaia](https://cosmos.network/docs/getting-started/installation.html) -- [Install docker](https://docs.docker.com/engine/installation/) -- [Install docker-compose](https://docs.docker.com/compose/install/) - -## Build - -Build the `gaiad` binary and the `tendermint/gaiadnode` docker image. - -Note the binary will be mounted into the container so it can be updated without -rebuilding the image. - -``` -cd $GOPATH/src/github.com/cosmos/cosmos-sdk - -# Build the linux binary in ./build -make build-linux - -# Build tendermint/gaiadnode image -make build-docker-gaiadnode -``` - -## Run a testnet - -To start a 4 node testnet run: - -``` -make localnet-start -``` - -The nodes bind their RPC servers to ports 26657, 26660, 26662, and 26664 on the host. -This file creates a 4-node network using the gaiadnode image. -The nodes of the network expose their P2P and RPC endpoints to the host machine on ports 26656-26657, 26659-26660, 26661-26662, and 26663-26664 respectively. - -To update the binary, just rebuild it and restart the nodes: - -``` -make build-linux -make localnet-stop -make localnet-start -``` - -## Configuration - -The `make localnet-start` creates files for a 4-node testnet in `./build` by calling the `gaiad testnet` command. - -The `./build` directory is mounted to the `/gaiad` mount point to attach the binary and config files to the container. - -For instance, to create a single node testnet: - -``` -cd $GOPATH/src/github.com/cosmos/cosmos-sdk - -# Clear the build folder -rm -rf ./build - -# Build binary -make build-linux - -# Create configuration -docker run -v `pwd`/build:/gaiad tendermint/gaiadnode testnet -o . --v 1 - -#Run the node -docker run -v `pwd`/build:/gaiad tendermint/gaiadnode -``` - -## Logging - -Log is saved under the attached volume, in the `gaiad.log` file and written on the screen. - -## 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. diff --git a/scripts/multisim.sh b/scripts/multisim.sh new file mode 100755 index 000000000..be59b0f1a --- /dev/null +++ b/scripts/multisim.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +seeds=(1 2 4 7 9 20 32 123 4728 37827 981928 87821 891823782 989182 89182391) + +echo "Running multi-seed simulation with seeds: ${seeds[@]}" +echo "Edit scripts/multisim.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/EXIT (i.e. Ctrl-C)." + +trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT + +tmpdir=$(mktemp -d) +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" + echo "Writing stdout to $file..." + go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 \ + -SimulationVerbose=true -SimulationCommit=true -SimulationSeed=$seed -v -timeout 24h > $file +} + +i=0 +pids=() +for seed in ${seeds[@]}; do + sim $seed & + pids[${i}]=$! + i=$(($i+1)) + sleep 0.1 # 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=$? + if [ $last -ne 0 ]; then + seed=${seeds[${i}]} + echo "Simulation with seed $seed failed!" + code=1 + fi + i=$(($i+1)) +done + +exit $code diff --git a/server/config/config.go b/server/config/config.go index e6fc6a4de..bd0d966e3 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -1,5 +1,41 @@ package config +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + defaultMinimumFees = "" +) + +// BaseConfig defines the server's basic configuration +type BaseConfig struct { + // Tx minimum fee + MinFees string `mapstructure:"minimum_fees"` +} + +// Config defines the server's top level configuration +type Config struct { + BaseConfig `mapstructure:",squash"` +} + +// SetMinimumFee sets the minimum fee. +func (c *Config) SetMinimumFees(fees sdk.Coins) { c.MinFees = fees.String() } + +// SetMinimumFee sets the minimum fee. +func (c *Config) MinimumFees() sdk.Coins { + fees, err := sdk.ParseCoins(c.MinFees) + if err != nil { + panic(fmt.Sprintf("invalid minimum fees: %v", err)) + } + return fees +} + +// DefaultConfig returns server's default configuration. +func DefaultConfig() *Config { return &Config{BaseConfig{MinFees: defaultMinimumFees}} } + //_____________________________________________________________________ // Configuration structure for command functions that share configuration. diff --git a/server/config/config_test.go b/server/config/config_test.go new file mode 100644 index 000000000..e4d552ad2 --- /dev/null +++ b/server/config/config_test.go @@ -0,0 +1,19 @@ +package config + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestDefaultConfig(t *testing.T) { + cfg := DefaultConfig() + require.True(t, cfg.MinimumFees().IsZero()) +} + +func TestSetMinimumFees(t *testing.T) { + cfg := DefaultConfig() + cfg.SetMinimumFees(sdk.Coins{sdk.NewCoin("foo", sdk.NewInt(100))}) + require.Equal(t, "100foo", cfg.MinFees) +} diff --git a/server/config/toml.go b/server/config/toml.go new file mode 100644 index 000000000..3c60fbdf9 --- /dev/null +++ b/server/config/toml.go @@ -0,0 +1,46 @@ +package config + +import ( + "bytes" + "text/template" + + "github.com/spf13/viper" + cmn "github.com/tendermint/tendermint/libs/common" +) + +const defaultConfigTemplate = `# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base config options ##### + +# Validators reject any tx from the mempool with less than the minimum fee per gas. +minimum_fees = "{{ .BaseConfig.MinFees }}" +` + +var configTemplate *template.Template + +func init() { + var err error + tmpl := template.New("cosmosConfigFileTemplate") + if configTemplate, err = tmpl.Parse(defaultConfigTemplate); err != nil { + panic(err) + } +} + +// ParseConfig retrieves the default environment configuration for Cosmos. +func ParseConfig() (*Config, error) { + conf := DefaultConfig() + err := viper.Unmarshal(conf) + return conf, err +} + +// WriteConfigFile renders config using the template and writes it to configFilePath. +func WriteConfigFile(configFilePath string, config *Config) { + var buffer bytes.Buffer + + if err := configTemplate.Execute(&buffer, config); err != nil { + panic(err) + } + + cmn.MustWriteFile(configFilePath, buffer.Bytes(), 0644) +} diff --git a/server/export.go b/server/export.go index 1d0f760ce..c3d6b5283 100644 --- a/server/export.go +++ b/server/export.go @@ -7,14 +7,14 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" tmtypes "github.com/tendermint/tendermint/types" "io/ioutil" "path" ) // ExportCmd dumps app state to JSON. -func ExportCmd(ctx *Context, cdc *wire.Codec, appExporter AppExporter) *cobra.Command { +func ExportCmd(ctx *Context, cdc *codec.Codec, appExporter AppExporter) *cobra.Command { return &cobra.Command{ Use: "export", Short: "Export state to JSON", @@ -50,7 +50,7 @@ func ExportCmd(ctx *Context, cdc *wire.Codec, appExporter AppExporter) *cobra.Co doc.AppState = appState doc.Validators = validators - encoded, err := wire.MarshalJSONIndent(cdc, doc) + encoded, err := codec.MarshalJSONIndent(cdc, doc) if err != nil { return err } diff --git a/server/export_test.go b/server/export_test.go index 488c55bbf..999ba3c00 100644 --- a/server/export_test.go +++ b/server/export_test.go @@ -2,8 +2,8 @@ package server import ( "bytes" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server/mock" - "github.com/cosmos/cosmos-sdk/wire" "github.com/stretchr/testify/require" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" "github.com/tendermint/tendermint/libs/log" @@ -18,7 +18,7 @@ func TestEmptyState(t *testing.T) { cfg, err := tcmd.ParseConfig() require.Nil(t, err) ctx := NewContext(cfg, logger) - cdc := wire.NewCodec() + cdc := codec.New() appInit := AppInit{ AppGenTx: mock.AppGenTx, AppGenState: mock.AppGenStateEmpty, diff --git a/server/init.go b/server/init.go index ffe2b6edd..adec4fba3 100644 --- a/server/init.go +++ b/server/init.go @@ -26,9 +26,9 @@ import ( tmtypes "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" - "github.com/cosmos/cosmos-sdk/wire" ) //Parameter names, for init gen-tx command @@ -63,7 +63,7 @@ type InitConfig struct { } // get cmd to initialize all files for tendermint and application -func GenTxCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command { +func GenTxCmd(ctx *Context, cdc *codec.Codec, appInit AppInit) *cobra.Command { cmd := &cobra.Command{ Use: "gen-tx", Short: "Create genesis transaction file (under [--home]/config/gentx/gentx-[nodeID].json)", @@ -99,7 +99,7 @@ func GenTxCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command { cliPrint, genTxFile, } - out, err := wire.MarshalJSONIndent(cdc, toPrint) + out, err := codec.MarshalJSONIndent(cdc, toPrint) if err != nil { return err } @@ -112,7 +112,7 @@ func GenTxCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command { return cmd } -func gentxWithConfig(cdc *wire.Codec, appInit AppInit, config *cfg.Config, genTxConfig serverconfig.GenTx) ( +func gentxWithConfig(cdc *codec.Codec, appInit AppInit, config *cfg.Config, genTxConfig serverconfig.GenTx) ( cliPrint json.RawMessage, genTxFile json.RawMessage, err error) { nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) if err != nil { @@ -132,7 +132,7 @@ func gentxWithConfig(cdc *wire.Codec, appInit AppInit, config *cfg.Config, genTx Validator: validator, AppGenTx: appGenTx, } - bz, err := wire.MarshalJSONIndent(cdc, tx) + bz, err := codec.MarshalJSONIndent(cdc, tx) if err != nil { return } @@ -158,7 +158,7 @@ func gentxWithConfig(cdc *wire.Codec, appInit AppInit, config *cfg.Config, genTx } // get cmd to initialize all files for tendermint and application -func InitCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command { +func InitCmd(ctx *Context, cdc *codec.Codec, appInit AppInit) *cobra.Command { cmd := &cobra.Command{ Use: "init", Short: "Initialize genesis config, priv-validator file, and p2p-node file", @@ -188,7 +188,7 @@ func InitCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command { nodeID, appMessage, } - out, err := wire.MarshalJSONIndent(cdc, toPrint) + out, err := codec.MarshalJSONIndent(cdc, toPrint) if err != nil { return err } @@ -205,7 +205,7 @@ func InitCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command { return cmd } -func initWithConfig(cdc *wire.Codec, appInit AppInit, config *cfg.Config, initConfig InitConfig) ( +func initWithConfig(cdc *codec.Codec, appInit AppInit, config *cfg.Config, initConfig InitConfig) ( chainID string, nodeID string, appMessage json.RawMessage, err error) { nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) if err != nil { @@ -273,7 +273,7 @@ func initWithConfig(cdc *wire.Codec, appInit AppInit, config *cfg.Config, initCo } // append a genesis-piece -func processGenTxs(genTxsDir string, cdc *wire.Codec) ( +func processGenTxs(genTxsDir string, cdc *codec.Codec) ( validators []tmtypes.GenesisValidator, appGenTxs []json.RawMessage, persistentPeers string, err error) { var fos []os.FileInfo @@ -345,7 +345,7 @@ func readOrCreatePrivValidator(tmConfig *cfg.Config) crypto.PubKey { // 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 *wire.Codec, genesisFile, chainID string, validators []tmtypes.GenesisValidator, appState json.RawMessage) error { +func writeGenesisFile(cdc *codec.Codec, genesisFile, chainID string, validators []tmtypes.GenesisValidator, appState json.RawMessage) error { genDoc := tmtypes.GenesisDoc{ ChainID: chainID, Validators: validators, @@ -369,12 +369,12 @@ type AppInit struct { FlagsAppGenTx *pflag.FlagSet // create the application genesis tx - AppGenTx func(cdc *wire.Codec, pk crypto.PubKey, genTxConfig serverconfig.GenTx) ( + 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 *wire.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) + AppGenState func(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) } //_____________________________________________________________________ @@ -391,7 +391,7 @@ type SimpleGenTx struct { } // Generate a genesis transaction -func SimpleAppGenTx(cdc *wire.Codec, pk crypto.PubKey, genTxConfig serverconfig.GenTx) ( +func SimpleAppGenTx(cdc *codec.Codec, pk crypto.PubKey, genTxConfig serverconfig.GenTx) ( appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { var addr sdk.AccAddress @@ -424,7 +424,7 @@ func SimpleAppGenTx(cdc *wire.Codec, pk crypto.PubKey, genTxConfig serverconfig. } // create the genesis app state -func SimpleAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { +func SimpleAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { if len(appGenTxs) != 1 { err = errors.New("must provide a single genesis transaction") diff --git a/server/init_test.go b/server/init_test.go index fb448bf5f..1913ac671 100644 --- a/server/init_test.go +++ b/server/init_test.go @@ -7,8 +7,8 @@ import ( "github.com/tendermint/tendermint/libs/log" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server/mock" - "github.com/cosmos/cosmos-sdk/wire" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" ) @@ -20,7 +20,7 @@ func TestInitCmd(t *testing.T) { cfg, err := tcmd.ParseConfig() require.Nil(t, err) ctx := NewContext(cfg, logger) - cdc := wire.NewCodec() + cdc := codec.New() appInit := AppInit{ AppGenState: mock.AppGenState, AppGenTx: mock.AppGenTx, diff --git a/server/mock/app.go b/server/mock/app.go index 3c6ad3ec2..abdec6be5 100644 --- a/server/mock/app.go +++ b/server/mock/app.go @@ -12,9 +12,9 @@ import ( 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" - "github.com/cosmos/cosmos-sdk/wire" ) // NewApp creates a simple mock kvstore app for testing. It should work @@ -105,7 +105,7 @@ 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(_ *wire.Codec, _ []json.RawMessage) (appState json.RawMessage, err error) { +func AppGenState(_ *codec.Codec, _ []json.RawMessage) (appState json.RawMessage, err error) { appState = json.RawMessage(`{ "values": [ { @@ -122,13 +122,13 @@ func AppGenState(_ *wire.Codec, _ []json.RawMessage) (appState json.RawMessage, } // AppGenStateEmpty returns an empty transaction state for mocking. -func AppGenStateEmpty(_ *wire.Codec, _ []json.RawMessage) (appState json.RawMessage, err error) { +func AppGenStateEmpty(_ *codec.Codec, _ []json.RawMessage) (appState json.RawMessage, err error) { appState = json.RawMessage(``) return } // Return a validator, not much else -func AppGenTx(_ *wire.Codec, pk crypto.PubKey, genTxConfig gc.GenTx) ( +func AppGenTx(_ *codec.Codec, pk crypto.PubKey, genTxConfig gc.GenTx) ( appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { validator = tmtypes.GenesisValidator{ diff --git a/server/mock/tx.go b/server/mock/tx.go index c15e0ab2c..3bd248c74 100644 --- a/server/mock/tx.go +++ b/server/mock/tx.go @@ -31,6 +31,10 @@ func (tx kvstoreTx) Type() string { return "kvstore" } +func (tx kvstoreTx) Name() string { + return "kvstore_tx" +} + func (tx kvstoreTx) GetMsgs() []sdk.Msg { return []sdk.Msg{tx} } diff --git a/server/start.go b/server/start.go index ac13258ca..170ff9dcb 100644 --- a/server/start.go +++ b/server/start.go @@ -20,6 +20,7 @@ const ( flagAddress = "address" flagTraceStore = "trace-store" flagPruning = "pruning" + flagMinimumFees = "minimum_fees" ) // StartCmd runs the service passed in, either stand-alone or in-process with @@ -46,6 +47,7 @@ func StartCmd(ctx *Context, appCreator AppCreator) *cobra.Command { cmd.Flags().String(flagAddress, "tcp://0.0.0.0:26658", "Listen address") cmd.Flags().String(flagTraceStore, "", "Enable KVStore tracing to an output file") cmd.Flags().String(flagPruning, "syncable", "Pruning strategy: syncable, nothing, everything") + cmd.Flags().String(flagMinimumFees, "", "Minimum fees validator will accept for transactions") // add support for all Tendermint-specific command line options tcmd.AddNodeFlags(cmd) diff --git a/server/start_test.go b/server/start_test.go index 570071e7b..db9fcd40f 100644 --- a/server/start_test.go +++ b/server/start_test.go @@ -8,8 +8,8 @@ import ( "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server/mock" - "github.com/cosmos/cosmos-sdk/wire" "github.com/tendermint/tendermint/abci/server" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" "github.com/tendermint/tendermint/libs/log" @@ -26,7 +26,7 @@ func TestStartStandAlone(t *testing.T) { cfg, err := tcmd.ParseConfig() require.Nil(t, err) ctx := NewContext(cfg, logger) - cdc := wire.NewCodec() + cdc := codec.New() appInit := AppInit{ AppGenState: mock.AppGenState, AppGenTx: mock.AppGenTx, diff --git a/server/testnet.go b/server/testnet.go index 93f563005..e76263160 100644 --- a/server/testnet.go +++ b/server/testnet.go @@ -11,16 +11,18 @@ import ( "os" - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" "github.com/spf13/viper" cfg "github.com/tendermint/tendermint/config" cmn "github.com/tendermint/tendermint/libs/common" ) var ( - nodeDirPrefix = "node-dir-prefix" - nValidators = "v" - outputDir = "output-dir" + nodeDirPrefix = "node-dir-prefix" + nValidators = "v" + outputDir = "output-dir" + nodeDaemonHome = "node-daemon-home" + nodeCliHome = "node-cli-home" startingIPAddress = "starting-ip-address" ) @@ -28,7 +30,7 @@ var ( const nodeDirPerm = 0755 // get cmd to initialize all files for tendermint testnet and application -func TestnetFilesCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command { +func TestnetFilesCmd(ctx *Context, cdc *codec.Codec, appInit AppInit) *cobra.Command { cmd := &cobra.Command{ Use: "testnet", Short: "Initialize files for a Gaiad testnet", @@ -39,7 +41,7 @@ Note, strict routability for addresses is turned off in the config file. Example: - gaiad testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2 + gaiad testnet --v 4 --o ./output --starting-ip-address 192.168.10.2 `, RunE: func(_ *cobra.Command, _ []string) error { config := ctx.Config @@ -53,21 +55,27 @@ Example: "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", "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)") return cmd } -func testnetWithConfig(config *cfg.Config, cdc *wire.Codec, appInit AppInit) error { +func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit AppInit) error { outDir := viper.GetString(outputDir) numValidators := viper.GetInt(nValidators) // Generate private key, node ID, initial transaction for i := 0; i < numValidators; i++ { nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i) - nodeDir := filepath.Join(outDir, nodeDirName, "gaiad") - clientDir := filepath.Join(outDir, nodeDirName, "gaiacli") + nodeDaemonHomeName := viper.GetString(nodeDaemonHome) + nodeCliHomeName := viper.GetString(nodeCliHome) + nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName) + clientDir := filepath.Join(outDir, nodeDirName, nodeCliHomeName) gentxsDir := filepath.Join(outDir, "gentxs") config.SetRoot(nodeDir) @@ -122,7 +130,8 @@ func testnetWithConfig(config *cfg.Config, cdc *wire.Codec, appInit AppInit) err for i := 0; i < numValidators; i++ { nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i) - nodeDir := filepath.Join(outDir, nodeDirName, "gaiad") + nodeDaemonHomeName := viper.GetString(nodeDaemonHome) + nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName) gentxsDir := filepath.Join(outDir, "gentxs") initConfig := InitConfig{ chainID, diff --git a/server/tm_cmds.go b/server/tm_cmds.go index bf208a5be..82652bdec 100644 --- a/server/tm_cmds.go +++ b/server/tm_cmds.go @@ -3,7 +3,7 @@ package server import ( "fmt" - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -83,8 +83,8 @@ func ShowAddressCmd(ctx *Context) *cobra.Command { } func printlnJSON(v interface{}) error { - cdc := wire.NewCodec() - wire.RegisterCrypto(cdc) + cdc := codec.New() + codec.RegisterCrypto(cdc) marshalled, err := cdc.MarshalJSON(v) if err != nil { return err diff --git a/server/util.go b/server/util.go index 04c539eb4..9508e0489 100644 --- a/server/util.go +++ b/server/util.go @@ -11,8 +11,9 @@ import ( "github.com/spf13/viper" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server/config" "github.com/cosmos/cosmos-sdk/version" - "github.com/cosmos/cosmos-sdk/wire" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/libs/cli" @@ -51,6 +52,10 @@ func PersistentPreRunEFn(context *Context) func(*cobra.Command, []string) error if err != nil { return err } + err = validateConfig(config) + if err != nil { + return err + } logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel()) if err != nil { @@ -93,12 +98,34 @@ func interceptLoadConfig() (conf *cfg.Config, err error) { if conf == nil { conf, err = tcmd.ParseConfig() } + + cosmosConfigFilePath := filepath.Join(rootDir, "config/gaiad.toml") + viper.SetConfigName("cosmos") + _ = viper.MergeInConfig() + var cosmosConf *config.Config + if _, err := os.Stat(cosmosConfigFilePath); os.IsNotExist(err) { + cosmosConf, _ := config.ParseConfig() + config.WriteConfigFile(cosmosConfigFilePath, cosmosConf) + } + + if cosmosConf == nil { + _, err = config.ParseConfig() + } + return } +// validate the config with the sdk's requirements. +func validateConfig(conf *cfg.Config) error { + if conf.Consensus.CreateEmptyBlocks == false { + return errors.New("config option CreateEmptyBlocks = false is currently unsupported") + } + return nil +} + // add server commands func AddCommands( - ctx *Context, cdc *wire.Codec, + ctx *Context, cdc *codec.Codec, rootCmd *cobra.Command, appInit AppInit, appCreator AppCreator, appExport AppExporter) { @@ -135,7 +162,7 @@ func AddCommands( // // NOTE: The ordering of the keys returned as the resulting JSON message is // non-deterministic, so the client should not rely on key ordering. -func InsertKeyJSON(cdc *wire.Codec, baseJSON []byte, key string, value json.RawMessage) ([]byte, error) { +func InsertKeyJSON(cdc *codec.Codec, baseJSON []byte, key string, value json.RawMessage) ([]byte, error) { var jsonMap map[string]json.RawMessage if err := cdc.UnmarshalJSON(baseJSON, &jsonMap); err != nil { @@ -143,7 +170,7 @@ func InsertKeyJSON(cdc *wire.Codec, baseJSON []byte, key string, value json.RawM } jsonMap[key] = value - bz, err := wire.MarshalJSONIndent(cdc, jsonMap) + bz, err := codec.MarshalJSONIndent(cdc, jsonMap) return json.RawMessage(bz), err } diff --git a/server/util_test.go b/server/util_test.go index 6082caa2c..2c16759c9 100644 --- a/server/util_test.go +++ b/server/util_test.go @@ -4,12 +4,12 @@ import ( "encoding/json" "testing" - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" "github.com/stretchr/testify/require" ) func TestInsertKeyJSON(t *testing.T) { - cdc := wire.NewCodec() + cdc := codec.New() foo := map[string]string{"foo": "foofoo"} bar := map[string]string{"barInner": "barbar"} diff --git a/store/cachekvstore_test.go b/store/cachekvstore_test.go index 4c56b9b87..37e0364fb 100644 --- a/store/cachekvstore_test.go +++ b/store/cachekvstore_test.go @@ -494,3 +494,25 @@ func (krc *keyRangeCounter) key() int { //-------------------------------------------------------- func bz(s string) []byte { return []byte(s) } + +func BenchmarkCacheKVStoreGetNoKeyFound(b *testing.B) { + st := newCacheKVStore() + b.ResetTimer() + // assumes b.N < 2**24 + for i := 0; i < b.N; i++ { + st.Get([]byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)}) + } +} + +func BenchmarkCacheKVStoreGetKeyFound(b *testing.B) { + st := newCacheKVStore() + for i := 0; i < b.N; i++ { + arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} + st.Set(arr, arr) + } + b.ResetTimer() + // assumes b.N < 2**24 + for i := 0; i < b.N; i++ { + st.Get([]byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)}) + } +} diff --git a/store/types.go b/store/codec.go similarity index 100% rename from store/types.go rename to store/codec.go diff --git a/store/gaskvstore.go b/store/gaskvstore.go index 53ac340bd..2ce6415e6 100644 --- a/store/gaskvstore.go +++ b/store/gaskvstore.go @@ -8,13 +8,15 @@ import ( var _ KVStore = &gasKVStore{} -// gasKVStore applies gas tracking to an underlying kvstore +// gasKVStore applies gas tracking to an underlying KVStore. It implements the +// KVStore interface. type gasKVStore struct { gasMeter sdk.GasMeter gasConfig sdk.GasConfig parent sdk.KVStore } +// NewGasKVStore returns a reference to a new GasKVStore. // nolint func NewGasKVStore(gasMeter sdk.GasMeter, gasConfig sdk.GasConfig, parent sdk.KVStore) *gasKVStore { kvs := &gasKVStore{ @@ -26,83 +28,96 @@ func NewGasKVStore(gasMeter sdk.GasMeter, gasConfig sdk.GasConfig, parent sdk.KV } // Implements Store. -func (gi *gasKVStore) GetStoreType() sdk.StoreType { - return gi.parent.GetStoreType() +func (gs *gasKVStore) GetStoreType() sdk.StoreType { + return gs.parent.GetStoreType() } // Implements KVStore. -func (gi *gasKVStore) Get(key []byte) (value []byte) { - gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostFlat, "ReadFlat") - value = gi.parent.Get(key) +func (gs *gasKVStore) Get(key []byte) (value []byte) { + gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, sdk.GasReadCostFlatDesc) + value = gs.parent.Get(key) + // TODO overflow-safe math? - gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*sdk.Gas(len(value)), "ReadPerByte") + gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*sdk.Gas(len(value)), sdk.GasReadPerByteDesc) return value } // Implements KVStore. -func (gi *gasKVStore) Set(key []byte, value []byte) { - gi.gasMeter.ConsumeGas(gi.gasConfig.WriteCostFlat, "WriteFlat") +func (gs *gasKVStore) Set(key []byte, value []byte) { + gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostFlat, sdk.GasWriteCostFlatDesc) // TODO overflow-safe math? - gi.gasMeter.ConsumeGas(gi.gasConfig.WriteCostPerByte*sdk.Gas(len(value)), "WritePerByte") - gi.parent.Set(key, value) + gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*sdk.Gas(len(value)), sdk.GasWritePerByteDesc) + gs.parent.Set(key, value) } // Implements KVStore. -func (gi *gasKVStore) Has(key []byte) bool { - gi.gasMeter.ConsumeGas(gi.gasConfig.HasCost, "Has") - return gi.parent.Has(key) +func (gs *gasKVStore) Has(key []byte) bool { + gs.gasMeter.ConsumeGas(gs.gasConfig.HasCost, sdk.GasHasDesc) + return gs.parent.Has(key) } // Implements KVStore. -func (gi *gasKVStore) Delete(key []byte) { - // No gas costs for deletion - gi.parent.Delete(key) +func (gs *gasKVStore) Delete(key []byte) { + // charge gas to prevent certain attack vectors even though space is being freed + gs.gasMeter.ConsumeGas(gs.gasConfig.DeleteCost, sdk.GasDeleteDesc) + gs.parent.Delete(key) } // Implements KVStore -func (gi *gasKVStore) Prefix(prefix []byte) KVStore { +func (gs *gasKVStore) Prefix(prefix []byte) KVStore { // Keep gasstore layer at the top return &gasKVStore{ - gasMeter: gi.gasMeter, - gasConfig: gi.gasConfig, - parent: prefixStore{gi.parent, prefix}, + gasMeter: gs.gasMeter, + gasConfig: gs.gasConfig, + parent: prefixStore{gs.parent, prefix}, } } // Implements KVStore -func (gi *gasKVStore) Gas(meter GasMeter, config GasConfig) KVStore { - return NewGasKVStore(meter, config, gi) +func (gs *gasKVStore) Gas(meter GasMeter, config GasConfig) KVStore { + return NewGasKVStore(meter, config, gs) +} + +// Iterator implements the KVStore interface. It returns an iterator which +// incurs a flat gas cost for seeking to the first key/value pair and a variable +// gas cost based on the current value's length if the iterator is valid. +func (gs *gasKVStore) Iterator(start, end []byte) sdk.Iterator { + return gs.iterator(start, end, true) +} + +// ReverseIterator implements the KVStore interface. It returns a reverse +// iterator which incurs a flat gas cost for seeking to the first key/value pair +// and a variable gas cost based on the current value's length if the iterator +// is valid. +func (gs *gasKVStore) ReverseIterator(start, end []byte) sdk.Iterator { + return gs.iterator(start, end, false) } // Implements KVStore. -func (gi *gasKVStore) Iterator(start, end []byte) sdk.Iterator { - return gi.iterator(start, end, true) -} - -// Implements KVStore. -func (gi *gasKVStore) ReverseIterator(start, end []byte) sdk.Iterator { - return gi.iterator(start, end, false) -} - -// Implements KVStore. -func (gi *gasKVStore) CacheWrap() sdk.CacheWrap { +func (gs *gasKVStore) CacheWrap() sdk.CacheWrap { panic("cannot CacheWrap a GasKVStore") } // CacheWrapWithTrace implements the KVStore interface. -func (gi *gasKVStore) CacheWrapWithTrace(_ io.Writer, _ TraceContext) CacheWrap { +func (gs *gasKVStore) CacheWrapWithTrace(_ io.Writer, _ TraceContext) CacheWrap { panic("cannot CacheWrapWithTrace a GasKVStore") } -func (gi *gasKVStore) iterator(start, end []byte, ascending bool) sdk.Iterator { +func (gs *gasKVStore) iterator(start, end []byte, ascending bool) sdk.Iterator { var parent sdk.Iterator if ascending { - parent = gi.parent.Iterator(start, end) + parent = gs.parent.Iterator(start, end) } else { - parent = gi.parent.ReverseIterator(start, end) + parent = gs.parent.ReverseIterator(start, end) } - return newGasIterator(gi.gasMeter, gi.gasConfig, parent) + + gi := newGasIterator(gs.gasMeter, gs.gasConfig, parent) + if gi.Valid() { + gi.(*gasIterator).consumeSeekGas() + } + + return gi } type gasIterator struct { @@ -120,36 +135,50 @@ func newGasIterator(gasMeter sdk.GasMeter, gasConfig sdk.GasConfig, parent sdk.I } // Implements Iterator. -func (g *gasIterator) Domain() (start []byte, end []byte) { - return g.parent.Domain() +func (gi *gasIterator) Domain() (start []byte, end []byte) { + return gi.parent.Domain() } // Implements Iterator. -func (g *gasIterator) Valid() bool { - return g.parent.Valid() +func (gi *gasIterator) Valid() bool { + return gi.parent.Valid() } -// Implements Iterator. -func (g *gasIterator) Next() { - g.parent.Next() +// Next implements the Iterator interface. It seeks to the next key/value pair +// in the iterator. It incurs a flat gas cost for seeking and a variable gas +// cost based on the current value's length if the iterator is valid. +func (gi *gasIterator) Next() { + if gi.Valid() { + gi.consumeSeekGas() + } + + gi.parent.Next() } -// Implements Iterator. -func (g *gasIterator) Key() (key []byte) { - g.gasMeter.ConsumeGas(g.gasConfig.KeyCostFlat, "KeyFlat") - key = g.parent.Key() +// Key implements the Iterator interface. It returns the current key and it does +// not incur any gas cost. +func (gi *gasIterator) Key() (key []byte) { + key = gi.parent.Key() return key } -// Implements Iterator. -func (g *gasIterator) Value() (value []byte) { - value = g.parent.Value() - g.gasMeter.ConsumeGas(g.gasConfig.ValueCostFlat, "ValueFlat") - g.gasMeter.ConsumeGas(g.gasConfig.ValueCostPerByte*sdk.Gas(len(value)), "ValuePerByte") +// Value implements the Iterator interface. It returns the current value and it +// does not incur any gas cost. +func (gi *gasIterator) Value() (value []byte) { + value = gi.parent.Value() return value } // Implements Iterator. -func (g *gasIterator) Close() { - g.parent.Close() +func (gi *gasIterator) Close() { + gi.parent.Close() +} + +// consumeSeekGas consumes a flat gas cost for seeking and a variable gas cost +// based on the current value's length. +func (gi *gasIterator) consumeSeekGas() { + value := gi.Value() + + gi.gasMeter.ConsumeGas(gi.gasConfig.ValueCostPerByte*sdk.Gas(len(value)), sdk.GasValuePerByteDesc) + gi.gasMeter.ConsumeGas(gi.gasConfig.IterNextCostFlat, sdk.GasIterNextCostFlatDesc) } diff --git a/store/gaskvstore_test.go b/store/gaskvstore_test.go index b2c23c955..eb3ae7dd4 100644 --- a/store/gaskvstore_test.go +++ b/store/gaskvstore_test.go @@ -25,7 +25,7 @@ func TestGasKVStoreBasic(t *testing.T) { require.Equal(t, valFmt(1), st.Get(keyFmt(1))) st.Delete(keyFmt(1)) require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") - require.Equal(t, meter.GasConsumed(), sdk.Gas(183)) + require.Equal(t, meter.GasConsumed(), sdk.Gas(193)) } func TestGasKVStoreIterator(t *testing.T) { @@ -49,7 +49,7 @@ func TestGasKVStoreIterator(t *testing.T) { iterator.Next() require.False(t, iterator.Valid()) require.Panics(t, iterator.Next) - require.Equal(t, meter.GasConsumed(), sdk.Gas(356)) + require.Equal(t, meter.GasConsumed(), sdk.Gas(384)) } func TestGasKVStoreOutOfGasSet(t *testing.T) { diff --git a/store/iavlstore.go b/store/iavlstore.go index a3872bb0f..d535fd436 100644 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -96,7 +96,7 @@ func (st *iavlStore) Commit() CommitID { // Implements Committer. func (st *iavlStore) LastCommitID() CommitID { return CommitID{ - Version: st.tree.Version64(), + Version: st.tree.Version(), Hash: st.tree.Hash(), } } @@ -180,7 +180,7 @@ func (st *iavlStore) ReverseIterator(start, end []byte) Iterator { func getHeight(tree *iavl.MutableTree, req abci.RequestQuery) int64 { height := req.Height if height == 0 { - latest := tree.Version64() + latest := tree.Version() if tree.VersionExists(latest - 1) { height = latest - 1 } else { diff --git a/store/multistoreproof.go b/store/multistoreproof.go index e25f1cc1f..d62bc4aca 100644 --- a/store/multistoreproof.go +++ b/store/multistoreproof.go @@ -2,6 +2,7 @@ package store import ( "bytes" + "github.com/pkg/errors" "github.com/tendermint/iavl" cmn "github.com/tendermint/tendermint/libs/common" @@ -47,7 +48,6 @@ func VerifyMultiStoreCommitInfo(storeName string, storeInfos []storeInfo, appHas Version: height, StoreInfos: storeInfos, } - if !bytes.Equal(appHash, ci.Hash()) { return nil, cmn.NewError("the merkle root of multiStoreCommitInfo doesn't equal to appHash") } diff --git a/store/multistoreproof_test.go b/store/multistoreproof_test.go index 790588fb2..45a102cd3 100644 --- a/store/multistoreproof_test.go +++ b/store/multistoreproof_test.go @@ -5,13 +5,14 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/tendermint/iavl" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/db" ) func TestVerifyMultiStoreCommitInfo(t *testing.T) { - appHash, _ := hex.DecodeString("ebf3c1fb724d3458023c8fefef7b33add2fc1e84") + appHash, _ := hex.DecodeString("69959B1B4E68E0F7BD3551A50C8F849B81801AF2") substoreRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") storeName := "acc" @@ -83,13 +84,13 @@ func TestVerifyMultiStoreCommitInfo(t *testing.T) { }) commitHash, err := VerifyMultiStoreCommitInfo(storeName, storeInfos, appHash) - assert.Nil(t, err) - assert.Equal(t, commitHash, substoreRootHash) + require.Nil(t, err) + require.Equal(t, commitHash, substoreRootHash) appHash, _ = hex.DecodeString("29de216bf5e2531c688de36caaf024cd3bb09ee3") _, err = VerifyMultiStoreCommitInfo(storeName, storeInfos, appHash) - assert.Error(t, err, "appHash doesn't match to the merkle root of multiStoreCommitInfo") + require.Error(t, err, "appHash doesn't match to the merkle root of multiStoreCommitInfo") } func TestVerifyRangeProof(t *testing.T) { diff --git a/store/rootmultistore.go b/store/rootmultistore.go index d9cf8a29a..7de465f77 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -5,10 +5,9 @@ import ( "io" "strings" - "golang.org/x/crypto/ripemd160" - abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/merkle" + "github.com/tendermint/tendermint/crypto/tmhash" dbm "github.com/tendermint/tendermint/libs/db" sdk "github.com/cosmos/cosmos-sdk/types" @@ -120,28 +119,28 @@ func (rs *rootMultiStore) LoadVersion(ver int64) error { return err } + // Convert StoreInfos slice to map + infos := make(map[StoreKey]storeInfo) + for _, storeInfo := range cInfo.StoreInfos { + infos[rs.nameToKey(storeInfo.Name)] = storeInfo + } + // Load each Store var newStores = make(map[StoreKey]CommitStore) - for _, storeInfo := range cInfo.StoreInfos { - key, commitID := rs.nameToKey(storeInfo.Name), storeInfo.Core.CommitID - storeParams := rs.storesParams[key] - store, err := rs.loadCommitStoreFromParams(key, commitID, storeParams) + for key, storeParams := range rs.storesParams { + var id CommitID + info, ok := infos[key] + if ok { + id = info.Core.CommitID + } + + store, err := rs.loadCommitStoreFromParams(key, id, storeParams) if err != nil { return fmt.Errorf("failed to load rootMultiStore: %v", err) } newStores[key] = store } - // TODO: detecting transient is quite adhoc - // If any nontransient CommitStoreLoaders were not used, return error. - for key, param := range rs.storesParams { - if param.typ != sdk.StoreTypeTransient { - if _, ok := newStores[key]; !ok { - return fmt.Errorf("unused CommitStoreLoader: %v", key) - } - } - } - // Success. rs.lastCommitID = cInfo.CommitID() rs.stores = newStores @@ -424,7 +423,7 @@ 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 - hasher := ripemd160.New() + hasher := tmhash.New() _, err := hasher.Write(bz) if err != nil { // TODO: Handle with #870 diff --git a/store/transientstore.go b/store/transientstore.go index 1c099fa0d..a3ce89631 100644 --- a/store/transientstore.go +++ b/store/transientstore.go @@ -1,6 +1,7 @@ package store import ( + sdk "github.com/cosmos/cosmos-sdk/types" dbm "github.com/tendermint/tendermint/libs/db" ) @@ -41,3 +42,8 @@ func (ts *transientStore) Prefix(prefix []byte) KVStore { func (ts *transientStore) Gas(meter GasMeter, config GasConfig) KVStore { return NewGasKVStore(meter, config, ts) } + +// Implements Store. +func (ts *transientStore) GetStoreType() StoreType { + return sdk.StoreTypeTransient +} diff --git a/store/wire.go b/store/wire.go index 7befbdfcd..5724c8e54 100644 --- a/store/wire.go +++ b/store/wire.go @@ -1,7 +1,7 @@ package store import ( - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" ) -var cdc = wire.NewCodec() +var cdc = codec.New() diff --git a/tools/Makefile b/tools/Makefile index 87544107c..c24e886ff 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -12,7 +12,6 @@ INEFFASSIGN = github.com/gordonklaus/ineffassign MISSPELL = github.com/client9/misspell/cmd/misspell ERRCHECK = github.com/kisielk/errcheck UNPARAM = mvdan.cc/unparam -GOCYCLO = github.com/alecthomas/gocyclo DEP_CHECK := $(shell command -v dep 2> /dev/null) GOLINT_CHECK := $(shell command -v golint 2> /dev/null) @@ -22,7 +21,6 @@ INEFFASSIGN_CHECK := $(shell command -v ineffassign 2> /dev/null) MISSPELL_CHECK := $(shell command -v misspell 2> /dev/null) ERRCHECK_CHECK := $(shell command -v errcheck 2> /dev/null) UNPARAM_CHECK := $(shell command -v unparam 2> /dev/null) -# GOCYCLO_CHECK := $(shell command -v gocyclo 2> /dev/null) check_tools: ifndef DEP_CHECK @@ -68,11 +66,6 @@ ifndef UNPARAM_CHECK else @echo "Found unparam in path." endif -ifndef GOCYCLO_CHECK - @echo "No gocyclo in path. Install with 'make get_tools'." -else - @echo "Found gocyclo in path." -endif get_tools: ifdef DEP_CHECK @@ -126,12 +119,6 @@ else @echo "Installing unparam" go get -v $(UNPARAM) endif -# ifdef GOCYCLO_CHECK -# @echo "gocyclo is already installed. Run 'make update_tools' to update." -# else -# @echo "Installing gocyclo" -# go get -v $(GOCYCLO) -# endif update_tools: @echo "Updating dep" @@ -153,8 +140,6 @@ update_dev_tools: go get -u -v $(ERRCHECK) @echo "Updating unparam" go get -u -v $(UNPARAM) - # @echo "Updating goyclo" - # go get -u -v $(GOCYCLO) # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. diff --git a/types/address.go b/types/address.go index 58b694f5d..ae13b2ad0 100644 --- a/types/address.go +++ b/types/address.go @@ -292,6 +292,11 @@ func ConsAddressFromBech32(address string) (addr ConsAddress, err error) { return ConsAddress(bz), nil } +// get ConsAddress from pubkey +func GetConsAddress(pubkey crypto.PubKey) ConsAddress { + return ConsAddress(pubkey.Address()) +} + // Returns boolean for whether two ConsAddress are Equal func (ca ConsAddress) Equals(ca2 ConsAddress) bool { if ca.Empty() && ca2.Empty() { diff --git a/types/address_test.go b/types/address_test.go index e2ec36876..6c6c78d6e 100644 --- a/types/address_test.go +++ b/types/address_test.go @@ -10,7 +10,7 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" "github.com/cosmos/cosmos-sdk/types" -) + ) var invalidStrs = []string{ "", diff --git a/types/wire.go b/types/codec.go similarity index 60% rename from types/wire.go rename to types/codec.go index 2ef28820d..3d789afe9 100644 --- a/types/wire.go +++ b/types/codec.go @@ -1,9 +1,9 @@ package types -import wire "github.com/cosmos/cosmos-sdk/wire" +import "github.com/cosmos/cosmos-sdk/codec" // Register the sdk message type -func RegisterWire(cdc *wire.Codec) { +func RegisterCodec(cdc *codec.Codec) { cdc.RegisterInterface((*Msg)(nil), nil) cdc.RegisterInterface((*Tx)(nil), nil) } diff --git a/types/coin.go b/types/coin.go index aa6029559..d7484a669 100644 --- a/types/coin.go +++ b/types/coin.go @@ -46,6 +46,12 @@ func (coin Coin) IsGTE(other Coin) bool { return coin.SameDenomAs(other) && (!coin.Amount.LT(other.Amount)) } +// 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) +} + // IsEqual returns true if the two sets of Coins have the same value func (coin Coin) IsEqual(other Coin) bool { return coin.SameDenomAs(other) && (coin.Amount.Equal(other.Amount)) @@ -181,6 +187,12 @@ 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) +} + // IsZero returns true if there are no coins // or all coins are zero. func (coins Coins) IsZero() bool { diff --git a/types/coin_test.go b/types/coin_test.go index 145c0c40a..ba7038010 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -1,6 +1,7 @@ package types import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -76,6 +77,24 @@ func TestIsGTECoin(t *testing.T) { } } +func TestIsLTCoin(t *testing.T) { + cases := []struct { + inputOne Coin + inputTwo Coin + expected bool + }{ + {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}, + } + + for tcIndex, tc := range cases { + res := tc.inputOne.IsLT(tc.inputTwo) + require.Equal(t, tc.expected, res, "coin LT relation is incorrect, tc #%d", tcIndex) + } +} + func TestIsEqualCoin(t *testing.T) { cases := []struct { inputOne Coin @@ -227,6 +246,8 @@ func TestCoins(t *testing.T) { 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.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") @@ -403,3 +424,55 @@ func TestAmountOf(t *testing.T) { assert.Equal(t, NewInt(tc.amountOfTREE), tc.coins.AmountOf("TREE")) } } + +func BenchmarkCoinsAdditionIntersect(b *testing.B) { + benchmarkingFunc := func(numCoinsA int, numCoinsB int) func(b *testing.B) { + return func(b *testing.B) { + coinsA := Coins(make([]Coin, numCoinsA)) + coinsB := Coins(make([]Coin, numCoinsB)) + for i := 0; i < numCoinsA; i++ { + coinsA[i] = NewCoin("COINZ_"+string(i), NewInt(int64(i))) + } + for i := 0; i < numCoinsB; i++ { + coinsB[i] = NewCoin("COINZ_"+string(i), NewInt(int64(i))) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + coinsA.Plus(coinsB) + } + } + } + + benchmarkSizes := [][]int{[]int{1, 1}, []int{5, 5}, []int{5, 20}, []int{1, 1000}, []int{2, 1000}} + for i := 0; i < len(benchmarkSizes); i++ { + sizeA := benchmarkSizes[i][0] + sizeB := benchmarkSizes[i][1] + b.Run(fmt.Sprintf("sizes: A_%d, B_%d", sizeA, sizeB), benchmarkingFunc(sizeA, sizeB)) + } +} + +func BenchmarkCoinsAdditionNoIntersect(b *testing.B) { + benchmarkingFunc := func(numCoinsA int, numCoinsB int) func(b *testing.B) { + return func(b *testing.B) { + coinsA := Coins(make([]Coin, numCoinsA)) + coinsB := Coins(make([]Coin, numCoinsB)) + for i := 0; i < numCoinsA; i++ { + coinsA[i] = NewCoin("COINZ_"+string(numCoinsB+i), NewInt(int64(i))) + } + for i := 0; i < numCoinsB; i++ { + coinsB[i] = NewCoin("COINZ_"+string(i), NewInt(int64(i))) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + coinsA.Plus(coinsB) + } + } + } + + benchmarkSizes := [][]int{[]int{1, 1}, []int{5, 5}, []int{5, 20}, []int{1, 1000}, []int{2, 1000}, []int{1000, 2}} + for i := 0; i < len(benchmarkSizes); i++ { + sizeA := benchmarkSizes[i][0] + sizeB := benchmarkSizes[i][1] + b.Run(fmt.Sprintf("sizes: A_%d, B_%d", sizeA, sizeB), benchmarkingFunc(sizeA, sizeB)) + } +} diff --git a/types/context.go b/types/context.go index 12a3e1478..fd2e07688 100644 --- a/types/context.go +++ b/types/context.go @@ -1,3 +1,4 @@ +// nolint package types import ( @@ -41,10 +42,12 @@ func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, logger log.Lo c = c.WithBlockHeader(header) c = c.WithBlockHeight(header.Height) c = c.WithChainID(header.ChainID) + c = c.WithIsCheckTx(isCheckTx) c = c.WithTxBytes(nil) c = c.WithLogger(logger) c = c.WithVoteInfos(nil) c = c.WithGasMeter(NewInfiniteGasMeter()) + c = c.WithMinimumFees(Coins{}) return c } @@ -132,10 +135,12 @@ const ( contextKeyBlockHeight contextKeyConsensusParams contextKeyChainID + contextKeyIsCheckTx contextKeyTxBytes contextKeyLogger contextKeyVoteInfos contextKeyGasMeter + contextKeyMinimumFees ) // NOTE: Do not expose MultiStore. @@ -145,41 +150,41 @@ func (c Context) multiStore() MultiStore { return c.Value(contextKeyMultiStore).(MultiStore) } -// nolint -func (c Context) BlockHeader() abci.Header { - return c.Value(contextKeyBlockHeader).(abci.Header) -} -func (c Context) BlockHeight() int64 { - return c.Value(contextKeyBlockHeight).(int64) -} +func (c Context) BlockHeader() abci.Header { return c.Value(contextKeyBlockHeader).(abci.Header) } + +func (c Context) BlockHeight() int64 { return c.Value(contextKeyBlockHeight).(int64) } + func (c Context) ConsensusParams() abci.ConsensusParams { return c.Value(contextKeyConsensusParams).(abci.ConsensusParams) } -func (c Context) ChainID() string { - return c.Value(contextKeyChainID).(string) -} -func (c Context) TxBytes() []byte { - return c.Value(contextKeyTxBytes).([]byte) -} -func (c Context) Logger() log.Logger { - return c.Value(contextKeyLogger).(log.Logger) -} + +func (c Context) ChainID() string { return c.Value(contextKeyChainID).(string) } + +func (c Context) TxBytes() []byte { return c.Value(contextKeyTxBytes).([]byte) } + +func (c Context) Logger() log.Logger { return c.Value(contextKeyLogger).(log.Logger) } + func (c Context) VoteInfos() []abci.VoteInfo { return c.Value(contextKeyVoteInfos).([]abci.VoteInfo) } -func (c Context) GasMeter() GasMeter { - return c.Value(contextKeyGasMeter).(GasMeter) -} -func (c Context) WithMultiStore(ms MultiStore) Context { - return c.withValue(contextKeyMultiStore, ms) -} + +func (c Context) GasMeter() GasMeter { return c.Value(contextKeyGasMeter).(GasMeter) } + +func (c Context) IsCheckTx() bool { return c.Value(contextKeyIsCheckTx).(bool) } + +func (c Context) MinimumFees() Coins { return c.Value(contextKeyMinimumFees).(Coins) } + +func (c Context) WithMultiStore(ms MultiStore) Context { return c.withValue(contextKeyMultiStore, ms) } + func (c Context) WithBlockHeader(header abci.Header) Context { var _ proto.Message = &header // for cloning. return c.withValue(contextKeyBlockHeader, header) } + func (c Context) WithBlockHeight(height int64) Context { return c.withValue(contextKeyBlockHeight, height) } + func (c Context) WithConsensusParams(params *abci.ConsensusParams) Context { if params == nil { return c @@ -187,20 +192,25 @@ func (c Context) WithConsensusParams(params *abci.ConsensusParams) Context { return c.withValue(contextKeyConsensusParams, params). WithGasMeter(NewGasMeter(params.TxSize.MaxGas)) } -func (c Context) WithChainID(chainID string) Context { - return c.withValue(contextKeyChainID, chainID) -} -func (c Context) WithTxBytes(txBytes []byte) Context { - return c.withValue(contextKeyTxBytes, txBytes) -} -func (c Context) WithLogger(logger log.Logger) Context { - return c.withValue(contextKeyLogger, logger) -} + +func (c Context) WithChainID(chainID string) Context { return c.withValue(contextKeyChainID, chainID) } + +func (c Context) WithTxBytes(txBytes []byte) Context { return c.withValue(contextKeyTxBytes, txBytes) } + +func (c Context) WithLogger(logger log.Logger) Context { return c.withValue(contextKeyLogger, logger) } + func (c Context) WithVoteInfos(VoteInfos []abci.VoteInfo) Context { return c.withValue(contextKeyVoteInfos, VoteInfos) } -func (c Context) WithGasMeter(meter GasMeter) Context { - return c.withValue(contextKeyGasMeter, meter) + +func (c Context) WithGasMeter(meter GasMeter) Context { return c.withValue(contextKeyGasMeter, meter) } + +func (c Context) WithIsCheckTx(isCheckTx bool) Context { + return c.withValue(contextKeyIsCheckTx, isCheckTx) +} + +func (c Context) WithMinimumFees(minFees Coins) Context { + return c.withValue(contextKeyMinimumFees, minFees) } // Cache the multistore and return a new cached context. The cached context is diff --git a/types/context_test.go b/types/context_test.go index c5ec604fa..002691229 100644 --- a/types/context_test.go +++ b/types/context_test.go @@ -162,20 +162,23 @@ func TestContextWithCustom(t *testing.T) { logger := NewMockLogger() voteinfos := []abci.VoteInfo{{}} meter := types.NewGasMeter(10000) + minFees := types.Coins{types.NewInt64Coin("feeCoin", 1)} ctx = types.NewContext(nil, header, ischeck, logger). WithBlockHeight(height). WithChainID(chainid). WithTxBytes(txbytes). WithVoteInfos(voteinfos). - WithGasMeter(meter) + 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()) require.Equal(t, txbytes, ctx.TxBytes()) require.Equal(t, logger, ctx.Logger()) require.Equal(t, voteinfos, ctx.VoteInfos()) require.Equal(t, meter, ctx.GasMeter()) - + require.Equal(t, minFees, types.Coins{types.NewInt64Coin("feeCoin", 1)}) } diff --git a/types/decimal.go b/types/decimal.go index 8e7db1340..13a8a26c1 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -174,13 +174,15 @@ func NewDecFromStr(str string) (d Dec, err Error) { //______________________________________________________________________________________________ //nolint -func (d Dec) IsZero() bool { return (d.Int).Sign() == 0 } // Is equal to zero -func (d Dec) Equal(d2 Dec) bool { return (d.Int).Cmp(d2.Int) == 0 } +func (d Dec) IsNil() bool { return d.Int == nil } // is decimal nil +func (d Dec) IsZero() bool { return (d.Int).Sign() == 0 } // is equal to zero +func (d Dec) Equal(d2 Dec) bool { return (d.Int).Cmp(d2.Int) == 0 } // equal decimals func (d Dec) GT(d2 Dec) bool { return (d.Int).Cmp(d2.Int) > 0 } // greater than func (d Dec) GTE(d2 Dec) bool { return (d.Int).Cmp(d2.Int) >= 0 } // greater than or equal func (d Dec) LT(d2 Dec) bool { return (d.Int).Cmp(d2.Int) < 0 } // less than func (d Dec) LTE(d2 Dec) bool { return (d.Int).Cmp(d2.Int) <= 0 } // less than or equal func (d Dec) Neg() Dec { return Dec{new(big.Int).Neg(d.Int)} } // reverse the decimal sign +func (d Dec) Abs() Dec { return Dec{new(big.Int).Abs(d.Int)} } // absolute value // addition func (d Dec) Add(d2 Dec) Dec { @@ -213,6 +215,16 @@ func (d Dec) Mul(d2 Dec) Dec { return Dec{chopped} } +// multiplication +func (d Dec) MulInt(i Int) Dec { + mul := new(big.Int).Mul(d.Int, i.i) + + if mul.BitLen() > 255+DecimalPrecisionBits { + panic("Int overflow") + } + return Dec{mul} +} + // quotient func (d Dec) Quo(d2 Dec) Dec { @@ -323,6 +335,32 @@ func (d Dec) RoundInt() Int { //___________________________________________________________________________________ +// similar to chopPrecisionAndRound, but always rounds down +func chopPrecisionAndTruncate(d *big.Int) *big.Int { + return d.Quo(d, precisionReuse) +} + +func chopPrecisionAndTruncateNonMutative(d *big.Int) *big.Int { + tmp := new(big.Int).Set(d) + return chopPrecisionAndTruncate(tmp) +} + +// TruncateInt64 truncates the decimals from the number and returns an int64 +func (d Dec) TruncateInt64() int64 { + chopped := chopPrecisionAndTruncateNonMutative(d.Int) + if !chopped.IsInt64() { + panic("Int64() out of bound") + } + return chopped.Int64() +} + +// TruncateInt truncates the decimals from the number and returns an Int +func (d Dec) TruncateInt() Int { + return NewIntFromBigInt(chopPrecisionAndTruncateNonMutative(d.Int)) +} + +//___________________________________________________________________________________ + // reuse nil values var ( nilAmino string diff --git a/types/decimal_test.go b/types/decimal_test.go index 115eeacb0..a6ec0740e 100644 --- a/types/decimal_test.go +++ b/types/decimal_test.go @@ -4,7 +4,7 @@ import ( "math/big" "testing" - wire "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" "github.com/stretchr/testify/require" ) @@ -202,6 +202,32 @@ func TestBankerRoundChop(t *testing.T) { } } +func TestTruncate(t *testing.T) { + tests := []struct { + d1 Dec + exp int64 + }{ + {mustNewDecFromStr(t, "0"), 0}, + {mustNewDecFromStr(t, "0.25"), 0}, + {mustNewDecFromStr(t, "0.75"), 0}, + {mustNewDecFromStr(t, "1"), 1}, + {mustNewDecFromStr(t, "1.5"), 1}, + {mustNewDecFromStr(t, "7.5"), 7}, + {mustNewDecFromStr(t, "7.6"), 7}, + {mustNewDecFromStr(t, "7.4"), 7}, + {mustNewDecFromStr(t, "100.1"), 100}, + {mustNewDecFromStr(t, "1000.1"), 1000}, + } + + for tcIndex, tc := range tests { + resNeg := tc.d1.Neg().TruncateInt64() + require.Equal(t, -1*tc.exp, resNeg, "negative tc %d", tcIndex) + + resPos := tc.d1.TruncateInt64() + require.Equal(t, tc.exp, resPos, "positive tc %d", tcIndex) + } +} + func TestToLeftPadded(t *testing.T) { tests := []struct { dec Dec @@ -220,7 +246,7 @@ func TestToLeftPadded(t *testing.T) { } } -var cdc = wire.NewCodec() +var cdc = codec.New() func TestZeroDeserializationJSON(t *testing.T) { d := Dec{new(big.Int)} @@ -242,7 +268,7 @@ func TestSerializationText(t *testing.T) { require.True(t, d.Equal(d2), "original: %v, unmarshalled: %v", d, d2) } -func TestSerializationGoWireJSON(t *testing.T) { +func TestSerializationGocodecJSON(t *testing.T) { d := mustNewDecFromStr(t, "0.333") bz, err := cdc.MarshalJSON(d) @@ -254,7 +280,7 @@ func TestSerializationGoWireJSON(t *testing.T) { require.True(t, d.Equal(d2), "original: %v, unmarshalled: %v", d, d2) } -func TestSerializationGoWireBinary(t *testing.T) { +func TestSerializationGocodecBinary(t *testing.T) { d := mustNewDecFromStr(t, "0.333") bz, err := cdc.MarshalBinary(d) @@ -273,7 +299,7 @@ type testDEmbedStruct struct { } // TODO make work for UnmarshalJSON -func TestEmbeddedStructSerializationGoWire(t *testing.T) { +func TestEmbeddedStructSerializationGocodec(t *testing.T) { obj := testDEmbedStruct{"foo", 10, NewDecWithPrec(1, 3)} bz, err := cdc.MarshalBinary(obj) require.Nil(t, err) @@ -299,3 +325,20 @@ func TestStringOverflow(t *testing.T) { dec3.String(), ) } + +func TestDecMulInt(t *testing.T) { + tests := []struct { + sdkDec Dec + sdkInt Int + want Dec + }{ + {NewDec(10), NewInt(2), NewDec(20)}, + {NewDec(1000000), NewInt(100), NewDec(100000000)}, + {NewDecWithPrec(1, 1), NewInt(10), NewDec(1)}, + {NewDecWithPrec(1, 5), NewInt(20), NewDecWithPrec(2, 4)}, + } + for i, tc := range tests { + got := tc.sdkDec.MulInt(tc.sdkInt) + require.Equal(t, tc.want, got, "Incorrect result on test case %d", i) + } +} diff --git a/types/errors.go b/types/errors.go index c72933d60..46bf748f4 100644 --- a/types/errors.go +++ b/types/errors.go @@ -4,9 +4,9 @@ import ( "fmt" "strings" + "github.com/cosmos/cosmos-sdk/codec" cmn "github.com/tendermint/tendermint/libs/common" - "github.com/cosmos/cosmos-sdk/wire" abci "github.com/tendermint/tendermint/abci/types" ) @@ -56,6 +56,7 @@ const ( CodeInvalidCoins CodeType = 11 CodeOutOfGas CodeType = 12 CodeMemoTooLarge CodeType = 13 + CodeInsufficientFee CodeType = 14 // CodespaceRoot is a codespace for error codes in this file only. // Notice that 0 is an "unset" codespace, which can be overridden with @@ -72,7 +73,6 @@ func unknownCodeMsg(code CodeType) string { } // NOTE: Don't stringer this, we'll put better messages in later. -// nolint: gocyclo func CodeToDefaultMsg(code CodeType) string { switch code { case CodeInternal: @@ -101,6 +101,8 @@ func CodeToDefaultMsg(code CodeType) string { return "out of gas" case CodeMemoTooLarge: return "memo too large" + case CodeInsufficientFee: + return "insufficient fee" default: return unknownCodeMsg(code) } @@ -150,6 +152,9 @@ func ErrOutOfGas(msg string) Error { func ErrMemoTooLarge(msg string) Error { return newErrorWithRootCodespace(CodeMemoTooLarge, msg) } +func ErrInsufficientFee(msg string) Error { + return newErrorWithRootCodespace(CodeInsufficientFee, msg) +} //---------------------------------------- // Error & sdkError @@ -252,7 +257,7 @@ func (err *sdkError) Code() CodeType { // Implements ABCIError. func (err *sdkError) ABCILog() string { - cdc := wire.NewCodec() + cdc := codec.New() parsedErrMsg := parseCmnError(err.cmnError.Error()) jsonErr := humanReadableError{ Codespace: err.codespace, diff --git a/types/gas.go b/types/gas.go index e8486c813..cce4975c3 100644 --- a/types/gas.go +++ b/types/gas.go @@ -1,9 +1,26 @@ package types +// Gas consumption descriptors. +const ( + GasIterNextCostFlatDesc = "IterNextFlat" + GasValuePerByteDesc = "ValuePerByte" + GasWritePerByteDesc = "WritePerByte" + GasReadPerByteDesc = "ReadPerByte" + GasWriteCostFlatDesc = "WriteFlat" + GasReadCostFlatDesc = "ReadFlat" + GasHasDesc = "Has" + GasDeleteDesc = "Delete" +) + +var ( + cachedDefaultGasConfig = DefaultGasConfig() + cachedTransientGasConfig = TransientGasConfig() +) + // Gas measured by the SDK type Gas = int64 -// Error thrown when out of gas +// ErrorOutOfGas defines an error thrown when an action results in out of gas. type ErrorOutOfGas struct { Descriptor string } @@ -19,6 +36,7 @@ type basicGasMeter struct { consumed Gas } +// NewGasMeter returns a reference to a new basicGasMeter. func NewGasMeter(limit Gas) GasMeter { return &basicGasMeter{ limit: limit, @@ -41,6 +59,7 @@ type infiniteGasMeter struct { consumed Gas } +// NewInfiniteGasMeter returns a reference to a new infiniteGasMeter. func NewInfiniteGasMeter() GasMeter { return &infiniteGasMeter{ consumed: 0, @@ -58,35 +77,30 @@ func (g *infiniteGasMeter) ConsumeGas(amount Gas, descriptor string) { // GasConfig defines gas cost for each operation on KVStores type GasConfig struct { HasCost Gas + DeleteCost Gas ReadCostFlat Gas ReadCostPerByte Gas WriteCostFlat Gas WriteCostPerByte Gas - KeyCostFlat Gas - ValueCostFlat Gas ValueCostPerByte Gas + IterNextCostFlat Gas } -var ( - cachedDefaultGasConfig = DefaultGasConfig() - cachedTransientGasConfig = TransientGasConfig() -) - -// Default gas config for KVStores +// DefaultGasConfig returns a default gas config for KVStores. func DefaultGasConfig() GasConfig { return GasConfig{ HasCost: 10, + DeleteCost: 10, ReadCostFlat: 10, ReadCostPerByte: 1, WriteCostFlat: 10, WriteCostPerByte: 10, - KeyCostFlat: 5, - ValueCostFlat: 10, ValueCostPerByte: 1, + IterNextCostFlat: 15, } } -// Default gas config for TransientStores +// TransientGasConfig returns a default gas config for TransientStores. func TransientGasConfig() GasConfig { // TODO: define gasconfig for transient stores return DefaultGasConfig() diff --git a/types/lib/linear.go b/types/lib/linear.go index 5a311a01f..1c25f4eb4 100644 --- a/types/lib/linear.go +++ b/types/lib/linear.go @@ -4,13 +4,13 @@ import ( "fmt" "strconv" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" ) // Linear defines a primitive mapper type type Linear struct { - cdc *wire.Codec + cdc *codec.Codec store sdk.KVStore keys *LinearKeys } @@ -36,7 +36,7 @@ func DefaultLinearKeys() *LinearKeys { } // NewLinear constructs new Linear -func NewLinear(cdc *wire.Codec, store sdk.KVStore, keys *LinearKeys) Linear { +func NewLinear(cdc *codec.Codec, store sdk.KVStore, keys *LinearKeys) Linear { if keys == nil { keys = cachedDefaultLinearKeys } @@ -87,7 +87,7 @@ type List interface { } // NewList constructs new List -func NewList(cdc *wire.Codec, store sdk.KVStore, keys *LinearKeys) List { +func NewList(cdc *codec.Codec, store sdk.KVStore, keys *LinearKeys) List { return NewLinear(cdc, store, keys) } @@ -182,7 +182,7 @@ type Queue interface { } // NewQueue constructs new Queue -func NewQueue(cdc *wire.Codec, store sdk.KVStore, keys *LinearKeys) Queue { +func NewQueue(cdc *codec.Codec, store sdk.KVStore, keys *LinearKeys) Queue { return NewLinear(cdc, store, keys) } diff --git a/types/lib/linear_test.go b/types/lib/linear_test.go index 2b5a6c405..d19c04061 100644 --- a/types/lib/linear_test.go +++ b/types/lib/linear_test.go @@ -11,9 +11,9 @@ import ( 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" - wire "github.com/cosmos/cosmos-sdk/wire" ) type S struct { @@ -21,18 +21,18 @@ type S struct { B bool } -func defaultComponents(key sdk.StoreKey) (sdk.Context, *wire.Codec) { +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 := wire.NewCodec() + cdc := codec.New() return ctx, cdc } func TestNewLinear(t *testing.T) { - cdc := wire.NewCodec() + 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}}) }) diff --git a/types/stake.go b/types/stake.go index 732493a10..c84ed8d05 100644 --- a/types/stake.go +++ b/types/stake.go @@ -1,6 +1,7 @@ package types import ( + abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" ) @@ -35,15 +36,24 @@ func (b BondStatus) Equal(b2 BondStatus) bool { // validator for a delegated proof of stake system type Validator interface { - GetJailed() bool // whether the validator is jailed - GetMoniker() string // moniker of the validator - GetStatus() BondStatus // status of the validator - GetOperator() ValAddress // operator address to receive/return validators coins - GetPubKey() crypto.PubKey // validation pubkey - GetPower() Dec // validation power - GetTokens() Dec // validation tokens - GetDelegatorShares() Dec // Total out standing delegator shares - GetBondHeight() int64 // height in which the validator became active + GetJailed() bool // whether the validator is jailed + GetMoniker() string // moniker of the validator + GetStatus() BondStatus // status of the validator + GetOperator() ValAddress // operator address to receive/return validators coins + GetConsPubKey() crypto.PubKey // validation consensus pubkey + GetConsAddr() ConsAddress // validation consensus address + GetPower() Dec // validation power + GetTokens() Dec // validation tokens + GetDelegatorShares() Dec // Total out standing delegator shares + GetBondHeight() int64 // height in which the validator became active +} + +// validator which fulfills abci validator interface for use in Tendermint +func ABCIValidator(v Validator) abci.Validator { + return abci.Validator{ + Address: v.GetConsPubKey().Address(), + Power: v.GetPower().RoundInt64(), + } } // properties for the set of all validators @@ -56,14 +66,14 @@ type ValidatorSet interface { IterateValidatorsBonded(Context, func(index int64, validator Validator) (stop bool)) - Validator(Context, ValAddress) Validator // get a particular validator by operator - ValidatorByPubKey(Context, crypto.PubKey) Validator // get a particular validator by signing PubKey + Validator(Context, ValAddress) Validator // get a particular validator by operator address + ValidatorByConsAddr(Context, ConsAddress) Validator // get a particular validator by consensus address TotalPower(Context) Dec // total power of the validator set // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction - Slash(Context, crypto.PubKey, int64, int64, Dec) - Jail(Context, crypto.PubKey) // jail a validator - Unjail(Context, crypto.PubKey) // unjail a validator + Slash(Context, ConsAddress, int64, int64, Dec) + Jail(Context, ConsAddress) // jail a validator + Unjail(Context, ConsAddress) // unjail a validator // Delegation allows for getting a particular delegation for a given validator // and delegator outside the scope of the staking module. @@ -76,7 +86,7 @@ type ValidatorSet interface { type Delegation interface { GetDelegator() AccAddress // delegator AccAddress for the bond GetValidator() ValAddress // validator operator address - GetBondShares() Dec // amount of validator's shares + GetShares() Dec // amount of validator's shares held in this delegation } // properties for the set of all delegations for a particular @@ -89,12 +99,25 @@ type DelegationSet interface { fn func(index int64, delegation Delegation) (stop bool)) } -// validator event hooks -// These can be utilized to communicate between a staking keeper -// and another keeper which must take particular actions when -// validators are bonded and unbonded. The second keeper must implement -// this interface, which then the staking keeper can call. -type ValidatorHooks interface { +//_______________________________________________________________________________ +// Event Hooks +// These can be utilized to communicate between a staking keeper and another +// keeper which must take particular actions when validators/delegators change +// state. The second keeper must implement this interface, which then the +// staking keeper can call. + +// TODO refactor event hooks out to the receiver modules + +// 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 + 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 + + OnDelegationCreated(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation is created + OnDelegationSharesModified(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation's shares are modified + OnDelegationRemoved(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation is removed } diff --git a/types/tx_msg.go b/types/tx_msg.go index b0f5c78f4..9b4aab937 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -11,6 +11,10 @@ type Msg interface { // Must be alphanumeric or empty. Type() string + // Returns a human-readable string for the message, intended for utilization + // within tags + Name() string + // ValidateBasic does a simple validation check that // doesn't require access to any other information. ValidateBasic() Error @@ -55,6 +59,7 @@ func NewTestMsg(addrs ...AccAddress) *TestMsg { //nolint func (msg *TestMsg) Type() string { return "TestMsg" } +func (msg *TestMsg) Name() 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 2e027676a..95fc779d7 100644 --- a/types/utils.go +++ b/types/utils.go @@ -1,6 +1,10 @@ package types -import "encoding/json" +import ( + "encoding/json" + tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" + tmtypes "github.com/tendermint/tendermint/types" +) // SortedJSON takes any JSON and returns it sorted by keys. Also, all white-spaces // are removed. @@ -29,3 +33,22 @@ func MustSortJSON(toSortJSON []byte) []byte { } return js } + +// DefaultChainID returns the chain ID from the genesis file if present. An +// error is returned if the file cannot be read or parsed. +// +// TODO: This should be removed and the chainID should always be provided by +// the end user. +func DefaultChainID() (string, error) { + cfg, err := tcmd.ParseConfig() + if err != nil { + return "", err + } + + doc, err := tmtypes.GenesisDocFromFile(cfg.GenesisFile()) + if err != nil { + return "", err + } + + return doc.ChainID, nil +} \ No newline at end of file diff --git a/version/command.go b/version/command.go index 2cff1bbe9..8f47dba8f 100644 --- a/version/command.go +++ b/version/command.go @@ -17,15 +17,10 @@ var ( // return version of CLI/node and commit hash func GetVersion() string { - v := Version - if GitCommit != "" { - v = v + "-" + GitCommit - } - return v + return Version } // CMD func printVersion(cmd *cobra.Command, args []string) { - v := GetVersion() - fmt.Println(v) + fmt.Println(GetVersion()) } diff --git a/version/version.go b/version/version.go index 8bfea9577..407797eeb 100644 --- a/version/version.go +++ b/version/version.go @@ -1,11 +1,5 @@ //nolint package version -const Maj = "0" -const Min = "24" -const Fix = "2" - -const Version = "0.24.2" - // GitCommit set by build flags -var GitCommit = "" +var Version = "" diff --git a/x/auth/account.go b/x/auth/account.go index 3340c8bd5..ceadd3951 100644 --- a/x/auth/account.go +++ b/x/auth/account.go @@ -3,8 +3,8 @@ package auth import ( "errors" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/tendermint/tendermint/crypto" ) @@ -119,8 +119,8 @@ func (acc *BaseAccount) SetSequence(seq int64) error { // Wire // Most users shouldn't use this, but this comes handy for tests. -func RegisterBaseAccount(cdc *wire.Codec) { +func RegisterBaseAccount(cdc *codec.Codec) { cdc.RegisterInterface((*Account)(nil), nil) cdc.RegisterConcrete(&BaseAccount{}, "cosmos-sdk/BaseAccount", nil) - wire.RegisterCrypto(cdc) + codec.RegisterCrypto(cdc) } diff --git a/x/auth/account_test.go b/x/auth/account_test.go index 17878ce6f..b7a78e2d2 100644 --- a/x/auth/account_test.go +++ b/x/auth/account_test.go @@ -8,8 +8,8 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" + codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" ) func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) { @@ -90,20 +90,19 @@ func TestBaseAccountMarshal(t *testing.T) { require.Nil(t, err) // need a codec for marshaling - codec := wire.NewCodec() - wire.RegisterCrypto(codec) + cdc := codec.New() + codec.RegisterCrypto(cdc) - b, err := codec.MarshalBinary(acc) + b, err := cdc.MarshalBinary(acc) require.Nil(t, err) acc2 := BaseAccount{} - err = codec.UnmarshalBinary(b, &acc2) + err = cdc.UnmarshalBinary(b, &acc2) require.Nil(t, err) require.Equal(t, acc, acc2) // error on bad bytes acc2 = BaseAccount{} - err = codec.UnmarshalBinary(b[:len(b)/2], &acc2) + err = cdc.UnmarshalBinary(b[:len(b)/2], &acc2) require.NotNil(t, err) - } diff --git a/x/auth/ante.go b/x/auth/ante.go index 5b2cdb155..f423c9465 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -12,17 +12,17 @@ import ( ) const ( - deductFeesCost sdk.Gas = 10 - memoCostPerByte sdk.Gas = 1 - ed25519VerifyCost = 59 - secp256k1VerifyCost = 100 - maxMemoCharacters = 100 + deductFeesCost sdk.Gas = 10 + memoCostPerByte sdk.Gas = 1 + ed25519VerifyCost = 59 + secp256k1VerifyCost = 100 + maxMemoCharacters = 100 + feeDeductionGasFactor = 0.001 ) // NewAnteHandler returns an AnteHandler that checks // and increments sequence numbers, checks signatures & account numbers, // and deducts fees from the first signer. -// nolint: gocyclo func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { return func( @@ -94,8 +94,15 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { return newCtx, res, true } + requiredFees := adjustFeesByGas(ctx.MinimumFees(), fee.Gas) + // fees must be greater than the minimum set by the validator adjusted by gas + if ctx.IsCheckTx() && !simulate && !ctx.MinimumFees().IsZero() && fee.Amount.IsLT(requiredFees) { + // validators reject any tx from the mempool with less than the minimum fee per gas * gas factor + return newCtx, sdk.ErrInsufficientFee(fmt.Sprintf( + "insufficient fee, got: %q required: %q", fee.Amount, requiredFees)).Result(), true + } + // first sig pays the fees - // TODO: Add min fees // Can this function be moved outside of the loop? if i == 0 && !fee.Amount.IsZero() { newCtx.GasMeter().ConsumeGas(deductFeesCost, "deductFees") @@ -236,6 +243,15 @@ func consumeSignatureVerificationGas(meter sdk.GasMeter, pubkey crypto.PubKey) { } } +func adjustFeesByGas(fees sdk.Coins, gas int64) sdk.Coins { + gasCost := int64(float64(gas) * feeDeductionGasFactor) + gasFees := make(sdk.Coins, len(fees)) + for i := 0; i < len(fees); i++ { + gasFees[i] = sdk.NewInt64Coin(fees[i].Denom, gasCost) + } + return fees.Plus(gasFees) +} + // Deduct the fee from the account. // We could use the CoinKeeper (in addition to the AccountMapper, // because the CoinKeeper doesn't give us accounts), but it seems easier to do this. diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index e13784512..2a289f317 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" + codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" @@ -110,7 +110,7 @@ func newTestTxWithSignBytes(msgs []sdk.Msg, privs []crypto.PrivKey, accNums []in func TestAnteHandlerSigErrors(t *testing.T) { // setup ms, capKey, capKey2 := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() RegisterBaseAccount(cdc) mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) @@ -163,7 +163,7 @@ func TestAnteHandlerSigErrors(t *testing.T) { func TestAnteHandlerAccountNumbers(t *testing.T) { // setup ms, capKey, capKey2 := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() RegisterBaseAccount(cdc) mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) @@ -222,7 +222,7 @@ func TestAnteHandlerAccountNumbers(t *testing.T) { func TestAnteHandlerSequences(t *testing.T) { // setup ms, capKey, capKey2 := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() RegisterBaseAccount(cdc) mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) @@ -300,7 +300,7 @@ func TestAnteHandlerSequences(t *testing.T) { func TestAnteHandlerFees(t *testing.T) { // setup ms, capKey, capKey2 := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() RegisterBaseAccount(cdc) mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) @@ -342,7 +342,7 @@ func TestAnteHandlerFees(t *testing.T) { func TestAnteHandlerMemoGas(t *testing.T) { // setup ms, capKey, capKey2 := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() RegisterBaseAccount(cdc) mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) @@ -385,7 +385,7 @@ func TestAnteHandlerMemoGas(t *testing.T) { func TestAnteHandlerMultiSigner(t *testing.T) { // setup ms, capKey, capKey2 := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() RegisterBaseAccount(cdc) mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) @@ -436,7 +436,7 @@ func TestAnteHandlerMultiSigner(t *testing.T) { func TestAnteHandlerBadSignBytes(t *testing.T) { // setup ms, capKey, capKey2 := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() RegisterBaseAccount(cdc) mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) @@ -517,7 +517,7 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { func TestAnteHandlerSetPubKey(t *testing.T) { // setup ms, capKey, capKey2 := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() RegisterBaseAccount(cdc) mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) @@ -570,7 +570,7 @@ func TestAnteHandlerSetPubKey(t *testing.T) { func TestProcessPubKey(t *testing.T) { ms, capKey, _ := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() RegisterBaseAccount(cdc) mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) @@ -627,3 +627,24 @@ func TestConsumeSignatureVerificationGas(t *testing.T) { }) } } + +func TestAdjustFeesByGas(t *testing.T) { + type args struct { + fee sdk.Coins + gas int64 + } + tests := []struct { + name string + args args + want sdk.Coins + }{ + {"nil coins", args{sdk.Coins{}, 10000}, sdk.Coins{}}, + {"nil coins", args{sdk.Coins{sdk.NewInt64Coin("A", 10), sdk.NewInt64Coin("B", 0)}, 10000}, sdk.Coins{sdk.NewInt64Coin("A", 20), sdk.NewInt64Coin("B", 10)}}, + {"negative coins", args{sdk.Coins{sdk.NewInt64Coin("A", -10), sdk.NewInt64Coin("B", 10)}, 10000}, sdk.Coins{sdk.NewInt64Coin("B", 20)}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.True(t, tt.want.IsEqual(adjustFeesByGas(tt.args.fee, tt.args.gas))) + }) + } +} diff --git a/x/auth/client/cli/account.go b/x/auth/client/cli/account.go index 6f6359288..13c3230cf 100644 --- a/x/auth/client/cli/account.go +++ b/x/auth/client/cli/account.go @@ -6,18 +6,18 @@ import ( "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" ) // GetAccountCmdDefault invokes the GetAccountCmd for the auth.BaseAccount type. -func GetAccountCmdDefault(storeName string, cdc *wire.Codec) *cobra.Command { +func GetAccountCmdDefault(storeName string, cdc *codec.Codec) *cobra.Command { return GetAccountCmd(storeName, cdc, GetAccountDecoder(cdc)) } // GetAccountDecoder gets the account decoder for auth.DefaultAccount. -func GetAccountDecoder(cdc *wire.Codec) auth.AccountDecoder { +func GetAccountDecoder(cdc *codec.Codec) auth.AccountDecoder { return func(accBytes []byte) (acct auth.Account, err error) { err = cdc.UnmarshalBinaryBare(accBytes, &acct) if err != nil { @@ -31,7 +31,7 @@ func GetAccountDecoder(cdc *wire.Codec) auth.AccountDecoder { // GetAccountCmd returns a query account that will display the state of the // account at a given address. // nolint: unparam -func GetAccountCmd(storeName string, cdc *wire.Codec, decoder auth.AccountDecoder) *cobra.Command { +func GetAccountCmd(storeName string, cdc *codec.Codec, decoder auth.AccountDecoder) *cobra.Command { return &cobra.Command{ Use: "account [address]", Short: "Query account balance", @@ -58,7 +58,7 @@ func GetAccountCmd(storeName string, cdc *wire.Codec, decoder auth.AccountDecode return err } - output, err := wire.MarshalJSONIndent(cdc, acc) + output, err := codec.MarshalJSONIndent(cdc, acc) if err != nil { return err } diff --git a/x/auth/client/cli/sign.go b/x/auth/client/cli/sign.go index d8ffae813..31648e3de 100644 --- a/x/auth/client/cli/sign.go +++ b/x/auth/client/cli/sign.go @@ -25,7 +25,7 @@ const ( func GetSignCommand(codec *amino.Codec, decoder auth.AccountDecoder) *cobra.Command { cmd := &cobra.Command{ Use: "sign <file>", - Short: "Sign transactions", + 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.`, RunE: makeSignCmd(codec, decoder), diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index 9e5fc88b5..db11cedb9 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -6,8 +6,8 @@ import ( "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/wire" "github.com/cosmos/cosmos-sdk/x/auth" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" @@ -15,7 +15,7 @@ import ( ) // register REST routes -func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, storeName string) { +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, storeName string) { r.HandleFunc( "/accounts/{address}", QueryAccountRequestHandlerFn(storeName, cdc, authcmd.GetAccountDecoder(cdc), cliCtx), @@ -28,7 +28,7 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, s // query accountREST Handler func QueryAccountRequestHandlerFn( - storeName string, cdc *wire.Codec, + storeName string, cdc *codec.Codec, decoder auth.AccountDecoder, cliCtx context.CLIContext, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { diff --git a/x/auth/client/rest/sign.go b/x/auth/client/rest/sign.go index 5acde51c5..6894d1c5d 100644 --- a/x/auth/client/rest/sign.go +++ b/x/auth/client/rest/sign.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/wire" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/auth" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" ) @@ -22,8 +22,9 @@ type SignBody struct { AppendSig bool `json:"append_sig"` } +// nolint: unparam // sign tx REST handler -func SignTxRequestHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc { +func SignTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var m SignBody @@ -51,7 +52,7 @@ func SignTxRequestHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.Han return } - output, err := wire.MarshalJSONIndent(cdc, signedTx) + output, err := codec.MarshalJSONIndent(cdc, signedTx) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return diff --git a/x/auth/client/txbuilder/stdsignmsg.go b/x/auth/client/txbuilder/stdsignmsg.go new file mode 100644 index 000000000..050a370ff --- /dev/null +++ b/x/auth/client/txbuilder/stdsignmsg.go @@ -0,0 +1,23 @@ +package context + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +// StdSignMsg is a convenience structure for passing along +// a Msg with the other requirements for a StdSignDoc before +// it is signed. For use in the CLI. +type StdSignMsg struct { + ChainID string `json:"chain_id"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` + Fee auth.StdFee `json:"fee"` + Msgs []sdk.Msg `json:"msgs"` + Memo string `json:"memo"` +} + +// get message bytes +func (msg StdSignMsg) Bytes() []byte { + return auth.StdSignBytes(msg.ChainID, msg.AccountNumber, msg.Sequence, msg.Fee, msg.Msgs, msg.Memo) +} diff --git a/x/auth/client/txbuilder/txbuilder.go b/x/auth/client/txbuilder/txbuilder.go index 026eabece..b488ef359 100644 --- a/x/auth/client/txbuilder/txbuilder.go +++ b/x/auth/client/txbuilder/txbuilder.go @@ -3,8 +3,8 @@ package context import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/pkg/errors" @@ -13,10 +13,12 @@ import ( // TxBuilder implements a transaction context created in SDK modules. type TxBuilder struct { - Codec *wire.Codec + Codec *codec.Codec AccountNumber int64 Sequence int64 - Gas int64 + Gas int64 // TODO: should this turn into uint64? requires further discussion - see #2173 + GasAdjustment float64 + SimulateGas bool ChainID string Memo string Fee string @@ -28,7 +30,7 @@ func NewTxBuilderFromCLI() TxBuilder { // if chain ID is not specified manually, read default chain ID chainID := viper.GetString(client.FlagChainID) if chainID == "" { - defaultChainID, err := defaultChainID() + defaultChainID, err := sdk.DefaultChainID() if err != nil { chainID = defaultChainID } @@ -36,16 +38,18 @@ func NewTxBuilderFromCLI() TxBuilder { return TxBuilder{ ChainID: chainID, - Gas: viper.GetInt64(client.FlagGas), AccountNumber: viper.GetInt64(client.FlagAccountNumber), + Gas: client.GasFlagVar.Gas, + GasAdjustment: viper.GetFloat64(client.FlagGasAdjustment), Sequence: viper.GetInt64(client.FlagSequence), + SimulateGas: client.GasFlagVar.Simulate, Fee: viper.GetString(client.FlagFee), Memo: viper.GetString(client.FlagMemo), } } // WithCodec returns a copy of the context with an updated codec. -func (bldr TxBuilder) WithCodec(cdc *wire.Codec) TxBuilder { +func (bldr TxBuilder) WithCodec(cdc *codec.Codec) TxBuilder { bldr.Codec = cdc return bldr } @@ -88,23 +92,23 @@ func (bldr TxBuilder) WithAccountNumber(accnum int64) TxBuilder { // Build builds a single message to be signed from a TxBuilder given a set of // messages. It returns an error if a fee is supplied but cannot be parsed. -func (bldr TxBuilder) Build(msgs []sdk.Msg) (auth.StdSignMsg, error) { +func (bldr TxBuilder) Build(msgs []sdk.Msg) (StdSignMsg, error) { chainID := bldr.ChainID if chainID == "" { - return auth.StdSignMsg{}, errors.Errorf("chain ID required but not specified") + return StdSignMsg{}, errors.Errorf("chain ID required but not specified") } fee := sdk.Coin{} if bldr.Fee != "" { parsedFee, err := sdk.ParseCoin(bldr.Fee) if err != nil { - return auth.StdSignMsg{}, err + return StdSignMsg{}, err } fee = parsedFee } - return auth.StdSignMsg{ + return StdSignMsg{ ChainID: bldr.ChainID, AccountNumber: bldr.AccountNumber, Sequence: bldr.Sequence, @@ -116,7 +120,7 @@ func (bldr TxBuilder) Build(msgs []sdk.Msg) (auth.StdSignMsg, error) { // Sign signs a transaction given a name, passphrase, and a single message to // signed. An error is returned if signing fails. -func (bldr TxBuilder) Sign(name, passphrase string, msg auth.StdSignMsg) ([]byte, error) { +func (bldr TxBuilder) Sign(name, passphrase string, msg StdSignMsg) ([]byte, error) { sig, err := MakeSignature(name, passphrase, msg) if err != nil { return nil, err @@ -168,7 +172,7 @@ func (bldr TxBuilder) BuildWithPubKey(name string, msgs []sdk.Msg) ([]byte, erro // SignStdTx appends a signature to a StdTx and returns a copy of a it. If append // is false, it replaces the signatures already attached with the new signature. func (bldr TxBuilder) SignStdTx(name, passphrase string, stdTx auth.StdTx, appendSig bool) (signedStdTx auth.StdTx, err error) { - stdSignature, err := MakeSignature(name, passphrase, auth.StdSignMsg{ + stdSignature, err := MakeSignature(name, passphrase, StdSignMsg{ ChainID: bldr.ChainID, AccountNumber: bldr.AccountNumber, Sequence: bldr.Sequence, @@ -191,7 +195,7 @@ func (bldr TxBuilder) SignStdTx(name, passphrase string, stdTx auth.StdTx, appen } // MakeSignature builds a StdSignature given key name, passphrase, and a StdSignMsg. -func MakeSignature(name, passphrase string, msg auth.StdSignMsg) (sig auth.StdSignature, err error) { +func MakeSignature(name, passphrase string, msg StdSignMsg) (sig auth.StdSignature, err error) { keybase, err := keys.GetKeyBase() if err != nil { return diff --git a/x/auth/client/txbuilder/txbuilder_test.go b/x/auth/client/txbuilder/txbuilder_test.go new file mode 100644 index 000000000..996d9b8b1 --- /dev/null +++ b/x/auth/client/txbuilder/txbuilder_test.go @@ -0,0 +1,81 @@ +package context + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/tendermint/tendermint/crypto/ed25519" +) + +var ( + priv = ed25519.GenPrivKey() + addr = sdk.AccAddress(priv.PubKey().Address()) +) + +func TestTxBuilderBuild(t *testing.T) { + type fields struct { + Codec *codec.Codec + AccountNumber int64 + Sequence int64 + Gas int64 + GasAdjustment float64 + SimulateGas bool + ChainID string + Memo string + Fee string + } + defaultMsg := []sdk.Msg{sdk.NewTestMsg(addr)} + tests := []struct { + fields fields + msgs []sdk.Msg + want StdSignMsg + wantErr bool + }{ + { + fields{ + Codec: codec.New(), + AccountNumber: 1, + Sequence: 1, + Gas: 100, + GasAdjustment: 1.1, + SimulateGas: false, + ChainID: "test-chain", + Memo: "hello", + Fee: "1steak", + }, + defaultMsg, + StdSignMsg{ + ChainID: "test-chain", + AccountNumber: 1, + Sequence: 1, + Memo: "hello", + Msgs: defaultMsg, + Fee: auth.NewStdFee(100, sdk.NewCoin("steak", sdk.NewInt(1))), + }, + false, + }, + } + for i, tc := range tests { + bldr := TxBuilder{ + Codec: tc.fields.Codec, + AccountNumber: tc.fields.AccountNumber, + Sequence: tc.fields.Sequence, + Gas: tc.fields.Gas, + GasAdjustment: tc.fields.GasAdjustment, + SimulateGas: tc.fields.SimulateGas, + ChainID: tc.fields.ChainID, + Memo: tc.fields.Memo, + Fee: tc.fields.Fee, + } + got, err := bldr.Build(tc.msgs) + require.Equal(t, tc.wantErr, (err != nil), "TxBuilder.Build() error = %v, wantErr %v, tc %d", err, tc.wantErr, i) + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("TxBuilder.Build() = %v, want %v", got, tc.want) + } + } +} diff --git a/x/auth/client/txbuilder/utils.go b/x/auth/client/txbuilder/utils.go deleted file mode 100644 index 22cc8220b..000000000 --- a/x/auth/client/txbuilder/utils.go +++ /dev/null @@ -1,25 +0,0 @@ -package context - -import ( - tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" - tmtypes "github.com/tendermint/tendermint/types" -) - -// defaultChainID returns the chain ID from the genesis file if present. An -// error is returned if the file cannot be read or parsed. -// -// TODO: This should be removed and the chainID should always be provided by -// the end user. -func defaultChainID() (string, error) { - cfg, err := tcmd.ParseConfig() - if err != nil { - return "", err - } - - doc, err := tmtypes.GenesisDocFromFile(cfg.GenesisFile()) - if err != nil { - return "", err - } - - return doc.ChainID, nil -} diff --git a/x/auth/codec.go b/x/auth/codec.go new file mode 100644 index 000000000..624bdf428 --- /dev/null +++ b/x/auth/codec.go @@ -0,0 +1,19 @@ +package auth + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +// Register concrete types on codec codec for default AppAccount +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterInterface((*Account)(nil), nil) + cdc.RegisterConcrete(&BaseAccount{}, "auth/Account", nil) + cdc.RegisterConcrete(StdTx{}, "auth/StdTx", nil) +} + +var msgCdc = codec.New() + +func init() { + RegisterCodec(msgCdc) + codec.RegisterCrypto(msgCdc) +} diff --git a/x/auth/feekeeper.go b/x/auth/feekeeper.go index 3e03a81aa..d2cf7ce62 100644 --- a/x/auth/feekeeper.go +++ b/x/auth/feekeeper.go @@ -1,8 +1,8 @@ package auth import ( + codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" ) var ( @@ -16,12 +16,12 @@ type FeeCollectionKeeper struct { // The (unexposed) key used to access the fee store from the Context. key sdk.StoreKey - // The wire codec for binary encoding/decoding of accounts. - cdc *wire.Codec + // The codec codec for binary encoding/decoding of accounts. + cdc *codec.Codec } // NewFeeKeeper returns a new FeeKeeper -func NewFeeCollectionKeeper(cdc *wire.Codec, key sdk.StoreKey) FeeCollectionKeeper { +func NewFeeCollectionKeeper(cdc *codec.Codec, key sdk.StoreKey) FeeCollectionKeeper { return FeeCollectionKeeper{ key: key, cdc: cdc, diff --git a/x/auth/feekeeper_test.go b/x/auth/feekeeper_test.go index 243293cc9..82bbe9c35 100644 --- a/x/auth/feekeeper_test.go +++ b/x/auth/feekeeper_test.go @@ -8,8 +8,8 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" + codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" ) var ( @@ -20,7 +20,7 @@ var ( func TestFeeCollectionKeeperGetSet(t *testing.T) { ms, _, capKey2 := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() // make context and keeper ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) @@ -39,7 +39,7 @@ func TestFeeCollectionKeeperGetSet(t *testing.T) { func TestFeeCollectionKeeperAdd(t *testing.T) { ms, _, capKey2 := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() // make context and keeper ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) @@ -59,7 +59,7 @@ func TestFeeCollectionKeeperAdd(t *testing.T) { func TestFeeCollectionKeeperClear(t *testing.T) { ms, _, capKey2 := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() // make context and keeper ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) diff --git a/x/auth/mapper.go b/x/auth/mapper.go index c8bf94a21..51565c0c9 100644 --- a/x/auth/mapper.go +++ b/x/auth/mapper.go @@ -1,8 +1,8 @@ package auth import ( + codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" "github.com/tendermint/tendermint/crypto" ) @@ -18,14 +18,14 @@ type AccountMapper struct { // The prototypical Account constructor. proto func() Account - // The wire codec for binary encoding/decoding of accounts. - cdc *wire.Codec + // The codec codec for binary encoding/decoding of accounts. + cdc *codec.Codec } // NewAccountMapper returns a new sdk.AccountMapper that // uses go-amino to (binary) encode and decode concrete sdk.Accounts. // nolint -func NewAccountMapper(cdc *wire.Codec, key sdk.StoreKey, proto func() Account) AccountMapper { +func NewAccountMapper(cdc *codec.Codec, key sdk.StoreKey, proto func() Account) AccountMapper { return AccountMapper{ key: key, proto: proto, diff --git a/x/auth/mapper_test.go b/x/auth/mapper_test.go index 679ee12cd..9580d3133 100644 --- a/x/auth/mapper_test.go +++ b/x/auth/mapper_test.go @@ -9,9 +9,9 @@ import ( 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" - wire "github.com/cosmos/cosmos-sdk/wire" ) func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) { @@ -27,7 +27,7 @@ func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) { func TestAccountMapperGetSet(t *testing.T) { ms, capKey, _ := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() RegisterBaseAccount(cdc) // make context and mapper @@ -60,3 +60,26 @@ func TestAccountMapperGetSet(t *testing.T) { require.NotNil(t, acc) require.Equal(t, newSequence, acc.GetSequence()) } + +func BenchmarkAccountMapperGetAccountFound(b *testing.B) { + ms, capKey, _ := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + + // make context and mapper + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + mapper := NewAccountMapper(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)) + } +} diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index 69627b31a..0c0a133ed 100644 --- a/x/auth/stdtx.go +++ b/x/auth/stdtx.go @@ -3,8 +3,8 @@ package auth import ( "encoding/json" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/tendermint/tendermint/crypto" ) @@ -139,23 +139,6 @@ func StdSignBytes(chainID string, accnum int64, sequence int64, fee StdFee, msgs return sdk.MustSortJSON(bz) } -// StdSignMsg is a convenience structure for passing along -// a Msg with the other requirements for a StdSignDoc before -// it is signed. For use in the CLI. -type StdSignMsg struct { - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Fee StdFee `json:"fee"` - Msgs []sdk.Msg `json:"msgs"` - Memo string `json:"memo"` -} - -// get message bytes -func (msg StdSignMsg) Bytes() []byte { - return StdSignBytes(msg.ChainID, msg.AccountNumber, msg.Sequence, msg.Fee, msg.Msgs, msg.Memo) -} - // Standard Signature type StdSignature struct { crypto.PubKey `json:"pub_key"` // optional @@ -165,7 +148,7 @@ type StdSignature struct { } // logic for standard transaction decoding -func DefaultTxDecoder(cdc *wire.Codec) sdk.TxDecoder { +func DefaultTxDecoder(cdc *codec.Codec) sdk.TxDecoder { return func(txBytes []byte) (sdk.Tx, sdk.Error) { var tx = StdTx{} diff --git a/x/auth/stdtx_test.go b/x/auth/stdtx_test.go index f6e3f8fc2..8d9172a92 100644 --- a/x/auth/stdtx_test.go +++ b/x/auth/stdtx_test.go @@ -4,15 +4,17 @@ import ( "fmt" "testing" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/ed25519" +) - sdk "github.com/cosmos/cosmos-sdk/types" +var ( + priv = ed25519.GenPrivKey() + addr = sdk.AccAddress(priv.PubKey().Address()) ) func TestStdTx(t *testing.T) { - priv := ed25519.GenPrivKey() - addr := sdk.AccAddress(priv.PubKey().Address()) msgs := []sdk.Msg{sdk.NewTestMsg(addr)} fee := newStdFee() sigs := []StdSignature{} @@ -26,17 +28,26 @@ func TestStdTx(t *testing.T) { } func TestStdSignBytes(t *testing.T) { - priv := ed25519.GenPrivKey() - addr := sdk.AccAddress(priv.PubKey().Address()) - msgs := []sdk.Msg{sdk.NewTestMsg(addr)} - fee := newStdFee() - signMsg := StdSignMsg{ - "1234", - 3, - 6, - fee, - msgs, - "memo", + type args struct { + chainID string + accnum int64 + sequence int64 + fee StdFee + msgs []sdk.Msg + memo string + } + defaultFee := newStdFee() + tests := []struct { + args args + want string + }{ + { + args{"1234", 3, 6, defaultFee, []sdk.Msg{sdk.NewTestMsg(addr)}, "memo"}, + fmt.Sprintf("{\"account_number\":\"3\",\"chain_id\":\"1234\",\"fee\":{\"amount\":[{\"amount\":\"150\",\"denom\":\"atom\"}],\"gas\":\"5000\"},\"memo\":\"memo\",\"msgs\":[[\"%s\"]],\"sequence\":\"6\"}", addr), + }, + } + for i, tc := range tests { + got := string(StdSignBytes(tc.args.chainID, tc.args.accnum, tc.args.sequence, tc.args.fee, tc.args.msgs, tc.args.memo)) + require.Equal(t, tc.want, got, "Got unexpected result on test case i: %d", i) } - require.Equal(t, fmt.Sprintf("{\"account_number\":\"3\",\"chain_id\":\"1234\",\"fee\":{\"amount\":[{\"amount\":\"150\",\"denom\":\"atom\"}],\"gas\":\"5000\"},\"memo\":\"memo\",\"msgs\":[[\"%s\"]],\"sequence\":\"6\"}", addr), string(signMsg.Bytes())) } diff --git a/x/auth/wire.go b/x/auth/wire.go deleted file mode 100644 index e22151101..000000000 --- a/x/auth/wire.go +++ /dev/null @@ -1,19 +0,0 @@ -package auth - -import ( - "github.com/cosmos/cosmos-sdk/wire" -) - -// Register concrete types on wire codec for default AppAccount -func RegisterWire(cdc *wire.Codec) { - cdc.RegisterInterface((*Account)(nil), nil) - cdc.RegisterConcrete(&BaseAccount{}, "auth/Account", nil) - cdc.RegisterConcrete(StdTx{}, "auth/StdTx", nil) -} - -var msgCdc = wire.NewCodec() - -func init() { - RegisterWire(msgCdc) - wire.RegisterCrypto(msgCdc) -} diff --git a/x/bank/bench_test.go b/x/bank/bench_test.go index 3d84a892d..a29a25b99 100644 --- a/x/bank/bench_test.go +++ b/x/bank/bench_test.go @@ -15,11 +15,11 @@ import ( func getBenchmarkMockApp() (*mock.App, error) { mapp := mock.NewApp() - RegisterWire(mapp.Cdc) - bankKeeper := NewKeeper(mapp.AccountMapper) + RegisterCodec(mapp.Cdc) + bankKeeper := NewBaseKeeper(mapp.AccountMapper) mapp.Router().AddRoute("bank", NewHandler(bankKeeper)) - err := mapp.CompleteSetup([]*sdk.KVStoreKey{}) + err := mapp.CompleteSetup() return mapp, err } diff --git a/x/bank/client/cli/broadcast.go b/x/bank/client/cli/broadcast.go index cef88246c..bf3d52943 100644 --- a/x/bank/client/cli/broadcast.go +++ b/x/bank/client/cli/broadcast.go @@ -29,9 +29,12 @@ in place of an input filename, the command reads from standard input.`, if err != nil { return } - return cliCtx.EnsureBroadcastTx(txBytes) + + _, err = cliCtx.BroadcastTx(txBytes) + return err }, } + return cmd } diff --git a/x/bank/client/cli/sendtx.go b/x/bank/client/cli/sendtx.go index 6c49fa4f0..befe6bf00 100644 --- a/x/bank/client/cli/sendtx.go +++ b/x/bank/client/cli/sendtx.go @@ -5,8 +5,8 @@ import ( "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/wire" 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/bank/client" @@ -22,7 +22,7 @@ const ( ) // SendTxCmd will create a send tx and sign it with the given key. -func SendTxCmd(cdc *wire.Codec) *cobra.Command { +func SendTxCmd(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "send", Short: "Create and sign a send tx", @@ -67,12 +67,12 @@ func SendTxCmd(cdc *wire.Codec) *cobra.Command { } // build and sign the transaction, then broadcast to Tendermint - msg := client.BuildMsg(from, to, coins) + msg := client.CreateMsg(from, to, coins) if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/bank/client/rest/broadcast.go b/x/bank/client/rest/broadcast.go index 5124b791b..6e34acdaa 100644 --- a/x/bank/client/rest/broadcast.go +++ b/x/bank/client/rest/broadcast.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/wire" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/auth" ) @@ -15,7 +15,7 @@ type broadcastBody struct { } // BroadcastTxRequestHandlerFn returns the broadcast tx REST handler -func BroadcastTxRequestHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc { +func BroadcastTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var m broadcastBody if ok := unmarshalBodyOrReturnBadRequest(cliCtx, w, r, &m); !ok { @@ -33,7 +33,7 @@ func BroadcastTxRequestHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) htt return } - output, err := wire.MarshalJSONIndent(cdc, res) + output, err := codec.MarshalJSONIndent(cdc, res) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 82df24642..e186db773 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -1,16 +1,13 @@ package rest import ( - "io/ioutil" "net/http" - cliclient "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/bank/client" @@ -18,119 +15,52 @@ import ( ) // RegisterRoutes - Central function to define routes that get registered by the main application -func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, kb keys.Keybase) { r.HandleFunc("/accounts/{address}/send", SendRequestHandlerFn(cdc, kb, cliCtx)).Methods("POST") r.HandleFunc("/tx/broadcast", BroadcastTxRequestHandlerFn(cdc, cliCtx)).Methods("POST") } -type sendBody struct { - // fees is not used currently - // Fees sdk.Coin `json="fees"` - Amount sdk.Coins `json:"amount"` - LocalAccountName string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas int64 `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` +type sendReq struct { + BaseReq utils.BaseReq `json:"base_req"` + Amount sdk.Coins `json:"amount"` } -var msgCdc = wire.NewCodec() +var msgCdc = codec.New() func init() { - bank.RegisterWire(msgCdc) + bank.RegisterCodec(msgCdc) } -// SendRequestHandlerFn - http request handler to send coins to a address -// nolint: gocyclo -func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { +// SendRequestHandlerFn - http request handler to send coins to a address. +func SendRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - // collect data vars := mux.Vars(r) - bech32addr := vars["address"] + bech32Addr := vars["address"] - to, err := sdk.AccAddressFromBech32(bech32addr) + to, err := sdk.AccAddressFromBech32(bech32Addr) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - var m sendBody - body, err := ioutil.ReadAll(r.Body) + var req sendReq + err = utils.ReadRESTReq(w, r, cdc, &req) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - err = msgCdc.UnmarshalJSON(body, &m) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - info, err := kb.Get(m.LocalAccountName) + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + info, err := kb.Get(baseReq.Name) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } - // build message - msg := client.BuildMsg(sdk.AccAddress(info.GetPubKey().Address()), to, m.Amount) - if err != nil { // XXX rechecking same error ? - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - txBldr := authtxb.TxBuilder{ - Codec: cdc, - Gas: m.Gas, - ChainID: m.ChainID, - AccountNumber: m.AccountNumber, - Sequence: m.Sequence, - } - - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, cliclient.DefaultGasAdjustment) - if !ok { - return - } - cliCtx = cliCtx.WithGasAdjustment(adjustment) - - if utils.HasDryRunArg(r) || m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, txBldr.Gas) - return - } - txBldr = newCtx - } - - if utils.HasGenerateOnlyArg(r) { - utils.WriteGenerateStdTxResponse(w, txBldr, []sdk.Msg{msg}) - return - } - - txBytes, err := txBldr.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - output, err := wire.MarshalJSONIndent(cdc, res) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(output) + msg := client.CreateMsg(sdk.AccAddress(info.GetPubKey().Address()), to, req.Amount) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/bank/client/util.go b/x/bank/client/util.go index c733517a8..b647f00d3 100644 --- a/x/bank/client/util.go +++ b/x/bank/client/util.go @@ -5,8 +5,8 @@ import ( bank "github.com/cosmos/cosmos-sdk/x/bank" ) -// build the sendTx msg -func BuildMsg(from sdk.AccAddress, to sdk.AccAddress, coins sdk.Coins) sdk.Msg { +// create the sendTx msg +func CreateMsg(from sdk.AccAddress, to sdk.AccAddress, coins sdk.Coins) sdk.Msg { input := bank.NewInput(from, coins) output := bank.NewOutput(to, coins) msg := bank.NewMsgSend([]bank.Input{input}, []bank.Output{output}) diff --git a/x/bank/codec.go b/x/bank/codec.go new file mode 100644 index 000000000..bcc2cbdda --- /dev/null +++ b/x/bank/codec.go @@ -0,0 +1,17 @@ +package bank + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +// Register concrete types on codec codec +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(MsgSend{}, "cosmos-sdk/Send", nil) + cdc.RegisterConcrete(MsgIssue{}, "cosmos-sdk/Issue", nil) +} + +var msgCdc = codec.New() + +func init() { + RegisterCodec(msgCdc) +} diff --git a/x/bank/keeper.go b/x/bank/keeper.go index 1494c8caf..2da4eedc8 100644 --- a/x/bank/keeper.go +++ b/x/bank/keeper.go @@ -15,102 +15,149 @@ const ( costAddCoins sdk.Gas = 10 ) -// Keeper manages transfers between accounts -type Keeper struct { +// 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 } -// NewKeeper returns a new Keeper -func NewKeeper(am auth.AccountMapper) Keeper { - return Keeper{am: am} +// NewBaseKeeper returns a new BaseKeeper +func NewBaseKeeper(am auth.AccountMapper) BaseKeeper { + return BaseKeeper{am: am} } // GetCoins returns the coins at the addr. -func (keeper Keeper) GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { +func (keeper BaseKeeper) GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { return getCoins(ctx, keeper.am, addr) } // SetCoins sets the coins at the addr. -func (keeper Keeper) SetCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) sdk.Error { +func (keeper BaseKeeper) SetCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) sdk.Error { return setCoins(ctx, keeper.am, addr, amt) } // HasCoins returns whether or not an account has at least amt coins. -func (keeper Keeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool { +func (keeper BaseKeeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool { return hasCoins(ctx, keeper.am, addr, amt) } // SubtractCoins subtracts amt from the coins at the addr. -func (keeper Keeper) SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { +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) } // AddCoins adds amt to the coins at the addr. -func (keeper Keeper) AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { +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) } // SendCoins moves coins from one account to another -func (keeper Keeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) { +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 Keeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) { +func (keeper BaseKeeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) { return inputOutputCoins(ctx, keeper.am, inputs, outputs) } //______________________________________________________________________________________________ -// SendKeeper only allows transfers between accounts, without the possibility of creating coins -type SendKeeper struct { +// 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) +} + +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 } -// NewSendKeeper returns a new Keeper -func NewSendKeeper(am auth.AccountMapper) SendKeeper { - return SendKeeper{am: am} +// NewBaseSendKeeper returns a new BaseSendKeeper. +func NewBaseSendKeeper(am auth.AccountMapper) BaseSendKeeper { + return BaseSendKeeper{am: am} } // GetCoins returns the coins at the addr. -func (keeper SendKeeper) GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { +func (keeper BaseSendKeeper) GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { return getCoins(ctx, keeper.am, addr) } // HasCoins returns whether or not an account has at least amt coins. -func (keeper SendKeeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool { +func (keeper BaseSendKeeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool { return hasCoins(ctx, keeper.am, addr, amt) } // SendCoins moves coins from one account to another -func (keeper SendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) { +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) } // InputOutputCoins handles a list of inputs and outputs -func (keeper SendKeeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) { +func (keeper BaseSendKeeper) InputOutputCoins( + ctx sdk.Context, inputs []Input, outputs []Output, +) (sdk.Tags, sdk.Error) { + return inputOutputCoins(ctx, keeper.am, inputs, outputs) } //______________________________________________________________________________________________ -// ViewKeeper only allows reading of balances -type ViewKeeper struct { +// ViewKeeper defines a module interface that facilitates read only access to +// account balances. +type ViewKeeper interface { + GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + 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 } -// NewViewKeeper returns a new Keeper -func NewViewKeeper(am auth.AccountMapper) ViewKeeper { - return ViewKeeper{am: am} +// NewBaseViewKeeper returns a new BaseViewKeeper. +func NewBaseViewKeeper(am auth.AccountMapper) BaseViewKeeper { + return BaseViewKeeper{am: am} } // GetCoins returns the coins at the addr. -func (keeper ViewKeeper) GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { +func (keeper BaseViewKeeper) GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { return getCoins(ctx, keeper.am, addr) } // HasCoins returns whether or not an account has at least amt coins. -func (keeper ViewKeeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool { +func (keeper BaseViewKeeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool { return hasCoins(ctx, keeper.am, addr, amt) } diff --git a/x/bank/keeper_test.go b/x/bank/keeper_test.go index 36c0000e7..c48410c15 100644 --- a/x/bank/keeper_test.go +++ b/x/bank/keeper_test.go @@ -10,9 +10,9 @@ import ( 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" - wire "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" ) @@ -29,12 +29,12 @@ func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) { func TestKeeper(t *testing.T) { ms, authKey := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() auth.RegisterBaseAccount(cdc) ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) accountMapper := auth.NewAccountMapper(cdc, authKey, auth.ProtoBaseAccount) - bankKeeper := NewKeeper(accountMapper) + bankKeeper := NewBaseKeeper(accountMapper) addr := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) @@ -114,13 +114,13 @@ func TestKeeper(t *testing.T) { func TestSendKeeper(t *testing.T) { ms, authKey := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() auth.RegisterBaseAccount(cdc) ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) accountMapper := auth.NewAccountMapper(cdc, authKey, auth.ProtoBaseAccount) - bankKeeper := NewKeeper(accountMapper) - sendKeeper := NewSendKeeper(accountMapper) + bankKeeper := NewBaseKeeper(accountMapper) + sendKeeper := NewBaseSendKeeper(accountMapper) addr := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) @@ -183,13 +183,13 @@ func TestSendKeeper(t *testing.T) { func TestViewKeeper(t *testing.T) { ms, authKey := setupMultiStore() - cdc := wire.NewCodec() + cdc := codec.New() auth.RegisterBaseAccount(cdc) ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) accountMapper := auth.NewAccountMapper(cdc, authKey, auth.ProtoBaseAccount) - bankKeeper := NewKeeper(accountMapper) - viewKeeper := NewViewKeeper(accountMapper) + bankKeeper := NewBaseKeeper(accountMapper) + viewKeeper := NewBaseViewKeeper(accountMapper) addr := sdk.AccAddress([]byte("addr1")) acc := accountMapper.NewAccountWithAddress(ctx, addr) diff --git a/x/bank/msgs.go b/x/bank/msgs.go index 316d66ba1..ed2d9a780 100644 --- a/x/bank/msgs.go +++ b/x/bank/msgs.go @@ -20,7 +20,9 @@ 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" } // Implements Msg. func (msg MsgSend) ValidateBasic() sdk.Error { @@ -101,7 +103,9 @@ 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" } // Implements Msg. func (msg MsgIssue) ValidateBasic() sdk.Error { diff --git a/x/bank/simulation/invariants.go b/x/bank/simulation/invariants.go index 847288e1f..20aa9570b 100644 --- a/x/bank/simulation/invariants.go +++ b/x/bank/simulation/invariants.go @@ -1,10 +1,8 @@ package simulation import ( + "errors" "fmt" - "testing" - - "github.com/stretchr/testify/require" "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" @@ -16,25 +14,25 @@ import ( // NonnegativeBalanceInvariant checks that all accounts in the application have non-negative balances func NonnegativeBalanceInvariant(mapper auth.AccountMapper) simulation.Invariant { - return func(t *testing.T, app *baseapp.BaseApp, log string) { + return func(app *baseapp.BaseApp) error { ctx := app.NewContext(false, abci.Header{}) accts := mock.GetAllAccounts(mapper, ctx) for _, acc := range accts { coins := acc.GetCoins() - require.True(t, coins.IsNotNegative(), - fmt.Sprintf("%s has a negative denomination of %s\n%s", + if !coins.IsNotNegative() { + return fmt.Errorf("%s has a negative denomination of %s", acc.GetAddress().String(), - coins.String(), - log), - ) + coins.String()) + } } + return nil } } // 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 { - return func(t *testing.T, app *baseapp.BaseApp, log string) { + return func(app *baseapp.BaseApp) error { ctx := app.NewContext(false, abci.Header{}) totalCoins := sdk.Coins{} @@ -45,6 +43,9 @@ func TotalCoinsInvariant(mapper auth.AccountMapper, totalSupplyFn func() sdk.Coi } mapper.IterateAccounts(ctx, chkAccount) - require.Equal(t, totalSupplyFn(), totalCoins, log) + if !totalSupplyFn().IsEqual(totalCoins) { + return errors.New("total calculated coins doesn't equal expected coins") + } + return nil } } diff --git a/x/bank/simulation/msgs.go b/x/bank/simulation/msgs.go index c53916a61..0b8ec3026 100644 --- a/x/bank/simulation/msgs.go +++ b/x/bank/simulation/msgs.go @@ -5,9 +5,6 @@ import ( "fmt" "math/big" "math/rand" - "testing" - - "github.com/stretchr/testify/require" "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" @@ -21,19 +18,18 @@ import ( // SimulateSingleInputMsgSend tests and runs a single msg send, with one input and one output, where both // accounts already exist. func SimulateSingleInputMsgSend(mapper auth.AccountMapper) simulation.Operation { - return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOps []simulation.FutureOperation, err sdk.Error) { - fromKey := simulation.RandomKey(r, keys) - fromAddr := sdk.AccAddress(fromKey.PubKey().Address()) - toKey := simulation.RandomKey(r, keys) + 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 := simulation.RandomAcc(r, accs) + toAcc := simulation.RandomAcc(r, accs) // Disallow sending money to yourself for { - if !fromKey.Equals(toKey) { + if !fromAcc.PubKey.Equals(toAcc.PubKey) { break } - toKey = simulation.RandomKey(r, keys) + toAcc = simulation.RandomAcc(r, accs) } - toAddr := sdk.AccAddress(toKey.PubKey().Address()) - initFromCoins := mapper.GetAccount(ctx, fromAddr).GetCoins() + toAddr := toAcc.Address + initFromCoins := mapper.GetAccount(ctx, fromAcc.Address).GetCoins() if len(initFromCoins) == 0 { return "skipping, no coins at all", nil, nil @@ -46,19 +42,21 @@ func SimulateSingleInputMsgSend(mapper auth.AccountMapper) simulation.Operation } action = fmt.Sprintf("%s is sending %s %s to %s", - fromAddr.String(), + fromAcc.Address.String(), amt.String(), initFromCoins[denomIndex].Denom, toAddr.String(), ) - log = fmt.Sprintf("%s\n%s", log, action) coins := sdk.Coins{{initFromCoins[denomIndex].Denom, amt}} var msg = bank.MsgSend{ - Inputs: []bank.Input{bank.NewInput(fromAddr, coins)}, + Inputs: []bank.Input{bank.NewInput(fromAcc.Address, coins)}, Outputs: []bank.Output{bank.NewOutput(toAddr, coins)}, } - sendAndVerifyMsgSend(tb, app, mapper, msg, ctx, log, []crypto.PrivKey{fromKey}) + goErr = sendAndVerifyMsgSend(app, mapper, msg, ctx, []crypto.PrivKey{fromAcc.PrivKey}) + if goErr != nil { + return "", nil, goErr + } event("bank/sendAndVerifyMsgSend/ok") return action, nil, nil @@ -66,7 +64,7 @@ func SimulateSingleInputMsgSend(mapper auth.AccountMapper) simulation.Operation } // Sends and verifies the transition of a msg send. This fails if there are repeated inputs or outputs -func sendAndVerifyMsgSend(tb testing.TB, app *baseapp.BaseApp, mapper auth.AccountMapper, msg bank.MsgSend, ctx sdk.Context, log string, privkeys []crypto.PrivKey) { +func sendAndVerifyMsgSend(app *baseapp.BaseApp, mapper auth.AccountMapper, msg bank.MsgSend, ctx sdk.Context, privkeys []crypto.PrivKey) error { initialInputAddrCoins := make([]sdk.Coins, len(msg.Inputs)) initialOutputAddrCoins := make([]sdk.Coins, len(msg.Outputs)) AccountNumbers := make([]int64, len(msg.Inputs)) @@ -89,25 +87,22 @@ func sendAndVerifyMsgSend(tb testing.TB, app *baseapp.BaseApp, mapper auth.Accou res := app.Deliver(tx) if !res.IsOK() { // TODO: Do this in a more 'canonical' way - fmt.Println(res) - fmt.Println(log) - tb.FailNow() + return fmt.Errorf("Deliver failed %v", res) } for i := 0; i < len(msg.Inputs); i++ { terminalInputCoins := mapper.GetAccount(ctx, msg.Inputs[i].Address).GetCoins() - require.Equal(tb, - initialInputAddrCoins[i].Minus(msg.Inputs[i].Coins), - terminalInputCoins, - fmt.Sprintf("Input #%d had an incorrect amount of coins\n%s", i, log), - ) + if !initialInputAddrCoins[i].Minus(msg.Inputs[i].Coins).IsEqual(terminalInputCoins) { + return fmt.Errorf("input #%d had an incorrect amount of coins", i) + } } for i := 0; i < len(msg.Outputs); i++ { terminalOutputCoins := mapper.GetAccount(ctx, msg.Outputs[i].Address).GetCoins() if !terminalOutputCoins.IsEqual(initialOutputAddrCoins[i].Plus(msg.Outputs[i].Coins)) { - tb.Fatalf("Output #%d had an incorrect amount of coins\n%s", i, log) + return fmt.Errorf("output #%d had an incorrect amount of coins", i) } } + return nil } func randPositiveInt(r *rand.Rand, max sdk.Int) (sdk.Int, error) { diff --git a/x/bank/simulation/sim_test.go b/x/bank/simulation/sim_test.go index 38461bad4..819289845 100644 --- a/x/bank/simulation/sim_test.go +++ b/x/bank/simulation/sim_test.go @@ -5,8 +5,6 @@ import ( "math/rand" "testing" - "github.com/tendermint/tendermint/crypto" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/mock" @@ -16,25 +14,25 @@ import ( func TestBankWithRandomMessages(t *testing.T) { mapp := mock.NewApp() - bank.RegisterWire(mapp.Cdc) + bank.RegisterCodec(mapp.Cdc) mapper := mapp.AccountMapper - bankKeeper := bank.NewKeeper(mapper) + bankKeeper := bank.NewBaseKeeper(mapper) mapp.Router().AddRoute("bank", bank.NewHandler(bankKeeper)) - err := mapp.CompleteSetup([]*sdk.KVStoreKey{}) + err := mapp.CompleteSetup() if err != nil { panic(err) } - appStateFn := func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage { - mock.RandomSetGenesis(r, mapp, accs, []string{"stake"}) + appStateFn := func(r *rand.Rand, accs []simulation.Account) json.RawMessage { + simulation.RandomSetGenesis(r, mapp, accs, []string{"stake"}) return json.RawMessage("{}") } simulation.Simulate( t, mapp.BaseApp, appStateFn, - []simulation.Operation{ - SimulateSingleInputMsgSend(mapper), + []simulation.WeightedOperation{ + {1, SimulateSingleInputMsgSend(mapper)}, }, []simulation.RandSetup{}, []simulation.Invariant{ diff --git a/x/bank/wire.go b/x/bank/wire.go deleted file mode 100644 index f468d3e53..000000000 --- a/x/bank/wire.go +++ /dev/null @@ -1,17 +0,0 @@ -package bank - -import ( - "github.com/cosmos/cosmos-sdk/wire" -) - -// Register concrete types on wire codec -func RegisterWire(cdc *wire.Codec) { - cdc.RegisterConcrete(MsgSend{}, "cosmos-sdk/Send", nil) - cdc.RegisterConcrete(MsgIssue{}, "cosmos-sdk/Issue", nil) -} - -var msgCdc = wire.NewCodec() - -func init() { - RegisterWire(msgCdc) -} diff --git a/x/distribution/keeper.go b/x/distribution/keeper.go deleted file mode 100644 index 858632f2a..000000000 --- a/x/distribution/keeper.go +++ /dev/null @@ -1,91 +0,0 @@ -package stake - -//// keeper of the staking store -//type Keeper struct { -//storeKey sdk.StoreKey -//cdc *wire.Codec -//bankKeeper bank.Keeper - -//// codespace -//codespace sdk.CodespaceType -//} - -//func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { -//keeper := Keeper{ -//storeKey: key, -//cdc: cdc, -//bankKeeper: ck, -//codespace: codespace, -//} -//return keeper -//} - -////_________________________________________________________________________ - -//// cummulative power of the non-absent prevotes -//func (k Keeper) GetTotalPrecommitVotingPower(ctx sdk.Context) sdk.Dec { -//store := ctx.KVStore(k.storeKey) - -//// get absent prevote indexes -//absents := ctx.AbsentValidators() - -//TotalPower := sdk.ZeroDec() -//i := int32(0) -//iterator := store.SubspaceIterator(ValidatorsBondedKey) -//for ; iterator.Valid(); iterator.Next() { - -//skip := false -//for j, absentIndex := range absents { -//if absentIndex > i { -//break -//} - -//// if non-voting validator found, skip adding its power -//if absentIndex == i { -//absents = append(absents[:j], absents[j+1:]...) // won't need again -//skip = true -//break -//} -//} -//if skip { -//continue -//} - -//bz := iterator.Value() -//var validator Validator -//k.cdc.MustUnmarshalBinary(bz, &validator) -//TotalPower = TotalPower.Add(validator.Power) -//i++ -//} -//iterator.Close() -//return TotalPower -//} - -////_______________________________________________________________________ - -//// XXX TODO trim functionality - -//// retrieve all the power changes which occur after a height -//func (k Keeper) GetPowerChangesAfterHeight(ctx sdk.Context, earliestHeight int64) (pcs []PowerChange) { -//store := ctx.KVStore(k.storeKey) - -//iterator := store.SubspaceIterator(PowerChangeKey) //smallest to largest -//for ; iterator.Valid(); iterator.Next() { -//pcBytes := iterator.Value() -//var pc PowerChange -//k.cdc.MustUnmarshalBinary(pcBytes, &pc) -//if pc.Height < earliestHeight { -//break -//} -//pcs = append(pcs, pc) -//} -//iterator.Close() -//return -//} - -//// set a power change -//func (k Keeper) setPowerChange(ctx sdk.Context, pc PowerChange) { -//store := ctx.KVStore(k.storeKey) -//b := k.cdc.MustMarshalBinary(pc) -//store.Set(GetPowerChangeKey(pc.Height), b) -//} diff --git a/x/distribution/keeper_test.go b/x/distribution/keeper_test.go deleted file mode 100644 index 890268060..000000000 --- a/x/distribution/keeper_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package stake - -//// test if is a gotValidator from the last update -//func TestGetTotalPrecommitVotingPower(t *testing.T) { -//ctx, _, keeper := createTestInput(t, false, 0) - -//amts := []int64{10000, 1000, 100, 10, 1} -//var candidatesIn [5]Candidate -//for i, amt := range amts { -//candidatesIn[i] = NewCandidate(addrVals[i], pks[i], Description{}) -//candidatesIn[i].BondedShares = sdk.NewDec(amt) -//candidatesIn[i].DelegatorShares = sdk.NewDec(amt) -//keeper.setCandidate(ctx, candidatesIn[i]) -//} - -//// test that an empty gotValidator set doesn't have any gotValidators -//gotValidators := keeper.GetValidators(ctx) -//require.Equal(t, 5, len(gotValidators)) - -//totPow := keeper.GetTotalPrecommitVotingPower(ctx) -//exp := sdk.NewDec(11111) -//require.True(t, exp.Equal(totPow), "exp %v, got %v", exp, totPow) - -//// set absent gotValidators to be the 1st and 3rd record sorted by pubKey address -//ctx = ctx.WithAbsentValidators([]int32{1, 3}) -//totPow = keeper.GetTotalPrecommitVotingPower(ctx) - -//// XXX verify that this order should infact exclude these two records -//exp = sdk.NewDec(11100) -//require.True(t, exp.Equal(totPow), "exp %v, got %v", exp, totPow) -//} diff --git a/x/distribution/movement.go b/x/distribution/movement.go deleted file mode 100644 index 399a25a68..000000000 --- a/x/distribution/movement.go +++ /dev/null @@ -1,72 +0,0 @@ -package stake - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// burn burn burn -func BurnFeeHandler(ctx sdk.Context, _ sdk.Tx, collectedFees sdk.Coins) {} - -//// Handle fee distribution to the validators and delegators -//func (k Keeper) FeeHandler(ctx sdk.Context, collectedFees sdk.Coins) { -//pool := k.GetPool(ctx) -//params := k.GetParams(ctx) - -//// XXX determine -//candidate := NewCandidate(addrs[0], pks[0], Description{}) - -//// calculate the proposer reward -//precommitPower := k.GetTotalPrecommitVotingPower(ctx) -//toProposer := coinsMulRat(collectedFees, (sdk.NewDec(1, 100).Add(sdk.NewDec(4, 100).Mul(precommitPower).Quo(pool.BondedShares)))) -//candidate.ProposerRewardPool = candidate.ProposerRewardPool.Plus(toProposer) - -//toReservePool := coinsMulRat(collectedFees, params.ReservePoolFee) -//pool.FeeReservePool = pool.FeeReservePool.Plus(toReservePool) - -//distributedReward := (collectedFees.Minus(toProposer)).Minus(toReservePool) -//pool.FeePool = pool.FeePool.Plus(distributedReward) -//pool.FeeSumReceived = pool.FeeSumReceived.Plus(distributedReward) -//pool.FeeRecent = distributedReward - -//// lastly update the FeeRecent term -//pool.FeeRecent = collectedFees - -//k.setPool(ctx, pool) -//} - -//func coinsMulRat(coins sdk.Coins, rat sdk.Dec) sdk.Coins { -//var res sdk.Coins -//for _, coin := range coins { -//coinMulAmt := rat.Mul(sdk.NewDec(coin.Amount)).Evaluate() -//coinMul := sdk.Coins{{coin.Denom, coinMulAmt}} -//res = res.Plus(coinMul) -//} -//return res -//} - -////____________________________________________________________________________- - -//// calculate adjustment changes for a candidate at a height -//func CalculateAdjustmentChange(candidate Candidate, pool Pool, denoms []string, height int64) (Candidate, Pool) { - -//heightRat := sdk.NewDec(height) -//lastHeightRat := sdk.NewDec(height - 1) -//candidateFeeCount := candidate.BondedShares.Mul(heightRat) -//poolFeeCount := pool.BondedShares.Mul(heightRat) - -//for i, denom := range denoms { -//poolFeeSumReceived := sdk.NewDec(pool.FeeSumReceived.AmountOf(denom)) -//poolFeeRecent := sdk.NewDec(pool.FeeRecent.AmountOf(denom)) -//// calculate simple and projected pools -//simplePool := candidateFeeCount.Quo(poolFeeCount).Mul(poolFeeSumReceived) -//calc1 := candidate.PrevBondedShares.Mul(lastHeightRat).Quo(pool.PrevBondedShares.Mul(lastHeightRat)).Mul(poolFeeRecent) -//calc2 := candidate.BondedShares.Quo(pool.BondedShares).Mul(poolFeeRecent) -//projectedPool := calc1.Add(calc2) - -//AdjustmentChange := simplePool.Sub(projectedPool) -//candidate.FeeAdjustments[i] = candidate.FeeAdjustments[i].Add(AdjustmentChange) -//pool.FeeAdjustments[i] = pool.FeeAdjustments[i].Add(AdjustmentChange) -//} - -//return candidate, pool -//} diff --git a/x/distribution/types.go b/x/distribution/types.go deleted file mode 100644 index 223410471..000000000 --- a/x/distribution/types.go +++ /dev/null @@ -1,107 +0,0 @@ -package stake - -//// GenesisState - all staking state that must be provided at genesis -//type GenesisState struct { -//Pool Pool `json:"pool"` -//Params Params `json:"params"` -//} - -//func NewGenesisState(pool Pool, params Params, candidates []Candidate, bonds []Delegation) GenesisState { -//return GenesisState{ -//Pool: pool, -//Params: params, -//} -//} - -//// get raw genesis raw message for testing -//func DefaultGenesisState() GenesisState { -//return GenesisState{ -//Pool: initialPool(), -//Params: defaultParams(), -//} -//} - -//// fee information for a validator -//type Validator struct { -//Adjustments []sdk.Dec `json:"fee_adjustments"` // XXX Adjustment factors for lazy fee accounting, couples with Params.BondDenoms -//PrevBondedShares sdk.Dec `json:"prev_bonded_shares"` // total shares of a global hold pools -//} - -////_________________________________________________________________________ - -//// Params defines the high level settings for staking -//type Params struct { -//FeeDenoms []string `json:"fee_denoms"` // accepted fee denoms -//ReservePoolFee sdk.Dec `json:"reserve_pool_fee"` // percent of fees which go to reserve pool -//} - -//func (p Params) equal(p2 Params) bool { -//return p.BondDenom == p2.BondDenom && -//p.ReservePoolFee.Equal(p2.ReservePoolFee) -//} - -//func defaultParams() Params { -//return Params{ -//FeeDenoms: []string{"steak"}, -//ReservePoolFee: sdk.NewDec(5, 100), -//} -//} - -////_________________________________________________________________________ - -//// Pool - dynamic parameters of the current state -//type Pool struct { -//FeeReservePool sdk.Coins `json:"fee_reserve_pool"` // XXX reserve pool of collected fees for use by governance -//FeePool sdk.Coins `json:"fee_pool"` // XXX fee pool for all the fee shares which have already been distributed -//FeeSumReceived sdk.Coins `json:"fee_sum_received"` // XXX sum of all fees received, post reserve pool `json:"fee_sum_received"` -//FeeRecent sdk.Coins `json:"fee_recent"` // XXX most recent fee collected -//FeeAdjustments []sdk.Dec `json:"fee_adjustments"` // XXX Adjustment factors for lazy fee accounting, couples with Params.BondDenoms -//PrevBondedShares sdk.Dec `json:"prev_bonded_shares"` // XXX last recorded bonded shares -//} - -//func (p Pool) equal(p2 Pool) bool { -//return p.FeeReservePool.IsEqual(p2.FeeReservePool) && -//p.FeePool.IsEqual(p2.FeePool) && -//p.FeeSumReceived.IsEqual(p2.FeeSumReceived) && -//p.FeeRecent.IsEqual(p2.FeeRecent) && -//sdk.DecsEqual(p.FeeAdjustments, p2.FeeAdjustments) && -//p.PrevBondedShares.Equal(p2.PrevBondedShares) -//} - -//// initial pool for testing -//func initialPool() Pool { -//return Pool{ -//FeeReservePool: sdk.Coins(nil), -//FeePool: sdk.Coins(nil), -//FeeSumReceived: sdk.Coins(nil), -//FeeRecent: sdk.Coins(nil), -//FeeAdjustments: []sdk.Dec{sdk.ZeroDec()}, -//PrevBondedShares: sdk.ZeroDec(), -//} -//} - -////_________________________________________________________________________ - -//// Used in calculation of fee shares, added to a queue for each block where a power change occures -//type PowerChange struct { -//Height int64 `json:"height"` // block height at change -//Power sdk.Dec `json:"power"` // total power at change -//PrevPower sdk.Dec `json:"prev_power"` // total power at previous height-1 -//FeesIn sdk.Coins `json:"fees_in"` // fees in at block height -//PrevFeePool sdk.Coins `json:"prev_fee_pool"` // total fees in at previous block height -//} - -////_________________________________________________________________________ -//// KEY MANAGEMENT - -//var ( -//// Keys for store prefixes -//PowerChangeKey = []byte{0x09} // prefix for power change object -//) - -//// get the key for the accumulated update validators -//func GetPowerChangeKey(height int64) []byte { -//heightBytes := make([]byte, binary.MaxVarintLen64) -//binary.BigEndian.PutUint64(heightBytes, ^uint64(height)) // invert height (older validators first) -//return append(PowerChangeKey, heightBytes...) -//} diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 4ac461c0a..2a759c218 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -6,8 +6,8 @@ import ( "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/wire" 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/gov" @@ -49,7 +49,7 @@ var proposalFlags = []string{ } // GetCmdSubmitProposal implements submitting a proposal transaction command. -func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command { +func GetCmdSubmitProposal(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "submit-proposal", Short: "Submit a proposal along with an initial deposit", @@ -111,7 +111,7 @@ $ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome // Build and sign the transaction, then broadcast to Tendermint // proposalID must be returned, and it is a part of response. cliCtx.PrintResponse = true - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -156,10 +156,10 @@ func parseSubmitProposalFlags() (*proposal, error) { } // GetCmdDeposit implements depositing tokens for an active proposal. -func GetCmdDeposit(cdc *wire.Codec) *cobra.Command { +func GetCmdDeposit(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "deposit", - Short: "deposit tokens for activing proposal", + Short: "Deposit tokens for activing proposal", RunE: func(cmd *cobra.Command, args []string) error { txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) cliCtx := context.NewCLIContext(). @@ -191,7 +191,7 @@ func GetCmdDeposit(cdc *wire.Codec) *cobra.Command { // Build and sign the transaction, then broadcast to a Tendermint // node. - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -202,10 +202,10 @@ func GetCmdDeposit(cdc *wire.Codec) *cobra.Command { } // GetCmdVote implements creating a new vote command. -func GetCmdVote(cdc *wire.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/NoWithVeto/Abstain", RunE: func(cmd *cobra.Command, args []string) error { txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) cliCtx := context.NewCLIContext(). @@ -242,7 +242,7 @@ func GetCmdVote(cdc *wire.Codec) *cobra.Command { // Build and sign the transaction, then broadcast to a Tendermint // node. - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -253,10 +253,10 @@ func GetCmdVote(cdc *wire.Codec) *cobra.Command { } // GetCmdQueryProposal implements the query proposal command. -func GetCmdQueryProposal(queryRoute string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryProposal(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "query-proposal", - Short: "query proposal details", + Use: "proposal", + Short: "Query details of a single proposal", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) @@ -285,12 +285,11 @@ func GetCmdQueryProposal(queryRoute string, cdc *wire.Codec) *cobra.Command { return cmd } -// nolint: gocyclo // GetCmdQueryProposals implements a query proposals command. -func GetCmdQueryProposals(queryRoute string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryProposals(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "query-proposals", - Short: "query proposals with optional filters", + Use: "proposals", + Short: "Query proposals with optional filters", RunE: func(cmd *cobra.Command, args []string) error { bechDepositerAddr := viper.GetString(flagDepositer) bechVoterAddr := viper.GetString(flagVoter) @@ -366,10 +365,10 @@ func GetCmdQueryProposals(queryRoute string, cdc *wire.Codec) *cobra.Command { // Command to Get a Proposal Information // GetCmdQueryVote implements the query proposal vote command. -func GetCmdQueryVote(queryRoute string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryVote(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "query-vote", - Short: "query vote", + Use: "vote", + Short: "Query details of a single vote", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) @@ -405,10 +404,10 @@ func GetCmdQueryVote(queryRoute string, cdc *wire.Codec) *cobra.Command { } // GetCmdQueryVotes implements the command to query for proposal votes. -func GetCmdQueryVotes(queryRoute string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryVotes(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "query-votes", - Short: "query votes on a proposal", + Use: "votes", + Short: "Query votes on a proposal", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) @@ -438,10 +437,10 @@ func GetCmdQueryVotes(queryRoute string, cdc *wire.Codec) *cobra.Command { // Command to Get a specific Deposit Information // GetCmdQueryDeposit implements the query proposal deposit command. -func GetCmdQueryDeposit(queryRoute string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "query-deposit", - Short: "query deposit", + Use: "deposit", + Short: "Query details of a deposit", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) @@ -477,10 +476,10 @@ func GetCmdQueryDeposit(queryRoute string, cdc *wire.Codec) *cobra.Command { } // GetCmdQueryDeposits implements the command to query for proposal deposits. -func GetCmdQueryDeposits(queryRoute string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryDeposits(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "query-deposits", - Short: "query deposits on a proposal", + Use: "deposits", + Short: "Query deposits on a proposal", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) @@ -509,10 +508,10 @@ func GetCmdQueryDeposits(queryRoute string, cdc *wire.Codec) *cobra.Command { } // GetCmdQueryDeposits implements the command to query for proposal deposits. -func GetCmdQueryTally(queryRoute string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryTally(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "query-tally", - Short: "get the tally of a proposal vote", + 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) diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 8bf675b02..f66331ad5 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -6,8 +6,8 @@ import ( "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/wire" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/gorilla/mux" @@ -26,7 +26,7 @@ const ( ) // RegisterRoutes - Central function to define routes that get registered by the main application -func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec) { +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) { r.HandleFunc("/gov/proposals", postProposalHandlerFn(cdc, cliCtx)).Methods("POST") 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") @@ -41,7 +41,7 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec) { } type postProposalReq struct { - BaseReq baseReq `json:"base_req"` + 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} @@ -50,26 +50,27 @@ type postProposalReq struct { } type depositReq struct { - BaseReq baseReq `json:"base_req"` + BaseReq utils.BaseReq `json:"base_req"` Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit } type voteReq struct { - BaseReq baseReq `json:"base_req"` + 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 } -func postProposalHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc { +func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req postProposalReq - err := buildReq(w, r, cdc, &req) + err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { return } - if !req.BaseReq.baseReqValidate(w) { + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { return } @@ -81,11 +82,11 @@ func postProposalHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.Hand return } - signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } -func depositHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc { +func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -96,17 +97,19 @@ func depositHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFu return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } var req depositReq - err := buildReq(w, r, cdc, &req) + err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { return } - if !req.BaseReq.baseReqValidate(w) { + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { return } @@ -118,11 +121,11 @@ func depositHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFu return } - signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } -func voteHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc { +func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -133,17 +136,19 @@ func voteHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } var req voteReq - err := buildReq(w, r, cdc, &req) + err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { return } - if !req.BaseReq.baseReqValidate(w) { + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { return } @@ -155,11 +160,11 @@ func voteHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc return } - signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } -func queryProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { +func queryProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -170,7 +175,7 @@ func queryProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -197,7 +202,7 @@ func queryProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { } } -func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc { +func queryDepositHandlerFn(cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -209,7 +214,7 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc { return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -264,7 +269,7 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc { } } -func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc { +func queryVoteHandlerFn(cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -276,7 +281,7 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc { return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -334,9 +339,8 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc { } } -// nolint: gocyclo // todo: Split this functionality into helper functions to remove the above -func queryVotesOnProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { +func queryVotesOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -347,7 +351,7 @@ func queryVotesOnProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -373,9 +377,8 @@ func queryVotesOnProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { } } -// nolint: gocyclo // todo: Split this functionality into helper functions to remove the above -func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc { +func queryProposalsWithParameterFn(cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { bechVoterAddr := r.URL.Query().Get(RestVoter) bechDepositerAddr := r.URL.Query().Get(RestDepositer) @@ -414,7 +417,7 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc { params.ProposalStatus = proposalStatus } if len(strNumLatest) != 0 { - numLatest, ok := parseInt64OrReturnBadRequest(strNumLatest, w) + numLatest, ok := utils.ParseInt64OrReturnBadRequest(w, strNumLatest) if !ok { return } @@ -439,9 +442,8 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc { } } -// nolint: gocyclo // todo: Split this functionality into helper functions to remove the above -func queryTallyOnProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { +func queryTallyOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -454,7 +456,7 @@ func queryTallyOnProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } diff --git a/x/gov/client/rest/util.go b/x/gov/client/rest/util.go deleted file mode 100644 index f9987c030..000000000 --- a/x/gov/client/rest/util.go +++ /dev/null @@ -1,136 +0,0 @@ -package rest - -import ( - "fmt" - "io/ioutil" - "net/http" - "strconv" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" -) - -type baseReq struct { - Name string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas int64 `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` -} - -func buildReq(w http.ResponseWriter, r *http.Request, cdc *wire.Codec, req interface{}) error { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return err - } - err = cdc.UnmarshalJSON(body, req) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return err - } - return nil -} - -func (req baseReq) baseReqValidate(w http.ResponseWriter) bool { - if len(req.Name) == 0 { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Name required but not specified") - return false - } - - if len(req.Password) == 0 { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Password required but not specified") - return false - } - - if len(req.ChainID) == 0 { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "ChainID required but not specified") - return false - } - - if req.AccountNumber < 0 { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Account Number required but not specified") - return false - } - - if req.Sequence < 0 { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Sequence required but not specified") - return false - } - return true -} - -// TODO: Build this function out into a more generic base-request -// (probably should live in client/lcd). -func signAndBuild(w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext, baseReq baseReq, msg sdk.Msg, cdc *wire.Codec) { - var err error - txBldr := authtxb.TxBuilder{ - Codec: cdc, - AccountNumber: baseReq.AccountNumber, - Sequence: baseReq.Sequence, - ChainID: baseReq.ChainID, - Gas: baseReq.Gas, - } - - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) - if !ok { - return - } - cliCtx = cliCtx.WithGasAdjustment(adjustment) - - if utils.HasDryRunArg(r) || baseReq.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, txBldr.Gas) - return - } - txBldr = newCtx - } - - if utils.HasGenerateOnlyArg(r) { - utils.WriteGenerateStdTxResponse(w, txBldr, []sdk.Msg{msg}) - return - } - - txBytes, err := txBldr.BuildAndSign(baseReq.Name, baseReq.Password, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - output, err := wire.MarshalJSONIndent(cdc, res) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(output) -} - -func parseInt64OrReturnBadRequest(s string, w http.ResponseWriter) (n int64, ok bool) { - var err error - n, err = strconv.ParseInt(s, 10, 64) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - err := fmt.Errorf("'%s' is not a valid int64", s) - w.Write([]byte(err.Error())) - return 0, false - } - return n, true -} diff --git a/x/gov/wire.go b/x/gov/codec.go similarity index 70% rename from x/gov/wire.go rename to x/gov/codec.go index 405ee464e..6df535449 100644 --- a/x/gov/wire.go +++ b/x/gov/codec.go @@ -1,11 +1,11 @@ package gov import ( - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" ) -// Register concrete types on wire codec -func RegisterWire(cdc *wire.Codec) { +// Register concrete types on codec codec +func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgSubmitProposal{}, "cosmos-sdk/MsgSubmitProposal", nil) cdc.RegisterConcrete(MsgDeposit{}, "cosmos-sdk/MsgDeposit", nil) @@ -15,4 +15,4 @@ func RegisterWire(cdc *wire.Codec) { cdc.RegisterConcrete(&TextProposal{}, "gov/TextProposal", nil) } -var msgCdc = wire.NewCodec() +var msgCdc = codec.New() diff --git a/x/gov/endblocker_test.go b/x/gov/endblocker_test.go index 710ecb1db..27eff15f6 100644 --- a/x/gov/endblocker_test.go +++ b/x/gov/endblocker_test.go @@ -2,11 +2,11 @@ package gov import ( "testing" + "time" "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake" abci "github.com/tendermint/tendermint/abci/types" ) @@ -28,12 +28,18 @@ func TestTickExpiredDepositPeriod(t *testing.T) { require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) - ctx = ctx.WithBlockHeight(10) + 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)) - ctx = ctx.WithBlockHeight(250) + newHeader = ctx.BlockHeader() + newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod) + ctx = ctx.WithBlockHeader(newHeader) + require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) EndBlocker(ctx, keeper) @@ -59,7 +65,10 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) - ctx = ctx.WithBlockHeight(10) + 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)) @@ -68,14 +77,20 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { res = govHandler(ctx, newProposalMsg2) require.True(t, res.IsOK()) - ctx = ctx.WithBlockHeight(205) + newHeader = ctx.BlockHeader() + newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod).Add(time.Duration(-1) * time.Second) + ctx = ctx.WithBlockHeader(newHeader) + require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) EndBlocker(ctx, keeper) require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) - ctx = ctx.WithBlockHeight(215) + 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)) EndBlocker(ctx, keeper) @@ -105,7 +120,10 @@ func TestTickPassedDepositPeriod(t *testing.T) { require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) - ctx = ctx.WithBlockHeight(10) + 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)) @@ -146,14 +164,20 @@ func TestTickPassedVotingPeriod(t *testing.T) { var proposalID int64 keeper.cdc.UnmarshalBinaryBare(res.Data, &proposalID) - ctx = ctx.WithBlockHeight(10) + 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)}) res = govHandler(ctx, newDepositMsg) require.True(t, res.IsOK()) EndBlocker(ctx, keeper) - ctx = ctx.WithBlockHeight(215) + newHeader = ctx.BlockHeader() + newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod).Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod) + ctx = ctx.WithBlockHeader(newHeader) + require.True(t, shouldPopActiveProposalQueue(ctx, keeper)) depositsIterator := keeper.GetDeposits(ctx, proposalID) require.True(t, depositsIterator.Valid()) @@ -169,56 +193,3 @@ func TestTickPassedVotingPeriod(t *testing.T) { require.Equal(t, StatusRejected, keeper.GetProposal(ctx, proposalID).GetStatus()) require.True(t, keeper.GetProposal(ctx, proposalID).GetTallyResult().Equals(EmptyTallyResult())) } - -func TestSlashing(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) - SortAddresses(addrs) - mapp.BeginBlock(abci.RequestBeginBlock{}) - ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - govHandler := NewHandler(keeper) - stakeHandler := stake.NewHandler(sk) - - valAddrs := make([]sdk.ValAddress, len(addrs[:3])) - for i, addr := range addrs[:3] { - valAddrs[i] = sdk.ValAddress(addr) - } - - createValidators(t, stakeHandler, ctx, valAddrs, []int64{25, 6, 7}) - - initTotalPower := keeper.ds.GetValidatorSet().TotalPower(ctx) - val0Initial := keeper.ds.GetValidatorSet().Validator(ctx, sdk.ValAddress(addrs[0])).GetPower().Quo(initTotalPower) - val1Initial := keeper.ds.GetValidatorSet().Validator(ctx, sdk.ValAddress(addrs[1])).GetPower().Quo(initTotalPower) - val2Initial := keeper.ds.GetValidatorSet().Validator(ctx, sdk.ValAddress(addrs[2])).GetPower().Quo(initTotalPower) - - newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("steak", 15)}) - - res := govHandler(ctx, newProposalMsg) - require.True(t, res.IsOK()) - var proposalID int64 - keeper.cdc.UnmarshalBinaryBare(res.Data, &proposalID) - - ctx = ctx.WithBlockHeight(10) - require.Equal(t, StatusVotingPeriod, keeper.GetProposal(ctx, proposalID).GetStatus()) - - newVoteMsg := NewMsgVote(addrs[0], proposalID, OptionYes) - res = govHandler(ctx, newVoteMsg) - require.True(t, res.IsOK()) - - EndBlocker(ctx, keeper) - - ctx = ctx.WithBlockHeight(215) - require.Equal(t, StatusVotingPeriod, keeper.GetProposal(ctx, proposalID).GetStatus()) - - EndBlocker(ctx, keeper) - - require.False(t, keeper.GetProposal(ctx, proposalID).GetTallyResult().Equals(EmptyTallyResult())) - - endTotalPower := keeper.ds.GetValidatorSet().TotalPower(ctx) - val0End := keeper.ds.GetValidatorSet().Validator(ctx, sdk.ValAddress(addrs[0])).GetPower().Quo(endTotalPower) - val1End := keeper.ds.GetValidatorSet().Validator(ctx, sdk.ValAddress(addrs[1])).GetPower().Quo(endTotalPower) - val2End := keeper.ds.GetValidatorSet().Validator(ctx, sdk.ValAddress(addrs[2])).GetPower().Quo(endTotalPower) - - require.True(t, val0End.GTE(val0Initial)) - require.True(t, val1End.LT(val1Initial)) - require.True(t, val2End.LT(val2Initial)) -} diff --git a/x/gov/genesis.go b/x/gov/genesis.go index 15f952c00..58273c8e8 100644 --- a/x/gov/genesis.go +++ b/x/gov/genesis.go @@ -1,6 +1,8 @@ package gov import ( + "time" + sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -27,10 +29,10 @@ func DefaultGenesisState() GenesisState { StartingProposalID: 1, DepositProcedure: DepositProcedure{ MinDeposit: sdk.Coins{sdk.NewInt64Coin("steak", 10)}, - MaxDepositPeriod: 200, + MaxDepositPeriod: time.Duration(172800) * time.Second, }, VotingProcedure: VotingProcedure{ - VotingPeriod: 200, + VotingPeriod: time.Duration(172800) * time.Second, }, TallyingProcedure: TallyingProcedure{ Threshold: sdk.NewDecWithPrec(5, 1), diff --git a/x/gov/handler.go b/x/gov/handler.go index 6424bb0a1..3f8cd312b 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -114,21 +114,27 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { resTags.AppendTag(tags.Action, tags.ActionProposalDropped) resTags.AppendTag(tags.ProposalID, proposalIDBytes) - logger.Info(fmt.Sprintf("Proposal %d - \"%s\" - didn't mean minimum deposit (had only %s), deleted", - inactiveProposal.GetProposalID(), inactiveProposal.GetTitle(), inactiveProposal.GetTotalDeposit())) + logger.Info( + fmt.Sprintf("proposal %d (%s) didn't meet minimum deposit of %v steak (had only %v steak); deleted", + inactiveProposal.GetProposalID(), + inactiveProposal.GetTitle(), + keeper.GetDepositProcedure(ctx).MinDeposit.AmountOf("steak"), + inactiveProposal.GetTotalDeposit().AmountOf("steak"), + ), + ) } // Check if earliest Active Proposal ended voting period yet for shouldPopActiveProposalQueue(ctx, keeper) { activeProposal := keeper.ActiveProposalQueuePop(ctx) - proposalStartBlock := activeProposal.GetVotingStartBlock() + proposalStartTime := activeProposal.GetVotingStartTime() votingPeriod := keeper.GetVotingProcedure(ctx).VotingPeriod - if ctx.BlockHeight() < proposalStartBlock+votingPeriod { + if ctx.BlockHeader().Time.Before(proposalStartTime.Add(votingPeriod)) { continue } - passes, tallyResults, nonVotingVals := tally(ctx, keeper, activeProposal) + passes, tallyResults := tally(ctx, keeper, activeProposal) proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(activeProposal.GetProposalID()) var action []byte if passes { @@ -143,21 +149,9 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { activeProposal.SetTallyResult(tallyResults) keeper.SetProposal(ctx, activeProposal) - logger.Info(fmt.Sprintf("Proposal %d - \"%s\" - tallied, passed: %v", + logger.Info(fmt.Sprintf("proposal %d (%s) tallied; passed: %v", activeProposal.GetProposalID(), activeProposal.GetTitle(), passes)) - for _, valAddr := range nonVotingVals { - val := keeper.ds.GetValidatorSet().Validator(ctx, valAddr) - keeper.ds.GetValidatorSet().Slash(ctx, - val.GetPubKey(), - ctx.BlockHeight(), - val.GetPower().RoundInt64(), - keeper.GetTallyingProcedure(ctx).GovernancePenalty) - - logger.Info(fmt.Sprintf("Validator %s failed to vote on proposal %d, slashing", - val.GetOperator(), activeProposal.GetProposalID())) - } - resTags.AppendTag(tags.Action, action) resTags.AppendTag(tags.ProposalID, proposalIDBytes) } @@ -172,7 +166,7 @@ func shouldPopInactiveProposalQueue(ctx sdk.Context, keeper Keeper) bool { return false } else if peekProposal.GetStatus() != StatusDepositPeriod { return true - } else if ctx.BlockHeight() >= peekProposal.GetSubmitBlock()+depositProcedure.MaxDepositPeriod { + } else if !ctx.BlockHeader().Time.Before(peekProposal.GetSubmitTime().Add(depositProcedure.MaxDepositPeriod)) { return true } return false @@ -184,7 +178,7 @@ func shouldPopActiveProposalQueue(ctx sdk.Context, keeper Keeper) bool { if peekProposal == nil { return false - } else if ctx.BlockHeight() >= peekProposal.GetVotingStartBlock()+votingProcedure.VotingPeriod { + } 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 576d2cc22..80e5a205c 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -1,8 +1,8 @@ package gov import ( + codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/params" ) @@ -31,15 +31,19 @@ type Keeper struct { // The (unexposed) keys used to access the stores from the Context. storeKey sdk.StoreKey - // The wire codec for binary encoding/decoding. - cdc *wire.Codec + // The codec codec for binary encoding/decoding. + cdc *codec.Codec // Reserved codespace codespace sdk.CodespaceType } -// NewGovernanceMapper returns a mapper that uses go-wire to (binary) encode and decode gov types. -func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ps params.Setter, ck bank.Keeper, ds sdk.DelegationSet, codespace sdk.CodespaceType) Keeper { +// NewKeeper returns a governance keeper. It handles: +// - submitting governance proposals +// - depositing funds into proposals, and activating upon sufficient funds being deposited +// - users voting on proposals, with weight proportional to stake in the system +// - and tallying the result of the vote. +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, ps params.Setter, ck bank.Keeper, ds sdk.DelegationSet, codespace sdk.CodespaceType) Keeper { return Keeper{ storeKey: key, ps: ps, @@ -51,11 +55,6 @@ func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ps params.Setter, ck bank.Keep } } -// Returns the go-wire codec. -func (keeper Keeper) WireCodec() *wire.Codec { - return keeper.cdc -} - // ===================================================== // Proposals @@ -66,15 +65,14 @@ func (keeper Keeper) NewTextProposal(ctx sdk.Context, title string, description return nil } var proposal Proposal = &TextProposal{ - ProposalID: proposalID, - Title: title, - Description: description, - ProposalType: proposalType, - Status: StatusDepositPeriod, - TallyResult: EmptyTallyResult(), - TotalDeposit: sdk.Coins{}, - SubmitBlock: ctx.BlockHeight(), - VotingStartBlock: -1, // TODO: Make Time + ProposalID: proposalID, + Title: title, + Description: description, + ProposalType: proposalType, + Status: StatusDepositPeriod, + TallyResult: EmptyTallyResult(), + TotalDeposit: sdk.Coins{}, + SubmitTime: ctx.BlockHeader().Time, } keeper.SetProposal(ctx, proposal) keeper.InactiveProposalQueuePush(ctx, proposal) @@ -108,7 +106,6 @@ func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposal Proposal) { store.Delete(KeyProposal(proposal.GetProposalID())) } -// nolint: gocyclo // Get Proposal from store by ProposalID func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositerAddr sdk.AccAddress, status ProposalStatus, numLatest int64) []Proposal { @@ -200,7 +197,7 @@ func (keeper Keeper) peekCurrentProposalID(ctx sdk.Context) (proposalID int64, e } func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) { - proposal.SetVotingStartBlock(ctx.BlockHeight()) + proposal.SetVotingStartTime(ctx.BlockHeader().Time) proposal.SetStatus(StatusVotingPeriod) keeper.SetProposal(ctx, proposal) keeper.ActiveProposalQueuePush(ctx, proposal) diff --git a/x/gov/keeper_test.go b/x/gov/keeper_test.go index a61292b93..91c41d7d7 100644 --- a/x/gov/keeper_test.go +++ b/x/gov/keeper_test.go @@ -2,6 +2,7 @@ package gov import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -45,12 +46,12 @@ func TestActivateVotingPeriod(t *testing.T) { proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - require.Equal(t, int64(-1), proposal.GetVotingStartBlock()) + require.True(t, proposal.GetVotingStartTime().Equal(time.Time{})) require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) keeper.activateVotingPeriod(ctx, proposal) - require.Equal(t, proposal.GetVotingStartBlock(), ctx.BlockHeight()) + require.True(t, proposal.GetVotingStartTime().Equal(ctx.BlockHeader().Time)) require.Equal(t, proposal.GetProposalID(), keeper.ActiveProposalQueuePeek(ctx).GetProposalID()) } @@ -77,7 +78,7 @@ func TestDeposits(t *testing.T) { // Check no deposits at beginning deposit, found := keeper.GetDeposit(ctx, proposalID, addrs[1]) require.False(t, found) - require.Equal(t, keeper.GetProposal(ctx, proposalID).GetVotingStartBlock(), int64(-1)) + require.True(t, keeper.GetProposal(ctx, proposalID).GetVotingStartTime().Equal(time.Time{})) require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) // Check first deposit @@ -114,7 +115,7 @@ func TestDeposits(t *testing.T) { require.Equal(t, addr1Initial.Minus(fourSteak), keeper.ck.GetCoins(ctx, addrs[1])) // Check that proposal moved to voting period - require.Equal(t, ctx.BlockHeight(), keeper.GetProposal(ctx, proposalID).GetVotingStartBlock()) + 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()) diff --git a/x/gov/msgs.go b/x/gov/msgs.go index dcd7112aa..a5a68ea21 100644 --- a/x/gov/msgs.go +++ b/x/gov/msgs.go @@ -9,6 +9,8 @@ import ( // name to idetify transaction types const MsgType = "gov" +var _, _, _ sdk.Msg = MsgSubmitProposal{}, MsgDeposit{}, MsgVote{} + //----------------------------------------------------------- // MsgSubmitProposal type MsgSubmitProposal struct { @@ -31,6 +33,7 @@ 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" } // Implements Msg. func (msg MsgSubmitProposal) ValidateBasic() sdk.Error { @@ -95,7 +98,9 @@ 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" } // Implements Msg. func (msg MsgDeposit) ValidateBasic() sdk.Error { @@ -154,7 +159,9 @@ 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" } // Implements Msg. func (msg MsgVote) ValidateBasic() sdk.Error { diff --git a/x/gov/procedures.go b/x/gov/procedures.go index f74091c74..e453add79 100644 --- a/x/gov/procedures.go +++ b/x/gov/procedures.go @@ -1,13 +1,15 @@ package gov import ( + "time" + sdk "github.com/cosmos/cosmos-sdk/types" ) // Procedure around Deposits for governance type DepositProcedure struct { - MinDeposit sdk.Coins `json:"min_deposit"` // Minimum deposit for a proposal to enter voting period. - MaxDepositPeriod int64 `json:"max_deposit_period"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months + 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 @@ -19,5 +21,5 @@ type TallyingProcedure struct { // Procedure around Voting in governance type VotingProcedure struct { - VotingPeriod int64 `json:"voting_period"` // Length of the voting period. + VotingPeriod time.Duration `json:"voting_period"` // Length of the voting period. } diff --git a/x/gov/proposals.go b/x/gov/proposals.go index b4c678367..37e29df70 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -3,6 +3,7 @@ package gov import ( "encoding/json" "fmt" + "time" "github.com/pkg/errors" @@ -30,14 +31,14 @@ type Proposal interface { GetTallyResult() TallyResult SetTallyResult(TallyResult) - GetSubmitBlock() int64 - SetSubmitBlock(int64) + GetSubmitTime() time.Time + SetSubmitTime(time.Time) GetTotalDeposit() sdk.Coins SetTotalDeposit(sdk.Coins) - GetVotingStartBlock() int64 - SetVotingStartBlock(int64) + GetVotingStartTime() time.Time + SetVotingStartTime(time.Time) } // checks if two proposals are equal @@ -48,9 +49,9 @@ func ProposalEqual(proposalA Proposal, proposalB Proposal) bool { proposalA.GetProposalType() == proposalB.GetProposalType() && proposalA.GetStatus() == proposalB.GetStatus() && proposalA.GetTallyResult().Equals(proposalB.GetTallyResult()) && - proposalA.GetSubmitBlock() == proposalB.GetSubmitBlock() && + proposalA.GetSubmitTime().Equal(proposalB.GetSubmitTime()) && proposalA.GetTotalDeposit().IsEqual(proposalB.GetTotalDeposit()) && - proposalA.GetVotingStartBlock() == proposalB.GetVotingStartBlock() { + proposalA.GetVotingStartTime().Equal(proposalB.GetVotingStartTime()) { return true } return false @@ -67,10 +68,10 @@ type TextProposal struct { Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected} TallyResult TallyResult `json:"tally_result"` // Result of Tallys - SubmitBlock int64 `json:"submit_block"` // Height of the block where TxGovSubmitProposal was included + 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 - VotingStartBlock int64 `json:"voting_start_block"` // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached + VotingStartTime time.Time `json:"voting_start_block"` // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached } // Implements Proposal Interface @@ -89,13 +90,13 @@ func (tp TextProposal) GetStatus() ProposalStatus { return tp.S func (tp *TextProposal) SetStatus(status ProposalStatus) { tp.Status = status } func (tp TextProposal) GetTallyResult() TallyResult { return tp.TallyResult } func (tp *TextProposal) SetTallyResult(tallyResult TallyResult) { tp.TallyResult = tallyResult } -func (tp TextProposal) GetSubmitBlock() int64 { return tp.SubmitBlock } -func (tp *TextProposal) SetSubmitBlock(submitBlock int64) { tp.SubmitBlock = submitBlock } +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) GetVotingStartBlock() int64 { return tp.VotingStartBlock } -func (tp *TextProposal) SetVotingStartBlock(votingStartBlock int64) { - tp.VotingStartBlock = votingStartBlock +func (tp TextProposal) GetVotingStartTime() time.Time { return tp.VotingStartTime } +func (tp *TextProposal) SetVotingStartTime(votingStartTime time.Time) { + tp.VotingStartTime = votingStartTime } //----------------------------------------------------------- @@ -191,8 +192,9 @@ func (pt ProposalKind) String() string { func (pt ProposalKind) Format(s fmt.State, verb rune) { switch verb { case 's': - s.Write([]byte(fmt.Sprintf("%s", pt.String()))) + s.Write([]byte(pt.String())) default: + // TODO: Do this conversion more directly s.Write([]byte(fmt.Sprintf("%v", byte(pt)))) } } @@ -294,8 +296,9 @@ func (status ProposalStatus) String() string { func (status ProposalStatus) Format(s fmt.State, verb rune) { switch verb { case 's': - s.Write([]byte(fmt.Sprintf("%s", status.String()))) + s.Write([]byte(status.String())) default: + // TODO: Do this conversion more directly s.Write([]byte(fmt.Sprintf("%v", byte(status)))) } } @@ -321,11 +324,8 @@ func EmptyTallyResult() TallyResult { // checks if two proposals are equal func (resultA TallyResult) Equals(resultB TallyResult) bool { - if resultA.Yes.Equal(resultB.Yes) && + return (resultA.Yes.Equal(resultB.Yes) && resultA.Abstain.Equal(resultB.Abstain) && resultA.No.Equal(resultB.No) && - resultA.NoWithVeto.Equal(resultB.NoWithVeto) { - return true - } - return false + resultA.NoWithVeto.Equal(resultB.NoWithVeto)) } diff --git a/x/gov/proposals_test.go b/x/gov/proposals_test.go new file mode 100644 index 000000000..1d5f2cf38 --- /dev/null +++ b/x/gov/proposals_test.go @@ -0,0 +1,40 @@ +package gov + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestProposalKind_Format(t *testing.T) { + typeText, _ := ProposalTypeFromString("Text") + tests := []struct { + pt ProposalKind + sprintFArgs string + expectedStringOutput string + }{ + {typeText, "%s", "Text"}, + {typeText, "%v", "1"}, + } + for _, tt := range tests { + got := fmt.Sprintf(tt.sprintFArgs, tt.pt) + require.Equal(t, tt.expectedStringOutput, got) + } +} + +func TestProposalStatus_Format(t *testing.T) { + statusDepositPeriod, _ := ProposalStatusFromString("DepositPeriod") + tests := []struct { + pt ProposalStatus + sprintFArgs string + expectedStringOutput string + }{ + {statusDepositPeriod, "%s", "DepositPeriod"}, + {statusDepositPeriod, "%v", "1"}, + } + for _, tt := range tests { + got := fmt.Sprintf(tt.sprintFArgs, tt.pt) + require.Equal(t, tt.expectedStringOutput, got) + } +} diff --git a/x/gov/queryable.go b/x/gov/queryable.go index 090b9a914..606f73a9c 100644 --- a/x/gov/queryable.go +++ b/x/gov/queryable.go @@ -3,27 +3,38 @@ package gov import ( "fmt" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" abci "github.com/tendermint/tendermint/abci/types" ) +// query endpoints supported by the governance Querier +const ( + QueryProposals = "proposals" + QueryProposal = "proposal" + QueryDeposits = "deposits" + QueryDeposit = "deposit" + QueryVotes = "votes" + QueryVote = "vote" + QueryTally = "tally" +) + 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 "proposal": - return queryProposal(ctx, path[1:], req, keeper) - case "deposit": - return queryDeposit(ctx, path[1:], req, keeper) - case "vote": - return queryVote(ctx, path[1:], req, keeper) - case "deposits": - return queryDeposits(ctx, path[1:], req, keeper) - case "votes": - return queryVotes(ctx, path[1:], req, keeper) - case "proposals": + case QueryProposals: return queryProposals(ctx, path[1:], req, keeper) - case "tally": + case QueryProposal: + return queryProposal(ctx, path[1:], req, keeper) + case QueryDeposits: + return queryDeposits(ctx, path[1:], req, keeper) + case QueryDeposit: + return queryDeposit(ctx, path[1:], req, keeper) + case QueryVotes: + return queryVotes(ctx, path[1:], req, keeper) + case QueryVote: + return queryVote(ctx, path[1:], req, keeper) + case QueryTally: return queryTally(ctx, path[1:], req, keeper) default: return nil, sdk.ErrUnknownRequest("unknown gov query endpoint") @@ -49,7 +60,7 @@ func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, keeper return []byte{}, ErrUnknownProposal(DefaultCodespace, params.ProposalID) } - bz, err2 := wire.MarshalJSONIndent(keeper.cdc, proposal) + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, proposal) if err2 != nil { panic("could not marshal result to JSON") } @@ -71,7 +82,7 @@ func queryDeposit(ctx sdk.Context, path []string, req abci.RequestQuery, keeper } deposit, _ := keeper.GetDeposit(ctx, params.ProposalID, params.Depositer) - bz, err2 := wire.MarshalJSONIndent(keeper.cdc, deposit) + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, deposit) if err2 != nil { panic("could not marshal result to JSON") } @@ -93,7 +104,7 @@ func queryVote(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Kee } vote, _ := keeper.GetVote(ctx, params.ProposalID, params.Voter) - bz, err2 := wire.MarshalJSONIndent(keeper.cdc, vote) + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, vote) if err2 != nil { panic("could not marshal result to JSON") } @@ -121,7 +132,7 @@ func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper deposits = append(deposits, deposit) } - bz, err2 := wire.MarshalJSONIndent(keeper.cdc, deposits) + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, deposits) if err2 != nil { panic("could not marshal result to JSON") } @@ -150,7 +161,7 @@ func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke votes = append(votes, vote) } - bz, err2 := wire.MarshalJSONIndent(keeper.cdc, votes) + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, votes) if err2 != nil { panic("could not marshal result to JSON") } @@ -175,7 +186,7 @@ func queryProposals(ctx sdk.Context, path []string, req abci.RequestQuery, keepe proposals := keeper.GetProposalsFiltered(ctx, params.Voter, params.Depositer, params.ProposalStatus, params.NumLatestProposals) - bz, err2 := wire.MarshalJSONIndent(keeper.cdc, proposals) + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, proposals) if err2 != nil { panic("could not marshal result to JSON") } @@ -194,12 +205,12 @@ func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke var proposalID int64 err2 := keeper.cdc.UnmarshalJSON(req.Data, proposalID) if err2 != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return res, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) } proposal := keeper.GetProposal(ctx, proposalID) if proposal == nil { - return []byte{}, ErrUnknownProposal(DefaultCodespace, proposalID) + return res, ErrUnknownProposal(DefaultCodespace, proposalID) } var tallyResult TallyResult @@ -209,10 +220,10 @@ func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke } else if proposal.GetStatus() == StatusPassed || proposal.GetStatus() == StatusRejected { tallyResult = proposal.GetTallyResult() } else { - _, tallyResult, _ = tally(ctx, keeper, proposal) + _, tallyResult = tally(ctx, keeper, proposal) } - bz, err2 := wire.MarshalJSONIndent(keeper.cdc, tallyResult) + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, tallyResult) if err2 != nil { panic("could not marshal result to JSON") } diff --git a/x/gov/simulation/invariants.go b/x/gov/simulation/invariants.go index e9275f3c1..6d5f41918 100644 --- a/x/gov/simulation/invariants.go +++ b/x/gov/simulation/invariants.go @@ -1,19 +1,15 @@ package simulation import ( - "testing" - - "github.com/stretchr/testify/require" - "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/x/mock/simulation" ) // AllInvariants tests all governance invariants func AllInvariants() simulation.Invariant { - return func(t *testing.T, app *baseapp.BaseApp, log string) { + return func(app *baseapp.BaseApp) error { // TODO Add some invariants! // Checking proposal queues, no passed-but-unexecuted proposals, etc. - require.Nil(t, nil) + return nil } } diff --git a/x/gov/simulation/msgs.go b/x/gov/simulation/msgs.go index 0be299e49..d5ff1881e 100644 --- a/x/gov/simulation/msgs.go +++ b/x/gov/simulation/msgs.go @@ -4,9 +4,7 @@ import ( "fmt" "math" "math/rand" - "testing" - - "github.com/tendermint/tendermint/crypto" + "time" "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" @@ -45,25 +43,32 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keepe }) statePercentageArray := []float64{1, .9, .75, .4, .15, 0} curNumVotesState := 1 - return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOps []simulation.FutureOperation, err sdk.Error) { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) { // 1) submit proposal now - sender := simulation.RandomKey(r, keys) - msg := simulationCreateMsgSubmitProposal(tb, r, sender, log) - action = simulateHandleMsgSubmitProposal(msg, sk, handler, ctx, event) + sender := simulation.RandomAcc(r, accs) + msg, err := simulationCreateMsgSubmitProposal(r, sender) + if err != nil { + return "", nil, err + } + action, ok := simulateHandleMsgSubmitProposal(msg, sk, handler, ctx, event) + // don't schedule votes if proposal failed + if !ok { + return action, nil, nil + } proposalID := k.GetLastProposalID(ctx) // 2) Schedule operations for votes // 2.1) first pick a number of people to vote. curNumVotesState = numVotesTransitionMatrix.NextState(r, curNumVotesState) - numVotes := int(math.Ceil(float64(len(keys)) * statePercentageArray[curNumVotesState])) + numVotes := int(math.Ceil(float64(len(accs)) * statePercentageArray[curNumVotesState])) // 2.2) select who votes and when - whoVotes := r.Perm(len(keys)) + whoVotes := r.Perm(len(accs)) // didntVote := whoVotes[numVotes:] whoVotes = whoVotes[:numVotes] votingPeriod := k.GetVotingProcedure(ctx).VotingPeriod fops := make([]simulation.FutureOperation, numVotes+1) for i := 0; i < numVotes; i++ { - whenVote := ctx.BlockHeight() + r.Int63n(votingPeriod) - fops[i] = simulation.FutureOperation{int(whenVote), operationSimulateMsgVote(k, sk, keys[whoVotes[i]], proposalID)} + 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)} } // 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 @@ -77,58 +82,60 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keepe // Note: Currently doesn't ensure that the proposal txt is in JSON form func SimulateMsgSubmitProposal(k gov.Keeper, sk stake.Keeper) simulation.Operation { handler := gov.NewHandler(k) - return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOps []simulation.FutureOperation, err sdk.Error) { - sender := simulation.RandomKey(r, keys) - msg := simulationCreateMsgSubmitProposal(tb, r, sender, log) - action = simulateHandleMsgSubmitProposal(msg, sk, handler, ctx, event) + 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) + msg, err := simulationCreateMsgSubmitProposal(r, sender) + if err != nil { + return "", nil, err + } + action, _ = simulateHandleMsgSubmitProposal(msg, sk, 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) { +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() result := handler(ctx, msg) - if result.IsOK() { + 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", result.IsOK())) - action = fmt.Sprintf("TestMsgSubmitProposal: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) - return action + event(fmt.Sprintf("gov/MsgSubmitProposal/%v", ok)) + action = fmt.Sprintf("TestMsgSubmitProposal: ok %v, msg %s", ok, msg.GetSignBytes()) + return } -func simulationCreateMsgSubmitProposal(tb testing.TB, r *rand.Rand, sender crypto.PrivKey, log string) gov.MsgSubmitProposal { - addr := sdk.AccAddress(sender.PubKey().Address()) +func simulationCreateMsgSubmitProposal(r *rand.Rand, sender simulation.Account) (msg gov.MsgSubmitProposal, err error) { deposit := randomDeposit(r) - msg := gov.NewMsgSubmitProposal( + msg = gov.NewMsgSubmitProposal( simulation.RandStringOfLength(r, 5), simulation.RandStringOfLength(r, 5), gov.ProposalTypeText, - addr, + sender.Address, deposit, ) if msg.ValidateBasic() != nil { - tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + err = fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } - return msg + return } // SimulateMsgDeposit func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation { - return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { - key := simulation.RandomKey(r, keys) - addr := sdk.AccAddress(key.PubKey().Address()) + 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) if !ok { return "no-operation", nil, nil } deposit := randomDeposit(r) - msg := gov.NewMsgDeposit(addr, proposalID, deposit) + msg := gov.NewMsgDeposit(acc.Address, proposalID, deposit) if msg.ValidateBasic() != nil { - tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } ctx, write := ctx.CacheContext() result := gov.NewHandler(k)(ctx, msg) @@ -148,33 +155,37 @@ 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, nil, -1) + return operationSimulateMsgVote(k, sk, simulation.Account{}, -1) } // nolint: unparam -func operationSimulateMsgVote(k gov.Keeper, sk stake.Keeper, key crypto.PrivKey, proposalID int64) simulation.Operation { - return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { - if key == nil { - key = simulation.RandomKey(r, keys) +func operationSimulateMsgVote(k gov.Keeper, sk stake.Keeper, acc simulation.Account, proposalID int64) 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) } + var ok bool + if proposalID < 0 { proposalID, ok = randomProposalID(r, k, ctx) if !ok { return "no-operation", nil, nil } } - addr := sdk.AccAddress(key.PubKey().Address()) option := randomVotingOption(r) - msg := gov.NewMsgVote(addr, proposalID, option) + + msg := gov.NewMsgVote(acc.Address, proposalID, option) if msg.ValidateBasic() != nil { - tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } + ctx, write := ctx.CacheContext() result := gov.NewHandler(k)(ctx, msg) if result.IsOK() { write() } + event(fmt.Sprintf("gov/MsgVote/%v", result.IsOK())) action = fmt.Sprintf("TestMsgVote: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil, nil diff --git a/x/gov/simulation/sim_test.go b/x/gov/simulation/sim_test.go index 317864a61..b0317d116 100644 --- a/x/gov/simulation/sim_test.go +++ b/x/gov/simulation/sim_test.go @@ -6,7 +6,6 @@ import ( "testing" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" @@ -21,12 +20,13 @@ import ( func TestGovWithRandomMessages(t *testing.T) { mapp := mock.NewApp() - bank.RegisterWire(mapp.Cdc) - gov.RegisterWire(mapp.Cdc) + bank.RegisterCodec(mapp.Cdc) + gov.RegisterCodec(mapp.Cdc) mapper := mapp.AccountMapper - bankKeeper := bank.NewKeeper(mapper) + bankKeeper := bank.NewBaseKeeper(mapper) stakeKey := sdk.NewKVStoreKey("stake") - stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, bankKeeper, stake.DefaultCodespace) + stakeTKey := sdk.NewTransientStoreKey("transient_stake") + stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, stakeTKey, bankKeeper, stake.DefaultCodespace) paramKey := sdk.NewKVStoreKey("params") paramKeeper := params.NewKeeper(mapp.Cdc, paramKey) govKey := sdk.NewKVStoreKey("gov") @@ -37,17 +37,17 @@ func TestGovWithRandomMessages(t *testing.T) { return abci.ResponseEndBlock{} }) - err := mapp.CompleteSetup([]*sdk.KVStoreKey{stakeKey, paramKey, govKey}) + err := mapp.CompleteSetup(stakeKey, stakeTKey, paramKey, govKey) if err != nil { panic(err) } - appStateFn := func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage { - mock.RandomSetGenesis(r, mapp, accs, []string{"stake"}) + appStateFn := func(r *rand.Rand, accs []simulation.Account) json.RawMessage { + simulation.RandomSetGenesis(r, mapp, accs, []string{"stake"}) return json.RawMessage("{}") } - setup := func(r *rand.Rand, privKeys []crypto.PrivKey) { + setup := func(r *rand.Rand, accs []simulation.Account) { ctx := mapp.NewContext(false, abci.Header{}) stake.InitGenesis(ctx, stakeKeeper, stake.DefaultGenesisState()) gov.InitGenesis(ctx, govKeeper, gov.DefaultGenesisState()) @@ -56,10 +56,10 @@ func TestGovWithRandomMessages(t *testing.T) { // Test with unscheduled votes simulation.Simulate( t, mapp.BaseApp, appStateFn, - []simulation.Operation{ - SimulateMsgSubmitProposal(govKeeper, stakeKeeper), - SimulateMsgDeposit(govKeeper, stakeKeeper), - SimulateMsgVote(govKeeper, stakeKeeper), + []simulation.WeightedOperation{ + {2, SimulateMsgSubmitProposal(govKeeper, stakeKeeper)}, + {3, SimulateMsgDeposit(govKeeper, stakeKeeper)}, + {20, SimulateMsgVote(govKeeper, stakeKeeper)}, }, []simulation.RandSetup{ setup, }, []simulation.Invariant{ @@ -71,9 +71,9 @@ func TestGovWithRandomMessages(t *testing.T) { // Test with scheduled votes simulation.Simulate( t, mapp.BaseApp, appStateFn, - []simulation.Operation{ - SimulateSubmittingVotingAndSlashingForProposal(govKeeper, stakeKeeper), - SimulateMsgDeposit(govKeeper, stakeKeeper), + []simulation.WeightedOperation{ + {10, SimulateSubmittingVotingAndSlashingForProposal(govKeeper, stakeKeeper)}, + {5, SimulateMsgDeposit(govKeeper, stakeKeeper)}, }, []simulation.RandSetup{ setup, }, []simulation.Invariant{ diff --git a/x/gov/tally.go b/x/gov/tally.go index a756eaf92..c5751258a 100644 --- a/x/gov/tally.go +++ b/x/gov/tally.go @@ -13,7 +13,7 @@ type validatorGovInfo struct { Vote VoteOption // Vote of the validator } -func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tallyResults TallyResult, nonVoting []sdk.ValAddress) { +func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tallyResults TallyResult) { results := make(map[VoteOption]sdk.Dec) results[OptionYes] = sdk.ZeroDec() results[OptionAbstain] = sdk.ZeroDec() @@ -53,10 +53,10 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall valAddrStr := delegation.GetValidator().String() if val, ok := currValidators[valAddrStr]; ok { - val.Minus = val.Minus.Add(delegation.GetBondShares()) + val.Minus = val.Minus.Add(delegation.GetShares()) currValidators[valAddrStr] = val - delegatorShare := delegation.GetBondShares().Quo(val.DelegatorShares) + delegatorShare := delegation.GetShares().Quo(val.DelegatorShares) votingPower := val.Power.Mul(delegatorShare) results[vote.Option] = results[vote.Option].Add(votingPower) @@ -70,12 +70,9 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall keeper.deleteVote(ctx, vote.ProposalID, vote.Voter) } - // iterate over the validators again to tally their voting power and see - // who didn't vote - nonVoting = []sdk.ValAddress{} + // iterate over the validators again to tally their voting power for _, val := range currValidators { if val.Vote == OptionEmpty { - nonVoting = append(nonVoting, val.Address) continue } @@ -98,19 +95,17 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall // If no one votes, proposal fails if totalVotingPower.Sub(results[OptionAbstain]).Equal(sdk.ZeroDec()) { - return false, tallyResults, nonVoting + return false, tallyResults } // If more than 1/3 of voters veto, proposal fails if results[OptionNoWithVeto].Quo(totalVotingPower).GT(tallyingProcedure.Veto) { - return false, tallyResults, nonVoting + 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) { - return true, tallyResults, nonVoting + return true, tallyResults } // If more than 1/2 of non-abstaining voters vote No, proposal fails - SortValAddresses(nonVoting) - - return false, tallyResults, nonVoting + return false, tallyResults } diff --git a/x/gov/tally_test.go b/x/gov/tally_test.go index 6c6d64aa6..28fe0cb59 100644 --- a/x/gov/tally_test.go +++ b/x/gov/tally_test.go @@ -15,13 +15,19 @@ import ( var ( pubkeys = []crypto.PubKey{ed25519.GenPrivKey().PubKey(), ed25519.GenPrivKey().PubKey(), ed25519.GenPrivKey().PubKey()} + + testDescription = stake.NewDescription("T", "E", "S", "T") + testCommissionMsg = stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) ) func createValidators(t *testing.T, stakeHandler sdk.Handler, ctx sdk.Context, addrs []sdk.ValAddress, coinAmt []int64) { require.True(t, len(addrs) <= len(pubkeys), "Not enough pubkeys specified at top of file.") - dummyDescription := stake.NewDescription("T", "E", "S", "T") + for i := 0; i < len(addrs); i++ { - valCreateMsg := stake.NewMsgCreateValidator(addrs[i], pubkeys[i], sdk.NewInt64Coin("steak", coinAmt[i]), dummyDescription) + valCreateMsg := stake.NewMsgCreateValidator( + addrs[i], pubkeys[i], sdk.NewInt64Coin("steak", coinAmt[i]), testDescription, testCommissionMsg, + ) + res := stakeHandler(ctx, valCreateMsg) require.True(t, res.IsOK()) } @@ -45,7 +51,7 @@ func TestTallyNoOneVotes(t *testing.T) { proposal.SetStatus(StatusVotingPeriod) keeper.SetProposal(ctx, proposal) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.True(t, tallyResults.Equals(EmptyTallyResult())) @@ -74,7 +80,7 @@ func TestTallyOnlyValidatorsAllYes(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.True(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -103,7 +109,7 @@ func TestTallyOnlyValidators51No(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo) require.Nil(t, err) - passes, _, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) } @@ -133,7 +139,7 @@ func TestTallyOnlyValidators51Yes(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.True(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -164,7 +170,7 @@ func TestTallyOnlyValidatorsVetoed(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNoWithVeto) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -195,7 +201,7 @@ func TestTallyOnlyValidatorsAbstainPasses(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.True(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -226,7 +232,7 @@ func TestTallyOnlyValidatorsAbstainFails(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -255,11 +261,9 @@ func TestTallyOnlyValidatorsNonVoter(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) require.Nil(t, err) - passes, tallyResults, nonVoting := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) - require.Equal(t, 1, len(nonVoting)) - require.Equal(t, sdk.ValAddress(addrs[0]), nonVoting[0]) require.False(t, tallyResults.Equals(EmptyTallyResult())) } @@ -293,7 +297,7 @@ func TestTallyDelgatorOverride(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[3], OptionNo) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -327,10 +331,9 @@ func TestTallyDelgatorInherit(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes) require.Nil(t, err) - passes, tallyResults, nonVoting := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.True(t, passes) - require.Equal(t, 0, len(nonVoting)) require.False(t, tallyResults.Equals(EmptyTallyResult())) } @@ -366,7 +369,7 @@ func TestTallyDelgatorMultipleOverride(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[3], OptionNo) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -378,20 +381,18 @@ func TestTallyDelgatorMultipleInherit(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) stakeHandler := stake.NewHandler(sk) - dummyDescription := stake.NewDescription("T", "E", "S", "T") - val1CreateMsg := stake.NewMsgCreateValidator( - sdk.ValAddress(addrs[0]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin("steak", 25), dummyDescription, + sdk.ValAddress(addrs[0]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin("steak", 25), testDescription, testCommissionMsg, ) stakeHandler(ctx, val1CreateMsg) val2CreateMsg := stake.NewMsgCreateValidator( - sdk.ValAddress(addrs[1]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin("steak", 6), dummyDescription, + sdk.ValAddress(addrs[1]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin("steak", 6), testDescription, testCommissionMsg, ) stakeHandler(ctx, val2CreateMsg) val3CreateMsg := stake.NewMsgCreateValidator( - sdk.ValAddress(addrs[2]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin("steak", 7), dummyDescription, + sdk.ValAddress(addrs[2]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin("steak", 7), testDescription, testCommissionMsg, ) stakeHandler(ctx, val3CreateMsg) @@ -413,7 +414,7 @@ func TestTallyDelgatorMultipleInherit(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -440,7 +441,7 @@ func TestTallyJailedValidator(t *testing.T) { val2, found := sk.GetValidator(ctx, sdk.ValAddress(addrs[1])) require.True(t, found) - sk.Jail(ctx, val2.ConsPubKey) + sk.Jail(ctx, sdk.ConsAddress(val2.ConsPubKey.Address())) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -454,7 +455,7 @@ func TestTallyJailedValidator(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.True(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) diff --git a/x/gov/test_common.go b/x/gov/test_common.go index 502cfbbf0..fdede91eb 100644 --- a/x/gov/test_common.go +++ b/x/gov/test_common.go @@ -23,23 +23,24 @@ import ( func getMockApp(t *testing.T, numGenAccs int) (*mock.App, Keeper, stake.Keeper, []sdk.AccAddress, []crypto.PubKey, []crypto.PrivKey) { mapp := mock.NewApp() - stake.RegisterWire(mapp.Cdc) - RegisterWire(mapp.Cdc) + stake.RegisterCodec(mapp.Cdc) + RegisterCodec(mapp.Cdc) keyGlobalParams := sdk.NewKVStoreKey("params") keyStake := sdk.NewKVStoreKey("stake") + tkeyStake := sdk.NewTransientStoreKey("transient_stake") keyGov := sdk.NewKVStoreKey("gov") pk := params.NewKeeper(mapp.Cdc, keyGlobalParams) - ck := bank.NewKeeper(mapp.AccountMapper) - sk := stake.NewKeeper(mapp.Cdc, keyStake, ck, mapp.RegisterCodespace(stake.DefaultCodespace)) + ck := bank.NewBaseKeeper(mapp.AccountMapper) + sk := stake.NewKeeper(mapp.Cdc, keyStake, tkeyStake, ck, mapp.RegisterCodespace(stake.DefaultCodespace)) keeper := NewKeeper(mapp.Cdc, keyGov, pk.Setter(), ck, sk, DefaultCodespace) mapp.Router().AddRoute("gov", NewHandler(keeper)) mapp.SetEndBlocker(getEndBlocker(keeper)) mapp.SetInitChainer(getInitChainer(mapp, keeper, sk)) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyStake, keyGov, keyGlobalParams})) + require.NoError(t, mapp.CompleteSetup(keyStake, keyGov, keyGlobalParams, tkeyStake)) genAccs, addrs, pubKeys, privKeys := mock.CreateGenAccounts(numGenAccs, sdk.Coins{sdk.NewInt64Coin("steak", 42)}) diff --git a/x/ibc/app_test.go b/x/ibc/app_test.go index eed7c982c..6806d9779 100644 --- a/x/ibc/app_test.go +++ b/x/ibc/app_test.go @@ -18,13 +18,13 @@ import ( func getMockApp(t *testing.T) *mock.App { mapp := mock.NewApp() - RegisterWire(mapp.Cdc) + RegisterCodec(mapp.Cdc) keyIBC := sdk.NewKVStoreKey("ibc") ibcMapper := NewMapper(mapp.Cdc, keyIBC, mapp.RegisterCodespace(DefaultCodespace)) - bankKeeper := bank.NewKeeper(mapp.AccountMapper) + bankKeeper := bank.NewBaseKeeper(mapp.AccountMapper) mapp.Router().AddRoute("ibc", NewHandler(ibcMapper, bankKeeper)) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyIBC})) + require.NoError(t, mapp.CompleteSetup(keyIBC)) return mapp } diff --git a/x/ibc/client/cli/ibctx.go b/x/ibc/client/cli/ibctx.go index 49494a218..f58b17879 100644 --- a/x/ibc/client/cli/ibctx.go +++ b/x/ibc/client/cli/ibctx.go @@ -7,8 +7,8 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" + codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" 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/ibc" @@ -24,7 +24,7 @@ const ( ) // IBCTransferCmd implements the IBC transfer command. -func IBCTransferCmd(cdc *wire.Codec) *cobra.Command { +func IBCTransferCmd(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "transfer", RunE: func(cmd *cobra.Command, args []string) error { @@ -47,7 +47,7 @@ func IBCTransferCmd(cdc *wire.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/ibc/client/cli/relay.go b/x/ibc/client/cli/relay.go index e93806974..ab7168aca 100644 --- a/x/ibc/client/cli/relay.go +++ b/x/ibc/client/cli/relay.go @@ -6,8 +6,8 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/keys" + codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" @@ -28,7 +28,7 @@ const ( ) type relayCommander struct { - cdc *wire.Codec + cdc *codec.Codec address sdk.AccAddress decoder auth.AccountDecoder mainStore string @@ -39,7 +39,7 @@ type relayCommander struct { } // IBCRelayCmd implements the IBC relay command. -func IBCRelayCmd(cdc *wire.Codec) *cobra.Command { +func IBCRelayCmd(cdc *codec.Codec) *cobra.Command { cmdr := relayCommander{ cdc: cdc, decoder: authcmd.GetAccountDecoder(cdc), @@ -91,11 +91,14 @@ func (c relayCommander) runIBCRelay(cmd *cobra.Command, args []string) { } // This is nolinted as someone is in the process of refactoring this to remove the goto -// nolint: gocyclo func (c relayCommander) loop(fromChainID, fromChainNode, toChainID, toChainNode string) { cliCtx := context.NewCLIContext() - passphrase, err := keys.ReadPassphraseFromStdin(cliCtx.FromAddressName) + name, err := cliCtx.GetFromName() + if err != nil { + panic(err) + } + passphrase, err := keys.ReadPassphraseFromStdin(name) if err != nil { panic(err) } @@ -202,7 +205,12 @@ func (c relayCommander) refine(bz []byte, sequence int64, passphrase string) []b txBldr := authtxb.NewTxBuilderFromCLI().WithSequence(sequence).WithCodec(c.cdc) cliCtx := context.NewCLIContext() - res, err := txBldr.BuildAndSign(cliCtx.FromAddressName, passphrase, []sdk.Msg{msg}) + name, err := cliCtx.GetFromName() + if err != nil { + panic(err) + } + + res, err := txBldr.BuildAndSign(name, passphrase, []sdk.Msg{msg}) if err != nil { panic(err) } diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index 110efe601..19c921971 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -1,126 +1,65 @@ package rest import ( - "io/ioutil" "net/http" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/ibc" "github.com/gorilla/mux" ) // RegisterRoutes - Central function to define routes that get registered by the main application -func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, kb keys.Keybase) { r.HandleFunc("/ibc/{destchain}/{address}/send", TransferRequestHandlerFn(cdc, kb, cliCtx)).Methods("POST") } -type transferBody struct { - // Fees sdk.Coin `json="fees"` - Amount sdk.Coins `json:"amount"` - LocalAccountName string `json:"name"` - Password string `json:"password"` - SrcChainID string `json:"src_chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas int64 `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` +type transferReq struct { + BaseReq utils.BaseReq `json:"base_req"` + Amount sdk.Coins `json:"amount"` } // TransferRequestHandler - http request handler to transfer coins to a address -// on a different chain via IBC -// nolint: gocyclo -func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { +// on a different chain via IBC. +func TransferRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) destChainID := vars["destchain"] - bech32addr := vars["address"] + bech32Addr := vars["address"] - to, err := sdk.AccAddressFromBech32(bech32addr) + to, err := sdk.AccAddressFromBech32(bech32Addr) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - var m transferBody - body, err := ioutil.ReadAll(r.Body) + var req transferReq + err = utils.ReadRESTReq(w, r, cdc, &req) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - err = cdc.UnmarshalJSON(body, &m) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { return } - info, err := kb.Get(m.LocalAccountName) + info, err := kb.Get(baseReq.Name) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } - // build message - packet := ibc.NewIBCPacket(sdk.AccAddress(info.GetPubKey().Address()), to, m.Amount, m.SrcChainID, destChainID) - msg := ibc.IBCTransferMsg{packet} + packet := ibc.NewIBCPacket( + sdk.AccAddress(info.GetPubKey().Address()), to, + req.Amount, baseReq.ChainID, destChainID, + ) + msg := ibc.IBCTransferMsg{IBCPacket: packet} - txBldr := authtxb.TxBuilder{ - Codec: cdc, - ChainID: m.SrcChainID, - AccountNumber: m.AccountNumber, - Sequence: m.Sequence, - Gas: m.Gas, - } - - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) - if !ok { - return - } - cliCtx = cliCtx.WithGasAdjustment(adjustment) - - if utils.HasDryRunArg(r) || m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, txBldr.Gas) - return - } - txBldr = newCtx - } - - if utils.HasGenerateOnlyArg(r) { - utils.WriteGenerateStdTxResponse(w, txBldr, []sdk.Msg{msg}) - return - } - - txBytes, err := txBldr.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - output, err := cdc.MarshalJSON(res) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(output) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/ibc/wire.go b/x/ibc/codec.go similarity index 59% rename from x/ibc/wire.go rename to x/ibc/codec.go index f5644acc5..43ffeb519 100644 --- a/x/ibc/wire.go +++ b/x/ibc/codec.go @@ -1,11 +1,11 @@ package ibc import ( - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" ) -// Register concrete types on wire codec -func RegisterWire(cdc *wire.Codec) { +// Register concrete types on codec codec +func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(IBCTransferMsg{}, "cosmos-sdk/IBCTransferMsg", nil) cdc.RegisterConcrete(IBCReceiveMsg{}, "cosmos-sdk/IBCReceiveMsg", nil) } diff --git a/x/ibc/ibc_test.go b/x/ibc/ibc_test.go index 88718b4a2..c8e7492be 100644 --- a/x/ibc/ibc_test.go +++ b/x/ibc/ibc_test.go @@ -10,9 +10,9 @@ import ( 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/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" ) @@ -38,8 +38,8 @@ func getCoins(ck bank.Keeper, ctx sdk.Context, addr sdk.AccAddress) (sdk.Coins, return coins, err } -func makeCodec() *wire.Codec { - var cdc = wire.NewCodec() +func makeCodec() *codec.Codec { + var cdc = codec.New() // Register Msgs cdc.RegisterInterface((*sdk.Msg)(nil), nil) @@ -51,7 +51,7 @@ func makeCodec() *wire.Codec { // Register AppAccount cdc.RegisterInterface((*auth.Account)(nil), nil) cdc.RegisterConcrete(&auth.BaseAccount{}, "test/ibc/Account", nil) - wire.RegisterCrypto(cdc) + codec.RegisterCrypto(cdc) cdc.Seal() @@ -65,7 +65,7 @@ func TestIBC(t *testing.T) { ctx := defaultContext(key) am := auth.NewAccountMapper(cdc, key, auth.ProtoBaseAccount) - ck := bank.NewKeeper(am) + ck := bank.NewBaseKeeper(am) src := newAddress() dest := newAddress() diff --git a/x/ibc/mapper.go b/x/ibc/mapper.go index 251699617..95c88b62f 100644 --- a/x/ibc/mapper.go +++ b/x/ibc/mapper.go @@ -3,20 +3,20 @@ package ibc import ( "fmt" + codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" ) // IBC Mapper type Mapper struct { key sdk.StoreKey - cdc *wire.Codec + cdc *codec.Codec codespace sdk.CodespaceType } // XXX: The Mapper should not take a CoinKeeper. Rather have the CoinKeeper // take an Mapper. -func NewMapper(cdc *wire.Codec, key sdk.StoreKey, codespace sdk.CodespaceType) Mapper { +func NewMapper(cdc *codec.Codec, key sdk.StoreKey, codespace sdk.CodespaceType) Mapper { // XXX: How are these codecs supposed to work? return Mapper{ key: key, @@ -60,7 +60,7 @@ func (ibcm Mapper) ReceiveIBCPacket(ctx sdk.Context, packet IBCPacket) sdk.Error // -------------------------- // Functions for accessing the underlying KVStore. -func marshalBinaryPanic(cdc *wire.Codec, value interface{}) []byte { +func marshalBinaryPanic(cdc *codec.Codec, value interface{}) []byte { res, err := cdc.MarshalBinary(value) if err != nil { panic(err) @@ -68,7 +68,7 @@ func marshalBinaryPanic(cdc *wire.Codec, value interface{}) []byte { return res } -func unmarshalBinaryPanic(cdc *wire.Codec, bz []byte, ptr interface{}) { +func unmarshalBinaryPanic(cdc *codec.Codec, bz []byte, ptr interface{}) { err := cdc.UnmarshalBinary(bz, ptr) if err != nil { panic(err) diff --git a/x/ibc/types.go b/x/ibc/types.go index b3fe6fc39..64b7d1f4c 100644 --- a/x/ibc/types.go +++ b/x/ibc/types.go @@ -3,16 +3,16 @@ package ibc import ( "encoding/json" + codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" ) var ( - msgCdc *wire.Codec + msgCdc *codec.Codec ) func init() { - msgCdc = wire.NewCodec() + msgCdc = codec.New() } // ------------------------------ @@ -72,6 +72,7 @@ type IBCTransferMsg struct { // nolint func (msg IBCTransferMsg) Type() string { return "ibc" } +func (msg IBCTransferMsg) Name() string { return "transfer" } // x/bank/tx.go MsgSend.GetSigners() func (msg IBCTransferMsg) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.SrcAddr} } @@ -100,6 +101,7 @@ type IBCReceiveMsg struct { // nolint func (msg IBCReceiveMsg) Type() string { return "ibc" } +func (msg IBCReceiveMsg) Name() string { return "receive" } func (msg IBCReceiveMsg) ValidateBasic() sdk.Error { return msg.IBCPacket.ValidateBasic() } // x/bank/tx.go MsgSend.GetSigners() diff --git a/x/mock/app.go b/x/mock/app.go index 97068a3a5..1fe411fbc 100644 --- a/x/mock/app.go +++ b/x/mock/app.go @@ -1,12 +1,13 @@ package mock import ( + "fmt" "math/rand" "os" 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/wire" "github.com/cosmos/cosmos-sdk/x/auth" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" @@ -23,7 +24,7 @@ const chainID = "" // capabilities aren't needed for testing. type App struct { *bam.BaseApp - Cdc *wire.Codec // Cdc is public since the codec is passed into the module anyways + Cdc *codec.Codec // Cdc is public since the codec is passed into the module anyways KeyMain *sdk.KVStoreKey KeyAccount *sdk.KVStoreKey @@ -42,10 +43,10 @@ func NewApp() *App { db := dbm.NewMemDB() // Create the cdc with some standard codecs - cdc := wire.NewCodec() - sdk.RegisterWire(cdc) - wire.RegisterCrypto(cdc) - auth.RegisterWire(cdc) + cdc := codec.New() + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + auth.RegisterCodec(cdc) // Create your application object app := &App{ @@ -75,11 +76,21 @@ func NewApp() *App { // CompleteSetup completes the application setup after the routes have been // registered. -func (app *App) CompleteSetup(newKeys []*sdk.KVStoreKey) error { +func (app *App) CompleteSetup(newKeys ...sdk.StoreKey) error { newKeys = append(newKeys, app.KeyMain) newKeys = append(newKeys, app.KeyAccount) - app.MountStoresIAVL(newKeys...) + for _, key := range newKeys { + switch key.(type) { + case *sdk.KVStoreKey: + app.MountStore(key, sdk.StoreTypeIAVL) + case *sdk.TransientStoreKey: + app.MountStore(key, sdk.StoreTypeTransient) + default: + return fmt.Errorf("unsupported StoreKey: %+v", key) + } + } + err := app.LoadLatestVersion(app.KeyMain) return err diff --git a/x/mock/app_test.go b/x/mock/app_test.go index 460757a04..d48a6ba14 100644 --- a/x/mock/app_test.go +++ b/x/mock/app_test.go @@ -24,6 +24,7 @@ type testMsg struct { } func (tx testMsg) Type() string { return msgType } +func (tx testMsg) Name() 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 +42,7 @@ func getMockApp(t *testing.T) *App { mApp := NewApp() mApp.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) - require.NoError(t, mApp.CompleteSetup([]*sdk.KVStoreKey{})) + require.NoError(t, mApp.CompleteSetup()) return mApp } diff --git a/x/mock/doc.go b/x/mock/doc.go index d23aac393..139cd87a5 100644 --- a/x/mock/doc.go +++ b/x/mock/doc.go @@ -1,15 +1,4 @@ /* -Package mock provides functions for creating applications for testing. - -This module also features randomized testing, so that various modules can test -that their operations are interoperable. - -The intended method of using this randomized testing framework is that every -module provides TestAndRunTx methods for each of its desired methods of fuzzing -its own txs, and it also provides the invariants that it assumes to be true. -You then pick and choose from these tx types and invariants. To pick and choose -these, you first build a mock app with the correct keepers. Then you call the -app.RandomizedTesting method with the set of desired txs, invariants, along -with the setups each module requires. +Package mock provides utility methods to ease writing tests. */ package mock diff --git a/x/mock/simulation/constants.go b/x/mock/simulation/constants.go index 985a22dca..544da50e3 100644 --- a/x/mock/simulation/constants.go +++ b/x/mock/simulation/constants.go @@ -5,10 +5,10 @@ const ( pastEvidenceFraction float64 = 0.5 // Minimum time per block - minTimePerBlock int64 = 86400 / 2 + minTimePerBlock int64 = 1000 / 2 // Maximum time per block - maxTimePerBlock int64 = 86400 + maxTimePerBlock int64 = 1000 // Number of keys numKeys int = 250 diff --git a/x/mock/simulation/doc.go b/x/mock/simulation/doc.go new file mode 100644 index 000000000..8b9a3f693 --- /dev/null +++ b/x/mock/simulation/doc.go @@ -0,0 +1,27 @@ +/* +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, + +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. + +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. +*/ +package simulation diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index fc010d76a..3c9e676a9 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -7,32 +7,33 @@ import ( "math/rand" "os" "os/signal" + "runtime/debug" "sort" + "strings" "syscall" "testing" "time" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/mock" ) // Simulate tests application by sending random messages. -func Simulate( - t *testing.T, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, ops []Operation, setups []RandSetup, - invariants []Invariant, numBlocks int, blockSize int, commit bool, -) { +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() - SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numBlocks, blockSize, commit) + return SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numBlocks, blockSize, commit) } -func initChain(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress, setups []RandSetup, app *baseapp.BaseApp, - appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage) (validators map[string]mockValidator) { - res := app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, keys, accs)}) +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 { pubkey, err := tmtypes.PB2TM.PubKey(validator.PubKey) @@ -44,7 +45,7 @@ func initChain(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress, setup } for i := 0; i < len(setups); i++ { - setups[i](r, keys) + setups[i](r, accounts) } return @@ -57,119 +58,157 @@ func randTimestamp(r *rand.Rand) time.Time { // 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, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, seed int64, ops []Operation, setups []RandSetup, - invariants []Invariant, numBlocks int, blockSize int, commit bool, -) { +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) - log := fmt.Sprintf("Starting SimulateFromSeed with randomness created with seed %d", int(seed)) + fmt.Printf("Starting SimulateFromSeed with randomness created with seed %d\n", int(seed)) r := rand.New(rand.NewSource(seed)) timestamp := randTimestamp(r) - log = updateLog(testingMode, log, "Starting the simulation from time %v, unixtime %v", timestamp.UTC().Format(time.UnixDate), timestamp.Unix()) - fmt.Printf("%s\n", log) + fmt.Printf("Starting the simulation from time %v, unixtime %v\n", timestamp.UTC().Format(time.UnixDate), timestamp.Unix()) timeDiff := maxTimePerBlock - minTimePerBlock - keys, accs := mock.GeneratePrivKeyAddressPairsFromRand(r, numKeys) + accs := RandomAccounts(r, numKeys) // Setup event stats events := make(map[string]uint) event := func(what string) { - log = updateLog(testingMode, log, "event - %s", what) events[what]++ } - validators := initChain(r, keys, accs, setups, app, appStateFn) + validators := initChain(r, accs, setups, app, appStateFn) 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) + signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) go func() { - <-c - fmt.Printf("Exiting early due to SIGTERM, on block %d, operation %d\n", header.Height, opCount) - DisplayEvents(events) - os.Exit(128 + int(syscall.SIGTERM)) + 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, log) + 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) + } + }() } - blockSimulator := createBlockSimulator(testingMode, tb, t, event, invariants, ops, operationQueue, numBlocks) - for i := 0; i < numBlocks; i++ { + 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) - log = updateLog(testingMode, log, "BeginBlock") if testingMode { // Make sure invariants hold at beginning of block - AssertAllInvariants(t, app, invariants, log) + 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 - log, numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), tb, r, app, ctx, keys, log, event) - opCount += numQueuedOpsRan - thisBlockSize -= numQueuedOpsRan - log, operations := blockSimulator(thisBlockSize, r, app, ctx, keys, log, header) - opCount += operations + 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) - log = updateLog(testingMode, log, "EndBlock") + logWriter("EndBlock") if testingMode { // Make sure invariants hold at end of block - AssertAllInvariants(t, app, invariants, log) + 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, log) + request = RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastVoteInfos, event, header) // Update the validator set validators = 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 []Operation, operationQueue map[int][]Operation, totalNumBlocks int) func( - blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, privKeys []crypto.PrivKey, log string, header abci.Header) (updatedLog string, opCount int) { - return func(blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - keys []crypto.PrivKey, log string, header abci.Header) (updatedLog string, opCount int) { - for j := 0; j < blocksize; j++ { - logUpdate, futureOps, err := ops[r.Intn(len(ops))](tb, r, app, ctx, keys, log, event) - log = updateLog(testingMode, log, logUpdate) - if err != nil { - tb.Fatalf("error on operation %d within block %d, %v, log %s", header.Height, opCount, err, log) +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, futureOps) + queueOperations(operationQueue, timeOperationQueue, futureOps) if testingMode { if onOperation { - AssertAllInvariants(t, app, invariants, log) + assertAllInvariants(t, app, invariants, displayLogs) } if opCount%50 == 0 { fmt.Printf("\rSimulating... block %d/%d, operation %d/%d. ", header.Height, totalNumBlocks, opCount, blocksize) @@ -177,7 +216,7 @@ func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, event f } opCount++ } - return log, opCount + return opCount } } @@ -192,14 +231,6 @@ func getTestingMode(tb testing.TB) (testingMode bool, t *testing.T, b *testing.B return } -func updateLog(testingMode bool, log string, update string, args ...interface{}) (updatedLog string) { - if testingMode { - update = fmt.Sprintf(update, args...) - return fmt.Sprintf("%s\n%s", log, update) - } - return "" -} - func getBlockSize(r *rand.Rand, blockSize int) int { load := r.Float64() switch { @@ -213,40 +244,67 @@ func getBlockSize(r *rand.Rand, blockSize int) int { } // adds all future operations into the operation queue. -func queueOperations(queuedOperations map[int][]Operation, futureOperations []FutureOperation) { +func queueOperations(queuedOperations map[int][]Operation, queuedTimeOperations []FutureOperation, futureOperations []FutureOperation) { if futureOperations == nil { return } for _, futureOp := range futureOperations { - if val, ok := queuedOperations[futureOp.BlockHeight]; ok { - queuedOperations[futureOp.BlockHeight] = append(val, futureOp.Op) + 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 { - queuedOperations[futureOp.BlockHeight] = []Operation{futureOp.Op} + // 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, - privKeys []crypto.PrivKey, log string, event func(string)) (updatedLog string, numOpsRan int) { - updatedLog = log + 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](tb, r, app, ctx, privKeys, updatedLog, event) - updatedLog = fmt.Sprintf("%s\n%s", updatedLog, logUpdate) + logUpdate, _, err := queuedOps[i](r, app, ctx, accounts, event) + logWriter(logUpdate) if err != nil { - fmt.Fprint(os.Stderr, updatedLog) + displayLogs() tb.FailNow() } } delete(queueOperations, height) - return updatedLog, numOps + return numOps } - return log, 0 + 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 { @@ -263,7 +321,7 @@ func getKeys(validators map[string]mockValidator) []string { // 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, log string) abci.RequestBeginBlock { + pastTimes []time.Time, pastVoteInfos [][]abci.VoteInfo, event func(string), header abci.Header) abci.RequestBeginBlock { if len(validators) == 0 { return abci.RequestBeginBlock{Header: header} } @@ -337,28 +395,16 @@ func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator, } } -// AssertAllInvariants asserts a list of provided invariants against application state -func AssertAllInvariants(t *testing.T, app *baseapp.BaseApp, tests []Invariant, log string) { - for i := 0; i < len(tests); i++ { - tests[i](t, app, log) - } -} - // 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 { switch { case update.Power == 0: - // // TEMPORARY DEBUG CODE TO PROVE THAT THE OLD METHOD WAS BROKEN - // // (i.e. didn't catch in the event of problem) - // if val, ok := tb.(*testing.T); ok { - // require.NotNil(val, current[string(update.PubKey.Data)]) - // } - // // CORRECT CHECK - // if _, ok := current[string(update.PubKey.Data)]; !ok { - // tb.Fatalf("tried to delete a nonexistent validator") - // } + if _, ok := current[string(update.PubKey.Data)]; !ok { + tb.Fatalf("tried to delete a nonexistent validator") + } + event("endblock/validatorupdates/kicked") delete(current, string(update.PubKey.Data)) default: @@ -373,5 +419,6 @@ func updateValidators(tb testing.TB, r *rand.Rand, current map[string]mockValida } } } + return current } diff --git a/x/mock/simulation/types.go b/x/mock/simulation/types.go index 6f5d1e6da..302ebcbd7 100644 --- a/x/mock/simulation/types.go +++ b/x/mock/simulation/types.go @@ -2,7 +2,7 @@ package simulation import ( "math/rand" - "testing" + "time" "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" @@ -22,18 +22,27 @@ type ( // // Operations can optionally provide a list of "FutureOperations" to run later // These will be ran at the beginning of the corresponding block. - Operation func( - tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - privKeys []crypto.PrivKey, log string, event func(string), - ) (action string, futureOperations []FutureOperation, err sdk.Error) + 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, privKeys []crypto.PrivKey) + RandSetup func(r *rand.Rand, accounts []Account) // An Invariant is a function which tests a particular invariant. - // If the invariant has been broken, the function should halt the - // test and output the log. - Invariant func(t *testing.T, app *baseapp.BaseApp, log string) + // 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 @@ -42,21 +51,36 @@ type ( // 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(t *testing.T, app *baseapp.BaseApp, log string) { + return func(app *baseapp.BaseApp) error { if int(app.LastBlockHeight())%period == offset { - invariant(t, app, log) + 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 1d64ba30d..54bfabc73 100644 --- a/x/mock/simulation/util.go +++ b/x/mock/simulation/util.go @@ -3,11 +3,18 @@ package simulation import ( "fmt" "math/rand" + "os" "sort" + "strings" + "testing" + "time" - "github.com/tendermint/tendermint/crypto" + "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 @@ -51,10 +58,10 @@ func DisplayEvents(events map[string]uint) { } } -// Pick a random key from an array -func RandomKey(r *rand.Rand, keys []crypto.PrivKey) crypto.PrivKey { - return keys[r.Intn( - len(keys), +// RandomAcc pick a random account from an array +func RandomAcc(r *rand.Rand, accs []Account) Account { + return accs[r.Intn( + len(accs), )] } @@ -62,3 +69,93 @@ func RandomKey(r *rand.Rand, keys []crypto.PrivKey) crypto.PrivKey { func RandomAmount(r *rand.Rand, max sdk.Int) sdk.Int { return sdk.NewInt(int64(r.Intn(int(max.Int64())))) } + +// 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 +} + +// 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") + } + } + 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() + } + } +} + +// 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) +} + +// 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()) + } + } + } + } + return func() {} +} diff --git a/x/params/keeper.go b/x/params/keeper.go index 7fd9bb3c9..ca158ad42 100644 --- a/x/params/keeper.go +++ b/x/params/keeper.go @@ -4,18 +4,18 @@ import ( "fmt" "reflect" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" ) // Keeper manages global parameter store type Keeper struct { - cdc *wire.Codec + cdc *codec.Codec key sdk.StoreKey } // NewKeeper constructs a new Keeper -func NewKeeper(cdc *wire.Codec, key sdk.StoreKey) Keeper { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey) Keeper { return Keeper{ cdc: cdc, key: key, @@ -24,7 +24,7 @@ func NewKeeper(cdc *wire.Codec, key sdk.StoreKey) Keeper { // InitKeeper constructs a new Keeper with initial parameters // nolint: errcheck -func InitKeeper(ctx sdk.Context, cdc *wire.Codec, key sdk.StoreKey, params ...interface{}) Keeper { +func InitKeeper(ctx sdk.Context, cdc *codec.Codec, key sdk.StoreKey, params ...interface{}) Keeper { if len(params)%2 != 0 { panic("Odd params list length for InitKeeper") } diff --git a/x/params/keeper_test.go b/x/params/keeper_test.go index 626d68c7d..7eac7319f 100644 --- a/x/params/keeper_test.go +++ b/x/params/keeper_test.go @@ -9,9 +9,9 @@ import ( 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/wire" ) func defaultContext(key sdk.StoreKey) sdk.Context { @@ -37,7 +37,7 @@ func TestKeeper(t *testing.T) { skey := sdk.NewKVStoreKey("test") ctx := defaultContext(skey) - setter := NewKeeper(wire.NewCodec(), skey).Setter() + setter := NewKeeper(codec.New(), skey).Setter() for _, kv := range kvs { err := setter.Set(ctx, kv.key, kv.param) @@ -51,7 +51,7 @@ func TestKeeper(t *testing.T) { assert.Equal(t, kv.param, param) } - cdc := wire.NewCodec() + cdc := codec.New() for _, kv := range kvs { var param int64 bz := setter.GetRaw(ctx, kv.key) @@ -75,7 +75,7 @@ func TestKeeper(t *testing.T) { func TestGetter(t *testing.T) { key := sdk.NewKVStoreKey("test") ctx := defaultContext(key) - keeper := NewKeeper(wire.NewCodec(), key) + keeper := NewKeeper(codec.New(), key) g := keeper.Getter() s := keeper.Setter() diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index e0b84cef7..a3370b1d8 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -24,13 +24,14 @@ var ( func getMockApp(t *testing.T) (*mock.App, stake.Keeper, Keeper) { mapp := mock.NewApp() - RegisterWire(mapp.Cdc) + RegisterCodec(mapp.Cdc) keyStake := sdk.NewKVStoreKey("stake") + tkeyStake := sdk.NewTransientStoreKey("transient_stake") keySlashing := sdk.NewKVStoreKey("slashing") keyParams := sdk.NewKVStoreKey("params") - bankKeeper := bank.NewKeeper(mapp.AccountMapper) + bankKeeper := bank.NewBaseKeeper(mapp.AccountMapper) paramsKeeper := params.NewKeeper(mapp.Cdc, keyParams) - stakeKeeper := stake.NewKeeper(mapp.Cdc, keyStake, bankKeeper, mapp.RegisterCodespace(stake.DefaultCodespace)) + stakeKeeper := stake.NewKeeper(mapp.Cdc, keyStake, tkeyStake, bankKeeper, mapp.RegisterCodespace(stake.DefaultCodespace)) keeper := NewKeeper(mapp.Cdc, keySlashing, stakeKeeper, paramsKeeper.Getter(), mapp.RegisterCodespace(DefaultCodespace)) mapp.Router().AddRoute("stake", stake.NewHandler(stakeKeeper)) @@ -38,7 +39,7 @@ func getMockApp(t *testing.T) (*mock.App, stake.Keeper, Keeper) { mapp.SetEndBlocker(getEndBlocker(stakeKeeper)) mapp.SetInitChainer(getInitChainer(mapp, stakeKeeper)) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyStake, keySlashing, keyParams})) + require.NoError(t, mapp.CompleteSetup(keyStake, keySlashing, keyParams, tkeyStake)) return mapp, stakeKeeper, keeper } @@ -98,9 +99,12 @@ func TestSlashingMsgs(t *testing.T) { } accs := []auth.Account{acc1} mock.SetGenesis(mapp, accs) + description := stake.NewDescription("foo_moniker", "", "", "") + commission := stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + createValidatorMsg := stake.NewMsgCreateValidator( - sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, + sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, commission, ) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) diff --git a/x/slashing/client/cli/query.go b/x/slashing/client/cli/query.go index 87d0ad41d..50ccc6c0e 100644 --- a/x/slashing/client/cli/query.go +++ b/x/slashing/client/cli/query.go @@ -8,13 +8,13 @@ import ( "github.com/tendermint/tendermint/libs/cli" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" // XXX fix sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" // XXX fix "github.com/cosmos/cosmos-sdk/x/slashing" ) // GetCmdQuerySigningInfo implements the command to query signing info. -func GetCmdQuerySigningInfo(storeName string, cdc *wire.Codec) *cobra.Command { +func GetCmdQuerySigningInfo(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "signing-info [validator-pubkey]", Short: "Query a validator's signing information", @@ -44,7 +44,7 @@ func GetCmdQuerySigningInfo(storeName string, cdc *wire.Codec) *cobra.Command { case "json": // parse out the signing info - output, err := wire.MarshalJSONIndent(cdc, signingInfo) + output, err := codec.MarshalJSONIndent(cdc, signingInfo) if err != nil { return err } diff --git a/x/slashing/client/cli/tx.go b/x/slashing/client/cli/tx.go index 1810ffeed..ad3646ea1 100644 --- a/x/slashing/client/cli/tx.go +++ b/x/slashing/client/cli/tx.go @@ -5,8 +5,8 @@ import ( "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/wire" 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/slashing" @@ -15,7 +15,7 @@ import ( ) // GetCmdUnjail implements the create unjail validator command. -func GetCmdUnjail(cdc *wire.Codec) *cobra.Command { +func GetCmdUnjail(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "unjail", Args: cobra.NoArgs, @@ -36,7 +36,7 @@ func GetCmdUnjail(cdc *wire.Codec) *cobra.Command { if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/slashing/client/rest/query.go b/x/slashing/client/rest/query.go index 0ebe13250..e83f1a235 100644 --- a/x/slashing/client/rest/query.go +++ b/x/slashing/client/rest/query.go @@ -5,13 +5,13 @@ import ( "net/http" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/gorilla/mux" ) -func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec) { +func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) { r.HandleFunc( "/slashing/signing_info/{validator}", signingInfoHandlerFn(cliCtx, "slashing", cdc), @@ -20,7 +20,7 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Cod // http request handler to query signing info // nolint: unparam -func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *wire.Codec) http.HandlerFunc { +func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) diff --git a/x/slashing/client/rest/rest.go b/x/slashing/client/rest/rest.go index 7c2fdf905..507fcb517 100644 --- a/x/slashing/client/rest/rest.go +++ b/x/slashing/client/rest/rest.go @@ -2,14 +2,14 @@ package rest import ( "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" - "github.com/cosmos/cosmos-sdk/wire" "github.com/gorilla/mux" ) // RegisterRoutes registers staking-related REST handlers to a router -func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, kb keys.Keybase) { registerQueryRoutes(cliCtx, r, cdc) registerTxRoutes(cliCtx, r, cdc, kb) } diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index 1d4fdefa1..972d4351f 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -2,24 +2,20 @@ package rest import ( "bytes" - "encoding/json" "fmt" - "io/ioutil" "net/http" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/gorilla/mux" ) -func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { +func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, kb keys.Keybase) { r.HandleFunc( "/slashing/unjail", unjailRequestHandlerFn(cdc, kb, cliCtx), @@ -27,101 +23,45 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, } // Unjail TX body -type UnjailBody struct { - LocalAccountName string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas int64 `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` - ValidatorAddr string `json:"validator_addr"` +type UnjailReq struct { + BaseReq utils.BaseReq `json:"base_req"` + ValidatorAddr string `json:"validator_addr"` } -// nolint: gocyclo -func unjailRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { +func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var m UnjailBody - body, err := ioutil.ReadAll(r.Body) + var req UnjailReq + err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - err = json.Unmarshal(body, &m) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - info, err := kb.Get(m.LocalAccountName) + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + info, err := kb.Get(baseReq.Name) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } - valAddr, err := sdk.ValAddressFromBech32(m.ValidatorAddr) + valAddr, err := sdk.ValAddressFromBech32(req.ValidatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) + utils.WriteErrorResponse( + w, http.StatusInternalServerError, + fmt.Sprintf("failed to decode validator; error: %s", err.Error()), + ) return } if !bytes.Equal(info.GetPubKey().Address(), valAddr) { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own validator address") + utils.WriteErrorResponse(w, http.StatusUnauthorized, "must use own validator address") return } - txBldr := authtxb.TxBuilder{ - Codec: cdc, - ChainID: m.ChainID, - AccountNumber: m.AccountNumber, - Sequence: m.Sequence, - Gas: m.Gas, - } - msg := slashing.NewMsgUnjail(valAddr) - - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) - if !ok { - return - } - cliCtx = cliCtx.WithGasAdjustment(adjustment) - - if utils.HasDryRunArg(r) || m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, txBldr.Gas) - return - } - txBldr = newCtx - } - - if utils.HasGenerateOnlyArg(r) { - utils.WriteGenerateStdTxResponse(w, txBldr, []sdk.Msg{msg}) - return - } - - txBytes, err := txBldr.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own validator address") - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - output, err := json.MarshalIndent(res, "", " ") - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(output) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/slashing/codec.go b/x/slashing/codec.go new file mode 100644 index 000000000..ebe08428c --- /dev/null +++ b/x/slashing/codec.go @@ -0,0 +1,12 @@ +package slashing + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +// Register concrete types on codec codec +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(MsgUnjail{}, "cosmos-sdk/MsgUnjail", nil) +} + +var cdcEmpty = codec.New() diff --git a/x/slashing/genesis.go b/x/slashing/genesis.go index 6e2809bfc..43ae6b0d0 100644 --- a/x/slashing/genesis.go +++ b/x/slashing/genesis.go @@ -8,7 +8,7 @@ import ( // InitGenesis initializes the keeper's address to pubkey map. func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) { for _, validator := range data.Validators { - keeper.addPubkey(ctx, validator.GetPubKey()) + keeper.addPubkey(ctx, validator.GetConsPubKey()) } return } diff --git a/x/slashing/handler.go b/x/slashing/handler.go index c43ed6be6..740166d2a 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -34,9 +34,9 @@ func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { return ErrValidatorNotJailed(k.codespace).Result() } - addr := sdk.ConsAddress(validator.GetPubKey().Address()) + consAddr := sdk.ConsAddress(validator.GetConsPubKey().Address()) - info, found := k.getValidatorSigningInfo(ctx, addr) + info, found := k.getValidatorSigningInfo(ctx, consAddr) if !found { return ErrNoValidatorForAddress(k.codespace).Result() } @@ -49,9 +49,9 @@ func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { // update the starting height so the validator can't be immediately jailed // again info.StartHeight = ctx.BlockHeight() - k.setValidatorSigningInfo(ctx, addr, info) + k.setValidatorSigningInfo(ctx, consAddr, info) - k.validatorSet.Unjail(ctx, validator.GetPubKey()) + k.validatorSet.Unjail(ctx, consAddr) tags := sdk.NewTags("action", []byte("unjail"), "validator", []byte(msg.ValidatorAddr.String())) diff --git a/x/slashing/hooks.go b/x/slashing/hooks.go index f5f3cc48c..701a6b2cd 100644 --- a/x/slashing/hooks.go +++ b/x/slashing/hooks.go @@ -22,25 +22,34 @@ func (k Keeper) onValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddre k.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod) } -// Wrapper struct for sdk.ValidatorHooks -type ValidatorHooks struct { +//_________________________________________________________________________________________ + +// Wrapper struct +type Hooks struct { k Keeper } -// Assert implementation -var _ sdk.ValidatorHooks = ValidatorHooks{} +var _ sdk.StakingHooks = Hooks{} -// Return a sdk.ValidatorHooks interface over the wrapper struct -func (k Keeper) ValidatorHooks() sdk.ValidatorHooks { - return ValidatorHooks{k} +// Return the wrapper struct +func (k Keeper) Hooks() Hooks { + return Hooks{k} } // Implements sdk.ValidatorHooks -func (v ValidatorHooks) OnValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { - v.k.onValidatorBonded(ctx, address) +func (h Hooks) OnValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { + h.k.onValidatorBonded(ctx, address) } // Implements sdk.ValidatorHooks -func (v ValidatorHooks) OnValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) { - v.k.onValidatorBeginUnbonding(ctx, address) +func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) { + h.k.onValidatorBeginUnbonding(ctx, address) } + +// 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) 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/keeper.go b/x/slashing/keeper.go index 081e1c1e5..0c1c70b36 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -6,8 +6,8 @@ import ( tmtypes "github.com/tendermint/tendermint/types" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/params" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" @@ -16,7 +16,7 @@ import ( // Keeper of the slashing store type Keeper struct { storeKey sdk.StoreKey - cdc *wire.Codec + cdc *codec.Codec validatorSet sdk.ValidatorSet params params.Getter // codespace @@ -24,7 +24,7 @@ type Keeper struct { } // NewKeeper creates a slashing keeper -func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, vs sdk.ValidatorSet, params params.Getter, codespace sdk.CodespaceType) Keeper { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, vs sdk.ValidatorSet, params params.Getter, codespace sdk.CodespaceType) Keeper { keeper := Keeper{ storeKey: key, cdc: cdc, @@ -40,10 +40,10 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio logger := ctx.Logger().With("module", "x/slashing") time := ctx.BlockHeader().Time age := time.Sub(timestamp) - address := sdk.ConsAddress(addr) + consAddr := sdk.ConsAddress(addr) pubkey, err := k.getPubkey(ctx, addr) if err != nil { - panic(fmt.Sprintf("Validator address %v not found", addr)) + panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr)) } // Double sign too old @@ -59,37 +59,37 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio // Cap the amount slashed to the penalty for the worst infraction // within the slashing period when this infraction was committed fraction := k.SlashFractionDoubleSign(ctx) - revisedFraction := k.capBySlashingPeriod(ctx, address, fraction, infractionHeight) + revisedFraction := k.capBySlashingPeriod(ctx, consAddr, fraction, infractionHeight) logger.Info(fmt.Sprintf("Fraction slashed capped by slashing period from %v to %v", fraction, revisedFraction)) // Slash validator - k.validatorSet.Slash(ctx, pubkey, infractionHeight, power, revisedFraction) + k.validatorSet.Slash(ctx, consAddr, infractionHeight, power, revisedFraction) // Jail validator - k.validatorSet.Jail(ctx, pubkey) + k.validatorSet.Jail(ctx, consAddr) // Set validator jail duration - signInfo, found := k.getValidatorSigningInfo(ctx, address) + signInfo, found := k.getValidatorSigningInfo(ctx, consAddr) if !found { - panic(fmt.Sprintf("Expected signing info for validator %s but not found", address)) + panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr)) } signInfo.JailedUntil = time.Add(k.DoubleSignUnbondDuration(ctx)) - k.setValidatorSigningInfo(ctx, address, signInfo) + k.setValidatorSigningInfo(ctx, consAddr, signInfo) } // handle a validator signature, must be called once per validator per block -// nolint gocyclo +// TODO refactor to take in a consensus address, additionally should maybe just take in the pubkey too func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, power int64, signed bool) { logger := ctx.Logger().With("module", "x/slashing") height := ctx.BlockHeight() - address := sdk.ConsAddress(addr) + consAddr := sdk.ConsAddress(addr) pubkey, err := k.getPubkey(ctx, addr) if err != nil { - panic(fmt.Sprintf("Validator address %v not found", addr)) + panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr)) } // Local index, so counts blocks validator *should* have signed // Will use the 0-value default signing info if not present, except for start height - signInfo, found := k.getValidatorSigningInfo(ctx, address) + 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) @@ -100,16 +100,16 @@ 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, address, index) + previous := k.getValidatorSigningBitArray(ctx, consAddr, index) if previous == signed { // Array value at this index has not changed, no need to update counter } else if previous && !signed { // Array value has changed from signed to unsigned, decrement counter - k.setValidatorSigningBitArray(ctx, address, index, false) + 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, address, index, true) + k.setValidatorSigningBitArray(ctx, consAddr, index, true) signInfo.SignedBlocksCounter++ } @@ -118,13 +118,13 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p } minHeight := signInfo.StartHeight + k.SignedBlocksWindow(ctx) if height > minHeight && signInfo.SignedBlocksCounter < k.MinSignedPerWindow(ctx) { - validator := k.validatorSet.ValidatorByPubKey(ctx, pubkey) + validator := k.validatorSet.ValidatorByConsAddr(ctx, consAddr) if validator != nil && !validator.GetJailed() { // Downtime confirmed: slash and jail the validator logger.Info(fmt.Sprintf("Validator %s past min height of %d and below signed blocks threshold of %d", pubkey.Address(), minHeight, k.MinSignedPerWindow(ctx))) - k.validatorSet.Slash(ctx, pubkey, height, power, k.SlashFractionDowntime(ctx)) - k.validatorSet.Jail(ctx, pubkey) + k.validatorSet.Slash(ctx, consAddr, height, power, k.SlashFractionDowntime(ctx)) + k.validatorSet.Jail(ctx, consAddr) signInfo.JailedUntil = ctx.BlockHeader().Time.Add(k.DowntimeUnbondDuration(ctx)) } else { // Validator was (a) not found or (b) already jailed, don't slash @@ -134,7 +134,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p } // Set the updated signing info - k.setValidatorSigningInfo(ctx, address, signInfo) + k.setValidatorSigningInfo(ctx, consAddr, signInfo) } // AddValidators adds the validators to the keepers validator addr to pubkey mapping. diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index ff50a594e..6845f35ce 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -18,13 +18,16 @@ func init() { defaultDoubleSignUnbondDuration = 60 * 60 } +// ______________________________________________________________ + // Test that a validator is slashed correctly // when we discover evidence of infraction +// TODO fix this test to not be using the same pubkey/address for signing and operating, it's confusing func TestHandleDoubleSign(t *testing.T) { // initial setup ctx, ck, sk, _, keeper := createTestInput(t) - sk = sk.WithValidatorHooks(keeper.ValidatorHooks()) + sk = sk.WithHooks(keeper.Hooks()) amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(addr, val, amt)) @@ -43,7 +46,7 @@ func TestHandleDoubleSign(t *testing.T) { // should be jailed require.True(t, sk.Validator(ctx, addr).GetJailed()) // unjail to measure power - sk.Unjail(ctx, val) + sk.Unjail(ctx, sdk.ConsAddress(addr)) // TODO distinguish cons address // power should be reduced require.Equal( t, sdk.NewDecFromInt(amt).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))), @@ -61,14 +64,16 @@ func TestHandleDoubleSign(t *testing.T) { // Test that the amount a validator is slashed for multiple double signs // is correctly capped by the slashing period in which they were committed +// TODO properly distinguish between consensus and operator address is variable names func TestSlashingPeriodCap(t *testing.T) { // initial setup ctx, ck, sk, _, keeper := createTestInput(t) - sk = sk.WithValidatorHooks(keeper.ValidatorHooks()) + sk = sk.WithHooks(keeper.Hooks()) amtInt := int64(100) - addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) - got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(addr, val, amt)) + addr, amt := addrs[0], sdk.NewInt(amtInt) + valConsPubKey, valConsAddr := pks[0], sdk.ConsAddress(pks[0].Address()) + got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(addr, valConsPubKey, amt)) require.True(t, got.IsOK()) validatorUpdates := stake.EndBlocker(ctx, sk) keeper.AddValidators(ctx, validatorUpdates) @@ -76,39 +81,39 @@ func TestSlashingPeriodCap(t *testing.T) { require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower())) // handle a signature to set signing info - keeper.handleValidatorSignature(ctx, val.Address(), amtInt, true) + keeper.handleValidatorSignature(ctx, valConsPubKey.Address(), amtInt, true) // double sign less than max age - keeper.handleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), amtInt) + keeper.handleDoubleSign(ctx, valConsPubKey.Address(), 0, time.Unix(0, 0), amtInt) // should be jailed require.True(t, sk.Validator(ctx, addr).GetJailed()) // update block height ctx = ctx.WithBlockHeight(int64(1)) // unjail to measure power - sk.Unjail(ctx, val) + sk.Unjail(ctx, valConsAddr) // power should be reduced expectedPower := sdk.NewDecFromInt(amt).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))) require.Equal(t, expectedPower, sk.Validator(ctx, addr).GetPower()) // double sign again, same slashing period - keeper.handleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), amtInt) + keeper.handleDoubleSign(ctx, valConsPubKey.Address(), 0, time.Unix(0, 0), amtInt) // should be jailed require.True(t, sk.Validator(ctx, addr).GetJailed()) // update block height ctx = ctx.WithBlockHeight(int64(2)) // unjail to measure power - sk.Unjail(ctx, val) + sk.Unjail(ctx, valConsAddr) // power should be equal, no more should have been slashed expectedPower = sdk.NewDecFromInt(amt).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))) require.Equal(t, expectedPower, sk.Validator(ctx, addr).GetPower()) // double sign again, new slashing period - keeper.handleDoubleSign(ctx, val.Address(), 2, time.Unix(0, 0), amtInt) + keeper.handleDoubleSign(ctx, valConsPubKey.Address(), 2, time.Unix(0, 0), amtInt) // should be jailed require.True(t, sk.Validator(ctx, addr).GetJailed()) // unjail to measure power - sk.Unjail(ctx, val) + sk.Unjail(ctx, valConsAddr) // power should be reduced expectedPower = sdk.NewDecFromInt(amt).Mul(sdk.NewDec(18).Quo(sdk.NewDec(20))) require.Equal(t, expectedPower, sk.Validator(ctx, addr).GetPower()) @@ -120,7 +125,7 @@ func TestHandleAbsentValidator(t *testing.T) { // initial setup ctx, ck, sk, _, keeper := createTestInput(t) - sk = sk.WithValidatorHooks(keeper.ValidatorHooks()) + sk = sk.WithHooks(keeper.Hooks()) amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) sh := stake.NewHandler(sk) @@ -162,7 +167,7 @@ func TestHandleAbsentValidator(t *testing.T) { require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx), info.SignedBlocksCounter) // validator should be bonded still - validator, _ := sk.GetValidatorByPubKey(ctx, val) + validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Bonded, validator.GetStatus()) pool := sk.GetPool(ctx) require.Equal(t, amtInt, pool.BondedTokens.RoundInt64()) @@ -176,7 +181,7 @@ func TestHandleAbsentValidator(t *testing.T) { require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-1, info.SignedBlocksCounter) // validator should have been jailed - validator, _ = sk.GetValidatorByPubKey(ctx, val) + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Unbonding, validator.GetStatus()) // unrevocation should fail prior to jail expiration @@ -189,7 +194,7 @@ func TestHandleAbsentValidator(t *testing.T) { require.True(t, got.IsOK()) // validator should be rebonded now - validator, _ = sk.GetValidatorByPubKey(ctx, val) + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Bonded, validator.GetStatus()) // validator should have been slashed @@ -207,7 +212,7 @@ func TestHandleAbsentValidator(t *testing.T) { height++ ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) - validator, _ = sk.GetValidatorByPubKey(ctx, val) + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Bonded, validator.GetStatus()) // 500 signed blocks @@ -223,7 +228,7 @@ func TestHandleAbsentValidator(t *testing.T) { ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) } - validator, _ = sk.GetValidatorByPubKey(ctx, val) + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Unbonding, validator.GetStatus()) } @@ -258,7 +263,7 @@ func TestHandleNewValidator(t *testing.T) { require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) // validator should be bonded still, should not have been jailed or slashed - validator, _ := sk.GetValidatorByPubKey(ctx, val) + validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Bonded, validator.GetStatus()) pool := sk.GetPool(ctx) require.Equal(t, int64(100), pool.BondedTokens.RoundInt64()) @@ -292,7 +297,7 @@ func TestHandleAlreadyJailed(t *testing.T) { } // validator should have been jailed and slashed - validator, _ := sk.GetValidatorByPubKey(ctx, val) + validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Unbonding, validator.GetStatus()) // validator should have been slashed @@ -303,7 +308,7 @@ func TestHandleAlreadyJailed(t *testing.T) { keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) // validator should not have been slashed twice - validator, _ = sk.GetValidatorByPubKey(ctx, val) + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, amtInt-1, validator.GetTokens().RoundInt64()) } diff --git a/x/slashing/msg.go b/x/slashing/msg.go index 3d2bdedca..3ba7f9273 100644 --- a/x/slashing/msg.go +++ b/x/slashing/msg.go @@ -1,11 +1,11 @@ package slashing import ( + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" ) -var cdc = wire.NewCodec() +var cdc = codec.New() // name to identify transaction types const MsgType = "slashing" @@ -26,6 +26,7 @@ func NewMsgUnjail(validatorAddr sdk.ValAddress) MsgUnjail { //nolint func (msg MsgUnjail) Type() string { return MsgType } +func (msg MsgUnjail) Name() string { return "unjail" } func (msg MsgUnjail) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{sdk.AccAddress(msg.ValidatorAddr)} } diff --git a/x/slashing/simulation/invariants.go b/x/slashing/simulation/invariants.go index 7352aa503..637a8064b 100644 --- a/x/slashing/simulation/invariants.go +++ b/x/slashing/simulation/invariants.go @@ -1,18 +1,14 @@ package simulation import ( - "testing" - - "github.com/stretchr/testify/require" - "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/x/mock/simulation" ) // AllInvariants tests all slashing invariants func AllInvariants() simulation.Invariant { - return func(t *testing.T, app *baseapp.BaseApp, log string) { + return func(app *baseapp.BaseApp) error { // TODO Any invariants to check here? - require.Nil(t, nil) + return nil } } diff --git a/x/slashing/simulation/msgs.go b/x/slashing/simulation/msgs.go index 9cbb2c48a..2b09226f2 100644 --- a/x/slashing/simulation/msgs.go +++ b/x/slashing/simulation/msgs.go @@ -3,9 +3,6 @@ package simulation import ( "fmt" "math/rand" - "testing" - - "github.com/tendermint/tendermint/crypto" "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" @@ -15,12 +12,12 @@ import ( // SimulateMsgUnjail func SimulateMsgUnjail(k slashing.Keeper) simulation.Operation { - return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { - key := simulation.RandomKey(r, keys) - address := sdk.ValAddress(key.PubKey().Address()) + 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) + address := sdk.ValAddress(acc.Address) msg := slashing.NewMsgUnjail(address) if msg.ValidateBasic() != nil { - tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } ctx, write := ctx.CacheContext() result := slashing.NewHandler(k)(ctx, msg) diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index afbe47d55..7c97a8537 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -14,9 +14,9 @@ import ( 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/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/params" @@ -39,24 +39,26 @@ var ( initCoins = sdk.NewInt(200) ) -func createTestCodec() *wire.Codec { - cdc := wire.NewCodec() - sdk.RegisterWire(cdc) - auth.RegisterWire(cdc) - bank.RegisterWire(cdc) - stake.RegisterWire(cdc) - wire.RegisterCrypto(cdc) +func createTestCodec() *codec.Codec { + cdc := codec.New() + sdk.RegisterCodec(cdc) + auth.RegisterCodec(cdc) + bank.RegisterCodec(cdc) + stake.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) return cdc } func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, params.Setter, Keeper) { keyAcc := sdk.NewKVStoreKey("acc") keyStake := sdk.NewKVStoreKey("stake") + tkeyStake := sdk.NewTransientStoreKey("transient_stake") keySlashing := sdk.NewKVStoreKey("slashing") keyParams := sdk.NewKVStoreKey("params") db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyStake, sdk.StoreTypeTransient, nil) ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keySlashing, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) @@ -65,9 +67,9 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, para 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) - ck := bank.NewKeeper(accountMapper) + ck := bank.NewBaseKeeper(accountMapper) params := params.NewKeeper(cdc, keyParams) - sk := stake.NewKeeper(cdc, keyStake, ck, stake.DefaultCodespace) + sk := stake.NewKeeper(cdc, keyStake, tkeyStake, ck, stake.DefaultCodespace) genesis := stake.DefaultGenesisState() genesis.Pool.LooseTokens = sdk.NewDec(initCoins.MulRaw(int64(len(addrs))).Int64()) @@ -101,12 +103,14 @@ func testAddr(addr string) sdk.AccAddress { } func newTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt sdk.Int) stake.MsgCreateValidator { + commission := stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) return stake.MsgCreateValidator{ Description: stake.Description{}, + Commission: commission, DelegatorAddr: sdk.AccAddress(address), ValidatorAddr: address, PubKey: pubKey, - Delegation: sdk.Coin{"steak", amt}, + Delegation: sdk.NewCoin("steak", amt), } } @@ -114,6 +118,6 @@ func newTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, delAmoun return stake.MsgDelegate{ DelegatorAddr: delAddr, ValidatorAddr: valAddr, - Delegation: sdk.Coin{"steak", delAmount}, + Delegation: sdk.NewCoin("steak", delAmount), } } diff --git a/x/slashing/tick_test.go b/x/slashing/tick_test.go index 52703d3d9..5f0ccf8ed 100644 --- a/x/slashing/tick_test.go +++ b/x/slashing/tick_test.go @@ -78,7 +78,7 @@ func TestBeginBlocker(t *testing.T) { } // validator should be jailed - validator, found := sk.GetValidatorByPubKey(ctx, pk) + validator, found := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)) require.True(t, found) require.Equal(t, sdk.Unbonding, validator.GetStatus()) } diff --git a/x/slashing/wire.go b/x/slashing/wire.go deleted file mode 100644 index 208acda06..000000000 --- a/x/slashing/wire.go +++ /dev/null @@ -1,12 +0,0 @@ -package slashing - -import ( - "github.com/cosmos/cosmos-sdk/wire" -) - -// Register concrete types on wire codec -func RegisterWire(cdc *wire.Codec) { - cdc.RegisterConcrete(MsgUnjail{}, "cosmos-sdk/MsgUnjail", nil) -} - -var cdcEmpty = wire.NewCodec() diff --git a/x/stake/app_test.go b/x/stake/app_test.go index 98dc8d56f..f96408c11 100644 --- a/x/stake/app_test.go +++ b/x/stake/app_test.go @@ -20,28 +20,31 @@ var ( addr3 = sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) priv4 = ed25519.GenPrivKey() addr4 = sdk.AccAddress(priv4.PubKey().Address()) - coins = sdk.Coins{{"foocoin", sdk.NewInt(10)}} - fee = auth.StdFee{ - sdk.Coins{{"foocoin", sdk.NewInt(0)}}, + 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()) ) // getMockApp returns an initialized mock application for this module. func getMockApp(t *testing.T) (*mock.App, Keeper) { mApp := mock.NewApp() - RegisterWire(mApp.Cdc) + RegisterCodec(mApp.Cdc) keyStake := sdk.NewKVStoreKey("stake") - bankKeeper := bank.NewKeeper(mApp.AccountMapper) - keeper := NewKeeper(mApp.Cdc, keyStake, bankKeeper, mApp.RegisterCodespace(DefaultCodespace)) + tkeyStake := sdk.NewTransientStoreKey("transient_stake") + bankKeeper := bank.NewBaseKeeper(mApp.AccountMapper) + keeper := NewKeeper(mApp.Cdc, keyStake, tkeyStake, bankKeeper, mApp.RegisterCodespace(DefaultCodespace)) mApp.Router().AddRoute("stake", NewHandler(keeper)) mApp.SetEndBlocker(getEndBlocker(keeper)) mApp.SetInitChainer(getInitChainer(mApp, keeper)) - require.NoError(t, mApp.CompleteSetup([]*sdk.KVStoreKey{keyStake})) + require.NoError(t, mApp.CompleteSetup(keyStake, tkeyStake)) return mApp, keeper } @@ -128,7 +131,7 @@ func TestStakeMsgs(t *testing.T) { // create validator description := NewDescription("foo_moniker", "", "", "") createValidatorMsg := NewMsgCreateValidator( - sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, + sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, commissionMsg, ) mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, true, priv1) @@ -142,7 +145,7 @@ func TestStakeMsgs(t *testing.T) { // addr1 create validator on behalf of addr2 createValidatorMsgOnBehalfOf := NewMsgCreateValidatorOnBehalfOf( - addr1, sdk.ValAddress(addr2), priv2.PubKey(), bondCoin, description, + 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) @@ -159,7 +162,7 @@ func TestStakeMsgs(t *testing.T) { // edit the validator description = NewDescription("bar_moniker", "", "", "") - editValidatorMsg := NewMsgEditValidator(sdk.ValAddress(addr1), description) + editValidatorMsg := NewMsgEditValidator(sdk.ValAddress(addr1), description, nil) mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{2}, true, true, priv1) validator = checkValidator(t, mApp, keeper, sdk.ValAddress(addr1), true) diff --git a/x/stake/client/cli/flags.go b/x/stake/client/cli/flags.go index d0e83ab3c..bec76298f 100644 --- a/x/stake/client/cli/flags.go +++ b/x/stake/client/cli/flags.go @@ -21,6 +21,10 @@ const ( FlagIdentity = "identity" FlagWebsite = "website" FlagDetails = "details" + + FlagCommissionRate = "commission-rate" + FlagCommissionMaxRate = "commission-max-rate" + FlagCommissionMaxChangeRate = "commission-max-change-rate" ) // common flagsets to add to various functions @@ -29,6 +33,8 @@ var ( fsAmount = flag.NewFlagSet("", flag.ContinueOnError) fsShares = flag.NewFlagSet("", flag.ContinueOnError) fsDescriptionCreate = flag.NewFlagSet("", flag.ContinueOnError) + fsCommissionCreate = flag.NewFlagSet("", flag.ContinueOnError) + fsCommissionUpdate = flag.NewFlagSet("", flag.ContinueOnError) fsDescriptionEdit = flag.NewFlagSet("", flag.ContinueOnError) fsValidator = flag.NewFlagSet("", flag.ContinueOnError) fsDelegator = flag.NewFlagSet("", flag.ContinueOnError) @@ -44,12 +50,16 @@ func init() { 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)") fsDescriptionEdit.String(FlagMoniker, types.DoNotModifyDesc, "validator name") fsDescriptionEdit.String(FlagIdentity, types.DoNotModifyDesc, "optional identity signature (ex. UPort or Keybase)") fsDescriptionEdit.String(FlagWebsite, types.DoNotModifyDesc, "optional website") fsDescriptionEdit.String(FlagDetails, types.DoNotModifyDesc, "optional details") - fsValidator.String(FlagAddressValidator, "", "hex address of the validator") - fsDelegator.String(FlagAddressDelegator, "", "hex address of the delegator") - fsRedelegation.String(FlagAddressValidatorSrc, "", "hex address of the source validator") - fsRedelegation.String(FlagAddressValidatorDst, "", "hex address of the destination validator") + fsValidator.String(FlagAddressValidator, "", "bech address of the validator") + fsDelegator.String(FlagAddressDelegator, "", "bech address of the delegator") + fsRedelegation.String(FlagAddressValidatorSrc, "", "bech address of the source validator") + fsRedelegation.String(FlagAddressValidatorDst, "", "bech address of the destination validator") } diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index 9a017fb59..26b57c2a6 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -8,14 +8,14 @@ import ( "github.com/tendermint/tendermint/libs/cli" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/stake" "github.com/cosmos/cosmos-sdk/x/stake/types" ) // GetCmdQueryValidator implements the validator query command. -func GetCmdQueryValidator(storeName string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryValidator(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "validator [operator-addr]", Short: "Query a validator", @@ -48,7 +48,7 @@ func GetCmdQueryValidator(storeName string, cdc *wire.Codec) *cobra.Command { case "json": // parse out the validator - output, err := wire.MarshalJSONIndent(cdc, validator) + output, err := codec.MarshalJSONIndent(cdc, validator) if err != nil { return err } @@ -65,7 +65,7 @@ func GetCmdQueryValidator(storeName string, cdc *wire.Codec) *cobra.Command { } // GetCmdQueryValidators implements the query all validators command. -func GetCmdQueryValidators(storeName string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryValidators(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "validators", Short: "Query for all validators", @@ -97,7 +97,7 @@ func GetCmdQueryValidators(storeName string, cdc *wire.Codec) *cobra.Command { fmt.Println(resp) } case "json": - output, err := wire.MarshalJSONIndent(cdc, validators) + output, err := codec.MarshalJSONIndent(cdc, validators) if err != nil { return err } @@ -115,7 +115,7 @@ func GetCmdQueryValidators(storeName string, cdc *wire.Codec) *cobra.Command { } // GetCmdQueryDelegation the query delegation command. -func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryDelegation(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegation", Short: "Query a delegation based on address and validator address", @@ -153,7 +153,7 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { fmt.Println(resp) case "json": - output, err := wire.MarshalJSONIndent(cdc, delegation) + output, err := codec.MarshalJSONIndent(cdc, delegation) if err != nil { return err } @@ -174,7 +174,7 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { // GetCmdQueryDelegations implements the command to query all the delegations // made from one delegator. -func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryDelegations(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegations [delegator-addr]", Short: "Query all delegations made from one delegator", @@ -200,7 +200,7 @@ func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { delegations = append(delegations, delegation) } - output, err := wire.MarshalJSONIndent(cdc, delegations) + output, err := codec.MarshalJSONIndent(cdc, delegations) if err != nil { return err } @@ -217,7 +217,7 @@ func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { // GetCmdQueryUnbondingDelegation implements the command to query a single // unbonding-delegation record. -func GetCmdQueryUnbondingDelegation(storeName string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryUnbondingDelegation(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "unbonding-delegation", Short: "Query an unbonding-delegation record based on delegator and validator address", @@ -252,7 +252,7 @@ func GetCmdQueryUnbondingDelegation(storeName string, cdc *wire.Codec) *cobra.Co fmt.Println(resp) case "json": - output, err := wire.MarshalJSONIndent(cdc, ubd) + output, err := codec.MarshalJSONIndent(cdc, ubd) if err != nil { return err } @@ -273,7 +273,7 @@ func GetCmdQueryUnbondingDelegation(storeName string, cdc *wire.Codec) *cobra.Co // GetCmdQueryUnbondingDelegations implements the command to query all the // unbonding-delegation records for a delegator. -func GetCmdQueryUnbondingDelegations(storeName string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryUnbondingDelegations(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "unbonding-delegations [delegator-addr]", Short: "Query all unbonding-delegations records for one delegator", @@ -299,7 +299,7 @@ func GetCmdQueryUnbondingDelegations(storeName string, cdc *wire.Codec) *cobra.C ubds = append(ubds, ubd) } - output, err := wire.MarshalJSONIndent(cdc, ubds) + output, err := codec.MarshalJSONIndent(cdc, ubds) if err != nil { return err } @@ -316,7 +316,7 @@ func GetCmdQueryUnbondingDelegations(storeName string, cdc *wire.Codec) *cobra.C // GetCmdQueryRedelegation implements the command to query a single // redelegation record. -func GetCmdQueryRedelegation(storeName string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryRedelegation(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "redelegation", Short: "Query a redelegation record based on delegator and a source and destination validator address", @@ -356,7 +356,7 @@ func GetCmdQueryRedelegation(storeName string, cdc *wire.Codec) *cobra.Command { fmt.Println(resp) case "json": - output, err := wire.MarshalJSONIndent(cdc, red) + output, err := codec.MarshalJSONIndent(cdc, red) if err != nil { return err } @@ -377,7 +377,7 @@ func GetCmdQueryRedelegation(storeName string, cdc *wire.Codec) *cobra.Command { // GetCmdQueryRedelegations implements the command to query all the // redelegation records for a delegator. -func GetCmdQueryRedelegations(storeName string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryRedelegations(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "redelegations [delegator-addr]", Short: "Query all redelegations records for one delegator", @@ -403,7 +403,7 @@ func GetCmdQueryRedelegations(storeName string, cdc *wire.Codec) *cobra.Command reds = append(reds, red) } - output, err := wire.MarshalJSONIndent(cdc, reds) + output, err := codec.MarshalJSONIndent(cdc, reds) if err != nil { return err } @@ -419,7 +419,7 @@ func GetCmdQueryRedelegations(storeName string, cdc *wire.Codec) *cobra.Command } // GetCmdQueryPool implements the pool query command. -func GetCmdQueryPool(storeName string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryPool(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "pool", Short: "Query the current staking pool values", @@ -443,7 +443,7 @@ func GetCmdQueryPool(storeName string, cdc *wire.Codec) *cobra.Command { case "json": // parse out the pool - output, err := wire.MarshalJSONIndent(cdc, pool) + output, err := codec.MarshalJSONIndent(cdc, pool) if err != nil { return err } @@ -458,7 +458,7 @@ func GetCmdQueryPool(storeName string, cdc *wire.Codec) *cobra.Command { } // GetCmdQueryPool implements the params query command. -func GetCmdQueryParams(storeName string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryParams(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "parameters", Short: "Query the current staking parameters information", @@ -482,7 +482,7 @@ func GetCmdQueryParams(storeName string, cdc *wire.Codec) *cobra.Command { case "json": // parse out the params - output, err := wire.MarshalJSONIndent(cdc, params) + output, err := codec.MarshalJSONIndent(cdc, params) if err != nil { return err } diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index fad881803..c68c12a5d 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -7,20 +7,18 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" 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/stake" - "github.com/cosmos/cosmos-sdk/x/stake/types" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" ) // GetCmdCreateValidator implements the create validator command handler. -func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command { +func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "create-validator", Short: "create new validator initialized with a self-delegation to it", @@ -66,6 +64,15 @@ func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command { Details: viper.GetString(FlagDetails), } + // get the initial validator commission parameters + rateStr := viper.GetString(FlagCommissionRate) + maxRateStr := viper.GetString(FlagCommissionMaxRate) + maxChangeRateStr := viper.GetString(FlagCommissionMaxChangeRate) + commissionMsg, err := buildCommissionMsg(rateStr, maxRateStr, maxChangeRateStr) + if err != nil { + return err + } + var msg sdk.Msg if viper.GetString(FlagAddressDelegator) != "" { delAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) @@ -73,28 +80,35 @@ func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command { return err } - msg = stake.NewMsgCreateValidatorOnBehalfOf(delAddr, sdk.ValAddress(valAddr), pk, amount, description) + msg = stake.NewMsgCreateValidatorOnBehalfOf( + delAddr, sdk.ValAddress(valAddr), pk, amount, description, commissionMsg, + ) } else { - msg = stake.NewMsgCreateValidator(sdk.ValAddress(valAddr), pk, amount, description) + msg = stake.NewMsgCreateValidator( + sdk.ValAddress(valAddr), pk, amount, description, commissionMsg, + ) } + if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } + // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } cmd.Flags().AddFlagSet(fsPk) cmd.Flags().AddFlagSet(fsAmount) cmd.Flags().AddFlagSet(fsDescriptionCreate) + cmd.Flags().AddFlagSet(fsCommissionCreate) cmd.Flags().AddFlagSet(fsDelegator) return cmd } // GetCmdEditValidator implements the create edit validator command. -func GetCmdEditValidator(cdc *wire.Codec) *cobra.Command { +func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "edit-validator", Short: "edit and existing validator account", @@ -117,23 +131,37 @@ func GetCmdEditValidator(cdc *wire.Codec) *cobra.Command { Details: viper.GetString(FlagDetails), } - msg := stake.NewMsgEditValidator(sdk.ValAddress(valAddr), description) + var newRate *sdk.Dec + + commissionRate := viper.GetString(FlagCommissionRate) + if commissionRate != "" { + rate, err := sdk.NewDecFromStr(commissionRate) + if err != nil { + return fmt.Errorf("invalid new commission rate: %v", err) + } + + newRate = &rate + } + + msg := stake.NewMsgEditValidator(sdk.ValAddress(valAddr), description, newRate) if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } + // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } cmd.Flags().AddFlagSet(fsDescriptionEdit) + cmd.Flags().AddFlagSet(fsCommissionUpdate) return cmd } // GetCmdDelegate implements the delegate command. -func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { +func GetCmdDelegate(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegate", Short: "delegate liquid tokens to an validator", @@ -165,7 +193,7 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -176,7 +204,7 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { } // GetCmdRedelegate implements the redelegate validator command. -func GetCmdRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { +func GetCmdRedelegate(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "redelegate", Short: "redelegate illiquid tokens from one validator to another", @@ -192,7 +220,7 @@ func GetCmdRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { } // GetCmdBeginRedelegate the begin redelegation command. -func GetCmdBeginRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { +func GetCmdBeginRedelegate(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "begin", Short: "begin redelegation", @@ -237,7 +265,7 @@ func GetCmdBeginRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -247,56 +275,8 @@ func GetCmdBeginRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { return cmd } -// nolint: gocyclo -// TODO: Make this pass gocyclo linting -func getShares( - storeName string, cdc *wire.Codec, sharesAmountStr, - sharesPercentStr string, delAddr sdk.AccAddress, valAddr sdk.ValAddress, -) (sharesAmount sdk.Dec, err error) { - switch { - case sharesAmountStr != "" && sharesPercentStr != "": - return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both") - case sharesAmountStr == "" && sharesPercentStr == "": - return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both") - case sharesAmountStr != "": - sharesAmount, err = sdk.NewDecFromStr(sharesAmountStr) - if err != nil { - return sharesAmount, err - } - if !sharesAmount.GT(sdk.ZeroDec()) { - return sharesAmount, errors.Errorf("shares amount must be positive number (ex. 123, 1.23456789)") - } - case sharesPercentStr != "": - var sharesPercent sdk.Dec - sharesPercent, err = sdk.NewDecFromStr(sharesPercentStr) - if err != nil { - return sharesAmount, err - } - if !sharesPercent.GT(sdk.ZeroDec()) || !sharesPercent.LTE(sdk.OneDec()) { - return sharesAmount, errors.Errorf("shares percent must be >0 and <=1 (ex. 0.01, 0.75, 1)") - } - - // make a query to get the existing delegation shares - key := stake.GetDelegationKey(delAddr, valAddr) - cliCtx := context.NewCLIContext(). - WithCodec(cdc). - WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - - resQuery, err := cliCtx.QueryStore(key, storeName) - if err != nil { - return sharesAmount, errors.Errorf("cannot find delegation to determine percent Error: %v", err) - } - delegation, err := types.UnmarshalDelegation(cdc, key, resQuery) - if err != nil { - return sdk.ZeroDec(), err - } - sharesAmount = sharesPercent.Mul(delegation.Shares) - } - return -} - // GetCmdCompleteRedelegate implements the complete redelegation command. -func GetCmdCompleteRedelegate(cdc *wire.Codec) *cobra.Command { +func GetCmdCompleteRedelegate(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "complete", Short: "complete redelegation", @@ -328,7 +308,7 @@ func GetCmdCompleteRedelegate(cdc *wire.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -338,7 +318,7 @@ func GetCmdCompleteRedelegate(cdc *wire.Codec) *cobra.Command { } // GetCmdUnbond implements the unbond validator command. -func GetCmdUnbond(storeName string, cdc *wire.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", @@ -354,7 +334,7 @@ func GetCmdUnbond(storeName string, cdc *wire.Codec) *cobra.Command { } // GetCmdBeginUnbonding implements the begin unbonding validator command. -func GetCmdBeginUnbonding(storeName string, cdc *wire.Codec) *cobra.Command { +func GetCmdBeginUnbonding(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "begin", Short: "begin unbonding", @@ -392,7 +372,7 @@ func GetCmdBeginUnbonding(storeName string, cdc *wire.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -403,7 +383,7 @@ func GetCmdBeginUnbonding(storeName string, cdc *wire.Codec) *cobra.Command { } // GetCmdCompleteUnbonding implements the complete unbonding validator command. -func GetCmdCompleteUnbonding(cdc *wire.Codec) *cobra.Command { +func GetCmdCompleteUnbonding(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "complete", Short: "complete unbonding", @@ -430,7 +410,7 @@ func GetCmdCompleteUnbonding(cdc *wire.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/stake/client/cli/utils.go b/x/stake/client/cli/utils.go new file mode 100644 index 000000000..9aca2d899 --- /dev/null +++ b/x/stake/client/cli/utils.go @@ -0,0 +1,88 @@ +package cli + +import ( + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/pkg/errors" +) + +func getShares( + storeName string, cdc *codec.Codec, sharesAmountStr, + sharesPercentStr string, delAddr sdk.AccAddress, valAddr sdk.ValAddress, +) (sharesAmount sdk.Dec, err error) { + + switch { + case sharesAmountStr != "" && sharesPercentStr != "": + return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both") + + case sharesAmountStr == "" && sharesPercentStr == "": + return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both") + + case sharesAmountStr != "": + sharesAmount, err = sdk.NewDecFromStr(sharesAmountStr) + if err != nil { + return sharesAmount, err + } + if !sharesAmount.GT(sdk.ZeroDec()) { + return sharesAmount, errors.Errorf("shares amount must be positive number (ex. 123, 1.23456789)") + } + + case sharesPercentStr != "": + var sharesPercent sdk.Dec + sharesPercent, err = sdk.NewDecFromStr(sharesPercentStr) + if err != nil { + return sharesAmount, err + } + if !sharesPercent.GT(sdk.ZeroDec()) || !sharesPercent.LTE(sdk.OneDec()) { + return sharesAmount, errors.Errorf("shares percent must be >0 and <=1 (ex. 0.01, 0.75, 1)") + } + + // make a query to get the existing delegation shares + key := stake.GetDelegationKey(delAddr, valAddr) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + + resQuery, err := cliCtx.QueryStore(key, storeName) + if err != nil { + return sharesAmount, errors.Errorf("cannot find delegation to determine percent Error: %v", err) + } + + delegation, err := types.UnmarshalDelegation(cdc, key, resQuery) + if err != nil { + return sdk.ZeroDec(), err + } + + sharesAmount = sharesPercent.Mul(delegation.Shares) + } + + return +} + +func buildCommissionMsg(rateStr, maxRateStr, maxChangeRateStr string) (commission types.CommissionMsg, err error) { + if rateStr == "" || maxRateStr == "" || maxChangeRateStr == "" { + return commission, errors.Errorf("must specify all validator commission parameters") + } + + rate, err := sdk.NewDecFromStr(rateStr) + if err != nil { + return commission, err + } + + maxRate, err := sdk.NewDecFromStr(maxRateStr) + if err != nil { + return commission, err + } + + maxChangeRate, err := sdk.NewDecFromStr(maxChangeRateStr) + if err != nil { + return commission, err + } + + commission = types.NewCommissionMsg(rate, maxRate, maxChangeRate) + return commission, nil +} diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 5d280daf3..a8fbfecf4 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -7,18 +7,17 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/stake" "github.com/cosmos/cosmos-sdk/x/stake/tags" - "github.com/cosmos/cosmos-sdk/x/stake/types" "github.com/gorilla/mux" ) const storeName = "stake" -func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec) { +func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) { // Get all delegations (delegation, undelegation and redelegation) from a delegator r.HandleFunc( @@ -50,16 +49,16 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Cod delegationHandlerFn(cliCtx, cdc), ).Methods("GET") - // Query all unbonding_delegations between a delegator and a validator + // Query all unbonding delegations between a delegator and a validator r.HandleFunc( "/stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}", - unbondingDelegationsHandlerFn(cliCtx, cdc), + unbondingDelegationHandlerFn(cliCtx, cdc), ).Methods("GET") // Get all validators r.HandleFunc( "/stake/validators", - validatorsHandlerFn(cliCtx, cdc), + validatorsHandlerFn(cliCtx), ).Methods("GET") // Get a single validator info @@ -71,119 +70,66 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Cod // Get the current state of the staking pool r.HandleFunc( "/stake/pool", - poolHandlerFn(cliCtx, cdc), + poolHandlerFn(cliCtx), ).Methods("GET") // Get the current staking parameter values r.HandleFunc( "/stake/parameters", - paramsHandlerFn(cliCtx, cdc), + paramsHandlerFn(cliCtx), ).Methods("GET") } -// already resolve the rational shares to not handle this in the client - -// defines a delegation without type Rat for shares -type DelegationWithoutRat struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - ValidatorAddr sdk.ValAddress `json:"validator_addr"` - Shares string `json:"shares"` - Height int64 `json:"height"` -} - -// aggregation of all delegations, unbondings and redelegations -type DelegationSummary struct { - Delegations []DelegationWithoutRat `json:"delegations"` - UnbondingDelegations []stake.UnbondingDelegation `json:"unbonding_delegations"` - Redelegations []stake.Redelegation `json:"redelegations"` -} - // HTTP request handler to query a delegator delegations -func delegatorHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { +func delegatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var valAddr sdk.ValAddress - var delegationSummary = DelegationSummary{} - - // read parameters vars := mux.Vars(r) bech32delegator := vars["delegatorAddr"] - delAddr, err := sdk.AccAddressFromBech32(bech32delegator) + 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 } - // Get all validators using key - validators, statusCode, errMsg, err := getBech32Validators(storeName, cliCtx, cdc) - if err != nil { - w.WriteHeader(statusCode) - w.Write([]byte(fmt.Sprintf("%s%s", errMsg, err.Error()))) - return + params := stake.QueryDelegatorParams{ + DelegatorAddr: delegatorAddr, } - for _, validator := range validators { - valAddr = validator.OperatorAddr - - // Delegations - delegations, statusCode, errMsg, err := getDelegatorDelegations(cliCtx, cdc, delAddr, valAddr) - if err != nil { - w.WriteHeader(statusCode) - w.Write([]byte(fmt.Sprintf("%s%s", errMsg, err.Error()))) - return - } - if statusCode != http.StatusNoContent { - delegationSummary.Delegations = append(delegationSummary.Delegations, delegations) - } - - // Undelegations - unbondingDelegation, statusCode, errMsg, err := getDelegatorUndelegations(cliCtx, cdc, delAddr, valAddr) - if err != nil { - w.WriteHeader(statusCode) - w.Write([]byte(fmt.Sprintf("%s%s", errMsg, err.Error()))) - return - } - if statusCode != http.StatusNoContent { - delegationSummary.UnbondingDelegations = append(delegationSummary.UnbondingDelegations, unbondingDelegation) - } - - // Redelegations - // only querying redelegations to a validator as this should give us already all relegations - // if we also would put in redelegations from, we would have every redelegation double - redelegations, statusCode, errMsg, err := getDelegatorRedelegations(cliCtx, cdc, delAddr, valAddr) - if err != nil { - w.WriteHeader(statusCode) - w.Write([]byte(fmt.Sprintf("%s%s", errMsg, err.Error()))) - return - } - if statusCode != http.StatusNoContent { - delegationSummary.Redelegations = append(delegationSummary.Redelegations, redelegations) - } - } - - output, err := cdc.MarshalJSON(delegationSummary) + bz, err := cdc.MarshalJSON(params) if err != nil { - w.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - w.Write(output) + res, err := cliCtx.QueryWithData("custom/stake/delegator", bz) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + + return + } + + w.Write(res) } } -// nolint gocyclo // HTTP request handler to query all staking txs (msgs) from a delegator -func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { +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) @@ -234,10 +180,10 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.Hand } for _, action := range actions { - foundTxs, errQuery := queryTxs(node, cdc, action, delegatorAddr) + foundTxs, errQuery := queryTxs(node, cliCtx, cdc, action, delegatorAddr) if errQuery != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("error querying transactions. Error: %s", errQuery.Error()))) + w.Write([]byte(errQuery.Error())) } txs = append(txs, foundTxs...) } @@ -253,134 +199,13 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.Hand } // HTTP request handler to query an unbonding-delegation -func unbondingDelegationsHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { +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"] - delAddr, err := sdk.AccAddressFromBech32(bech32delegator) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - valAddr, err := sdk.ValAddressFromBech32(bech32validator) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - key := stake.GetUBDKey(delAddr, valAddr) - - res, err := cliCtx.QueryStore(key, storeName) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) - return - } - - // the query will return empty if there is no data for this record - if len(res) == 0 { - w.WriteHeader(http.StatusNoContent) - return - } - - ubd, err := types.UnmarshalUBD(cdc, key, res) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't unmarshall unbonding-delegation. Error: %s", err.Error()))) - return - } - - // unbondings will be a list in the future but is not yet, but we want to keep the API consistent - ubdArray := []stake.UnbondingDelegation{ubd} - - output, err := cdc.MarshalJSON(ubdArray) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't marshall unbonding-delegation. Error: %s", err.Error()))) - return - } - - w.Write(output) - } -} - -// HTTP request handler to query a bonded validator -func delegationHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - // read parameters - vars := mux.Vars(r) - bech32delegator := vars["delegatorAddr"] - bech32validator := vars["validatorAddr"] - - delAddr, err := sdk.AccAddressFromBech32(bech32delegator) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - valAddr, err := sdk.ValAddressFromBech32(bech32validator) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - key := stake.GetDelegationKey(delAddr, valAddr) - - res, err := cliCtx.QueryStore(key, storeName) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't query delegation. Error: %s", err.Error()))) - return - } - - // the query will return empty if there is no data for this record - if len(res) == 0 { - w.WriteHeader(http.StatusNoContent) - return - } - - delegation, err := types.UnmarshalDelegation(cdc, key, res) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - outputDelegation := DelegationWithoutRat{ - DelegatorAddr: delegation.DelegatorAddr, - ValidatorAddr: delegation.ValidatorAddr, - Height: delegation.Height, - Shares: delegation.Shares.String(), - } - - output, err := cdc.MarshalJSON(outputDelegation) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - return - } - - w.Write(output) - } -} - -// HTTP request handler to query all delegator bonded validators -func delegatorValidatorsHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - - var valAddr sdk.ValAddress - var bondedValidators []types.BechValidator - - // read parameters - vars := mux.Vars(r) - bech32delegator := vars["delegatorAddr"] + w.Header().Set("Content-Type", "application/json") delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) if err != nil { @@ -389,239 +214,256 @@ func delegatorValidatorsHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) ht return } - // Get all validators using key - kvs, err := cliCtx.QuerySubspace(stake.ValidatorsKey, storeName) + validatorAddr, err := sdk.ValAddressFromBech32(bech32validator) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't query validators. Error: %s", err.Error()))) - return - } else if len(kvs) == 0 { - // the query will return empty if there are no validators - w.WriteHeader(http.StatusNoContent) - return - } - - validators, err := getValidators(kvs, cdc) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("Error: %s", err.Error()))) - return - } - - for _, validator := range validators { - // get all transactions from the delegator to val and append - valAddr = validator.OperatorAddr - - validator, statusCode, errMsg, errRes := getDelegatorValidator(cliCtx, cdc, delegatorAddr, valAddr) - if errRes != nil { - w.WriteHeader(statusCode) - w.Write([]byte(fmt.Sprintf("%s%s", errMsg, errRes.Error()))) - return - } else if statusCode == http.StatusNoContent { - continue - } - - bondedValidators = append(bondedValidators, validator) - } - output, err := cdc.MarshalJSON(bondedValidators) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - w.Write(output) + + 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) } } -// HTTP request handler to get information from a currently bonded validator -func delegatorValidatorHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { +// HTTP request handler to query a bonded validator +func delegationHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // read parameters - var output []byte vars := mux.Vars(r) bech32delegator := vars["delegatorAddr"] bech32validator := vars["validatorAddr"] - delAddr, err := sdk.AccAddressFromBech32(bech32delegator) - valAddr, err := sdk.ValAddressFromBech32(bech32validator) + w.Header().Set("Content-Type", "application/json") + + delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) if err != nil { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(fmt.Sprintf("Error: %s", err.Error()))) - return - } - - // Check if there if the delegator is bonded or redelegated to the validator - - validator, statusCode, errMsg, err := getDelegatorValidator(cliCtx, cdc, delAddr, valAddr) - if err != nil { - w.WriteHeader(statusCode) - w.Write([]byte(fmt.Sprintf("%s%s", errMsg, err.Error()))) - return - } else if statusCode == http.StatusNoContent { - w.WriteHeader(statusCode) - return - } - output, err = cdc.MarshalJSON(validator) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } - w.Write(output) + + 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) } } -// TODO bech32 -// http request handler to query list of validators -func validatorsHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { +// 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) { - kvs, err := cliCtx.QuerySubspace(stake.ValidatorsKey, storeName) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't query validators. Error: %s", err.Error()))) - return - } + // read parameters + vars := mux.Vars(r) + bech32delegator := vars["delegatorAddr"] - // the query will return empty if there are no validators - if len(kvs) == 0 { - w.WriteHeader(http.StatusNoContent) - return - } + w.Header().Set("Content-Type", "application/json") - validators, err := getValidators(kvs, cdc) + delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("Error: %s", err.Error()))) - return - } - - output, err := cdc.MarshalJSON(validators) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - w.Write(output) + 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) + } +} + +// 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) + } +} + +// HTTP request handler to query list of validators +func validatorsHandlerFn(cliCtx context.CLIContext) 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())) + + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(res) } } // HTTP request handler to query the validator information from a given validator address -func validatorHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { +func validatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var output []byte - // read parameters vars := mux.Vars(r) bech32validatorAddr := vars["addr"] - valAddr, err := sdk.ValAddressFromBech32(bech32validatorAddr) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(fmt.Sprintf("error: %s", err.Error()))) - return - } + w.Header().Set("Content-Type", "application/json") - key := stake.GetValidatorKey(valAddr) - - res, err := cliCtx.QueryStore(key, storeName) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't query validator, error: %s", err.Error()))) - return - } - - // the query will return empty if there is no data for this record - if len(res) == 0 { - w.WriteHeader(http.StatusNoContent) - return - } - - validator, err := types.UnmarshalValidator(cdc, valAddr, res) + validatorAddr, err := sdk.ValAddressFromBech32(bech32validatorAddr) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - bech32Validator, err := validator.Bech32Validator() + params := stake.QueryValidatorParams{ + ValidatorAddr: validatorAddr, + } + + bz, err := cdc.MarshalJSON(params) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - output, err = cdc.MarshalJSON(bech32Validator) + res, err := cliCtx.QueryWithData("custom/stake/validator", bz) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("Error: %s", err.Error()))) + w.Write([]byte(err.Error())) + return } - if output == nil { - w.WriteHeader(http.StatusNoContent) - return - } - w.Write(output) + w.Write(res) } } // HTTP request handler to query the pool information -func poolHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { +func poolHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - key := stake.PoolKey - res, err := cliCtx.QueryStore(key, storeName) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't query pool. Error: %s", err.Error()))) - return - } + w.Header().Set("Content-Type", "application/json") - pool, err := types.UnmarshalPool(cdc, res) + res, err := cliCtx.QueryWithData("custom/stake/pool", nil) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) + return } - output, err := cdc.MarshalJSON(pool) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - return - } - - w.Write(output) + w.Write(res) } } // HTTP request handler to query the staking params values -func paramsHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { +func paramsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - key := stake.ParamKey - res, err := cliCtx.QueryStore(key, storeName) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't query parameters. Error: %s", err.Error()))) - return - } + w.Header().Set("Content-Type", "application/json") - params, err := types.UnmarshalParams(cdc, res) + res, err := cliCtx.QueryWithData("custom/stake/parameters", nil) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) + return } - output, err := cdc.MarshalJSON(params) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - return - } - - w.Write(output) + w.Write(res) } } diff --git a/x/stake/client/rest/rest.go b/x/stake/client/rest/rest.go index 7c2fdf905..507fcb517 100644 --- a/x/stake/client/rest/rest.go +++ b/x/stake/client/rest/rest.go @@ -2,14 +2,14 @@ package rest import ( "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" - "github.com/cosmos/cosmos-sdk/wire" "github.com/gorilla/mux" ) // RegisterRoutes registers staking-related REST handlers to a router -func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, kb keys.Keybase) { registerQueryRoutes(cliCtx, r, cdc) registerTxRoutes(cliCtx, r, cdc, kb) } diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index d84a8daea..ee2432228 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -9,9 +9,9 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/stake" @@ -20,61 +20,62 @@ import ( ctypes "github.com/tendermint/tendermint/rpc/core/types" ) -func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { +func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, kb keys.Keybase) { r.HandleFunc( "/stake/delegators/{delegatorAddr}/delegations", delegationsRequestHandlerFn(cdc, kb, cliCtx), ).Methods("POST") } -type msgDelegationsInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorAddr string `json:"validator_addr"` // in bech32 - Delegation sdk.Coin `json:"delegation"` -} -type msgBeginRedelegateInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 - ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 - SharesAmount string `json:"shares"` -} -type msgCompleteRedelegateInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 - ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 -} -type msgBeginUnbondingInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorAddr string `json:"validator_addr"` // in bech32 - SharesAmount string `json:"shares"` -} -type msgCompleteUnbondingInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorAddr string `json:"validator_addr"` // in bech32 -} +type ( + msgDelegationsInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + Delegation sdk.Coin `json:"delegation"` + } -// the request body for edit delegations -type EditDelegationsBody struct { - LocalAccountName string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas int64 `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` - Delegations []msgDelegationsInput `json:"delegations"` - BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"` - CompleteUnbondings []msgCompleteUnbondingInput `json:"complete_unbondings"` - BeginRedelegates []msgBeginRedelegateInput `json:"begin_redelegates"` - CompleteRedelegates []msgCompleteRedelegateInput `json:"complete_redelegates"` -} + msgBeginRedelegateInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 + ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 + SharesAmount string `json:"shares"` + } + + msgCompleteRedelegateInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 + ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 + } + + msgBeginUnbondingInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + SharesAmount string `json:"shares"` + } + + msgCompleteUnbondingInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + } + + // the request body for edit delegations + EditDelegationsReq struct { + BaseReq utils.BaseReq `json:"base_req"` + Delegations []msgDelegationsInput `json:"delegations"` + BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"` + CompleteUnbondings []msgCompleteUnbondingInput `json:"complete_unbondings"` + BeginRedelegates []msgBeginRedelegateInput `json:"begin_redelegates"` + CompleteRedelegates []msgCompleteRedelegateInput `json:"complete_redelegates"` + } +) -// nolint: gocyclo // TODO: Split this up into several smaller functions, and remove the above nolint // TODO: use sdk.ValAddress instead of sdk.AccAddress for validators in messages -func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { +// TODO: Seriously consider how to refactor...do we need to make it multiple txs? +// If not, we can just use CompleteAndBroadcastTxREST. +func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var m EditDelegationsBody + var req EditDelegationsReq body, err := ioutil.ReadAll(r.Body) if err != nil { @@ -83,14 +84,19 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex return } - err = cdc.UnmarshalJSON(body, &m) + err = cdc.UnmarshalJSON(body, &req) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - info, err := kb.Get(m.LocalAccountName) + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + info, err := kb.Get(baseReq.Name) if err != nil { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(err.Error())) @@ -98,14 +104,14 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex } // build messages - messages := make([]sdk.Msg, len(m.Delegations)+ - len(m.BeginRedelegates)+ - len(m.CompleteRedelegates)+ - len(m.BeginUnbondings)+ - len(m.CompleteUnbondings)) + messages := make([]sdk.Msg, len(req.Delegations)+ + len(req.BeginRedelegates)+ + len(req.CompleteRedelegates)+ + len(req.BeginUnbondings)+ + len(req.CompleteUnbondings)) i := 0 - for _, msg := range m.Delegations { + 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())) @@ -132,7 +138,7 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex i++ } - for _, msg := range m.BeginRedelegates { + 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())) @@ -171,7 +177,7 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex i++ } - for _, msg := range m.CompleteRedelegates { + for _, msg := range req.CompleteRedelegates { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) @@ -204,7 +210,7 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex i++ } - for _, msg := range m.BeginUnbondings { + 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())) @@ -237,7 +243,7 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex i++ } - for _, msg := range m.CompleteUnbondings { + for _, msg := range req.CompleteUnbondings { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) @@ -263,38 +269,47 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex i++ } + simulateGas, gas, err := client.ReadGasFlag(baseReq.Gas) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) + if !ok { + return + } + txBldr := authtxb.TxBuilder{ - Codec: cdc, - ChainID: m.ChainID, - Gas: m.Gas, + Codec: cdc, + Gas: gas, + GasAdjustment: adjustment, + SimulateGas: simulateGas, + ChainID: baseReq.ChainID, } // sign messages signedTxs := make([][]byte, len(messages[:])) for i, msg := range messages { // increment sequence for each message - txBldr = txBldr.WithAccountNumber(m.AccountNumber) - txBldr = txBldr.WithSequence(m.Sequence) + txBldr = txBldr.WithAccountNumber(baseReq.AccountNumber) + txBldr = txBldr.WithSequence(baseReq.Sequence) - m.Sequence++ + baseReq.Sequence++ - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) - if !ok { - return - } - cliCtx = cliCtx.WithGasAdjustment(adjustment) - - if utils.HasDryRunArg(r) || m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) + if utils.HasDryRunArg(r) || txBldr.SimulateGas { + newBldr, err := utils.EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } + if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, txBldr.Gas) + utils.WriteSimulationResponse(w, newBldr.Gas) return } - txBldr = newCtx + + txBldr = newBldr } if utils.HasGenerateOnlyArg(r) { @@ -302,7 +317,7 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex return } - txBytes, err := txBldr.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) + txBytes, err := txBldr.BuildAndSign(baseReq.Name, baseReq.Password, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return @@ -325,7 +340,7 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex results[i] = res } - output, err := wire.MarshalJSONIndent(cdc, results[:]) + output, err := codec.MarshalJSONIndent(cdc, results[:]) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return diff --git a/x/stake/client/rest/utils.go b/x/stake/client/rest/utils.go index d8ad5fec5..e7b8891ea 100644 --- a/x/stake/client/rest/utils.go +++ b/x/stake/client/rest/utils.go @@ -2,15 +2,12 @@ package rest import ( "fmt" - "net/http" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/tx" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" - "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/stake/tags" - "github.com/cosmos/cosmos-sdk/x/stake/types" + rpcclient "github.com/tendermint/tendermint/rpc/client" ) @@ -24,163 +21,25 @@ func contains(stringSlice []string, txType string) bool { return false } -func getDelegatorValidator(cliCtx context.CLIContext, cdc *wire.Codec, delAddr sdk.AccAddress, valAddr sdk.ValAddress) ( - bech32Validator types.BechValidator, httpStatusCode int, errMsg string, err error) { - - key := stake.GetDelegationKey(delAddr, valAddr) - res, err := cliCtx.QueryStore(key, storeName) - if err != nil { - return types.BechValidator{}, http.StatusInternalServerError, "couldn't query delegation. Error: ", err - } - if len(res) == 0 { - return types.BechValidator{}, http.StatusNoContent, "", nil - } - - key = stake.GetValidatorKey(valAddr) - res, err = cliCtx.QueryStore(key, storeName) - if err != nil { - return types.BechValidator{}, http.StatusInternalServerError, "couldn't query validator. Error: ", err - } - if len(res) == 0 { - return types.BechValidator{}, http.StatusNoContent, "", nil - } - validator, err := types.UnmarshalValidator(cdc, valAddr, res) - if err != nil { - return types.BechValidator{}, http.StatusBadRequest, "", err - } - bech32Validator, err = validator.Bech32Validator() - if err != nil { - return types.BechValidator{}, http.StatusBadRequest, "", err - } - - return bech32Validator, http.StatusOK, "", nil -} - -func getDelegatorDelegations( - cliCtx context.CLIContext, cdc *wire.Codec, delAddr sdk.AccAddress, valAddr sdk.ValAddress) ( - outputDelegation DelegationWithoutRat, httpStatusCode int, errMsg string, err error) { - - delegationKey := stake.GetDelegationKey(delAddr, valAddr) - marshalledDelegation, err := cliCtx.QueryStore(delegationKey, storeName) - if err != nil { - return DelegationWithoutRat{}, http.StatusInternalServerError, "couldn't query delegation. Error: ", err - } - - if len(marshalledDelegation) == 0 { - return DelegationWithoutRat{}, http.StatusNoContent, "", nil - } - - delegation, err := types.UnmarshalDelegation(cdc, delegationKey, marshalledDelegation) - if err != nil { - return DelegationWithoutRat{}, http.StatusInternalServerError, "couldn't unmarshall delegation. Error: ", err - } - - outputDelegation = DelegationWithoutRat{ - DelegatorAddr: delegation.DelegatorAddr, - ValidatorAddr: delegation.ValidatorAddr, - Height: delegation.Height, - Shares: delegation.Shares.String(), - } - - return outputDelegation, http.StatusOK, "", nil -} - -func getDelegatorUndelegations( - cliCtx context.CLIContext, cdc *wire.Codec, delAddr sdk.AccAddress, valAddr sdk.ValAddress) ( - unbonds types.UnbondingDelegation, httpStatusCode int, errMsg string, err error) { - - undelegationKey := stake.GetUBDKey(delAddr, valAddr) - marshalledUnbondingDelegation, err := cliCtx.QueryStore(undelegationKey, storeName) - if err != nil { - return types.UnbondingDelegation{}, http.StatusInternalServerError, "couldn't query unbonding-delegation. Error: ", err - } - - if len(marshalledUnbondingDelegation) == 0 { - return types.UnbondingDelegation{}, http.StatusNoContent, "", nil - } - - unbondingDelegation, err := types.UnmarshalUBD(cdc, undelegationKey, marshalledUnbondingDelegation) - if err != nil { - return types.UnbondingDelegation{}, http.StatusInternalServerError, "couldn't unmarshall unbonding-delegation. Error: ", err - } - return unbondingDelegation, http.StatusOK, "", nil -} - -func getDelegatorRedelegations( - cliCtx context.CLIContext, cdc *wire.Codec, delAddr sdk.AccAddress, valAddr sdk.ValAddress) ( - regelegations types.Redelegation, httpStatusCode int, errMsg string, err error) { - - key := stake.GetREDsByDelToValDstIndexKey(delAddr, valAddr) - marshalledRedelegations, err := cliCtx.QueryStore(key, storeName) - if err != nil { - return types.Redelegation{}, http.StatusInternalServerError, "couldn't query redelegation. Error: ", err - } - - if len(marshalledRedelegations) == 0 { - return types.Redelegation{}, http.StatusNoContent, "", nil - } - - redelegations, err := types.UnmarshalRED(cdc, key, marshalledRedelegations) - if err != nil { - return types.Redelegation{}, http.StatusInternalServerError, "couldn't unmarshall redelegations. Error: ", err - } - - return redelegations, http.StatusOK, "", nil -} - // queries staking txs -func queryTxs(node rpcclient.Client, cdc *wire.Codec, tag string, delegatorAddr string) ([]tx.Info, error) { +func queryTxs(node rpcclient.Client, cliCtx context.CLIContext, cdc *codec.Codec, tag string, delegatorAddr string) ([]tx.Info, error) { page := 0 perPage := 100 - prove := false + prove := !cliCtx.TrustNode query := fmt.Sprintf("%s='%s' AND %s='%s'", tags.Action, tag, tags.Delegator, delegatorAddr) res, err := node.TxSearch(query, prove, page, perPage) if err != nil { return nil, err } + if prove { + for _, txData := range res.Txs { + err := tx.ValidateTxResult(cliCtx, txData) + if err != nil { + return nil, err + } + } + } + return tx.FormatTxResults(cdc, res.Txs) } - -// gets all validators -func getValidators(validatorKVs []sdk.KVPair, cdc *wire.Codec) ([]types.BechValidator, error) { - validators := make([]types.BechValidator, len(validatorKVs)) - for i, kv := range validatorKVs { - - addr := kv.Key[1:] - validator, err := types.UnmarshalValidator(cdc, addr, kv.Value) - if err != nil { - return nil, err - } - - bech32Validator, err := validator.Bech32Validator() - if err != nil { - return nil, err - } - validators[i] = bech32Validator - } - return validators, nil -} - -// gets all Bech32 validators from a key -// nolint: unparam -func getBech32Validators(storeName string, cliCtx context.CLIContext, cdc *wire.Codec) ( - validators []types.BechValidator, httpStatusCode int, errMsg string, err error) { - - // Get all validators using key - kvs, err := cliCtx.QuerySubspace(stake.ValidatorsKey, storeName) - if err != nil { - return nil, http.StatusInternalServerError, "couldn't query validators. Error: ", err - } - - // the query will return empty if there are no validators - if len(kvs) == 0 { - return nil, http.StatusNoContent, "", nil - } - - validators, err = getValidators(kvs, cdc) - if err != nil { - return nil, http.StatusInternalServerError, "Error: ", err - } - return validators, http.StatusOK, "", nil -} diff --git a/x/stake/genesis.go b/x/stake/genesis.go index 13f2bec04..740eba415 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -32,7 +32,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ } // Manually set indexes for the first time - keeper.SetValidatorByPubKeyIndex(ctx, validator) + keeper.SetValidatorByConsAddr(ctx, validator) keeper.SetValidatorByPowerIndex(ctx, validator, data.Pool) if validator.Status == sdk.Bonded { @@ -75,7 +75,7 @@ func WriteGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisValidator) { keeper.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) { vals = append(vals, tmtypes.GenesisValidator{ - PubKey: validator.GetPubKey(), + PubKey: validator.GetConsPubKey(), Power: validator.GetPower().RoundInt64(), Name: validator.GetMoniker(), }) diff --git a/x/stake/genesis_test.go b/x/stake/genesis_test.go index 6460c35e0..0051ff252 100644 --- a/x/stake/genesis_test.go +++ b/x/stake/genesis_test.go @@ -42,6 +42,12 @@ func TestInitGenesis(t *testing.T) { vals, err := InitGenesis(ctx, keeper, genesisState) require.NoError(t, err) + actualGenesis := WriteGenesis(ctx, keeper) + require.Equal(t, genesisState.Pool, actualGenesis.Pool) + require.Equal(t, genesisState.Params, actualGenesis.Params) + require.Equal(t, genesisState.Bonds, actualGenesis.Bonds) + require.EqualValues(t, keeper.GetAllValidators(ctx), actualGenesis.Validators) + // now make sure the validators are bonded and intra-tx counters are correct resVal, found := keeper.GetValidator(ctx, sdk.ValAddress(keep.Addrs[0])) require.True(t, found) diff --git a/x/stake/handler.go b/x/stake/handler.go index f3b2f4339..65915a65e 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -53,7 +53,6 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Valid // calculate validator set changes ValidatorUpdates = k.GetTendermintUpdates(ctx) - k.ClearTendermintUpdates(ctx) return } @@ -63,44 +62,59 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Valid // now we just perform action and save func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k keeper.Keeper) sdk.Result { - // check to see if the pubkey or sender has been registered before _, found := k.GetValidator(ctx, msg.ValidatorAddr) if found { return ErrValidatorOwnerExists(k.Codespace()).Result() } - _, found = k.GetValidatorByPubKey(ctx, msg.PubKey) + + _, found = k.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(msg.PubKey)) if found { return ErrValidatorPubKeyExists(k.Codespace()).Result() } + if msg.Delegation.Denom != k.GetParams(ctx).BondDenom { return ErrBadDenom(k.Codespace()).Result() } validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description) - k.SetValidator(ctx, validator) - k.SetValidatorByPubKeyIndex(ctx, validator) + commission := NewCommissionWithTime( + msg.Commission.Rate, msg.Commission.MaxChangeRate, + msg.Commission.MaxChangeRate, ctx.BlockHeader().Time, + ) - // 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) + validator, err := validator.SetInitialCommission(commission) if err != nil { return err.Result() } + k.SetValidator(ctx, validator) + k.SetValidatorByConsAddr(ctx, validator) + + // 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) + if err != nil { + 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()), tags.Moniker, []byte(msg.Description.Moniker), tags.Identity, []byte(msg.Description.Identity), ) + return sdk.Result{ Tags: tags, } } func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keeper.Keeper) sdk.Result { - // validator must already be registered validator, found := k.GetValidator(ctx, msg.ValidatorAddr) if !found { @@ -112,17 +126,26 @@ func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keepe if err != nil { return err.Result() } + validator.Description = description + if msg.CommissionRate != nil { + if err := k.UpdateValidatorCommission(ctx, validator, *msg.CommissionRate); err != nil { + return err.Result() + } + } + // We don't need to run through all the power update logic within k.UpdateValidator // We just need to override the entry in state, since only the description has changed. k.SetValidator(ctx, validator) + tags := sdk.NewTags( tags.Action, tags.ActionEditValidator, tags.DstValidator, []byte(msg.ValidatorAddr.String()), tags.Moniker, []byte(description.Moniker), tags.Identity, []byte(description.Identity), ) + return sdk.Result{ Tags: tags, } @@ -147,6 +170,9 @@ 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()), diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 4862b14c3..5780bf543 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -17,24 +17,27 @@ import ( //______________________________________________________________________ func newTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt int64) MsgCreateValidator { - return types.NewMsgCreateValidator(address, pubKey, sdk.Coin{"steak", sdk.NewInt(amt)}, Description{}) + 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.Coin{"steak", sdk.NewInt(amt)}, + 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.Coin{"steak", sdk.NewInt(amt)}, + Delegation: sdk.NewCoin("steak", sdk.NewInt(amt)), } } @@ -81,8 +84,9 @@ func TestValidatorByPowerIndex(t *testing.T) { require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) // slash and jail the first validator - keeper.Slash(ctx, keep.PKs[0], 0, initBond, sdk.NewDecWithPrec(5, 1)) - keeper.Jail(ctx, keep.PKs[0]) + consAddr0 := sdk.ConsAddress(keep.PKs[0].Address()) + keeper.Slash(ctx, consAddr0, 0, initBond, sdk.NewDecWithPrec(5, 1)) + keeper.Jail(ctx, consAddr0) validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) require.Equal(t, sdk.Unbonding, validator.Status) // ensure is unbonding @@ -198,11 +202,12 @@ func TestLegacyValidatorDelegations(t *testing.T) { setInstantUnbondPeriod(keeper, ctx) bondAmount := int64(10) - valAddr, valPubKey := sdk.ValAddress(keep.Addrs[0]), keep.PKs[0] + valAddr := sdk.ValAddress(keep.Addrs[0]) + valConsPubKey, valConsAddr := keep.PKs[0], sdk.ConsAddress(keep.PKs[0].Address()) delAddr := keep.Addrs[1] // create validator - msgCreateVal := newTestMsgCreateValidator(valAddr, valPubKey, 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) @@ -264,7 +269,7 @@ func TestLegacyValidatorDelegations(t *testing.T) { require.Equal(t, bondAmount*2, validator.Tokens.RoundInt64()) // unjail the validator now that is has non-zero self-delegated shares - keeper.Unjail(ctx, valPubKey) + keeper.Unjail(ctx, valConsAddr) // verify the validator can now accept delegations msgDelegate = newTestMsgDelegate(delAddr, valAddr, bondAmount) @@ -911,6 +916,7 @@ func TestCliffValidator(t *testing.T) { func TestBondUnbondRedelegateSlashTwice(t *testing.T) { ctx, _, keeper := keep.CreateTestInput(t, false, 1000) 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) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) @@ -944,7 +950,7 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { require.Equal(t, sdk.NewDec(6), delegation.Shares) // slash the validator by half - keeper.Slash(ctx, keep.PKs[0], 0, 20, sdk.NewDecWithPrec(5, 1)) + keeper.Slash(ctx, consAddr0, 0, 20, sdk.NewDecWithPrec(5, 1)) // unbonding delegation should have been slashed by half unbonding, found := keeper.GetUnbondingDelegation(ctx, del, valA) @@ -968,7 +974,7 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { // slash the validator for an infraction committed after the unbonding and redelegation begin ctx = ctx.WithBlockHeight(3) - keeper.Slash(ctx, keep.PKs[0], 2, 10, sdk.NewDecWithPrec(5, 1)) + keeper.Slash(ctx, consAddr0, 2, 10, sdk.NewDecWithPrec(5, 1)) // unbonding delegation should be unchanged unbonding, found = keeper.GetUnbondingDelegation(ctx, del, valA) diff --git a/x/stake/keeper/_store.md b/x/stake/keeper/_store.md index 818b17ac0..5c070b9e0 100644 --- a/x/stake/keeper/_store.md +++ b/x/stake/keeper/_store.md @@ -3,6 +3,7 @@ This document provided a bit more insight as to the purpose of several related prefixed areas of the staking store which are accessed in `x/stake/keeper.go`. +# IAVL Store ## Validators - Prefix Key Space: ValidatorsKey @@ -36,10 +37,13 @@ prefixed areas of the staking store which are accessed in `x/stake/keeper.go`. through this set to determine who we've kicked out. retrieving validator by tendermint index +# Transient Store + +The transient store persists between transations but not between blocks + ## Tendermint Updates - - Prefix Key Space: TendermintUpdatesKey + - Prefix Key Space: TendermintUpdatesTKey - Key/Sort: Validator Operator Address - Value: Tendermint ABCI Validator - Contains: Validators are queued to affect the consensus validation set in Tendermint - - Used For: Informing Tendermint of the validator set updates, is used only intra-block, as the - updates are applied then cleared on endblock + - Used For: Informing Tendermint of the validator set updates diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 0ef3eb9cf..cc46646a7 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -8,7 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake/types" ) -// load a delegation +// return a specific delegation func (k Keeper) GetDelegation(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) ( delegation types.Delegation, found bool) { @@ -24,44 +24,37 @@ func (k Keeper) GetDelegation(ctx sdk.Context, return delegation, true } -// load all delegations used during genesis dump +// return all delegations used during genesis dump func (k Keeper) GetAllDelegations(ctx sdk.Context) (delegations []types.Delegation) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, DelegationKey) + defer iterator.Close() - i := 0 - for ; ; i++ { - if !iterator.Valid() { - break - } + for ; iterator.Valid(); iterator.Next() { delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) delegations = append(delegations, delegation) - iterator.Next() } - iterator.Close() return delegations } -// load all delegations for a delegator -func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.AccAddress, - maxRetrieve int16) (delegations []types.Delegation) { +// 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) { + + delegations = make([]types.Delegation, maxRetrieve) store := ctx.KVStore(k.storeKey) delegatorPrefixKey := GetDelegationsKey(delegator) - iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest + iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) + defer iterator.Close() - delegations = make([]types.Delegation, maxRetrieve) i := 0 - for ; ; i++ { - if !iterator.Valid() || i > int(maxRetrieve-1) { - break - } + for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() { delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) delegations[i] = delegation - iterator.Next() + i++ } - iterator.Close() - return delegations[:i] // trim + return delegations[:i] // trim if the array length < maxRetrieve } // set the delegation @@ -71,15 +64,36 @@ func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) { store.Set(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr), b) } -// remove the delegation +// remove a delegation from store func (k Keeper) RemoveDelegation(ctx sdk.Context, delegation types.Delegation) { + k.OnDelegationRemoved(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) store := ctx.KVStore(k.storeKey) store.Delete(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr)) } //_____________________________________________________________________________________ -// load a unbonding delegation +// return a given amount of all the delegator unbonding-delegations +func (k Keeper) GetUnbondingDelegations(ctx sdk.Context, delegator sdk.AccAddress, + maxRetrieve uint16) (unbondingDelegations []types.UnbondingDelegation) { + + unbondingDelegations = make([]types.UnbondingDelegation, maxRetrieve) + + store := ctx.KVStore(k.storeKey) + delegatorPrefixKey := GetUBDsKey(delegator) + iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) + defer iterator.Close() + + i := 0 + for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() { + unbondingDelegation := types.MustUnmarshalUBD(k.cdc, iterator.Key(), iterator.Value()) + unbondingDelegations[i] = unbondingDelegation + i++ + } + return unbondingDelegations[:i] // trim if the array length < maxRetrieve +} + +// return a unbonding delegation func (k Keeper) GetUnbondingDelegation(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (ubd types.UnbondingDelegation, found bool) { @@ -94,21 +108,18 @@ func (k Keeper) GetUnbondingDelegation(ctx sdk.Context, return ubd, true } -// load all unbonding delegations from a particular validator +// return all unbonding delegations from a particular validator func (k Keeper) GetUnbondingDelegationsFromValidator(ctx sdk.Context, valAddr sdk.ValAddress) (ubds []types.UnbondingDelegation) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, GetUBDsByValIndexKey(valAddr)) - for { - if !iterator.Valid() { - break - } + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { key := GetUBDKeyFromValIndexKey(iterator.Key()) value := store.Get(key) ubd := types.MustUnmarshalUBD(k.cdc, key, value) ubds = append(ubds, ubd) - iterator.Next() } - iterator.Close() return ubds } @@ -116,16 +127,15 @@ func (k Keeper) GetUnbondingDelegationsFromValidator(ctx sdk.Context, valAddr sd func (k Keeper) IterateUnbondingDelegations(ctx sdk.Context, fn func(index int64, ubd types.UnbondingDelegation) (stop bool)) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, UnbondingDelegationKey) - i := int64(0) - for ; iterator.Valid(); iterator.Next() { + defer iterator.Close() + + for i := int64(0); iterator.Valid(); iterator.Next() { ubd := types.MustUnmarshalUBD(k.cdc, iterator.Key(), iterator.Value()) - stop := fn(i, ubd) - if stop { + if stop := fn(i, ubd); stop { break } i++ } - iterator.Close() } // set the unbonding delegation and associated index @@ -147,7 +157,26 @@ func (k Keeper) RemoveUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDe //_____________________________________________________________________________________ -// load a redelegation +// return a given amount of all the delegator redelegations +func (k Keeper) GetRedelegations(ctx sdk.Context, delegator sdk.AccAddress, + maxRetrieve uint16) (redelegations []types.Redelegation) { + redelegations = make([]types.Redelegation, maxRetrieve) + + store := ctx.KVStore(k.storeKey) + delegatorPrefixKey := GetREDsKey(delegator) + iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) + defer iterator.Close() + + i := 0 + for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() { + redelegation := types.MustUnmarshalRED(k.cdc, iterator.Key(), iterator.Value()) + redelegations[i] = redelegation + i++ + } + return redelegations[:i] // trim if the array length < maxRetrieve +} + +// return a redelegation func (k Keeper) GetRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) (red types.Redelegation, found bool) { @@ -162,38 +191,34 @@ func (k Keeper) GetRedelegation(ctx sdk.Context, return red, true } -// load all redelegations from a particular validator +// return all redelegations from a particular validator func (k Keeper) GetRedelegationsFromValidator(ctx sdk.Context, valAddr sdk.ValAddress) (reds []types.Redelegation) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, GetREDsFromValSrcIndexKey(valAddr)) - for { - if !iterator.Valid() { - break - } + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { key := GetREDKeyFromValSrcIndexKey(iterator.Key()) value := store.Get(key) red := types.MustUnmarshalRED(k.cdc, key, value) reds = append(reds, red) - iterator.Next() } - iterator.Close() return reds } -// has a redelegation +// check if validator is receiving a redelegation func (k Keeper) HasReceivingRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, valDstAddr sdk.ValAddress) bool { store := ctx.KVStore(k.storeKey) prefix := GetREDsByDelToValDstIndexKey(delAddr, valDstAddr) - iterator := sdk.KVStorePrefixIterator(store, prefix) //smallest to largest + iterator := sdk.KVStorePrefixIterator(store, prefix) + defer iterator.Close() found := false if iterator.Valid() { - //record found found = true } - iterator.Close() return found } @@ -258,6 +283,8 @@ 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 { @@ -309,6 +336,7 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA k.RemoveValidator(ctx, validator.OperatorAddr) } + k.OnDelegationSharesModified(ctx, delegation.DelegatorAddr, validator.OperatorAddr) return amount, nil } @@ -358,7 +386,7 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context, // create the unbonding delegation params := k.GetParams(ctx) minTime, height, completeNow := k.getBeginInfo(ctx, params, valAddr) - balance := sdk.Coin{params.BondDenom, returnAmount.RoundInt()} + balance := sdk.NewCoin(params.BondDenom, returnAmount.RoundInt()) // no need to create the ubd object just complete now if completeNow { @@ -418,7 +446,7 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, } params := k.GetParams(ctx) - returnCoin := sdk.Coin{params.BondDenom, returnAmount.RoundInt()} + returnCoin := sdk.NewCoin(params.BondDenom, returnAmount.RoundInt()) dstValidator, found := k.GetValidator(ctx, valDstAddr) if !found { return types.ErrBadRedelegationDst(k.Codespace()) diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index 023642bb2..ed65f1f40 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" ) -// tests GetDelegation, GetDelegations, SetDelegation, RemoveDelegation, GetDelegations +// tests GetDelegation, GetDelegatorDelegations, SetDelegation, RemoveDelegation, GetDelegatorDelegations func TestDelegation(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 10) pool := keeper.GetPool(ctx) @@ -30,6 +30,7 @@ func TestDelegation(t *testing.T) { validators[2] = keeper.UpdateValidator(ctx, validators[2]) // first add a validators[0] to delegate too + bond1to1 := types.Delegation{ DelegatorAddr: addrDels[0], ValidatorAddr: addrVals[0], @@ -66,16 +67,16 @@ func TestDelegation(t *testing.T) { keeper.SetDelegation(ctx, bond2to3) // test all bond retrieve capabilities - resBonds := keeper.GetDelegations(ctx, addrDels[0], 5) + resBonds := keeper.GetDelegatorDelegations(ctx, addrDels[0], 5) require.Equal(t, 3, len(resBonds)) require.True(t, bond1to1.Equal(resBonds[0])) require.True(t, bond1to2.Equal(resBonds[1])) require.True(t, bond1to3.Equal(resBonds[2])) - resBonds = keeper.GetDelegations(ctx, addrDels[0], 3) + resBonds = keeper.GetAllDelegatorDelegations(ctx, addrDels[0]) require.Equal(t, 3, len(resBonds)) - resBonds = keeper.GetDelegations(ctx, addrDels[0], 2) + resBonds = keeper.GetDelegatorDelegations(ctx, addrDels[0], 2) require.Equal(t, 2, len(resBonds)) - resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) + resBonds = keeper.GetDelegatorDelegations(ctx, addrDels[1], 5) require.Equal(t, 3, len(resBonds)) require.True(t, bond2to1.Equal(resBonds[0])) require.True(t, bond2to2.Equal(resBonds[1])) @@ -89,15 +90,34 @@ func TestDelegation(t *testing.T) { require.True(t, bond2to2.Equal(allBonds[4])) require.True(t, bond2to3.Equal(allBonds[5])) + resVals := keeper.GetDelegatorValidators(ctx, addrDels[0], 3) + require.Equal(t, 3, len(resVals)) + resVals = keeper.GetDelegatorValidators(ctx, addrDels[1], 4) + require.Equal(t, 3, len(resVals)) + + for i := 0; i < 3; i++ { + + resVal, err := keeper.GetDelegatorValidator(ctx, addrDels[0], addrVals[i]) + require.Nil(t, err) + require.Equal(t, addrVals[i], resVal.GetOperator()) + + resVal, err = keeper.GetDelegatorValidator(ctx, addrDels[1], addrVals[i]) + require.Nil(t, err) + require.Equal(t, addrVals[i], resVal.GetOperator()) + } + // delete a record keeper.RemoveDelegation(ctx, bond2to3) _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[2]) require.False(t, found) - resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) + resBonds = keeper.GetDelegatorDelegations(ctx, addrDels[1], 5) require.Equal(t, 2, len(resBonds)) require.True(t, bond2to1.Equal(resBonds[0])) require.True(t, bond2to2.Equal(resBonds[1])) + resBonds = keeper.GetAllDelegatorDelegations(ctx, addrDels[1]) + require.Equal(t, 2, len(resBonds)) + // delete all the records from delegator 2 keeper.RemoveDelegation(ctx, bond2to1) keeper.RemoveDelegation(ctx, bond2to2) @@ -105,7 +125,7 @@ func TestDelegation(t *testing.T) { require.False(t, found) _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[1]) require.False(t, found) - resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) + resBonds = keeper.GetDelegatorDelegations(ctx, addrDels[1], 5) require.Equal(t, 0, len(resBonds)) } @@ -123,21 +143,35 @@ func TestUnbondingDelegation(t *testing.T) { // set and retrieve a record keeper.SetUnbondingDelegation(ctx, ubd) - resBond, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + resUnbond, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) - require.True(t, ubd.Equal(resBond)) + require.True(t, ubd.Equal(resUnbond)) // modify a records, save, and retrieve ubd.Balance = sdk.NewInt64Coin("steak", 21) keeper.SetUnbondingDelegation(ctx, ubd) - resBond, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + + resUnbonds := keeper.GetUnbondingDelegations(ctx, addrDels[0], 5) + require.Equal(t, 1, len(resUnbonds)) + + resUnbonds = keeper.GetAllUnbondingDelegations(ctx, addrDels[0]) + require.Equal(t, 1, len(resUnbonds)) + + resUnbond, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) - require.True(t, ubd.Equal(resBond)) + require.True(t, ubd.Equal(resUnbond)) // delete a record keeper.RemoveUnbondingDelegation(ctx, ubd) _, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.False(t, found) + + resUnbonds = keeper.GetUnbondingDelegations(ctx, addrDels[0], 5) + require.Equal(t, 0, len(resUnbonds)) + + resUnbonds = keeper.GetAllUnbondingDelegations(ctx, addrDels[0]) + require.Equal(t, 0, len(resUnbonds)) + } func TestUnbondDelegation(t *testing.T) { @@ -413,12 +447,20 @@ func TestRedelegation(t *testing.T) { // set and retrieve a record keeper.SetRedelegation(ctx, rd) - resBond, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + resRed, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.True(t, found) redelegations := keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) require.Equal(t, 1, len(redelegations)) - require.True(t, redelegations[0].Equal(resBond)) + require.True(t, redelegations[0].Equal(resRed)) + + redelegations = keeper.GetRedelegations(ctx, addrDels[0], 5) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resRed)) + + redelegations = keeper.GetAllRedelegations(ctx, addrDels[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resRed)) // check if has the redelegation has = keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1]) @@ -429,18 +471,28 @@ func TestRedelegation(t *testing.T) { rd.SharesDst = sdk.NewDec(21) keeper.SetRedelegation(ctx, rd) - resBond, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + resRed, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.True(t, found) - require.True(t, rd.Equal(resBond)) + require.True(t, rd.Equal(resRed)) redelegations = keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) require.Equal(t, 1, len(redelegations)) - require.True(t, redelegations[0].Equal(resBond)) + require.True(t, redelegations[0].Equal(resRed)) + + redelegations = keeper.GetRedelegations(ctx, addrDels[0], 5) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resRed)) // delete a record keeper.RemoveRedelegation(ctx, rd) _, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.False(t, found) + + redelegations = keeper.GetRedelegations(ctx, addrDels[0], 5) + require.Equal(t, 0, len(redelegations)) + + redelegations = keeper.GetAllRedelegations(ctx, addrDels[0]) + require.Equal(t, 0, len(redelegations)) } func TestRedelegateSelfDelegation(t *testing.T) { diff --git a/x/stake/keeper/hooks.go b/x/stake/keeper/hooks.go new file mode 100644 index 000000000..81bf5594b --- /dev/null +++ b/x/stake/keeper/hooks.go @@ -0,0 +1,54 @@ +//nolint +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Expose the hooks if present +func (k Keeper) OnValidatorCreated(ctx sdk.Context, address sdk.ValAddress) { + if k.hooks != nil { + k.hooks.OnValidatorCreated(ctx, address) + } +} +func (k Keeper) OnValidatorCommissionChange(ctx sdk.Context, address sdk.ValAddress) { + if k.hooks != nil { + k.hooks.OnValidatorCommissionChange(ctx, address) + } +} + +func (k Keeper) OnValidatorRemoved(ctx sdk.Context, address sdk.ValAddress) { + if k.hooks != nil { + k.hooks.OnValidatorRemoved(ctx, address) + } +} + +func (k Keeper) OnValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { + if k.hooks != nil { + k.hooks.OnValidatorBonded(ctx, address) + } +} + +func (k Keeper) OnValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) { + if k.hooks != nil { + k.hooks.OnValidatorBeginUnbonding(ctx, address) + } +} + +func (k Keeper) OnDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.OnDelegationCreated(ctx, delAddr, valAddr) + } +} + +func (k Keeper) OnDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.OnDelegationSharesModified(ctx, delAddr, valAddr) + } +} + +func (k Keeper) OnDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.OnDelegationRemoved(ctx, delAddr, valAddr) + } +} diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go index c928c9744..82170a4ae 100644 --- a/x/stake/keeper/keeper.go +++ b/x/stake/keeper/keeper.go @@ -1,8 +1,8 @@ package keeper import ( + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/stake/types" @@ -10,32 +10,34 @@ import ( // keeper of the stake store type Keeper struct { - storeKey sdk.StoreKey - cdc *wire.Codec - bankKeeper bank.Keeper - validatorHooks sdk.ValidatorHooks + storeKey sdk.StoreKey + storeTKey sdk.StoreKey + cdc *codec.Codec + bankKeeper bank.Keeper + hooks sdk.StakingHooks // codespace codespace sdk.CodespaceType } -func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { +func NewKeeper(cdc *codec.Codec, key, tkey sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { keeper := Keeper{ - storeKey: key, - cdc: cdc, - bankKeeper: ck, - validatorHooks: nil, - codespace: codespace, + storeKey: key, + storeTKey: tkey, + cdc: cdc, + bankKeeper: ck, + hooks: nil, + codespace: codespace, } return keeper } // Set the validator hooks -func (k Keeper) WithValidatorHooks(v sdk.ValidatorHooks) Keeper { - if k.validatorHooks != nil { +func (k Keeper) WithHooks(sh sdk.StakingHooks) Keeper { + if k.hooks != nil { panic("cannot set validator hooks twice") } - k.validatorHooks = v + k.hooks = sh return k } diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index 9dbf50c03..91e6a6970 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -3,8 +3,6 @@ package keeper import ( "encoding/binary" - "github.com/tendermint/tendermint/crypto" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" ) @@ -17,19 +15,21 @@ var ( ParamKey = []byte{0x00} // key for parameters relating to staking PoolKey = []byte{0x01} // key for the staking pools ValidatorsKey = []byte{0x02} // prefix for each key to a validator - ValidatorsByPubKeyIndexKey = []byte{0x03} // prefix for each key to a validator index, by pubkey + 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 ValidatorCliffIndexKey = []byte{0x06} // key for the validator index of the cliff validator ValidatorPowerCliffKey = []byte{0x07} // key for the power of the validator on the cliff - TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated - IntraTxCounterKey = []byte{0x09} // key for intra-block tx index - DelegationKey = []byte{0x0A} // key for a delegation - UnbondingDelegationKey = []byte{0x0B} // key for an unbonding-delegation - UnbondingDelegationByValIndexKey = []byte{0x0C} // prefix for each key for an unbonding-delegation, by validator operator - RedelegationKey = []byte{0x0D} // key for a redelegation - RedelegationByValSrcIndexKey = []byte{0x0E} // prefix for each key for an redelegation, by source validator operator - RedelegationByValDstIndexKey = []byte{0x0F} // prefix for each key for an redelegation, by destination validator operator + IntraTxCounterKey = []byte{0x08} // key for intra-block tx index + DelegationKey = []byte{0x09} // key for a delegation + UnbondingDelegationKey = []byte{0x0A} // key for an unbonding-delegation + UnbondingDelegationByValIndexKey = []byte{0x0B} // prefix for each key for an unbonding-delegation, by validator operator + RedelegationKey = []byte{0x0C} // key for a redelegation + RedelegationByValSrcIndexKey = []byte{0x0D} // prefix for each key for an redelegation, by source validator operator + RedelegationByValDstIndexKey = []byte{0x0E} // prefix for each key for an redelegation, by destination validator operator + + // Keys for store prefixes (transient) + TendermintUpdatesTKey = []byte{0x00} // prefix for each key to a validator which is being updated ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch @@ -42,8 +42,8 @@ func GetValidatorKey(operatorAddr sdk.ValAddress) []byte { // gets the key for the validator with pubkey // VALUE: validator operator address ([]byte) -func GetValidatorByPubKeyIndexKey(pubkey crypto.PubKey) []byte { - return append(ValidatorsByPubKeyIndexKey, pubkey.Bytes()...) +func GetValidatorByConsAddrKey(addr sdk.ConsAddress) []byte { + return append(ValidatorsByConsAddrKey, addr.Bytes()...) } // gets the key for the current validator group @@ -98,8 +98,8 @@ func getValidatorPowerRank(validator types.Validator, pool types.Pool) []byte { // get the key for the accumulated update validators // VALUE: abci.Validator // note records using these keys should never persist between blocks -func GetTendermintUpdatesKey(operatorAddr sdk.ValAddress) []byte { - return append(TendermintUpdatesKey, operatorAddr.Bytes()...) +func GetTendermintUpdatesTKey(operatorAddr sdk.ValAddress) []byte { + return append(TendermintUpdatesTKey, operatorAddr.Bytes()...) } //______________________________________________________________________________ diff --git a/x/stake/keeper/query_utils.go b/x/stake/keeper/query_utils.go new file mode 100644 index 000000000..c1575cd42 --- /dev/null +++ b/x/stake/keeper/query_utils.go @@ -0,0 +1,101 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// Return all validators that a delegator is bonded to. If maxRetrieve is supplied, the respective amount will be returned. +func (k Keeper) GetDelegatorValidators(ctx sdk.Context, delegatorAddr sdk.AccAddress, + maxRetrieve uint16) (validators []types.Validator) { + validators = make([]types.Validator, maxRetrieve) + + store := ctx.KVStore(k.storeKey) + delegatorPrefixKey := GetDelegationsKey(delegatorAddr) + iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest + defer iterator.Close() + + i := 0 + for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() { + addr := iterator.Key() + delegation := types.MustUnmarshalDelegation(k.cdc, addr, iterator.Value()) + + validator, found := k.GetValidator(ctx, delegation.ValidatorAddr) + if !found { + panic(types.ErrNoValidatorFound(types.DefaultCodespace)) + } + validators[i] = validator + i++ + } + return validators[:i] // trim +} + +// return a validator that a delegator is bonded to +func (k Keeper) GetDelegatorValidator(ctx sdk.Context, delegatorAddr sdk.AccAddress, + validatorAddr sdk.ValAddress) (validator types.Validator, err sdk.Error) { + + delegation, found := k.GetDelegation(ctx, delegatorAddr, validatorAddr) + if !found { + return validator, types.ErrNoDelegation(types.DefaultCodespace) + } + + validator, found = k.GetValidator(ctx, delegation.ValidatorAddr) + if !found { + panic(types.ErrNoValidatorFound(types.DefaultCodespace)) + } + return +} + +//_____________________________________________________________________________________ + +// return all delegations for a delegator +func (k Keeper) GetAllDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddress) ( + delegations []types.Delegation) { + + store := ctx.KVStore(k.storeKey) + delegatorPrefixKey := GetDelegationsKey(delegator) + iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest + defer iterator.Close() + + i := 0 + for ; iterator.Valid(); iterator.Next() { + delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) + delegations = append(delegations, delegation) + i++ + } + return delegations +} + +// return all unbonding-delegations for a delegator +func (k Keeper) GetAllUnbondingDelegations(ctx sdk.Context, delegator sdk.AccAddress) ( + unbondingDelegations []types.UnbondingDelegation) { + + store := ctx.KVStore(k.storeKey) + delegatorPrefixKey := GetUBDsKey(delegator) + iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest + defer iterator.Close() + + i := 0 + for ; iterator.Valid(); iterator.Next() { + unbondingDelegation := types.MustUnmarshalUBD(k.cdc, iterator.Key(), iterator.Value()) + unbondingDelegations = append(unbondingDelegations, unbondingDelegation) + i++ + } + return unbondingDelegations +} + +// return all redelegations for a delegator +func (k Keeper) GetAllRedelegations(ctx sdk.Context, delegator sdk.AccAddress) (redelegations []types.Redelegation) { + store := ctx.KVStore(k.storeKey) + delegatorPrefixKey := GetREDsKey(delegator) + iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest + defer iterator.Close() + + i := 0 + for ; iterator.Valid(); iterator.Next() { + redelegation := types.MustUnmarshalRED(k.cdc, iterator.Key(), iterator.Value()) + redelegations = append(redelegations, redelegation) + i++ + } + return redelegations +} diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go index e4c7a1c19..d702e845d 100644 --- a/x/stake/keeper/sdk_types.go +++ b/x/stake/keeper/sdk_types.go @@ -3,8 +3,6 @@ package keeper import ( "fmt" - "github.com/tendermint/tendermint/crypto" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" ) @@ -60,8 +58,8 @@ func (k Keeper) Validator(ctx sdk.Context, address sdk.ValAddress) sdk.Validator } // get the sdk.validator for a particular pubkey -func (k Keeper) ValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) sdk.Validator { - val, found := k.GetValidatorByPubKey(ctx, pubkey) +func (k Keeper) ValidatorByConsAddr(ctx sdk.Context, addr sdk.ConsAddress) sdk.Validator { + val, found := k.GetValidatorByConsAddr(ctx, addr) if !found { return nil } @@ -95,15 +93,16 @@ func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.AccAddress, addrVal sdk. return bond } -// iterate through the active validator set and perform the provided function -func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.AccAddress, fn func(index int64, delegation sdk.Delegation) (stop bool)) { +// iterate through all of the delegations from a delegator +func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.AccAddress, + fn func(index int64, del sdk.Delegation) (stop bool)) { + store := ctx.KVStore(k.storeKey) - key := GetDelegationsKey(delAddr) - iterator := sdk.KVStorePrefixIterator(store, key) - i := int64(0) - for ; iterator.Valid(); iterator.Next() { - delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) - stop := fn(i, delegation) // XXX is this safe will the fields be able to get written to? + delegatorPrefixKey := GetDelegationsKey(delAddr) + iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest + for i := int64(0); iterator.Valid(); iterator.Next() { + del := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) + stop := fn(i, del) if stop { break } diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index 5a004e807..7bfe4eb25 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -5,7 +5,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" types "github.com/cosmos/cosmos-sdk/x/stake/types" - "github.com/tendermint/tendermint/crypto" ) // Slash a validator for an infraction committed at a known height @@ -22,13 +21,11 @@ import ( // CONTRACT: // Infraction committed at the current height or at a past height, // not at a height in the future -// -// nolint: gocyclo -func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight int64, power int64, slashFactor sdk.Dec) { +func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeight int64, power int64, slashFactor sdk.Dec) { logger := ctx.Logger().With("module", "x/stake") if slashFactor.LT(sdk.ZeroDec()) { - panic(fmt.Errorf("attempted to slash with a negative slashFactor: %v", slashFactor)) + panic(fmt.Errorf("attempted to slash with a negative slash factor: %v", slashFactor)) } // Amount of slashing = slash slashFactor * power at time of infraction @@ -36,7 +33,7 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in // ref https://github.com/cosmos/cosmos-sdk/issues/1348 // ref https://github.com/cosmos/cosmos-sdk/issues/1471 - validator, found := k.GetValidatorByPubKey(ctx, pubkey) + validator, found := k.GetValidatorByConsAddr(ctx, consAddr) if !found { // If not found, the validator must have been overslashed and removed - so we don't need to do anything // NOTE: Correctness dependent on invariant that unbonding delegations / redelegations must also have been completely @@ -44,13 +41,13 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in // Log the slash attempt for future reference (maybe we should tag it too) logger.Error(fmt.Sprintf( "WARNING: Ignored attempt to slash a nonexistent validator with address %s, we recommend you investigate immediately", - pubkey.Address())) + consAddr)) return } // should not be slashing unbonded if validator.IsUnbonded(ctx) { - panic(fmt.Sprintf("should not be slashing unbonded validator: %v", validator)) + panic(fmt.Sprintf("should not be slashing unbonded validator: %s", validator.GetOperator())) } operatorAddress := validator.GetOperator() @@ -72,7 +69,7 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in // Special-case slash at current height for efficiency - we don't need to look through unbonding delegations or redelegations logger.Info(fmt.Sprintf( - "Slashing at current height %d, not scanning unbonding delegations & redelegations", + "slashing at current height %d, not scanning unbonding delegations & redelegations", infractionHeight)) case infractionHeight < ctx.BlockHeight(): @@ -117,36 +114,36 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in // Log that a slash occurred! logger.Info(fmt.Sprintf( - "Validator %s slashed by slashFactor %s, burned %v tokens", - pubkey.Address(), slashFactor.String(), tokensToBurn)) + "validator %s slashed by slash factor of %s; burned %v tokens", + validator.GetOperator(), slashFactor.String(), tokensToBurn)) // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 return } // jail a validator -func (k Keeper) Jail(ctx sdk.Context, pubkey crypto.PubKey) { - k.setJailed(ctx, pubkey, true) +func (k Keeper) Jail(ctx sdk.Context, consAddr sdk.ConsAddress) { + k.setJailed(ctx, consAddr, true) logger := ctx.Logger().With("module", "x/stake") - logger.Info(fmt.Sprintf("Validator %s jailed", pubkey.Address())) + logger.Info(fmt.Sprintf("validator %s jailed", consAddr)) // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 return } // unjail a validator -func (k Keeper) Unjail(ctx sdk.Context, pubkey crypto.PubKey) { - k.setJailed(ctx, pubkey, false) +func (k Keeper) Unjail(ctx sdk.Context, consAddr sdk.ConsAddress) { + k.setJailed(ctx, consAddr, false) logger := ctx.Logger().With("module", "x/stake") - logger.Info(fmt.Sprintf("Validator %s unjailed", pubkey.Address())) + logger.Info(fmt.Sprintf("validator %s unjailed", consAddr)) // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 return } // set the jailed flag on a validator -func (k Keeper) setJailed(ctx sdk.Context, pubkey crypto.PubKey, isJailed bool) { - validator, found := k.GetValidatorByPubKey(ctx, pubkey) +func (k Keeper) setJailed(ctx sdk.Context, consAddr sdk.ConsAddress, isJailed bool) { + validator, found := k.GetValidatorByConsAddr(ctx, consAddr) if !found { - panic(fmt.Errorf("Validator with pubkey %s not found, cannot set jailed to %v", pubkey, isJailed)) + panic(fmt.Errorf("validator with consensus-Address %s not found, cannot set jailed to %v", consAddr, isJailed)) } validator.Jailed = isJailed k.UpdateValidator(ctx, validator) // update validator, possibly unbonding or bonding it @@ -175,7 +172,7 @@ func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation ty } // Calculate slash amount proportional to stake contributing to infraction - slashAmount = sdk.NewDecFromInt(unbondingDelegation.InitialBalance.Amount).Mul(slashFactor) + slashAmount = slashFactor.MulInt(unbondingDelegation.InitialBalance.Amount) // Don't slash more tokens than held // Possible since the unbonding delegation may already @@ -221,7 +218,7 @@ func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, re } // Calculate slash amount proportional to stake contributing to infraction - slashAmount = sdk.NewDecFromInt(redelegation.InitialBalance.Amount).Mul(slashFactor) + slashAmount = slashFactor.MulInt(redelegation.InitialBalance.Amount) // Don't slash more tokens than held // Possible since the redelegation may already diff --git a/x/stake/keeper/slash_test.go b/x/stake/keeper/slash_test.go index 65bac2d80..0a6cccac2 100644 --- a/x/stake/keeper/slash_test.go +++ b/x/stake/keeper/slash_test.go @@ -14,6 +14,7 @@ import ( // TODO integrate with test_common.go helper (CreateTestInput) // setup helper function - creates two validators func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) { + // setup ctx, _, keeper := CreateTestInput(t, false, amt) params := keeper.GetParams(ctx) @@ -27,7 +28,7 @@ func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) { validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) validator = keeper.UpdateValidator(ctx, validator) - keeper.SetValidatorByPubKeyIndex(ctx, validator) + keeper.SetValidatorByConsAddr(ctx, validator) } pool = keeper.GetPool(ctx) @@ -42,7 +43,7 @@ func TestRevocation(t *testing.T) { // setup ctx, keeper, _ := setupHelper(t, 10) addr := addrVals[0] - pk := PKs[0] + consAddr := sdk.ConsAddress(PKs[0].Address()) // initial state val, found := keeper.GetValidator(ctx, addr) @@ -50,13 +51,13 @@ func TestRevocation(t *testing.T) { require.False(t, val.GetJailed()) // test jail - keeper.Jail(ctx, pk) + keeper.Jail(ctx, consAddr) val, found = keeper.GetValidator(ctx, addr) require.True(t, found) require.True(t, val.GetJailed()) // test unjail - keeper.Unjail(ctx, pk) + keeper.Unjail(ctx, consAddr) val, found = keeper.GetValidator(ctx, addr) require.True(t, found) require.False(t, val.GetJailed()) @@ -179,24 +180,24 @@ func TestSlashRedelegation(t *testing.T) { // tests Slash at a future height (must panic) func TestSlashAtFutureHeight(t *testing.T) { ctx, keeper, _ := setupHelper(t, 10) - pk := PKs[0] + consAddr := sdk.ConsAddress(PKs[0].Address()) fraction := sdk.NewDecWithPrec(5, 1) - require.Panics(t, func() { keeper.Slash(ctx, pk, 1, 10, fraction) }) + require.Panics(t, func() { keeper.Slash(ctx, consAddr, 1, 10, fraction) }) } // tests Slash at the current height func TestSlashValidatorAtCurrentHeight(t *testing.T) { ctx, keeper, _ := setupHelper(t, 10) - pk := PKs[0] + consAddr := sdk.ConsAddress(PKs[0].Address()) fraction := sdk.NewDecWithPrec(5, 1) oldPool := keeper.GetPool(ctx) - validator, found := keeper.GetValidatorByPubKey(ctx, pk) + validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) - keeper.Slash(ctx, pk, ctx.BlockHeight(), 10, fraction) + keeper.Slash(ctx, consAddr, ctx.BlockHeight(), 10, fraction) // read updated state - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) newPool := keeper.GetPool(ctx) @@ -209,7 +210,7 @@ func TestSlashValidatorAtCurrentHeight(t *testing.T) { // tests Slash at a previous height with an unbonding delegation func TestSlashWithUnbondingDelegation(t *testing.T) { ctx, keeper, params := setupHelper(t, 10) - pk := PKs[0] + consAddr := sdk.ConsAddress(PKs[0].Address()) fraction := sdk.NewDecWithPrec(5, 1) // set an unbonding delegation @@ -227,9 +228,9 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // slash validator for the first time ctx = ctx.WithBlockHeight(12) oldPool := keeper.GetPool(ctx) - validator, found := keeper.GetValidatorByPubKey(ctx, pk) + validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) - keeper.Slash(ctx, pk, 10, 10, fraction) + keeper.Slash(ctx, consAddr, 10, 10, fraction) // read updating unbonding delegation ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) @@ -241,7 +242,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // bonded tokens burned require.Equal(t, int64(3), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) // power decreased by 3 - 6 stake originally bonded at the time of infraction // was still bonded at the time of discovery and was slashed by half, 4 stake @@ -251,7 +252,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // slash validator again ctx = ctx.WithBlockHeight(13) - keeper.Slash(ctx, pk, 9, 10, fraction) + keeper.Slash(ctx, consAddr, 9, 10, fraction) ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) // balance decreased again @@ -261,7 +262,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // bonded tokens burned again require.Equal(t, int64(6), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) // power decreased by 3 again require.Equal(t, sdk.NewDec(4), validator.GetPower()) @@ -271,7 +272,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // on the unbonding delegation, but it will slash stake bonded since the infraction // this may not be the desirable behaviour, ref https://github.com/cosmos/cosmos-sdk/issues/1440 ctx = ctx.WithBlockHeight(13) - keeper.Slash(ctx, pk, 9, 10, fraction) + keeper.Slash(ctx, consAddr, 9, 10, fraction) ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) // balance unchanged @@ -281,7 +282,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // bonded tokens burned again require.Equal(t, int64(9), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) // power decreased by 3 again require.Equal(t, sdk.NewDec(1), validator.GetPower()) @@ -291,7 +292,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // on the unbonding delegation, but it will slash stake bonded since the infraction // this may not be the desirable behaviour, ref https://github.com/cosmos/cosmos-sdk/issues/1440 ctx = ctx.WithBlockHeight(13) - keeper.Slash(ctx, pk, 9, 10, fraction) + keeper.Slash(ctx, consAddr, 9, 10, fraction) ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) // balance unchanged @@ -303,14 +304,14 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // read updated validator // power decreased by 1 again, validator is out of stake // ergo validator should have been removed from the store - _, found = keeper.GetValidatorByPubKey(ctx, pk) + _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.False(t, found) } // tests Slash at a previous height with a redelegation func TestSlashWithRedelegation(t *testing.T) { ctx, keeper, params := setupHelper(t, 10) - pk := PKs[0] + consAddr := sdk.ConsAddress(PKs[0].Address()) fraction := sdk.NewDecWithPrec(5, 1) // set a redelegation @@ -343,9 +344,9 @@ func TestSlashWithRedelegation(t *testing.T) { // slash validator ctx = ctx.WithBlockHeight(12) oldPool := keeper.GetPool(ctx) - validator, found := keeper.GetValidatorByPubKey(ctx, pk) + validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) - keeper.Slash(ctx, pk, 10, 10, fraction) + keeper.Slash(ctx, consAddr, 10, 10, fraction) // read updating redelegation rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) @@ -357,7 +358,7 @@ func TestSlashWithRedelegation(t *testing.T) { // bonded tokens burned require.Equal(t, int64(5), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) // power decreased by 2 - 4 stake originally bonded at the time of infraction // was still bonded at the time of discovery and was slashed by half, 4 stake @@ -367,9 +368,9 @@ func TestSlashWithRedelegation(t *testing.T) { // slash the validator again ctx = ctx.WithBlockHeight(12) - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) - require.NotPanics(t, func() { keeper.Slash(ctx, pk, 10, 10, sdk.OneDec()) }) + require.NotPanics(t, func() { keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) }) // read updating redelegation rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) @@ -381,16 +382,16 @@ func TestSlashWithRedelegation(t *testing.T) { // seven bonded tokens burned require.Equal(t, int64(12), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) // power decreased by 4 require.Equal(t, sdk.NewDec(4), validator.GetPower()) // slash the validator again, by 100% ctx = ctx.WithBlockHeight(12) - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) - keeper.Slash(ctx, pk, 10, 10, sdk.OneDec()) + keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) // read updating redelegation rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) @@ -403,16 +404,16 @@ func TestSlashWithRedelegation(t *testing.T) { require.Equal(t, int64(16), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator // validator decreased to zero power, should have been removed from the store - _, found = keeper.GetValidatorByPubKey(ctx, pk) + _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.False(t, found) // slash the validator again, by 100% // no stake remains to be slashed ctx = ctx.WithBlockHeight(12) // validator no longer in the store - _, found = keeper.GetValidatorByPubKey(ctx, pk) + _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.False(t, found) - keeper.Slash(ctx, pk, 10, 10, sdk.OneDec()) + keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) // read updating redelegation rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) @@ -425,7 +426,7 @@ func TestSlashWithRedelegation(t *testing.T) { require.Equal(t, int64(16), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator // power still zero, still not in the store - _, found = keeper.GetValidatorByPubKey(ctx, pk) + _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.False(t, found) } @@ -472,9 +473,10 @@ func TestSlashBoth(t *testing.T) { // slash validator ctx = ctx.WithBlockHeight(12) oldPool := keeper.GetPool(ctx) - validator, found := keeper.GetValidatorByPubKey(ctx, PKs[0]) + validator, found := keeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(PKs[0])) require.True(t, found) - keeper.Slash(ctx, PKs[0], 10, 10, fraction) + consAddr0 := sdk.ConsAddress(PKs[0].Address()) + keeper.Slash(ctx, consAddr0, 10, 10, fraction) // read updating redelegation rdA, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) @@ -488,7 +490,7 @@ func TestSlashBoth(t *testing.T) { // bonded tokens burned require.Equal(t, int64(3), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator - validator, found = keeper.GetValidatorByPubKey(ctx, PKs[0]) + validator, found = keeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(PKs[0])) require.True(t, found) // power not decreased, all stake was bonded since require.Equal(t, sdk.NewDec(10), validator.GetPower()) diff --git a/x/stake/keeper/test_common.go b/x/stake/keeper/test_common.go index b84ce9d93..03d4642b6 100644 --- a/x/stake/keeper/test_common.go +++ b/x/stake/keeper/test_common.go @@ -14,9 +14,9 @@ import ( 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/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/stake/types" @@ -52,8 +52,8 @@ func ValEq(t *testing.T, exp, got types.Validator) (*testing.T, bool, string, ty //_______________________________________________________________________________________ // create a codec used only for testing -func MakeTestCodec() *wire.Codec { - var cdc = wire.NewCodec() +func MakeTestCodec() *codec.Codec { + var cdc = codec.New() // Register Msgs cdc.RegisterInterface((*sdk.Msg)(nil), nil) @@ -69,7 +69,7 @@ func MakeTestCodec() *wire.Codec { // Register AppAccount cdc.RegisterInterface((*auth.Account)(nil), nil) cdc.RegisterConcrete(&auth.BaseAccount{}, "test/stake/Account", nil) - wire.RegisterCrypto(cdc) + codec.RegisterCrypto(cdc) return cdc } @@ -90,10 +90,12 @@ func ParamsNoInflation() types.Params { func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, auth.AccountMapper, Keeper) { keyStake := sdk.NewKVStoreKey("stake") + tkeyStake := sdk.NewTransientStoreKey("transient_stake") keyAcc := sdk.NewKVStoreKey("acc") db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(tkeyStake, sdk.StoreTypeTransient, nil) ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) err := ms.LoadLatestVersion() @@ -106,8 +108,8 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context keyAcc, // target store auth.ProtoBaseAccount, // prototype ) - ck := bank.NewKeeper(accountMapper) - keeper := NewKeeper(cdc, keyStake, ck, types.DefaultCodespace) + ck := bank.NewBaseKeeper(accountMapper) + keeper := NewKeeper(cdc, keyStake, tkeyStake, ck, types.DefaultCodespace) keeper.SetPool(ctx, types.InitialPool()) keeper.SetNewParams(ctx, types.DefaultParams()) keeper.InitIntraTxCounter(ctx) diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index faa7b451a..668a2af23 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -6,7 +6,7 @@ import ( "fmt" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" + tmtypes "github.com/tendermint/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" @@ -58,14 +58,14 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator ty return validator, true } -// get a single validator by pubkey -func (k Keeper) GetValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) (validator types.Validator, found bool) { +// get a single validator by consensus address +func (k Keeper) GetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) (validator types.Validator, found bool) { store := ctx.KVStore(k.storeKey) - addr := store.Get(GetValidatorByPubKeyIndexKey(pubkey)) - if addr == nil { + opAddr := store.Get(GetValidatorByConsAddrKey(consAddr)) + if opAddr == nil { return validator, false } - return k.GetValidator(ctx, addr) + return k.GetValidator(ctx, opAddr) } // set the main record holding validator details @@ -76,9 +76,10 @@ func (k Keeper) SetValidator(ctx sdk.Context, validator types.Validator) { } // validator index -func (k Keeper) SetValidatorByPubKeyIndex(ctx sdk.Context, validator types.Validator) { +func (k Keeper) SetValidatorByConsAddr(ctx sdk.Context, validator types.Validator) { store := ctx.KVStore(k.storeKey) - store.Set(GetValidatorByPubKeyIndexKey(validator.ConsPubKey), validator.OperatorAddr) + consAddr := sdk.ConsAddress(validator.ConsPubKey.Address()) + store.Set(GetValidatorByConsAddrKey(consAddr), validator.OperatorAddr) } // validator index @@ -103,39 +104,32 @@ func (k Keeper) validatorByPowerIndexExists(ctx sdk.Context, power []byte) bool func (k Keeper) GetAllValidators(ctx sdk.Context) (validators []types.Validator) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) + defer iterator.Close() - i := 0 - for ; ; i++ { - if !iterator.Valid() { - break - } + for ; iterator.Valid(); iterator.Next() { addr := iterator.Key()[1:] validator := types.MustUnmarshalValidator(k.cdc, addr, iterator.Value()) validators = append(validators, validator) - iterator.Next() } - iterator.Close() return validators } -// Get the set of all validators, retrieve a maxRetrieve number of records -func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve int16) (validators []types.Validator) { +// return a given amount of all the validators +func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve uint16) (validators []types.Validator) { store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) - validators = make([]types.Validator, maxRetrieve) + + iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) + defer iterator.Close() + i := 0 - for ; ; i++ { - if !iterator.Valid() || i > int(maxRetrieve-1) { - break - } + for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() { addr := iterator.Key()[1:] validator := types.MustUnmarshalValidator(k.cdc, addr, iterator.Value()) validators[i] = validator - iterator.Next() + i++ } - iterator.Close() - return validators[:i] // trim + return validators[:i] // trim if the array length < maxRetrieve } //___________________________________________________________________________ @@ -149,6 +143,8 @@ func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validat validators = make([]types.Validator, maxValidators) iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + defer iterator.Close() + i := 0 for ; iterator.Valid(); iterator.Next() { @@ -158,14 +154,11 @@ func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validat } address := GetAddressFromValBondedIndexKey(iterator.Key()) validator, found := k.GetValidator(ctx, address) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", address)) - } + ensureValidatorFound(found, address) validators[i] = validator i++ } - iterator.Close() return validators[:i] // trim } @@ -176,24 +169,21 @@ func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []types.Validator { store := ctx.KVStore(k.storeKey) maxValidators := k.GetParams(ctx).MaxValidators validators := make([]types.Validator, maxValidators) - iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) // largest to smallest + + iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) + defer iterator.Close() + i := 0 - for { - if !iterator.Valid() || i > int(maxValidators-1) { - break - } + for ; iterator.Valid() && i < int(maxValidators); iterator.Next() { address := iterator.Value() validator, found := k.GetValidator(ctx, address) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", address)) - } + ensureValidatorFound(found, address) + if validator.Status == sdk.Bonded { validators[i] = validator i++ } - iterator.Next() } - iterator.Close() return validators[:i] // trim } @@ -201,32 +191,44 @@ func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []types.Validator { // Accumulated updates to the active/bonded validator set for tendermint // get the most recently updated validators +// +// 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 +// are returned to Tendermint. func (k Keeper) GetTendermintUpdates(ctx sdk.Context) (updates []abci.ValidatorUpdate) { - store := ctx.KVStore(k.storeKey) + tstore := ctx.TransientStore(k.storeTKey) - iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) //smallest to largest + iterator := sdk.KVStorePrefixIterator(tstore, TendermintUpdatesTKey) + defer iterator.Close() for ; iterator.Valid(); iterator.Next() { - valBytes := iterator.Value() - var val abci.ValidatorUpdate - k.cdc.MustUnmarshalBinary(valBytes, &val) - updates = append(updates, val) + var abciVal abci.ValidatorUpdate + + abciValBytes := iterator.Value() + k.cdc.MustUnmarshalBinary(abciValBytes, &abciVal) + + pub, err := tmtypes.PB2TM.PubKey(abciVal.GetPubKey()) + if err != nil { + panic(err) + } + val, found := k.GetValidator(ctx, sdk.ValAddress(pub.Address())) + if found { + // The validator is new or already exists in the store and must adhere to + // Tendermint invariants. + prevBonded := val.BondHeight < ctx.BlockHeight() && val.BondHeight > val.UnbondingHeight + zeroPower := val.GetPower().Equal(sdk.ZeroDec()) + + if !zeroPower || zeroPower && prevBonded { + updates = append(updates, abciVal) + } + } else { + // Add the ABCI validator in such a case where the validator was removed + // from the store as it must have existed before. + updates = append(updates, abciVal) + } } - iterator.Close() return } -// remove all validator update entries after applied to Tendermint -func (k Keeper) ClearTendermintUpdates(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - - // delete subspace - iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) - for ; iterator.Valid(); iterator.Next() { - store.Delete(iterator.Key()) - } - iterator.Close() -} - //___________________________________________________________________________ // Perform all the necessary steps for when a validator changes its power. This @@ -234,18 +236,22 @@ func (k Keeper) ClearTendermintUpdates(ctx sdk.Context) { // It may kick out validators if a new validator is entering the bonded validator // group. // -// nolint: gocyclo // TODO: Remove above nolint, function needs to be simplified! func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) types.Validator { - store := ctx.KVStore(k.storeKey) + tstore := ctx.TransientStore(k.storeTKey) pool := k.GetPool(ctx) oldValidator, oldFound := k.GetValidator(ctx, validator.OperatorAddr) validator = k.updateForJailing(ctx, oldFound, oldValidator, validator) powerIncreasing := k.getPowerIncreasing(ctx, oldFound, oldValidator, validator) - validator.BondHeight, validator.BondIntraTxCounter = k.bondIncrement(ctx, oldFound, oldValidator, validator) + validator.BondHeight, validator.BondIntraTxCounter = k.bondIncrement(ctx, oldFound, oldValidator) valPower := k.updateValidatorPower(ctx, oldFound, oldValidator, validator, pool) cliffPower := k.GetCliffValidatorPower(ctx) + cliffValExists := (cliffPower != nil) + var valPowerLTcliffPower bool + if cliffValExists { + valPowerLTcliffPower = (bytes.Compare(valPower, cliffPower) == -1) + } switch { @@ -257,9 +263,9 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type (oldFound && oldValidator.Status == sdk.Bonded): bz := k.cdc.MustMarshalBinary(validator.ABCIValidatorUpdate()) - store.Set(GetTendermintUpdatesKey(validator.OperatorAddr), bz) + tstore.Set(GetTendermintUpdatesTKey(validator.OperatorAddr), bz) - if cliffPower != nil { + if cliffValExists { cliffAddr := sdk.ValAddress(k.GetCliffValidator(ctx)) if bytes.Equal(cliffAddr, validator.OperatorAddr) { k.updateCliffValidator(ctx, validator) @@ -267,14 +273,13 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type } // if is a new validator and the new power is less than the cliff validator - case cliffPower != nil && !oldFound && - bytes.Compare(valPower, cliffPower) == -1: //(valPower < cliffPower + case cliffValExists && !oldFound && valPowerLTcliffPower: // skip to completion // if was unbonded and the new power is less than the cliff validator - case cliffPower != nil && + case cliffValExists && (oldFound && oldValidator.Status == sdk.Unbonded) && - bytes.Compare(valPower, cliffPower) == -1: //(valPower < cliffPower + valPowerLTcliffPower: //(valPower < cliffPower // skip to completion default: @@ -293,7 +298,7 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type // if decreased in power but still bonded, update Tendermint validator if oldFound && oldValidator.BondedTokens().GT(validator.BondedTokens()) { bz := k.cdc.MustMarshalBinary(validator.ABCIValidatorUpdate()) - store.Set(GetTendermintUpdatesKey(validator.OperatorAddr), bz) + tstore.Set(GetTendermintUpdatesTKey(validator.OperatorAddr), bz) } } @@ -315,7 +320,7 @@ func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validato oldCliffVal, found := k.GetValidator(ctx, cliffAddr) if !found { - panic(fmt.Sprintf("cliff validator record not found for address: %v\n", cliffAddr)) + panic(fmt.Sprintf("cliff validator record not found for address: %X\n", cliffAddr)) } // Create a validator iterator ranging from smallest to largest by power @@ -327,12 +332,10 @@ func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validato if iterator.Valid() { ownerAddr := iterator.Value() currVal, found := k.GetValidator(ctx, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } + ensureValidatorFound(found, ownerAddr) if currVal.Status != sdk.Bonded || currVal.Jailed { - panic(fmt.Sprintf("unexpected jailed or unbonded validator for address: %s\n", ownerAddr)) + panic(fmt.Sprintf("unexpected jailed or unbonded validator for address: %X\n", ownerAddr)) } newCliffVal = currVal @@ -345,13 +348,10 @@ func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validato newCliffValRank := GetValidatorsByPowerIndexKey(newCliffVal, pool) if bytes.Equal(affectedVal.OperatorAddr, newCliffVal.OperatorAddr) { - // The affected validator remains the cliff validator, however, since // the store does not contain the new power, update the new power rank. store.Set(ValidatorPowerCliffKey, affectedValRank) - } else if bytes.Compare(affectedValRank, newCliffValRank) > 0 { - // The affected validator no longer remains the cliff validator as it's // power is greater than the new cliff validator. k.setCliffValidator(ctx, newCliffVal, pool) @@ -382,18 +382,20 @@ func (k Keeper) getPowerIncreasing(ctx sdk.Context, oldFound bool, oldValidator, // get the bond height and incremented intra-tx counter // nolint: unparam -func (k Keeper) bondIncrement(ctx sdk.Context, oldFound bool, oldValidator, - newValidator types.Validator) (height int64, intraTxCounter int16) { +func (k Keeper) bondIncrement( + ctx sdk.Context, found bool, oldValidator types.Validator) (height int64, intraTxCounter int16) { - // if already a validator, copy the old block height and counter, else set them - if oldFound && oldValidator.Status == sdk.Bonded { + // if already a validator, copy the old block height and counter + if found && oldValidator.Status == sdk.Bonded { height = oldValidator.BondHeight intraTxCounter = oldValidator.BondIntraTxCounter return } + height = ctx.BlockHeight() counter := k.GetIntraTxCounter(ctx) intraTxCounter = counter + k.SetIntraTxCounter(ctx, counter+1) return } @@ -423,9 +425,6 @@ func (k Keeper) updateValidatorPower(ctx sdk.Context, oldFound bool, oldValidato // updated in store with the ValidatorsBondedIndexKey. This store is used to // determine if a validator is a validator without needing to iterate over all // validators. -// -// nolint: gocyclo -// TODO: Remove the above golint func (k Keeper) UpdateBondedValidators( ctx sdk.Context, affectedValidator types.Validator) ( updatedVal types.Validator, updated bool) { @@ -440,10 +439,7 @@ func (k Keeper) UpdateBondedValidators( // create a validator iterator ranging from largest to smallest by power iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) - for { - if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { - break - } + for ; iterator.Valid() && bondedValidatorsCount < int(maxValidators); iterator.Next() { // either retrieve the original validator from the store, or under the // situation that this is the "affected validator" just use the @@ -454,17 +450,20 @@ func (k Keeper) UpdateBondedValidators( } else { var found bool validator, found = k.GetValidator(ctx, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } + ensureValidatorFound(found, ownerAddr) } // if we've reached jailed validators no further bonded validators exist if validator.Jailed { + if validator.Status == sdk.Bonded { + panic(fmt.Sprintf("jailed validator cannot be bonded, address: %X\n", ownerAddr)) + } + break } - // increment bondedValidatorsCount / get the validator to bond + // increment the total number of bonded validators and potentially mark + // the validator to bond if validator.Status != sdk.Bonded { validatorToBond = validator if newValidatorBonded { @@ -473,15 +472,7 @@ func (k Keeper) UpdateBondedValidators( newValidatorBonded = true } - // increment the total number of bonded validators and potentially mark - // the validator to bond - if validator.Status != sdk.Bonded { - validatorToBond = validator - newValidatorBonded = true - } - bondedValidatorsCount++ - iterator.Next() } iterator.Close() @@ -502,9 +493,7 @@ func (k Keeper) UpdateBondedValidators( if newValidatorBonded { if oldCliffValidatorAddr != nil { oldCliffVal, found := k.GetValidator(ctx, oldCliffValidatorAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", oldCliffValidatorAddr)) - } + ensureValidatorFound(found, oldCliffValidatorAddr) if bytes.Equal(validatorToBond.OperatorAddr, affectedValidator.OperatorAddr) { @@ -512,7 +501,6 @@ func (k Keeper) UpdateBondedValidators( // validator was newly bonded and has greater power k.beginUnbondingValidator(ctx, oldCliffVal) } else { - // otherwise begin unbonding the affected validator, which must // have been kicked out affectedValidator = k.beginUnbondingValidator(ctx, affectedValidator) @@ -551,18 +539,12 @@ func (k Keeper) UpdateBondedValidatorsFull(ctx sdk.Context) { bondedValidatorsCount := 0 iterator = sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) - for { - if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { - break - } - + for ; iterator.Valid() && bondedValidatorsCount < int(maxValidators); iterator.Next() { var found bool ownerAddr := iterator.Value() validator, found = k.GetValidator(ctx, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } + ensureValidatorFound(found, ownerAddr) _, found = toKickOut[string(ownerAddr)] if found { @@ -580,12 +562,10 @@ func (k Keeper) UpdateBondedValidatorsFull(ctx sdk.Context) { if validator.Status == sdk.Bonded { panic(fmt.Sprintf("jailed validator cannot be bonded for address: %s\n", ownerAddr)) } - break } bondedValidatorsCount++ - iterator.Next() } iterator.Close() @@ -605,9 +585,7 @@ func kickOutValidators(k Keeper, ctx sdk.Context, toKickOut map[string]byte) { for key := range toKickOut { ownerAddr := []byte(key) validator, found := k.GetValidator(ctx, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } + ensureValidatorFound(found, ownerAddr) k.beginUnbondingValidator(ctx, validator) } } @@ -637,17 +615,13 @@ func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validat // add to accumulated changes for tendermint bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidatorUpdateZero()) - store.Set(GetTendermintUpdatesKey(validator.OperatorAddr), bzABCI) + tstore := ctx.TransientStore(k.storeTKey) + tstore.Set(GetTendermintUpdatesTKey(validator.OperatorAddr), bzABCI) // also remove from the Bonded types.Validators Store store.Delete(GetValidatorsBondedIndexKey(validator.OperatorAddr)) - // call the unbond hook if present - if k.validatorHooks != nil { - k.validatorHooks.OnValidatorBeginUnbonding(ctx, validator.ConsAddress()) - } - - // return updated validator + k.OnValidatorBeginUnbonding(ctx, validator.ConsAddress()) return validator } @@ -662,6 +636,8 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types. panic(fmt.Sprintf("should not already be bonded, validator: %v\n", validator)) } + validator.BondHeight = ctx.BlockHeight() + // set the status validator, pool = validator.UpdateStatus(pool, sdk.Bonded) k.SetPool(ctx, pool) @@ -672,20 +648,18 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types. // add to accumulated changes for tendermint bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidatorUpdate()) - store.Set(GetTendermintUpdatesKey(validator.OperatorAddr), bzABCI) + tstore := ctx.TransientStore(k.storeTKey) + tstore.Set(GetTendermintUpdatesTKey(validator.OperatorAddr), bzABCI) - // call the bond hook if present - if k.validatorHooks != nil { - k.validatorHooks.OnValidatorBonded(ctx, validator.ConsAddress()) - } - - // return updated validator + k.OnValidatorBonded(ctx, validator.ConsAddress()) return validator } // remove the validator record and associated indexes func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { + k.OnValidatorRemoved(ctx, address) + // first retrieve the old validator record validator, found := k.GetValidator(ctx, address) if !found { @@ -696,7 +670,7 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { store := ctx.KVStore(k.storeKey) pool := k.GetPool(ctx) store.Delete(GetValidatorKey(address)) - store.Delete(GetValidatorByPubKeyIndexKey(validator.ConsPubKey)) + store.Delete(GetValidatorByConsAddrKey(sdk.ConsAddress(validator.ConsPubKey.Address()))) store.Delete(GetValidatorsByPowerIndexKey(validator, pool)) // delete from the current and power weighted validator groups if the validator @@ -707,7 +681,26 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { store.Delete(GetValidatorsBondedIndexKey(validator.OperatorAddr)) bz := k.cdc.MustMarshalBinary(validator.ABCIValidatorUpdateZero()) - store.Set(GetTendermintUpdatesKey(address), bz) + tstore := ctx.TransientStore(k.storeTKey) + tstore.Set(GetTendermintUpdatesTKey(address), bz) +} + +// UpdateValidatorCommission attempts to update a validator's commission rate. +// An error is returned if the new commission rate is invalid. +func (k Keeper) UpdateValidatorCommission(ctx sdk.Context, validator types.Validator, newRate sdk.Dec) sdk.Error { + commission := validator.Commission + blockTime := ctx.BlockHeader().Time + + if err := commission.ValidateNewRate(newRate, blockTime); err != nil { + return err + } + + validator.Commission.Rate = newRate + validator.Commission.UpdateTime = blockTime + + k.SetValidator(ctx, validator) + k.OnValidatorCommissionChange(ctx, validator.OperatorAddr) + return nil } //__________________________________________________________________________ @@ -738,3 +731,9 @@ func (k Keeper) clearCliffValidator(ctx sdk.Context) { store.Delete(ValidatorPowerCliffKey) store.Delete(ValidatorCliffIndexKey) } + +func ensureValidatorFound(found bool, ownerAddr []byte) { + if !found { + panic(fmt.Sprintf("validator record not found for address: %X\n", ownerAddr)) + } +} diff --git a/x/stake/keeper/validator_test.go b/x/stake/keeper/validator_test.go index 6d97fd5b8..c12758de6 100644 --- a/x/stake/keeper/validator_test.go +++ b/x/stake/keeper/validator_test.go @@ -3,21 +3,39 @@ package keeper import ( "fmt" "testing" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" - tmtypes "github.com/tendermint/tendermint/types" + abci "github.com/tendermint/tendermint/abci/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// for testing, remove all validator update entries after applied to Tendermint +func clearTendermintUpdates(ctx sdk.Context, k Keeper) { + store := ctx.TransientStore(k.storeTKey) + + // delete subspace + iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesTKey) + for ; iterator.Valid(); iterator.Next() { + store.Delete(iterator.Key()) + } + iterator.Close() +} + +//_______________________________________________________ + func TestSetValidator(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 10) pool := keeper.GetPool(ctx) + valPubKey := PKs[0] + valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) + // test how the validator is set from a purely unbonbed pool - validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator := types.NewValidator(valAddr, valPubKey, types.Description{}) validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, sdk.Unbonded, validator.Status) assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.Tokens)) @@ -26,14 +44,14 @@ func TestSetValidator(t *testing.T) { keeper.UpdateValidator(ctx, validator) // after the save the validator should be bonded - validator, found := keeper.GetValidator(ctx, addrVals[0]) + validator, found := keeper.GetValidator(ctx, valAddr) require.True(t, found) require.Equal(t, sdk.Bonded, validator.Status) assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.Tokens)) assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.DelegatorShares)) // Check each store for being saved - resVal, found := keeper.GetValidator(ctx, addrVals[0]) + resVal, found := keeper.GetValidator(ctx, valAddr) assert.True(ValEq(t, validator, resVal)) require.True(t, found) @@ -43,11 +61,22 @@ func TestSetValidator(t *testing.T) { resVals = keeper.GetValidatorsByPower(ctx) require.Equal(t, 1, len(resVals)) - assert.True(ValEq(t, validator, resVals[0])) + require.True(ValEq(t, validator, resVals[0])) - updates := keeper.GetTendermintUpdates(ctx) + resVals = keeper.GetValidators(ctx, 1) + require.Equal(t, 1, len(resVals)) + require.True(ValEq(t, validator, resVals[0])) + + resVals = keeper.GetValidators(ctx, 10) + require.Equal(t, 1, len(resVals)) + require.True(ValEq(t, validator, resVals[0])) + + updates := keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 1, len(updates)) require.Equal(t, validator.ABCIValidatorUpdate(), updates[0]) + + allVals := keeper.GetAllValidators(ctx) + require.Equal(t, 1, len(allVals)) } func TestUpdateValidatorByPowerIndex(t *testing.T) { @@ -233,12 +262,13 @@ func TestSlashToZeroPowerRemoved(t *testing.T) { require.Equal(t, sdk.Unbonded, validator.Status) require.Equal(t, int64(100), validator.Tokens.RoundInt64()) keeper.SetPool(ctx, pool) - keeper.SetValidatorByPubKeyIndex(ctx, validator) + keeper.SetValidatorByConsAddr(ctx, validator) validator = keeper.UpdateValidator(ctx, validator) require.Equal(t, int64(100), validator.Tokens.RoundInt64(), "\nvalidator %v\npool %v", validator, pool) // slash the validator by 100% - keeper.Slash(ctx, PKs[0], 0, 100, sdk.OneDec()) + consAddr0 := sdk.ConsAddress(PKs[0].Address()) + keeper.Slash(ctx, consAddr0, 0, 100, sdk.OneDec()) // validator should have been deleted _, found := keeper.GetValidator(ctx, addrVals[0]) require.False(t, found) @@ -267,17 +297,29 @@ func TestValidatorBasics(t *testing.T) { _, found := keeper.GetValidator(ctx, addrVals[0]) require.False(t, found) resVals := keeper.GetValidatorsBonded(ctx) - assert.Zero(t, len(resVals)) + require.Zero(t, len(resVals)) + + resVals = keeper.GetValidators(ctx, 2) + require.Zero(t, len(resVals)) pool = keeper.GetPool(ctx) assert.True(sdk.DecEq(t, sdk.ZeroDec(), pool.BondedTokens)) // set and retrieve a record validators[0] = keeper.UpdateValidator(ctx, validators[0]) + keeper.SetValidatorByConsAddr(ctx, validators[0]) resVal, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) assert.True(ValEq(t, validators[0], resVal)) + // retrieve from consensus + resVal, found = keeper.GetValidatorByConsAddr(ctx, sdk.ConsAddress(PKs[0].Address())) + require.True(t, found) + assert.True(ValEq(t, validators[0], resVal)) + resVal, found = keeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(PKs[0])) + require.True(t, found) + assert.True(ValEq(t, validators[0], resVal)) + resVals = keeper.GetValidatorsBonded(ctx) require.Equal(t, 1, len(resVals)) assert.True(ValEq(t, validators[0], resVals[0])) @@ -633,49 +675,30 @@ func TestFullValidatorSetPowerChange(t *testing.T) { assert.True(ValEq(t, validators[2], resValidators[1])) } -// clear the tracked changes to the gotValidator set -func TestClearTendermintUpdates(t *testing.T) { - ctx, _, keeper := CreateTestInput(t, false, 1000) - - amts := []int64{100, 400, 200} - validators := make([]types.Validator, len(amts)) - for i, amt := range amts { - pool := keeper.GetPool(ctx) - validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) - validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) - keeper.SetPool(ctx, pool) - keeper.UpdateValidator(ctx, validators[i]) - } - - updates := keeper.GetTendermintUpdates(ctx) - require.Equal(t, len(amts), len(updates)) - keeper.ClearTendermintUpdates(ctx) - updates = keeper.GetTendermintUpdates(ctx) - require.Equal(t, 0, len(updates)) -} - -func TestGetTendermintUpdatesAllNone(t *testing.T) { +func TestGetValidTendermintUpdatesAllNone(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{10, 20} var validators [2]types.Validator for i, amt := range amts { pool := keeper.GetPool(ctx) - validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) + + valPubKey := PKs[i+1] + valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) + + validators[i] = types.NewValidator(valAddr, valPubKey, types.Description{}) validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } // test from nothing to something // tendermintUpdate set: {} -> {c1, c3} - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.GetValidTendermintUpdates(ctx) assert.Equal(t, 2, len(updates)) - assert.Equal(t, validators[0].ABCIValidatorUpdate(), updates[0]) - assert.Equal(t, validators[1].ABCIValidatorUpdate(), updates[1]) // test from something to nothing // tendermintUpdate set: {} -> {c1, c2, c3, c4} @@ -691,9 +714,11 @@ func TestGetTendermintUpdatesAllNone(t *testing.T) { assert.Equal(t, tmtypes.TM2PB.PubKey(validators[1].ConsPubKey), updates[1].PubKey) assert.Equal(t, int64(0), updates[0].Power) assert.Equal(t, int64(0), updates[1].Power) + assert.Equal(t, validators[0].ABCIValidatorUpdate(), updates[0]) + assert.Equal(t, validators[1].ABCIValidatorUpdate(), updates[1]) } -func TestGetTendermintUpdatesIdentical(t *testing.T) { +func TestGetValidTendermintUpdatesIdentical(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{10, 20} @@ -706,17 +731,17 @@ func TestGetTendermintUpdatesIdentical(t *testing.T) { } validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - keeper.ClearTendermintUpdates(ctx) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) // test identical, // tendermintUpdate set: {} -> {} validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) } -func TestGetTendermintUpdatesSingleValueChange(t *testing.T) { +func TestGetValidTendermintUpdatesSingleValueChange(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{10, 20} @@ -729,8 +754,8 @@ func TestGetTendermintUpdatesSingleValueChange(t *testing.T) { } validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - keeper.ClearTendermintUpdates(ctx) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) // test single value change // tendermintUpdate set: {} -> {c1'} @@ -738,13 +763,13 @@ func TestGetTendermintUpdatesSingleValueChange(t *testing.T) { validators[0].Tokens = sdk.NewDec(600) validators[0] = keeper.UpdateValidator(ctx, validators[0]) - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 1, len(updates)) require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[0]) } -func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) { +func TestGetValidTendermintUpdatesMultipleValueChange(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{10, 20} @@ -757,8 +782,8 @@ func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) { } validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - keeper.ClearTendermintUpdates(ctx) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) // test multiple value change // tendermintUpdate set: {c1, c3} -> {c1', c3'} @@ -769,13 +794,13 @@ func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) { validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 2, len(updates)) require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[0]) require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[1]) } -func TestGetTendermintUpdatesInserted(t *testing.T) { +func TestGetValidTendermintUpdatesInserted(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{10, 20, 5, 15, 25} @@ -788,34 +813,34 @@ func TestGetTendermintUpdatesInserted(t *testing.T) { } validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - keeper.ClearTendermintUpdates(ctx) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) // test validtor added at the beginning // tendermintUpdate set: {} -> {c0} validators[2] = keeper.UpdateValidator(ctx, validators[2]) - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 1, len(updates)) require.Equal(t, validators[2].ABCIValidatorUpdate(), updates[0]) // test validtor added at the beginning // tendermintUpdate set: {} -> {c0} - keeper.ClearTendermintUpdates(ctx) + clearTendermintUpdates(ctx, keeper) validators[3] = keeper.UpdateValidator(ctx, validators[3]) - updates = keeper.GetTendermintUpdates(ctx) + updates = keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 1, len(updates)) require.Equal(t, validators[3].ABCIValidatorUpdate(), updates[0]) // test validtor added at the end // tendermintUpdate set: {} -> {c0} - keeper.ClearTendermintUpdates(ctx) + clearTendermintUpdates(ctx, keeper) validators[4] = keeper.UpdateValidator(ctx, validators[4]) - updates = keeper.GetTendermintUpdates(ctx) + updates = keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 1, len(updates)) require.Equal(t, validators[4].ABCIValidatorUpdate(), updates[0]) } -func TestGetTendermintUpdatesWithCliffValidator(t *testing.T) { +func TestGetValidTendermintUpdatesWithCliffValidator(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) params := types.DefaultParams() params.MaxValidators = 2 @@ -831,32 +856,32 @@ func TestGetTendermintUpdatesWithCliffValidator(t *testing.T) { } validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - keeper.ClearTendermintUpdates(ctx) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) // test validator added at the end but not inserted in the valset // tendermintUpdate set: {} -> {} keeper.UpdateValidator(ctx, validators[2]) - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 0, len(updates)) // test validator change its power and become a gotValidator (pushing out an existing) // tendermintUpdate set: {} -> {c0, c4} - keeper.ClearTendermintUpdates(ctx) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) pool := keeper.GetPool(ctx) validators[2], pool, _ = validators[2].AddTokensFromDel(pool, sdk.NewInt(10)) keeper.SetPool(ctx, pool) validators[2] = keeper.UpdateValidator(ctx, validators[2]) - updates = keeper.GetTendermintUpdates(ctx) + updates = keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 2, len(updates), "%v", updates) require.Equal(t, validators[0].ABCIValidatorUpdateZero(), updates[0]) require.Equal(t, validators[2].ABCIValidatorUpdate(), updates[1]) } -func TestGetTendermintUpdatesPowerDecrease(t *testing.T) { +func TestGetValidTendermintUpdatesPowerDecrease(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{100, 100} @@ -869,8 +894,8 @@ func TestGetTendermintUpdatesPowerDecrease(t *testing.T) { } validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - keeper.ClearTendermintUpdates(ctx) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) // check initial power require.Equal(t, sdk.NewDec(100).RoundInt64(), validators[0].GetPower().RoundInt64()) @@ -890,8 +915,210 @@ func TestGetTendermintUpdatesPowerDecrease(t *testing.T) { require.Equal(t, sdk.NewDec(70).RoundInt64(), validators[1].GetPower().RoundInt64()) // Tendermint updates should reflect power change - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 2, len(updates)) require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[0]) require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[1]) } + +func TestGetValidTendermintUpdatesNewValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + params := keeper.GetParams(ctx) + params.MaxValidators = uint16(3) + + keeper.SetParams(ctx, params) + + amts := []int64{100, 100} + var validators [2]types.Validator + + // initialize some validators into the state + for i, amt := range amts { + pool := keeper.GetPool(ctx) + valPubKey := PKs[i+1] + valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) + + validators[i] = types.NewValidator(valAddr, valPubKey, types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) + + keeper.SetPool(ctx, pool) + validators[i] = keeper.UpdateValidator(ctx, validators[i]) + } + + // verify initial Tendermint updates are correct + updates := keeper.GetValidTendermintUpdates(ctx) + require.Equal(t, len(validators), len(updates)) + require.Equal(t, validators[0].ABCIValidator(), updates[0]) + require.Equal(t, validators[1].ABCIValidator(), updates[1]) + + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) + + // update initial validator set + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) + + keeper.SetPool(ctx, pool) + validators[i] = keeper.UpdateValidator(ctx, validators[i]) + } + + // add a new validator that goes from zero power, to non-zero power, back to + // zero power + pool := keeper.GetPool(ctx) + valPubKey := PKs[len(validators)+1] + valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) + amt := sdk.NewInt(100) + + validator := types.NewValidator(valAddr, valPubKey, types.Description{}) + validator, pool, _ = validator.AddTokensFromDel(pool, amt) + + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + + validator, pool, _ = validator.RemoveDelShares(pool, sdk.NewDecFromInt(amt)) + validator = keeper.UpdateValidator(ctx, validator) + + // add a new validator that increases in power + valPubKey = PKs[len(validators)+2] + valAddr = sdk.ValAddress(valPubKey.Address().Bytes()) + + validator = types.NewValidator(valAddr, valPubKey, types.Description{}) + validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(500)) + + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + + // verify initial Tendermint updates are correct + updates = keeper.GetValidTendermintUpdates(ctx) + require.Equal(t, len(validators)+1, len(updates)) + require.Equal(t, validator.ABCIValidator(), updates[0]) + require.Equal(t, validators[0].ABCIValidator(), updates[1]) + require.Equal(t, validators[1].ABCIValidator(), updates[2]) +} + +func TestGetValidTendermintUpdatesBondTransition(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + params := keeper.GetParams(ctx) + params.MaxValidators = uint16(2) + + keeper.SetParams(ctx, params) + + amts := []int64{100, 200, 300} + var validators [3]types.Validator + + // initialize some validators into the state + for i, amt := range amts { + pool := keeper.GetPool(ctx) + moniker := fmt.Sprintf("%d", i) + valPubKey := PKs[i+1] + valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) + + validators[i] = types.NewValidator(valAddr, valPubKey, types.Description{Moniker: moniker}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) + + keeper.SetPool(ctx, pool) + validators[i] = keeper.UpdateValidator(ctx, validators[i]) + } + + // verify initial Tendermint updates are correct + updates := keeper.GetValidTendermintUpdates(ctx) + require.Equal(t, 2, len(updates)) + require.Equal(t, validators[2].ABCIValidator(), updates[0]) + require.Equal(t, validators[1].ABCIValidator(), updates[1]) + + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) + + // delegate to validator with lowest power but not enough to bond + ctx = ctx.WithBlockHeight(1) + pool := keeper.GetPool(ctx) + + validator, found := keeper.GetValidator(ctx, validators[0].OperatorAddr) + require.True(t, found) + + validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(1)) + + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validator) + + // verify initial Tendermint updates are correct + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) + + // create a series of events that will bond and unbond the validator with + // lowest power in a single block context (height) + ctx = ctx.WithBlockHeight(2) + pool = keeper.GetPool(ctx) + + validator, found = keeper.GetValidator(ctx, validators[1].OperatorAddr) + require.True(t, found) + + validator, pool, _ = validator.RemoveDelShares(pool, validator.DelegatorShares) + + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + + validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(250)) + + keeper.SetPool(ctx, pool) + validators[1] = keeper.UpdateValidator(ctx, validator) + + // verify initial Tendermint updates are correct + updates = keeper.GetValidTendermintUpdates(ctx) + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[1].ABCIValidator(), updates[0]) + + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) +} + +func TestUpdateValidatorCommission(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Now().UTC()}) + + commission1 := types.NewCommissionWithTime( + sdk.NewDecWithPrec(1, 1), sdk.NewDecWithPrec(3, 1), + sdk.NewDecWithPrec(1, 1), time.Now().UTC().Add(time.Duration(-1)*time.Hour), + ) + commission2 := types.NewCommission(sdk.NewDecWithPrec(1, 1), sdk.NewDecWithPrec(3, 1), sdk.NewDecWithPrec(1, 1)) + + val1 := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + val2 := types.NewValidator(addrVals[1], PKs[1], types.Description{}) + + val1, _ = val1.SetInitialCommission(commission1) + val2, _ = val2.SetInitialCommission(commission2) + + testCases := []struct { + validator types.Validator + newRate sdk.Dec + expectedErr bool + }{ + {val1, sdk.ZeroDec(), true}, + {val2, sdk.NewDecWithPrec(-1, 1), true}, + {val2, sdk.NewDecWithPrec(4, 1), true}, + {val2, sdk.NewDecWithPrec(3, 1), true}, + {val2, sdk.NewDecWithPrec(2, 1), false}, + } + + for i, tc := range testCases { + err := keeper.UpdateValidatorCommission(ctx, tc.validator, tc.newRate) + + if tc.expectedErr { + require.Error(t, err, "expected error for test case #%d with rate: %s", i, tc.newRate) + } else { + val, found := keeper.GetValidator(ctx, tc.validator.OperatorAddr) + + require.True(t, found, + "expected to find validator for test case #%d with rate: %s", i, tc.newRate, + ) + require.NoError(t, err, + "unexpected error for test case #%d with rate: %s", i, tc.newRate, + ) + require.Equal(t, tc.newRate, val.Commission.Rate, + "expected new validator commission rate for test case #%d with rate: %s", i, tc.newRate, + ) + require.Equal(t, ctx.BlockHeader().Time, val.Commission.UpdateTime, + "expected new validator commission update time for test case #%d with rate: %s", i, tc.newRate, + ) + } + } +} diff --git a/x/stake/querier/queryable.go b/x/stake/querier/queryable.go new file mode 100644 index 000000000..8537f2bd4 --- /dev/null +++ b/x/stake/querier/queryable.go @@ -0,0 +1,227 @@ +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" + "github.com/cosmos/cosmos-sdk/x/stake/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +// 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" +) + +// creates a querier for staking REST endpoints +func NewQuerier(k keep.Keeper, cdc *codec.Codec) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { + switch path[0] { + case QueryValidators: + return queryValidators(ctx, cdc, k) + case QueryValidator: + return queryValidator(ctx, cdc, req, k) + case QueryDelegator: + return queryDelegator(ctx, cdc, req, k) + case QueryDelegation: + return queryDelegation(ctx, cdc, req, k) + case QueryUnbondingDelegation: + return queryUnbondingDelegation(ctx, cdc, req, k) + case QueryDelegatorValidators: + return queryDelegatorValidators(ctx, cdc, req, k) + case QueryDelegatorValidator: + return queryDelegatorValidator(ctx, cdc, req, k) + case QueryPool: + return queryPool(ctx, cdc, k) + case QueryParameters: + return queryParameters(ctx, cdc, k) + default: + return nil, sdk.ErrUnknownRequest("unknown stake query endpoint") + } + } +} + +// defines the params for the following queries: +// - 'custom/stake/delegator' +// - 'custom/stake/delegatorValidators' +type QueryDelegatorParams struct { + DelegatorAddr sdk.AccAddress +} + +// defines the params for the following queries: +// - 'custom/stake/validator' +type QueryValidatorParams struct { + ValidatorAddr sdk.ValAddress +} + +// defines the params for the following queries: +// - 'custom/stake/delegation' +// - 'custom/stake/unbondingDelegation' +// - 'custom/stake/delegatorValidator' +type QueryBondsParams struct { + DelegatorAddr sdk.AccAddress + ValidatorAddr sdk.ValAddress +} + +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 res, nil +} + +func queryValidator(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(fmt.Sprintf("incorrectly formatted request address: %s", err.Error())) + } + + validator, found := k.GetValidator(ctx, params.ValidatorAddr) + if !found { + return []byte{}, types.ErrNoValidatorFound(types.DefaultCodespace) + } + + 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 res, nil +} + +func queryDelegator(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())) + } + delegations := k.GetAllDelegatorDelegations(ctx, params.DelegatorAddr) + 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, summary) + if errRes != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + } + return res, nil +} + +func queryDelegatorValidators(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) (res []byte, err sdk.Error) { + var params QueryDelegatorParams + + stakeParams := k.GetParams(ctx) + + errRes := cdc.UnmarshalJSON(req.Data, ¶ms) + if errRes != nil { + return []byte{}, sdk.ErrUnknownAddress(fmt.Sprintf("incorrectly formatted request address: %s", errRes.Error())) + } + + 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 res, nil +} + +func queryDelegatorValidator(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) (res []byte, err sdk.Error) { + var params QueryBondsParams + + errRes := cdc.UnmarshalJSON(req.Data, ¶ms) + if errRes != nil { + return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request address: %s", errRes.Error())) + } + + validator, err := k.GetDelegatorValidator(ctx, params.DelegatorAddr, params.ValidatorAddr) + if err != nil { + return + } + + 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 res, nil +} + +func queryDelegation(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) (res []byte, err sdk.Error) { + var params QueryBondsParams + + errRes := cdc.UnmarshalJSON(req.Data, ¶ms) + if errRes != nil { + return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request address: %s", errRes.Error())) + } + + delegation, found := k.GetDelegation(ctx, params.DelegatorAddr, params.ValidatorAddr) + if !found { + return []byte{}, types.ErrNoDelegation(types.DefaultCodespace) + } + + 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 res, nil +} + +func queryUnbondingDelegation(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) (res []byte, err sdk.Error) { + var params QueryBondsParams + + errRes := cdc.UnmarshalJSON(req.Data, ¶ms) + if errRes != nil { + return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request address: %s", errRes.Error())) + } + + unbond, found := k.GetUnbondingDelegation(ctx, params.DelegatorAddr, params.ValidatorAddr) + if !found { + return []byte{}, types.ErrNoUnbondingDelegation(types.DefaultCodespace) + } + + 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 res, nil +} + +func queryPool(ctx sdk.Context, cdc *codec.Codec, k keep.Keeper) (res []byte, err sdk.Error) { + pool := k.GetPool(ctx) + + 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 res, nil +} + +func queryParameters(ctx sdk.Context, cdc *codec.Codec, k keep.Keeper) (res []byte, err sdk.Error) { + params := k.GetParams(ctx) + + 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 res, nil +} diff --git a/x/stake/querier/queryable_test.go b/x/stake/querier/queryable_test.go new file mode 100644 index 000000000..ee52773c6 --- /dev/null +++ b/x/stake/querier/queryable_test.go @@ -0,0 +1,215 @@ +package querier + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + keep "github.com/cosmos/cosmos-sdk/x/stake/keeper" + "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +var ( + addrAcc1, addrAcc2 = keep.Addrs[0], keep.Addrs[1] + addrVal1, addrVal2 = sdk.ValAddress(keep.Addrs[0]), sdk.ValAddress(keep.Addrs[1]) + pk1, pk2 = keep.PKs[0], keep.PKs[1] +) + +func newTestDelegatorQuery(delegatorAddr sdk.AccAddress) QueryDelegatorParams { + return QueryDelegatorParams{ + DelegatorAddr: delegatorAddr, + } +} + +func newTestValidatorQuery(validatorAddr sdk.ValAddress) QueryValidatorParams { + return QueryValidatorParams{ + ValidatorAddr: validatorAddr, + } +} + +func newTestBondQuery(delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) QueryBondsParams { + return QueryBondsParams{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + } +} + +func TestQueryParametersPool(t *testing.T) { + cdc := codec.New() + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + + res, err := queryParameters(ctx, cdc, keeper) + require.Nil(t, err) + + var params types.Params + errRes := cdc.UnmarshalJSON(res, ¶ms) + require.Nil(t, errRes) + require.Equal(t, keeper.GetParams(ctx), params) + + res, err = queryPool(ctx, cdc, keeper) + require.Nil(t, err) + + var pool types.Pool + errRes = cdc.UnmarshalJSON(res, &pool) + require.Nil(t, errRes) + require.Equal(t, keeper.GetPool(ctx), pool) +} + +func TestQueryValidators(t *testing.T) { + cdc := codec.New() + ctx, _, keeper := keep.CreateTestInput(t, false, 10000) + pool := keeper.GetPool(ctx) + params := keeper.GetParams(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) + } + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + + // Query Validators + queriedValidators := keeper.GetValidators(ctx, params.MaxValidators) + + res, err := queryValidators(ctx, cdc, keeper) + require.Nil(t, err) + + var validatorsResp []types.Validator + errRes := cdc.UnmarshalJSON(res, &validatorsResp) + require.Nil(t, errRes) + + require.Equal(t, len(queriedValidators), len(validatorsResp)) + require.ElementsMatch(t, queriedValidators, validatorsResp) + + // Query each validator + queryParams := newTestValidatorQuery(addrVal1) + bz, errRes := cdc.MarshalJSON(queryParams) + require.Nil(t, errRes) + + query := abci.RequestQuery{ + Path: "/custom/stake/validator", + Data: bz, + } + res, err = queryValidator(ctx, cdc, query, keeper) + require.Nil(t, err) + + var validator types.Validator + errRes = cdc.UnmarshalJSON(res, &validator) + require.Nil(t, errRes) + + require.Equal(t, queriedValidators[0], validator) +} + +func TestQueryDelegation(t *testing.T) { + cdc := codec.New() + ctx, _, keeper := keep.CreateTestInput(t, false, 10000) + params := keeper.GetParams(ctx) + + // Create Validators and Delegation + val1 := types.NewValidator(addrVal1, pk1, types.Description{}) + keeper.SetValidator(ctx, val1) + + keeper.Delegate(ctx, addrAcc2, sdk.NewCoin("steak", sdk.NewInt(20)), val1, true) + + // Query Delegator bonded validators + queryParams := newTestDelegatorQuery(addrAcc2) + bz, errRes := cdc.MarshalJSON(queryParams) + require.Nil(t, errRes) + + query := abci.RequestQuery{ + Path: "/custom/stake/delegatorValidators", + Data: bz, + } + + delValidators := keeper.GetDelegatorValidators(ctx, addrAcc2, params.MaxValidators) + + res, err := queryDelegatorValidators(ctx, cdc, query, keeper) + require.Nil(t, err) + + var validatorsResp []types.Validator + errRes = cdc.UnmarshalJSON(res, &validatorsResp) + require.Nil(t, errRes) + + require.Equal(t, len(delValidators), len(validatorsResp)) + require.ElementsMatch(t, delValidators, validatorsResp) + + // Query bonded validator + queryBondParams := newTestBondQuery(addrAcc2, addrVal1) + bz, errRes = cdc.MarshalJSON(queryBondParams) + require.Nil(t, errRes) + + query = abci.RequestQuery{ + Path: "/custom/stake/delegatorValidator", + Data: bz, + } + + res, err = queryDelegatorValidator(ctx, cdc, query, keeper) + require.Nil(t, err) + + var validator types.Validator + errRes = cdc.UnmarshalJSON(res, &validator) + require.Nil(t, errRes) + + require.Equal(t, delValidators[0], validator) + + // Query delegation + + query = abci.RequestQuery{ + Path: "/custom/stake/delegation", + Data: bz, + } + + delegation, found := keeper.GetDelegation(ctx, addrAcc2, addrVal1) + require.True(t, found) + + res, err = queryDelegation(ctx, cdc, query, keeper) + require.Nil(t, err) + + var delegationRes types.Delegation + errRes = cdc.UnmarshalJSON(res, &delegationRes) + require.Nil(t, errRes) + + require.Equal(t, delegation, delegationRes) + + // Query unbonging delegation + keeper.BeginUnbonding(ctx, addrAcc2, val1.OperatorAddr, sdk.NewDec(10)) + + query = abci.RequestQuery{ + Path: "/custom/stake/unbondingDelegation", + Data: bz, + } + + unbond, found := keeper.GetUnbondingDelegation(ctx, addrAcc2, addrVal1) + require.True(t, found) + + res, err = queryUnbondingDelegation(ctx, cdc, query, keeper) + require.Nil(t, err) + + var unbondRes types.UnbondingDelegation + errRes = cdc.UnmarshalJSON(res, &unbondRes) + require.Nil(t, errRes) + + require.Equal(t, unbond, unbondRes) + + // Query Delegator Summary + + query = abci.RequestQuery{ + Path: "/custom/stake/delegator", + Data: bz, + } + + res, err = queryDelegator(ctx, cdc, query, keeper) + require.Nil(t, err) + + var summary types.DelegationSummary + errRes = cdc.UnmarshalJSON(res, &summary) + require.Nil(t, errRes) + + require.Equal(t, unbond, summary.UnbondingDelegations[0]) +} diff --git a/x/stake/simulation/invariants.go b/x/stake/simulation/invariants.go index 8f218f45b..cdd80a8c4 100644 --- a/x/stake/simulation/invariants.go +++ b/x/stake/simulation/invariants.go @@ -1,9 +1,7 @@ package simulation import ( - "testing" - - "github.com/stretchr/testify/require" + "fmt" "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" @@ -17,17 +15,24 @@ import ( // 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 { - return func(t *testing.T, app *baseapp.BaseApp, log string) { - SupplyInvariants(ck, k, am)(t, app, log) - PositivePowerInvariant(k)(t, app, log) - ValidatorSetInvariant(k)(t, app, log) + return func(app *baseapp.BaseApp) error { + err := SupplyInvariants(ck, k, am)(app) + if err != nil { + return err + } + err = PositivePowerInvariant(k)(app) + if err != nil { + return err + } + err = ValidatorSetInvariant(k)(app) + return err } } // 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 { - return func(t *testing.T, app *baseapp.BaseApp, log string) { + return func(app *baseapp.BaseApp) error { ctx := app.NewContext(false, abci.Header{}) //pool := k.GetPool(ctx) @@ -64,23 +69,30 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) sim // pool.BondedTokens.RoundInt64(), bonded.RoundInt64(), log) // TODO Inflation check on total supply + return nil } } // PositivePowerInvariant checks that all stored validators have > 0 power func PositivePowerInvariant(k stake.Keeper) simulation.Invariant { - return func(t *testing.T, app *baseapp.BaseApp, log string) { + return func(app *baseapp.BaseApp) error { ctx := app.NewContext(false, abci.Header{}) + var err error k.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) bool { - require.True(t, validator.GetPower().GT(sdk.ZeroDec()), "validator with non-positive power stored") + if !validator.GetPower().GT(sdk.ZeroDec()) { + err = fmt.Errorf("validator with non-positive power stored. (pubkey %v)", validator.GetConsPubKey()) + return true + } return false }) + return err } } // ValidatorSetInvariant checks equivalence of Tendermint validator set and SDK validator set func ValidatorSetInvariant(k stake.Keeper) simulation.Invariant { - return func(t *testing.T, app *baseapp.BaseApp, log string) { + return func(app *baseapp.BaseApp) error { // TODO + return nil } } diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index 264460132..0cd4e08a9 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -3,7 +3,6 @@ package simulation import ( "fmt" "math/rand" - "testing" "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" @@ -12,44 +11,57 @@ import ( "github.com/cosmos/cosmos-sdk/x/mock/simulation" "github.com/cosmos/cosmos-sdk/x/stake" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" ) // SimulateMsgCreateValidator func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.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), } - key := simulation.RandomKey(r, keys) - pubkey := key.PubKey() - address := sdk.ValAddress(pubkey.Address()) - amount := m.GetAccount(ctx, sdk.AccAddress(address)).GetCoins().AmountOf(denom) + + maxCommission := sdk.NewInt(10) + 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), + ) + + acc := simulation.RandomAcc(r, accs) + address := sdk.ValAddress(acc.Address) + amount := m.GetAccount(ctx, acc.Address).GetCoins().AmountOf(denom) if amount.GT(sdk.ZeroInt()) { amount = simulation.RandomAmount(r, amount) } + if amount.Equal(sdk.ZeroInt()) { return "no-operation", nil, nil } + msg := stake.MsgCreateValidator{ Description: description, + Commission: commission, ValidatorAddr: address, - DelegatorAddr: sdk.AccAddress(address), - PubKey: pubkey, + DelegatorAddr: acc.Address, + PubKey: acc.PubKey, Delegation: sdk.NewCoin(denom, amount), } + if msg.ValidateBasic() != nil { - tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + 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("stake/MsgCreateValidator/%v", result.IsOK())) + // require.True(t, result.IsOK(), "expected OK result but instead got %v", result) action = fmt.Sprintf("TestMsgCreateValidator: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil, nil @@ -59,7 +71,7 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation // SimulateMsgEditValidator func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.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), @@ -67,16 +79,22 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { Website: simulation.RandStringOfLength(r, 10), Details: simulation.RandStringOfLength(r, 10), } - key := simulation.RandomKey(r, keys) - pubkey := key.PubKey() - address := sdk.ValAddress(pubkey.Address()) + + maxCommission := sdk.NewInt(10) + newCommissionRate := sdk.NewDecWithPrec(simulation.RandomAmount(r, maxCommission).Int64(), 1) + + acc := simulation.RandomAcc(r, accs) + address := sdk.ValAddress(acc.Address) msg := stake.MsgEditValidator{ - Description: description, - ValidatorAddr: address, + Description: description, + ValidatorAddr: address, + CommissionRate: &newCommissionRate, } + if msg.ValidateBasic() != nil { - tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } + ctx, write := ctx.CacheContext() result := handler(ctx, msg) if result.IsOK() { @@ -91,13 +109,13 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { // SimulateMsgDelegate func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.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 - validatorKey := simulation.RandomKey(r, keys) - validatorAddress := sdk.ValAddress(validatorKey.PubKey().Address()) - delegatorKey := simulation.RandomKey(r, keys) - delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + 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) @@ -111,7 +129,7 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operat Delegation: sdk.NewCoin(denom, amount), } if msg.ValidateBasic() != nil { - tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } ctx, write := ctx.CacheContext() result := handler(ctx, msg) @@ -127,13 +145,13 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operat // SimulateMsgBeginUnbonding func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.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 - validatorKey := simulation.RandomKey(r, keys) - validatorAddress := sdk.ValAddress(validatorKey.PubKey().Address()) - delegatorKey := simulation.RandomKey(r, keys) - delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + 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) @@ -147,7 +165,7 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. SharesAmount: sdk.NewDecFromInt(amount), } if msg.ValidateBasic() != nil { - tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } ctx, write := ctx.CacheContext() result := handler(ctx, msg) @@ -163,18 +181,18 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. // SimulateMsgCompleteUnbonding func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.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) { - validatorKey := simulation.RandomKey(r, keys) - validatorAddress := sdk.ValAddress(validatorKey.PubKey().Address()) - delegatorKey := simulation.RandomKey(r, keys) - delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + validatorAcc := simulation.RandomAcc(r, accs) + validatorAddress := sdk.ValAddress(validatorAcc.Address) + delegatorAcc := simulation.RandomAcc(r, accs) + delegatorAddress := delegatorAcc.Address msg := stake.MsgCompleteUnbonding{ DelegatorAddr: delegatorAddress, ValidatorAddr: validatorAddress, } if msg.ValidateBasic() != nil { - tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } ctx, write := ctx.CacheContext() result := handler(ctx, msg) @@ -190,15 +208,15 @@ func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.Operation { // SimulateMsgBeginRedelegate func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.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 - sourceValidatorKey := simulation.RandomKey(r, keys) - sourceValidatorAddress := sdk.ValAddress(sourceValidatorKey.PubKey().Address()) - destValidatorKey := simulation.RandomKey(r, keys) - destValidatorAddress := sdk.ValAddress(destValidatorKey.PubKey().Address()) - delegatorKey := simulation.RandomKey(r, keys) - delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + sourceValidatorAcc := simulation.RandomAcc(r, accs) + sourceValidatorAddress := sdk.ValAddress(sourceValidatorAcc.Address) + destValidatorAcc := simulation.RandomAcc(r, accs) + destValidatorAddress := sdk.ValAddress(destValidatorAcc.Address) + delegatorAcc := simulation.RandomAcc(r, accs) + delegatorAddress := delegatorAcc.Address // TODO amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) if amount.GT(sdk.ZeroInt()) { @@ -214,7 +232,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation SharesAmount: sdk.NewDecFromInt(amount), } if msg.ValidateBasic() != nil { - tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } ctx, write := ctx.CacheContext() result := handler(ctx, msg) @@ -230,21 +248,21 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation // SimulateMsgCompleteRedelegate func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.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) { - validatorSrcKey := simulation.RandomKey(r, keys) - validatorSrcAddress := sdk.ValAddress(validatorSrcKey.PubKey().Address()) - validatorDstKey := simulation.RandomKey(r, keys) - validatorDstAddress := sdk.ValAddress(validatorDstKey.PubKey().Address()) - delegatorKey := simulation.RandomKey(r, keys) - delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + validatorSrcAcc := simulation.RandomAcc(r, accs) + validatorSrcAddress := sdk.ValAddress(validatorSrcAcc.Address) + validatorDstAcc := simulation.RandomAcc(r, accs) + validatorDstAddress := sdk.ValAddress(validatorDstAcc.Address) + delegatorAcc := simulation.RandomAcc(r, accs) + delegatorAddress := delegatorAcc.Address msg := stake.MsgCompleteRedelegate{ DelegatorAddr: delegatorAddress, ValidatorSrcAddr: validatorSrcAddress, ValidatorDstAddr: validatorDstAddress, } if msg.ValidateBasic() != nil { - tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } ctx, write := ctx.CacheContext() result := handler(ctx, msg) @@ -260,7 +278,7 @@ func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.Operation { // Setup // nolint: errcheck func Setup(mapp *mock.App, k stake.Keeper) simulation.RandSetup { - return func(r *rand.Rand, privKeys []crypto.PrivKey) { + return func(r *rand.Rand, accs []simulation.Account) { ctx := mapp.NewContext(false, abci.Header{}) gen := stake.DefaultGenesisState() gen.Params.InflationMax = sdk.NewDec(0) diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go index 3feec637b..b81555f03 100644 --- a/x/stake/simulation/sim_test.go +++ b/x/stake/simulation/sim_test.go @@ -6,7 +6,6 @@ import ( "testing" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" @@ -19,11 +18,12 @@ import ( func TestStakeWithRandomMessages(t *testing.T) { mapp := mock.NewApp() - bank.RegisterWire(mapp.Cdc) + bank.RegisterCodec(mapp.Cdc) mapper := mapp.AccountMapper - bankKeeper := bank.NewKeeper(mapper) + bankKeeper := bank.NewBaseKeeper(mapper) stakeKey := sdk.NewKVStoreKey("stake") - stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, bankKeeper, stake.DefaultCodespace) + stakeTKey := sdk.NewTransientStoreKey("transient_stake") + stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, stakeTKey, bankKeeper, stake.DefaultCodespace) mapp.Router().AddRoute("stake", stake.NewHandler(stakeKeeper)) mapp.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { validatorUpdates := stake.EndBlocker(ctx, stakeKeeper) @@ -32,26 +32,26 @@ func TestStakeWithRandomMessages(t *testing.T) { } }) - err := mapp.CompleteSetup([]*sdk.KVStoreKey{stakeKey}) + err := mapp.CompleteSetup(stakeKey, stakeTKey) if err != nil { panic(err) } - appStateFn := func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage { - mock.RandomSetGenesis(r, mapp, accs, []string{"stake"}) + appStateFn := func(r *rand.Rand, accs []simulation.Account) json.RawMessage { + simulation.RandomSetGenesis(r, mapp, accs, []string{"stake"}) return json.RawMessage("{}") } simulation.Simulate( t, mapp.BaseApp, appStateFn, - []simulation.Operation{ - SimulateMsgCreateValidator(mapper, stakeKeeper), - SimulateMsgEditValidator(stakeKeeper), - SimulateMsgDelegate(mapper, stakeKeeper), - SimulateMsgBeginUnbonding(mapper, stakeKeeper), - SimulateMsgCompleteUnbonding(stakeKeeper), - SimulateMsgBeginRedelegate(mapper, stakeKeeper), - SimulateMsgCompleteRedelegate(stakeKeeper), + []simulation.WeightedOperation{ + {10, SimulateMsgCreateValidator(mapper, stakeKeeper)}, + {5, SimulateMsgEditValidator(stakeKeeper)}, + {15, SimulateMsgDelegate(mapper, stakeKeeper)}, + {10, SimulateMsgBeginUnbonding(mapper, stakeKeeper)}, + {3, SimulateMsgCompleteUnbonding(stakeKeeper)}, + {10, SimulateMsgBeginRedelegate(mapper, stakeKeeper)}, + {3, SimulateMsgCompleteRedelegate(stakeKeeper)}, }, []simulation.RandSetup{ Setup(mapp, stakeKeeper), }, []simulation.Invariant{ diff --git a/x/stake/stake.go b/x/stake/stake.go index 2782957ce..7e60b3113 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -3,6 +3,7 @@ package stake import ( "github.com/cosmos/cosmos-sdk/x/stake/keeper" + "github.com/cosmos/cosmos-sdk/x/stake/querier" "github.com/cosmos/cosmos-sdk/x/stake/tags" "github.com/cosmos/cosmos-sdk/x/stake/types" ) @@ -10,9 +11,10 @@ import ( type ( Keeper = keeper.Keeper Validator = types.Validator - BechValidator = types.BechValidator Description = types.Description + Commission = types.Commission Delegation = types.Delegation + DelegationSummary = types.DelegationSummary UnbondingDelegation = types.UnbondingDelegation Redelegation = types.Redelegation Params = types.Params @@ -25,27 +27,30 @@ type ( MsgBeginRedelegate = types.MsgBeginRedelegate MsgCompleteRedelegate = types.MsgCompleteRedelegate GenesisState = types.GenesisState + QueryDelegatorParams = querier.QueryDelegatorParams + QueryValidatorParams = querier.QueryValidatorParams + QueryBondsParams = querier.QueryBondsParams ) var ( NewKeeper = keeper.NewKeeper GetValidatorKey = keeper.GetValidatorKey - GetValidatorByPubKeyIndexKey = keeper.GetValidatorByPubKeyIndexKey + GetValidatorByConsAddrKey = keeper.GetValidatorByConsAddrKey GetValidatorsBondedIndexKey = keeper.GetValidatorsBondedIndexKey GetValidatorsByPowerIndexKey = keeper.GetValidatorsByPowerIndexKey - GetTendermintUpdatesKey = keeper.GetTendermintUpdatesKey + GetTendermintUpdatesTKey = keeper.GetTendermintUpdatesTKey GetDelegationKey = keeper.GetDelegationKey GetDelegationsKey = keeper.GetDelegationsKey ParamKey = keeper.ParamKey PoolKey = keeper.PoolKey ValidatorsKey = keeper.ValidatorsKey - ValidatorsByPubKeyIndexKey = keeper.ValidatorsByPubKeyIndexKey + ValidatorsByConsAddrKey = keeper.ValidatorsByConsAddrKey ValidatorsBondedIndexKey = keeper.ValidatorsBondedIndexKey ValidatorsByPowerIndexKey = keeper.ValidatorsByPowerIndexKey ValidatorCliffIndexKey = keeper.ValidatorCliffIndexKey ValidatorPowerCliffKey = keeper.ValidatorPowerCliffKey - TendermintUpdatesKey = keeper.TendermintUpdatesKey + TendermintUpdatesTKey = keeper.TendermintUpdatesTKey DelegationKey = keeper.DelegationKey IntraTxCounterKey = keeper.IntraTxCounterKey GetUBDKey = keeper.GetUBDKey @@ -60,13 +65,16 @@ var ( GetREDsToValDstIndexKey = keeper.GetREDsToValDstIndexKey GetREDsByDelToValDstIndexKey = keeper.GetREDsByDelToValDstIndexKey - DefaultParams = types.DefaultParams - InitialPool = types.InitialPool - NewValidator = types.NewValidator - NewDescription = types.NewDescription - NewGenesisState = types.NewGenesisState - DefaultGenesisState = types.DefaultGenesisState - RegisterWire = types.RegisterWire + DefaultParams = types.DefaultParams + InitialPool = types.InitialPool + NewValidator = types.NewValidator + NewDescription = types.NewDescription + NewCommission = types.NewCommission + NewCommissionMsg = types.NewCommissionMsg + NewCommissionWithTime = types.NewCommissionWithTime + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + RegisterCodec = types.RegisterCodec NewMsgCreateValidator = types.NewMsgCreateValidator NewMsgCreateValidatorOnBehalfOf = types.NewMsgCreateValidatorOnBehalfOf @@ -76,6 +84,8 @@ var ( NewMsgCompleteUnbonding = types.NewMsgCompleteUnbonding NewMsgBeginRedelegate = types.NewMsgBeginRedelegate NewMsgCompleteRedelegate = types.NewMsgCompleteRedelegate + + NewQuerier = querier.NewQuerier ) const ( diff --git a/x/stake/types/wire.go b/x/stake/types/codec.go similarity index 69% rename from x/stake/types/wire.go rename to x/stake/types/codec.go index 86f9f5f09..4921cdf8e 100644 --- a/x/stake/types/wire.go +++ b/x/stake/types/codec.go @@ -1,11 +1,11 @@ package types import ( - "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/codec" ) -// Register concrete types on wire codec -func RegisterWire(cdc *wire.Codec) { +// Register concrete types on codec codec +func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgCreateValidator{}, "cosmos-sdk/MsgCreateValidator", nil) cdc.RegisterConcrete(MsgEditValidator{}, "cosmos-sdk/MsgEditValidator", nil) cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil) @@ -16,12 +16,11 @@ func RegisterWire(cdc *wire.Codec) { } // generic sealed codec to be used throughout sdk -var MsgCdc *wire.Codec +var MsgCdc *codec.Codec func init() { - cdc := wire.NewCodec() - RegisterWire(cdc) - wire.RegisterCrypto(cdc) - MsgCdc = cdc - //MsgCdc = cdc.Seal() //TODO use when upgraded to go-amino 0.9.10 + cdc := codec.New() + RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + MsgCdc = cdc.Seal() } diff --git a/x/stake/types/commission.go b/x/stake/types/commission.go new file mode 100644 index 000000000..b76971faa --- /dev/null +++ b/x/stake/types/commission.go @@ -0,0 +1,128 @@ +package types + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +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 + 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 + } + + // CommissionMsg defines a commission message to be used for creating a + // validator. + CommissionMsg 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 + MaxChangeRate sdk.Dec `json:"max_change_rate"` // maximum daily increase of the validator commission + } +) + +// NewCommissionMsg returns an initialized validator commission message. +func NewCommissionMsg(rate, maxRate, maxChangeRate sdk.Dec) CommissionMsg { + return CommissionMsg{ + Rate: rate, + MaxRate: maxRate, + MaxChangeRate: maxChangeRate, + } +} + +// NewCommission returns an initialized validator commission. +func NewCommission(rate, maxRate, maxChangeRate sdk.Dec) Commission { + return Commission{ + Rate: rate, + MaxRate: maxRate, + MaxChangeRate: maxChangeRate, + UpdateTime: time.Unix(0, 0).UTC(), + } +} + +// NewCommission returns an initialized validator commission with a specified +// update time which should be the current block BFT time. +func NewCommissionWithTime(rate, maxRate, maxChangeRate sdk.Dec, updatedAt time.Time) Commission { + return Commission{ + Rate: rate, + MaxRate: maxRate, + MaxChangeRate: maxChangeRate, + UpdateTime: updatedAt, + } +} + +// Equal checks if the given Commission object is equal to the receiving +// Commission object. +func (c Commission) Equal(c2 Commission) bool { + return c.Rate.Equal(c2.Rate) && + c.MaxRate.Equal(c2.MaxRate) && + c.MaxChangeRate.Equal(c2.MaxChangeRate) && + c.UpdateTime.Equal(c2.UpdateTime) +} + +// String implements the Stringer interface for a Commission. +func (c Commission) String() string { + return fmt.Sprintf("rate: %s, maxRate: %s, maxChangeRate: %s, updateTime: %s", + c.Rate, c.MaxRate, c.MaxChangeRate, c.UpdateTime, + ) +} + +// Validate performs basic sanity validation checks of initial commission +// parameters. If validation fails, an SDK error is returned. +func (c Commission) Validate() sdk.Error { + switch { + case c.MaxRate.LT(sdk.ZeroDec()): + // max rate cannot be negative + return ErrCommissionNegative(DefaultCodespace) + + case c.MaxRate.GT(sdk.OneDec()): + // max rate cannot be greater than 100% + return ErrCommissionHuge(DefaultCodespace) + + case c.Rate.LT(sdk.ZeroDec()): + // rate cannot be negative + return ErrCommissionNegative(DefaultCodespace) + + case c.Rate.GT(c.MaxRate): + // rate cannot be greater than the max rate + return ErrCommissionGTMaxRate(DefaultCodespace) + + case c.MaxChangeRate.LT(sdk.ZeroDec()): + // change rate cannot be negative + return ErrCommissionChangeRateNegative(DefaultCodespace) + + case c.MaxChangeRate.GT(c.MaxRate): + // change rate cannot be greater than the max rate + return ErrCommissionChangeRateGTMaxRate(DefaultCodespace) + } + + return nil +} + +// ValidateNewRate performs basic sanity validation checks of a new commission +// rate. If validation fails, an SDK error is returned. +func (c Commission) ValidateNewRate(newRate sdk.Dec, blockTime time.Time) sdk.Error { + switch { + case blockTime.Sub(c.UpdateTime).Hours() < 24: + // new rate cannot be changed more than once within 24 hours + return ErrCommissionUpdateTime(DefaultCodespace) + + case newRate.LT(sdk.ZeroDec()): + // new rate cannot be negative + return ErrCommissionNegative(DefaultCodespace) + + case newRate.GT(c.MaxRate): + // new rate cannot be greater than the max rate + return ErrCommissionGTMaxRate(DefaultCodespace) + + case newRate.Sub(c.Rate).Abs().GT(c.MaxChangeRate): + // new rate % points change cannot be greater than the max change rate + return ErrCommissionGTMaxChangeRate(DefaultCodespace) + } + + return nil +} diff --git a/x/stake/types/delegation.go b/x/stake/types/delegation.go index 5a2274c3a..57ac70a57 100644 --- a/x/stake/types/delegation.go +++ b/x/stake/types/delegation.go @@ -5,8 +5,8 @@ import ( "fmt" "time" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" ) // Delegation represents the bond with tokens held by an account. It is @@ -24,8 +24,15 @@ 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 *wire.Codec, delegation Delegation) []byte { +func MustMarshalDelegation(cdc *codec.Codec, delegation Delegation) []byte { val := delegationValue{ delegation.Shares, delegation.Height, @@ -34,7 +41,7 @@ func MustMarshalDelegation(cdc *wire.Codec, delegation Delegation) []byte { } // return the delegation without fields contained within the key for the store -func MustUnmarshalDelegation(cdc *wire.Codec, key, value []byte) Delegation { +func MustUnmarshalDelegation(cdc *codec.Codec, key, value []byte) Delegation { delegation, err := UnmarshalDelegation(cdc, key, value) if err != nil { panic(err) @@ -43,7 +50,7 @@ func MustUnmarshalDelegation(cdc *wire.Codec, key, value []byte) Delegation { } // return the delegation without fields contained within the key for the store -func UnmarshalDelegation(cdc *wire.Codec, key, value []byte) (delegation Delegation, err error) { +func UnmarshalDelegation(cdc *codec.Codec, key, value []byte) (delegation Delegation, err error) { var storeValue delegationValue err = cdc.UnmarshalBinary(value, &storeValue) if err != nil { @@ -82,7 +89,7 @@ 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) GetBondShares() sdk.Dec { return d.Shares } +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 +122,7 @@ type ubdValue struct { } // return the unbonding delegation without fields contained within the key for the store -func MustMarshalUBD(cdc *wire.Codec, ubd UnbondingDelegation) []byte { +func MustMarshalUBD(cdc *codec.Codec, ubd UnbondingDelegation) []byte { val := ubdValue{ ubd.CreationHeight, ubd.MinTime, @@ -126,7 +133,7 @@ func MustMarshalUBD(cdc *wire.Codec, ubd UnbondingDelegation) []byte { } // unmarshal a unbonding delegation from a store key and value -func MustUnmarshalUBD(cdc *wire.Codec, key, value []byte) UnbondingDelegation { +func MustUnmarshalUBD(cdc *codec.Codec, key, value []byte) UnbondingDelegation { ubd, err := UnmarshalUBD(cdc, key, value) if err != nil { panic(err) @@ -135,7 +142,7 @@ func MustUnmarshalUBD(cdc *wire.Codec, key, value []byte) UnbondingDelegation { } // unmarshal a unbonding delegation from a store key and value -func UnmarshalUBD(cdc *wire.Codec, key, value []byte) (ubd UnbondingDelegation, err error) { +func UnmarshalUBD(cdc *codec.Codec, key, value []byte) (ubd UnbondingDelegation, err error) { var storeValue ubdValue err = cdc.UnmarshalBinary(value, &storeValue) if err != nil { @@ -205,7 +212,7 @@ type redValue struct { } // return the redelegation without fields contained within the key for the store -func MustMarshalRED(cdc *wire.Codec, red Redelegation) []byte { +func MustMarshalRED(cdc *codec.Codec, red Redelegation) []byte { val := redValue{ red.CreationHeight, red.MinTime, @@ -218,7 +225,7 @@ func MustMarshalRED(cdc *wire.Codec, red Redelegation) []byte { } // unmarshal a redelegation from a store key and value -func MustUnmarshalRED(cdc *wire.Codec, key, value []byte) Redelegation { +func MustUnmarshalRED(cdc *codec.Codec, key, value []byte) Redelegation { red, err := UnmarshalRED(cdc, key, value) if err != nil { panic(err) @@ -227,7 +234,7 @@ func MustUnmarshalRED(cdc *wire.Codec, key, value []byte) Redelegation { } // unmarshal a redelegation from a store key and value -func UnmarshalRED(cdc *wire.Codec, key, value []byte) (red Redelegation, err error) { +func UnmarshalRED(cdc *codec.Codec, key, value []byte) (red Redelegation, err error) { var storeValue redValue err = cdc.UnmarshalBinary(value, &storeValue) if err != nil { diff --git a/x/stake/types/errors.go b/x/stake/types/errors.go index 366012bbf..84a7e5ae6 100644 --- a/x/stake/types/errors.go +++ b/x/stake/types/errors.go @@ -65,6 +65,26 @@ func ErrCommissionHuge(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "commission cannot be more than 100%") } +func ErrCommissionGTMaxRate(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission cannot be more than the max rate") +} + +func ErrCommissionUpdateTime(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission cannot be changed more than once in 24h") +} + +func ErrCommissionChangeRateNegative(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission change rate must be positive") +} + +func ErrCommissionChangeRateGTMaxRate(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission change rate cannot be more than the max rate") +} + +func ErrCommissionGTMaxChangeRate(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission cannot be changed more than max change rate") +} + func ErrNilDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "delegator address is nil") } diff --git a/x/stake/types/inflation_test.go b/x/stake/types/inflation_test.go index fd181af3c..159ecb4c4 100644 --- a/x/stake/types/inflation_test.go +++ b/x/stake/types/inflation_test.go @@ -107,7 +107,6 @@ func updateProvisions(t *testing.T, pool Pool, params Params, hr int) (sdk.Dec, } // Checks that The inflation will correctly increase or decrease after an update to the pool -// nolint: gocyclo func checkInflation(t *testing.T, pool Pool, previousInflation, updatedInflation sdk.Dec, msg string) { inflationChange := updatedInflation.Sub(previousInflation) diff --git a/x/stake/types/msg.go b/x/stake/types/msg.go index 71a8c8631..a313dd64f 100644 --- a/x/stake/types/msg.go +++ b/x/stake/types/msg.go @@ -7,7 +7,7 @@ import ( "github.com/tendermint/tendermint/crypto" ) -// name to idetify transaction types +// name to identify transaction types const MsgType = "stake" // Verify interface at compile time @@ -20,6 +20,7 @@ var _, _ sdk.Msg = &MsgBeginRedelegate{}, &MsgCompleteRedelegate{} // MsgCreateValidator - struct for unbonding transactions type MsgCreateValidator struct { Description + Commission CommissionMsg DelegatorAddr sdk.AccAddress `json:"delegator_address"` ValidatorAddr sdk.ValAddress `json:"validator_address"` PubKey crypto.PubKey `json:"pubkey"` @@ -28,27 +29,29 @@ type MsgCreateValidator struct { // Default way to create validator. Delegator address and validator address are the same func NewMsgCreateValidator(valAddr sdk.ValAddress, pubkey crypto.PubKey, - selfDelegation sdk.Coin, description Description) MsgCreateValidator { + selfDelegation sdk.Coin, description Description, commission CommissionMsg) MsgCreateValidator { return NewMsgCreateValidatorOnBehalfOf( - sdk.AccAddress(valAddr), valAddr, pubkey, selfDelegation, description, + sdk.AccAddress(valAddr), valAddr, pubkey, selfDelegation, description, commission, ) } // Creates validator msg by delegator address on behalf of validator address func NewMsgCreateValidatorOnBehalfOf(delAddr sdk.AccAddress, valAddr sdk.ValAddress, - pubkey crypto.PubKey, delegation sdk.Coin, description Description) MsgCreateValidator { + pubkey crypto.PubKey, delegation sdk.Coin, description Description, commission CommissionMsg) MsgCreateValidator { return MsgCreateValidator{ Description: description, DelegatorAddr: delAddr, ValidatorAddr: valAddr, PubKey: pubkey, Delegation: delegation, + Commission: commission, } } //nolint func (msg MsgCreateValidator) Type() string { return MsgType } +func (msg MsgCreateValidator) Name() string { return "create_validator" } // Return address(es) that must sign over msg.GetSignBytes() func (msg MsgCreateValidator) GetSigners() []sdk.AccAddress { @@ -94,10 +97,13 @@ func (msg MsgCreateValidator) ValidateBasic() sdk.Error { if !(msg.Delegation.Amount.GT(sdk.ZeroInt())) { return ErrBadDelegationAmount(DefaultCodespace) } - empty := Description{} - if msg.Description == empty { + if msg.Description == (Description{}) { return sdk.NewError(DefaultCodespace, CodeInvalidInput, "description must be included") } + if msg.Commission == (CommissionMsg{}) { + return sdk.NewError(DefaultCodespace, CodeInvalidInput, "commission must be included") + } + return nil } @@ -107,17 +113,26 @@ func (msg MsgCreateValidator) ValidateBasic() sdk.Error { type MsgEditValidator struct { Description ValidatorAddr sdk.ValAddress `json:"address"` + + // We pass a reference to the new commission rate as it's not mandatory to + // update. If not updated, the deserialized rate will be zero with no way to + // distinguish if an update was intended. + // + // REF: #2373 + CommissionRate *sdk.Dec `json:"commission_rate"` } -func NewMsgEditValidator(valAddr sdk.ValAddress, description Description) MsgEditValidator { +func NewMsgEditValidator(valAddr sdk.ValAddress, description Description, newRate *sdk.Dec) MsgEditValidator { return MsgEditValidator{ - Description: description, - ValidatorAddr: valAddr, + Description: description, + CommissionRate: newRate, + ValidatorAddr: valAddr, } } //nolint func (msg MsgEditValidator) Type() string { return MsgType } +func (msg MsgEditValidator) Name() string { return "edit_validator" } func (msg MsgEditValidator) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{sdk.AccAddress(msg.ValidatorAddr)} } @@ -142,10 +157,11 @@ func (msg MsgEditValidator) ValidateBasic() sdk.Error { if msg.ValidatorAddr == nil { return sdk.NewError(DefaultCodespace, CodeInvalidInput, "nil validator address") } - empty := Description{} - if msg.Description == empty { + + if msg.Description == (Description{}) { return sdk.NewError(DefaultCodespace, CodeInvalidInput, "transaction must include some information to modify") } + return nil } @@ -168,6 +184,7 @@ 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) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.DelegatorAddr} } @@ -218,6 +235,7 @@ 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) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.DelegatorAddr} } @@ -275,6 +293,7 @@ func NewMsgCompleteRedelegate(delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk //nolint func (msg MsgCompleteRedelegate) Type() string { return MsgType } +func (msg MsgCompleteRedelegate) Name() string { return "complete_redelegate" } func (msg MsgCompleteRedelegate) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.DelegatorAddr} } @@ -321,6 +340,7 @@ 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) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.DelegatorAddr} } // get the bytes for the message signer to sign on @@ -369,6 +389,7 @@ func NewMsgCompleteUnbonding(delAddr sdk.AccAddress, valAddr sdk.ValAddress) Msg //nolint func (msg MsgCompleteUnbonding) Type() string { return MsgType } +func (msg MsgCompleteUnbonding) Name() string { return "complete_unbonding" } func (msg MsgCompleteUnbonding) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.DelegatorAddr} } diff --git a/x/stake/types/msg_test.go b/x/stake/types/msg_test.go index eb66c0422..b5adbd0ad 100644 --- a/x/stake/types/msg_test.go +++ b/x/stake/types/msg_test.go @@ -17,26 +17,30 @@ var ( // test ValidateBasic for MsgCreateValidator func TestMsgCreateValidator(t *testing.T) { + commission1 := NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + commission2 := NewCommissionMsg(sdk.NewDec(5), sdk.NewDec(5), sdk.NewDec(5)) + tests := []struct { name, moniker, identity, website, details string + commissionMsg CommissionMsg validatorAddr sdk.ValAddress pubkey crypto.PubKey bond sdk.Coin expectPass bool }{ - {"basic good", "a", "b", "c", "d", addr1, pk1, coinPos, true}, - {"partial description", "", "", "c", "", addr1, pk1, coinPos, true}, - {"empty description", "", "", "", "", addr1, pk1, coinPos, false}, - {"empty address", "a", "b", "c", "d", emptyAddr, pk1, coinPos, false}, - {"empty pubkey", "a", "b", "c", "d", addr1, emptyPubkey, coinPos, true}, - {"empty bond", "a", "b", "c", "d", addr1, pk1, coinZero, false}, - {"negative bond", "a", "b", "c", "d", addr1, pk1, coinNeg, false}, - {"negative bond", "a", "b", "c", "d", addr1, pk1, coinNeg, false}, + {"basic good", "a", "b", "c", "d", commission1, addr1, pk1, coinPos, true}, + {"partial description", "", "", "c", "", commission1, addr1, pk1, coinPos, true}, + {"empty description", "", "", "", "", commission2, addr1, pk1, coinPos, false}, + {"empty address", "a", "b", "c", "d", commission2, emptyAddr, pk1, coinPos, false}, + {"empty pubkey", "a", "b", "c", "d", commission1, addr1, emptyPubkey, coinPos, true}, + {"empty bond", "a", "b", "c", "d", commission2, addr1, pk1, coinZero, false}, + {"negative bond", "a", "b", "c", "d", commission2, addr1, pk1, coinNeg, false}, + {"negative bond", "a", "b", "c", "d", commission1, addr1, pk1, coinNeg, false}, } for _, tc := range tests { description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgCreateValidator(tc.validatorAddr, tc.pubkey, tc.bond, description) + msg := NewMsgCreateValidator(tc.validatorAddr, tc.pubkey, tc.bond, description, tc.commissionMsg) if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) } else { @@ -60,7 +64,9 @@ func TestMsgEditValidator(t *testing.T) { for _, tc := range tests { description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgEditValidator(tc.validatorAddr, description) + newRate := sdk.ZeroDec() + + msg := NewMsgEditValidator(tc.validatorAddr, description, &newRate) if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) } else { @@ -71,28 +77,35 @@ func TestMsgEditValidator(t *testing.T) { // test ValidateBasic and GetSigners for MsgCreateValidatorOnBehalfOf func TestMsgCreateValidatorOnBehalfOf(t *testing.T) { + commission1 := NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + commission2 := NewCommissionMsg(sdk.NewDec(5), sdk.NewDec(5), sdk.NewDec(5)) + tests := []struct { name, moniker, identity, website, details string + commissionMsg CommissionMsg delegatorAddr sdk.AccAddress validatorAddr sdk.ValAddress validatorPubKey crypto.PubKey bond sdk.Coin expectPass bool }{ - {"basic good", "a", "b", "c", "d", sdk.AccAddress(addr1), addr2, pk2, coinPos, true}, - {"partial description", "", "", "c", "", sdk.AccAddress(addr1), addr2, pk2, coinPos, true}, - {"empty description", "", "", "", "", sdk.AccAddress(addr1), addr2, pk2, coinPos, false}, - {"empty delegator address", "a", "b", "c", "d", sdk.AccAddress(emptyAddr), addr2, pk2, coinPos, false}, - {"empty validator address", "a", "b", "c", "d", sdk.AccAddress(addr1), emptyAddr, pk2, coinPos, false}, - {"empty pubkey", "a", "b", "c", "d", sdk.AccAddress(addr1), addr2, emptyPubkey, coinPos, true}, - {"empty bond", "a", "b", "c", "d", sdk.AccAddress(addr1), addr2, pk2, coinZero, false}, - {"negative bond", "a", "b", "c", "d", sdk.AccAddress(addr1), addr2, pk2, coinNeg, false}, - {"negative bond", "a", "b", "c", "d", sdk.AccAddress(addr1), addr2, pk2, coinNeg, false}, + {"basic good", "a", "b", "c", "d", commission2, sdk.AccAddress(addr1), addr2, pk2, coinPos, true}, + {"partial description", "", "", "c", "", commission2, sdk.AccAddress(addr1), addr2, pk2, coinPos, true}, + {"empty description", "", "", "", "", commission1, sdk.AccAddress(addr1), addr2, pk2, coinPos, false}, + {"empty delegator address", "a", "b", "c", "d", commission1, sdk.AccAddress(emptyAddr), addr2, pk2, coinPos, false}, + {"empty validator address", "a", "b", "c", "d", commission2, sdk.AccAddress(addr1), emptyAddr, pk2, coinPos, false}, + {"empty pubkey", "a", "b", "c", "d", commission1, sdk.AccAddress(addr1), addr2, emptyPubkey, coinPos, true}, + {"empty bond", "a", "b", "c", "d", commission2, sdk.AccAddress(addr1), addr2, pk2, coinZero, false}, + {"negative bond", "a", "b", "c", "d", commission1, sdk.AccAddress(addr1), addr2, pk2, coinNeg, false}, + {"negative bond", "a", "b", "c", "d", commission2, sdk.AccAddress(addr1), addr2, pk2, coinNeg, false}, } for _, tc := range tests { description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgCreateValidatorOnBehalfOf(tc.delegatorAddr, tc.validatorAddr, tc.validatorPubKey, tc.bond, description) + msg := NewMsgCreateValidatorOnBehalfOf( + tc.delegatorAddr, tc.validatorAddr, tc.validatorPubKey, tc.bond, description, tc.commissionMsg, + ) + if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) } else { @@ -100,11 +113,11 @@ func TestMsgCreateValidatorOnBehalfOf(t *testing.T) { } } - msg := NewMsgCreateValidator(addr1, pk1, coinPos, Description{}) + msg := NewMsgCreateValidator(addr1, pk1, coinPos, Description{}, CommissionMsg{}) addrs := msg.GetSigners() require.Equal(t, []sdk.AccAddress{sdk.AccAddress(addr1)}, addrs, "Signers on default msg is wrong") - msg = NewMsgCreateValidatorOnBehalfOf(sdk.AccAddress(addr2), addr1, pk1, coinPos, Description{}) + msg = NewMsgCreateValidatorOnBehalfOf(sdk.AccAddress(addr2), addr1, pk1, coinPos, Description{}, CommissionMsg{}) addrs = msg.GetSigners() require.Equal(t, []sdk.AccAddress{sdk.AccAddress(addr2), sdk.AccAddress(addr1)}, addrs, "Signers for onbehalfof msg is wrong") } diff --git a/x/stake/types/params.go b/x/stake/types/params.go index bad4251cb..4dcc3782a 100644 --- a/x/stake/types/params.go +++ b/x/stake/types/params.go @@ -5,8 +5,8 @@ import ( "fmt" "time" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" ) // defaultUnbondingTime reflects three weeks in seconds as the default @@ -62,7 +62,7 @@ func (p Params) HumanReadableString() string { } // unmarshal the current staking params value from store key or panic -func MustUnmarshalParams(cdc *wire.Codec, value []byte) Params { +func MustUnmarshalParams(cdc *codec.Codec, value []byte) Params { params, err := UnmarshalParams(cdc, value) if err != nil { panic(err) @@ -71,7 +71,7 @@ func MustUnmarshalParams(cdc *wire.Codec, value []byte) Params { } // unmarshal the current staking params value from store key -func UnmarshalParams(cdc *wire.Codec, value []byte) (params Params, err error) { +func UnmarshalParams(cdc *codec.Codec, value []byte) (params Params, err error) { err = cdc.UnmarshalBinary(value, ¶ms) if err != nil { return diff --git a/x/stake/types/pool.go b/x/stake/types/pool.go index b01ed8e9a..c7cb69748 100644 --- a/x/stake/types/pool.go +++ b/x/stake/types/pool.go @@ -5,8 +5,8 @@ import ( "fmt" "time" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" ) // Pool - dynamic parameters of the current state @@ -142,7 +142,7 @@ func (p Pool) HumanReadableString() string { } // unmarshal the current pool value from store key or panics -func MustUnmarshalPool(cdc *wire.Codec, value []byte) Pool { +func MustUnmarshalPool(cdc *codec.Codec, value []byte) Pool { pool, err := UnmarshalPool(cdc, value) if err != nil { panic(err) @@ -151,7 +151,7 @@ func MustUnmarshalPool(cdc *wire.Codec, value []byte) Pool { } // unmarshal the current pool value from store key -func UnmarshalPool(cdc *wire.Codec, value []byte) (pool Pool, err error) { +func UnmarshalPool(cdc *codec.Codec, value []byte) (pool Pool, err error) { err = cdc.UnmarshalBinary(value, &pool) if err != nil { return diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index eaf15fc48..57794691f 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -9,8 +9,8 @@ import ( "github.com/tendermint/tendermint/crypto" tmtypes "github.com/tendermint/tendermint/types" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" ) // Validator defines the total amount of bond shares and their exchange rate to @@ -21,8 +21,8 @@ import ( // exchange rate. Voting power can be calculated as total bonds multiplied by // exchange rate. type Validator struct { - OperatorAddr sdk.ValAddress `json:"operator_address"` // address of the validator's operator - ConsPubKey crypto.PubKey `json:"consensus_pubkey"` // the consensus public key of the validator + OperatorAddr sdk.ValAddress `json:"operator_address"` // address of the validator's operator; bech encoded in JSON + ConsPubKey crypto.PubKey `json:"consensus_pubkey"` // the consensus public key of the validator; bech encoded in JSON Jailed bool `json:"jailed"` // has the validator been jailed from bonded status? Status sdk.BondStatus `json:"status"` // validator status (bonded/unbonding/unbonded) @@ -36,74 +36,62 @@ type Validator struct { UnbondingHeight int64 `json:"unbonding_height"` // if unbonding, height at which this validator has begun unbonding UnbondingMinTime time.Time `json:"unbonding_time"` // if unbonding, min time for the validator to complete unbonding - Commission sdk.Dec `json:"commission"` // XXX the commission rate of fees charged to any delegators - CommissionMax sdk.Dec `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge - CommissionChangeRate sdk.Dec `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission - CommissionChangeToday sdk.Dec `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time) + Commission Commission `json:"commission"` // commission parameters } // NewValidator - initialize a new validator func NewValidator(operator sdk.ValAddress, pubKey crypto.PubKey, description Description) Validator { return Validator{ - OperatorAddr: operator, - ConsPubKey: pubKey, - Jailed: false, - Status: sdk.Unbonded, - Tokens: sdk.ZeroDec(), - DelegatorShares: sdk.ZeroDec(), - Description: description, - BondHeight: int64(0), - BondIntraTxCounter: int16(0), - UnbondingHeight: int64(0), - UnbondingMinTime: time.Unix(0, 0), - Commission: sdk.ZeroDec(), - CommissionMax: sdk.ZeroDec(), - CommissionChangeRate: sdk.ZeroDec(), - CommissionChangeToday: sdk.ZeroDec(), + OperatorAddr: operator, + ConsPubKey: pubKey, + Jailed: false, + Status: sdk.Unbonded, + Tokens: sdk.ZeroDec(), + DelegatorShares: sdk.ZeroDec(), + Description: description, + BondHeight: int64(0), + BondIntraTxCounter: int16(0), + UnbondingHeight: int64(0), + UnbondingMinTime: time.Unix(0, 0).UTC(), + Commission: NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), } } // what's kept in the store value type validatorValue struct { - ConsPubKey crypto.PubKey - Jailed bool - Status sdk.BondStatus - Tokens sdk.Dec - DelegatorShares sdk.Dec - Description Description - BondHeight int64 - BondIntraTxCounter int16 - UnbondingHeight int64 - UnbondingMinTime time.Time - Commission sdk.Dec - CommissionMax sdk.Dec - CommissionChangeRate sdk.Dec - CommissionChangeToday sdk.Dec + ConsPubKey crypto.PubKey + Jailed bool + Status sdk.BondStatus + Tokens sdk.Dec + DelegatorShares sdk.Dec + Description Description + BondHeight int64 + BondIntraTxCounter int16 + UnbondingHeight int64 + UnbondingMinTime time.Time + Commission Commission } // return the redelegation without fields contained within the key for the store -func MustMarshalValidator(cdc *wire.Codec, validator Validator) []byte { +func MustMarshalValidator(cdc *codec.Codec, validator Validator) []byte { val := validatorValue{ - ConsPubKey: validator.ConsPubKey, - Jailed: validator.Jailed, - Status: validator.Status, - Tokens: validator.Tokens, - DelegatorShares: validator.DelegatorShares, - Description: validator.Description, - BondHeight: validator.BondHeight, - BondIntraTxCounter: validator.BondIntraTxCounter, - UnbondingHeight: validator.UnbondingHeight, - UnbondingMinTime: validator.UnbondingMinTime, - Commission: validator.Commission, - CommissionMax: validator.CommissionMax, - CommissionChangeRate: validator.CommissionChangeRate, - CommissionChangeToday: validator.CommissionChangeToday, + ConsPubKey: validator.ConsPubKey, + Jailed: validator.Jailed, + Status: validator.Status, + Tokens: validator.Tokens, + DelegatorShares: validator.DelegatorShares, + Description: validator.Description, + BondHeight: validator.BondHeight, + BondIntraTxCounter: validator.BondIntraTxCounter, + UnbondingHeight: validator.UnbondingHeight, + UnbondingMinTime: validator.UnbondingMinTime, + Commission: validator.Commission, } return cdc.MustMarshalBinary(val) } // unmarshal a redelegation from a store key and value -func MustUnmarshalValidator(cdc *wire.Codec, operatorAddr, value []byte) Validator { +func MustUnmarshalValidator(cdc *codec.Codec, operatorAddr, value []byte) Validator { validator, err := UnmarshalValidator(cdc, operatorAddr, value) if err != nil { panic(err) @@ -112,7 +100,7 @@ func MustUnmarshalValidator(cdc *wire.Codec, operatorAddr, value []byte) Validat } // unmarshal a redelegation from a store key and value -func UnmarshalValidator(cdc *wire.Codec, operatorAddr, value []byte) (validator Validator, err error) { +func UnmarshalValidator(cdc *codec.Codec, operatorAddr, value []byte) (validator Validator, err error) { if len(operatorAddr) != sdk.AddrLen { err = fmt.Errorf("%v", ErrBadValidatorAddr(DefaultCodespace).Data()) return @@ -124,21 +112,18 @@ func UnmarshalValidator(cdc *wire.Codec, operatorAddr, value []byte) (validator } return Validator{ - OperatorAddr: operatorAddr, - ConsPubKey: storeValue.ConsPubKey, - Jailed: storeValue.Jailed, - Tokens: storeValue.Tokens, - Status: storeValue.Status, - DelegatorShares: storeValue.DelegatorShares, - Description: storeValue.Description, - BondHeight: storeValue.BondHeight, - BondIntraTxCounter: storeValue.BondIntraTxCounter, - UnbondingHeight: storeValue.UnbondingHeight, - UnbondingMinTime: storeValue.UnbondingMinTime, - Commission: storeValue.Commission, - CommissionMax: storeValue.CommissionMax, - CommissionChangeRate: storeValue.CommissionChangeRate, - CommissionChangeToday: storeValue.CommissionChangeToday, + OperatorAddr: operatorAddr, + ConsPubKey: storeValue.ConsPubKey, + Jailed: storeValue.Jailed, + Tokens: storeValue.Tokens, + Status: storeValue.Status, + DelegatorShares: storeValue.DelegatorShares, + Description: storeValue.Description, + BondHeight: storeValue.BondHeight, + BondIntraTxCounter: storeValue.BondIntraTxCounter, + UnbondingHeight: storeValue.UnbondingHeight, + UnbondingMinTime: storeValue.UnbondingMinTime, + Commission: storeValue.Commission, }, nil } @@ -156,24 +141,21 @@ func (v Validator) HumanReadableString() (string, error) { resp += fmt.Sprintf("Validator Consensus Pubkey: %s\n", bechConsPubKey) resp += fmt.Sprintf("Jailed: %v\n", v.Jailed) resp += fmt.Sprintf("Status: %s\n", sdk.BondStatusToString(v.Status)) - resp += fmt.Sprintf("Tokens: %s\n", v.Tokens.String()) - resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares.String()) + resp += fmt.Sprintf("Tokens: %s\n", v.Tokens) + resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares) resp += fmt.Sprintf("Description: %s\n", v.Description) resp += fmt.Sprintf("Bond Height: %d\n", v.BondHeight) resp += fmt.Sprintf("Unbonding Height: %d\n", v.UnbondingHeight) resp += fmt.Sprintf("Minimum Unbonding Time: %v\n", v.UnbondingMinTime) - resp += fmt.Sprintf("Commission: %s\n", v.Commission.String()) - resp += fmt.Sprintf("Max Commission Rate: %s\n", v.CommissionMax.String()) - resp += fmt.Sprintf("Commission Change Rate: %s\n", v.CommissionChangeRate.String()) - resp += fmt.Sprintf("Commission Change Today: %s\n", v.CommissionChangeToday.String()) + resp += fmt.Sprintf("Commission: {%s}\n", v.Commission) return resp, nil } //___________________________________________________________________ -// validator struct for bech output -type BechValidator struct { +// this is a helper struct used for JSON de- and encoding only +type bechValidator struct { OperatorAddr sdk.ValAddress `json:"operator_address"` // the bech32 address of the validator's operator ConsPubKey string `json:"consensus_pubkey"` // the bech32 consensus public key of the validator Jailed bool `json:"jailed"` // has the validator been jailed from bonded status? @@ -189,56 +171,70 @@ type BechValidator struct { UnbondingHeight int64 `json:"unbonding_height"` // if unbonding, height at which this validator has begun unbonding UnbondingMinTime time.Time `json:"unbonding_time"` // if unbonding, min time for the validator to complete unbonding - Commission sdk.Dec `json:"commission"` // XXX the commission rate of fees charged to any delegators - CommissionMax sdk.Dec `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge - CommissionChangeRate sdk.Dec `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission - CommissionChangeToday sdk.Dec `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time) + Commission Commission `json:"commission"` // commission parameters } -// get the bech validator from the the regular validator -func (v Validator) Bech32Validator() (BechValidator, error) { +// MarshalJSON marshals the validator to JSON using Bech32 +func (v Validator) MarshalJSON() ([]byte, error) { bechConsPubKey, err := sdk.Bech32ifyConsPub(v.ConsPubKey) if err != nil { - return BechValidator{}, err + return nil, err } - return BechValidator{ - OperatorAddr: v.OperatorAddr, - ConsPubKey: bechConsPubKey, - Jailed: v.Jailed, - - Status: v.Status, - Tokens: v.Tokens, - DelegatorShares: v.DelegatorShares, - + return codec.Cdc.MarshalJSON(bechValidator{ + OperatorAddr: v.OperatorAddr, + ConsPubKey: bechConsPubKey, + Jailed: v.Jailed, + Status: v.Status, + Tokens: v.Tokens, + DelegatorShares: v.DelegatorShares, Description: v.Description, BondHeight: v.BondHeight, BondIntraTxCounter: v.BondIntraTxCounter, UnbondingHeight: v.UnbondingHeight, UnbondingMinTime: v.UnbondingMinTime, + Commission: v.Commission, + }) +} - Commission: v.Commission, - CommissionMax: v.CommissionMax, - CommissionChangeRate: v.CommissionChangeRate, - CommissionChangeToday: v.CommissionChangeToday, - }, nil +// UnmarshalJSON unmarshals the validator from JSON using Bech32 +func (v *Validator) UnmarshalJSON(data []byte) error { + bv := &bechValidator{} + if err := codec.Cdc.UnmarshalJSON(data, bv); err != nil { + return err + } + consPubKey, err := sdk.GetConsPubKeyBech32(bv.ConsPubKey) + if err != nil { + return err + } + *v = Validator{ + OperatorAddr: bv.OperatorAddr, + ConsPubKey: consPubKey, + Jailed: bv.Jailed, + Tokens: bv.Tokens, + Status: bv.Status, + DelegatorShares: bv.DelegatorShares, + Description: bv.Description, + BondHeight: bv.BondHeight, + BondIntraTxCounter: bv.BondIntraTxCounter, + UnbondingHeight: bv.UnbondingHeight, + UnbondingMinTime: bv.UnbondingMinTime, + Commission: bv.Commission, + } + return nil } //___________________________________________________________________ // only the vitals - does not check bond height of IntraTxCounter -// nolint gocyclo - why dis fail? -func (v Validator) Equal(c2 Validator) bool { - return v.ConsPubKey.Equals(c2.ConsPubKey) && - bytes.Equal(v.OperatorAddr, c2.OperatorAddr) && - v.Status.Equal(c2.Status) && - v.Tokens.Equal(c2.Tokens) && - v.DelegatorShares.Equal(c2.DelegatorShares) && - v.Description == c2.Description && - v.Commission.Equal(c2.Commission) && - v.CommissionMax.Equal(c2.CommissionMax) && - v.CommissionChangeRate.Equal(c2.CommissionChangeRate) && - v.CommissionChangeToday.Equal(c2.CommissionChangeToday) +func (v Validator) Equal(v2 Validator) bool { + return v.ConsPubKey.Equals(v2.ConsPubKey) && + bytes.Equal(v.OperatorAddr, v2.OperatorAddr) && + v.Status.Equal(v2.Status) && + v.Tokens.Equal(v2.Tokens) && + v.DelegatorShares.Equal(v2.DelegatorShares) && + v.Description == v2.Description && + v.Commission.Equal(v2.Commission) } // return the TM validator address @@ -372,6 +368,17 @@ func (v Validator) RemoveTokens(pool Pool, tokens sdk.Dec) (Validator, Pool) { return v, pool } +// SetInitialCommission attempts to set a validator's initial commission. An +// error is returned if the commission is invalid. +func (v Validator) SetInitialCommission(commission Commission) (Validator, sdk.Error) { + if err := commission.Validate(); err != nil { + return v, err + } + + v.Commission = commission + return v, nil +} + //_________________________________________________________________________________________________________ // AddTokensFromDel adds tokens to a validator @@ -442,12 +449,14 @@ func (v Validator) IsUnbonded(ctx sdk.Context) bool { var _ sdk.Validator = Validator{} // nolint - for sdk.Validator -func (v Validator) GetJailed() bool { return v.Jailed } -func (v Validator) GetMoniker() string { return v.Description.Moniker } -func (v Validator) GetStatus() sdk.BondStatus { return v.Status } -func (v Validator) GetOperator() sdk.ValAddress { return v.OperatorAddr } -func (v Validator) GetPubKey() crypto.PubKey { return v.ConsPubKey } -func (v Validator) GetPower() sdk.Dec { return v.BondedTokens() } -func (v Validator) GetTokens() sdk.Dec { return v.Tokens } -func (v Validator) GetDelegatorShares() sdk.Dec { return v.DelegatorShares } -func (v Validator) GetBondHeight() int64 { return v.BondHeight } +func (v Validator) GetJailed() bool { return v.Jailed } +func (v Validator) GetMoniker() string { return v.Description.Moniker } +func (v Validator) GetStatus() sdk.BondStatus { return v.Status } +func (v Validator) GetOperator() sdk.ValAddress { return v.OperatorAddr } +func (v Validator) GetConsPubKey() crypto.PubKey { return v.ConsPubKey } +func (v Validator) GetConsAddr() sdk.ConsAddress { return sdk.ConsAddress(v.ConsPubKey.Address()) } +func (v Validator) GetPower() sdk.Dec { return v.BondedTokens() } +func (v Validator) GetTokens() sdk.Dec { return v.Tokens } +func (v Validator) GetCommission() sdk.Dec { return v.Commission.Rate } +func (v Validator) GetDelegatorShares() sdk.Dec { return v.DelegatorShares } +func (v Validator) GetBondHeight() int64 { return v.BondHeight } diff --git a/x/stake/types/validator_test.go b/x/stake/types/validator_test.go index 31b8a03c0..36ac0da57 100644 --- a/x/stake/types/validator_test.go +++ b/x/stake/types/validator_test.go @@ -5,7 +5,9 @@ import ( "testing" "time" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" tmtypes "github.com/tendermint/tendermint/types" @@ -260,3 +262,49 @@ func TestHumanReadableString(t *testing.T) { require.Nil(t, err) require.NotEmpty(t, valStr) } + +func TestValidatorMarshalUnmarshalJSON(t *testing.T) { + validator := NewValidator(addr1, pk1, Description{}) + js, err := codec.Cdc.MarshalJSON(validator) + require.NoError(t, err) + require.NotEmpty(t, js) + require.Contains(t, string(js), "\"consensus_pubkey\":\"cosmosvalconspu") + got := &Validator{} + err = codec.Cdc.UnmarshalJSON(js, got) + assert.NoError(t, err) + assert.Equal(t, validator, *got) +} + +func TestValidatorSetInitialCommission(t *testing.T) { + val := NewValidator(addr1, pk1, Description{}) + testCases := []struct { + validator Validator + commission Commission + expectedErr bool + }{ + {val, NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), false}, + {val, NewCommission(sdk.ZeroDec(), sdk.NewDecWithPrec(-1, 1), sdk.ZeroDec()), true}, + {val, NewCommission(sdk.ZeroDec(), sdk.NewDec(15000000000), sdk.ZeroDec()), true}, + {val, NewCommission(sdk.NewDecWithPrec(-1, 1), sdk.ZeroDec(), sdk.ZeroDec()), true}, + {val, NewCommission(sdk.NewDecWithPrec(2, 1), sdk.NewDecWithPrec(1, 1), sdk.ZeroDec()), true}, + {val, NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(-1, 1)), true}, + {val, NewCommission(sdk.ZeroDec(), sdk.NewDecWithPrec(1, 1), sdk.NewDecWithPrec(2, 1)), true}, + } + + for i, tc := range testCases { + val, err := tc.validator.SetInitialCommission(tc.commission) + + if tc.expectedErr { + require.Error(t, err, + "expected error for test case #%d with commission: %s", i, tc.commission, + ) + } else { + require.NoError(t, err, + "unexpected error for test case #%d with commission: %s", i, tc.commission, + ) + require.Equal(t, tc.commission, val.Commission, + "invalid validator commission for test case #%d with commission: %s", i, tc.commission, + ) + } + } +}