diff --git a/.circleci/config.yml b/.circleci/config.yml index 879df07f0..708b5887d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -137,6 +137,24 @@ jobs: export PATH="$GOBIN:$PATH" make test_sim_gaia_fast + test_sim_gaia_import_export: + <<: *defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - run: + name: dependencies + command: | + export PATH="$GOBIN:$PATH" + make get_vendor_deps + - run: + name: Test Gaia import/export simulation + command: | + export PATH="$GOBIN:$PATH" + make test_sim_gaia_import_export + test_sim_gaia_multi_seed: <<: *defaults parallelism: 1 @@ -259,6 +277,9 @@ workflows: - test_sim_gaia_fast: requires: - setup_dependencies + - test_sim_gaia_import_export: + requires: + - setup_dependencies - test_sim_gaia_multi_seed: requires: - setup_dependencies diff --git a/CHANGELOG.md b/CHANGELOG.md index 764d481f8..f635b7ea6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,62 @@ # Changelog +## 0.26.0 + +BREAKING CHANGES + +* Gaia + * [gaiad init] [\#2602](https://github.com/cosmos/cosmos-sdk/issues/2602) New genesis workflow + +* SDK + * [simulation] [\#2665](https://github.com/cosmos/cosmos-sdk/issues/2665) only argument to simulation.Invariant is now app + +* Tendermint + * Upgrade to version 0.26.0 + +FEATURES + +* Gaia CLI (`gaiacli`) + * [cli] [\#2569](https://github.com/cosmos/cosmos-sdk/pull/2569) Add commands to query validator unbondings and redelegations + * [cli] [\#2569](https://github.com/cosmos/cosmos-sdk/pull/2569) Add commands to query validator unbondings and redelegations + * [cli] [\#2524](https://github.com/cosmos/cosmos-sdk/issues/2524) Add support offline mode to `gaiacli tx sign`. Lookups are not performed if the flag `--offline` is on. + * [cli] [\#2558](https://github.com/cosmos/cosmos-sdk/issues/2558) Rename --print-sigs to --validate-signatures. It now performs a complete set of sanity checks and reports to the user. Also added --print-signature-only to print the signature only, not the whole transaction. + +* SDK + * [\#1336](https://github.com/cosmos/cosmos-sdk/issues/1336) Mechanism for SDK Users to configure their own Bech32 prefixes instead of using the default cosmos prefixes. + +IMPROVEMENTS + +* Gaia + * [\#2637](https://github.com/cosmos/cosmos-sdk/issues/2637) [x/gov] Switched inactive and active proposal queues to an iterator based queue + +* SDK + * [\#2573](https://github.com/cosmos/cosmos-sdk/issues/2573) [x/distribution] add accum invariance + * [\#2556](https://github.com/cosmos/cosmos-sdk/issues/2556) [x/mock/simulation] Fix debugging output + * [\#2396](https://github.com/cosmos/cosmos-sdk/issues/2396) [x/mock/simulation] Change parameters to get more slashes + * [\#2617](https://github.com/cosmos/cosmos-sdk/issues/2617) [x/mock/simulation] Randomize all genesis parameters + * [\#2669](https://github.com/cosmos/cosmos-sdk/issues/2669) [x/stake] Added invarant check to make sure validator's power aligns with its spot in the power store. + * [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) [x/mock/simulation] Use a transition matrix for block size + * [\#2660](https://github.com/cosmos/cosmos-sdk/issues/2660) [x/mock/simulation] Staking transactions get tested far more frequently + * [\#2610](https://github.com/cosmos/cosmos-sdk/issues/2610) [x/stake] Block redelegation to and from the same validator + * [\#2652](https://github.com/cosmos/cosmos-sdk/issues/2652) [x/auth] Add benchmark for get and set account + * [\#2685](https://github.com/cosmos/cosmos-sdk/issues/2685) [store] Add general merkle absence proof (also for empty substores) + * [\#2708](https://github.com/cosmos/cosmos-sdk/issues/2708) [store] Disallow setting nil values + +BUG FIXES + +* Gaia + * [\#2670](https://github.com/cosmos/cosmos-sdk/issues/2670) [x/stake] fixed incorrect `IterateBondedValidators` and split into two functions: `IterateBondedValidators` and `IterateLastBlockConsValidators` + * [\#2691](https://github.com/cosmos/cosmos-sdk/issues/2691) Fix local testnet creation by using a single canonical genesis time + - [\#2670](https://github.com/cosmos/cosmos-sdk/issues/2670) [x/stake] fixed incorrent `IterateBondedValidators` and split into two functions: `IterateBondedValidators` and `IterateLastBlockConsValidators` + - [\#2648](https://github.com/cosmos/cosmos-sdk/issues/2648) [gaiad] Fix `gaiad export` / `gaiad import` consistency, test in CI + +* SDK + * [\#2625](https://github.com/cosmos/cosmos-sdk/issues/2625) [x/gov] fix AppendTag function usage error + * [\#2677](https://github.com/cosmos/cosmos-sdk/issues/2677) [x/stake, x/distribution] various staking/distribution fixes as found by the simulator + * [\#2674](https://github.com/cosmos/cosmos-sdk/issues/2674) [types] Fix coin.IsLT() impl, coins.IsLT() impl, and renamed coins.Is\* to coins.IsAll\* (see [\#2686](https://github.com/cosmos/cosmos-sdk/issues/2686)) + * [\#2711](https://github.com/cosmos/cosmos-sdk/issues/2711) [x/stake] Add commission data to `MsgCreateValidator` signature bytes. + * Temporarily disable insecure mode for Gaia Lite + ## 0.25.0 *October 24th, 2018* @@ -752,7 +809,7 @@ Update to Tendermint v0.19.4 (fixes a consensus bug and improves logging) BREAKING CHANGES -* [stake] MarshalJSON -> MarshalBinary +* [stake] MarshalJSON -> MarshalBinaryLengthPrefixed * Queries against the store must be prefixed with the path "/store" FEATURES diff --git a/Gopkg.lock b/Gopkg.lock index 68350442d..8dd4be708 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -10,6 +10,7 @@ revision = "48b08affede2cea076a3cf13b2e3f72ed262b743" [[projects]] + branch = "master" digest = "1:09a7f74eb6bb3c0f14d8926610c87f569c5cff68e978d30e9a3540aeb626fdf0" name = "github.com/bartekn/go-bip39" packages = ["."] @@ -38,7 +39,7 @@ name = "github.com/btcsuite/btcd" packages = ["btcec"] pruneopts = "UT" - revision = "2a560b2036bee5e3679ec2133eb6520b2f195213" + revision = "67e573d211ace594f1366b4ce9d39726c4b19bd0" [[projects]] digest = "1:386de157f7d19259a7f9c81f26ce011223ce0f090353c1152ffdf730d7d10ac2" @@ -62,13 +63,6 @@ revision = "346938d642f2ec3594ed81d874461961cd0faa76" version = "v1.1.0" -[[projects]] - digest = "1:c7644c73a3d23741fdba8a99b1464e021a224b7e205be497271a8003a15ca41b" - name = "github.com/ebuchman/fail-test" - packages = ["."] - pruneopts = "UT" - revision = "95f809107225be108efcf10a3509e4ea6ceef3c4" - [[projects]] digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd" name = "github.com/fsnotify/fsnotify" @@ -245,12 +239,12 @@ version = "v1.0.0" [[projects]] - digest = "1:e32dfc6abff6a3633ef4d9a1022fd707c8ef26f1e1e8f855dc58dc415ce7c8f3" + digest = "1:53bc4cd4914cd7cd52139990d5170d6dc99067ae31c56530621b18b35fc30318" name = "github.com/mitchellh/mapstructure" packages = ["."] pruneopts = "UT" - revision = "fe40af7a9c397fa3ddba203c38a5042c5d0475ad" - version = "v1.1.1" + revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe" + version = "v1.1.2" [[projects]] digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e" @@ -296,7 +290,7 @@ [[projects]] branch = "master" - digest = "1:63b68062b8968092eb86bedc4e68894bd096ea6b24920faca8b9dcf451f54bb5" + digest = "1:db712fde5d12d6cdbdf14b777f0c230f4ff5ab0be8e35b239fc319953ed577a4" name = "github.com/prometheus/common" packages = [ "expfmt", @@ -304,11 +298,11 @@ "model", ] pruneopts = "UT" - revision = "c7de2306084e37d54b8be01f3541a8464345e9a5" + revision = "7e9e6cabbd393fc208072eedef99188d0ce788b6" [[projects]] branch = "master" - digest = "1:ef1dd9945e58ee9b635273d28c0ef3fa3742a7dedc038ebe207fd63e6ce000ef" + digest = "1:ef74914912f99c79434d9c09658274678bc85080ebe3ab32bec3940ebce5e1fc" name = "github.com/prometheus/procfs" packages = [ ".", @@ -317,7 +311,7 @@ "xfs", ] pruneopts = "UT" - revision = "418d78d0b9a7b7de3a6bbc8a23def624cc977bb2" + revision = "185b4288413d2a0dd0806f78c90dde719829e5ae" [[projects]] digest = "1:ea0700160aca4ef099f4e06686a665a87691f4248dddd40796925eda2e46bd64" @@ -346,12 +340,12 @@ version = "v1.1.2" [[projects]] - digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f" + digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc" name = "github.com/spf13/cast" packages = ["."] pruneopts = "UT" - revision = "8965335b8c7107321228e3e3702cab9832751bac" - version = "v1.2.0" + revision = "8c9545af88b134710ab1cd196795e7f2388358d7" + version = "v1.3.0" [[projects]] digest = "1:7ffc0983035bc7e297da3688d9fe19d60a420e9c38bef23f845c53788ed6a05e" @@ -370,12 +364,12 @@ version = "v1.0.0" [[projects]] - digest = "1:dab83a1bbc7ad3d7a6ba1a1cc1760f25ac38cdf7d96a5cdd55cd915a4f5ceaf9" + digest = "1:c1b1102241e7f645bc8e0c22ae352e8f0dc6484b6cb4d132fa9f24174e0119e2" name = "github.com/spf13/pflag" packages = ["."] pruneopts = "UT" - revision = "9a97c102cda95a86cec2345a6f09f55a939babf5" - version = "v1.0.2" + revision = "298182f68c66c05229eb03ac171abe6e309ee79a" + version = "v1.0.3" [[projects]] digest = "1:f8e1a678a2571e265f4bf91a3e5e32aa6b1474a55cb0ea849750cc177b664d96" @@ -424,35 +418,23 @@ revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df" [[projects]] - branch = "master" - digest = "1:087aaa7920e5d0bf79586feb57ce01c35c830396ab4392798112e8aae8c47722" - name = "github.com/tendermint/ed25519" - packages = [ - ".", - "edwards25519", - "extra25519", - ] - pruneopts = "UT" - revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057" - -[[projects]] - digest = "1:2c971a45c89ca2ccc735af50919cdee05fbdc54d4bf50625073693300e31ead8" + digest = "1:10b3a599325740c84a7c81f3f3cb2e1fdb70b3ea01b7fa28495567a2519df431" name = "github.com/tendermint/go-amino" packages = ["."] pruneopts = "UT" - revision = "faa6e731944e2b7b6a46ad202902851e8ce85bee" - version = "v0.12.0" + revision = "6dcc6ddc143e116455c94b25c1004c99e0d0ca12" + version = "v0.14.0" [[projects]] - digest = "1:53397098d6acb7613358683cc84ae59281a60c6033f0bff62fa8d3f279c6c430" + digest = "1:9f8c4c93658315a795ffd3e0c943d39f78067dd8382b8d7bcfaf6686b92f3978" name = "github.com/tendermint/iavl" packages = ["."] pruneopts = "UT" - revision = "3acc91fb8811db2c5409a855ae1f8e441fe98e2d" - version = "v0.11.0" + revision = "fa74114f764f9827c4ad5573f990ed25bf8c4bac" + version = "v0.11.1" [[projects]] - digest = "1:f9c7a1f3ee087476f4883c33cc7c1bdbe56b9670b2fb27855ea2f386393272f5" + digest = "1:395820b381043b9d2204e181ddf0f9147397c4a7b8f5dc3162de4cfcddf4589a" name = "github.com/tendermint/tendermint" packages = [ "abci/client", @@ -485,6 +467,7 @@ "libs/db", "libs/errors", "libs/events", + "libs/fail", "libs/flowrate", "libs/log", "libs/pubsub", @@ -505,7 +488,6 @@ "rpc/core", "rpc/core/types", "rpc/grpc", - "rpc/lib", "rpc/lib/client", "rpc/lib/server", "rpc/lib/types", @@ -518,8 +500,8 @@ "version", ] pruneopts = "UT" - revision = "90eda9bfb6e6daeed1c8015df41cb36772d91778" - version = "v0.25.1-rc0" + revision = "03e42d2e3866f01a00625f608e3bbfaeb30690de" + version = "v0.26.1-rc0" [[projects]] digest = "1:7886f86064faff6f8d08a3eb0e8c773648ff5a2e27730831e2bfbf07467f6666" @@ -530,13 +512,15 @@ version = "v0.1.0" [[projects]] - digest = "1:aaff04fa01d9b824fde6799759cc597b3ac3671b9ad31924c28b6557d0ee5284" + digest = "1:6f6dc6060c4e9ba73cf28aa88f12a69a030d3d19d518ef8e931879eaa099628d" name = "golang.org/x/crypto" packages = [ "bcrypt", "blowfish", "chacha20poly1305", "curve25519", + "ed25519", + "ed25519/internal/edwards25519", "hkdf", "internal/chacha20", "internal/subtle", @@ -705,6 +689,7 @@ "github.com/tendermint/tendermint/rpc/lib/client", "github.com/tendermint/tendermint/rpc/lib/server", "github.com/tendermint/tendermint/types", + "github.com/tendermint/tendermint/types/time", "github.com/tendermint/tendermint/version", "github.com/zondax/ledger-goclient", "golang.org/x/crypto/bcrypt", diff --git a/Gopkg.toml b/Gopkg.toml index 0902bc412..572600885 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -1,24 +1,3 @@ -# Gopkg.toml example -# -# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - [[constraint]] name = "github.com/bgentry/speakeasy" version = "~0.1.0" @@ -49,15 +28,15 @@ [[override]] name = "github.com/tendermint/go-amino" - version = "=v0.12.0" + version = "v0.14.0" [[override]] name = "github.com/tendermint/iavl" - version = "=v0.11.0" + version = "=v0.11.1" [[override]] name = "github.com/tendermint/tendermint" - version = "=0.25.1-rc0" + version = "v0.26.1-rc0" # TODO replace w/ 0.26.1 ## deps without releases: @@ -89,7 +68,6 @@ version = "1.0.0" ## transitive deps, without releases: -# [[override]] name = "github.com/syndtr/goleveldb" @@ -106,4 +84,3 @@ [prune] go-tests = true unused-packages = true - diff --git a/Makefile b/Makefile index b36ef61d4..aaa2fbcf3 100644 --- a/Makefile +++ b/Makefile @@ -169,13 +169,22 @@ test_sim_gaia_nondeterminism: test_sim_gaia_fast: @echo "Running quick Gaia simulation. This may take several minutes..." - @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=400 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=9 -v -timeout 24h + @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=500 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=10 -v -timeout 24h + +test_sim_gaia_import_export: + @echo "Running Gaia import/export simulation. This may take several minutes..." + @go test ./cmd/gaia/app -run TestGaiaImportExport -SimulationEnabled=true -SimulationNumBlocks=50 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=4 -v -timeout 24h + @go test ./cmd/gaia/app -run TestGaiaImportExport -SimulationEnabled=true -SimulationNumBlocks=50 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=11 -v -timeout 24h + @go test ./cmd/gaia/app -run TestGaiaImportExport -SimulationEnabled=true -SimulationNumBlocks=50 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=12 -v -timeout 24h + @go test ./cmd/gaia/app -run TestGaiaImportExport -SimulationEnabled=true -SimulationNumBlocks=50 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=13 -v -timeout 24h + @go test ./cmd/gaia/app -run TestGaiaImportExport -SimulationEnabled=true -SimulationNumBlocks=50 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=414 -v -timeout 24h + @go test ./cmd/gaia/app -run TestGaiaImportExport -SimulationEnabled=true -SimulationNumBlocks=50 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=4142 -v -timeout 24h test_sim_gaia_multi_seed: @echo "Running multi-seed Gaia simulation. This may take awhile!" - @bash scripts/multisim.sh 10 + @bash scripts/multisim.sh 25 -SIM_NUM_BLOCKS ?= 210 +SIM_NUM_BLOCKS ?= 500 SIM_BLOCK_SIZE ?= 200 SIM_COMMIT ?= true test_sim_gaia_benchmark: @@ -250,4 +259,5 @@ localnet-stop: check_tools check_dev_tools get_tools get_dev_tools get_vendor_deps draw_deps test test_cli test_unit \ test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update \ build-linux build-docker-gaiadnode localnet-start localnet-stop \ -format check-ledger test_sim_gaia_nondeterminism test_sim_modules test_sim_gaia_fast test_sim_gaia_multi_seed update_tools update_dev_tools +format check-ledger test_sim_gaia_nondeterminism test_sim_modules test_sim_gaia_fast \ +test_sim_gaia_multi_seed test_sim_gaia_import_export update_tools update_dev_tools diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 17942b976..827536d21 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -337,7 +337,7 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abc } // Encode with json - value := codec.Cdc.MustMarshalBinary(result) + value := codec.Cdc.MustMarshalBinaryLengthPrefixed(result) return abci.ResponseQuery{ Code: uint32(sdk.ABCICodeOK), Value: value, @@ -394,6 +394,7 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res ctx := sdk.NewContext(app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger). WithMinimumFees(app.minimumFees) + // Passes the rest of the path as an argument to the querier. // For example, in the path "custom/gov/proposal/test", the gov querier gets []string{"proposal", "test"} as the path resBytes, err := querier(ctx, path[2:], req) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index ff6cbb834..9f6414214 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -358,7 +358,7 @@ func testTxDecoder(cdc *codec.Codec) sdk.TxDecoder { if len(txBytes) == 0 { return nil, sdk.ErrTxDecode("txBytes are empty") } - err := cdc.UnmarshalBinary(txBytes, &tx) + err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) if err != nil { return nil, sdk.ErrTxDecode("").TraceSDK(err.Error()) } @@ -455,7 +455,7 @@ func TestCheckTx(t *testing.T) { for i := int64(0); i < nTxs; i++ { tx := newTxCounter(i, 0) - txBytes, err := codec.MarshalBinary(tx) + txBytes, err := codec.MarshalBinaryLengthPrefixed(tx) require.NoError(t, err) r := app.CheckTx(txBytes) assert.True(t, r.IsOK(), fmt.Sprintf("%v", r)) @@ -503,7 +503,7 @@ func TestDeliverTx(t *testing.T) { for i := 0; i < txPerHeight; i++ { counter := int64(blockN*txPerHeight + i) tx := newTxCounter(counter, counter) - txBytes, err := codec.MarshalBinary(tx) + txBytes, err := codec.MarshalBinaryLengthPrefixed(tx) require.NoError(t, err) res := app.DeliverTx(txBytes) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) @@ -544,7 +544,7 @@ func TestMultiMsgDeliverTx(t *testing.T) { { app.BeginBlock(abci.RequestBeginBlock{}) tx := newTxCounter(0, 0, 1, 2) - txBytes, err := codec.MarshalBinary(tx) + txBytes, err := codec.MarshalBinaryLengthPrefixed(tx) require.NoError(t, err) res := app.DeliverTx(txBytes) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) @@ -565,7 +565,7 @@ func TestMultiMsgDeliverTx(t *testing.T) { tx := newTxCounter(1, 3) tx.Msgs = append(tx.Msgs, msgCounter2{0}) tx.Msgs = append(tx.Msgs, msgCounter2{1}) - txBytes, err := codec.MarshalBinary(tx) + txBytes, err := codec.MarshalBinaryLengthPrefixed(tx) require.NoError(t, err) res := app.DeliverTx(txBytes) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) @@ -638,7 +638,7 @@ func TestSimulateTx(t *testing.T) { require.Equal(t, gasConsumed, result.GasUsed) // simulate by calling Query with encoded tx - txBytes, err := cdc.MarshalBinary(tx) + txBytes, err := cdc.MarshalBinaryLengthPrefixed(tx) require.Nil(t, err) query := abci.RequestQuery{ Path: "/app/simulate", @@ -648,7 +648,7 @@ func TestSimulateTx(t *testing.T) { require.True(t, queryResult.IsOK(), queryResult.Log) var res sdk.Result - codec.Cdc.MustUnmarshalBinary(queryResult.Value, &res) + codec.Cdc.MustUnmarshalBinaryLengthPrefixed(queryResult.Value, &res) require.Nil(t, err, "Result unmarshalling failed") require.True(t, res.IsOK(), res.Log) require.Equal(t, gasConsumed, res.GasUsed, res.Log) @@ -729,7 +729,7 @@ func TestRunInvalidTransaction(t *testing.T) { registerTestCodec(newCdc) newCdc.RegisterConcrete(&msgNoDecode{}, "cosmos-sdk/baseapp/msgNoDecode", nil) - txBytes, err := newCdc.MarshalBinary(tx) + txBytes, err := newCdc.MarshalBinaryLengthPrefixed(tx) require.NoError(t, err) res := app.DeliverTx(txBytes) require.EqualValues(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeTxDecode), res.Code) diff --git a/client/context/context.go b/client/context/context.go index ce36c37a8..9108b3d0b 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -121,8 +121,13 @@ func createVerifier() tmlite.Verifier { fmt.Printf("Must specify these options: %s when --trust-node is false\n", errMsg.String()) os.Exit(1) } + node := rpcclient.NewHTTP(nodeURI, "/websocket") - verifier, err := tmliteProxy.NewVerifier(chainID, filepath.Join(home, ".gaialite"), node, log.NewNopLogger()) + cacheSize := 10 // TODO: determine appropriate cache size + verifier, err := tmliteProxy.NewVerifier( + chainID, filepath.Join(home, ".gaialite"), + node, log.NewNopLogger(), cacheSize, + ) if err != nil { fmt.Printf("Create verifier failed: %s\n", err.Error()) diff --git a/client/context/query.go b/client/context/query.go index 8fca9becf..572a13778 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -10,9 +10,9 @@ import ( "strings" - "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/merkle" cmn "github.com/tendermint/tendermint/libs/common" tmliteErr "github.com/tendermint/tendermint/lite/errors" tmliteProxy "github.com/tendermint/tendermint/lite/proxy" @@ -54,7 +54,7 @@ func (ctx CLIContext) QuerySubspace(subspace []byte, storeName string) (res []sd return res, err } - ctx.Codec.MustUnmarshalBinary(resRaw, &res) + ctx.Codec.MustUnmarshalBinaryLengthPrefixed(resRaw, &res) return } @@ -157,8 +157,8 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro } opts := rpcclient.ABCIQueryOptions{ - Height: ctx.Height, - Trusted: ctx.TrustNode, + Height: ctx.Height, + Prove: !ctx.TrustNode, } result, err := node.ABCIQueryWithOptions(path, key, opts) @@ -198,7 +198,7 @@ func (ctx CLIContext) Verify(height int64) (tmtypes.SignedHeader, error) { } // verifyProof perform response proof verification. -func (ctx CLIContext) verifyProof(_ string, resp abci.ResponseQuery) error { +func (ctx CLIContext) verifyProof(queryPath string, resp abci.ResponseQuery) error { if ctx.Verifier == nil { return fmt.Errorf("missing valid certifier to verify data from distrusted node") } @@ -209,25 +209,22 @@ func (ctx CLIContext) verifyProof(_ string, resp abci.ResponseQuery) error { return err } - var multiStoreProof store.MultiStoreProof - cdc := codec.New() + // TODO: Instead of reconstructing, stash on CLIContext field? + prt := store.DefaultProofRuntime() - err = cdc.UnmarshalBinary(resp.Proof, &multiStoreProof) + // TODO: Better convention for path? + storeName, err := parseQueryStorePath(queryPath) if err != nil { - return errors.Wrap(err, "failed to unmarshalBinary rangeProof") + return err } - // verify the substore commit hash against trusted appHash - substoreCommitHash, err := store.VerifyMultiStoreCommitInfo( - multiStoreProof.StoreName, multiStoreProof.StoreInfos, commit.Header.AppHash, - ) - if err != nil { - return errors.Wrap(err, "failed in verifying the proof against appHash") - } + kp := merkle.KeyPath{} + kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL) + kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL) - err = store.VerifyRangeProof(resp.Key, resp.Value, substoreCommitHash, &multiStoreProof.RangeProof) + err = prt.VerifyValue(resp.Proof, commit.Header.AppHash, kp.String(), resp.Value) if err != nil { - return errors.Wrap(err, "failed in the range proof verification") + return errors.Wrap(err, "failed to prove merkle proof") } return nil @@ -241,20 +238,40 @@ func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([ } // isQueryStoreWithProof expects a format like /// -// queryType can be app or store. +// queryType must be "store" and subpath must be "key" to require a proof. func isQueryStoreWithProof(path string) bool { if !strings.HasPrefix(path, "/") { return false } paths := strings.SplitN(path[1:], "/", 3) - if len(paths) != 3 { + switch { + case len(paths) != 3: return false - } - - if store.RequireProof("/" + paths[2]) { + case paths[0] != "store": + return false + case store.RequireProof("/" + paths[2]): return true } return false } + +// parseQueryStorePath expects a format like /store//key. +func parseQueryStorePath(path string) (storeName string, err error) { + if !strings.HasPrefix(path, "/") { + return "", errors.New("expected path to start with /") + } + + paths := strings.SplitN(path[1:], "/", 3) + switch { + case len(paths) != 3: + return "", errors.New("expected format like /store//key") + case paths[0] != "store": + return "", errors.New("expected format like /store//key") + case paths[2] != "key": + return "", errors.New("expected format like /store//key") + } + + return paths[1], nil +} diff --git a/client/keys/utils.go b/client/keys/utils.go index 8a7eefea3..742b512d3 100644 --- a/client/keys/utils.go +++ b/client/keys/utils.go @@ -96,7 +96,12 @@ func GetKeyBaseFromDirWithWritePerm(rootDir string) (keys.Keybase, error) { // GetKeyBaseFromDir initializes a read-only keybase at a particular dir. func GetKeyBaseFromDir(rootDir string) (keys.Keybase, error) { - return getKeyBaseFromDirWithOpts(rootDir, &opt.Options{ReadOnly: true}) + // Disabled because of the inability to create a new keys database directory + // in the instance of when ReadOnly is set to true. + // + // ref: syndtr/goleveldb#240 + // return getKeyBaseFromDirWithOpts(rootDir, &opt.Options{ReadOnly: true}) + return getKeyBaseFromDirWithOpts(rootDir, nil) } func getKeyBaseFromDirWithOpts(rootDir string, o *opt.Options) (keys.Keybase, error) { diff --git a/client/lcd/certificates.go b/client/lcd/certificates.go index 1516ed35a..0ec527e13 100644 --- a/client/lcd/certificates.go +++ b/client/lcd/certificates.go @@ -38,6 +38,7 @@ func generateSelfSignedCert(host string) (certBytes []byte, priv *ecdsa.PrivateK Subject: pkix.Name{ Organization: []string{"Gaia Lite"}, }, + DNSNames: []string{"localhost"}, NotBefore: notBefore, NotAfter: notAfter, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, diff --git a/client/lcd/certificates_test.go b/client/lcd/certificates_test.go index 14bddfa0f..3f48c194c 100644 --- a/client/lcd/certificates_test.go +++ b/client/lcd/certificates_test.go @@ -17,7 +17,7 @@ func TestGenerateSelfSignedCert(t *testing.T) { cert, err := x509.ParseCertificate(certBytes) require.Nil(t, err) require.Equal(t, 2, len(cert.IPAddresses)) - require.Equal(t, 1, len(cert.DNSNames)) + require.Equal(t, 2, len(cert.DNSNames)) require.True(t, cert.IsCA) } diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index cb25721e8..92167346d 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -158,11 +158,11 @@ func TestNodeStatus(t *testing.T) { res, body := Request(t, port, "GET", "/node_info", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var nodeInfo p2p.NodeInfo + var nodeInfo p2p.DefaultNodeInfo err := cdc.UnmarshalJSON([]byte(body), &nodeInfo) require.Nil(t, err, "Couldn't parse node info") - require.NotEqual(t, p2p.NodeInfo{}, nodeInfo, "res: %v", res) + require.NotEqual(t, p2p.DefaultNodeInfo{}, nodeInfo, "res: %v", res) // syncing res, body = Request(t, port, "GET", "/syncing", nil) @@ -628,8 +628,8 @@ func TestSubmitProposal(t *testing.T) { require.Equal(t, uint32(0), resultTx.CheckTx.Code) require.Equal(t, uint32(0), resultTx.DeliverTx.Code) - var proposalID int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID) + var proposalID uint64 + cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID) // query proposal proposal := getProposal(t, port, proposalID) @@ -650,8 +650,8 @@ func TestDeposit(t *testing.T) { require.Equal(t, uint32(0), resultTx.CheckTx.Code) require.Equal(t, uint32(0), resultTx.DeliverTx.Code) - var proposalID int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID) + var proposalID uint64 + cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID) // query proposal proposal := getProposal(t, port, proposalID) @@ -684,8 +684,8 @@ func TestVote(t *testing.T) { require.Equal(t, uint32(0), resultTx.CheckTx.Code) require.Equal(t, uint32(0), resultTx.DeliverTx.Code) - var proposalID int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID) + var proposalID uint64 + cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID) // query proposal proposal := getProposal(t, port, proposalID) @@ -732,18 +732,18 @@ func TestProposalsQuery(t *testing.T) { // Addr1 proposes (and deposits) proposals #1 and #2 resultTx := doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], 5) - var proposalID1 int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID1) + var proposalID1 uint64 + cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID1) tests.WaitForHeight(resultTx.Height+1, port) resultTx = doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], 5) - var proposalID2 int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID2) + var proposalID2 uint64 + cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID2) tests.WaitForHeight(resultTx.Height+1, port) // Addr2 proposes (and deposits) proposals #3 resultTx = doSubmitProposal(t, port, seeds[1], names[1], passwords[1], addrs[1], 5) - var proposalID3 int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID3) + var proposalID3 uint64 + cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID3) tests.WaitForHeight(resultTx.Height+1, port) // Addr2 deposits on proposals #2 & #3 @@ -1230,7 +1230,7 @@ func getValidatorRedelegations(t *testing.T, port string, validatorAddr sdk.ValA // ============= Governance Module ================ -func getProposal(t *testing.T, port string, proposalID int64) gov.Proposal { +func getProposal(t *testing.T, port string, proposalID uint64) gov.Proposal { res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d", proposalID), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var proposal gov.Proposal @@ -1239,7 +1239,7 @@ func getProposal(t *testing.T, port string, proposalID int64) gov.Proposal { return proposal } -func getDeposits(t *testing.T, port string, proposalID int64) []gov.Deposit { +func getDeposits(t *testing.T, port string, proposalID uint64) []gov.Deposit { res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var deposits []gov.Deposit @@ -1248,7 +1248,7 @@ func getDeposits(t *testing.T, port string, proposalID int64) []gov.Deposit { return deposits } -func getDeposit(t *testing.T, port string, proposalID int64, depositerAddr sdk.AccAddress) gov.Deposit { +func getDeposit(t *testing.T, port string, proposalID uint64, depositerAddr sdk.AccAddress) gov.Deposit { res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/deposits/%s", proposalID, depositerAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var deposit gov.Deposit @@ -1257,7 +1257,7 @@ func getDeposit(t *testing.T, port string, proposalID int64, depositerAddr sdk.A return deposit } -func getVote(t *testing.T, port string, proposalID int64, voterAddr sdk.AccAddress) gov.Vote { +func getVote(t *testing.T, port string, proposalID uint64, voterAddr sdk.AccAddress) gov.Vote { res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/votes/%s", proposalID, voterAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var vote gov.Vote @@ -1266,7 +1266,7 @@ func getVote(t *testing.T, port string, proposalID int64, voterAddr sdk.AccAddre return vote } -func getVotes(t *testing.T, port string, proposalID int64) []gov.Vote { +func getVotes(t *testing.T, port string, proposalID uint64) []gov.Vote { res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/votes", proposalID), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var votes []gov.Vote @@ -1358,7 +1358,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA return results } -func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID int64, amount int64) (resultTx ctypes.ResultBroadcastTxCommit) { +func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64, amount int64) (resultTx ctypes.ResultBroadcastTxCommit) { acc := getAccount(t, port, proposerAddr) accnum := acc.GetAccountNumber() @@ -1388,7 +1388,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk return results } -func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID int64) (resultTx ctypes.ResultBroadcastTxCommit) { +func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64) (resultTx ctypes.ResultBroadcastTxCommit) { // get the account to get the sequence acc := getAccount(t, port, proposerAddr) accnum := acc.GetAccountNumber() diff --git a/client/lcd/root.go b/client/lcd/root.go index 475186ed0..8366b6114 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -2,6 +2,7 @@ package lcd import ( "errors" + "fmt" "net" "net/http" "os" @@ -12,6 +13,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest" @@ -21,7 +23,6 @@ import ( "github.com/rakyll/statik/fs" "github.com/spf13/cobra" "github.com/spf13/viper" - cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" tmserver "github.com/tendermint/tendermint/rpc/lib/server" ) @@ -47,24 +48,38 @@ func ServeCommand(cdc *codec.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) (err error) { listenAddr := viper.GetString(flagListenAddr) handler := createHandler(cdc) + registerSwaggerUI(handler) + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "rest-server") maxOpen := viper.GetInt(flagMaxOpenConnections) sslHosts := viper.GetString(flagSSLHosts) certFile := viper.GetString(flagSSLCertFile) keyFile := viper.GetString(flagSSLKeyFile) - cleanupFunc := func() {} var listener net.Listener var fingerprint string + + server.TrapSignal(func() { + err := listener.Close() + logger.Error("error closing listener", "err", err) + }) + + var cleanupFunc func() + + // TODO: re-enable insecure mode once #2715 has been addressed if viper.GetBool(flagInsecure) { - listener, err = tmserver.StartHTTPServer( - listenAddr, handler, logger, - tmserver.Config{MaxOpenConnections: maxOpen}, + fmt.Println( + "Insecure mode is temporarily disabled, please locally generate an " + + "SSL certificate to test. Support will be re-enabled soon!", ) - if err != nil { - return - } + // listener, err = tmserver.StartHTTPServer( + // listenAddr, handler, logger, + // tmserver.Config{MaxOpenConnections: maxOpen}, + // ) + // if err != nil { + // return + // } } else { if certFile != "" { // validateCertKeyFiles() is needed to work around tendermint/tendermint#2460 @@ -72,6 +87,7 @@ func ServeCommand(cdc *codec.Codec) *cobra.Command { if err != nil { return err } + // cert/key pair is provided, read the fingerprint fingerprint, err = fingerprintFromFile(certFile) if err != nil { @@ -83,12 +99,15 @@ func ServeCommand(cdc *codec.Codec) *cobra.Command { if err != nil { return err } + cleanupFunc = func() { os.Remove(certFile) os.Remove(keyFile) } + defer cleanupFunc() } + listener, err = tmserver.StartHTTPAndTLSServer( listenAddr, handler, certFile, keyFile, @@ -98,16 +117,12 @@ func ServeCommand(cdc *codec.Codec) *cobra.Command { if err != nil { return } - logger.Info(fingerprint) - } - logger.Info("REST server started") - // wait forever and cleanup - cmn.TrapSignal(func() { - defer cleanupFunc() - err := listener.Close() - logger.Error("error closing listener", "err", err) - }) + logger.Info(fingerprint) + logger.Info("REST server started") + } + + // logger.Info("REST server started") return nil }, @@ -124,6 +139,7 @@ func ServeCommand(cdc *codec.Codec) *cobra.Command { cmd.Flags().Int(flagMaxOpenConnections, 1000, "The number of maximum open connections") cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") cmd.Flags().Bool(client.FlagIndentResponse, false, "Add indent to JSON response") + viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index 2a81239ac..39c76865c 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -214,6 +214,7 @@ func InitializeTestLCD( genTxs := []json.RawMessage{} // append any additional (non-proposing) validators + var accs []gapp.GenesisAccount for i := 0; i < nValidators; i++ { operPrivKey := secp256k1.GenPrivKey() operAddr := operPrivKey.PubKey().Address() @@ -242,9 +243,17 @@ func InitializeTestLCD( genTxs = append(genTxs, txBytes) valConsPubKeys = append(valConsPubKeys, pubKey) valOperAddrs = append(valOperAddrs, sdk.ValAddress(operAddr)) + + accAuth := auth.NewBaseAccountWithAddress(sdk.AccAddress(operAddr)) + accAuth.Coins = sdk.Coins{sdk.NewInt64Coin("steak", 150)} + accs = append(accs, gapp.NewGenesisAccount(&accAuth)) } - genesisState, err := gapp.GaiaAppGenState(cdc, genTxs) + appGenState := gapp.NewDefaultGenesisState() + appGenState.Accounts = accs + genDoc.AppState, err = cdc.MarshalJSON(appGenState) + require.NoError(t, err) + genesisState, err := gapp.GaiaAppGenState(cdc, *genDoc, genTxs) require.NoError(t, err) // add some tokens to init accounts diff --git a/client/tx/query.go b/client/tx/query.go index da94404f2..3bbbf9a13 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -125,7 +125,7 @@ type Info struct { func parseTx(cdc *codec.Codec, txBytes []byte) (sdk.Tx, error) { var tx auth.StdTx - err := cdc.UnmarshalBinary(txBytes, &tx) + err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) if err != nil { return nil, err } diff --git a/client/utils/rest.go b/client/utils/rest.go index e9d948514..53c618692 100644 --- a/client/utils/rest.go +++ b/client/utils/rest.go @@ -65,6 +65,20 @@ func ParseInt64OrReturnBadRequest(w http.ResponseWriter, s string) (n int64, ok return n, true } +// ParseUint64OrReturnBadRequest converts s to a uint64 value. +func ParseUint64OrReturnBadRequest(w http.ResponseWriter, s string) (n uint64, ok bool) { + var err error + + n, err = strconv.ParseUint(s, 10, 64) + if err != nil { + err := fmt.Errorf("'%s' is not a valid uint64", s) + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return n, false + } + + return n, true +} + // ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a // default value, defaultIfEmpty, if the string is empty. func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEmpty float64) (n float64, ok bool) { diff --git a/client/utils/utils.go b/client/utils/utils.go index f5cf04168..fee7c7fe2 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -123,7 +123,8 @@ func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, // Check whether the address is a signer if !isTxSigner(sdk.AccAddress(addr), stdTx.GetSigners()) { - fmt.Fprintf(os.Stderr, "WARNING: The generated transaction's intended signer does not match the given signer: '%v'\n", name) + return signedStdTx, fmt.Errorf( + "The generated transaction's intended signer does not match the given signer: %q", name) } if !offline && txBldr.AccountNumber == 0 { @@ -166,7 +167,7 @@ func adjustGasEstimate(estimate int64, adjustment float64) int64 { func parseQueryResponse(cdc *amino.Codec, rawRes []byte) (int64, error) { var simulationResult sdk.Result - if err := cdc.UnmarshalBinary(rawRes, &simulationResult); err != nil { + if err := cdc.UnmarshalBinaryLengthPrefixed(rawRes, &simulationResult); err != nil { return 0, err } return simulationResult.GasUsed, nil diff --git a/client/utils/utils_test.go b/client/utils/utils_test.go index 731ded903..c3bf31569 100644 --- a/client/utils/utils_test.go +++ b/client/utils/utils_test.go @@ -12,7 +12,7 @@ import ( func TestParseQueryResponse(t *testing.T) { cdc := app.MakeCodec() - sdkResBytes := cdc.MustMarshalBinary(sdk.Result{GasUsed: 10}) + sdkResBytes := cdc.MustMarshalBinaryLengthPrefixed(sdk.Result{GasUsed: 10}) gas, err := parseQueryResponse(cdc, sdkResBytes) assert.Equal(t, gas, int64(10)) assert.Nil(t, err) @@ -28,7 +28,7 @@ func TestCalculateGas(t *testing.T) { if wantErr { return nil, errors.New("") } - return cdc.MustMarshalBinary(sdk.Result{GasUsed: gasUsed}), nil + return cdc.MustMarshalBinaryLengthPrefixed(sdk.Result{GasUsed: gasUsed}), nil } } type args struct { diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 21ef855f0..918bfc67d 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -109,7 +109,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio app.cdc, app.keyParams, app.tkeyParams, ) - app.stakeKeeper = stake.NewKeeper( + stakeKeeper := stake.NewKeeper( app.cdc, app.keyStake, app.tkeyStake, app.bankKeeper, app.paramsKeeper.Subspace(stake.DefaultParamspace), @@ -117,30 +117,32 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio ) app.mintKeeper = mint.NewKeeper(app.cdc, app.keyMint, app.paramsKeeper.Subspace(mint.DefaultParamspace), - app.stakeKeeper, app.feeCollectionKeeper, + &stakeKeeper, app.feeCollectionKeeper, ) app.distrKeeper = distr.NewKeeper( app.cdc, app.keyDistr, app.paramsKeeper.Subspace(distr.DefaultParamspace), - app.bankKeeper, app.stakeKeeper, app.feeCollectionKeeper, + app.bankKeeper, &stakeKeeper, app.feeCollectionKeeper, app.RegisterCodespace(stake.DefaultCodespace), ) app.slashingKeeper = slashing.NewKeeper( app.cdc, app.keySlashing, - app.stakeKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), + &stakeKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), app.RegisterCodespace(slashing.DefaultCodespace), ) app.govKeeper = gov.NewKeeper( app.cdc, app.keyGov, - app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamspace), app.bankKeeper, app.stakeKeeper, + app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamspace), app.bankKeeper, &stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace), ) // register the staking hooks - app.stakeKeeper = app.stakeKeeper.WithHooks( + // NOTE: stakeKeeper above are passed by reference, + // so that it can be modified like below: + app.stakeKeeper = *stakeKeeper.SetHooks( NewHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks())) // register message routes @@ -208,9 +210,6 @@ func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.R tags := gov.EndBlocker(ctx, app.govKeeper) validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper) - // Add these new validators to the addr -> pubkey map. - app.slashingKeeper.AddValidators(ctx, validatorUpdates) - return abci.ResponseEndBlock{ ValidatorUpdates: validatorUpdates, Tags: tags, @@ -229,6 +228,10 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci // return sdk.ErrGenesisParse("").TraceCause(err, "") } + // sort by account number to maintain consistency + sort.Slice(genesisState.Accounts, func(i, j int) bool { + return genesisState.Accounts[i].AccountNumber < genesisState.Accounts[j].AccountNumber + }) // load the accounts for _, gacc := range genesisState.Accounts { acc := gacc.ToAccount() @@ -242,7 +245,8 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci panic(err) // TODO find a way to do this w/o panics } - // load the address to pubkey map + // initialize module-specific stores + auth.InitGenesis(ctx, app.feeCollectionKeeper, genesisState.AuthData) slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakeData) gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData) mint.InitGenesis(ctx, app.mintKeeper, genesisState.MintData) @@ -259,7 +263,7 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci if err != nil { panic(err) } - bz := app.cdc.MustMarshalBinary(tx) + bz := app.cdc.MustMarshalBinaryLengthPrefixed(tx) res := app.BaseApp.DeliverTx(bz) if !res.IsOK() { panic(res.Log) @@ -268,12 +272,12 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci validators = app.stakeKeeper.ApplyAndReturnValidatorSetUpdates(ctx) } - app.slashingKeeper.AddValidators(ctx, validators) // sanity check if len(req.Validators) > 0 { if len(req.Validators) != len(validators) { - panic(fmt.Errorf("len(RequestInitChain.Validators) != len(validators) (%d != %d) ", len(req.Validators), len(validators))) + panic(fmt.Errorf("len(RequestInitChain.Validators) != len(validators) (%d != %d)", + len(req.Validators), len(validators))) } sort.Sort(abci.ValidatorUpdates(req.Validators)) sort.Sort(abci.ValidatorUpdates(validators)) @@ -303,11 +307,12 @@ func (app *GaiaApp) ExportAppStateAndValidators() (appState json.RawMessage, val app.accountKeeper.IterateAccounts(ctx, appendAccount) genState := NewGenesisState( accounts, - stake.WriteGenesis(ctx, app.stakeKeeper), - mint.WriteGenesis(ctx, app.mintKeeper), - distr.WriteGenesis(ctx, app.distrKeeper), - gov.WriteGenesis(ctx, app.govKeeper), - slashing.GenesisState{}, // TODO create write methods + auth.ExportGenesis(ctx, app.feeCollectionKeeper), + stake.ExportGenesis(ctx, app.stakeKeeper), + mint.ExportGenesis(ctx, app.mintKeeper), + distr.ExportGenesis(ctx, app.distrKeeper), + gov.ExportGenesis(ctx, app.govKeeper), + slashing.ExportGenesis(ctx, app.slashingKeeper), ) appState, err = codec.MarshalJSONIndent(app.cdc, genState) if err != nil { @@ -334,12 +339,15 @@ var _ sdk.StakingHooks = Hooks{} // nolint func (h Hooks) OnValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { h.dh.OnValidatorCreated(ctx, valAddr) + h.sh.OnValidatorCreated(ctx, valAddr) } func (h Hooks) OnValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { h.dh.OnValidatorModified(ctx, valAddr) + h.sh.OnValidatorModified(ctx, valAddr) } -func (h Hooks) OnValidatorRemoved(ctx sdk.Context, valAddr sdk.ValAddress) { - h.dh.OnValidatorRemoved(ctx, valAddr) +func (h Hooks) OnValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.OnValidatorRemoved(ctx, consAddr, valAddr) + h.sh.OnValidatorRemoved(ctx, consAddr, valAddr) } func (h Hooks) OnValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { h.dh.OnValidatorBonded(ctx, consAddr, valAddr) @@ -355,10 +363,13 @@ func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddre } func (h Hooks) OnDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { h.dh.OnDelegationCreated(ctx, delAddr, valAddr) + h.sh.OnDelegationCreated(ctx, delAddr, valAddr) } func (h Hooks) OnDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { h.dh.OnDelegationSharesModified(ctx, delAddr, valAddr) + h.sh.OnDelegationSharesModified(ctx, delAddr, valAddr) } func (h Hooks) OnDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { h.dh.OnDelegationRemoved(ctx, delAddr, valAddr) + h.sh.OnDelegationRemoved(ctx, delAddr, valAddr) } diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index df28bcf6c..38256e415 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -26,11 +26,13 @@ var ( // bonded tokens given to genesis validators/accounts freeFermionVal = int64(100) freeFermionsAcc = sdk.NewInt(150) + bondDenom = "steak" ) // State to Unmarshal type GenesisState struct { Accounts []GenesisAccount `json:"accounts"` + AuthData auth.GenesisState `json:"auth"` StakeData stake.GenesisState `json:"stake"` MintData mint.GenesisState `json:"mint"` DistrData distr.GenesisState `json:"distr"` @@ -39,11 +41,12 @@ type GenesisState struct { GenTxs []json.RawMessage `json:"gentxs"` } -func NewGenesisState(accounts []GenesisAccount, stakeData stake.GenesisState, mintData mint.GenesisState, +func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, stakeData stake.GenesisState, mintData mint.GenesisState, distrData distr.GenesisState, govData gov.GenesisState, slashingData slashing.GenesisState) GenesisState { return GenesisState{ Accounts: accounts, + AuthData: authData, StakeData: stakeData, MintData: mintData, DistrData: distrData, @@ -52,31 +55,39 @@ func NewGenesisState(accounts []GenesisAccount, stakeData stake.GenesisState, mi } } -// GenesisAccount doesn't need pubkey or sequence +// nolint type GenesisAccount struct { - Address sdk.AccAddress `json:"address"` - Coins sdk.Coins `json:"coins"` + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` + Sequence int64 `json:"sequence_number"` + AccountNumber int64 `json:"account_number"` } func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount { return GenesisAccount{ - Address: acc.Address, - Coins: acc.Coins, + Address: acc.Address, + Coins: acc.Coins, + AccountNumber: acc.AccountNumber, + Sequence: acc.Sequence, } } func NewGenesisAccountI(acc auth.Account) GenesisAccount { return GenesisAccount{ - Address: acc.GetAddress(), - Coins: acc.GetCoins(), + Address: acc.GetAddress(), + Coins: acc.GetCoins(), + AccountNumber: acc.GetAccountNumber(), + Sequence: acc.GetSequence(), } } // convert GenesisAccount to auth.BaseAccount func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount) { return &auth.BaseAccount{ - Address: ga.Address, - Coins: ga.Coins.Sort(), + Address: ga.Address, + Coins: ga.Coins.Sort(), + AccountNumber: ga.AccountNumber, + Sequence: ga.Sequence, } } @@ -90,58 +101,60 @@ func GaiaAppInit() server.AppInit { // Create the core parameters for genesis initialization for gaia // note that the pubkey input is this machines pubkey -func GaiaAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (genesisState GenesisState, err error) { - if len(appGenTxs) == 0 { - err = errors.New("must provide at least genesis transaction") - return +func GaiaAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( + genesisState GenesisState, err error) { + + if err = cdc.UnmarshalJSON(genDoc.AppState, &genesisState); err != nil { + return genesisState, err } - // start with the default staking genesis state - stakeData := stake.DefaultGenesisState() - slashingData := slashing.DefaultGenesisState() - - // get genesis flag account information - genaccs := make([]GenesisAccount, len(appGenTxs)) + // if there are no gen txs to be processed, return the default empty state + if len(appGenTxs) == 0 { + return genesisState, errors.New("there must be at least one genesis tx") + } + stakeData := genesisState.StakeData for i, genTx := range appGenTxs { var tx auth.StdTx - err = cdc.UnmarshalJSON(genTx, &tx) - if err != nil { - return + if err := cdc.UnmarshalJSON(genTx, &tx); err != nil { + return genesisState, err } msgs := tx.GetMsgs() if len(msgs) != 1 { - err = errors.New("must provide genesis StdTx with exactly 1 CreateValidator message") - return + return genesisState, errors.New( + "must provide genesis StdTx with exactly 1 CreateValidator message") + } + if _, ok := msgs[0].(stake.MsgCreateValidator); !ok { + return genesisState, fmt.Errorf( + "Genesis transaction %v does not contain a MsgCreateValidator", i) } - msg := msgs[0].(stake.MsgCreateValidator) - - // create the genesis account, give'm few steaks and a buncha token with there name - genaccs[i] = genesisAccountFromMsgCreateValidator(msg, freeFermionsAcc) - stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(freeFermionsAcc)) // increase the supply } - // create the final app state - genesisState = GenesisState{ - Accounts: genaccs, - StakeData: stakeData, + for _, acc := range genesisState.Accounts { + // create the genesis account, give'm few steaks and a buncha token with there name + for _, coin := range acc.Coins { + if coin.Denom == bondDenom { + stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens. + Add(sdk.NewDecFromInt(coin.Amount)) // increase the supply + } + } + } + genesisState.StakeData = stakeData + genesisState.GenTxs = appGenTxs + return genesisState, nil +} + +// NewDefaultGenesisState generates the default state for gaia. +func NewDefaultGenesisState() GenesisState { + return GenesisState{ + Accounts: nil, + StakeData: stake.DefaultGenesisState(), MintData: mint.DefaultGenesisState(), DistrData: distr.DefaultGenesisState(), GovData: gov.DefaultGenesisState(), - SlashingData: slashingData, - GenTxs: appGenTxs, + SlashingData: slashing.DefaultGenesisState(), + GenTxs: nil, } - - return -} - -func genesisAccountFromMsgCreateValidator(msg stake.MsgCreateValidator, amount sdk.Int) GenesisAccount { - accAuth := auth.NewBaseAccountWithAddress(sdk.AccAddress(msg.ValidatorAddr)) - accAuth.Coins = []sdk.Coin{ - {msg.Description.Moniker + "Token", sdk.NewInt(1000)}, - {"steak", amount}, - } - return NewGenesisAccount(&accAuth) } // GaiaValidateGenesisState ensures that the genesis state obeys the expected invariants @@ -175,27 +188,43 @@ func validateGenesisStateAccounts(accs []GenesisAccount) (err error) { } // GaiaAppGenState but with JSON -func GaiaAppGenStateJSON(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { +func GaiaAppGenStateJSON(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( + appState json.RawMessage, err error) { // create the final app state - genesisState, err := GaiaAppGenState(cdc, appGenTxs) + genesisState, err := GaiaAppGenState(cdc, genDoc, appGenTxs) if err != nil { return nil, err } - appState, err = codec.MarshalJSONIndent(cdc, genesisState) - return + return codec.MarshalJSONIndent(cdc, genesisState) } -// CollectStdTxs processes and validates application's genesis StdTxs and returns the list of validators, -// appGenTxs, and persistent peers required to generate genesis.json. -func CollectStdTxs(moniker string, genTxsDir string, cdc *codec.Codec) ( - validators []tmtypes.GenesisValidator, appGenTxs []auth.StdTx, persistentPeers string, err error) { +// CollectStdTxs processes and validates application's genesis StdTxs and returns +// the list of appGenTxs, and persistent peers required to generate genesis.json. +func CollectStdTxs(cdc *codec.Codec, moniker string, genTxsDir string, genDoc tmtypes.GenesisDoc) ( + appGenTxs []auth.StdTx, persistentPeers string, err error) { + var fos []os.FileInfo fos, err = ioutil.ReadDir(genTxsDir) if err != nil { - return + return appGenTxs, persistentPeers, err } - var addresses []string + // prepare a map of all accounts in genesis state to then validate + // against the validators addresses + var appState GenesisState + if err := cdc.UnmarshalJSON(genDoc.AppState, &appState); err != nil { + return appGenTxs, persistentPeers, err + } + addrMap := make(map[string]GenesisAccount, len(appState.Accounts)) + for i := 0; i < len(appState.Accounts); i++ { + acc := appState.Accounts[i] + strAddr := string(acc.Address) + addrMap[strAddr] = acc + } + + // addresses and IPs (and port) validator server info + var addressesIPs []string + for _, fo := range fos { filename := filepath.Join(genTxsDir, fo.Name()) if !fo.IsDir() && (filepath.Ext(filename) != ".json") { @@ -204,48 +233,55 @@ func CollectStdTxs(moniker string, genTxsDir string, cdc *codec.Codec) ( // get the genStdTx var jsonRawTx []byte - jsonRawTx, err = ioutil.ReadFile(filename) - if err != nil { - return + if jsonRawTx, err = ioutil.ReadFile(filename); err != nil { + return appGenTxs, persistentPeers, err } var genStdTx auth.StdTx - err = cdc.UnmarshalJSON(jsonRawTx, &genStdTx) - if err != nil { - return + if err = cdc.UnmarshalJSON(jsonRawTx, &genStdTx); err != nil { + return appGenTxs, persistentPeers, err } appGenTxs = append(appGenTxs, genStdTx) - nodeAddr := genStdTx.GetMemo() - if len(nodeAddr) == 0 { - err = fmt.Errorf("couldn't find node's address in %s", fo.Name()) - return + // the memo flag is used to store + // the ip and node-id, for example this may be: + // "528fd3df22b31f4969b05652bfe8f0fe921321d5@192.168.2.37:26656" + nodeAddrIP := genStdTx.GetMemo() + if len(nodeAddrIP) == 0 { + return appGenTxs, persistentPeers, fmt.Errorf( + "couldn't find node's address and IP in %s", fo.Name()) } + // genesis transactions must be single-message msgs := genStdTx.GetMsgs() if len(msgs) != 1 { - err = errors.New("each genesis transaction must provide a single genesis message") - return + + return appGenTxs, persistentPeers, errors.New( + "each genesis transaction must provide a single genesis message") } - // TODO: this could be decoupled from stake.MsgCreateValidator - // TODO: and we likely want to do it for real world Gaia + // validate the validator address and funds against the accounts in the state msg := msgs[0].(stake.MsgCreateValidator) - validators = append(validators, tmtypes.GenesisValidator{ - PubKey: msg.PubKey, - Power: freeFermionVal, - Name: msg.Description.Moniker, - }) + addr := string(sdk.AccAddress(msg.ValidatorAddr)) + acc, ok := addrMap[addr] + if !ok { + return appGenTxs, persistentPeers, fmt.Errorf( + "account %v not in genesis.json: %+v", addr, addrMap) + } + if acc.Coins.AmountOf(msg.Delegation.Denom).LT(msg.Delegation.Amount) { + err = fmt.Errorf("insufficient fund for the delegation: %s < %s", + acc.Coins.AmountOf(msg.Delegation.Denom), msg.Delegation.Amount) + } // exclude itself from persistent peers if msg.Description.Moniker != moniker { - addresses = append(addresses, nodeAddr) + addressesIPs = append(addressesIPs, nodeAddrIP) } } - sort.Strings(addresses) - persistentPeers = strings.Join(addresses, ",") + sort.Strings(addressesIPs) + persistentPeers = strings.Join(addressesIPs, ",") - return + return appGenTxs, persistentPeers, nil } func NewDefaultGenesisAccount(addr sdk.AccAddress) GenesisAccount { diff --git a/cmd/gaia/app/genesis_test.go b/cmd/gaia/app/genesis_test.go index 1acc9f393..579ad93a8 100644 --- a/cmd/gaia/app/genesis_test.go +++ b/cmd/gaia/app/genesis_test.go @@ -1,11 +1,14 @@ package app import ( + "encoding/json" "testing" + "github.com/tendermint/tendermint/crypto/secp256k1" + tmtypes "github.com/tendermint/tendermint/types" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/stake" stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" "github.com/stretchr/testify/require" @@ -27,7 +30,8 @@ var ( func makeGenesisState(t *testing.T, genTxs []auth.StdTx) GenesisState { // start with the default staking genesis state - stakeData := stake.DefaultGenesisState() + appState := NewDefaultGenesisState() + stakeData := appState.StakeData genAccs := make([]GenesisAccount, len(genTxs)) for i, genTx := range genTxs { @@ -35,17 +39,15 @@ func makeGenesisState(t *testing.T, genTxs []auth.StdTx) GenesisState { require.Equal(t, 1, len(msgs)) msg := msgs[0].(stake.MsgCreateValidator) - // get genesis flag account information - genAccs[i] = genesisAccountFromMsgCreateValidator(msg, freeFermionsAcc) - stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(freeFermionsAcc)) // increase the supply + acc := auth.NewBaseAccountWithAddress(sdk.AccAddress(msg.ValidatorAddr)) + acc.Coins = sdk.Coins{sdk.NewInt64Coin(bondDenom, 150)} + genAccs[i] = NewGenesisAccount(&acc) + stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDec(150)) // increase the supply } // create the final app state - return GenesisState{ - Accounts: genAccs, - StakeData: stakeData, - GovData: gov.DefaultGenesisState(), - } + appState.Accounts = genAccs + return appState } func TestToAccount(t *testing.T) { @@ -68,6 +70,19 @@ func TestGaiaAppGenTx(t *testing.T) { func TestGaiaAppGenState(t *testing.T) { cdc := MakeCodec() _ = cdc + var genDoc tmtypes.GenesisDoc + + // test unmarshalling error + _, err := GaiaAppGenState(cdc, genDoc, []json.RawMessage{}) + require.Error(t, err) + + appState := makeGenesisState(t, []auth.StdTx{}) + genDoc.AppState, err = json.Marshal(appState) + require.NoError(t, err) + + // test validation error + _, err = GaiaAppGenState(cdc, genDoc, []json.RawMessage{}) + require.Error(t, err) // TODO test must provide at least genesis transaction // TODO test with both one and two genesis transactions: @@ -77,7 +92,10 @@ func TestGaiaAppGenState(t *testing.T) { func makeMsg(name string, pk crypto.PubKey) auth.StdTx { desc := stake.NewDescription(name, "", "", "") comm := stakeTypes.CommissionMsg{} - msg := stake.NewMsgCreateValidator(sdk.ValAddress(pk.Address()), pk, sdk.NewInt64Coin("steak", 50), desc, comm) + msg := stake.NewMsgCreateValidator( + sdk.ValAddress(pk.Address()), pk, + sdk.NewInt64Coin(bondDenom, 50), desc, comm, + ) return auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, nil, "") } @@ -106,3 +124,10 @@ func TestGaiaGenesisValidation(t *testing.T) { err = GaiaValidateGenesisState(genesisState) require.NotNil(t, err) } + +func TestNewDefaultGenesisAccount(t *testing.T) { + addr := secp256k1.GenPrivKeySecp256k1([]byte("")).PubKey().Address() + acc := NewDefaultGenesisAccount(sdk.AccAddress(addr)) + require.Equal(t, sdk.NewInt(1000), acc.Coins.AmountOf("fooToken")) + require.Equal(t, sdk.NewInt(150), acc.Coins.AmountOf(bondDenom)) +} diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 384afbf6d..60befcc38 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -4,12 +4,15 @@ import ( "encoding/json" "flag" "fmt" + "io/ioutil" "math/rand" "os" "testing" + "time" "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" @@ -49,42 +52,93 @@ func init() { func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { var genesisAccounts []GenesisAccount - amt := int64(10000) + amount := int64(r.Intn(1e6)) + numInitiallyBonded := int64(r.Intn(250)) + numAccs := int64(len(accs)) + if numInitiallyBonded > numAccs { + numInitiallyBonded = numAccs + } + fmt.Printf("Selected randomly generated parameters for simulated genesis: {amount of steak per account: %v, initially bonded validators: %v}\n", amount, numInitiallyBonded) // Randomly generate some genesis accounts for _, acc := range accs { - coins := sdk.Coins{sdk.Coin{"steak", sdk.NewInt(amt)}} + coins := sdk.Coins{sdk.Coin{"steak", sdk.NewInt(amount)}} genesisAccounts = append(genesisAccounts, GenesisAccount{ Address: acc.Address, Coins: coins, }) } - // Default genesis state - govGenesis := gov.DefaultGenesisState() - stakeGenesis := stake.DefaultGenesisState() - slashingGenesis := slashing.DefaultGenesisState() + // Random genesis states + govGenesis := gov.GenesisState{ + StartingProposalID: uint64(r.Intn(100)), + DepositParams: gov.DepositParams{ + MinDeposit: sdk.Coins{sdk.NewInt64Coin("steak", int64(r.Intn(1e3)))}, + MaxDepositPeriod: time.Duration(r.Intn(2*172800)) * time.Second, + }, + VotingParams: gov.VotingParams{ + VotingPeriod: time.Duration(r.Intn(2*172800)) * time.Second, + }, + TallyParams: gov.TallyParams{ + Threshold: sdk.NewDecWithPrec(5, 1), + Veto: sdk.NewDecWithPrec(334, 3), + GovernancePenalty: sdk.NewDecWithPrec(1, 2), + }, + } + fmt.Printf("Selected randomly generated governance parameters: %+v\n", govGenesis) + stakeGenesis := stake.GenesisState{ + Pool: stake.InitialPool(), + Params: stake.Params{ + UnbondingTime: time.Duration(r.Intn(60*60*24*3*2)) * time.Second, + MaxValidators: uint16(r.Intn(250)), + BondDenom: "steak", + }, + } + fmt.Printf("Selected randomly generated staking parameters: %+v\n", stakeGenesis) + slashingGenesis := slashing.GenesisState{ + Params: slashing.Params{ + MaxEvidenceAge: stakeGenesis.Params.UnbondingTime, + DoubleSignUnbondDuration: time.Duration(r.Intn(60*60*24)) * time.Second, + SignedBlocksWindow: int64(r.Intn(1000)), + DowntimeUnbondDuration: time.Duration(r.Intn(86400)) * time.Second, + MinSignedPerWindow: sdk.NewDecWithPrec(int64(r.Intn(10)), 1), + SlashFractionDoubleSign: sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(50) + 1))), + SlashFractionDowntime: sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(200) + 1))), + }, + } + fmt.Printf("Selected randomly generated slashing parameters: %+v\n", slashingGenesis) + mintGenesis := mint.GenesisState{ + Minter: mint.Minter{ + InflationLastTime: time.Unix(0, 0), + Inflation: sdk.NewDecWithPrec(int64(r.Intn(99)), 2), + }, + Params: mint.Params{ + MintDenom: "steak", + InflationRateChange: sdk.NewDecWithPrec(int64(r.Intn(99)), 2), + InflationMax: sdk.NewDecWithPrec(20, 2), + InflationMin: sdk.NewDecWithPrec(7, 2), + GoalBonded: sdk.NewDecWithPrec(67, 2), + }, + } + fmt.Printf("Selected randomly generated minting parameters: %v\n", mintGenesis) var validators []stake.Validator var delegations []stake.Delegation - // XXX Try different numbers of initially bonded validators - numInitiallyBonded := int64(50) valAddrs := make([]sdk.ValAddress, numInitiallyBonded) for i := 0; i < int(numInitiallyBonded); i++ { valAddr := sdk.ValAddress(accs[i].Address) valAddrs[i] = valAddr validator := stake.NewValidator(valAddr, accs[i].PubKey, stake.Description{}) - validator.Tokens = sdk.NewDec(amt) - validator.DelegatorShares = sdk.NewDec(amt) - delegation := stake.Delegation{accs[i].Address, valAddr, sdk.NewDec(amt), 0} + validator.Tokens = sdk.NewDec(amount) + validator.DelegatorShares = sdk.NewDec(amount) + delegation := stake.Delegation{accs[i].Address, valAddr, sdk.NewDec(amount), 0} validators = append(validators, validator) delegations = append(delegations, delegation) } - stakeGenesis.Pool.LooseTokens = sdk.NewDec(amt*250 + (numInitiallyBonded * amt)) + stakeGenesis.Pool.LooseTokens = sdk.NewDec((amount * numAccs) + (numInitiallyBonded * amount)) stakeGenesis.Validators = validators stakeGenesis.Bonds = delegations - mintGenesis := mint.DefaultGenesisState() genesis := GenesisState{ Accounts: genesisAccounts, @@ -113,7 +167,7 @@ func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation { {50, distrsim.SimulateMsgWithdrawDelegatorReward(app.accountKeeper, app.distrKeeper)}, {50, distrsim.SimulateMsgWithdrawValidatorRewardsAll(app.accountKeeper, app.distrKeeper)}, {5, govsim.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, app.stakeKeeper)}, - {100, govsim.SimulateMsgDeposit(app.govKeeper, app.stakeKeeper)}, + {100, govsim.SimulateMsgDeposit(app.govKeeper)}, {100, stakesim.SimulateMsgCreateValidator(app.accountKeeper, app.stakeKeeper)}, {5, stakesim.SimulateMsgEditValidator(app.stakeKeeper)}, {100, stakesim.SimulateMsgDelegate(app.accountKeeper, app.stakeKeeper)}, @@ -141,7 +195,7 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { var logger log.Logger logger = log.NewNopLogger() var db dbm.DB - dir := os.TempDir() + dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") db, _ = dbm.NewGoLevelDB("Simulation", dir) defer func() { db.Close() @@ -183,7 +237,13 @@ func TestFullGaiaSimulation(t *testing.T) { } else { logger = log.NewNopLogger() } - db := dbm.NewMemDB() + var db dbm.DB + dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") + db, _ = dbm.NewGoLevelDB("Simulation", dir) + defer func() { + db.Close() + os.RemoveAll(dir) + }() app := NewGaiaApp(logger, db, nil) require.Equal(t, "GaiaApp", app.Name()) @@ -198,11 +258,112 @@ func TestFullGaiaSimulation(t *testing.T) { commit, ) if commit { - fmt.Println("Database Size", db.Stats()["database.size"]) + // for memdb: + // fmt.Println("Database Size", db.Stats()["database.size"]) + fmt.Println("GoLevelDB Stats") + fmt.Println(db.Stats()["leveldb.stats"]) + fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) } require.Nil(t, err) } +func TestGaiaImportExport(t *testing.T) { + if !enabled { + t.Skip("Skipping Gaia import/export simulation") + } + + // Setup Gaia application + var logger log.Logger + if verbose { + logger = log.TestingLogger() + } else { + logger = log.NewNopLogger() + } + var db dbm.DB + dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") + db, _ = dbm.NewGoLevelDB("Simulation", dir) + defer func() { + db.Close() + os.RemoveAll(dir) + }() + app := NewGaiaApp(logger, db, nil) + require.Equal(t, "GaiaApp", app.Name()) + + // Run randomized simulation + err := simulation.SimulateFromSeed( + t, app.BaseApp, appStateFn, seed, + testAndRunTxs(app), + []simulation.RandSetup{}, + invariants(app), + numBlocks, + blockSize, + commit, + ) + if commit { + // for memdb: + // fmt.Println("Database Size", db.Stats()["database.size"]) + fmt.Println("GoLevelDB Stats") + fmt.Println(db.Stats()["leveldb.stats"]) + fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) + } + require.Nil(t, err) + + fmt.Printf("Exporting genesis...\n") + + appState, _, err := app.ExportAppStateAndValidators() + if err != nil { + panic(err) + } + + fmt.Printf("Importing genesis...\n") + + newDir, _ := ioutil.TempDir("", "goleveldb-gaia-sim-2") + newDB, _ := dbm.NewGoLevelDB("Simulation-2", dir) + defer func() { + newDB.Close() + os.RemoveAll(newDir) + }() + newApp := NewGaiaApp(log.NewNopLogger(), newDB, nil) + require.Equal(t, "GaiaApp", newApp.Name()) + request := abci.RequestInitChain{ + AppStateBytes: appState, + } + newApp.InitChain(request) + newApp.Commit() + + fmt.Printf("Comparing stores...\n") + ctxA := app.NewContext(true, abci.Header{}) + ctxB := newApp.NewContext(true, abci.Header{}) + type StoreKeysPrefixes struct { + A sdk.StoreKey + B sdk.StoreKey + Prefixes [][]byte + } + storeKeysPrefixes := []StoreKeysPrefixes{ + {app.keyMain, newApp.keyMain, [][]byte{}}, + {app.keyAccount, newApp.keyAccount, [][]byte{}}, + {app.keyStake, newApp.keyStake, [][]byte{stake.UnbondingQueueKey, stake.RedelegationQueueKey, stake.ValidatorQueueKey}}, // ordering may change but it doesn't matter + {app.keySlashing, newApp.keySlashing, [][]byte{}}, + {app.keyMint, newApp.keyMint, [][]byte{}}, + {app.keyDistr, newApp.keyDistr, [][]byte{}}, + {app.keyFeeCollection, newApp.keyFeeCollection, [][]byte{}}, + {app.keyParams, newApp.keyParams, [][]byte{}}, + {app.keyGov, newApp.keyGov, [][]byte{}}, + } + for _, storeKeysPrefix := range storeKeysPrefixes { + storeKeyA := storeKeysPrefix.A + storeKeyB := storeKeysPrefix.B + prefixes := storeKeysPrefix.Prefixes + storeA := ctxA.KVStore(storeKeyA) + storeB := ctxB.KVStore(storeKeyB) + kvA, kvB, count, equal := sdk.DiffKVStores(storeA, storeB, prefixes) + fmt.Printf("Compared %d key/value pairs between %s and %s\n", count, storeKeyA, storeKeyB) + require.True(t, equal, "unequal stores: %s / %s:\nstore A %s (%X) => %s (%X)\nstore B %s (%X) => %s (%X)", + storeKeyA, storeKeyB, kvA.Key, kvA.Key, kvA.Value, kvA.Value, kvB.Key, kvB.Key, kvB.Value, kvB.Value) + } + +} + // TODO: Make another test for the fuzzer itself, which just has noOp txs // and doesn't depend on gaia func TestAppStateDeterminism(t *testing.T) { diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index c375bcf4e..bc9151200 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -8,8 +8,11 @@ import ( "io/ioutil" "os" "path" + "path/filepath" "testing" + "github.com/tendermint/tendermint/types" + "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -207,7 +210,7 @@ func TestGaiaCLIGasAuto(t *testing.T) { func TestGaiaCLICreateValidator(t *testing.T) { chainID, servAddr, port := initializeFixtures(t) - flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) + flags := fmt.Sprintf("--home=%s --chain-id=%v --node=%s", gaiacliHome, chainID, servAddr) // start gaiad server proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr)) @@ -285,6 +288,12 @@ func TestGaiaCLICreateValidator(t *testing.T) { validator = executeGetValidator(t, fmt.Sprintf("gaiacli query validator %s --output=json %v", sdk.ValAddress(barAddr), flags)) require.Equal(t, "1.0000000000", validator.Tokens.String()) + validatorUbds := executeGetValidatorUnbondingDelegations(t, + fmt.Sprintf("gaiacli query unbonding-delegations-from %s --output=json %v", + sdk.ValAddress(barAddr), flags)) + require.Len(t, validatorUbds, 1) + require.Equal(t, "1", validatorUbds[0].Balance.Amount.String()) + params := executeGetParams(t, fmt.Sprintf("gaiacli query parameters --output=json %v", flags)) require.True(t, defaultParams.Equal(params)) @@ -340,7 +349,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, int64(45), fooAcc.GetCoins().AmountOf("steak").Int64()) proposal1 := executeGetProposal(t, fmt.Sprintf("gaiacli query proposal --proposal-id=1 --output=json %v", flags)) - require.Equal(t, int64(1), proposal1.GetProposalID()) + require.Equal(t, uint64(1), proposal1.GetProposalID()) require.Equal(t, gov.StatusDepositPeriod, proposal1.GetStatus()) proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals %v", flags), "") @@ -383,7 +392,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(35), fooAcc.GetCoins().AmountOf("steak").Int64()) proposal1 = executeGetProposal(t, fmt.Sprintf("gaiacli query proposal --proposal-id=1 --output=json %v", flags)) - require.Equal(t, int64(1), proposal1.GetProposalID()) + require.Equal(t, uint64(1), proposal1.GetProposalID()) require.Equal(t, gov.StatusVotingPeriod, proposal1.GetStatus()) voteStr := fmt.Sprintf("gaiacli tx vote %v", flags) @@ -405,12 +414,12 @@ func TestGaiaCLISubmitProposal(t *testing.T) { tests.WaitForNextNBlocksTM(2, port) vote := executeGetVote(t, fmt.Sprintf("gaiacli query vote --proposal-id=1 --voter=%s --output=json %v", fooAddr, flags)) - require.Equal(t, int64(1), vote.ProposalID) + require.Equal(t, uint64(1), vote.ProposalID) require.Equal(t, gov.OptionYes, vote.Option) votes := executeGetVotes(t, fmt.Sprintf("gaiacli query votes --proposal-id=1 --output=json %v", flags)) require.Len(t, votes, 1) - require.Equal(t, int64(1), votes[0].ProposalID) + require.Equal(t, uint64(1), votes[0].ProposalID) require.Equal(t, gov.OptionYes, votes[0].Option) proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --status=DepositPeriod %v", flags), "") @@ -430,7 +439,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { executeWrite(t, spStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --latest=1 %v", flags), "") + proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --limit=1 %v", flags), "") require.Equal(t, " 2 - Apples", proposalsQuery) } @@ -439,7 +448,8 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) // start gaiad server - proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr)) + proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf( + "gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr)) defer proc.Stop(false) tests.WaitForTMStart(port) @@ -484,11 +494,11 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { unsignedTxFile := writeToNewTempFile(t, stdout) defer os.Remove(unsignedTxFile.Name()) - // Test sign --print-sigs + // Test sign --validate-signatures success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli tx sign %v --print-sigs %v", flags, unsignedTxFile.Name())) - require.True(t, success) - require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n", fooAddr.String()), stdout) + "gaiacli tx sign %v --validate-signatures %v", flags, unsignedTxFile.Name())) + require.False(t, success) + require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n\n", fooAddr.String()), stdout) // Test sign success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( @@ -505,15 +515,17 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test sign --print-signatures success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli tx sign %v --print-sigs %v", flags, signedTxFile.Name())) + "gaiacli tx sign %v --validate-signatures %v", flags, signedTxFile.Name())) require.True(t, success) - require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n 0: %v\n", fooAddr.String(), fooAddr.String()), stdout) + require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n 0: %v\t[OK]\n\n", fooAddr.String(), + fooAddr.String()), stdout) // Test broadcast fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) - success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf("gaiacli tx broadcast %v --json %v", flags, signedTxFile.Name())) + success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( + "gaiacli tx broadcast %v --json %v", flags, signedTxFile.Name())) require.True(t, success) var result struct { Response abci.ResponseDeliverTx @@ -535,7 +547,7 @@ func TestGaiaCLIConfig(t *testing.T) { servAddr, port, err := server.FreeTCPAddr() require.NoError(t, err) node := fmt.Sprintf("%s:%s", servAddr, port) - chainID := executeInit(t, fmt.Sprintf("gaiad init -o --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome)) + chainID := executeInit(t, fmt.Sprintf("gaiad init -o --moniker=foo --home=%s", gaiadHome)) executeWrite(t, fmt.Sprintf("gaiacli --home=%s config", gaiadHome), gaiacliHome, node, "y") config, err := ioutil.ReadFile(path.Join(gaiacliHome, "config", "config.toml")) require.NoError(t, err) @@ -585,12 +597,27 @@ func initializeFixtures(t *testing.T) (chainID, servAddr, port string) { tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe-reset-all", gaiadHome), "") executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s foo", gaiacliHome), app.DefaultKeyPass) executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), app.DefaultKeyPass) - - chainID = executeInit(t, fmt.Sprintf("gaiad init -o --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome)) + executeWrite(t, fmt.Sprintf("gaiacli keys add --home=%s foo", gaiacliHome), app.DefaultKeyPass) executeWrite(t, fmt.Sprintf("gaiacli keys add --home=%s bar", gaiacliHome), app.DefaultKeyPass) - + fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf( + "gaiacli keys show foo --output=json --home=%s", gaiacliHome)) + chainID = executeInit(t, fmt.Sprintf("gaiad init -o --moniker=foo --home=%s", gaiadHome)) + genFile := filepath.Join(gaiadHome, "config", "genesis.json") + genDoc := readGenesisFile(t, genFile) + var appState app.GenesisState + err := codec.Cdc.UnmarshalJSON(genDoc.AppState, &appState) + require.NoError(t, err) + appState.Accounts = []app.GenesisAccount{app.NewDefaultGenesisAccount(fooAddr)} + appStateJSON, err := codec.Cdc.MarshalJSON(appState) + require.NoError(t, err) + genDoc.AppState = appStateJSON + genDoc.SaveAs(genFile) + executeWrite(t, fmt.Sprintf( + "gaiad gentx --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome), + app.DefaultKeyPass) + executeWrite(t, fmt.Sprintf("gaiad collect-gentxs --home=%s", gaiadHome), app.DefaultKeyPass) // get a free port, also setup some common flags - servAddr, port, err := server.FreeTCPAddr() + servAddr, port, err = server.FreeTCPAddr() require.NoError(t, err) return } @@ -609,6 +636,18 @@ func writeToNewTempFile(t *testing.T, s string) *os.File { return fp } +func readGenesisFile(t *testing.T, genFile string) types.GenesisDoc { + var genDoc types.GenesisDoc + fp, err := os.Open(genFile) + require.NoError(t, err) + fileContents, err := ioutil.ReadAll(fp) + require.NoError(t, err) + defer fp.Close() + err = codec.Cdc.UnmarshalJSON(fileContents, &genDoc) + require.NoError(t, err) + return genDoc +} + //___________________________________________________________________________________ // executors @@ -693,6 +732,24 @@ func executeGetValidator(t *testing.T, cmdStr string) stake.Validator { return validator } +func executeGetValidatorUnbondingDelegations(t *testing.T, cmdStr string) []stake.UnbondingDelegation { + out, _ := tests.ExecuteT(t, cmdStr, "") + var ubds []stake.UnbondingDelegation + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &ubds) + require.NoError(t, err, "out %v\n, err %v", out, err) + return ubds +} + +func executeGetValidatorRedelegations(t *testing.T, cmdStr string) []stake.Redelegation { + out, _ := tests.ExecuteT(t, cmdStr, "") + var reds []stake.Redelegation + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &reds) + require.NoError(t, err, "out %v\n, err %v", out, err) + return reds +} + func executeGetPool(t *testing.T, cmdStr string) stake.Pool { out, _ := tests.ExecuteT(t, cmdStr, "") var pool stake.Pool diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index a2804706b..212a67021 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -15,6 +15,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" @@ -28,10 +29,11 @@ import ( ) const ( - storeAcc = "acc" - storeGov = "gov" - storeSlashing = "slashing" - storeStake = "stake" + storeAcc = "acc" + storeGov = "gov" + storeSlashing = "slashing" + storeStake = "stake" + queryRouteStake = "stake" ) // rootCmd is the entry point for this binary @@ -46,6 +48,12 @@ func main() { cobra.EnableCommandSorting = false cdc := app.MakeCodec() + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + config.Seal() + // TODO: setup keybase, viper object, etc. to be passed into // the below functions and eliminate global vars, like we do // with the cdc @@ -70,21 +78,23 @@ func main() { authcmd.GetAccountCmd(storeAcc, cdc, authcmd.GetAccountDecoder(cdc)), stakecmd.GetCmdQueryDelegation(storeStake, cdc), stakecmd.GetCmdQueryDelegations(storeStake, cdc), + stakecmd.GetCmdQueryUnbondingDelegation(storeStake, cdc), + stakecmd.GetCmdQueryUnbondingDelegations(storeStake, cdc), + stakecmd.GetCmdQueryRedelegation(storeStake, cdc), + stakecmd.GetCmdQueryRedelegations(storeStake, cdc), + stakecmd.GetCmdQueryValidator(storeStake, cdc), + stakecmd.GetCmdQueryValidators(storeStake, cdc), + stakecmd.GetCmdQueryValidatorUnbondingDelegations(queryRouteStake, cdc), + stakecmd.GetCmdQueryValidatorRedelegations(queryRouteStake, cdc), stakecmd.GetCmdQueryParams(storeStake, cdc), stakecmd.GetCmdQueryPool(storeStake, cdc), govcmd.GetCmdQueryProposal(storeGov, cdc), govcmd.GetCmdQueryProposals(storeGov, cdc), - govcmd.GetCmdQueryDeposit(storeGov, cdc), - govcmd.GetCmdQueryDeposits(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), + govcmd.GetCmdQueryDeposit(storeGov, cdc), + govcmd.GetCmdQueryDeposits(storeGov, cdc), + slashingcmd.GetCmdQuerySigningInfo(storeSlashing, cdc), )...) //Add query commands diff --git a/cmd/gaia/cmd/gaiad/main.go b/cmd/gaia/cmd/gaiad/main.go index 1304b32f3..ae076571b 100644 --- a/cmd/gaia/cmd/gaiad/main.go +++ b/cmd/gaia/cmd/gaiad/main.go @@ -18,10 +18,18 @@ import ( "github.com/cosmos/cosmos-sdk/cmd/gaia/app" gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init" "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" ) func main() { cdc := app.MakeCodec() + + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + config.Seal() + ctx := server.NewDefaultContext() cobra.EnableCommandSorting = false rootCmd := &cobra.Command{ @@ -31,7 +39,8 @@ func main() { } appInit := app.GaiaAppInit() rootCmd.AddCommand(gaiaInit.InitCmd(ctx, cdc, appInit)) - rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, appInit)) + rootCmd.AddCommand(gaiaInit.CollectGenTxsCmd(ctx, cdc)) + rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, server.AppInit{})) rootCmd.AddCommand(gaiaInit.GenTxCmd(ctx, cdc)) server.AddCommands(ctx, cdc, rootCmd, appInit, diff --git a/cmd/gaia/cmd/gaiadebug/main.go b/cmd/gaia/cmd/gaiadebug/main.go index 73840537b..240a74cb4 100644 --- a/cmd/gaia/cmd/gaiadebug/main.go +++ b/cmd/gaia/cmd/gaiadebug/main.go @@ -19,6 +19,13 @@ import ( ) func init() { + + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + config.Seal() + rootCmd.AddCommand(txCmd) rootCmd.AddCommand(pubkeyCmd) rootCmd.AddCommand(addrCmd) @@ -213,7 +220,7 @@ func runTxCmd(cmd *cobra.Command, args []string) error { var tx = auth.StdTx{} cdc := gaia.MakeCodec() - err = cdc.UnmarshalBinary(txBytes, &tx) + err = cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) if err != nil { return err } diff --git a/cmd/gaia/init/collect.go b/cmd/gaia/init/collect.go new file mode 100644 index 000000000..cdfc1688c --- /dev/null +++ b/cmd/gaia/init/collect.go @@ -0,0 +1,118 @@ +package init + +import ( + "encoding/json" + "path/filepath" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/spf13/cobra" + "github.com/spf13/viper" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/types" +) + +type initConfig struct { + ChainID string + GenTxsDir string + Name string + NodeID string + ValPubKey crypto.PubKey +} + +// nolint +func CollectGenTxsCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "collect-gentxs", + Short: "Collect genesis txs and output a genesis.json file", + RunE: func(_ *cobra.Command, _ []string) error { + config := ctx.Config + config.SetRoot(viper.GetString(cli.HomeFlag)) + name := viper.GetString(client.FlagName) + + nodeID, valPubKey, err := InitializeNodeValidatorFiles(config) + if err != nil { + return err + } + + genDoc, err := loadGenesisDoc(cdc, config.GenesisFile()) + if err != nil { + return err + } + + toPrint := printInfo{ + Moniker: config.Moniker, + ChainID: genDoc.ChainID, + NodeID: nodeID, + } + + initCfg := initConfig{ + ChainID: genDoc.ChainID, + GenTxsDir: filepath.Join(config.RootDir, "config", "gentx"), + Name: name, + NodeID: nodeID, + ValPubKey: valPubKey, + } + + appMessage, err := genAppStateFromConfig(cdc, config, initCfg, genDoc) + if err != nil { + return err + } + + toPrint.AppMessage = appMessage + + // print out some key information + return displayInfo(cdc, toPrint) + }, + } + + cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") + return cmd +} + +func genAppStateFromConfig( + cdc *codec.Codec, config *cfg.Config, initCfg initConfig, genDoc types.GenesisDoc, +) (appState json.RawMessage, err error) { + + genFile := config.GenesisFile() + var ( + appGenTxs []auth.StdTx + persistentPeers string + genTxs []json.RawMessage + jsonRawTx json.RawMessage + ) + + // process genesis transactions, else create default genesis.json + appGenTxs, persistentPeers, err = app.CollectStdTxs( + cdc, config.Moniker, initCfg.GenTxsDir, genDoc, + ) + if err != nil { + return + } + + genTxs = make([]json.RawMessage, len(appGenTxs)) + config.P2P.PersistentPeers = persistentPeers + + for i, stdTx := range appGenTxs { + jsonRawTx, err = cdc.MarshalJSON(stdTx) + if err != nil { + return + } + genTxs[i] = jsonRawTx + } + + cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) + + appState, err = app.GaiaAppGenStateJSON(cdc, genDoc, genTxs) + if err != nil { + return + } + + err = ExportGenesisFile(genFile, initCfg.ChainID, nil, appState) + return +} diff --git a/cmd/gaia/init/gentx.go b/cmd/gaia/init/gentx.go index 1b24a3576..7ed820375 100644 --- a/cmd/gaia/init/gentx.go +++ b/cmd/gaia/init/gentx.go @@ -55,9 +55,20 @@ following delegation and commission default parameters: if err != nil { return err } + genDoc, err := loadGenesisDoc(cdc, config.GenesisFile()) + if err != nil { + return err + } + // Read --pubkey, if empty take it from priv_validator.json + if valPubKeyString := viper.GetString(cli.FlagPubKey); valPubKeyString != "" { + valPubKey, err = sdk.GetConsPubKeyBech32(valPubKeyString) + if err != nil { + return err + } + } // Run gaiad tx create-validator - prepareFlagsForTxCreateValidator(config, nodeID, ip, valPubKey) + prepareFlagsForTxCreateValidator(config, nodeID, ip, genDoc.ChainID, valPubKey) createValidatorCmd := cli.GetCmdCreateValidator(cdc) w, err := ioutil.TempFile("", "gentx") @@ -82,28 +93,41 @@ following delegation and commission default parameters: }, } + cmd.Flags().String(tmcli.HomeFlag, app.DefaultNodeHome, "node's home directory") cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") - cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id") cmd.Flags().String(client.FlagName, "", "name of private key with which to sign the gentx") + cmd.Flags().AddFlagSet(cli.FsCommissionCreate) + cmd.Flags().AddFlagSet(cli.FsAmount) + cmd.Flags().AddFlagSet(cli.FsPk) cmd.MarkFlagRequired(client.FlagName) return cmd } -func prepareFlagsForTxCreateValidator(config *cfg.Config, nodeID, ip string, valPubKey crypto.PubKey) { - viper.Set(tmcli.HomeFlag, viper.GetString(flagClientHome)) // --home +func prepareFlagsForTxCreateValidator(config *cfg.Config, nodeID, ip, chainID string, + valPubKey crypto.PubKey) { + viper.Set(tmcli.HomeFlag, viper.GetString(flagClientHome)) // --home + viper.Set(client.FlagChainID, chainID) viper.Set(client.FlagFrom, viper.GetString(client.FlagName)) // --from viper.Set(cli.FlagNodeID, nodeID) // --node-id viper.Set(cli.FlagIP, ip) // --ip viper.Set(cli.FlagPubKey, sdk.MustBech32ifyConsPub(valPubKey)) // --pubkey - viper.Set(cli.FlagAmount, defaultAmount) // --amount - viper.Set(cli.FlagCommissionRate, defaultCommissionRate) - viper.Set(cli.FlagCommissionMaxRate, defaultCommissionMaxRate) - viper.Set(cli.FlagCommissionMaxChangeRate, defaultCommissionMaxChangeRate) - viper.Set(cli.FlagGenesisFormat, true) // --genesis-format - viper.Set(cli.FlagMoniker, config.Moniker) // --moniker + viper.Set(cli.FlagGenesisFormat, true) // --genesis-format + viper.Set(cli.FlagMoniker, config.Moniker) // --moniker if config.Moniker == "" { viper.Set(cli.FlagMoniker, viper.GetString(client.FlagName)) } + if viper.GetString(cli.FlagAmount) == "" { + viper.Set(cli.FlagAmount, defaultAmount) + } + if viper.GetString(cli.FlagCommissionRate) == "" { + viper.Set(cli.FlagCommissionRate, defaultCommissionRate) + } + if viper.GetString(cli.FlagCommissionMaxRate) == "" { + viper.Set(cli.FlagCommissionMaxRate, defaultCommissionMaxRate) + } + if viper.GetString(cli.FlagCommissionMaxChangeRate) == "" { + viper.Set(cli.FlagCommissionMaxChangeRate, defaultCommissionMaxChangeRate) + } } func prepareFlagsForTxSign() { diff --git a/cmd/gaia/init/init.go b/cmd/gaia/init/init.go index 467ea3fc2..a297caee2 100644 --- a/cmd/gaia/init/init.go +++ b/cmd/gaia/init/init.go @@ -2,51 +2,27 @@ package init import ( "encoding/json" - "errors" "fmt" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/cmd/gaia/app" - "github.com/cosmos/cosmos-sdk/x/auth" - authtx "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" - "github.com/cosmos/cosmos-sdk/x/stake" - "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/privval" "os" "path/filepath" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/spf13/cobra" "github.com/spf13/viper" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/libs/cli" "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/p2p" - "github.com/tendermint/tendermint/types" ) const ( - flagWithTxs = "with-txs" - flagOverwrite = "overwrite" - flagClientHome = "home-client" - flagOverwriteKey = "overwrite-key" - flagSkipGenesis = "skip-genesis" - flagMoniker = "moniker" + flagOverwrite = "overwrite" + flagClientHome = "home-client" + flagMoniker = "moniker" ) -type initConfig struct { - ChainID string - GenTxsDir string - Name string - NodeID string - ClientHome string - WithTxs bool - Overwrite bool - OverwriteKey bool - ValPubKey crypto.PubKey -} - type printInfo struct { Moniker string `json:"moniker"` ChainID string `json:"chain_id"` @@ -70,22 +46,16 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cob cmd := &cobra.Command{ Use: "init", Short: "Initialize private validator, p2p, genesis, and application configuration files", - Long: `Initialize validators's and node's configuration files. - -Note that only node's configuration files will be written if the flag --skip-genesis is -enabled, and the genesis file will not be generated. -`, - Args: cobra.NoArgs, + Long: `Initialize validators's and node's configuration files.`, + Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { config := ctx.Config config.SetRoot(viper.GetString(cli.HomeFlag)) - - name := viper.GetString(client.FlagName) chainID := viper.GetString(client.FlagChainID) if chainID == "" { chainID = fmt.Sprintf("test-chain-%v", common.RandStr(6)) } - nodeID, valPubKey, err := InitializeNodeValidatorFiles(config) + nodeID, _, err := InitializeNodeValidatorFiles(config) if err != nil { return err } @@ -93,37 +63,26 @@ enabled, and the genesis file will not be generated. if viper.GetString(flagMoniker) != "" { config.Moniker = viper.GetString(flagMoniker) } - if config.Moniker == "" && name != "" { - config.Moniker = name - } - toPrint := printInfo{ - ChainID: chainID, - Moniker: config.Moniker, - NodeID: nodeID, - } - if viper.GetBool(flagSkipGenesis) { - cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) - return displayInfo(cdc, toPrint) - } - initCfg := initConfig{ - ChainID: chainID, - GenTxsDir: filepath.Join(config.RootDir, "config", "gentx"), - Name: name, - NodeID: nodeID, - ClientHome: viper.GetString(flagClientHome), - WithTxs: viper.GetBool(flagWithTxs), - Overwrite: viper.GetBool(flagOverwrite), - OverwriteKey: viper.GetBool(flagOverwriteKey), - ValPubKey: valPubKey, + var appState json.RawMessage + genFile := config.GenesisFile() + if appState, err = initializeEmptyGenesis(cdc, genFile, chainID, + viper.GetBool(flagOverwrite)); err != nil { + return err } - appMessage, err := initWithConfig(cdc, config, initCfg) - // print out some key information - if err != nil { + if err = ExportGenesisFile(genFile, chainID, nil, appState); err != nil { return err } - toPrint.AppMessage = appMessage + toPrint := printInfo{ + ChainID: chainID, + Moniker: config.Moniker, + NodeID: nodeID, + AppMessage: appState, + } + + cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) + return displayInfo(cdc, toPrint) }, } @@ -131,151 +90,6 @@ enabled, and the genesis file will not be generated. cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") cmd.Flags().BoolP(flagOverwrite, "o", false, "overwrite the genesis.json file") cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") - cmd.Flags().Bool(flagWithTxs, false, "apply existing genesis transactions from [--home]/config/gentx/") - cmd.Flags().String(client.FlagName, "", "name of private key with which to sign the gentx") - cmd.Flags().String(flagMoniker, "", "overrides --name flag and set the validator's moniker to a different value; ignored if it runs without the --with-txs flag") - cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") - cmd.Flags().Bool(flagOverwriteKey, false, "overwrite client's key") - cmd.Flags().Bool(flagSkipGenesis, false, "do not create genesis.json") + cmd.Flags().String(flagMoniker, "", "set the validator's moniker") return cmd } - -// InitializeNodeValidatorFiles creates private validator and p2p configuration files. -func InitializeNodeValidatorFiles(config *cfg.Config) (nodeID string, valPubKey crypto.PubKey, err error) { - nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) - if err != nil { - return - } - nodeID = string(nodeKey.ID()) - valPubKey = ReadOrCreatePrivValidator(config.PrivValidatorFile()) - return -} - -func initWithConfig(cdc *codec.Codec, config *cfg.Config, initCfg initConfig) ( - appMessage json.RawMessage, err error) { - genFile := config.GenesisFile() - if !initCfg.Overwrite && common.FileExists(genFile) { - err = fmt.Errorf("genesis.json file already exists: %v", genFile) - return - } - - // process genesis transactions, else create default genesis.json - var appGenTxs []auth.StdTx - var persistentPeers string - var genTxs []json.RawMessage - var appState json.RawMessage - var jsonRawTx json.RawMessage - chainID := initCfg.ChainID - - if initCfg.WithTxs { - _, appGenTxs, persistentPeers, err = app.CollectStdTxs(config.Moniker, initCfg.GenTxsDir, cdc) - if err != nil { - return - } - genTxs = make([]json.RawMessage, len(appGenTxs)) - config.P2P.PersistentPeers = persistentPeers - for i, stdTx := range appGenTxs { - jsonRawTx, err = cdc.MarshalJSON(stdTx) - if err != nil { - return - } - genTxs[i] = jsonRawTx - } - } else { - var ip, keyPass, secret string - var addr sdk.AccAddress - var signedTx auth.StdTx - - if initCfg.Name == "" { - err = errors.New("must specify validator's moniker (--name)") - return - } - - config.Moniker = initCfg.Name - ip, err = server.ExternalIP() - if err != nil { - return - } - memo := fmt.Sprintf("%s@%s:26656", initCfg.NodeID, ip) - buf := client.BufferStdin() - prompt := fmt.Sprintf("Password for account %q (default: %q):", initCfg.Name, app.DefaultKeyPass) - keyPass, err = client.GetPassword(prompt, buf) - if err != nil && keyPass != "" { - // An error was returned that either failed to read the password from - // STDIN or the given password is not empty but failed to meet minimum - // length requirements. - return - } - if keyPass == "" { - keyPass = app.DefaultKeyPass - } - - addr, secret, err = server.GenerateSaveCoinKey(initCfg.ClientHome, initCfg.Name, keyPass, initCfg.OverwriteKey) - if err != nil { - return - } - appMessage, err = json.Marshal(map[string]string{"secret": secret}) - if err != nil { - return - } - - msg := stake.NewMsgCreateValidator( - sdk.ValAddress(addr), - initCfg.ValPubKey, - sdk.NewInt64Coin("steak", 100), - stake.NewDescription(config.Moniker, "", "", ""), - stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), - ) - txBldr := authtx.NewTxBuilderFromCLI().WithCodec(cdc).WithMemo(memo).WithChainID(chainID) - signedTx, err = txBldr.SignStdTx( - initCfg.Name, keyPass, auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo), false, - ) - if err != nil { - return - } - jsonRawTx, err = cdc.MarshalJSON(signedTx) - if err != nil { - return - } - genTxs = []json.RawMessage{jsonRawTx} - } - - cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) - appState, err = app.GaiaAppGenStateJSON(cdc, genTxs) - if err != nil { - return - } - err = WriteGenesisFile(genFile, chainID, nil, appState) - - return -} - -// 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(genesisFile, chainID string, validators []types.GenesisValidator, appState json.RawMessage) error { - genDoc := types.GenesisDoc{ - ChainID: chainID, - Validators: validators, - AppState: appState, - } - - if err := genDoc.ValidateAndComplete(); err != nil { - return err - } - - return genDoc.SaveAs(genesisFile) -} - -// read of create the private key file for this config -func ReadOrCreatePrivValidator(privValFile string) crypto.PubKey { - // private validator - var privValidator *privval.FilePV - if common.FileExists(privValFile) { - privValidator = privval.LoadFilePV(privValFile) - } else { - privValidator = privval.GenFilePV(privValFile) - privValidator.Save() - } - return privValidator.GetPubKey() -} diff --git a/cmd/gaia/init/init_test.go b/cmd/gaia/init/init_test.go index 48a5d9247..bd5274c26 100644 --- a/cmd/gaia/init/init_test.go +++ b/cmd/gaia/init/init_test.go @@ -42,7 +42,6 @@ func setupClientHome(t *testing.T) func() { clientDir, err := ioutil.TempDir("", "mock-sdk-cmd") require.Nil(t, err) viper.Set(flagClientHome, clientDir) - viper.Set(flagOverwriteKey, true) return func() { if err := os.RemoveAll(clientDir); err != nil { // TODO: Handle with #870 diff --git a/cmd/gaia/init/testnet.go b/cmd/gaia/init/testnet.go index 3002b83a0..8bada583e 100644 --- a/cmd/gaia/init/testnet.go +++ b/cmd/gaia/init/testnet.go @@ -3,6 +3,10 @@ package init import ( "encoding/json" "fmt" + "net" + "os" + "path/filepath" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/codec" @@ -10,9 +14,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" authtx "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/stake" - "net" - "os" - "path/filepath" "github.com/cosmos/cosmos-sdk/server" "github.com/spf13/cobra" @@ -20,22 +21,25 @@ import ( cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" ) var ( - nodeDirPrefix = "node-dir-prefix" - nValidators = "v" - outputDir = "output-dir" - nodeDaemonHome = "node-daemon-home" - nodeCliHome = "node-cli-home" - - startingIPAddress = "starting-ip-address" + flagNodeDirPrefix = "node-dir-prefix" + flagNumValidators = "v" + flagOutputDir = "output-dir" + flagNodeDaemonHome = "node-daemon-home" + flagNodeCliHome = "node-cli-home" + flagStartingIPAddress = "starting-ip-address" ) const nodeDirPerm = 0755 // get cmd to initialize all files for tendermint testnet and application -func TestnetFilesCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command { +func TestnetFilesCmd(ctx *server.Context, cdc *codec.Codec, + appInit server.AppInit) *cobra.Command { + cmd := &cobra.Command{ Use: "testnet", Short: "Initialize files for a Gaiad testnet", @@ -45,48 +49,59 @@ necessary files (private validator, genesis, config, etc.). Note, strict routability for addresses is turned off in the config file. Example: - - gaiad testnet --v 4 --o ./output --starting-ip-address 192.168.10.2 + gaiad testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2 `, RunE: func(_ *cobra.Command, _ []string) error { config := ctx.Config - return testnetWithConfig(config, cdc, appInit) + return initTestnet(config, cdc) }, } - cmd.Flags().Int(nValidators, 4, - "Number of validators to initialize the testnet with") - cmd.Flags().StringP(outputDir, "o", "./mytestnet", - "Directory to store initialization data for the testnet") - cmd.Flags().String(nodeDirPrefix, "node", - "Prefix the directory name for each node with (node results in node0, node1, ...)") - cmd.Flags().String(nodeDaemonHome, "gaiad", - "Home directory of the node's daemon configuration") - cmd.Flags().String(nodeCliHome, "gaiacli", - "Home directory of the node's cli configuration") - cmd.Flags().String(startingIPAddress, "192.168.0.1", + cmd.Flags().Int(flagNumValidators, 4, + "Number of validators to initialize the testnet with", + ) + cmd.Flags().StringP(flagOutputDir, "o", "./mytestnet", + "Directory to store initialization data for the testnet", + ) + cmd.Flags().String(flagNodeDirPrefix, "node", + "Prefix the directory name for each node with (node results in node0, node1, ...)", + ) + cmd.Flags().String(flagNodeDaemonHome, "gaiad", + "Home directory of the node's daemon configuration", + ) + cmd.Flags().String(flagNodeCliHome, "gaiacli", + "Home directory of the node's cli configuration", + ) + cmd.Flags().String(flagStartingIPAddress, "192.168.0.1", "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)") + return cmd } -func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit server.AppInit) error { - outDir := viper.GetString(outputDir) - numValidators := viper.GetInt(nValidators) +func initTestnet(config *cfg.Config, cdc *codec.Codec) error { + outDir := viper.GetString(flagOutputDir) + numValidators := viper.GetInt(flagNumValidators) - // Generate genesis.json and config.toml chainID := "chain-" + cmn.RandStr(6) + monikers := make([]string, numValidators) nodeIDs := make([]string, numValidators) valPubKeys := make([]crypto.PubKey, numValidators) - // Generate private key, node ID, initial transaction + var ( + accs []app.GenesisAccount + genFiles []string + ) + + // generate private keys, node IDs, and initial transactions for i := 0; i < numValidators; i++ { - nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i) - nodeDaemonHomeName := viper.GetString(nodeDaemonHome) - nodeCliHomeName := viper.GetString(nodeCliHome) + nodeDirName := fmt.Sprintf("%s%d", viper.GetString(flagNodeDirPrefix), i) + nodeDaemonHomeName := viper.GetString(flagNodeDaemonHome) + nodeCliHomeName := viper.GetString(flagNodeCliHome) nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName) clientDir := filepath.Join(outDir, nodeDirName, nodeCliHomeName) gentxsDir := filepath.Join(outDir, "gentxs") + config.SetRoot(nodeDir) err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm) @@ -103,20 +118,27 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit server.AppI monikers = append(monikers, nodeDirName) config.Moniker = nodeDirName - ip, err := getIP(i) + + ip, err := getIP(i, viper.GetString(flagStartingIPAddress)) if err != nil { _ = os.RemoveAll(outDir) return err } + nodeIDs[i], valPubKeys[i], err = InitializeNodeValidatorFiles(config) if err != nil { _ = os.RemoveAll(outDir) return err } + memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip) + genFiles = append(genFiles, config.GenesisFile()) buf := client.BufferStdin() - prompt := fmt.Sprintf("Password for account '%s' (default %s):", nodeDirName, app.DefaultKeyPass) + prompt := fmt.Sprintf( + "Password for account '%s' (default %s):", nodeDirName, app.DefaultKeyPass, + ) + keyPass, err := client.GetPassword(prompt, buf) if err != nil && keyPass != "" { // An error was returned that either failed to read the password from @@ -124,6 +146,7 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit server.AppI // length requirements. return err } + if keyPass == "" { keyPass = app.DefaultKeyPass } @@ -133,17 +156,28 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit server.AppI _ = os.RemoveAll(outDir) return err } + info := map[string]string{"secret": secret} + cliPrint, err := json.Marshal(info) if err != nil { return err } - // Save private key seed words + + // save private key seed words err = writeFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, cliPrint) if err != nil { return err } + accs = append(accs, app.GenesisAccount{ + Address: addr, + Coins: sdk.Coins{ + sdk.NewInt64Coin(fmt.Sprintf("%sToken", nodeDirName), 1000), + sdk.NewInt64Coin("steak", 150), + }, + }) + msg := stake.NewMsgCreateValidator( sdk.ValAddress(addr), valPubKeys[i], @@ -153,6 +187,7 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit server.AppI ) tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo) txBldr := authtx.NewTxBuilderFromCLI().WithChainID(chainID).WithMemo(memo) + signedTx, err := txBldr.SignStdTx(nodeDirName, app.DefaultKeyPass, tx, false) if err != nil { _ = os.RemoveAll(outDir) @@ -165,7 +200,7 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit server.AppI return err } - // Gather gentxs folder + // gather gentxs folder err = writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBytes) if err != nil { _ = os.RemoveAll(outDir) @@ -173,64 +208,140 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit server.AppI } } + if err := initGenFiles(cdc, chainID, accs, genFiles, numValidators); err != nil { + return err + } + + err := collectGenFiles( + cdc, config, chainID, monikers, nodeIDs, valPubKeys, numValidators, + outDir, viper.GetString(flagNodeDirPrefix), viper.GetString(flagNodeDaemonHome), + ) + if err != nil { + return err + } + + fmt.Printf("Successfully initialized %d node directories\n", numValidators) + return nil +} + +func initGenFiles( + cdc *codec.Codec, chainID string, accs []app.GenesisAccount, + genFiles []string, numValidators int, +) error { + + appGenState := app.NewDefaultGenesisState() + appGenState.Accounts = accs + + appGenStateJSON, err := codec.MarshalJSONIndent(cdc, appGenState) + if err != nil { + return err + } + + genDoc := types.GenesisDoc{ + ChainID: chainID, + AppState: appGenStateJSON, + Validators: nil, + } + + // generate empty genesis files for each validator and save for i := 0; i < numValidators; i++ { - - nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i) - nodeDaemonHomeName := viper.GetString(nodeDaemonHome) - nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName) - gentxsDir := filepath.Join(outDir, "gentxs") - moniker := monikers[i] - config.Moniker = nodeDirName - config.SetRoot(nodeDir) - - nodeID, valPubKey := nodeIDs[i], valPubKeys[i] - // Run `init` and generate genesis.json and config.toml - initCfg := initConfig{ - ChainID: chainID, - GenTxsDir: gentxsDir, - Name: moniker, - WithTxs: true, - Overwrite: true, - OverwriteKey: false, - NodeID: nodeID, - ValPubKey: valPubKey, - } - if _, err := initWithConfig(cdc, config, initCfg); err != nil { + if err := genDoc.SaveAs(genFiles[i]); err != nil { return err } } - fmt.Printf("Successfully initialized %v node directories\n", viper.GetInt(nValidators)) return nil } -func getIP(i int) (ip string, err error) { - ip = viper.GetString(startingIPAddress) - if len(ip) == 0 { +func collectGenFiles( + cdc *codec.Codec, config *cfg.Config, chainID string, + monikers, nodeIDs []string, valPubKeys []crypto.PubKey, + numValidators int, outDir, nodeDirPrefix, nodeDaemonHomeName string, +) error { + + var appState json.RawMessage + genTime := tmtime.Now() + + for i := 0; i < numValidators; i++ { + nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i) + nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName) + gentxsDir := filepath.Join(outDir, "gentxs") + moniker := monikers[i] + config.Moniker = nodeDirName + + config.SetRoot(nodeDir) + + nodeID, valPubKey := nodeIDs[i], valPubKeys[i] + initCfg := initConfig{ + ChainID: chainID, + GenTxsDir: gentxsDir, + Name: moniker, + NodeID: nodeID, + ValPubKey: valPubKey, + } + + genDoc, err := loadGenesisDoc(cdc, config.GenesisFile()) + if err != nil { + return err + } + + nodeAppState, err := genAppStateFromConfig(cdc, config, initCfg, genDoc) + if err != nil { + return err + } + + if appState == nil { + // set the canonical application state (they should not differ) + appState = nodeAppState + } + + genFile := config.GenesisFile() + + // overwrite each validator's genesis file to have a canonical genesis time + err = ExportGenesisFileWithTime(genFile, chainID, nil, appState, genTime) + if err != nil { + return err + } + } + + return nil +} + +func getIP(i int, startingIPAddr string) (string, error) { + var ( + ip string + err error + ) + + if len(startingIPAddr) == 0 { ip, err = server.ExternalIP() if err != nil { return "", err } } else { - ip, err = calculateIP(ip, i) + ip, err = calculateIP(startingIPAddr, i) if err != nil { return "", err } } + return ip, nil } func writeFile(name string, dir string, contents []byte) error { writePath := filepath.Join(dir) file := filepath.Join(writePath, name) + err := cmn.EnsureDir(writePath, 0700) if err != nil { return err } + err = cmn.WriteFile(file, contents, 0600) if err != nil { return err } + return nil } @@ -243,5 +354,6 @@ func calculateIP(ip string, i int) (string, error) { for j := 0; j < i; j++ { ipv4[3]++ } + return ipv4.String(), nil } diff --git a/cmd/gaia/init/utils.go b/cmd/gaia/init/utils.go new file mode 100644 index 000000000..58edc9b2a --- /dev/null +++ b/cmd/gaia/init/utils.go @@ -0,0 +1,112 @@ +package init + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "time" + + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/codec" + amino "github.com/tendermint/go-amino" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/types" +) + +// ExportGenesisFile creates and writes the genesis configuration to disk. An +// error is returned if building or writing the configuration to file fails. +func ExportGenesisFile( + genFile, chainID string, validators []types.GenesisValidator, appState json.RawMessage, +) error { + + genDoc := types.GenesisDoc{ + ChainID: chainID, + Validators: validators, + AppState: appState, + } + + if err := genDoc.ValidateAndComplete(); err != nil { + return err + } + + return genDoc.SaveAs(genFile) +} + +// ExportGenesisFileWithTime creates and writes the genesis configuration to disk. +// An error is returned if building or writing the configuration to file fails. +func ExportGenesisFileWithTime( + genFile, chainID string, validators []types.GenesisValidator, + appState json.RawMessage, genTime time.Time, +) error { + + genDoc := types.GenesisDoc{ + GenesisTime: genTime, + ChainID: chainID, + Validators: validators, + AppState: appState, + } + + if err := genDoc.ValidateAndComplete(); err != nil { + return err + } + + return genDoc.SaveAs(genFile) +} + +// read of create the private key file for this config +func ReadOrCreatePrivValidator(privValFile string) crypto.PubKey { + var privValidator *privval.FilePV + + if common.FileExists(privValFile) { + privValidator = privval.LoadFilePV(privValFile) + } else { + privValidator = privval.GenFilePV(privValFile) + privValidator.Save() + } + + return privValidator.GetPubKey() +} + +// InitializeNodeValidatorFiles creates private validator and p2p configuration files. +func InitializeNodeValidatorFiles( + config *cfg.Config) (nodeID string, valPubKey crypto.PubKey, err error, +) { + + nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + if err != nil { + return nodeID, valPubKey, err + } + + nodeID = string(nodeKey.ID()) + valPubKey = ReadOrCreatePrivValidator(config.PrivValidatorFile()) + + return nodeID, valPubKey, nil +} + +func loadGenesisDoc(cdc *amino.Codec, genFile string) (genDoc types.GenesisDoc, err error) { + genContents, err := ioutil.ReadFile(genFile) + if err != nil { + return genDoc, err + } + + if err := cdc.UnmarshalJSON(genContents, &genDoc); err != nil { + return genDoc, err + } + + return genDoc, err +} + +func initializeEmptyGenesis( + cdc *codec.Codec, genFile, chainID string, overwrite bool, +) (appState json.RawMessage, err error) { + + if !overwrite && common.FileExists(genFile) { + return nil, fmt.Errorf("genesis.json file already exists: %v", genFile) + } + + return codec.MarshalJSONIndent(cdc, app.NewDefaultGenesisState()) +} diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index 86ae5c157..0bd500fee 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -273,7 +273,7 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t if err != nil { return nil, nil, err } - cdc.MustUnmarshalBinary([]byte(signed), sig) + cdc.MustUnmarshalBinaryLengthPrefixed([]byte(signed), sig) return sig, linfo.GetPubKey(), nil } sig, err = priv.Sign(msg) diff --git a/crypto/keys/types.go b/crypto/keys/types.go index ff90c3205..eeb4fdfcf 100644 --- a/crypto/keys/types.go +++ b/crypto/keys/types.go @@ -182,11 +182,11 @@ func (i offlineInfo) GetAddress() types.AccAddress { // encoding info func writeInfo(i Info) []byte { - return cdc.MustMarshalBinary(i) + return cdc.MustMarshalBinaryLengthPrefixed(i) } // decoding info func readInfo(bz []byte) (info Info, err error) { - err = cdc.UnmarshalBinary(bz, &info) + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &info) return } diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 5f09b5bb0..4746b3dd4 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -72,8 +72,8 @@ module.exports = { title: "Light Client", collapsable: false, children: [ - "/light/", - "/light/getting_started" + "/lite/", + "/lite/getting_started" ] }, { diff --git a/docs/PRIORITIES.md b/docs/PRIORITIES.md index 02aa495bb..522f1b866 100644 --- a/docs/PRIORITIES.md +++ b/docs/PRIORITIES.md @@ -36,6 +36,8 @@ - Tags [#1780](https://github.com/cosmos/cosmos-sdk/issues/1780) # Lower priority +- Create some diagrams (see `docs/resources/diagrams/todo.md`) + ## Governance v2 - Circuit breaker - https://github.com/cosmos/cosmos-sdk/issues/926 diff --git a/docs/config.js b/docs/config.js deleted file mode 100644 index 93426bcc5..000000000 --- a/docs/config.js +++ /dev/null @@ -1,86 +0,0 @@ -module.exports = { - title: "Cosmos Network", - description: "Documentation for the Cosmos Network.", - dest: "./dist/docs", - base: "/docs/", - markdown: { - lineNumbers: true - }, - themeConfig: { - lastUpdated: "Last Updated", - nav: [{ text: "Back to Cosmos", link: "https://cosmos.network" }], - sidebar: [ - { - title: "Introduction", - collapsable: false, - children: [ - "/introduction/cosmos-hub", - "/introduction/tendermint", - ] - }, - { - title: "Getting Started", - collapsable: false, - children: [ - "/getting-started/voyager", - "/getting-started/installation", - "/getting-started/full-node", - "/getting-started/create-testnet" - ] - }, - { - title: "Cosmos SDK", - collapsable: false, - children: [ - ["/sdk/overview", "Overview"], - ["/sdk/core/intro", "Core"], - "/sdk/core/app1", - "/sdk/core/app2", - "/sdk/core/app3", - "/sdk/core/app4", - "/sdk/core/app5", - // "/sdk/modules", - "/sdk/clients" - ] - }, - // { - // title: "Specifications", - // collapsable: false, - // children: [ - // ["/specs/overview", "Overview"], - // "/specs/governance", - // "/specs/ibc", - // "/specs/staking", - // "/specs/icts", - // ] - // }, - { - title: "Lotion JS", - collapsable: false, - children: [["/lotion/overview", "Overview"], "/lotion/building-an-app"] - }, - { - title: "Validators", - collapsable: false, - children: [ - ["/validators/overview", "Overview"], - ["/validators/security", "Security"], - ["/validators/validator-setup", "Validator Setup"], - "/validators/validator-faq" - ] - }, - { - title: "Resources", - collapsable: false, - children: [ - // ["/resources/faq" "General"], - "/resources/delegator-faq", - ["/resources/whitepaper", "Whitepaper - English"], - ["/resources/whitepaper-ko", "Whitepaper - 한국어"], - ["/resources/whitepaper-zh-CN", "Whitepaper - 中文"], - ["/resources/whitepaper-pt", "Whitepaper - Português"] - ] - } - ] - } -} diff --git a/docs/resources/diagrams/todo.md b/docs/resources/diagrams/todo.md new file mode 100644 index 000000000..b032b55cf --- /dev/null +++ b/docs/resources/diagrams/todo.md @@ -0,0 +1,17 @@ +The following diagrams should be created to aid in comprehension of the SDK: + - Genesis circuit + - App structure (aka use of baseapp in something like gaia) + - Simulation framework + - Slashing Mechanism + - Staking Mechanism + - Staking/Slashing Mechanism specific to use of hooks + - Governance Mechanism + - Distribution Mechanism + - Inflation Mechanism (easier) + - IBC Mechanism + +These diagrams should reference specific structs/interfaces from the codebase, +logic flow and interconnectivity with other mechanisms etc. It's recommended that +https://www.draw.io/ be used, hence the raw diagram xml can be saved directly to +the Cosmos-SDK repo and adapted with the codebase. + diff --git a/docs/sdk/clients.md b/docs/sdk/clients.md index e28a7f2b3..4f0669183 100644 --- a/docs/sdk/clients.md +++ b/docs/sdk/clients.md @@ -181,6 +181,12 @@ gaiacli tx sign \ unsignedSendTx.json > signedSendTx.json ``` +You can validate the transaction's signagures by typing the following: + +```bash +gaiacli tx sign --validate-signatures signedSendTx.json +``` + You can broadcast the signed transaction to a node by providing the JSON file to the following command: ``` @@ -285,7 +291,13 @@ Or if you want to check all your current unbonding-delegations with disctinct va gaiacli query unbonding-delegations ``` -You can also get previous unbonding-delegation(s) status by adding the `--height` flag. +Additionally, as you can get all the unbonding-delegations from a particular validator: + +```bash + gaiacli query unbonding-delegations-from +``` + +To get previous unbonding-delegation(s) status on past blocks, try adding the `--height` flag. #### Redelegate Tokens @@ -321,7 +333,13 @@ Or if you want to check all your current unbonding-delegations with disctinct va gaiacli query redelegations ``` -You can also get previous redelegation(s) status by adding the `--height` flag. +Additionally, as you can get all the outgoing redelegations from a particular validator: + +```bash + gaiacli query redelegations-from +``` + +To get previous redelegation(s) status on past blocks, try adding the `--height` flag. ### Governance diff --git a/docs/sdk/core/app2.md b/docs/sdk/core/app2.md index d8040a850..b0e42fd10 100644 --- a/docs/sdk/core/app2.md +++ b/docs/sdk/core/app2.md @@ -178,7 +178,7 @@ func (tx app2Tx) GetMsgs() []sdk.Msg { func tx2Decoder(cdc *codec.Codec) sdk.TxDecoder { return func(txBytes []byte) (sdk.Tx, sdk.Error) { var tx app2Tx - err := cdc.UnmarshalBinary(txBytes, &tx) + err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) if err != nil { return nil, sdk.ErrTxDecode(err.Error()) } diff --git a/docs/sdk/core/examples/app2.go b/docs/sdk/core/examples/app2.go index c1ae43666..24458384c 100644 --- a/docs/sdk/core/examples/app2.go +++ b/docs/sdk/core/examples/app2.go @@ -201,7 +201,7 @@ func (tx app2Tx) GetSignature() []byte { func tx2Decoder(cdc *codec.Codec) sdk.TxDecoder { return func(txBytes []byte) (sdk.Tx, sdk.Error) { var tx app2Tx - err := cdc.UnmarshalBinary(txBytes, &tx) + err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) if err != nil { return nil, sdk.ErrTxDecode(err.Error()) } diff --git a/docs/sdk/core/examples/app2_test.go b/docs/sdk/core/examples/app2_test.go index 913903488..c59dec264 100644 --- a/docs/sdk/core/examples/app2_test.go +++ b/docs/sdk/core/examples/app2_test.go @@ -39,7 +39,7 @@ func TestEncoding(t *testing.T) { cdc := NewCodec() testTxDecoder := tx2Decoder(cdc) - encodedSendTx, err := cdc.MarshalBinary(sendTxBefore) + encodedSendTx, err := cdc.MarshalBinaryLengthPrefixed(sendTxBefore) require.Nil(t, err, "Error encoding sendTx") @@ -69,7 +69,7 @@ func TestEncoding(t *testing.T) { Signature: sig, } - encodedIssueTx, err2 := cdc.MarshalBinary(issueTxBefore) + encodedIssueTx, err2 := cdc.MarshalBinaryLengthPrefixed(issueTxBefore) require.Nil(t, err2, "Error encoding issueTx") diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index c5c25ecae..b8785619b 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -1,159 +1,469 @@ -## Vesting +# Vesting -### Intro and Requirements + -This paper specifies vesting account implementation for the Cosmos Hub. -The requirements for this vesting account is that it should be initialized during genesis with -a starting balance X coins and a vesting endtime T. The owner of this account should be able to delegate to validators -and vote with locked coins, however they cannot send locked coins to other accounts until those coins have been unlocked. -The vesting account should also be able to spend any coins it receives from other users. -Thus, the bank module's `MsgSend` handler should error if a vesting account is trying to send an amount that exceeds their -unlocked coin amount. +- [Vesting](#vesting) + - [Intro and Requirements](#intro-and-requirements) + - [Vesting Account Types](#vesting-account-types) + - [Vesting Account Specification](#vesting-account-specification) + - [Determining Vesting & Vested Amounts](#determining-vesting--vested-amounts) + - [Continuously Vesting Accounts](#continuously-vesting-accounts) + - [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts) + - [Transferring/Sending](#transferringsending) + - [Continuously Vesting Accounts](#continuously-vesting-accounts-1) + - [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts-1) + - [Keepers/Handlers](#keepershandlers) + - [Delegating](#delegating) + - [Continuously Vesting Accounts](#continuously-vesting-accounts-2) + - [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts-2) + - [Keepers/Handlers](#keepershandlers-1) + - [Undelegating](#undelegating) + - [Continuously Vesting Accounts](#continuously-vesting-accounts-3) + - [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts-3) + - [Keepers/Handlers](#keepershandlers-2) + - [Keepers & Handlers](#keepers--handlers) + - [Initializing at Genesis](#initializing-at-genesis) + - [Examples](#examples) + - [Simple](#simple) + - [Slashing](#slashing) + - [Glossary](#glossary) -### Implementation + -##### Vesting Account implementation +## Intro and Requirements -NOTE: `Now = ctx.BlockHeader().Time` +This paper specifies vesting account implementation for the Cosmos Hub. +The requirements for this vesting account is that it should be initialized +during genesis with a starting balance `X` coins and a vesting end time `T`. + +The owner of this account should be able to delegate to validators +and vote with locked coins, however they cannot send locked coins to other +accounts until those coins have been unlocked. When it comes to governance, it +is yet undefined if we want to allow a vesting account to be able to deposit +vesting coins into proposals. + +In addition, a vesting account vests all of its coin denominations at the same +rate. This may be subject to change. + +**Note**: A vesting account could have some vesting and non-vesting coins. To +support such a feature, the `GenesisAccount` type will need to be updated in +order to make such a distinction. + +## Vesting Account Types ```go +// VestingAccount defines an interface that any vesting account type must +// implement. type VestingAccount interface { Account - AssertIsVestingAccount() // existence implies that account is vesting. + AssertIsVestingAccount() // existence implies that account is vesting - // Calculates amount of coins that can be sent to other accounts given the current time - SendableCoins(sdk.Context) sdk.Coins + // Calculates the amount of coins that can be sent to other accounts given + // the current time. + SpendableCoins(Context) Coins + // Performs delegation accounting. + TrackDelegation(amount) + // Performs undelegation accounting. + TrackUndelegation(amount) } -// Implements Vesting Account -// Continuously vests by unlocking coins linearly with respect to time +// BaseVestingAccount implements the VestingAccount interface. It contains all +// the necessary fields needed for any vesting account implementation. +type BaseVestingAccount struct { + BaseAccount + + OriginalVesting Coins // coins in account upon initialization + DelegatedFree Coins // coins that are vested and delegated + EndTime Time // when the coins become unlocked +} + +// ContinuousVestingAccount implements the VestingAccount interface. It +// continuously vests by unlocking coins linearly with respect to time. type ContinuousVestingAccount struct { BaseAccount - OriginalVestingCoins sdk.Coins // Coins in account on Initialization - ReceivedCoins sdk.Coins // Coins received from other accounts - SentCoins sdk.Coins // Coins sent to other accounts + BaseVestingAccount - // StartTime and EndTime used to calculate how much of OriginalCoins is unlocked at any given point - StartTime time.Time - EndTime time.Time + DelegatedVesting Coins // coins that vesting and delegated + StartTime Time // when the coins start to vest } -// Uses time in context to calculate total unlocked coins -SendableCoins(vacc ContinuousVestingAccount, ctx sdk.Context) sdk.Coins: - - // Coins unlocked by vesting schedule - unlockedCoins := ReceivedCoins - SentCoins + OriginalVestingCoins * (Now - StartTime) / (EndTime - StartTime) - - // Must still check for currentCoins constraint since some unlocked coins may have been delegated. - currentCoins := vacc.BaseAccount.GetCoins() - - // min will return sdk.Coins with each denom having the minimum amount from unlockedCoins and currentCoins - return min(unlockedCoins, currentCoins) - +// DelayedVestingAccount implements the VestingAccount interface. It vests all +// coins after a specific time, but non prior. In other words, it keeps them +// locked until a specified time. +type DelayedVestingAccount struct { + BaseAccount + BaseVestingAccount +} ``` -The `VestingAccount` interface is used to assert that an account is a vesting account like so: +## Vesting Account Specification + +Given a vesting account, we define the following in the proceeding operations: + +- `OV`: The original vesting coin amount. It is a constant value. +- `V`: The number of `OV` coins that are still _vesting_. It is derived by `OV`, `StartTime` and `EndTime`. This value is computed on demand and not on a per-block basis. +- `V'`: The number of `OV` coins that are _vested_ (unlocked). This value is computed on demand and not a per-block basis. +- `DV`: The number of delegated _vesting_ coins. It is a variable value. It is stored and modified directly in the vesting account. +- `DF`: The number of delegated _vested_ (unlocked) coins. It is a variable value. It is stored and modified directly in the vesting account. +- `BC`: The number of `OV` coins less any coins that are transferred, which can be negative, or delegated (`DV + DF`). It is considered to be balance of the embedded base account. It is stored and modified directly in the vesting account. + +### Determining Vesting & Vested Amounts + +It is important to note that these values are computed on demand and not on a +mandatory per-block basis. + +#### Continuously Vesting Accounts + +To determine the amount of coins that are vested for a given block `B`, the +following is performed: + +1. Compute `X := B.Time - StartTime` +2. Compute `Y := EndTime - StartTime` +3. Compute `V' := OV * (X / Y)` +4. Compute `V := OV - V'` + +Thus, the total amount of _vested_ coins is `V'` and the remaining amount, `V`, +is _vesting_. ```go -vacc, ok := acc.(VestingAccount); ok +func (cva ContinuousVestingAccount) GetVestedCoins(b Block) Coins { + // We must handle the case where the start time for a vesting account has + // been set into the future or when the start of the chain is not exactly + // known. + if b.Time < va.StartTime { + return ZeroCoins + } + + x := b.Time - cva.StartTime + y := cva.EndTime - cva.StartTime + + return cva.OriginalVesting * (x / y) +} + +func (cva ContinuousVestingAccount) GetVestingCoins(b Block) Coins { + return cva.OriginalVesting - cva.GetVestedCoins(b) +} ``` -as well as to calculate the SendableCoins at any given moment. +#### Delayed/Discrete Vesting Accounts -The `ContinuousVestingAccount` struct implements the Vesting account interface. It uses `OriginalVestingCoins`, `ReceivedCoins`, -`SentCoins`, `StartTime`, and `EndTime` to calculate how many coins are sendable at any given point. -Since the vesting restrictions need to be implemented on a per-module basis, the `ContinuousVestingAccount` implements -the `Account` interface exactly like `BaseAccount`. Thus, `ContinuousVestingAccount.GetCoins()` will return the total of -both locked coins and unlocked coins currently in the account. Delegated coins are deducted from `Account.GetCoins()`, but do not count against unlocked coins because they are still at stake and will be reinstated (partially if slashed) after waiting the full unbonding period. - -##### Changes to Keepers/Handler - -Since a vesting account should be capable of doing everything but sending with its locked coins, the restriction should be -handled at the `bank.Keeper` level. Specifically in methods that are explicitly used for sending like -`sendCoins` and `inputOutputCoins`. These methods must check that an account is a vesting account using the check described above. +Delayed vesting accounts are easier to reason about as they only have the full +amount vesting up until a certain time, then they all become vested (unlocked). ```go -if acc is VestingAccount and Now < vestingAccount.EndTime: - // Check if amount is less than currently allowed sendable coins - if msg.Amount > vestingAccount.SendableCoins(ctx) then fail - else: - vestingAccount.SentCoins += msg.Amount +func (dva DelayedVestingAccount) GetVestedCoins(b Block) Coins { + if b.Time >= dva.EndTime { + return dva.OriginalVesting + } -else: - // Account has fully vested, treat like regular account - if msg.Amount > account.GetCoins() then fail - -// All checks passed, send the coins -SendCoins(inputs, outputs) + return ZeroCoins +} +func (dva DelayedVestingAccount) GetVestingCoins(b Block) Coins { + return cva.OriginalVesting - cva.GetVestedCoins(b) +} ``` -Coins that are sent to a vesting account after initialization by users sending them coins should be spendable -immediately after receiving them. Thus, handlers (like staking or bank) that send coins that a vesting account did not -originally own should increment `ReceivedCoins` by the amount sent. -Unlocked coins that are sent to other accounts will increment the vesting account's `SentCoins` attribute. +### Transferring/Sending -CONTRACT: Handlers SHOULD NOT update `ReceivedCoins` if they were originally sent from the vesting account. For example, if a vesting account unbonds from a validator, their tokens should be added back to account but staking handlers SHOULD NOT update `ReceivedCoins`. -However when a user sends coins to vesting account, then `ReceivedCoins` SHOULD be incremented. +#### Continuously Vesting Accounts -### Initializing at Genesis +At any given time, a continuous vesting account may transfer: `min((BC + DV) - V, BC)`. -To initialize both vesting accounts and base accounts, the `GenesisAccount` struct will include an EndTime. Accounts meant to be -BaseAccounts will have `EndTime = 0`. The `initChainer` method will parse the GenesisAccount into BaseAccounts and VestingAccounts -as appropriate. +In other words, a vesting account may transfer the minimum of the base account +balance and the base account balance plus the number of currently delegated +vesting coins less the number of coins vested so far. + +```go +func (cva ContinuousVestingAccount) SpendableCoins() Coins { + bc := cva.GetCoins() + return min((bc + cva.DelegatedVesting) - cva.GetVestingCoins(), bc) +} +``` + +##### Delayed/Discrete Vesting Accounts + +A delayed vesting account may send any coins it has received. In addition, if it +has fully vested, it can send any of it's vested coins. + +```go +func (dva DelayedVestingAccount) SpendableCoins() Coins { + bc := dva.GetCoins() + return bc - dva.GetVestingCoins() +} +``` + +##### Keepers/Handlers + +The corresponding `x/bank` keeper should appropriately handle sending coins +based on if the account is a vesting account or not. + +```go +func SendCoins(from Account, to Account amount Coins) { + if isVesting(from) { + sc := from.SpendableCoins() + } else { + sc := from.GetCoins() + } + + if amount <= sc { + from.SetCoins(sc - amount) + to.SetCoins(amount) + // save accounts... + } +} +``` + +### Delegating + +#### Continuously Vesting Accounts + +For a continuous vesting account attempting to delegate `D` coins, the following +is performed: + +1. Verify `BC >= D > 0` +2. Compute `X := min(max(V - DV, 0), D)` (portion of `D` that is vesting) +3. Compute `Y := D - X` (portion of `D` that is free) +4. Set `DV += X` +5. Set `DF += Y` +6. Set `BC -= D` + +```go +func (cva ContinuousVestingAccount) TrackDelegation(amount Coins) { + x := min(max(cva.GetVestingCoins() - cva.DelegatedVesting, 0), amount) + y := amount - x + + cva.DelegatedVesting += x + cva.DelegatedFree += y +} +``` + +##### Delayed/Discrete Vesting Accounts + +For a delayed vesting account, it can only delegate with received coins and +coins that are fully vested so we only need to update `DF`. + +```go +func (dva DelayedVestingAccount) TrackDelegation(amount Coins) { + dva.DelegatedFree += amount +} +``` + +##### Keepers/Handlers + +```go +func DelegateCoins(from Account, amount Coins) { + // canDelegate checks different semantics for continuous and delayed vesting + // accounts + if isVesting(from) && canDelegate(from) { + sc := from.GetCoins() + + if amount <= sc { + from.TrackDelegation(amount) + from.SetCoins(sc - amount) + // save account... + } + } else { + sc := from.GetCoins() + + if amount <= sc { + from.SetCoins(sc - amount) + // save account... + } + } +} +``` + +### Undelegating + +#### Continuously Vesting Accounts + +For a continuous vesting account attempting to undelegate `D` coins, the +following is performed: + +1. Verify `(DV + DF) >= D > 0` (this is simply a sanity check) +2. Compute `Y := min(DF, D)` (portion of `D` that should become free, prioritizing free coins) +3. Compute `X := D - Y` (portion of `D` that should remain vesting) +4. Set `DV -= X` +5. Set `DF -= Y` +6. Set `BC += D` + +```go +func (cva ContinuousVestingAccount) TrackUndelegation(amount Coins) { + y := min(cva.DelegatedFree, amount) + x := amount - y + + cva.DelegatedVesting -= x + cva.DelegatedFree -= y +} +``` + +**Note**: If a delegation is slashed, the continuous vesting account will end up +with excess an `DV` amount, even after all its coins have vested. This is because +undelegating free coins are prioritized. + +##### Delayed/Discrete Vesting Accounts + +For a delayed vesting account, it only needs to add back the `DF` amount since +the account is fully vested. + +```go +func (dva DelayedVestingAccount) TrackUndelegation(amount Coins) { + dva.DelegatedFree -= amount +} +``` + +##### Keepers/Handlers + +```go +func UndelegateCoins(to Account, amount Coins) { + if isVesting(to) { + if to.DelegatedFree + to.DelegatedVesting >= amount { + to.TrackUndelegation(amount) + AddCoins(to, amount) + // save account ... + } + } else { + AddCoins(to, amount) + // save account... + } +} +``` + +## Keepers & Handlers + +The `VestingAccount` implementations reside in `x/auth`. However, any keeper in +a module (e.g. staking in `x/stake`) wishing to potentially utilize any vesting +coins, must call explicit methods on the `x/bank` keeper (e.g. `DelegateCoins`) +opposed to `SendCoins` and `SubtractCoins`. + +In addition, the vesting account should also be able to spend any coins it +receives from other users. Thus, the bank module's `MsgSend` handler should +error if a vesting account is trying to send an amount that exceeds their +unlocked coin amount. + +See the above specification for full implementation details. + +## Initializing at Genesis + +To initialize both vesting accounts and base accounts, the `GenesisAccount` +struct will include an `EndTime`. Accounts meant to be of type `BaseAccount` will +have `EndTime = 0`. The `initChainer` method will parse the GenesisAccount into +BaseAccounts and VestingAccounts as appropriate. ```go type GenesisAccount struct { - Address sdk.AccAddress `json:"address"` - GenesisCoins sdk.Coins `json:"coins"` - EndTime int64 `json:"lock"` + Address sdk.AccAddress + GenesisCoins sdk.Coins + EndTime int64 } -initChainer: - for gacc in GenesisAccounts: +func initChainer() { + for genAcc in GenesisAccounts { baseAccount := BaseAccount{ - Address: gacc.Address, - Coins: gacc.GenesisCoins, + Address: genAcc.Address, + Coins: genAcc.GenesisCoins, } - if gacc.EndTime != 0: - vestingAccount := ContinuouslyVestingAccount{ - BaseAccount: baseAccount, - OriginalVestingCoins: gacc.GenesisCoins, - StartTime: RequestInitChain.Time, - EndTime: gacc.EndTime, - } - AddAccountToState(vestingAccount) - else: - AddAccountToState(baseAccount) + if genAcc.EndTime != 0 { + vestingAccount := ContinuousVestingAccount{ + BaseAccount: baseAccount, + OriginalVesting: genAcc.GenesisCoins, + StartTime: RequestInitChain.Time, + EndTime: genAcc.EndTime, + } + + AddAccountToState(vestingAccount) + } else { + AddAccountToState(baseAccount) + } + } +} ``` -### Formulas +## Examples -`OriginalVestingCoins`: Amount of coins in account at Genesis +### Simple -`CurrentCoins`: Coins currently in the baseaccount (both locked and unlocked: `vestingAccount.GetCoins`) +Given a continuous vesting account with 10 vesting coins. -`ReceivedCoins`: Coins received from other accounts (always unlocked) +``` +OV = 10 +DF = 0 +DV = 0 +BC = 10 +V = 10 +V' = 0 +``` -`LockedCoins`: Coins that are currently locked +1. Immediately receives 1 coin + ``` + BC = 11 + ``` +2. Time passes, 2 coins vest + ``` + V = 8 + V' = 2 + ``` +3. Delegates 4 coins to validator A + ``` + DV = 4 + BC = 7 + ``` +4. Sends 3 coins + ``` + BC = 4 + ``` +5. More time passes, 2 more coins vest + ``` + V = 6 + V' = 4 + ``` +6. Sends 2 coins. At this point the account cannot send anymore until further coins vest or it receives additional coins. It can still however, delegate. + ``` + BC = 2 + ``` -`Delegated`: Coins that have been delegated (no longer in account; may be locked or unlocked) +### Slashing -`Sent`: Coins sent to other accounts (MUST be unlocked) +Same initial starting conditions as the simple example. -Maximum amount of coins vesting schedule allows to be sent: +1. Time passes, 5 coins vest + ``` + V = 5 + V' = 5 + ``` +2. Delegate 5 coins to validator A + ``` + DV = 5 + BC = 5 + ``` +3. Delegate 5 coins to validator B + ``` + DF = 5 + BC = 0 + ``` +4. Validator A gets slashed by 50%, making the delegation to A now worth 2.5 coins +5. Undelegate from validator A (2.5 coins) + ``` + DF = 5 - 2.5 = 2.5 + BC = 0 + 2.5 = 2.5 + ``` +6. Undelegate from validator B (5 coins). The account at this point can only send 2.5 coins unless it receives more coins or until more coins vest. It can still however, delegate. + ``` + DV = 5 - 2.5 = 2.5 + DF = 2.5 - 2.5 = 0 + BC = 2.5 + 5 = 7.5 + ``` -`ReceivedCoins - SentCoins + OriginalVestingCoins * (Now - StartTime) / (EndTime - StartTime)` +Notice how we have an excess amount of `DV`. -`ReceivedCoins - SentCoins + OriginalVestingCoins - LockedCoins` +## Glossary -Coins currently in Account: - -`CurrentCoins = OriginalVestingCoins + ReceivedCoins - Delegated - Sent` - -`CurrentCoins = vestingAccount.GetCoins()` - -**Maximum amount of coins spendable right now:** - -`min( ReceivedCoins - SentCoins + OriginalVestingCoins - LockedCoins, CurrentCoins )` +- OriginalVesting: The amount of coins (per denomination) that are initially part of a vesting account. These coins are set at genesis. +- StartTime: The BFT time at which a vesting account starts to vest. +- EndTime: The BFT time at which a vesting account is fully vested. +- DelegatedFree: The tracked amount of coins (per denomination) that are delegated from a vesting account that have been fully vested at time of delegation. +- DelegatedVesting: The tracked amount of coins (per denomination) that are delegated from a vesting account that were vesting at time of delegation. +- ContinuousVestingAccount: A vesting account implementation that vests coins linearly over time. +- DelayedVestingAccount: A vesting account implementation that only fully vests all coins at a given time. diff --git a/docs/spec/governance/state.md b/docs/spec/governance/state.md index 5b577cec3..ca130bd4c 100644 --- a/docs/spec/governance/state.md +++ b/docs/spec/governance/state.md @@ -96,10 +96,12 @@ type Proposal struct { Type ProposalType // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} TotalDeposit sdk.Coins // Current deposit on this proposal. Initial value is set at InitialDeposit Deposits []Deposit // List of deposits on the proposal - SubmitTime time.Time // Time of the block where TxGovSubmitProposal was included - Submitter sdk.Address // Address of the submitter + SubmitTime time.Time // Time of the block where TxGovSubmitProposal was included + DepositEndTime time.Time // Time that the DepositPeriod of a proposal would expire + Submitter sdk.AccAddress // Address of the submitter - VotingStartTime time.Time // Time of the block where MinDeposit was reached. time.Time{} if MinDeposit is not reached + VotingStartTime time.Time // Time of the block where MinDeposit was reached. time.Time{} if MinDeposit is not reached + VotingEndTime time.Time // Time of the block that the VotingPeriod for a proposal will end. CurrentStatus ProposalStatus // Current status of the proposal YesVotes sdk.Dec @@ -134,46 +136,26 @@ For pseudocode purposes, here are the two function we will use to read or write **Store:** * `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the - `ProposalIDs` of proposals that reached `MinDeposit`. Each round, the oldest - element of `ProposalProcessingQueue` is checked during `BeginBlock` to see if - `CurrentTime == VotingStartTime + activeProcedure.VotingPeriod`. If it is, - then the application tallies the votes, compute the votes of each validator and checks if every validator in the valdiator set have voted - and, if not, applies `GovernancePenalty`. If the proposal is accepted, deposits are refunded. - After that proposal is ejected from `ProposalProcessingQueue` and the next element of the queue is evaluated. + `ProposalIDs` of proposals that reached `MinDeposit`. Each `EndBlock`, all the proposals + that have reached the end of their voting period are processed. + To process a finished proposal, the application tallies the votes, compute the votes of + each validator and checks if every validator in the valdiator set have voted. + If the proposal is accepted, deposits are refunded. And the pseudocode for the `ProposalProcessingQueue`: ```go in EndBlock do - checkProposal() // First call of the recursive function - - - // Recursive function. First call in BeginBlock - func checkProposal() - proposalID = ProposalProcessingQueue.Peek() - if (proposalID == nil) - return + for finishedProposalID in GetAllFinishedProposalIDs(block.Time) + proposal = load(Governance, ) // proposal is a const key - proposal = load(Governance, ) // proposal is a const key - votingProcedure = load(GlobalParams, 'VotingProcedure') + validators = Keeper.getAllValidators() + tmpValMap := map(sdk.AccAddress)ValidatorGovInfo - if (CurrentTime == proposal.VotingStartTime + votingProcedure.VotingPeriod && proposal.CurrentStatus == ProposalStatusActive) - - // End of voting period, tally - - ProposalProcessingQueue.pop() - validators = - - - Keeper.getAllValidators() - tmpValMap := map(sdk.Address)ValidatorGovInfo - - // Initiate mapping at 0. Validators that remain at 0 at the end of tally will be punished + // Initiate mapping at 0. This is the amount of shares of the validator's vote that will be overridden by their delegator's votes for each validator in validators - tmpValMap(validator).Minus = 0 - - + tmpValMap(validator.OperatorAddr).Minus = 0 // Tally voterIterator = rangeQuery(Governance, ) //return all the addresses that voted on the proposal @@ -212,5 +194,4 @@ And the pseudocode for the `ProposalProcessingQueue`: proposal.CurrentStatus = ProposalStatusRejected store(Governance, , proposal) - checkProposal() ``` diff --git a/docs/spec/governance/transactions.md b/docs/spec/governance/transactions.md index 5ce91284f..f3636facd 100644 --- a/docs/spec/governance/transactions.md +++ b/docs/spec/governance/transactions.md @@ -45,6 +45,8 @@ upon receiving txGovSubmitProposal from sender do if (txGovSubmitProposal.Type != ProposalTypePlainText) OR (txGovSubmitProposal.Type != ProposalTypeSoftwareUpgrade) sender.AtomBalance -= initialDeposit.Atoms + + depositProcedure = load(GlobalParams, 'DepositProcedure') proposalID = generate new proposalID proposal = NewProposal() @@ -53,27 +55,15 @@ upon receiving txGovSubmitProposal from sender do proposal.Description = txGovSubmitProposal.Description proposal.Type = txGovSubmitProposal.Type proposal.TotalDeposit = initialDeposit - proposal.SubmitBlock = CurrentBlock + proposal.SubmitTime = + proposal.DepositEndTime = .Add(depositProcedure.MaxDepositPeriod) proposal.Deposits.append({initialDeposit, sender}) proposal.Submitter = sender proposal.YesVotes = 0 proposal.NoVotes = 0 proposal.NoWithVetoVotes = 0 proposal.AbstainVotes = 0 - - depositProcedure = load(GlobalParams, 'DepositProcedure') - - if (initialDeposit < depositProcedure.MinDeposit) - // MinDeposit is not reached - - proposal.CurrentStatus = ProposalStatusOpen - - else - // MinDeposit is reached - - proposal.CurrentStatus = ProposalStatusActive - proposal.VotingStartBlock = CurrentBlock - ProposalProcessingQueue.push(proposalID) + proposal.CurrentStatus = ProposalStatusOpen store(Proposals, , proposal) // Store proposal in Proposals mapping return proposalID diff --git a/docs/spec/staking/hooks.md b/docs/spec/staking/hooks.md index 7d24e32e6..3644155a6 100644 --- a/docs/spec/staking/hooks.md +++ b/docs/spec/staking/hooks.md @@ -7,7 +7,7 @@ The staking module allow for the following hooks to be registered with staking e type StakingHooks interface { OnValidatorCreated(ctx Context, address ValAddress) // Must be called when a validator is created OnValidatorModified(ctx Context, address ValAddress) // Must be called when a validator's state changes - OnValidatorRemoved(ctx Context, address ValAddress) // Must be called when a validator is deleted + OnValidatorRemoved(ctx Context, address ConsAddress, operator ValAddress) // Must be called when a validator is deleted OnValidatorBonded(ctx Context, address ConsAddress) // called when a validator is bonded OnValidatorBeginUnbonding(ctx Context, address ConsAddress, operator ValAddress) // called when a validator begins unbonding diff --git a/examples/basecoin/cmd/basecli/main.go b/examples/basecoin/cmd/basecli/main.go index 94c5c6e01..1732e82eb 100644 --- a/examples/basecoin/cmd/basecli/main.go +++ b/examples/basecoin/cmd/basecli/main.go @@ -49,6 +49,8 @@ func main() { client.GetCommands( stakecmd.GetCmdQueryValidator("stake", cdc), stakecmd.GetCmdQueryValidators("stake", cdc), + stakecmd.GetCmdQueryValidatorUnbondingDelegations("stake", cdc), + stakecmd.GetCmdQueryValidatorRedelegations("stake", cdc), stakecmd.GetCmdQueryDelegation("stake", cdc), stakecmd.GetCmdQueryDelegations("stake", cdc), stakecmd.GetCmdQueryPool("stake", cdc), diff --git a/examples/basecoin/cmd/basecoind/main.go b/examples/basecoin/cmd/basecoind/main.go index fc23db78f..f07fbd3ff 100644 --- a/examples/basecoin/cmd/basecoind/main.go +++ b/examples/basecoin/cmd/basecoind/main.go @@ -41,7 +41,6 @@ func main() { appInit := server.DefaultAppInit rootCmd.AddCommand(InitCmd(ctx, cdc, appInit)) - rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, appInit)) server.AddCommands(ctx, cdc, rootCmd, appInit, newApp, exportAppStateAndTMValidators) @@ -85,7 +84,8 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cob return err } - appState, err := appInit.AppGenState(cdc, []json.RawMessage{genTx}) + appState, err := appInit.AppGenState( + cdc, tmtypes.GenesisDoc{}, []json.RawMessage{genTx}) if err != nil { return err } @@ -108,13 +108,15 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cob return err } fmt.Fprintf(os.Stderr, "%s\n", string(out)) - return gaiaInit.WriteGenesisFile(config.GenesisFile(), chainID, []tmtypes.GenesisValidator{validator}, appStateJSON) + return gaiaInit.ExportGenesisFile(config.GenesisFile(), chainID, + []tmtypes.GenesisValidator{validator}, appStateJSON) }, } cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") - cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") + cmd.Flags().String(client.FlagChainID, "", + "genesis file chain-id, if left blank will be randomly created") cmd.Flags().String(client.FlagName, "", "validator's moniker") cmd.MarkFlagRequired(client.FlagName) return cmd @@ -124,7 +126,8 @@ func newApp(logger log.Logger, db dbm.DB, storeTracer io.Writer) abci.Applicatio return app.NewBasecoinApp(logger, db, baseapp.SetPruning(viper.GetString("pruning"))) } -func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, storeTracer io.Writer) (json.RawMessage, []tmtypes.GenesisValidator, error) { +func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, storeTracer io.Writer) ( + json.RawMessage, []tmtypes.GenesisValidator, error) { bapp := app.NewBasecoinApp(logger, db) return bapp.ExportAppStateAndValidators() } diff --git a/examples/democoin/app/app.go b/examples/democoin/app/app.go index 12f5d8d29..e8ddd066d 100644 --- a/examples/democoin/app/app.go +++ b/examples/democoin/app/app.go @@ -186,8 +186,8 @@ func (app *DemocoinApp) ExportAppStateAndValidators() (appState json.RawMessage, genState := types.GenesisState{ Accounts: accounts, - POWGenesis: pow.WriteGenesis(ctx, app.powKeeper), - CoolGenesis: cool.WriteGenesis(ctx, app.coolKeeper), + POWGenesis: pow.ExportGenesis(ctx, app.powKeeper), + CoolGenesis: cool.ExportGenesis(ctx, app.coolKeeper), } appState, err = codec.MarshalJSONIndent(app.cdc, genState) if err != nil { diff --git a/examples/democoin/cmd/democli/main.go b/examples/democoin/cmd/democli/main.go index 08f131168..6bb2da868 100644 --- a/examples/democoin/cmd/democli/main.go +++ b/examples/democoin/cmd/democli/main.go @@ -21,6 +21,8 @@ import ( coolcmd "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool/client/cli" powcmd "github.com/cosmos/cosmos-sdk/examples/democoin/x/pow/client/cli" simplestakingcmd "github.com/cosmos/cosmos-sdk/examples/democoin/x/simplestake/client/cli" + + sdk "github.com/cosmos/cosmos-sdk/types" ) // rootCmd is the entry point for this binary @@ -38,6 +40,13 @@ func main() { // get the codec cdc := app.MakeCodec() + // Setup certain SDK config + config := sdk.GetConfig() + config.SetBech32PrefixForAccount("demoacc", "demopub") + config.SetBech32PrefixForValidator("demoval", "demovalpub") + config.SetBech32PrefixForConsensusNode("democons", "democonspub") + config.Seal() + // TODO: setup keybase, viper object, etc. to be passed into // the below functions and eliminate global vars, like we do // with the cdc diff --git a/examples/democoin/cmd/democoind/main.go b/examples/democoin/cmd/democoind/main.go index 692564b88..d095b4c79 100644 --- a/examples/democoin/cmd/democoind/main.go +++ b/examples/democoin/cmd/democoind/main.go @@ -23,6 +23,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/examples/democoin/app" "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" ) const ( @@ -35,8 +36,9 @@ var CoolAppInit = server.AppInit{ } // coolGenAppParams sets up the app_state and appends the cool app state -func CoolAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { - appState, err = server.SimpleAppGenState(cdc, appGenTxs) +func CoolAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( + appState json.RawMessage, err error) { + appState, err = server.SimpleAppGenState(cdc, tmtypes.GenesisDoc{}, appGenTxs) if err != nil { return } @@ -89,7 +91,8 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cob return err } - appState, err := appInit.AppGenState(cdc, []json.RawMessage{genTx}) + appState, err := appInit.AppGenState(cdc, tmtypes.GenesisDoc{}, + []json.RawMessage{genTx}) if err != nil { return err } @@ -112,13 +115,15 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cob return err } fmt.Fprintf(os.Stderr, "%s\n", string(out)) - return gaiaInit.WriteGenesisFile(config.GenesisFile(), chainID, []tmtypes.GenesisValidator{validator}, appStateJSON) + return gaiaInit.ExportGenesisFile(config.GenesisFile(), chainID, + []tmtypes.GenesisValidator{validator}, appStateJSON) }, } cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") - cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") + cmd.Flags().String(client.FlagChainID, "", + "genesis file chain-id, if left blank will be randomly created") cmd.Flags().String(client.FlagName, "", "validator's moniker") cmd.MarkFlagRequired(client.FlagName) return cmd @@ -128,13 +133,22 @@ func newApp(logger log.Logger, db dbm.DB, _ io.Writer) abci.Application { return app.NewDemocoinApp(logger, db) } -func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, _ io.Writer) (json.RawMessage, []tmtypes.GenesisValidator, error) { +func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, _ io.Writer) ( + json.RawMessage, []tmtypes.GenesisValidator, error) { dapp := app.NewDemocoinApp(logger, db) return dapp.ExportAppStateAndValidators() } func main() { cdc := app.MakeCodec() + + // Setup certain SDK config + config := sdk.GetConfig() + config.SetBech32PrefixForAccount("demoacc", "demopub") + config.SetBech32PrefixForValidator("demoval", "demovalpub") + config.SetBech32PrefixForConsensusNode("democons", "democonspub") + config.Seal() + ctx := server.NewDefaultContext() rootCmd := &cobra.Command{ diff --git a/examples/democoin/mock/validator.go b/examples/democoin/mock/validator.go index fc00b79be..1d10c48b2 100644 --- a/examples/democoin/mock/validator.go +++ b/examples/democoin/mock/validator.go @@ -82,8 +82,13 @@ func (vs *ValidatorSet) IterateValidators(ctx sdk.Context, fn func(index int64, } } -// IterateValidatorsBonded implements sdk.ValidatorSet -func (vs *ValidatorSet) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) { +// IterateBondedValidatorsByPower implements sdk.ValidatorSet +func (vs *ValidatorSet) IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) { + vs.IterateValidators(ctx, fn) +} + +// IterateLastValidators implements sdk.ValidatorSet +func (vs *ValidatorSet) IterateLastValidators(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) { vs.IterateValidators(ctx, fn) } diff --git a/examples/democoin/x/cool/keeper.go b/examples/democoin/x/cool/keeper.go index f805ca880..9f46b0209 100644 --- a/examples/democoin/x/cool/keeper.go +++ b/examples/democoin/x/cool/keeper.go @@ -49,8 +49,8 @@ func InitGenesis(ctx sdk.Context, k Keeper, data Genesis) error { return nil } -// WriteGenesis - output the genesis trend -func WriteGenesis(ctx sdk.Context, k Keeper) Genesis { +// ExportGenesis - output the genesis trend +func ExportGenesis(ctx sdk.Context, k Keeper) Genesis { trend := k.GetTrend(ctx) return Genesis{trend} } diff --git a/examples/democoin/x/cool/keeper_test.go b/examples/democoin/x/cool/keeper_test.go index 1eb40dfb2..904681382 100644 --- a/examples/democoin/x/cool/keeper_test.go +++ b/examples/democoin/x/cool/keeper_test.go @@ -37,7 +37,7 @@ func TestCoolKeeper(t *testing.T) { err := InitGenesis(ctx, keeper, Genesis{"icy"}) require.Nil(t, err) - genesis := WriteGenesis(ctx, keeper) + genesis := ExportGenesis(ctx, keeper) require.Nil(t, err) require.Equal(t, genesis, Genesis{"icy"}) diff --git a/examples/democoin/x/oracle/keeper.go b/examples/democoin/x/oracle/keeper.go index e55cd7083..d061d2f8e 100644 --- a/examples/democoin/x/oracle/keeper.go +++ b/examples/democoin/x/oracle/keeper.go @@ -71,7 +71,7 @@ func (keeper Keeper) Info(ctx sdk.Context, p Payload) (res Info) { if bz == nil { return EmptyInfo(ctx) } - keeper.cdc.MustUnmarshalBinary(bz, &res) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &res) return } @@ -80,7 +80,7 @@ func (keeper Keeper) setInfo(ctx sdk.Context, p Payload, info Info) { store := ctx.KVStore(keeper.key) key := GetInfoKey(p, keeper.cdc) - bz := keeper.cdc.MustMarshalBinary(info) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(info) store.Set(key, bz) } diff --git a/examples/democoin/x/oracle/keeper_keys.go b/examples/democoin/x/oracle/keeper_keys.go index 9b71aeaa1..d678692be 100644 --- a/examples/democoin/x/oracle/keeper_keys.go +++ b/examples/democoin/x/oracle/keeper_keys.go @@ -7,13 +7,13 @@ import ( // GetInfoKey returns the key for OracleInfo func GetInfoKey(p Payload, cdc *codec.Codec) []byte { - bz := cdc.MustMarshalBinary(p) + bz := cdc.MustMarshalBinaryLengthPrefixed(p) return append([]byte{0x00}, bz...) } // GetSignPrefix returns the prefix for signs func GetSignPrefix(p Payload, cdc *codec.Codec) []byte { - bz := cdc.MustMarshalBinary(p) + bz := cdc.MustMarshalBinaryLengthPrefixed(p) return append([]byte{0x01}, bz...) } diff --git a/examples/democoin/x/oracle/oracle_test.go b/examples/democoin/x/oracle/oracle_test.go index 7621ea860..1284f75b6 100644 --- a/examples/democoin/x/oracle/oracle_test.go +++ b/examples/democoin/x/oracle/oracle_test.go @@ -82,7 +82,7 @@ func getSequence(ctx sdk.Context, key sdk.StoreKey) int { if seqbz == nil { seq = 0 } else { - codec.New().MustUnmarshalBinary(seqbz, &seq) + codec.New().MustUnmarshalBinaryLengthPrefixed(seqbz, &seq) } return seq @@ -96,7 +96,7 @@ func handleSeqOracle(ctx sdk.Context, key sdk.StoreKey, o seqOracle) sdk.Error { return sdk.NewError(sdk.CodespaceRoot, 1, "") } - bz := codec.New().MustMarshalBinary(seq + 1) + bz := codec.New().MustMarshalBinaryLengthPrefixed(seq + 1) store.Set([]byte("seq"), bz) return nil diff --git a/examples/democoin/x/pow/keeper.go b/examples/democoin/x/pow/keeper.go index 38a0d93c6..6c3bfc4eb 100644 --- a/examples/democoin/x/pow/keeper.go +++ b/examples/democoin/x/pow/keeper.go @@ -43,8 +43,8 @@ func InitGenesis(ctx sdk.Context, k Keeper, genesis Genesis) error { return nil } -// WriteGenesis for the PoW module -func WriteGenesis(ctx sdk.Context, k Keeper) Genesis { +// ExportGenesis for the PoW module +func ExportGenesis(ctx sdk.Context, k Keeper) Genesis { difficulty, err := k.GetLastDifficulty(ctx) if err != nil { panic(err) diff --git a/examples/democoin/x/pow/keeper_test.go b/examples/democoin/x/pow/keeper_test.go index 86ccbc8c0..c8d5406f9 100644 --- a/examples/democoin/x/pow/keeper_test.go +++ b/examples/democoin/x/pow/keeper_test.go @@ -41,7 +41,7 @@ func TestPowKeeperGetSet(t *testing.T) { err := InitGenesis(ctx, keeper, Genesis{uint64(1), uint64(0)}) require.Nil(t, err) - genesis := WriteGenesis(ctx, keeper) + genesis := ExportGenesis(ctx, keeper) require.Nil(t, err) require.Equal(t, genesis, Genesis{uint64(1), uint64(0)}) diff --git a/examples/democoin/x/simplestake/keeper.go b/examples/democoin/x/simplestake/keeper.go index 7bdc17937..607f61d83 100644 --- a/examples/democoin/x/simplestake/keeper.go +++ b/examples/democoin/x/simplestake/keeper.go @@ -39,7 +39,7 @@ func (k Keeper) getBondInfo(ctx sdk.Context, addr sdk.AccAddress) bondInfo { return bondInfo{} } var bi bondInfo - err := k.cdc.UnmarshalBinary(bz, &bi) + err := k.cdc.UnmarshalBinaryLengthPrefixed(bz, &bi) if err != nil { panic(err) } @@ -48,7 +48,7 @@ func (k Keeper) getBondInfo(ctx sdk.Context, addr sdk.AccAddress) bondInfo { func (k Keeper) setBondInfo(ctx sdk.Context, addr sdk.AccAddress, bi bondInfo) { store := ctx.KVStore(k.key) - bz, err := k.cdc.MarshalBinary(bi) + bz, err := k.cdc.MarshalBinaryLengthPrefixed(bi) if err != nil { panic(err) } diff --git a/scripts/multisim.sh b/scripts/multisim.sh index 8ffa338b8..ff5784e4b 100755 --- a/scripts/multisim.sh +++ b/scripts/multisim.sh @@ -1,6 +1,7 @@ #!/bin/bash -seeds=(1 2 4 7 9 20 32 123 124 582 1893 2989 3012 4728 37827 981928 87821 891823782 989182 89182391) +seeds=(1 2 4 7 9 20 32 123 124 582 1893 2989 3012 4728 37827 981928 87821 891823782 989182 89182391 \ +11 22 44 77 99 2020 3232 123123 124124 582582 18931893 29892989 30123012 47284728 37827) blocks=$1 echo "Running multi-seed simulation with seeds ${seeds[@]}" diff --git a/server/init.go b/server/init.go index 6a5d821c1..75e13f452 100644 --- a/server/init.go +++ b/server/init.go @@ -19,7 +19,8 @@ import ( type AppInit struct { // AppGenState creates the core parameters initialization. It takes in a // pubkey meant to represent the pubkey of the validator of this machine. - AppGenState func(cdc *codec.Codec, appGenTx []json.RawMessage) (appState json.RawMessage, err error) + AppGenState func(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( + appState json.RawMessage, err error) } // SimpleGenTx is a simple genesis tx @@ -35,7 +36,8 @@ var DefaultAppInit = AppInit{ } // Generate a genesis transaction -func SimpleAppGenTx(cdc *codec.Codec, pk crypto.PubKey) (appGenTx, cliPrint json.RawMessage, validator types.GenesisValidator, err error) { +func SimpleAppGenTx(cdc *codec.Codec, pk crypto.PubKey) ( + appGenTx, cliPrint json.RawMessage, validator types.GenesisValidator, err error) { var addr sdk.AccAddress var secret string addr, secret, err = GenerateCoinKey() @@ -63,7 +65,8 @@ func SimpleAppGenTx(cdc *codec.Codec, pk crypto.PubKey) (appGenTx, cliPrint json } // create the genesis app state -func SimpleAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { +func SimpleAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( + appState json.RawMessage, err error) { if len(appGenTxs) != 1 { err = errors.New("must provide a single genesis transaction") diff --git a/server/mock/app.go b/server/mock/app.go index 18afa164e..dd8f3f5ab 100644 --- a/server/mock/app.go +++ b/server/mock/app.go @@ -3,6 +3,7 @@ package mock import ( "encoding/json" "fmt" + "github.com/tendermint/tendermint/types" "path/filepath" abci "github.com/tendermint/tendermint/abci/types" @@ -102,7 +103,8 @@ func InitChainer(key sdk.StoreKey) func(sdk.Context, abci.RequestInitChain) abci // AppGenState can be passed into InitCmd, returns a static string of a few // key-values that can be parsed by InitChainer -func AppGenState(_ *codec.Codec, _ []json.RawMessage) (appState json.RawMessage, err error) { +func AppGenState(_ *codec.Codec, _ types.GenesisDoc, _ []json.RawMessage) (appState json. + RawMessage, err error) { appState = json.RawMessage(`{ "values": [ { @@ -119,7 +121,8 @@ func AppGenState(_ *codec.Codec, _ []json.RawMessage) (appState json.RawMessage, } // AppGenStateEmpty returns an empty transaction state for mocking. -func AppGenStateEmpty(_ *codec.Codec, _ []json.RawMessage) (appState json.RawMessage, err error) { +func AppGenStateEmpty(_ *codec.Codec, _ types.GenesisDoc, _ []json.RawMessage) ( + appState json.RawMessage, err error) { appState = json.RawMessage(``) return } diff --git a/server/mock/app_test.go b/server/mock/app_test.go index 05ec86521..a5f2a078b 100644 --- a/server/mock/app_test.go +++ b/server/mock/app_test.go @@ -1,6 +1,7 @@ package mock import ( + "github.com/tendermint/tendermint/types" "testing" "github.com/stretchr/testify/require" @@ -20,7 +21,7 @@ func TestInitApp(t *testing.T) { require.NoError(t, err) // initialize it future-way - appState, err := AppGenState(nil, nil) + appState, err := AppGenState(nil, types.GenesisDoc{}, nil) require.NoError(t, err) //TODO test validators in the init chain? diff --git a/server/start.go b/server/start.go index 82fbbbbeb..cf39ff71b 100644 --- a/server/start.go +++ b/server/start.go @@ -93,7 +93,6 @@ func startStandAlone(ctx *Context, appCreator AppCreator) error { return nil } -// nolint: unparam func startInProcess(ctx *Context, appCreator AppCreator) (*node.Node, error) { cfg := ctx.Config home := cfg.RootDir @@ -135,7 +134,12 @@ func startInProcess(ctx *Context, appCreator AppCreator) (*node.Node, error) { return nil, err } - // trap signal (run forever) - tmNode.RunForever() - return tmNode, nil + TrapSignal(func() { + if tmNode.IsRunning() { + _ = tmNode.Stop() + } + }) + + // run forever (the node will not be returned) + select {} } diff --git a/server/util.go b/server/util.go index 5199c1207..51f10c765 100644 --- a/server/util.go +++ b/server/util.go @@ -4,7 +4,9 @@ import ( "encoding/json" "net" "os" + "os/signal" "path/filepath" + "syscall" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -203,6 +205,23 @@ func ExternalIP() (string, error) { return "", errors.New("are you connected to the network?") } +// TrapSignal traps SIGINT and SIGTERM and terminates the server correctly. +func TrapSignal(cleanupFunc func()) { + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + go func() { + sig := <-sigs + switch sig { + case syscall.SIGTERM: + defer cleanupFunc() + os.Exit(128 + int(syscall.SIGTERM)) + case syscall.SIGINT: + defer cleanupFunc() + os.Exit(128 + int(syscall.SIGINT)) + } + }() +} + func skipInterface(iface net.Interface) bool { if iface.Flags&net.FlagUp == 0 { return true // interface down diff --git a/store/cachekvstore.go b/store/cachekvstore.go index 9eb4ae932..4a2940f4d 100644 --- a/store/cachekvstore.go +++ b/store/cachekvstore.go @@ -61,6 +61,7 @@ func (ci *cacheKVStore) Set(key []byte, value []byte) { ci.mtx.Lock() defer ci.mtx.Unlock() ci.assertValidKey(key) + ci.assertValidValue(value) ci.setCacheValue(key, value, false, true) } @@ -196,6 +197,12 @@ func (ci *cacheKVStore) assertValidKey(key []byte) { } } +func (ci *cacheKVStore) assertValidValue(value []byte) { + if value == nil { + panic("value is nil") + } +} + // Only entrypoint to mutate ci.cache. func (ci *cacheKVStore) setCacheValue(key, value []byte, deleted bool, dirty bool) { ci.cache[string(key)] = cValue{ diff --git a/store/iavlstore.go b/store/iavlstore.go index d535fd436..fccde38e2 100644 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -5,9 +5,9 @@ import ( "io" "sync" - "github.com/tendermint/go-amino" "github.com/tendermint/iavl" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/merkle" cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" @@ -210,44 +210,59 @@ func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) { res.Height = getHeight(tree, req) switch req.Path { - case "/store", "/key": // Get by key - key := req.Data // Data holds the key bytes + case "/key": // get by key + key := req.Data // data holds the key bytes + res.Key = key if !st.VersionExists(res.Height) { res.Log = cmn.ErrorWrap(iavl.ErrVersionDoesNotExist, "").Error() break } + if req.Prove { value, proof, err := tree.GetVersionedWithProof(key, res.Height) if err != nil { res.Log = err.Error() break } - res.Value = value - cdc := amino.NewCodec() - p, err := cdc.MarshalBinary(proof) - if err != nil { - res.Log = err.Error() - break + if proof == nil { + // Proof == nil implies that the store is empty. + if value != nil { + panic("unexpected value for an empty proof") + } + } + if value != nil { + // value was found + res.Value = value + res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{iavl.NewIAVLValueOp(key, proof).ProofOp()}} + } else { + // value wasn't found + res.Value = nil + res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{iavl.NewIAVLAbsenceOp(key, proof).ProofOp()}} } - res.Proof = p } else { _, res.Value = tree.GetVersioned(key, res.Height) } + case "/subspace": + var KVs []KVPair + subspace := req.Data res.Key = subspace - var KVs []KVPair + iterator := sdk.KVStorePrefixIterator(st, subspace) for ; iterator.Valid(); iterator.Next() { KVs = append(KVs, KVPair{Key: iterator.Key(), Value: iterator.Value()}) } + iterator.Close() - res.Value = cdc.MustMarshalBinary(KVs) + res.Value = cdc.MustMarshalBinaryLengthPrefixed(KVs) + default: msg := fmt.Sprintf("Unexpected Query path: %v", req.Path) return sdk.ErrUnknownRequest(msg).QueryResult() } + return } diff --git a/store/iavlstore_test.go b/store/iavlstore_test.go index 1e9263b7b..d26cee055 100644 --- a/store/iavlstore_test.go +++ b/store/iavlstore_test.go @@ -394,9 +394,9 @@ func TestIAVLStoreQuery(t *testing.T) { {Key: k1, Value: v3}, {Key: k2, Value: v2}, } - valExpSubEmpty := cdc.MustMarshalBinary(KVs0) - valExpSub1 := cdc.MustMarshalBinary(KVs1) - valExpSub2 := cdc.MustMarshalBinary(KVs2) + valExpSubEmpty := cdc.MustMarshalBinaryLengthPrefixed(KVs0) + valExpSub1 := cdc.MustMarshalBinaryLengthPrefixed(KVs1) + valExpSub2 := cdc.MustMarshalBinaryLengthPrefixed(KVs2) cid := iavlStore.Commit() ver := cid.Version @@ -459,7 +459,7 @@ func TestIAVLStoreQuery(t *testing.T) { require.Equal(t, valExpSub2, qres.Value) // default (height 0) will show latest -1 - query0 := abci.RequestQuery{Path: "/store", Data: k1} + query0 := abci.RequestQuery{Path: "/key", Data: k1} qres = iavlStore.Query(query0) require.Equal(t, uint32(sdk.CodeOK), qres.Code) require.Equal(t, v1, qres.Value) diff --git a/store/list.go b/store/list.go index 9905b0d6f..b38f11b80 100644 --- a/store/list.go +++ b/store/list.go @@ -42,21 +42,22 @@ func (m List) Len() (res uint64) { if bz == nil { return 0 } - m.cdc.MustUnmarshalBinary(bz, &res) + + m.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &res) return } // Get() returns the element by its index func (m List) Get(index uint64, ptr interface{}) error { bz := m.store.Get(ElemKey(index)) - return m.cdc.UnmarshalBinary(bz, ptr) + return m.cdc.UnmarshalBinaryLengthPrefixed(bz, ptr) } // Set() stores the element to the given position // Setting element out of range will break length counting // Use Push() instead of Set() to append a new element func (m List) Set(index uint64, value interface{}) { - bz := m.cdc.MustMarshalBinary(value) + bz := m.cdc.MustMarshalBinaryLengthPrefixed(value) m.store.Set(ElemKey(index), bz) } @@ -72,7 +73,7 @@ func (m List) Delete(index uint64) { func (m List) Push(value interface{}) { length := m.Len() m.Set(length, value) - m.store.Set(LengthKey(), m.cdc.MustMarshalBinary(length+1)) + m.store.Set(LengthKey(), m.cdc.MustMarshalBinaryLengthPrefixed(length+1)) } // Iterate() is used to iterate over all existing elements in the list @@ -85,13 +86,16 @@ func (m List) Iterate(ptr interface{}, fn func(uint64) bool) { iter := sdk.KVStorePrefixIterator(m.store, []byte{0x01}) for ; iter.Valid(); iter.Next() { v := iter.Value() - m.cdc.MustUnmarshalBinary(v, ptr) + m.cdc.MustUnmarshalBinaryLengthPrefixed(v, ptr) + k := iter.Key() s := string(k[len(k)-20:]) + index, err := strconv.ParseUint(s, 10, 64) if err != nil { panic(err) } + if fn(index) { break } diff --git a/store/multistoreproof.go b/store/multistoreproof.go index d62bc4aca..96f0a4837 100644 --- a/store/multistoreproof.go +++ b/store/multistoreproof.go @@ -2,90 +2,139 @@ package store import ( "bytes" + "fmt" - "github.com/pkg/errors" "github.com/tendermint/iavl" + "github.com/tendermint/tendermint/crypto/merkle" cmn "github.com/tendermint/tendermint/libs/common" ) // MultiStoreProof defines a collection of store proofs in a multi-store type MultiStoreProof struct { StoreInfos []storeInfo - StoreName string - RangeProof iavl.RangeProof } -// buildMultiStoreProof build MultiStoreProof based on iavl proof and storeInfos -func buildMultiStoreProof(iavlProof []byte, storeName string, storeInfos []storeInfo) []byte { - var rangeProof iavl.RangeProof - cdc.MustUnmarshalBinary(iavlProof, &rangeProof) - - msp := MultiStoreProof{ - StoreInfos: storeInfos, - StoreName: storeName, - RangeProof: rangeProof, - } - - proof := cdc.MustMarshalBinary(msp) - return proof +func NewMultiStoreProof(storeInfos []storeInfo) *MultiStoreProof { + return &MultiStoreProof{StoreInfos: storeInfos} } -// VerifyMultiStoreCommitInfo verify multiStoreCommitInfo against appHash -func VerifyMultiStoreCommitInfo(storeName string, storeInfos []storeInfo, appHash []byte) ([]byte, error) { - var substoreCommitHash []byte - var height int64 - for _, storeInfo := range storeInfos { - if storeInfo.Name == storeName { - substoreCommitHash = storeInfo.Core.CommitID.Hash - height = storeInfo.Core.CommitID.Version - } - } - if len(substoreCommitHash) == 0 { - return nil, cmn.NewError("failed to get substore root commit hash by store name") - } - +// ComputeRootHash returns the root hash for a given multi-store proof. +func (proof *MultiStoreProof) ComputeRootHash() []byte { ci := commitInfo{ - Version: height, - StoreInfos: storeInfos, + Version: -1, // TODO: Not needed; improve code. + StoreInfos: proof.StoreInfos, } - if !bytes.Equal(appHash, ci.Hash()) { - return nil, cmn.NewError("the merkle root of multiStoreCommitInfo doesn't equal to appHash") - } - return substoreCommitHash, nil + return ci.Hash() } -// VerifyRangeProof verify iavl RangeProof -func VerifyRangeProof(key, value []byte, substoreCommitHash []byte, rangeProof *iavl.RangeProof) error { - - // verify the proof to ensure data integrity. - err := rangeProof.Verify(substoreCommitHash) - if err != nil { - return errors.Wrap(err, "proof root hash doesn't equal to substore commit root hash") - } - - if len(value) != 0 { - // verify existence proof - err = rangeProof.VerifyItem(key, value) - if err != nil { - return errors.Wrap(err, "failed in existence verification") - } - } else { - // verify absence proof - err = rangeProof.VerifyAbsence(key) - if err != nil { - return errors.Wrap(err, "failed in absence verification") - } - } - - return nil -} - -// RequireProof return whether proof is require for the subpath +// RequireProof returns whether proof is required for the subpath. func RequireProof(subpath string) bool { - // Currently, only when query subpath is "/store" or "/key", will proof be included in response. - // If there are some changes about proof building in iavlstore.go, we must change code here to keep consistency with iavlstore.go:212 - if subpath == "/store" || subpath == "/key" { + // XXX: create a better convention. + // Currently, only when query subpath is "/key", will proof be included in + // response. If there are some changes about proof building in iavlstore.go, + // we must change code here to keep consistency with iavlStore#Query. + if subpath == "/key" { return true } + return false } + +//----------------------------------------------------------------------------- + +var _ merkle.ProofOperator = MultiStoreProofOp{} + +// the multi-store proof operation constant value +const ProofOpMultiStore = "multistore" + +// TODO: document +type MultiStoreProofOp struct { + // Encoded in ProofOp.Key + key []byte + + // To encode in ProofOp.Data. + Proof *MultiStoreProof `json:"proof"` +} + +func NewMultiStoreProofOp(key []byte, proof *MultiStoreProof) MultiStoreProofOp { + return MultiStoreProofOp{ + key: key, + Proof: proof, + } +} + +// MultiStoreProofOpDecoder returns a multi-store merkle proof operator from a +// given proof operation. +func MultiStoreProofOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) { + if pop.Type != ProofOpMultiStore { + return nil, cmn.NewError("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpMultiStore) + } + + // XXX: a bit strange as we'll discard this, but it works + var op MultiStoreProofOp + + err := cdc.UnmarshalBinaryLengthPrefixed(pop.Data, &op) + if err != nil { + return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into MultiStoreProofOp") + } + + return NewMultiStoreProofOp(pop.Key, op.Proof), nil +} + +// ProofOp return a merkle proof operation from a given multi-store proof +// operation. +func (op MultiStoreProofOp) ProofOp() merkle.ProofOp { + bz := cdc.MustMarshalBinaryLengthPrefixed(op) + return merkle.ProofOp{ + Type: ProofOpMultiStore, + Key: op.key, + Data: bz, + } +} + +// String implements the Stringer interface for a mult-store proof operation. +func (op MultiStoreProofOp) String() string { + return fmt.Sprintf("MultiStoreProofOp{%v}", op.GetKey()) +} + +// GetKey returns the key for a multi-store proof operation. +func (op MultiStoreProofOp) GetKey() []byte { + return op.key +} + +// Run executes a multi-store proof operation for a given value. It returns +// the root hash if the value matches all the store's commitID's hash or an +// error otherwise. +func (op MultiStoreProofOp) Run(args [][]byte) ([][]byte, error) { + if len(args) != 1 { + return nil, cmn.NewError("Value size is not 1") + } + + value := args[0] + root := op.Proof.ComputeRootHash() + + for _, si := range op.Proof.StoreInfos { + if si.Name == string(op.key) { + if bytes.Equal(value, si.Core.CommitID.Hash) { + return [][]byte{root}, nil + } + + return nil, cmn.NewError("hash mismatch for substore %v: %X vs %X", si.Name, si.Core.CommitID.Hash, value) + } + } + + return nil, cmn.NewError("key %v not found in multistore proof", op.key) +} + +//----------------------------------------------------------------------------- + +// XXX: This should be managed by the rootMultiStore which may want to register +// more proof ops? +func DefaultProofRuntime() (prt *merkle.ProofRuntime) { + prt = merkle.NewProofRuntime() + prt.RegisterOpDecoder(merkle.ProofOpSimpleValue, merkle.SimpleValueOpDecoder) + prt.RegisterOpDecoder(iavl.ProofOpIAVLValue, iavl.IAVLValueOpDecoder) + prt.RegisterOpDecoder(iavl.ProofOpIAVLAbsence, iavl.IAVLAbsenceOpDecoder) + prt.RegisterOpDecoder(ProofOpMultiStore, MultiStoreProofOpDecoder) + return +} diff --git a/store/multistoreproof_test.go b/store/multistoreproof_test.go index 45a102cd3..db3b65cad 100644 --- a/store/multistoreproof_test.go +++ b/store/multistoreproof_test.go @@ -1,123 +1,174 @@ package store import ( - "encoding/hex" "testing" - "github.com/stretchr/testify/assert" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" - "github.com/tendermint/iavl" - cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/libs/db" + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" ) -func TestVerifyMultiStoreCommitInfo(t *testing.T) { - appHash, _ := hex.DecodeString("69959B1B4E68E0F7BD3551A50C8F849B81801AF2") - - substoreRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") - storeName := "acc" - - var storeInfos []storeInfo - - gocRootHash, _ := hex.DecodeString("62c171bb022e47d1f745608ff749e676dbd25f78") - storeInfos = append(storeInfos, storeInfo{ - Name: "gov", - Core: storeCore{ - CommitID: CommitID{ - Version: 689, - Hash: gocRootHash, - }, - }, - }) - - storeInfos = append(storeInfos, storeInfo{ - Name: "main", - Core: storeCore{ - CommitID: CommitID{ - Version: 689, - Hash: nil, - }, - }, - }) - - accRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") - storeInfos = append(storeInfos, storeInfo{ - Name: "acc", - Core: storeCore{ - CommitID: CommitID{ - Version: 689, - Hash: accRootHash, - }, - }, - }) - - storeInfos = append(storeInfos, storeInfo{ - Name: "ibc", - Core: storeCore{ - CommitID: CommitID{ - Version: 689, - Hash: nil, - }, - }, - }) - - stakeRootHash, _ := hex.DecodeString("987d1d27b8771d93aa3691262f661d2c85af7ca4") - storeInfos = append(storeInfos, storeInfo{ - Name: "stake", - Core: storeCore{ - CommitID: CommitID{ - Version: 689, - Hash: stakeRootHash, - }, - }, - }) - - slashingRootHash, _ := hex.DecodeString("388ee6e5b11f367069beb1eefd553491afe9d73e") - storeInfos = append(storeInfos, storeInfo{ - Name: "slashing", - Core: storeCore{ - CommitID: CommitID{ - Version: 689, - Hash: slashingRootHash, - }, - }, - }) - - commitHash, err := VerifyMultiStoreCommitInfo(storeName, storeInfos, appHash) +func TestVerifyIAVLStoreQueryProof(t *testing.T) { + // Create main tree for testing. + db := dbm.NewMemDB() + iStore, err := LoadIAVLStore(db, CommitID{}, sdk.PruneNothing) + store := iStore.(*iavlStore) require.Nil(t, err) - require.Equal(t, commitHash, substoreRootHash) + store.Set([]byte("MYKEY"), []byte("MYVALUE")) + cid := store.Commit() - appHash, _ = hex.DecodeString("29de216bf5e2531c688de36caaf024cd3bb09ee3") + // Get Proof + res := store.Query(abci.RequestQuery{ + Path: "/key", // required path to get key/value+proof + Data: []byte("MYKEY"), + Prove: true, + }) + require.NotNil(t, res.Proof) - _, err = VerifyMultiStoreCommitInfo(storeName, storeInfos, appHash) - require.Error(t, err, "appHash doesn't match to the merkle root of multiStoreCommitInfo") + // Verify proof. + prt := DefaultProofRuntime() + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte("MYVALUE")) + require.Nil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY_NOT", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte("MYVALUE_NOT")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte(nil)) + require.NotNil(t, err) } -func TestVerifyRangeProof(t *testing.T) { - tree := iavl.NewMutableTree(db.NewMemDB(), 0) +func TestVerifyMultiStoreQueryProof(t *testing.T) { + // Create main tree for testing. + db := dbm.NewMemDB() + store := NewCommitMultiStore(db) + iavlStoreKey := sdk.NewKVStoreKey("iavlStoreKey") - rand := cmn.NewRand() - rand.Seed(0) // for determinism - for _, ikey := range []byte{0x11, 0x32, 0x50, 0x72, 0x99} { - key := []byte{ikey} - tree.Set(key, []byte(rand.Str(8))) - } + store.MountStoreWithDB(iavlStoreKey, sdk.StoreTypeIAVL, nil) + store.LoadVersion(0) - root := tree.WorkingHash() + iavlStore := store.GetCommitStore(iavlStoreKey).(*iavlStore) + iavlStore.Set([]byte("MYKEY"), []byte("MYVALUE")) + cid := store.Commit() - key := []byte{0x32} - val, proof, err := tree.GetWithProof(key) - assert.Nil(t, err) - assert.NotEmpty(t, val) - assert.NotEmpty(t, proof) - err = VerifyRangeProof(key, val, root, proof) - assert.Nil(t, err) + // Get Proof + res := store.Query(abci.RequestQuery{ + Path: "/iavlStoreKey/key", // required path to get key/value+proof + Data: []byte("MYKEY"), + Prove: true, + }) + require.NotNil(t, res.Proof) - key = []byte{0x40} - val, proof, err = tree.GetWithProof(key) - assert.Nil(t, err) - assert.Empty(t, val) - assert.NotEmpty(t, proof) - err = VerifyRangeProof(key, val, root, proof) - assert.Nil(t, err) + // Verify proof. + prt := DefaultProofRuntime() + err := prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE")) + require.Nil(t, err) + + // Verify proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE")) + require.Nil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY_NOT", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "iavlStoreKey/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE_NOT")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte(nil)) + require.NotNil(t, err) +} + +func TestVerifyMultiStoreQueryProofEmptyStore(t *testing.T) { + // Create main tree for testing. + db := dbm.NewMemDB() + store := NewCommitMultiStore(db) + iavlStoreKey := sdk.NewKVStoreKey("iavlStoreKey") + + store.MountStoreWithDB(iavlStoreKey, sdk.StoreTypeIAVL, nil) + store.LoadVersion(0) + cid := store.Commit() // Commit with empty iavl store. + + // Get Proof + res := store.Query(abci.RequestQuery{ + Path: "/iavlStoreKey/key", // required path to get key/value+proof + Data: []byte("MYKEY"), + Prove: true, + }) + require.NotNil(t, res.Proof) + + // Verify proof. + prt := DefaultProofRuntime() + err := prt.VerifyAbsence(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY") + require.Nil(t, err) + + // Verify (bad) proof. + prt = DefaultProofRuntime() + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) +} + +func TestVerifyMultiStoreQueryProofAbsence(t *testing.T) { + // Create main tree for testing. + db := dbm.NewMemDB() + store := NewCommitMultiStore(db) + iavlStoreKey := sdk.NewKVStoreKey("iavlStoreKey") + + store.MountStoreWithDB(iavlStoreKey, sdk.StoreTypeIAVL, nil) + store.LoadVersion(0) + + iavlStore := store.GetCommitStore(iavlStoreKey).(*iavlStore) + iavlStore.Set([]byte("MYKEY"), []byte("MYVALUE")) + cid := store.Commit() // Commit with empty iavl store. + + // Get Proof + res := store.Query(abci.RequestQuery{ + Path: "/iavlStoreKey/key", // required path to get key/value+proof + Data: []byte("MYABSENTKEY"), + Prove: true, + }) + require.NotNil(t, res.Proof) + + // Verify proof. + prt := DefaultProofRuntime() + err := prt.VerifyAbsence(res.Proof, cid.Hash, "/iavlStoreKey/MYABSENTKEY") + require.Nil(t, err) + + // Verify (bad) proof. + prt = DefaultProofRuntime() + err = prt.VerifyAbsence(res.Proof, cid.Hash, "/MYABSENTKEY") + require.NotNil(t, err) + + // Verify (bad) proof. + prt = DefaultProofRuntime() + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYABSENTKEY", []byte("")) + require.NotNil(t, err) } diff --git a/store/queue.go b/store/queue.go index 4081f63b3..f41ecb7d7 100644 --- a/store/queue.go +++ b/store/queue.go @@ -27,12 +27,12 @@ func (m Queue) getTop() (res uint64) { return 0 } - m.List.cdc.MustUnmarshalBinary(bz, &res) + m.List.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &res) return } func (m Queue) setTop(top uint64) { - bz := m.List.cdc.MustMarshalBinary(top) + bz := m.List.cdc.MustMarshalBinaryLengthPrefixed(top) m.List.store.Set(TopKey(), bz) } diff --git a/store/queue_test.go b/store/queue_test.go index 5ea6d906a..58e96f56a 100644 --- a/store/queue_test.go +++ b/store/queue_test.go @@ -81,7 +81,7 @@ func TestKeys(t *testing.T) { var actual int // Checking keys.LengthKey - err := cdc.UnmarshalBinary(store.Get(LengthKey()), &len) + err := cdc.UnmarshalBinaryLengthPrefixed(store.Get(LengthKey()), &len) require.Nil(t, err) require.Equal(t, len, queue.List.Len()) @@ -89,14 +89,14 @@ func TestKeys(t *testing.T) { for i := 0; i < 10; i++ { queue.List.Get(uint64(i), &expected) bz := store.Get(ElemKey(uint64(i))) - err = cdc.UnmarshalBinary(bz, &actual) + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &actual) require.Nil(t, err) require.Equal(t, expected, actual) } queue.Pop() - err = cdc.UnmarshalBinary(store.Get(TopKey()), &top) + err = cdc.UnmarshalBinaryLengthPrefixed(store.Get(TopKey()), &top) require.Nil(t, err) require.Equal(t, top, queue.getTop()) } diff --git a/store/rootmultistore.go b/store/rootmultistore.go index 7de465f77..cd2d0135f 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -295,13 +295,23 @@ func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery { return res } + if res.Proof == nil || len(res.Proof.Ops) == 0 { + return sdk.ErrInternal("substore proof was nil/empty when it should never be").QueryResult() + } + commitInfo, errMsg := getCommitInfo(rs.db, res.Height) if errMsg != nil { return sdk.ErrInternal(errMsg.Error()).QueryResult() } - res.Proof = buildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos) + // Restore origin path and append proof op. + res.Proof.Ops = append(res.Proof.Ops, NewMultiStoreProofOp( + []byte(storeName), + NewMultiStoreProof(commitInfo.StoreInfos), + ).ProofOp()) + // TODO: handle in another TM v0.26 update PR + // res.Proof = buildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos) return res } @@ -313,11 +323,14 @@ func parsePath(path string) (storeName string, subpath string, err sdk.Error) { err = sdk.ErrUnknownRequest(fmt.Sprintf("invalid path: %s", path)) return } + paths := strings.SplitN(path[1:], "/", 2) storeName = paths[0] + if len(paths) == 2 { subpath = "/" + paths[1] } + return } @@ -386,11 +399,12 @@ type commitInfo struct { // Hash returns the simple merkle root hash of the stores sorted by name. func (ci commitInfo) Hash() []byte { - // TODO cache to ci.hash []byte - m := make(map[string]merkle.Hasher, len(ci.StoreInfos)) + // TODO: cache to ci.hash []byte + m := make(map[string][]byte, len(ci.StoreInfos)) for _, storeInfo := range ci.StoreInfos { - m[storeInfo.Name] = storeInfo + m[storeInfo.Name] = storeInfo.Hash() } + return merkle.SimpleHashFromMap(m) } @@ -422,13 +436,15 @@ type storeCore struct { func (si storeInfo) Hash() []byte { // Doesn't write Name, since merkle.SimpleHashFromMap() will // include them via the keys. - bz, _ := cdc.MarshalBinary(si.Core) // Does not error + bz, _ := cdc.MarshalBinaryLengthPrefixed(si.Core) hasher := tmhash.New() + _, err := hasher.Write(bz) if err != nil { // TODO: Handle with #870 panic(err) } + return hasher.Sum(nil) } @@ -441,16 +457,18 @@ func getLatestVersion(db dbm.DB) int64 { if latestBytes == nil { return 0 } - err := cdc.UnmarshalBinary(latestBytes, &latest) + + err := cdc.UnmarshalBinaryLengthPrefixed(latestBytes, &latest) if err != nil { panic(err) } + return latest } // Set the latest version. func setLatestVersion(batch dbm.Batch, version int64) { - latestBytes, _ := cdc.MarshalBinary(version) // Does not error + latestBytes, _ := cdc.MarshalBinaryLengthPrefixed(version) batch.Set([]byte(latestVersionKey), latestBytes) } @@ -491,21 +509,19 @@ func getCommitInfo(db dbm.DB, ver int64) (commitInfo, error) { return commitInfo{}, fmt.Errorf("failed to get rootMultiStore: no data") } - // Parse bytes. var cInfo commitInfo - err := cdc.UnmarshalBinary(cInfoBytes, &cInfo) + + err := cdc.UnmarshalBinaryLengthPrefixed(cInfoBytes, &cInfo) if err != nil { return commitInfo{}, fmt.Errorf("failed to get rootMultiStore: %v", err) } + return cInfo, nil } // Set a commitInfo for given version. func setCommitInfo(batch dbm.Batch, version int64, cInfo commitInfo) { - cInfoBytes, err := cdc.MarshalBinary(cInfo) - if err != nil { - panic(err) - } + cInfoBytes := cdc.MustMarshalBinaryLengthPrefixed(cInfo) cInfoKey := fmt.Sprintf(commitInfoKeyFmt, version) batch.Set([]byte(cInfoKey), cInfoBytes) } diff --git a/store/rootmultistore_test.go b/store/rootmultistore_test.go index d6a714c66..501c5b730 100644 --- a/store/rootmultistore_test.go +++ b/store/rootmultistore_test.go @@ -215,7 +215,7 @@ func getExpectedCommitID(store *rootMultiStore, ver int64) CommitID { } func hashStores(stores map[StoreKey]CommitStore) []byte { - m := make(map[string]merkle.Hasher, len(stores)) + m := make(map[string][]byte, len(stores)) for key, store := range stores { name := key.Name() m[name] = storeInfo{ @@ -224,7 +224,7 @@ func hashStores(stores map[StoreKey]CommitStore) []byte { CommitID: store.LastCommitID(), // StoreType: store.GetStoreType(), }, - } + }.Hash() } return merkle.SimpleHashFromMap(m) } diff --git a/types/address.go b/types/address.go index ae13b2ad0..36eb5ac32 100644 --- a/types/address.go +++ b/types/address.go @@ -55,7 +55,8 @@ func AccAddressFromHex(address string) (addr AccAddress, err error) { // AccAddressFromBech32 creates an AccAddress from a Bech32 string. func AccAddressFromBech32(address string) (addr AccAddress, err error) { - bz, err := GetFromBech32(address, Bech32PrefixAccAddr) + bech32PrefixAccAddr := GetConfig().GetBech32AccountAddrPrefix() + bz, err := GetFromBech32(address, bech32PrefixAccAddr) if err != nil { return nil, err } @@ -124,7 +125,8 @@ func (aa AccAddress) Bytes() []byte { // String implements the Stringer interface. func (aa AccAddress) String() string { - bech32Addr, err := bech32.ConvertAndEncode(Bech32PrefixAccAddr, aa.Bytes()) + bech32PrefixAccAddr := GetConfig().GetBech32AccountAddrPrefix() + bech32Addr, err := bech32.ConvertAndEncode(bech32PrefixAccAddr, aa.Bytes()) if err != nil { panic(err) } @@ -169,7 +171,8 @@ func ValAddressFromHex(address string) (addr ValAddress, err error) { // ValAddressFromBech32 creates a ValAddress from a Bech32 string. func ValAddressFromBech32(address string) (addr ValAddress, err error) { - bz, err := GetFromBech32(address, Bech32PrefixValAddr) + bech32PrefixValAddr := GetConfig().GetBech32ValidatorAddrPrefix() + bz, err := GetFromBech32(address, bech32PrefixValAddr) if err != nil { return nil, err } @@ -239,7 +242,8 @@ func (va ValAddress) Bytes() []byte { // String implements the Stringer interface. func (va ValAddress) String() string { - bech32Addr, err := bech32.ConvertAndEncode(Bech32PrefixValAddr, va.Bytes()) + bech32PrefixValAddr := GetConfig().GetBech32ValidatorAddrPrefix() + bech32Addr, err := bech32.ConvertAndEncode(bech32PrefixValAddr, va.Bytes()) if err != nil { panic(err) } @@ -284,7 +288,8 @@ func ConsAddressFromHex(address string) (addr ConsAddress, err error) { // ConsAddressFromBech32 creates a ConsAddress from a Bech32 string. func ConsAddressFromBech32(address string) (addr ConsAddress, err error) { - bz, err := GetFromBech32(address, Bech32PrefixConsAddr) + bech32PrefixConsAddr := GetConfig().GetBech32ConsensusAddrPrefix() + bz, err := GetFromBech32(address, bech32PrefixConsAddr) if err != nil { return nil, err } @@ -359,7 +364,8 @@ func (ca ConsAddress) Bytes() []byte { // String implements the Stringer interface. func (ca ConsAddress) String() string { - bech32Addr, err := bech32.ConvertAndEncode(Bech32PrefixConsAddr, ca.Bytes()) + bech32PrefixConsAddr := GetConfig().GetBech32ConsensusAddrPrefix() + bech32Addr, err := bech32.ConvertAndEncode(bech32PrefixConsAddr, ca.Bytes()) if err != nil { panic(err) } @@ -387,7 +393,8 @@ func (ca ConsAddress) Format(s fmt.State, verb rune) { // Bech32ifyAccPub returns a Bech32 encoded string containing the // Bech32PrefixAccPub prefix for a given account PubKey. func Bech32ifyAccPub(pub crypto.PubKey) (string, error) { - return bech32.ConvertAndEncode(Bech32PrefixAccPub, pub.Bytes()) + bech32PrefixAccPub := GetConfig().GetBech32AccountPubPrefix() + return bech32.ConvertAndEncode(bech32PrefixAccPub, pub.Bytes()) } // MustBech32ifyAccPub returns the result of Bech32ifyAccPub panicing on failure. @@ -403,7 +410,8 @@ func MustBech32ifyAccPub(pub crypto.PubKey) string { // Bech32ifyValPub returns a Bech32 encoded string containing the // Bech32PrefixValPub prefix for a given validator operator's PubKey. func Bech32ifyValPub(pub crypto.PubKey) (string, error) { - return bech32.ConvertAndEncode(Bech32PrefixValPub, pub.Bytes()) + bech32PrefixValPub := GetConfig().GetBech32ValidatorPubPrefix() + return bech32.ConvertAndEncode(bech32PrefixValPub, pub.Bytes()) } // MustBech32ifyValPub returns the result of Bech32ifyValPub panicing on failure. @@ -419,7 +427,8 @@ func MustBech32ifyValPub(pub crypto.PubKey) string { // Bech32ifyConsPub returns a Bech32 encoded string containing the // Bech32PrefixConsPub prefixfor a given consensus node's PubKey. func Bech32ifyConsPub(pub crypto.PubKey) (string, error) { - return bech32.ConvertAndEncode(Bech32PrefixConsPub, pub.Bytes()) + bech32PrefixConsPub := GetConfig().GetBech32ConsensusPubPrefix() + return bech32.ConvertAndEncode(bech32PrefixConsPub, pub.Bytes()) } // MustBech32ifyConsPub returns the result of Bech32ifyConsPub panicing on @@ -436,7 +445,8 @@ func MustBech32ifyConsPub(pub crypto.PubKey) string { // GetAccPubKeyBech32 creates a PubKey for an account with a given public key // string using the Bech32 Bech32PrefixAccPub prefix. func GetAccPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) { - bz, err := GetFromBech32(pubkey, Bech32PrefixAccPub) + bech32PrefixAccPub := GetConfig().GetBech32AccountPubPrefix() + bz, err := GetFromBech32(pubkey, bech32PrefixAccPub) if err != nil { return nil, err } @@ -463,7 +473,8 @@ func MustGetAccPubKeyBech32(pubkey string) (pk crypto.PubKey) { // GetValPubKeyBech32 creates a PubKey for a validator's operator with a given // public key string using the Bech32 Bech32PrefixValPub prefix. func GetValPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) { - bz, err := GetFromBech32(pubkey, Bech32PrefixValPub) + bech32PrefixValPub := GetConfig().GetBech32ValidatorPubPrefix() + bz, err := GetFromBech32(pubkey, bech32PrefixValPub) if err != nil { return nil, err } @@ -490,7 +501,8 @@ func MustGetValPubKeyBech32(pubkey string) (pk crypto.PubKey) { // GetConsPubKeyBech32 creates a PubKey for a consensus node with a given public // key string using the Bech32 Bech32PrefixConsPub prefix. func GetConsPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) { - bz, err := GetFromBech32(pubkey, Bech32PrefixConsPub) + bech32PrefixConsPub := GetConfig().GetBech32ConsensusPubPrefix() + bz, err := GetFromBech32(pubkey, bech32PrefixConsPub) if err != nil { return nil, err } diff --git a/types/address_test.go b/types/address_test.go index e2ec36876..51c44a12c 100644 --- a/types/address_test.go +++ b/types/address_test.go @@ -9,6 +9,8 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" + "strings" + "github.com/cosmos/cosmos-sdk/types" ) @@ -178,3 +180,44 @@ func TestConsAddress(t *testing.T) { require.NotNil(t, err) } } + +const letterBytes = "abcdefghijklmnopqrstuvwxyz" + +func RandString(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Intn(len(letterBytes))] + } + return string(b) +} + +func TestConfiguredPrefix(t *testing.T) { + var pub ed25519.PubKeyEd25519 + for length := 1; length < 10; length++ { + for times := 1; times < 20; times++ { + rand.Read(pub[:]) + // Test if randomly generated prefix of a given length works + prefix := RandString(length) + // Assuming that GetConfig is not sealed. + config := types.GetConfig() + config.SetBech32PrefixForAccount(prefix+"acc", prefix+"pub") + acc := types.AccAddress(pub.Address()) + require.True(t, strings.HasPrefix(acc.String(), prefix+"acc")) + bech32Pub := types.MustBech32ifyAccPub(pub) + require.True(t, strings.HasPrefix(bech32Pub, prefix+"pub")) + + config.SetBech32PrefixForValidator(prefix+"valaddr", prefix+"valpub") + val := types.ValAddress(pub.Address()) + require.True(t, strings.HasPrefix(val.String(), prefix+"valaddr")) + bech32ValPub := types.MustBech32ifyValPub(pub) + require.True(t, strings.HasPrefix(bech32ValPub, prefix+"valpub")) + + config.SetBech32PrefixForConsensusNode(prefix+"consaddr", prefix+"conspub") + cons := types.ConsAddress(pub.Address()) + require.True(t, strings.HasPrefix(cons.String(), prefix+"consaddr")) + bech32ConsPub := types.MustBech32ifyConsPub(pub) + require.True(t, strings.HasPrefix(bech32ConsPub, prefix+"conspub")) + } + + } +} diff --git a/types/coin.go b/types/coin.go index d7484a669..31f0e98a4 100644 --- a/types/coin.go +++ b/types/coin.go @@ -49,7 +49,7 @@ func (coin Coin) IsGTE(other Coin) bool { // IsLT returns true if they are the same type and the receiver is // a smaller value func (coin Coin) IsLT(other Coin) bool { - return !coin.IsGTE(other) + return coin.SameDenomAs(other) && coin.Amount.LT(other.Amount) } // IsEqual returns true if the two sets of Coins have the same value @@ -142,7 +142,11 @@ func (coins Coins) Plus(coinsB Coins) Coins { coinA, coinB := coins[indexA], coinsB[indexB] switch strings.Compare(coinA.Denom, coinB.Denom) { case -1: - sum = append(sum, coinA) + if coinA.IsZero() { + // ignore 0 sum coin type + } else { + sum = append(sum, coinA) + } indexA++ case 0: if coinA.Amount.Add(coinB.Amount).IsZero() { @@ -153,7 +157,11 @@ func (coins Coins) Plus(coinsB Coins) Coins { indexA++ indexB++ case 1: - sum = append(sum, coinB) + if coinB.IsZero() { + // ignore 0 sum coin type + } else { + sum = append(sum, coinB) + } indexB++ } } @@ -176,10 +184,19 @@ func (coins Coins) Minus(coinsB Coins) Coins { return coins.Plus(coinsB.Negative()) } -// IsGTE returns True iff coins is NonNegative(), and for every -// currency in coinsB, the currency is present at an equal or greater -// amount in coinsB -func (coins Coins) IsGTE(coinsB Coins) bool { +// IsAllGT returns True iff for every denom in coins, the denom is present at a +// greater amount in coinsB. +func (coins Coins) IsAllGT(coinsB Coins) bool { + diff := coins.Minus(coinsB) + if len(diff) == 0 { + return false + } + return diff.IsPositive() +} + +// IsAllGTE returns True iff for every denom in coins, the denom is present at an +// equal or greater amount in coinsB. +func (coins Coins) IsAllGTE(coinsB Coins) bool { diff := coins.Minus(coinsB) if len(diff) == 0 { return true @@ -187,14 +204,27 @@ func (coins Coins) IsGTE(coinsB Coins) bool { return diff.IsNotNegative() } -// IsLT returns True iff every currency in coins, the currency is -// present at a smaller amount in coins -func (coins Coins) IsLT(coinsB Coins) bool { - return !coins.IsGTE(coinsB) +// IsAllLT returns True iff for every denom in coins, the denom is present at +// a smaller amount in coinsB. +func (coins Coins) IsAllLT(coinsB Coins) bool { + diff := coinsB.Minus(coins) + if len(diff) == 0 { + return false + } + return diff.IsPositive() } -// IsZero returns true if there are no coins -// or all coins are zero. +// IsAllLTE returns True iff for every denom in coins, the denom is present at +// a smaller or equal amount in coinsB. +func (coins Coins) IsAllLTE(coinsB Coins) bool { + diff := coinsB.Minus(coins) + if len(diff) == 0 { + return true + } + return diff.IsNotNegative() +} + +// IsZero returns true if there are no coins or all coins are zero. func (coins Coins) IsZero() bool { for _, coin := range coins { if !coin.IsZero() { diff --git a/types/coin_test.go b/types/coin_test.go index bc0441279..ac9fcb391 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -86,7 +86,10 @@ func TestIsLTCoin(t *testing.T) { {NewInt64Coin("A", 1), NewInt64Coin("A", 1), false}, {NewInt64Coin("A", 2), NewInt64Coin("A", 1), false}, {NewInt64Coin("A", -1), NewInt64Coin("A", 5), true}, - {NewInt64Coin("a", 0), NewInt64Coin("b", 1), true}, + {NewInt64Coin("a", 0), NewInt64Coin("b", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("a", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("a", 2), true}, } for tcIndex, tc := range cases { @@ -245,9 +248,9 @@ func TestCoins(t *testing.T) { assert.True(t, good.IsValid(), "Coins are valid") assert.True(t, good.IsPositive(), "Expected coins to be positive: %v", good) assert.False(t, null.IsPositive(), "Expected coins to not be positive: %v", null) - assert.True(t, good.IsGTE(empty), "Expected %v to be >= %v", good, empty) - assert.False(t, good.IsLT(empty), "Expected %v to be < %v", good, empty) - assert.True(t, empty.IsLT(good), "Expected %v to be < %v", empty, good) + assert.True(t, good.IsAllGTE(empty), "Expected %v to be >= %v", good, empty) + assert.False(t, good.IsAllLT(empty), "Expected %v to be < %v", good, empty) + assert.True(t, empty.IsAllLT(good), "Expected %v to be < %v", empty, good) assert.False(t, neg.IsPositive(), "Expected neg coins to not be positive: %v", neg) assert.Zero(t, len(sum), "Expected 0 coins") assert.False(t, badSort1.IsValid(), "Coins are not sorted") @@ -257,6 +260,60 @@ func TestCoins(t *testing.T) { } +func TestCoinsGT(t *testing.T) { + one := NewInt(1) + two := NewInt(2) + + assert.False(t, Coins{}.IsAllGT(Coins{})) + assert.True(t, Coins{{"A", one}}.IsAllGT(Coins{})) + assert.False(t, Coins{{"A", one}}.IsAllGT(Coins{{"A", one}})) + assert.False(t, Coins{{"A", one}}.IsAllGT(Coins{{"B", one}})) + assert.True(t, Coins{{"A", one}, {"B", one}}.IsAllGT(Coins{{"B", one}})) + assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllGT(Coins{{"B", two}})) +} + +func TestCoinsGTE(t *testing.T) { + one := NewInt(1) + two := NewInt(2) + + assert.True(t, Coins{}.IsAllGTE(Coins{})) + assert.True(t, Coins{{"A", one}}.IsAllGTE(Coins{})) + assert.True(t, Coins{{"A", one}}.IsAllGTE(Coins{{"A", one}})) + assert.False(t, Coins{{"A", one}}.IsAllGTE(Coins{{"B", one}})) + assert.True(t, Coins{{"A", one}, {"B", one}}.IsAllGTE(Coins{{"B", one}})) + assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllGTE(Coins{{"B", two}})) +} + +func TestCoinsLT(t *testing.T) { + one := NewInt(1) + two := NewInt(2) + + assert.False(t, Coins{}.IsAllLT(Coins{})) + assert.False(t, Coins{{"A", one}}.IsAllLT(Coins{})) + assert.False(t, Coins{{"A", one}}.IsAllLT(Coins{{"A", one}})) + assert.False(t, Coins{{"A", one}}.IsAllLT(Coins{{"B", one}})) + assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllLT(Coins{{"B", one}})) + assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllLT(Coins{{"B", two}})) + assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllLT(Coins{{"A", one}, {"B", one}})) + assert.True(t, Coins{{"A", one}, {"B", one}}.IsAllLT(Coins{{"A", one}, {"B", two}})) + assert.True(t, Coins{}.IsAllLT(Coins{{"A", one}})) +} + +func TestCoinsLTE(t *testing.T) { + one := NewInt(1) + two := NewInt(2) + + assert.True(t, Coins{}.IsAllLTE(Coins{})) + assert.False(t, Coins{{"A", one}}.IsAllLTE(Coins{})) + assert.True(t, Coins{{"A", one}}.IsAllLTE(Coins{{"A", one}})) + assert.False(t, Coins{{"A", one}}.IsAllLTE(Coins{{"B", one}})) + assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllLTE(Coins{{"B", one}})) + assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllLTE(Coins{{"B", two}})) + assert.True(t, Coins{{"A", one}, {"B", one}}.IsAllLTE(Coins{{"A", one}, {"B", one}})) + assert.True(t, Coins{{"A", one}, {"B", one}}.IsAllLTE(Coins{{"A", one}, {"B", two}})) + assert.True(t, Coins{}.IsAllLTE(Coins{{"A", one}})) +} + func TestPlusCoins(t *testing.T) { one := NewInt(1) zero := NewInt(0) diff --git a/types/config.go b/types/config.go new file mode 100644 index 000000000..da1a387fe --- /dev/null +++ b/types/config.go @@ -0,0 +1,105 @@ +package types + +import ( + "sync" +) + +// Config is the structure that holds the SDK configuration parameters. +// This could be used to initialize certain configuration parameters for the SDK. +type Config struct { + mtx sync.RWMutex + sealed bool + bech32AddressPrefix map[string]string +} + +var ( + // Initializing an instance of Config + sdkConfig = &Config{ + sealed: false, + bech32AddressPrefix: map[string]string{ + "account_addr": Bech32PrefixAccAddr, + "validator_addr": Bech32PrefixValAddr, + "consensus_addr": Bech32PrefixConsAddr, + "account_pub": Bech32PrefixAccPub, + "validator_pub": Bech32PrefixValPub, + "consensus_pub": Bech32PrefixConsPub, + }, + } +) + +// GetConfig returns the config instance for the SDK. +func GetConfig() *Config { + return sdkConfig +} + +func (config *Config) assertNotSealed() { + config.mtx.Lock() + defer config.mtx.Unlock() + + if config.sealed { + panic("Config is sealed") + } +} + +// SetBech32PrefixForAccount builds the Config with Bech32 addressPrefix and publKeyPrefix for accounts +// and returns the config instance +func (config *Config) SetBech32PrefixForAccount(addressPrefix, pubKeyPrefix string) { + config.assertNotSealed() + config.bech32AddressPrefix["account_addr"] = addressPrefix + config.bech32AddressPrefix["account_pub"] = pubKeyPrefix +} + +// SetBech32PrefixForValidator builds the Config with Bech32 addressPrefix and publKeyPrefix for validators +// and returns the config instance +func (config *Config) SetBech32PrefixForValidator(addressPrefix, pubKeyPrefix string) { + config.assertNotSealed() + config.bech32AddressPrefix["validator_addr"] = addressPrefix + config.bech32AddressPrefix["validator_pub"] = pubKeyPrefix +} + +// SetBech32PrefixForConsensusNode builds the Config with Bech32 addressPrefix and publKeyPrefix for consensus nodes +// and returns the config instance +func (config *Config) SetBech32PrefixForConsensusNode(addressPrefix, pubKeyPrefix string) { + config.assertNotSealed() + config.bech32AddressPrefix["consensus_addr"] = addressPrefix + config.bech32AddressPrefix["consensus_pub"] = pubKeyPrefix +} + +// Seal seals the config such that the config state could not be modified further +func (config *Config) Seal() *Config { + config.mtx.Lock() + defer config.mtx.Unlock() + + config.sealed = true + return config +} + +// GetBech32AccountAddrPrefix returns the Bech32 prefix for account address +func (config *Config) GetBech32AccountAddrPrefix() string { + return config.bech32AddressPrefix["account_addr"] +} + +// GetBech32ValidatorAddrPrefix returns the Bech32 prefix for validator address +func (config *Config) GetBech32ValidatorAddrPrefix() string { + return config.bech32AddressPrefix["validator_addr"] +} + +// GetBech32ConsensusAddrPrefix returns the Bech32 prefix for consensus node address +func (config *Config) GetBech32ConsensusAddrPrefix() string { + return config.bech32AddressPrefix["consensus_addr"] +} + +// GetBech32AccountPubPrefix returns the Bech32 prefix for account public key +func (config *Config) GetBech32AccountPubPrefix() string { + return config.bech32AddressPrefix["account_pub"] +} + +// GetBech32ValidatorPubPrefix returns the Bech32 prefix for validator public key +func (config *Config) GetBech32ValidatorPubPrefix() string { + return config.bech32AddressPrefix["validator_pub"] +} + +// GetBech32ConsensusPubPrefix returns the Bech32 prefix for consensus node public key +func (config *Config) GetBech32ConsensusPubPrefix() string { + return config.bech32AddressPrefix["consensus_pub"] +} diff --git a/types/decimal_test.go b/types/decimal_test.go index 07329c7dc..b35894771 100644 --- a/types/decimal_test.go +++ b/types/decimal_test.go @@ -305,11 +305,11 @@ func TestSerializationGocodecJSON(t *testing.T) { func TestSerializationGocodecBinary(t *testing.T) { d := mustNewDecFromStr(t, "0.333") - bz, err := cdc.MarshalBinary(d) + bz, err := cdc.MarshalBinaryLengthPrefixed(d) require.NoError(t, err) var d2 Dec - err = cdc.UnmarshalBinary(bz, &d2) + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &d2) require.NoError(t, err) require.True(t, d.Equal(d2), "original: %v, unmarshalled: %v", d, d2) } @@ -323,11 +323,11 @@ type testDEmbedStruct struct { // TODO make work for UnmarshalJSON func TestEmbeddedStructSerializationGocodec(t *testing.T) { obj := testDEmbedStruct{"foo", 10, NewDecWithPrec(1, 3)} - bz, err := cdc.MarshalBinary(obj) + bz, err := cdc.MarshalBinaryLengthPrefixed(obj) require.Nil(t, err) var obj2 testDEmbedStruct - err = cdc.UnmarshalBinary(bz, &obj2) + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &obj2) require.Nil(t, err) require.Equal(t, obj.Field1, obj2.Field1) diff --git a/types/stake.go b/types/stake.go index f5d3a4aae..38f1c0c9b 100644 --- a/types/stake.go +++ b/types/stake.go @@ -64,7 +64,11 @@ type ValidatorSet interface { func(index int64, validator Validator) (stop bool)) // iterate through bonded validators by operator address, execute func for each validator - IterateValidatorsBonded(Context, + IterateBondedValidatorsByPower(Context, + func(index int64, validator Validator) (stop bool)) + + // iterate through the consensus validator set of the last block by operator address, execute func for each validator + IterateLastValidators(Context, func(index int64, validator Validator) (stop bool)) Validator(Context, ValAddress) Validator // get a particular validator by operator address @@ -111,9 +115,9 @@ type DelegationSet interface { // event hooks for staking validator object type StakingHooks interface { - OnValidatorCreated(ctx Context, valAddr ValAddress) // Must be called when a validator is created - OnValidatorModified(ctx Context, valAddr ValAddress) // Must be called when a validator's state changes - OnValidatorRemoved(ctx Context, valAddr ValAddress) // Must be called when a validator is deleted + OnValidatorCreated(ctx Context, valAddr ValAddress) // Must be called when a validator is created + OnValidatorModified(ctx Context, valAddr ValAddress) // Must be called when a validator's state changes + OnValidatorRemoved(ctx Context, consAddr ConsAddress, valAddr ValAddress) // Must be called when a validator is deleted OnValidatorBonded(ctx Context, consAddr ConsAddress, valAddr ValAddress) // Must be called when a validator is bonded OnValidatorBeginUnbonding(ctx Context, consAddr ConsAddress, valAddr ValAddress) // Must be called when a validator begins unbonding diff --git a/types/store.go b/types/store.go index 7bab93b97..4b6e79a76 100644 --- a/types/store.go +++ b/types/store.go @@ -1,6 +1,7 @@ package types import ( + "bytes" "fmt" "io" @@ -127,7 +128,7 @@ type KVStore interface { // Has checks if a key exists. Panics on nil key. Has(key []byte) bool - // Set sets the key. Panics on nil key. + // Set sets the key. Panics on nil key or value. Set(key, value []byte) // Delete deletes the key. Panics on nil key. @@ -176,6 +177,43 @@ func KVStoreReversePrefixIterator(kvs KVStore, prefix []byte) Iterator { return kvs.ReverseIterator(prefix, PrefixEndBytes(prefix)) } +// Compare two KVstores, return either the first key/value pair +// at which they differ and whether or not they are equal, skipping +// value comparison for a set of provided prefixes +func DiffKVStores(a KVStore, b KVStore, prefixesToSkip [][]byte) (kvA cmn.KVPair, kvB cmn.KVPair, count int64, equal bool) { + iterA := a.Iterator(nil, nil) + iterB := b.Iterator(nil, nil) + count = int64(0) + for { + if !iterA.Valid() && !iterB.Valid() { + break + } + var kvA, kvB cmn.KVPair + if iterA.Valid() { + kvA = cmn.KVPair{Key: iterA.Key(), Value: iterA.Value()} + iterA.Next() + } + if iterB.Valid() { + kvB = cmn.KVPair{Key: iterB.Key(), Value: iterB.Value()} + iterB.Next() + } + compareValue := true + for _, prefix := range prefixesToSkip { + if bytes.Equal(kvA.Key[:len(prefix)], prefix) { + compareValue = false + } + } + if !bytes.Equal(kvA.Key, kvB.Key) { + return kvA, kvB, count, false + } + if compareValue && !bytes.Equal(kvA.Value, kvB.Value) { + return kvA, kvB, count, false + } + count++ + } + return cmn.KVPair{}, cmn.KVPair{}, count, true +} + // CacheKVStore cache-wraps a KVStore. After calling .Write() on // the CacheKVStore, all previously created CacheKVStores on the // object expire. diff --git a/types/utils.go b/types/utils.go index 10ec85472..91cdcf9b0 100644 --- a/types/utils.go +++ b/types/utils.go @@ -1,6 +1,7 @@ package types import ( + "encoding/binary" "encoding/json" "time" @@ -36,6 +37,13 @@ func MustSortJSON(toSortJSON []byte) []byte { return js } +// Uint64ToBigEndian - marshals uint64 to a bigendian byte slice so it can be sorted +func Uint64ToBigEndian(i uint64) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, i) + return b +} + // Slight modification of the RFC3339Nano but it right pads all zeros and drops the time zone info const SortableTimeFormat = "2006-01-02T15:04:05.000000000" diff --git a/x/auth/account_test.go b/x/auth/account_test.go index b7a78e2d2..e48060fbe 100644 --- a/x/auth/account_test.go +++ b/x/auth/account_test.go @@ -93,16 +93,16 @@ func TestBaseAccountMarshal(t *testing.T) { cdc := codec.New() codec.RegisterCrypto(cdc) - b, err := cdc.MarshalBinary(acc) + b, err := cdc.MarshalBinaryLengthPrefixed(acc) require.Nil(t, err) acc2 := BaseAccount{} - err = cdc.UnmarshalBinary(b, &acc2) + err = cdc.UnmarshalBinaryLengthPrefixed(b, &acc2) require.Nil(t, err) require.Equal(t, acc, acc2) // error on bad bytes acc2 = BaseAccount{} - err = cdc.UnmarshalBinary(b[:len(b)/2], &acc2) + err = cdc.UnmarshalBinaryLengthPrefixed(b[:len(b)/2], &acc2) require.NotNil(t, err) } diff --git a/x/auth/ante.go b/x/auth/ante.go index 9d5bc5059..e88a20a43 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -282,7 +282,8 @@ func ensureSufficientMempoolFees(ctx sdk.Context, stdTx StdTx) sdk.Result { // TODO: Make the gasPrice not a constant, and account for tx size. requiredFees := adjustFeesByGas(ctx.MinimumFees(), stdTx.Fee.Gas) - if !ctx.MinimumFees().IsZero() && stdTx.Fee.Amount.IsLT(requiredFees) { + // NOTE: !A.IsAllGTE(B) is not the same as A.IsAllLT(B). + if !ctx.MinimumFees().IsZero() && !stdTx.Fee.Amount.IsAllGTE(requiredFees) { // validators reject any tx from the mempool with less than the minimum fee per gas * gas factor return sdk.ErrInsufficientFee(fmt.Sprintf( "insufficient fee, got: %q required: %q", stdTx.Fee.Amount, requiredFees)).Result() diff --git a/x/auth/client/cli/sign.go b/x/auth/client/cli/sign.go index 67c64dc2a..f4a4548d4 100644 --- a/x/auth/client/cli/sign.go +++ b/x/auth/client/cli/sign.go @@ -2,9 +2,9 @@ package cli import ( "fmt" - "io/ioutil" - + "github.com/pkg/errors" "github.com/spf13/viper" + "io/ioutil" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" @@ -13,13 +13,14 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/spf13/cobra" - amino "github.com/tendermint/go-amino" + "github.com/tendermint/go-amino" ) const ( - flagAppend = "append" - flagPrintSigs = "print-sigs" - flagOffline = "offline" + flagAppend = "append" + flagValidateSigs = "validate-signatures" + flagOffline = "offline" + flagSigOnly = "signature-only" ) // GetSignCommand returns the sign command @@ -30,6 +31,13 @@ func GetSignCommand(codec *amino.Codec, decoder auth.AccountDecoder) *cobra.Comm Long: `Sign transactions created with the --generate-only flag. Read a transaction from , sign it, and print its JSON encoding. +If the flag --signature-only flag is on, it outputs a JSON representation +of the generated signature only. + +If the flag --validate-signatures is on, then the command would check whether all required +signers have signed the transactions and whether the signatures were collected in the right +order. + The --offline flag makes sure that the client will not reach out to the local cache. Thus account number or sequence number lookups will not be performed and it is recommended to set such parameters manually.`, @@ -37,8 +45,11 @@ recommended to set such parameters manually.`, Args: cobra.ExactArgs(1), } cmd.Flags().String(client.FlagName, "", "Name of private key with which to sign") - cmd.Flags().Bool(flagAppend, true, "Append the signature to the existing ones. If disabled, old signatures would be overwritten") - cmd.Flags().Bool(flagPrintSigs, false, "Print the addresses that must sign the transaction and those who have already signed it, then exit") + cmd.Flags().Bool(flagAppend, true, + "Append the signature to the existing ones. If disabled, old signatures would be overwritten") + cmd.Flags().Bool(flagSigOnly, false, "Print only the generated signature, then exit.") + cmd.Flags().Bool(flagValidateSigs, false, "Print the addresses that must sign the transaction, "+ + "those who have already signed it, and make sure that signatures are in the correct order.") cmd.Flags().Bool(flagOffline, false, "Offline mode. Do not query local cache.") return cmd } @@ -50,24 +61,45 @@ func makeSignCmd(cdc *amino.Codec, decoder auth.AccountDecoder) func(cmd *cobra. return } - if viper.GetBool(flagPrintSigs) { - printSignatures(stdTx) + if viper.GetBool(flagValidateSigs) { + if !printSignatures(stdTx) { + return fmt.Errorf("signatures validation failed") + } return nil } name := viper.GetString(client.FlagName) + if name == "" { + return errors.New("required flag \"name\" has not been set") + } cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(decoder) txBldr := authtxb.NewTxBuilderFromCLI() - newTx, err := utils.SignStdTx(txBldr, cliCtx, name, stdTx, viper.GetBool(flagAppend), viper.GetBool(flagOffline)) + // if --signature-only is on, then override --append + generateSignatureOnly := viper.GetBool(flagSigOnly) + appendSig := viper.GetBool(flagAppend) && !generateSignatureOnly + newTx, err := utils.SignStdTx(txBldr, cliCtx, name, stdTx, appendSig, viper.GetBool(flagOffline)) if err != nil { return err } + var json []byte - if cliCtx.Indent { - json, err = cdc.MarshalJSONIndent(newTx, "", " ") - } else { - json, err = cdc.MarshalJSON(newTx) + + switch generateSignatureOnly { + case true: + switch cliCtx.Indent { + case true: + json, err = cdc.MarshalJSONIndent(newTx.Signatures[0], "", " ") + default: + json, err = cdc.MarshalJSON(newTx.Signatures[0]) + } + default: + switch cliCtx.Indent { + case true: + json, err = cdc.MarshalJSONIndent(newTx, "", " ") + default: + json, err = cdc.MarshalJSON(newTx) + } } if err != nil { return err @@ -77,17 +109,31 @@ func makeSignCmd(cdc *amino.Codec, decoder auth.AccountDecoder) func(cmd *cobra. } } -func printSignatures(stdTx auth.StdTx) { +func printSignatures(stdTx auth.StdTx) bool { fmt.Println("Signers:") - for i, signer := range stdTx.GetSigners() { + signers := stdTx.GetSigners() + for i, signer := range signers { fmt.Printf(" %v: %v\n", i, signer.String()) } + + sigs := stdTx.GetSignatures() fmt.Println("") fmt.Println("Signatures:") - for i, sig := range stdTx.GetSignatures() { - fmt.Printf(" %v: %v\n", i, sdk.AccAddress(sig.Address()).String()) + success := true + if len(sigs) != len(signers) { + success = false } - return + for i, sig := range stdTx.GetSignatures() { + sigAddr := sdk.AccAddress(sig.Address()) + sigSanity := "OK" + if i >= len(signers) || !sigAddr.Equals(signers[i]) { + sigSanity = fmt.Sprintf("ERROR: signature %d does not match its respective signer", i) + success = false + } + fmt.Printf(" %v: %v\t[%s]\n", i, sigAddr.String(), sigSanity) + } + fmt.Println("") + return success } func readAndUnmarshalStdTx(cdc *amino.Codec, filename string) (stdTx auth.StdTx, err error) { diff --git a/x/auth/client/txbuilder/txbuilder.go b/x/auth/client/txbuilder/txbuilder.go index b488ef359..f516de451 100644 --- a/x/auth/client/txbuilder/txbuilder.go +++ b/x/auth/client/txbuilder/txbuilder.go @@ -125,7 +125,8 @@ func (bldr TxBuilder) Sign(name, passphrase string, msg StdSignMsg) ([]byte, err if err != nil { return nil, err } - return bldr.Codec.MarshalBinary(auth.NewStdTx(msg.Msgs, msg.Fee, []auth.StdSignature{sig}, msg.Memo)) + + return bldr.Codec.MarshalBinaryLengthPrefixed(auth.NewStdTx(msg.Msgs, msg.Fee, []auth.StdSignature{sig}, msg.Memo)) } // BuildAndSign builds a single message to be signed, and signs a transaction @@ -166,7 +167,7 @@ func (bldr TxBuilder) BuildWithPubKey(name string, msgs []sdk.Msg) ([]byte, erro PubKey: info.GetPubKey(), }} - return bldr.Codec.MarshalBinary(auth.NewStdTx(msg.Msgs, msg.Fee, sigs, msg.Memo)) + return bldr.Codec.MarshalBinaryLengthPrefixed(auth.NewStdTx(msg.Msgs, msg.Fee, sigs, msg.Memo)) } // SignStdTx appends a signature to a StdTx and returns a copy of a it. If append diff --git a/x/auth/feekeeper.go b/x/auth/feekeeper.go index a6be2e12d..0006a9185 100644 --- a/x/auth/feekeeper.go +++ b/x/auth/feekeeper.go @@ -36,12 +36,12 @@ func (fck FeeCollectionKeeper) GetCollectedFees(ctx sdk.Context) sdk.Coins { } feePool := &(sdk.Coins{}) - fck.cdc.MustUnmarshalBinary(bz, feePool) + fck.cdc.MustUnmarshalBinaryLengthPrefixed(bz, feePool) return *feePool } func (fck FeeCollectionKeeper) setCollectedFees(ctx sdk.Context, coins sdk.Coins) { - bz := fck.cdc.MustMarshalBinary(coins) + bz := fck.cdc.MustMarshalBinaryLengthPrefixed(coins) store := ctx.KVStore(fck.key) store.Set(collectedFeesKey, bz) } diff --git a/x/auth/genesis.go b/x/auth/genesis.go new file mode 100644 index 000000000..abc4fc3ae --- /dev/null +++ b/x/auth/genesis.go @@ -0,0 +1,33 @@ +package auth + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GenesisState - all auth state that must be provided at genesis +type GenesisState struct { + CollectedFees sdk.Coins `json:"collected_fees"` // collected fees +} + +// Create a new genesis state +func NewGenesisState(collectedFees sdk.Coins) GenesisState { + return GenesisState{ + CollectedFees: collectedFees, + } +} + +// Return a default genesis state +func DefaultGenesisState() GenesisState { + return NewGenesisState(sdk.Coins{}) +} + +// Init store state from genesis data +func InitGenesis(ctx sdk.Context, keeper FeeCollectionKeeper, data GenesisState) { + keeper.setCollectedFees(ctx, data.CollectedFees) +} + +// ExportGenesis returns a GenesisState for a given context and keeper +func ExportGenesis(ctx sdk.Context, keeper FeeCollectionKeeper) GenesisState { + collectedFees := keeper.GetCollectedFees(ctx) + return NewGenesisState(collectedFees) +} diff --git a/x/auth/mapper.go b/x/auth/keeper.go similarity index 97% rename from x/auth/mapper.go rename to x/auth/keeper.go index dd127c731..da5481749 100644 --- a/x/auth/mapper.go +++ b/x/auth/keeper.go @@ -148,13 +148,13 @@ func (am AccountKeeper) GetNextAccountNumber(ctx sdk.Context) int64 { if bz == nil { accNumber = 0 } else { - err := am.cdc.UnmarshalBinary(bz, &accNumber) + err := am.cdc.UnmarshalBinaryLengthPrefixed(bz, &accNumber) if err != nil { panic(err) } } - bz = am.cdc.MustMarshalBinary(accNumber + 1) + bz = am.cdc.MustMarshalBinaryLengthPrefixed(accNumber + 1) store.Set(globalAccountNumberKey, bz) return accNumber diff --git a/x/auth/mapper_test.go b/x/auth/keeper_test.go similarity index 58% rename from x/auth/mapper_test.go rename to x/auth/keeper_test.go index 326f83dcc..f1f4c7eaf 100644 --- a/x/auth/mapper_test.go +++ b/x/auth/keeper_test.go @@ -3,15 +3,13 @@ package auth import ( "testing" - "github.com/stretchr/testify/require" - - abci "github.com/tendermint/tendermint/abci/types" - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" - codec "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" ) func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) { @@ -115,9 +113,92 @@ func BenchmarkAccountMapperGetAccountFound(b *testing.B) { acc := mapper.NewAccountWithAddress(ctx, addr) mapper.SetAccount(ctx, acc) } + b.ResetTimer() for i := 0; i < b.N; i++ { arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} mapper.GetAccount(ctx, sdk.AccAddress(arr)) } } + +func BenchmarkAccountMapperGetAccountFoundWithCoins(b *testing.B) { + ms, capKey, _ := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + + // make context and mapper + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + + coins := sdk.Coins{ + sdk.NewCoin("LTC", sdk.NewInt(1000)), + sdk.NewCoin("BTC", sdk.NewInt(1000)), + sdk.NewCoin("ETH", sdk.NewInt(1000)), + sdk.NewCoin("XRP", sdk.NewInt(1000)), + sdk.NewCoin("BCH", sdk.NewInt(1000)), + sdk.NewCoin("EOS", sdk.NewInt(1000)), + } + + // assumes b.N < 2**24 + for i := 0; i < b.N; i++ { + arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} + addr := sdk.AccAddress(arr) + acc := mapper.NewAccountWithAddress(ctx, addr) + acc.SetCoins(coins) + mapper.SetAccount(ctx, acc) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} + mapper.GetAccount(ctx, sdk.AccAddress(arr)) + } +} + +func BenchmarkAccountMapperSetAccount(b *testing.B) { + ms, capKey, _ := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + + // make context and mapper + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + + b.ResetTimer() + // assumes b.N < 2**24 + for i := 0; i < b.N; i++ { + arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} + addr := sdk.AccAddress(arr) + acc := mapper.NewAccountWithAddress(ctx, addr) + mapper.SetAccount(ctx, acc) + } +} + +func BenchmarkAccountMapperSetAccountWithCoins(b *testing.B) { + ms, capKey, _ := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + + // make context and mapper + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + + coins := sdk.Coins{ + sdk.NewCoin("LTC", sdk.NewInt(1000)), + sdk.NewCoin("BTC", sdk.NewInt(1000)), + sdk.NewCoin("ETH", sdk.NewInt(1000)), + sdk.NewCoin("XRP", sdk.NewInt(1000)), + sdk.NewCoin("BCH", sdk.NewInt(1000)), + sdk.NewCoin("EOS", sdk.NewInt(1000)), + } + + b.ResetTimer() + // assumes b.N < 2**24 + for i := 0; i < b.N; i++ { + arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} + addr := sdk.AccAddress(arr) + acc := mapper.NewAccountWithAddress(ctx, addr) + acc.SetCoins(coins) + mapper.SetAccount(ctx, acc) + } +} diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index ca6eb7b42..84bf88a4b 100644 --- a/x/auth/stdtx.go +++ b/x/auth/stdtx.go @@ -151,10 +151,11 @@ func DefaultTxDecoder(cdc *codec.Codec) sdk.TxDecoder { // StdTx.Msg is an interface. The concrete types // are registered by MakeTxCodec - err := cdc.UnmarshalBinary(txBytes, &tx) + err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) if err != nil { return nil, sdk.ErrTxDecode("").TraceSDK(err.Error()) } + return tx, nil } } diff --git a/x/bank/client/cli/broadcast.go b/x/bank/client/cli/broadcast.go index e7e6bc16d..dd045439e 100644 --- a/x/bank/client/cli/broadcast.go +++ b/x/bank/client/cli/broadcast.go @@ -25,7 +25,8 @@ in place of an input filename, the command reads from standard input.`, if err != nil { return } - txBytes, err := cliCtx.Codec.MarshalBinary(stdTx) + + txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(stdTx) if err != nil { return } diff --git a/x/bank/client/cli/sendtx.go b/x/bank/client/cli/sendtx.go index c72ed6fcb..bae217976 100644 --- a/x/bank/client/cli/sendtx.go +++ b/x/bank/client/cli/sendtx.go @@ -59,7 +59,7 @@ func SendTxCmd(cdc *codec.Codec) *cobra.Command { } // ensure account has enough coins - if !account.GetCoins().IsGTE(coins) { + if !account.GetCoins().IsAllGTE(coins) { return errors.Errorf("Address %s doesn't have enough coins to pay for this transaction.", from) } diff --git a/x/bank/client/rest/broadcast.go b/x/bank/client/rest/broadcast.go index c52961caf..71696f12b 100644 --- a/x/bank/client/rest/broadcast.go +++ b/x/bank/client/rest/broadcast.go @@ -22,11 +22,12 @@ func BroadcastTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) ht return } - txBytes, err := cliCtx.Codec.MarshalBinary(m.Tx) + txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(m.Tx) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } + res, err := cliCtx.BroadcastTx(txBytes) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) diff --git a/x/bank/keeper.go b/x/bank/keeper.go index ac7a484d5..67c05d320 100644 --- a/x/bank/keeper.go +++ b/x/bank/keeper.go @@ -190,7 +190,7 @@ func setCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt s // HasCoins returns whether or not an account has at least amt coins. func hasCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) bool { ctx.GasMeter().ConsumeGas(costHasCoins, "hasCoins") - return getCoins(ctx, am, addr).IsGTE(amt) + return getCoins(ctx, am, addr).IsAllGTE(amt) } // SubtractCoins subtracts amt from the coins at the addr. diff --git a/x/bank/simulation/invariants.go b/x/bank/simulation/invariants.go index 6574407a4..f0cd5ba63 100644 --- a/x/bank/simulation/invariants.go +++ b/x/bank/simulation/invariants.go @@ -14,7 +14,7 @@ import ( // NonnegativeBalanceInvariant checks that all accounts in the application have non-negative balances func NonnegativeBalanceInvariant(mapper auth.AccountKeeper) simulation.Invariant { - return func(app *baseapp.BaseApp, _ abci.Header) error { + return func(app *baseapp.BaseApp) error { ctx := app.NewContext(false, abci.Header{}) accts := mock.GetAllAccounts(mapper, ctx) for _, acc := range accts { @@ -32,7 +32,7 @@ func NonnegativeBalanceInvariant(mapper auth.AccountKeeper) simulation.Invariant // TotalCoinsInvariant checks that the sum of the coins across all accounts // is what is expected func TotalCoinsInvariant(mapper auth.AccountKeeper, totalSupplyFn func() sdk.Coins) simulation.Invariant { - return func(app *baseapp.BaseApp, _ abci.Header) error { + return func(app *baseapp.BaseApp) error { ctx := app.NewContext(false, abci.Header{}) totalCoins := sdk.Coins{} diff --git a/x/distribution/genesis.go b/x/distribution/genesis.go index 4ee5651a6..346808916 100644 --- a/x/distribution/genesis.go +++ b/x/distribution/genesis.go @@ -21,11 +21,12 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) { for _, dw := range data.DelegatorWithdrawInfos { keeper.SetDelegatorWithdrawAddr(ctx, dw.DelegatorAddr, dw.WithdrawAddr) } + keeper.SetPreviousProposerConsAddr(ctx, data.PreviousProposer) } -// WriteGenesis returns a GenesisState for a given context and keeper. The +// ExportGenesis returns a GenesisState for a given context and keeper. The // GenesisState will contain the pool, and validator/delegator distribution info's -func WriteGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { +func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { feePool := keeper.GetFeePool(ctx) communityTax := keeper.GetCommunityTax(ctx) baseProposerRewards := keeper.GetBaseProposerReward(ctx) @@ -33,6 +34,7 @@ func WriteGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { vdis := keeper.GetAllValidatorDistInfos(ctx) ddis := keeper.GetAllDelegationDistInfos(ctx) dwis := keeper.GetAllDelegatorWithdrawInfos(ctx) + pp := keeper.GetPreviousProposerConsAddr(ctx) return NewGenesisState(feePool, communityTax, baseProposerRewards, - bonusProposerRewards, vdis, ddis, dwis) + bonusProposerRewards, vdis, ddis, dwis, pp) } diff --git a/x/distribution/keeper/delegation.go b/x/distribution/keeper/delegation.go index 4dcc31a51..ac519107d 100644 --- a/x/distribution/keeper/delegation.go +++ b/x/distribution/keeper/delegation.go @@ -23,14 +23,14 @@ func (k Keeper) GetDelegationDistInfo(ctx sdk.Context, delAddr sdk.AccAddress, panic("Stored delegation-distribution info should not have been nil") } - k.cdc.MustUnmarshalBinary(b, &ddi) + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &ddi) return } // set the delegator distribution info func (k Keeper) SetDelegationDistInfo(ctx sdk.Context, ddi types.DelegationDistInfo) { store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(ddi) + b := k.cdc.MustMarshalBinaryLengthPrefixed(ddi) store.Set(GetDelegationDistInfoKey(ddi.DelegatorAddr, ddi.ValOperatorAddr), b) } diff --git a/x/distribution/keeper/genesis.go b/x/distribution/keeper/genesis.go index 06b153a51..8e5a37abe 100644 --- a/x/distribution/keeper/genesis.go +++ b/x/distribution/keeper/genesis.go @@ -13,7 +13,7 @@ func (k Keeper) GetAllValidatorDistInfos(ctx sdk.Context) (vdis []types.Validato for ; iterator.Valid(); iterator.Next() { var vdi types.ValidatorDistInfo - k.cdc.MustUnmarshalBinary(iterator.Value(), &vdi) + k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &vdi) vdis = append(vdis, vdi) } return vdis @@ -27,7 +27,7 @@ func (k Keeper) GetAllDelegationDistInfos(ctx sdk.Context) (ddis []types.Delegat for ; iterator.Valid(); iterator.Next() { var ddi types.DelegationDistInfo - k.cdc.MustUnmarshalBinary(iterator.Value(), &ddi) + k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &ddi) ddis = append(ddis, ddi) } return ddis @@ -36,12 +36,12 @@ func (k Keeper) GetAllDelegationDistInfos(ctx sdk.Context) (ddis []types.Delegat // Get the set of all delegator-withdraw addresses with no limits, used during genesis dump func (k Keeper) GetAllDelegatorWithdrawInfos(ctx sdk.Context) (dwis []types.DelegatorWithdrawInfo) { store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, DelegationDistInfoKey) + iterator := sdk.KVStorePrefixIterator(store, DelegatorWithdrawInfoKey) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { dw := types.DelegatorWithdrawInfo{ - DelegatorAddr: sdk.AccAddress(iterator.Key()), + DelegatorAddr: GetDelegatorWithdrawInfoAddress(iterator.Key()), WithdrawAddr: sdk.AccAddress(iterator.Value()), } dwis = append(dwis, dw) diff --git a/x/distribution/keeper/hooks.go b/x/distribution/keeper/hooks.go index efd2c0610..a4f4353fa 100644 --- a/x/distribution/keeper/hooks.go +++ b/x/distribution/keeper/hooks.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/distribution/types" ) @@ -38,11 +40,12 @@ func (k Keeper) onValidatorBonded(ctx sdk.Context, valAddr sdk.ValAddress) { k.onValidatorModified(ctx, valAddr) } -// XXX Consider removing this after debugging. +// Sanity check, very useful! func (k Keeper) onValidatorPowerDidChange(ctx sdk.Context, valAddr sdk.ValAddress) { vi := k.GetValidatorDistInfo(ctx, valAddr) if vi.FeePoolWithdrawalHeight != ctx.BlockHeight() { - panic("expected validator dist info FeePoolWithdrawalHeight to be updated, but was not.") + panic(fmt.Sprintf("expected validator (%v) dist info FeePoolWithdrawalHeight to be updated to %v, but was %v.", + valAddr.String(), ctx.BlockHeight(), vi.FeePoolWithdrawalHeight)) } } @@ -100,7 +103,7 @@ func (h Hooks) OnValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { func (h Hooks) OnValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { h.k.onValidatorModified(ctx, valAddr) } -func (h Hooks) OnValidatorRemoved(ctx sdk.Context, valAddr sdk.ValAddress) { +func (h Hooks) OnValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { h.k.onValidatorRemoved(ctx, valAddr) } func (h Hooks) OnDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { diff --git a/x/distribution/keeper/keeper.go b/x/distribution/keeper/keeper.go index d167e0efe..a6fed9635 100644 --- a/x/distribution/keeper/keeper.go +++ b/x/distribution/keeper/keeper.go @@ -44,14 +44,14 @@ func (k Keeper) GetFeePool(ctx sdk.Context) (feePool types.FeePool) { if b == nil { panic("Stored fee pool should not have been nil") } - k.cdc.MustUnmarshalBinary(b, &feePool) + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &feePool) return } // set the global fee pool distribution info func (k Keeper) SetFeePool(ctx sdk.Context, feePool types.FeePool) { store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(feePool) + b := k.cdc.MustMarshalBinaryLengthPrefixed(feePool) store.Set(FeePoolKey, b) } @@ -77,14 +77,14 @@ func (k Keeper) GetPreviousProposerConsAddr(ctx sdk.Context) (consAddr sdk.ConsA panic("Previous proposer not set") } - k.cdc.MustUnmarshalBinary(b, &consAddr) + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &consAddr) return } // get the proposer public key for this block func (k Keeper) SetPreviousProposerConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) { store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(consAddr) + b := k.cdc.MustMarshalBinaryLengthPrefixed(consAddr) store.Set(ProposerKey, b) } diff --git a/x/distribution/keeper/key.go b/x/distribution/keeper/key.go index 466d83c24..443d13079 100644 --- a/x/distribution/keeper/key.go +++ b/x/distribution/keeper/key.go @@ -44,3 +44,12 @@ func GetDelegationDistInfosKey(delAddr sdk.AccAddress) []byte { func GetDelegatorWithdrawAddrKey(delAddr sdk.AccAddress) []byte { return append(DelegatorWithdrawInfoKey, delAddr.Bytes()...) } + +// gets an address from a delegator's withdraw info key +func GetDelegatorWithdrawInfoAddress(key []byte) (delAddr sdk.AccAddress) { + addr := key[1:] + if len(addr) != sdk.AddrLen { + panic("unexpected key length") + } + return sdk.AccAddress(addr) +} diff --git a/x/distribution/keeper/test_common.go b/x/distribution/keeper/test_common.go index b1bd4ae4e..660abbd0e 100644 --- a/x/distribution/keeper/test_common.go +++ b/x/distribution/keeper/test_common.go @@ -130,7 +130,7 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initCoins int64, keeper := NewKeeper(cdc, keyDistr, pk.Subspace(DefaultParamspace), ck, sk, fck, types.DefaultCodespace) // set the distribution hooks on staking - sk = sk.WithHooks(keeper.Hooks()) + sk.SetHooks(keeper.Hooks()) // set genesis items required for distribution keeper.SetFeePool(ctx, types.InitialFeePool()) @@ -172,7 +172,7 @@ func (k Keeper) IterateValidatorDistInfos(ctx sdk.Context, index := int64(0) for ; iter.Valid(); iter.Next() { var vdi types.ValidatorDistInfo - k.cdc.MustUnmarshalBinary(iter.Value(), &vdi) + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &vdi) if fn(index, vdi) { return } diff --git a/x/distribution/keeper/validator.go b/x/distribution/keeper/validator.go index d267d66b2..d2c755cfa 100644 --- a/x/distribution/keeper/validator.go +++ b/x/distribution/keeper/validator.go @@ -23,14 +23,14 @@ func (k Keeper) GetValidatorDistInfo(ctx sdk.Context, panic("Stored validator-distribution info should not have been nil") } - k.cdc.MustUnmarshalBinary(b, &vdi) + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &vdi) return } // set the validator distribution info func (k Keeper) SetValidatorDistInfo(ctx sdk.Context, vdi types.ValidatorDistInfo) { store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(vdi) + b := k.cdc.MustMarshalBinaryLengthPrefixed(vdi) store.Set(GetValidatorDistInfoKey(vdi.OperatorAddr), b) } diff --git a/x/distribution/simulation/invariants.go b/x/distribution/simulation/invariants.go index 45c015cf0..75863bfdb 100644 --- a/x/distribution/simulation/invariants.go +++ b/x/distribution/simulation/invariants.go @@ -13,9 +13,8 @@ import ( // AllInvariants runs all invariants of the distribution module // Currently: total supply, positive power func AllInvariants(d distr.Keeper, sk distr.StakeKeeper) simulation.Invariant { - - return func(app *baseapp.BaseApp, header abci.Header) error { - err := ValAccumInvariants(d, sk)(app, header) + return func(app *baseapp.BaseApp) error { + err := ValAccumInvariants(d, sk)(app) if err != nil { return err } @@ -26,8 +25,9 @@ func AllInvariants(d distr.Keeper, sk distr.StakeKeeper) simulation.Invariant { // ValAccumInvariants checks that the fee pool accum == sum all validators' accum func ValAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invariant { - return func(app *baseapp.BaseApp, header abci.Header) error { - ctx := app.NewContext(false, header) + return func(app *baseapp.BaseApp) error { + mockHeader := abci.Header{Height: app.LastBlockHeight() + 1} + ctx := app.NewContext(false, mockHeader) height := ctx.BlockHeight() valAccum := sdk.ZeroDec() diff --git a/x/distribution/types/genesis.go b/x/distribution/types/genesis.go index 528295e5c..0577af4d3 100644 --- a/x/distribution/types/genesis.go +++ b/x/distribution/types/genesis.go @@ -18,10 +18,11 @@ type GenesisState struct { ValidatorDistInfos []ValidatorDistInfo `json:"validator_dist_infos"` DelegationDistInfos []DelegationDistInfo `json:"delegator_dist_infos"` DelegatorWithdrawInfos []DelegatorWithdrawInfo `json:"delegator_withdraw_infos"` + PreviousProposer sdk.ConsAddress `json:"previous_proposer"` } func NewGenesisState(feePool FeePool, communityTax, baseProposerReward, bonusProposerReward sdk.Dec, - vdis []ValidatorDistInfo, ddis []DelegationDistInfo, dwis []DelegatorWithdrawInfo) GenesisState { + vdis []ValidatorDistInfo, ddis []DelegationDistInfo, dwis []DelegatorWithdrawInfo, pp sdk.ConsAddress) GenesisState { return GenesisState{ FeePool: feePool, @@ -31,6 +32,7 @@ func NewGenesisState(feePool FeePool, communityTax, baseProposerReward, bonusPro ValidatorDistInfos: vdis, DelegationDistInfos: ddis, DelegatorWithdrawInfos: dwis, + PreviousProposer: pp, } } diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 4b23002bd..455bd58db 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -21,17 +21,17 @@ import ( ) const ( - flagProposalID = "proposal-id" - flagTitle = "title" - flagDescription = "description" - flagProposalType = "type" - flagDeposit = "deposit" - flagVoter = "voter" - flagOption = "option" - flagDepositer = "depositer" - flagStatus = "status" - flagLatestProposalIDs = "latest" - flagProposal = "proposal" + flagProposalID = "proposal-id" + flagTitle = "title" + flagDescription = "description" + flagProposalType = "type" + flagDeposit = "deposit" + flagVoter = "voter" + flagOption = "option" + flagDepositer = "depositer" + flagStatus = "status" + flagNumLimit = "limit" + flagProposal = "proposal" ) type proposal struct { @@ -170,7 +170,7 @@ func GetCmdDeposit(cdc *codec.Codec) *cobra.Command { return err } - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) amount, err := sdk.ParseCoins(viper.GetString(flagDeposit)) if err != nil { @@ -215,7 +215,7 @@ func GetCmdVote(cdc *codec.Codec) *cobra.Command { return err } - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) option := viper.GetString(flagOption) byteVoteOption, err := gov.VoteOptionFromString(client.NormalizeVoteOption(option)) @@ -256,7 +256,7 @@ func GetCmdQueryProposal(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Query details of a single proposal", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) params := gov.QueryProposalParams{ ProposalID: proposalID, @@ -291,10 +291,10 @@ func GetCmdQueryProposals(queryRoute string, cdc *codec.Codec) *cobra.Command { bechDepositerAddr := viper.GetString(flagDepositer) bechVoterAddr := viper.GetString(flagVoter) strProposalStatus := viper.GetString(flagStatus) - latestProposalsIDs := viper.GetInt64(flagLatestProposalIDs) + numLimit := uint64(viper.GetInt64(flagNumLimit)) params := gov.QueryProposalsParams{ - NumLatestProposals: latestProposalsIDs, + Limit: numLimit, } if len(bechDepositerAddr) != 0 { @@ -352,7 +352,7 @@ func GetCmdQueryProposals(queryRoute string, cdc *codec.Codec) *cobra.Command { }, } - cmd.Flags().String(flagLatestProposalIDs, "", "(optional) limit to latest [number] proposals. Defaults to all proposals") + cmd.Flags().String(flagNumLimit, "", "(optional) limit to latest [number] proposals. Defaults to all proposals") cmd.Flags().String(flagDepositer, "", "(optional) filter by proposals deposited on by depositer") cmd.Flags().String(flagVoter, "", "(optional) filter by proposals voted on by voted") cmd.Flags().String(flagStatus, "", "(optional) filter proposals by proposal status, status: deposit_period/voting_period/passed/rejected") @@ -368,7 +368,7 @@ func GetCmdQueryVote(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Query details of a single vote", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) voterAddr, err := sdk.AccAddressFromBech32(viper.GetString(flagVoter)) if err != nil { @@ -407,7 +407,7 @@ func GetCmdQueryVotes(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Query votes on a proposal", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) params := gov.QueryVotesParams{ ProposalID: proposalID, @@ -440,7 +440,7 @@ func GetCmdQueryDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Query details of a deposit", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) depositerAddr, err := sdk.AccAddressFromBech32(viper.GetString(flagDepositer)) if err != nil { @@ -479,7 +479,7 @@ func GetCmdQueryDeposits(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Query deposits on a proposal", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) params := gov.QueryDepositsParams{ ProposalID: proposalID, @@ -511,7 +511,7 @@ func GetCmdQueryTally(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Get the tally of a proposal vote", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := viper.GetInt64(flagProposalID) + proposalID := uint64(viper.GetInt64(flagProposalID)) params := gov.QueryTallyParams{ ProposalID: proposalID, diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 374f5cc2a..f477f2ff9 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -22,7 +22,7 @@ const ( RestDepositer = "depositer" RestVoter = "voter" RestProposalStatus = "status" - RestNumLatest = "latest" + RestNumLimit = "limit" storeName = "gov" ) @@ -104,7 +104,7 @@ func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerF return } - proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -143,7 +143,7 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc return } - proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -188,7 +188,7 @@ func queryProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha return } - proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -218,7 +218,7 @@ func queryDepositsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha vars := mux.Vars(r) strProposalID := vars[RestProposalID] - proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -255,7 +255,7 @@ func queryDepositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han return } - proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -292,7 +292,7 @@ func queryDepositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han var deposit gov.Deposit cdc.UnmarshalJSON(res, &deposit) if deposit.Empty() { - res, err := cliCtx.QueryWithData("custom/gov/proposal", cdc.MustMarshalBinary(gov.QueryProposalParams{params.ProposalID})) + res, err := cliCtx.QueryWithData("custom/gov/proposal", cdc.MustMarshalBinaryLengthPrefixed(gov.QueryProposalParams{params.ProposalID})) if err != nil || len(res) == 0 { err := errors.Errorf("proposalID [%d] does not exist", proposalID) utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) @@ -319,7 +319,7 @@ func queryVoteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Handle return } - proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -386,7 +386,7 @@ func queryVotesOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) return } - proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -416,7 +416,7 @@ func queryProposalsWithParameterFn(cdc *codec.Codec, cliCtx context.CLIContext) bechVoterAddr := r.URL.Query().Get(RestVoter) bechDepositerAddr := r.URL.Query().Get(RestDepositer) strProposalStatus := r.URL.Query().Get(RestProposalStatus) - strNumLatest := r.URL.Query().Get(RestNumLatest) + strNumLimit := r.URL.Query().Get(RestNumLimit) params := gov.QueryProposalsParams{} @@ -446,12 +446,12 @@ func queryProposalsWithParameterFn(cdc *codec.Codec, cliCtx context.CLIContext) } params.ProposalStatus = proposalStatus } - if len(strNumLatest) != 0 { - numLatest, ok := utils.ParseInt64OrReturnBadRequest(w, strNumLatest) + if len(strNumLimit) != 0 { + numLimit, ok := utils.ParseUint64OrReturnBadRequest(w, strNumLimit) if !ok { return } - params.NumLatestProposals = numLatest + params.Limit = numLimit } bz, err := cdc.MarshalJSON(params) @@ -484,7 +484,7 @@ func queryTallyOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) return } - proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } diff --git a/x/gov/depositsvotes.go b/x/gov/depositsvotes.go index d1179023f..7a6b043e6 100644 --- a/x/gov/depositsvotes.go +++ b/x/gov/depositsvotes.go @@ -11,7 +11,7 @@ import ( // Vote type Vote struct { Voter sdk.AccAddress `json:"voter"` // address of the voter - ProposalID int64 `json:"proposal_id"` // proposalID of the proposal + ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal Option VoteOption `json:"option"` // option from OptionSet chosen by the voter } @@ -29,7 +29,7 @@ func (voteA Vote) Empty() bool { // Deposit type Deposit struct { Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer - ProposalID int64 `json:"proposal_id"` // proposalID of the proposal + ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal Amount sdk.Coins `json:"amount"` // Deposit amount } diff --git a/x/gov/endblocker_test.go b/x/gov/endblocker_test.go index 27eff15f6..535cf036a 100644 --- a/x/gov/endblocker_test.go +++ b/x/gov/endblocker_test.go @@ -16,35 +16,40 @@ func TestTickExpiredDepositPeriod(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) govHandler := NewHandler(keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("steak", 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newHeader = ctx.BlockHeader() - newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod) + newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositParams(ctx).MaxDepositPeriod) ctx = ctx.WithBlockHeader(newHeader) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.True(t, inactiveQueue.Valid()) + inactiveQueue.Close() + EndBlocker(ctx, keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() } func TestTickMultipleExpiredDepositPeriod(t *testing.T) { @@ -53,49 +58,54 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) govHandler := NewHandler(keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("steak", 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(2) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newProposalMsg2 := NewMsgSubmitProposal("Test2", "test2", ProposalTypeText, addrs[1], sdk.Coins{sdk.NewInt64Coin("steak", 5)}) res = govHandler(ctx, newProposalMsg2) require.True(t, res.IsOK()) newHeader = ctx.BlockHeader() - newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod).Add(time.Duration(-1) * time.Second) + newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositParams(ctx).MaxDepositPeriod).Add(time.Duration(-1) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.True(t, inactiveQueue.Valid()) + inactiveQueue.Close() EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newHeader = ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(5) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.True(t, inactiveQueue.Valid()) + inactiveQueue.Close() EndBlocker(ctx, keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() } func TestTickPassedDepositPeriod(t *testing.T) { @@ -104,45 +114,39 @@ func TestTickPassedDepositPeriod(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) govHandler := NewHandler(keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) - require.False(t, shouldPopActiveProposalQueue(ctx, keeper)) + inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() + activeQueue := keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, activeQueue.Valid()) + activeQueue.Close() newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("steak", 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) - var proposalID int64 - keeper.cdc.UnmarshalBinaryBare(res.Data, &proposalID) + var proposalID uint64 + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newDepositMsg := NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewInt64Coin("steak", 5)}) res = govHandler(ctx, newDepositMsg) require.True(t, res.IsOK()) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) - require.NotNil(t, keeper.ActiveProposalQueuePeek(ctx)) - - EndBlocker(ctx, keeper) - - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) - require.NotNil(t, keeper.ActiveProposalQueuePeek(ctx)) - require.False(t, shouldPopActiveProposalQueue(ctx, keeper)) - + activeQueue = keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, activeQueue.Valid()) + activeQueue.Close() } func TestTickPassedVotingPeriod(t *testing.T) { @@ -152,17 +156,19 @@ func TestTickPassedVotingPeriod(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) govHandler := NewHandler(keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) - require.False(t, shouldPopActiveProposalQueue(ctx, keeper)) + inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() + activeQueue := keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, activeQueue.Valid()) + activeQueue.Close() newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("steak", 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) - var proposalID int64 - keeper.cdc.UnmarshalBinaryBare(res.Data, &proposalID) + var proposalID uint64 + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID) newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) @@ -172,24 +178,27 @@ func TestTickPassedVotingPeriod(t *testing.T) { res = govHandler(ctx, newDepositMsg) require.True(t, res.IsOK()) - EndBlocker(ctx, keeper) - newHeader = ctx.BlockHeader() - newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod).Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod) + newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositParams(ctx).MaxDepositPeriod).Add(keeper.GetVotingParams(ctx).VotingPeriod) ctx = ctx.WithBlockHeader(newHeader) - require.True(t, shouldPopActiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() + + activeQueue = keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.True(t, activeQueue.Valid()) + var activeProposalID uint64 + keeper.cdc.UnmarshalBinaryLengthPrefixed(activeQueue.Value(), &activeProposalID) + require.Equal(t, StatusVotingPeriod, keeper.GetProposal(ctx, activeProposalID).GetStatus()) depositsIterator := keeper.GetDeposits(ctx, proposalID) require.True(t, depositsIterator.Valid()) depositsIterator.Close() - require.Equal(t, StatusVotingPeriod, keeper.GetProposal(ctx, proposalID).GetStatus()) + activeQueue.Close() EndBlocker(ctx, keeper) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) - depositsIterator = keeper.GetDeposits(ctx, proposalID) - require.False(t, depositsIterator.Valid()) - depositsIterator.Close() - require.Equal(t, StatusRejected, keeper.GetProposal(ctx, proposalID).GetStatus()) - require.True(t, keeper.GetProposal(ctx, proposalID).GetTallyResult().Equals(EmptyTallyResult())) + activeQueue = keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, activeQueue.Valid()) + activeQueue.Close() } diff --git a/x/gov/errors.go b/x/gov/errors.go index cd1cf8a17..e803d080e 100644 --- a/x/gov/errors.go +++ b/x/gov/errors.go @@ -26,19 +26,19 @@ const ( //---------------------------------------- // Error constructors -func ErrUnknownProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error { +func ErrUnknownProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { return sdk.NewError(codespace, CodeUnknownProposal, fmt.Sprintf("Unknown proposal with id %d", proposalID)) } -func ErrInactiveProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error { +func ErrInactiveProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { return sdk.NewError(codespace, CodeInactiveProposal, fmt.Sprintf("Inactive proposal with id %d", proposalID)) } -func ErrAlreadyActiveProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error { +func ErrAlreadyActiveProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { return sdk.NewError(codespace, CodeAlreadyActiveProposal, fmt.Sprintf("Proposal %d has been already active", proposalID)) } -func ErrAlreadyFinishedProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error { +func ErrAlreadyFinishedProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { return sdk.NewError(codespace, CodeAlreadyFinishedProposal, fmt.Sprintf("Proposal %d has already passed its voting period", proposalID)) } diff --git a/x/gov/genesis.go b/x/gov/genesis.go index 58273c8e8..1243bf559 100644 --- a/x/gov/genesis.go +++ b/x/gov/genesis.go @@ -8,18 +8,33 @@ import ( // GenesisState - all staking state that must be provided at genesis type GenesisState struct { - StartingProposalID int64 `json:"starting_proposalID"` - DepositProcedure DepositProcedure `json:"deposit_period"` - VotingProcedure VotingProcedure `json:"voting_period"` - TallyingProcedure TallyingProcedure `json:"tallying_procedure"` + StartingProposalID uint64 `json:"starting_proposal_id"` + Deposits []DepositWithMetadata `json:"deposits"` + Votes []VoteWithMetadata `json:"votes"` + Proposals []Proposal `json:"proposals"` + DepositParams DepositParams `json:"deposit_params"` + VotingParams VotingParams `json:"voting_params"` + TallyParams TallyParams `json:"tally_params"` } -func NewGenesisState(startingProposalID int64, dp DepositProcedure, vp VotingProcedure, tp TallyingProcedure) GenesisState { +// DepositWithMetadata (just for genesis) +type DepositWithMetadata struct { + ProposalID uint64 `json:"proposal_id"` + Deposit Deposit `json:"deposit"` +} + +// VoteWithMetadata (just for genesis) +type VoteWithMetadata struct { + ProposalID uint64 `json:"proposal_id"` + Vote Vote `json:"vote"` +} + +func NewGenesisState(startingProposalID uint64, dp DepositParams, vp VotingParams, tp TallyParams) GenesisState { return GenesisState{ StartingProposalID: startingProposalID, - DepositProcedure: dp, - VotingProcedure: vp, - TallyingProcedure: tp, + DepositParams: dp, + VotingParams: vp, + TallyParams: tp, } } @@ -27,14 +42,14 @@ func NewGenesisState(startingProposalID int64, dp DepositProcedure, vp VotingPro func DefaultGenesisState() GenesisState { return GenesisState{ StartingProposalID: 1, - DepositProcedure: DepositProcedure{ + DepositParams: DepositParams{ MinDeposit: sdk.Coins{sdk.NewInt64Coin("steak", 10)}, MaxDepositPeriod: time.Duration(172800) * time.Second, }, - VotingProcedure: VotingProcedure{ + VotingParams: VotingParams{ VotingPeriod: time.Duration(172800) * time.Second, }, - TallyingProcedure: TallyingProcedure{ + TallyParams: TallyParams{ Threshold: sdk.NewDecWithPrec(5, 1), Veto: sdk.NewDecWithPrec(334, 3), GovernancePenalty: sdk.NewDecWithPrec(1, 2), @@ -49,22 +64,52 @@ func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { // TODO: Handle this with #870 panic(err) } - k.setDepositProcedure(ctx, data.DepositProcedure) - k.setVotingProcedure(ctx, data.VotingProcedure) - k.setTallyingProcedure(ctx, data.TallyingProcedure) + k.setDepositParams(ctx, data.DepositParams) + k.setVotingParams(ctx, data.VotingParams) + k.setTallyParams(ctx, data.TallyParams) + for _, deposit := range data.Deposits { + k.setDeposit(ctx, deposit.ProposalID, deposit.Deposit.Depositer, deposit.Deposit) + } + for _, vote := range data.Votes { + k.setVote(ctx, vote.ProposalID, vote.Vote.Voter, vote.Vote) + } + for _, proposal := range data.Proposals { + k.SetProposal(ctx, proposal) + } } -// WriteGenesis - output genesis parameters -func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState { - startingProposalID, _ := k.getNewProposalID(ctx) - depositProcedure := k.GetDepositProcedure(ctx) - votingProcedure := k.GetVotingProcedure(ctx) - tallyingProcedure := k.GetTallyingProcedure(ctx) +// ExportGenesis - output genesis parameters +func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { + startingProposalID, _ := k.peekCurrentProposalID(ctx) + depositParams := k.GetDepositParams(ctx) + votingParams := k.GetVotingParams(ctx) + tallyParams := k.GetTallyParams(ctx) + var deposits []DepositWithMetadata + var votes []VoteWithMetadata + proposals := k.GetProposalsFiltered(ctx, nil, nil, StatusNil, 0) + for _, proposal := range proposals { + proposalID := proposal.GetProposalID() + depositsIterator := k.GetDeposits(ctx, proposalID) + for ; depositsIterator.Valid(); depositsIterator.Next() { + var deposit Deposit + k.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) + deposits = append(deposits, DepositWithMetadata{proposalID, deposit}) + } + votesIterator := k.GetVotes(ctx, proposalID) + for ; votesIterator.Valid(); votesIterator.Next() { + var vote Vote + k.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) + votes = append(votes, VoteWithMetadata{proposalID, vote}) + } + } return GenesisState{ StartingProposalID: startingProposalID, - DepositProcedure: depositProcedure, - VotingProcedure: votingProcedure, - TallyingProcedure: tallyingProcedure, + Deposits: deposits, + Votes: votes, + Proposals: proposals, + DepositParams: depositParams, + VotingParams: votingParams, + TallyParams: tallyParams, } } diff --git a/x/gov/handler.go b/x/gov/handler.go index 3f8cd312b..180f7a21a 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -33,7 +33,7 @@ func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitPropos return err.Result() } - proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(proposal.GetProposalID()) + proposalIDBytes := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposal.GetProposalID()) resTags := sdk.NewTags( tags.Action, tags.ActionSubmitProposal, @@ -42,7 +42,7 @@ func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitPropos ) if votingStarted { - resTags.AppendTag(tags.VotingPeriodStart, proposalIDBytes) + resTags = resTags.AppendTag(tags.VotingPeriodStart, proposalIDBytes) } return sdk.Result{ @@ -68,7 +68,7 @@ func handleMsgDeposit(ctx sdk.Context, keeper Keeper, msg MsgDeposit) sdk.Result ) if votingStarted { - resTags.AppendTag(tags.VotingPeriodStart, proposalIDBytes) + resTags = resTags.AppendTag(tags.VotingPeriodStart, proposalIDBytes) } return sdk.Result{ @@ -102,40 +102,35 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { resTags = sdk.NewTags() - // Delete proposals that haven't met minDeposit - for shouldPopInactiveProposalQueue(ctx, keeper) { - inactiveProposal := keeper.InactiveProposalQueuePop(ctx) - if inactiveProposal.GetStatus() != StatusDepositPeriod { - continue - } + inactiveIterator := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + for ; inactiveIterator.Valid(); inactiveIterator.Next() { + var proposalID uint64 + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(inactiveIterator.Value(), &proposalID) + inactiveProposal := keeper.GetProposal(ctx, proposalID) + keeper.DeleteProposal(ctx, proposalID) + keeper.DeleteDeposits(ctx, proposalID) // delete any associated deposits (burned) - proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(inactiveProposal.GetProposalID()) - keeper.DeleteProposal(ctx, inactiveProposal) - resTags.AppendTag(tags.Action, tags.ActionProposalDropped) - resTags.AppendTag(tags.ProposalID, proposalIDBytes) + resTags = resTags.AppendTag(tags.Action, tags.ActionProposalDropped) + resTags = resTags.AppendTag(tags.ProposalID, []byte(string(proposalID))) logger.Info( - fmt.Sprintf("proposal %d (%s) didn't meet minimum deposit of %v steak (had only %v steak); deleted", + fmt.Sprintf("proposal %d (%s) didn't meet minimum deposit of %s (had only %s); deleted", inactiveProposal.GetProposalID(), inactiveProposal.GetTitle(), - keeper.GetDepositProcedure(ctx).MinDeposit.AmountOf("steak"), - inactiveProposal.GetTotalDeposit().AmountOf("steak"), + keeper.GetDepositParams(ctx).MinDeposit, + inactiveProposal.GetTotalDeposit(), ), ) } + inactiveIterator.Close() - // Check if earliest Active Proposal ended voting period yet - for shouldPopActiveProposalQueue(ctx, keeper) { - activeProposal := keeper.ActiveProposalQueuePop(ctx) - - proposalStartTime := activeProposal.GetVotingStartTime() - votingPeriod := keeper.GetVotingProcedure(ctx).VotingPeriod - if ctx.BlockHeader().Time.Before(proposalStartTime.Add(votingPeriod)) { - continue - } - + activeIterator := keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + for ; activeIterator.Valid(); activeIterator.Next() { + var proposalID uint64 + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID) + activeProposal := keeper.GetProposal(ctx, proposalID) passes, tallyResults := tally(ctx, keeper, activeProposal) - proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(activeProposal.GetProposalID()) + var action []byte if passes { keeper.RefundDeposits(ctx, activeProposal.GetProposalID()) @@ -149,37 +144,15 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { activeProposal.SetTallyResult(tallyResults) keeper.SetProposal(ctx, activeProposal) + keeper.RemoveFromActiveProposalQueue(ctx, activeProposal.GetVotingEndTime(), activeProposal.GetProposalID()) + logger.Info(fmt.Sprintf("proposal %d (%s) tallied; passed: %v", activeProposal.GetProposalID(), activeProposal.GetTitle(), passes)) - resTags.AppendTag(tags.Action, action) - resTags.AppendTag(tags.ProposalID, proposalIDBytes) + resTags = resTags.AppendTag(tags.Action, action) + resTags = resTags.AppendTag(tags.ProposalID, []byte(string(proposalID))) } + activeIterator.Close() return resTags } -func shouldPopInactiveProposalQueue(ctx sdk.Context, keeper Keeper) bool { - depositProcedure := keeper.GetDepositProcedure(ctx) - peekProposal := keeper.InactiveProposalQueuePeek(ctx) - - if peekProposal == nil { - return false - } else if peekProposal.GetStatus() != StatusDepositPeriod { - return true - } else if !ctx.BlockHeader().Time.Before(peekProposal.GetSubmitTime().Add(depositProcedure.MaxDepositPeriod)) { - return true - } - return false -} - -func shouldPopActiveProposalQueue(ctx sdk.Context, keeper Keeper) bool { - votingProcedure := keeper.GetVotingProcedure(ctx) - peekProposal := keeper.ActiveProposalQueuePeek(ctx) - - if peekProposal == nil { - return false - } else if !ctx.BlockHeader().Time.Before(peekProposal.GetVotingStartTime().Add(votingProcedure.VotingPeriod)) { - return true - } - return false -} diff --git a/x/gov/keeper.go b/x/gov/keeper.go index d061f1206..862db8274 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -1,10 +1,14 @@ package gov import ( + "time" + codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/params" + + "github.com/tendermint/tendermint/crypto" ) // Parameter store default namestore @@ -14,17 +18,20 @@ const ( // Parameter store key var ( - ParamStoreKeyDepositProcedure = []byte("depositprocedure") - ParamStoreKeyVotingProcedure = []byte("votingprocedure") - ParamStoreKeyTallyingProcedure = []byte("tallyingprocedure") + ParamStoreKeyDepositParams = []byte("depositparams") + ParamStoreKeyVotingParams = []byte("votingparams") + ParamStoreKeyTallyParams = []byte("tallyparams") + + DepositedCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("govDepositedCoins"))) + BurnedDepositCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("govBurnedDepositCoins"))) ) // Type declaration for parameters func ParamTypeTable() params.TypeTable { return params.NewTypeTable( - ParamStoreKeyDepositProcedure, DepositProcedure{}, - ParamStoreKeyVotingProcedure, VotingProcedure{}, - ParamStoreKeyTallyingProcedure, TallyingProcedure{}, + ParamStoreKeyDepositParams, DepositParams{}, + ParamStoreKeyVotingParams, VotingParams{}, + ParamStoreKeyTallyParams, TallyParams{}, ) } @@ -92,13 +99,17 @@ func (keeper Keeper) NewTextProposal(ctx sdk.Context, title string, description TotalDeposit: sdk.Coins{}, SubmitTime: ctx.BlockHeader().Time, } + + depositPeriod := keeper.GetDepositParams(ctx).MaxDepositPeriod + proposal.SetDepositEndTime(proposal.GetSubmitTime().Add(depositPeriod)) + keeper.SetProposal(ctx, proposal) - keeper.InactiveProposalQueuePush(ctx, proposal) + keeper.InsertInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposalID) return proposal } // Get Proposal from store by ProposalID -func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID int64) Proposal { +func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID uint64) Proposal { store := ctx.KVStore(keeper.storeKey) bz := store.Get(KeyProposal(proposalID)) if bz == nil { @@ -106,7 +117,7 @@ func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID int64) Proposal { } var proposal Proposal - keeper.cdc.MustUnmarshalBinary(bz, &proposal) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposal) return proposal } @@ -114,18 +125,21 @@ func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID int64) Proposal { // Implements sdk.AccountKeeper. func (keeper Keeper) SetProposal(ctx sdk.Context, proposal Proposal) { store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinary(proposal) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposal) store.Set(KeyProposal(proposal.GetProposalID()), bz) } // Implements sdk.AccountKeeper. -func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposal Proposal) { +func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) - store.Delete(KeyProposal(proposal.GetProposalID())) + proposal := keeper.GetProposal(ctx, proposalID) + keeper.RemoveFromInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposalID) + keeper.RemoveFromActiveProposalQueue(ctx, proposal.GetVotingEndTime(), proposalID) + store.Delete(KeyProposal(proposalID)) } // Get Proposal from store by ProposalID -func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositerAddr sdk.AccAddress, status ProposalStatus, numLatest int64) []Proposal { +func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositerAddr sdk.AccAddress, status ProposalStatus, numLatest uint64) []Proposal { maxProposalID, err := keeper.peekCurrentProposalID(ctx) if err != nil { @@ -134,7 +148,7 @@ func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddr matchingProposals := []Proposal{} - if numLatest <= 0 { + if numLatest == 0 { numLatest = maxProposalID } @@ -169,19 +183,19 @@ func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddr return matchingProposals } -func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID int64) sdk.Error { +func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID uint64) sdk.Error { store := ctx.KVStore(keeper.storeKey) bz := store.Get(KeyNextProposalID) if bz != nil { return ErrInvalidGenesis(keeper.codespace, "Initial ProposalID already set") } - bz = keeper.cdc.MustMarshalBinary(proposalID) + bz = keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) store.Set(KeyNextProposalID, bz) return nil } // Get the last used proposal ID -func (keeper Keeper) GetLastProposalID(ctx sdk.Context) (proposalID int64) { +func (keeper Keeper) GetLastProposalID(ctx sdk.Context) (proposalID uint64) { proposalID, err := keeper.peekCurrentProposalID(ctx) if err != nil { return 0 @@ -191,83 +205,87 @@ func (keeper Keeper) GetLastProposalID(ctx sdk.Context) (proposalID int64) { } // Gets the next available ProposalID and increments it -func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID int64, err sdk.Error) { +func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID uint64, err sdk.Error) { store := ctx.KVStore(keeper.storeKey) bz := store.Get(KeyNextProposalID) if bz == nil { - return -1, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") + return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") } - keeper.cdc.MustUnmarshalBinary(bz, &proposalID) - bz = keeper.cdc.MustMarshalBinary(proposalID + 1) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID) + bz = keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID + 1) store.Set(KeyNextProposalID, bz) return proposalID, nil } // Peeks the next available ProposalID without incrementing it -func (keeper Keeper) peekCurrentProposalID(ctx sdk.Context) (proposalID int64, err sdk.Error) { +func (keeper Keeper) peekCurrentProposalID(ctx sdk.Context) (proposalID uint64, err sdk.Error) { store := ctx.KVStore(keeper.storeKey) bz := store.Get(KeyNextProposalID) if bz == nil { - return -1, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") + return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") } - keeper.cdc.MustUnmarshalBinary(bz, &proposalID) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID) return proposalID, nil } func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) { proposal.SetVotingStartTime(ctx.BlockHeader().Time) + votingPeriod := keeper.GetVotingParams(ctx).VotingPeriod + proposal.SetVotingEndTime(proposal.GetVotingStartTime().Add(votingPeriod)) proposal.SetStatus(StatusVotingPeriod) keeper.SetProposal(ctx, proposal) - keeper.ActiveProposalQueuePush(ctx, proposal) + + keeper.RemoveFromInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposal.GetProposalID()) + keeper.InsertActiveProposalQueue(ctx, proposal.GetVotingEndTime(), proposal.GetProposalID()) } // ===================================================== -// Procedures +// Params -// Returns the current Deposit Procedure from the global param store +// Returns the current DepositParams from the global param store // nolint: errcheck -func (keeper Keeper) GetDepositProcedure(ctx sdk.Context) DepositProcedure { - var depositProcedure DepositProcedure - keeper.paramSpace.Get(ctx, ParamStoreKeyDepositProcedure, &depositProcedure) - return depositProcedure +func (keeper Keeper) GetDepositParams(ctx sdk.Context) DepositParams { + var depositParams DepositParams + keeper.paramSpace.Get(ctx, ParamStoreKeyDepositParams, &depositParams) + return depositParams } // Returns the current Voting Procedure from the global param store // nolint: errcheck -func (keeper Keeper) GetVotingProcedure(ctx sdk.Context) VotingProcedure { - var votingProcedure VotingProcedure - keeper.paramSpace.Get(ctx, ParamStoreKeyVotingProcedure, &votingProcedure) - return votingProcedure +func (keeper Keeper) GetVotingParams(ctx sdk.Context) VotingParams { + var votingParams VotingParams + keeper.paramSpace.Get(ctx, ParamStoreKeyVotingParams, &votingParams) + return votingParams } // Returns the current Tallying Procedure from the global param store // nolint: errcheck -func (keeper Keeper) GetTallyingProcedure(ctx sdk.Context) TallyingProcedure { - var tallyingProcedure TallyingProcedure - keeper.paramSpace.Get(ctx, ParamStoreKeyTallyingProcedure, &tallyingProcedure) - return tallyingProcedure +func (keeper Keeper) GetTallyParams(ctx sdk.Context) TallyParams { + var tallyParams TallyParams + keeper.paramSpace.Get(ctx, ParamStoreKeyTallyParams, &tallyParams) + return tallyParams } // nolint: errcheck -func (keeper Keeper) setDepositProcedure(ctx sdk.Context, depositProcedure DepositProcedure) { - keeper.paramSpace.Set(ctx, ParamStoreKeyDepositProcedure, &depositProcedure) +func (keeper Keeper) setDepositParams(ctx sdk.Context, depositParams DepositParams) { + keeper.paramSpace.Set(ctx, ParamStoreKeyDepositParams, &depositParams) } // nolint: errcheck -func (keeper Keeper) setVotingProcedure(ctx sdk.Context, votingProcedure VotingProcedure) { - keeper.paramSpace.Set(ctx, ParamStoreKeyVotingProcedure, &votingProcedure) +func (keeper Keeper) setVotingParams(ctx sdk.Context, votingParams VotingParams) { + keeper.paramSpace.Set(ctx, ParamStoreKeyVotingParams, &votingParams) } // nolint: errcheck -func (keeper Keeper) setTallyingProcedure(ctx sdk.Context, tallyingProcedure TallyingProcedure) { - keeper.paramSpace.Set(ctx, ParamStoreKeyTallyingProcedure, &tallyingProcedure) +func (keeper Keeper) setTallyParams(ctx sdk.Context, tallyParams TallyParams) { + keeper.paramSpace.Set(ctx, ParamStoreKeyTallyParams, &tallyParams) } // ===================================================== // Votes // Adds a vote on a specific proposal -func (keeper Keeper) AddVote(ctx sdk.Context, proposalID int64, voterAddr sdk.AccAddress, option VoteOption) sdk.Error { +func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, option VoteOption) sdk.Error { proposal := keeper.GetProposal(ctx, proposalID) if proposal == nil { return ErrUnknownProposal(keeper.codespace, proposalID) @@ -291,30 +309,30 @@ func (keeper Keeper) AddVote(ctx sdk.Context, proposalID int64, voterAddr sdk.Ac } // Gets the vote of a specific voter on a specific proposal -func (keeper Keeper) GetVote(ctx sdk.Context, proposalID int64, voterAddr sdk.AccAddress) (Vote, bool) { +func (keeper Keeper) GetVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) (Vote, bool) { store := ctx.KVStore(keeper.storeKey) bz := store.Get(KeyVote(proposalID, voterAddr)) if bz == nil { return Vote{}, false } var vote Vote - keeper.cdc.MustUnmarshalBinary(bz, &vote) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &vote) return vote, true } -func (keeper Keeper) setVote(ctx sdk.Context, proposalID int64, voterAddr sdk.AccAddress, vote Vote) { +func (keeper Keeper) setVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, vote Vote) { store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinary(vote) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(vote) store.Set(KeyVote(proposalID, voterAddr), bz) } // Gets all the votes on a specific proposal -func (keeper Keeper) GetVotes(ctx sdk.Context, proposalID int64) sdk.Iterator { +func (keeper Keeper) GetVotes(ctx sdk.Context, proposalID uint64) sdk.Iterator { store := ctx.KVStore(keeper.storeKey) return sdk.KVStorePrefixIterator(store, KeyVotesSubspace(proposalID)) } -func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID int64, voterAddr sdk.AccAddress) { +func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) { store := ctx.KVStore(keeper.storeKey) store.Delete(KeyVote(proposalID, voterAddr)) } @@ -323,26 +341,26 @@ func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID int64, voterAddr sdk // Deposits // Gets the deposit of a specific depositer on a specific proposal -func (keeper Keeper) GetDeposit(ctx sdk.Context, proposalID int64, depositerAddr sdk.AccAddress) (Deposit, bool) { +func (keeper Keeper) GetDeposit(ctx sdk.Context, proposalID uint64, depositerAddr sdk.AccAddress) (Deposit, bool) { store := ctx.KVStore(keeper.storeKey) bz := store.Get(KeyDeposit(proposalID, depositerAddr)) if bz == nil { return Deposit{}, false } var deposit Deposit - keeper.cdc.MustUnmarshalBinary(bz, &deposit) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &deposit) return deposit, true } -func (keeper Keeper) setDeposit(ctx sdk.Context, proposalID int64, depositerAddr sdk.AccAddress, deposit Deposit) { +func (keeper Keeper) setDeposit(ctx sdk.Context, proposalID uint64, depositerAddr sdk.AccAddress, deposit Deposit) { store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinary(deposit) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(deposit) store.Set(KeyDeposit(proposalID, depositerAddr), bz) } // Adds or updates a deposit of a specific depositer on a specific proposal // Activates voting period when appropriate -func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID int64, depositerAddr sdk.AccAddress, depositAmount sdk.Coins) (sdk.Error, bool) { +func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositerAddr sdk.AccAddress, depositAmount sdk.Coins) (sdk.Error, bool) { // Checks to see if proposal exists proposal := keeper.GetProposal(ctx, proposalID) if proposal == nil { @@ -354,8 +372,8 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID int64, depositerAddr return ErrAlreadyFinishedProposal(keeper.codespace, proposalID), false } - // Subtract coins from depositer's account - _, _, err := keeper.ck.SubtractCoins(ctx, depositerAddr, depositAmount) + // Send coins from depositer's account to DepositedCoinsAccAddr account + _, err := keeper.ck.SendCoins(ctx, depositerAddr, DepositedCoinsAccAddr, depositAmount) if err != nil { return err, false } @@ -367,7 +385,7 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID int64, depositerAddr // Check if deposit tipped proposal into voting period // Active voting period if so activatedVotingPeriod := false - if proposal.GetStatus() == StatusDepositPeriod && proposal.GetTotalDeposit().IsGTE(keeper.GetDepositProcedure(ctx).MinDeposit) { + if proposal.GetStatus() == StatusDepositPeriod && proposal.GetTotalDeposit().IsAllGTE(keeper.GetDepositParams(ctx).MinDeposit) { keeper.activateVotingPeriod(ctx, proposal) activatedVotingPeriod = true } @@ -386,21 +404,21 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID int64, depositerAddr } // Gets all the deposits on a specific proposal -func (keeper Keeper) GetDeposits(ctx sdk.Context, proposalID int64) sdk.Iterator { +func (keeper Keeper) GetDeposits(ctx sdk.Context, proposalID uint64) sdk.Iterator { store := ctx.KVStore(keeper.storeKey) return sdk.KVStorePrefixIterator(store, KeyDepositsSubspace(proposalID)) } // Returns and deletes all the deposits on a specific proposal -func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID int64) { +func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) depositsIterator := keeper.GetDeposits(ctx, proposalID) for ; depositsIterator.Valid(); depositsIterator.Next() { deposit := &Deposit{} - keeper.cdc.MustUnmarshalBinary(depositsIterator.Value(), deposit) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit) - _, _, err := keeper.ck.AddCoins(ctx, deposit.Depositer, deposit.Amount) + _, err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, deposit.Depositer, deposit.Amount) if err != nil { panic("should not happen") } @@ -412,11 +430,19 @@ func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID int64) { } // Deletes all the deposits on a specific proposal without refunding them -func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID int64) { +func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) depositsIterator := keeper.GetDeposits(ctx, proposalID) for ; depositsIterator.Valid(); depositsIterator.Next() { + deposit := &Deposit{} + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit) + + _, err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, BurnedDepositCoinsAccAddr, deposit.Amount) + if err != nil { + panic("should not happen") + } + store.Delete(depositsIterator.Key()) } @@ -426,93 +452,40 @@ func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID int64) { // ===================================================== // ProposalQueues -func (keeper Keeper) getActiveProposalQueue(ctx sdk.Context) ProposalQueue { +// Returns an iterator for all the proposals in the Active Queue that expire by endTime +func (keeper Keeper) ActiveProposalQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { store := ctx.KVStore(keeper.storeKey) - bz := store.Get(KeyActiveProposalQueue) - if bz == nil { - return nil - } - - var proposalQueue ProposalQueue - keeper.cdc.MustUnmarshalBinary(bz, &proposalQueue) - - return proposalQueue + return store.Iterator(PrefixActiveProposalQueue, sdk.PrefixEndBytes(PrefixActiveProposalQueueTime(endTime))) } -func (keeper Keeper) setActiveProposalQueue(ctx sdk.Context, proposalQueue ProposalQueue) { +// Inserts a ProposalID into the active proposal queue at endTime +func (keeper Keeper) InsertActiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinary(proposalQueue) - store.Set(KeyActiveProposalQueue, bz) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) + store.Set(KeyActiveProposalQueueProposal(endTime, proposalID), bz) } -// Return the Proposal at the front of the ProposalQueue -func (keeper Keeper) ActiveProposalQueuePeek(ctx sdk.Context) Proposal { - proposalQueue := keeper.getActiveProposalQueue(ctx) - if len(proposalQueue) == 0 { - return nil - } - return keeper.GetProposal(ctx, proposalQueue[0]) -} - -// Remove and return a Proposal from the front of the ProposalQueue -func (keeper Keeper) ActiveProposalQueuePop(ctx sdk.Context) Proposal { - proposalQueue := keeper.getActiveProposalQueue(ctx) - if len(proposalQueue) == 0 { - return nil - } - frontElement, proposalQueue := proposalQueue[0], proposalQueue[1:] - keeper.setActiveProposalQueue(ctx, proposalQueue) - return keeper.GetProposal(ctx, frontElement) -} - -// Add a proposalID to the back of the ProposalQueue -func (keeper Keeper) ActiveProposalQueuePush(ctx sdk.Context, proposal Proposal) { - proposalQueue := append(keeper.getActiveProposalQueue(ctx), proposal.GetProposalID()) - keeper.setActiveProposalQueue(ctx, proposalQueue) -} - -func (keeper Keeper) getInactiveProposalQueue(ctx sdk.Context) ProposalQueue { +// removes a proposalID from the Active Proposal Queue +func (keeper Keeper) RemoveFromActiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) - bz := store.Get(KeyInactiveProposalQueue) - if bz == nil { - return nil - } - - var proposalQueue ProposalQueue - - keeper.cdc.MustUnmarshalBinary(bz, &proposalQueue) - - return proposalQueue + store.Delete(KeyActiveProposalQueueProposal(endTime, proposalID)) } -func (keeper Keeper) setInactiveProposalQueue(ctx sdk.Context, proposalQueue ProposalQueue) { +// Returns an iterator for all the proposals in the Inactive Queue that expire by endTime +func (keeper Keeper) InactiveProposalQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinary(proposalQueue) - store.Set(KeyInactiveProposalQueue, bz) + return store.Iterator(PrefixInactiveProposalQueue, sdk.PrefixEndBytes(PrefixInactiveProposalQueueTime(endTime))) } -// Return the Proposal at the front of the ProposalQueue -func (keeper Keeper) InactiveProposalQueuePeek(ctx sdk.Context) Proposal { - proposalQueue := keeper.getInactiveProposalQueue(ctx) - if len(proposalQueue) == 0 { - return nil - } - return keeper.GetProposal(ctx, proposalQueue[0]) +// Inserts a ProposalID into the inactive proposal queue at endTime +func (keeper Keeper) InsertInactiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) + store.Set(KeyInactiveProposalQueueProposal(endTime, proposalID), bz) } -// Remove and return a Proposal from the front of the ProposalQueue -func (keeper Keeper) InactiveProposalQueuePop(ctx sdk.Context) Proposal { - proposalQueue := keeper.getInactiveProposalQueue(ctx) - if len(proposalQueue) == 0 { - return nil - } - frontElement, proposalQueue := proposalQueue[0], proposalQueue[1:] - keeper.setInactiveProposalQueue(ctx, proposalQueue) - return keeper.GetProposal(ctx, frontElement) -} - -// Add a proposalID to the back of the ProposalQueue -func (keeper Keeper) InactiveProposalQueuePush(ctx sdk.Context, proposal Proposal) { - proposalQueue := append(keeper.getInactiveProposalQueue(ctx), proposal.GetProposalID()) - keeper.setInactiveProposalQueue(ctx, proposalQueue) +// removes a proposalID from the Inactive Proposal Queue +func (keeper Keeper) RemoveFromInactiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + store.Delete(KeyInactiveProposalQueueProposal(endTime, proposalID)) } diff --git a/x/gov/keeper_keys.go b/x/gov/keeper_keys.go index 7b1bf43f2..8a3324fd1 100644 --- a/x/gov/keeper_keys.go +++ b/x/gov/keeper_keys.go @@ -1,7 +1,9 @@ package gov import ( + "bytes" "fmt" + "time" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -10,32 +12,68 @@ import ( // Key for getting a the next available proposalID from the store var ( - KeyNextProposalID = []byte("newProposalID") - KeyActiveProposalQueue = []byte("activeProposalQueue") - KeyInactiveProposalQueue = []byte("inactiveProposalQueue") + KeyDelimiter = []byte(":") + + KeyNextProposalID = []byte("newProposalID") + PrefixActiveProposalQueue = []byte("activeProposalQueue") + PrefixInactiveProposalQueue = []byte("inactiveProposalQueue") ) // Key for getting a specific proposal from the store -func KeyProposal(proposalID int64) []byte { +func KeyProposal(proposalID uint64) []byte { return []byte(fmt.Sprintf("proposals:%d", proposalID)) } // Key for getting a specific deposit from the store -func KeyDeposit(proposalID int64, depositerAddr sdk.AccAddress) []byte { +func KeyDeposit(proposalID uint64, depositerAddr sdk.AccAddress) []byte { return []byte(fmt.Sprintf("deposits:%d:%d", proposalID, depositerAddr)) } // Key for getting a specific vote from the store -func KeyVote(proposalID int64, voterAddr sdk.AccAddress) []byte { +func KeyVote(proposalID uint64, voterAddr sdk.AccAddress) []byte { return []byte(fmt.Sprintf("votes:%d:%d", proposalID, voterAddr)) } // Key for getting all deposits on a proposal from the store -func KeyDepositsSubspace(proposalID int64) []byte { +func KeyDepositsSubspace(proposalID uint64) []byte { return []byte(fmt.Sprintf("deposits:%d:", proposalID)) } // Key for getting all votes on a proposal from the store -func KeyVotesSubspace(proposalID int64) []byte { +func KeyVotesSubspace(proposalID uint64) []byte { return []byte(fmt.Sprintf("votes:%d:", proposalID)) } + +// Returns the key for a proposalID in the activeProposalQueue +func PrefixActiveProposalQueueTime(endTime time.Time) []byte { + return bytes.Join([][]byte{ + PrefixActiveProposalQueue, + sdk.FormatTimeBytes(endTime), + }, KeyDelimiter) +} + +// Returns the key for a proposalID in the activeProposalQueue +func KeyActiveProposalQueueProposal(endTime time.Time, proposalID uint64) []byte { + return bytes.Join([][]byte{ + PrefixActiveProposalQueue, + sdk.FormatTimeBytes(endTime), + sdk.Uint64ToBigEndian(proposalID), + }, KeyDelimiter) +} + +// Returns the key for a proposalID in the activeProposalQueue +func PrefixInactiveProposalQueueTime(endTime time.Time) []byte { + return bytes.Join([][]byte{ + PrefixInactiveProposalQueue, + sdk.FormatTimeBytes(endTime), + }, KeyDelimiter) +} + +// Returns the key for a proposalID in the activeProposalQueue +func KeyInactiveProposalQueueProposal(endTime time.Time, proposalID uint64) []byte { + return bytes.Join([][]byte{ + PrefixInactiveProposalQueue, + sdk.FormatTimeBytes(endTime), + sdk.Uint64ToBigEndian(proposalID), + }, KeyDelimiter) +} diff --git a/x/gov/keeper_test.go b/x/gov/keeper_test.go index 91c41d7d7..2ff1344b7 100644 --- a/x/gov/keeper_test.go +++ b/x/gov/keeper_test.go @@ -36,7 +36,7 @@ func TestIncrementProposalNumber(t *testing.T) { keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposal6 := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - require.Equal(t, int64(6), proposal6.GetProposalID()) + require.Equal(t, uint64(6), proposal6.GetProposalID()) } func TestActivateVotingPeriod(t *testing.T) { @@ -47,12 +47,17 @@ func TestActivateVotingPeriod(t *testing.T) { proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) require.True(t, proposal.GetVotingStartTime().Equal(time.Time{})) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) keeper.activateVotingPeriod(ctx, proposal) require.True(t, proposal.GetVotingStartTime().Equal(ctx.BlockHeader().Time)) - require.Equal(t, proposal.GetProposalID(), keeper.ActiveProposalQueuePeek(ctx).GetProposalID()) + + activeIterator := keeper.ActiveProposalQueueIterator(ctx, proposal.GetVotingEndTime()) + require.True(t, activeIterator.Valid()) + var proposalID uint64 + keeper.cdc.UnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID) + require.Equal(t, proposalID, proposal.GetProposalID()) + activeIterator.Close() } func TestDeposits(t *testing.T) { @@ -79,7 +84,6 @@ func TestDeposits(t *testing.T) { deposit, found := keeper.GetDeposit(ctx, proposalID, addrs[1]) require.False(t, found) require.True(t, keeper.GetProposal(ctx, proposalID).GetVotingStartTime().Equal(time.Time{})) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) // Check first deposit err, votingStarted := keeper.AddDeposit(ctx, proposalID, addrs[0], fourSteak) @@ -116,17 +120,15 @@ func TestDeposits(t *testing.T) { // Check that proposal moved to voting period require.True(t, keeper.GetProposal(ctx, proposalID).GetVotingStartTime().Equal(ctx.BlockHeader().Time)) - require.NotNil(t, keeper.ActiveProposalQueuePeek(ctx)) - require.Equal(t, proposalID, keeper.ActiveProposalQueuePeek(ctx).GetProposalID()) // Test deposit iterator depositsIterator := keeper.GetDeposits(ctx, proposalID) require.True(t, depositsIterator.Valid()) - keeper.cdc.MustUnmarshalBinary(depositsIterator.Value(), &deposit) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) require.Equal(t, addrs[0], deposit.Depositer) require.Equal(t, fourSteak.Plus(fiveSteak), deposit.Amount) depositsIterator.Next() - keeper.cdc.MustUnmarshalBinary(depositsIterator.Value(), &deposit) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) require.Equal(t, addrs[1], deposit.Depositer) require.Equal(t, fourSteak, deposit.Amount) depositsIterator.Next() @@ -184,14 +186,14 @@ func TestVotes(t *testing.T) { // Test vote iterator votesIterator := keeper.GetVotes(ctx, proposalID) require.True(t, votesIterator.Valid()) - keeper.cdc.MustUnmarshalBinary(votesIterator.Value(), &vote) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) require.True(t, votesIterator.Valid()) require.Equal(t, addrs[0], vote.Voter) require.Equal(t, proposalID, vote.ProposalID) require.Equal(t, OptionYes, vote.Option) votesIterator.Next() require.True(t, votesIterator.Valid()) - keeper.cdc.MustUnmarshalBinary(votesIterator.Value(), &vote) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) require.True(t, votesIterator.Valid()) require.Equal(t, addrs[1], vote.Voter) require.Equal(t, proposalID, vote.ProposalID) @@ -207,44 +209,21 @@ func TestProposalQueues(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) mapp.InitChainer(ctx, abci.RequestInitChain{}) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) - // create test proposals proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposal2 := keeper.NewTextProposal(ctx, "Test2", "description", ProposalTypeText) - proposal3 := keeper.NewTextProposal(ctx, "Test3", "description", ProposalTypeText) - proposal4 := keeper.NewTextProposal(ctx, "Test4", "description", ProposalTypeText) - // test pushing to inactive proposal queue - keeper.InactiveProposalQueuePush(ctx, proposal) - keeper.InactiveProposalQueuePush(ctx, proposal2) - keeper.InactiveProposalQueuePush(ctx, proposal3) - keeper.InactiveProposalQueuePush(ctx, proposal4) + inactiveIterator := keeper.InactiveProposalQueueIterator(ctx, proposal.GetDepositEndTime()) + require.True(t, inactiveIterator.Valid()) + var proposalID uint64 + keeper.cdc.UnmarshalBinaryLengthPrefixed(inactiveIterator.Value(), &proposalID) + require.Equal(t, proposalID, proposal.GetProposalID()) + inactiveIterator.Close() - // test peeking and popping from inactive proposal queue - require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal2.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal2.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal3.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal3.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal4.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal4.GetProposalID()) + keeper.activateVotingPeriod(ctx, proposal) - // test pushing to active proposal queue - keeper.ActiveProposalQueuePush(ctx, proposal) - keeper.ActiveProposalQueuePush(ctx, proposal2) - keeper.ActiveProposalQueuePush(ctx, proposal3) - keeper.ActiveProposalQueuePush(ctx, proposal4) - - // test peeking and popping from active proposal queue - require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal2.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal2.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal3.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal3.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal4.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal4.GetProposalID()) + activeIterator := keeper.ActiveProposalQueueIterator(ctx, proposal.GetVotingEndTime()) + require.True(t, activeIterator.Valid()) + keeper.cdc.UnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID) + require.Equal(t, proposalID, proposal.GetProposalID()) + activeIterator.Close() } diff --git a/x/gov/msgs.go b/x/gov/msgs.go index 847c04ab0..b8325d81c 100644 --- a/x/gov/msgs.go +++ b/x/gov/msgs.go @@ -84,12 +84,12 @@ func (msg MsgSubmitProposal) GetSigners() []sdk.AccAddress { //----------------------------------------------------------- // MsgDeposit type MsgDeposit struct { - ProposalID int64 `json:"proposal_id"` // ID of the proposal + ProposalID uint64 `json:"proposal_id"` // ID of the proposal Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit } -func NewMsgDeposit(depositer sdk.AccAddress, proposalID int64, amount sdk.Coins) MsgDeposit { +func NewMsgDeposit(depositer sdk.AccAddress, proposalID uint64, amount sdk.Coins) MsgDeposit { return MsgDeposit{ ProposalID: proposalID, Depositer: depositer, @@ -145,12 +145,12 @@ func (msg MsgDeposit) GetSigners() []sdk.AccAddress { //----------------------------------------------------------- // MsgVote type MsgVote struct { - ProposalID int64 `json:"proposal_id"` // ID of the proposal + ProposalID uint64 `json:"proposal_id"` // ID of the proposal Voter sdk.AccAddress `json:"voter"` // address of the voter Option VoteOption `json:"option"` // option from OptionSet chosen by the voter } -func NewMsgVote(voter sdk.AccAddress, proposalID int64, option VoteOption) MsgVote { +func NewMsgVote(voter sdk.AccAddress, proposalID uint64, option VoteOption) MsgVote { return MsgVote{ ProposalID: proposalID, Voter: voter, diff --git a/x/gov/msgs_test.go b/x/gov/msgs_test.go index 6d2b8bb38..bdc273dcd 100644 --- a/x/gov/msgs_test.go +++ b/x/gov/msgs_test.go @@ -53,13 +53,12 @@ func TestMsgSubmitProposal(t *testing.T) { func TestMsgDeposit(t *testing.T) { _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) tests := []struct { - proposalID int64 + proposalID uint64 depositerAddr sdk.AccAddress depositAmount sdk.Coins expectPass bool }{ {0, addrs[0], coinsPos, true}, - {-1, addrs[0], coinsPos, false}, {1, sdk.AccAddress{}, coinsPos, false}, {1, addrs[0], coinsZero, true}, {1, addrs[0], coinsNeg, false}, @@ -80,13 +79,12 @@ func TestMsgDeposit(t *testing.T) { func TestMsgVote(t *testing.T) { _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) tests := []struct { - proposalID int64 + proposalID uint64 voterAddr sdk.AccAddress option VoteOption expectPass bool }{ {0, addrs[0], OptionYes, true}, - {-1, addrs[0], OptionYes, false}, {0, sdk.AccAddress{}, OptionYes, false}, {0, addrs[0], OptionNo, true}, {0, addrs[0], OptionNoWithVeto, true}, diff --git a/x/gov/procedures.go b/x/gov/params.go similarity index 91% rename from x/gov/procedures.go rename to x/gov/params.go index e453add79..4d8126d14 100644 --- a/x/gov/procedures.go +++ b/x/gov/params.go @@ -7,19 +7,19 @@ import ( ) // Procedure around Deposits for governance -type DepositProcedure struct { +type DepositParams struct { MinDeposit sdk.Coins `json:"min_deposit"` // Minimum deposit for a proposal to enter voting period. MaxDepositPeriod time.Duration `json:"max_deposit_period"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months } // Procedure around Tallying votes in governance -type TallyingProcedure struct { +type TallyParams struct { Threshold sdk.Dec `json:"threshold"` // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 Veto sdk.Dec `json:"veto"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 GovernancePenalty sdk.Dec `json:"governance_penalty"` // Penalty if validator does not vote } // Procedure around Voting in governance -type VotingProcedure struct { +type VotingParams struct { VotingPeriod time.Duration `json:"voting_period"` // Length of the voting period. } diff --git a/x/gov/proposals.go b/x/gov/proposals.go index 9d1ba860a..e943fe116 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -13,8 +13,8 @@ import ( //----------------------------------------------------------- // Proposal interface type Proposal interface { - GetProposalID() int64 - SetProposalID(int64) + GetProposalID() uint64 + SetProposalID(uint64) GetTitle() string SetTitle(string) @@ -34,11 +34,17 @@ type Proposal interface { GetSubmitTime() time.Time SetSubmitTime(time.Time) + GetDepositEndTime() time.Time + SetDepositEndTime(time.Time) + GetTotalDeposit() sdk.Coins SetTotalDeposit(sdk.Coins) GetVotingStartTime() time.Time SetVotingStartTime(time.Time) + + GetVotingEndTime() time.Time + SetVotingEndTime(time.Time) } // checks if two proposals are equal @@ -50,8 +56,10 @@ func ProposalEqual(proposalA Proposal, proposalB Proposal) bool { proposalA.GetStatus() == proposalB.GetStatus() && proposalA.GetTallyResult().Equals(proposalB.GetTallyResult()) && proposalA.GetSubmitTime().Equal(proposalB.GetSubmitTime()) && + proposalA.GetDepositEndTime().Equal(proposalB.GetDepositEndTime()) && proposalA.GetTotalDeposit().IsEqual(proposalB.GetTotalDeposit()) && - proposalA.GetVotingStartTime().Equal(proposalB.GetVotingStartTime()) { + proposalA.GetVotingStartTime().Equal(proposalB.GetVotingStartTime()) && + proposalA.GetVotingEndTime().Equal(proposalB.GetVotingEndTime()) { return true } return false @@ -60,7 +68,7 @@ func ProposalEqual(proposalA Proposal, proposalB Proposal) bool { //----------------------------------------------------------- // Text Proposals type TextProposal struct { - ProposalID int64 `json:"proposal_id"` // ID of the proposal + ProposalID uint64 `json:"proposal_id"` // ID of the proposal Title string `json:"title"` // Title of the proposal Description string `json:"description"` // Description of the proposal ProposalType ProposalKind `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} @@ -68,18 +76,20 @@ type TextProposal struct { Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected} TallyResult TallyResult `json:"tally_result"` // Result of Tallys - SubmitTime time.Time `json:"submit_time"` // Height of the block where TxGovSubmitProposal was included - TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit + SubmitTime time.Time `json:"submit_time"` // Time of the block where TxGovSubmitProposal was included + DepositEndTime time.Time `json:"deposit_end_time"` // Time that the Proposal would expire if deposit amount isn't met + TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit - VotingStartTime time.Time `json:"voting_start_time"` // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached + VotingStartTime time.Time `json:"voting_start_time"` // Time of the block where MinDeposit was reached. -1 if MinDeposit is not reached + VotingEndTime time.Time `json:"voting_end_time"` // Time that the VotingPeriod for this proposal will end and votes will be tallied } // Implements Proposal Interface var _ Proposal = (*TextProposal)(nil) // nolint -func (tp TextProposal) GetProposalID() int64 { return tp.ProposalID } -func (tp *TextProposal) SetProposalID(proposalID int64) { tp.ProposalID = proposalID } +func (tp TextProposal) GetProposalID() uint64 { return tp.ProposalID } +func (tp *TextProposal) SetProposalID(proposalID uint64) { tp.ProposalID = proposalID } func (tp TextProposal) GetTitle() string { return tp.Title } func (tp *TextProposal) SetTitle(title string) { tp.Title = title } func (tp TextProposal) GetDescription() string { return tp.Description } @@ -92,16 +102,24 @@ func (tp TextProposal) GetTallyResult() TallyResult { return tp.T func (tp *TextProposal) SetTallyResult(tallyResult TallyResult) { tp.TallyResult = tallyResult } func (tp TextProposal) GetSubmitTime() time.Time { return tp.SubmitTime } func (tp *TextProposal) SetSubmitTime(submitTime time.Time) { tp.SubmitTime = submitTime } -func (tp TextProposal) GetTotalDeposit() sdk.Coins { return tp.TotalDeposit } -func (tp *TextProposal) SetTotalDeposit(totalDeposit sdk.Coins) { tp.TotalDeposit = totalDeposit } -func (tp TextProposal) GetVotingStartTime() time.Time { return tp.VotingStartTime } +func (tp TextProposal) GetDepositEndTime() time.Time { return tp.DepositEndTime } +func (tp *TextProposal) SetDepositEndTime(depositEndTime time.Time) { + tp.DepositEndTime = depositEndTime +} +func (tp TextProposal) GetTotalDeposit() sdk.Coins { return tp.TotalDeposit } +func (tp *TextProposal) SetTotalDeposit(totalDeposit sdk.Coins) { tp.TotalDeposit = totalDeposit } +func (tp TextProposal) GetVotingStartTime() time.Time { return tp.VotingStartTime } func (tp *TextProposal) SetVotingStartTime(votingStartTime time.Time) { tp.VotingStartTime = votingStartTime } +func (tp TextProposal) GetVotingEndTime() time.Time { return tp.VotingEndTime } +func (tp *TextProposal) SetVotingEndTime(votingEndTime time.Time) { + tp.VotingEndTime = votingEndTime +} //----------------------------------------------------------- // ProposalQueue -type ProposalQueue []int64 +type ProposalQueue []uint64 //----------------------------------------------------------- // ProposalKind diff --git a/x/gov/queryable.go b/x/gov/queryable.go index 93469a5ac..3e70d43aa 100644 --- a/x/gov/queryable.go +++ b/x/gov/queryable.go @@ -42,7 +42,7 @@ func NewQuerier(keeper Keeper) sdk.Querier { // Params for query 'custom/gov/proposal' type QueryProposalParams struct { - ProposalID int64 + ProposalID uint64 } // nolint: unparam @@ -67,7 +67,7 @@ func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, keeper // Params for query 'custom/gov/deposit' type QueryDepositParams struct { - ProposalID int64 + ProposalID uint64 Depositer sdk.AccAddress } @@ -89,7 +89,7 @@ func queryDeposit(ctx sdk.Context, path []string, req abci.RequestQuery, keeper // Params for query 'custom/gov/vote' type QueryVoteParams struct { - ProposalID int64 + ProposalID uint64 Voter sdk.AccAddress } @@ -111,7 +111,7 @@ func queryVote(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Kee // Params for query 'custom/gov/deposits' type QueryDepositsParams struct { - ProposalID int64 + ProposalID uint64 } // nolint: unparam @@ -126,7 +126,7 @@ func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper depositsIterator := keeper.GetDeposits(ctx, params.ProposalID) for ; depositsIterator.Valid(); depositsIterator.Next() { deposit := Deposit{} - keeper.cdc.MustUnmarshalBinary(depositsIterator.Value(), &deposit) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) deposits = append(deposits, deposit) } @@ -139,7 +139,7 @@ func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper // Params for query 'custom/gov/votes' type QueryVotesParams struct { - ProposalID int64 + ProposalID uint64 } // nolint: unparam @@ -155,7 +155,7 @@ func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke votesIterator := keeper.GetVotes(ctx, params.ProposalID) for ; votesIterator.Valid(); votesIterator.Next() { vote := Vote{} - keeper.cdc.MustUnmarshalBinary(votesIterator.Value(), &vote) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) votes = append(votes, vote) } @@ -168,10 +168,10 @@ func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke // Params for query 'custom/gov/proposals' type QueryProposalsParams struct { - Voter sdk.AccAddress - Depositer sdk.AccAddress - ProposalStatus ProposalStatus - NumLatestProposals int64 + Voter sdk.AccAddress + Depositer sdk.AccAddress + ProposalStatus ProposalStatus + Limit uint64 } // nolint: unparam @@ -182,7 +182,7 @@ func queryProposals(ctx sdk.Context, path []string, req abci.RequestQuery, keepe return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } - proposals := keeper.GetProposalsFiltered(ctx, params.Voter, params.Depositer, params.ProposalStatus, params.NumLatestProposals) + proposals := keeper.GetProposalsFiltered(ctx, params.Voter, params.Depositer, params.ProposalStatus, params.Limit) bz, err2 := codec.MarshalJSONIndent(keeper.cdc, proposals) if err2 != nil { @@ -193,19 +193,21 @@ func queryProposals(ctx sdk.Context, path []string, req abci.RequestQuery, keepe // Params for query 'custom/gov/tally' type QueryTallyParams struct { - ProposalID int64 + ProposalID uint64 } // nolint: unparam func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { // TODO: Dependant on #1914 - var proposalID int64 - err2 := keeper.cdc.UnmarshalJSON(req.Data, proposalID) + var params QueryTallyParams + err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } + proposalID := params.ProposalID + proposal := keeper.GetProposal(ctx, proposalID) if proposal == nil { return nil, ErrUnknownProposal(DefaultCodespace, proposalID) diff --git a/x/gov/simulation/invariants.go b/x/gov/simulation/invariants.go index 3135ac80c..6d5f41918 100644 --- a/x/gov/simulation/invariants.go +++ b/x/gov/simulation/invariants.go @@ -3,12 +3,11 @@ package simulation import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/x/mock/simulation" - abci "github.com/tendermint/tendermint/abci/types" ) // AllInvariants tests all governance invariants func AllInvariants() simulation.Invariant { - return func(app *baseapp.BaseApp, _ abci.Header) error { + return func(app *baseapp.BaseApp) error { // TODO Add some invariants! // Checking proposal queues, no passed-but-unexecuted proposals, etc. return nil diff --git a/x/gov/simulation/msgs.go b/x/gov/simulation/msgs.go index d5ff1881e..0eadc7feb 100644 --- a/x/gov/simulation/msgs.go +++ b/x/gov/simulation/msgs.go @@ -50,7 +50,7 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keepe if err != nil { return "", nil, err } - action, ok := simulateHandleMsgSubmitProposal(msg, sk, handler, ctx, event) + action, ok := simulateHandleMsgSubmitProposal(msg, handler, ctx, event) // don't schedule votes if proposal failed if !ok { return action, nil, nil @@ -64,11 +64,11 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keepe whoVotes := r.Perm(len(accs)) // didntVote := whoVotes[numVotes:] whoVotes = whoVotes[:numVotes] - votingPeriod := k.GetVotingProcedure(ctx).VotingPeriod + votingPeriod := k.GetVotingParams(ctx).VotingPeriod fops := make([]simulation.FutureOperation, numVotes+1) for i := 0; i < numVotes; i++ { whenVote := ctx.BlockHeader().Time.Add(time.Duration(r.Int63n(int64(votingPeriod.Seconds()))) * time.Second) - fops[i] = simulation.FutureOperation{BlockTime: whenVote, Op: operationSimulateMsgVote(k, sk, accs[whoVotes[i]], proposalID)} + fops[i] = simulation.FutureOperation{BlockTime: whenVote, Op: operationSimulateMsgVote(k, accs[whoVotes[i]], proposalID)} } // 3) Make an operation to ensure slashes were done correctly. (Really should be a future invariant) // TODO: Find a way to check if a validator was slashed other than just checking their balance a block @@ -80,7 +80,7 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keepe // SimulateMsgSubmitProposal simulates a msg Submit Proposal // Note: Currently doesn't ensure that the proposal txt is in JSON form -func SimulateMsgSubmitProposal(k gov.Keeper, sk stake.Keeper) simulation.Operation { +func SimulateMsgSubmitProposal(k gov.Keeper) simulation.Operation { handler := gov.NewHandler(k) return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) { sender := simulation.RandomAcc(r, accs) @@ -88,22 +88,14 @@ func SimulateMsgSubmitProposal(k gov.Keeper, sk stake.Keeper) simulation.Operati if err != nil { return "", nil, err } - action, _ = simulateHandleMsgSubmitProposal(msg, sk, handler, ctx, event) + action, _ = simulateHandleMsgSubmitProposal(msg, handler, ctx, event) return action, nil, nil } } -func simulateHandleMsgSubmitProposal(msg gov.MsgSubmitProposal, sk stake.Keeper, handler sdk.Handler, ctx sdk.Context, event func(string)) (action string, ok bool) { - ctx, write := ctx.CacheContext() - result := handler(ctx, msg) - ok = result.IsOK() - if ok { - // Update pool to keep invariants - pool := sk.GetPool(ctx) - pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewDecFromInt(msg.InitialDeposit.AmountOf(denom))) - sk.SetPool(ctx, pool) - write() - } +func simulateHandleMsgSubmitProposal(msg gov.MsgSubmitProposal, handler sdk.Handler, ctx sdk.Context, event func(string)) (action string, ok bool) { + ctx, _ = ctx.CacheContext() + handler(ctx, msg) event(fmt.Sprintf("gov/MsgSubmitProposal/%v", ok)) action = fmt.Sprintf("TestMsgSubmitProposal: ok %v, msg %s", ok, msg.GetSignBytes()) return @@ -125,7 +117,7 @@ func simulationCreateMsgSubmitProposal(r *rand.Rand, sender simulation.Account) } // SimulateMsgDeposit -func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation { +func SimulateMsgDeposit(k gov.Keeper) simulation.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { acc := simulation.RandomAcc(r, accs) proposalID, ok := randomProposalID(r, k, ctx) @@ -137,15 +129,8 @@ func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation { if msg.ValidateBasic() != nil { return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } - ctx, write := ctx.CacheContext() + ctx, _ = ctx.CacheContext() result := gov.NewHandler(k)(ctx, msg) - if result.IsOK() { - // Update pool to keep invariants - pool := sk.GetPool(ctx) - pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewDecFromInt(deposit.AmountOf(denom))) - sk.SetPool(ctx, pool) - write() - } event(fmt.Sprintf("gov/MsgDeposit/%v", result.IsOK())) action = fmt.Sprintf("TestMsgDeposit: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil, nil @@ -154,12 +139,12 @@ func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation { // SimulateMsgVote // nolint: unparam -func SimulateMsgVote(k gov.Keeper, sk stake.Keeper) simulation.Operation { - return operationSimulateMsgVote(k, sk, simulation.Account{}, -1) +func SimulateMsgVote(k gov.Keeper) simulation.Operation { + return operationSimulateMsgVote(k, simulation.Account{}, 0) } // nolint: unparam -func operationSimulateMsgVote(k gov.Keeper, sk stake.Keeper, acc simulation.Account, proposalID int64) simulation.Operation { +func operationSimulateMsgVote(k gov.Keeper, acc simulation.Account, proposalID uint64) simulation.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { if acc.Equals(simulation.Account{}) { acc = simulation.RandomAcc(r, accs) @@ -200,12 +185,12 @@ func randomDeposit(r *rand.Rand) sdk.Coins { } // Pick a random proposal ID -func randomProposalID(r *rand.Rand, k gov.Keeper, ctx sdk.Context) (proposalID int64, ok bool) { +func randomProposalID(r *rand.Rand, k gov.Keeper, ctx sdk.Context) (proposalID uint64, ok bool) { lastProposalID := k.GetLastProposalID(ctx) if lastProposalID < 1 { return 0, false } - proposalID = int64(r.Intn(int(lastProposalID))) + proposalID = uint64(r.Intn(int(lastProposalID))) return proposalID, true } diff --git a/x/gov/simulation/sim_test.go b/x/gov/simulation/sim_test.go index a222c19a5..5b6068c57 100644 --- a/x/gov/simulation/sim_test.go +++ b/x/gov/simulation/sim_test.go @@ -59,9 +59,9 @@ func TestGovWithRandomMessages(t *testing.T) { simulation.Simulate( t, mapp.BaseApp, appStateFn, []simulation.WeightedOperation{ - {2, SimulateMsgSubmitProposal(govKeeper, stakeKeeper)}, - {3, SimulateMsgDeposit(govKeeper, stakeKeeper)}, - {20, SimulateMsgVote(govKeeper, stakeKeeper)}, + {2, SimulateMsgSubmitProposal(govKeeper)}, + {3, SimulateMsgDeposit(govKeeper)}, + {20, SimulateMsgVote(govKeeper)}, }, []simulation.RandSetup{ setup, }, []simulation.Invariant{ @@ -75,7 +75,7 @@ func TestGovWithRandomMessages(t *testing.T) { t, mapp.BaseApp, appStateFn, []simulation.WeightedOperation{ {10, SimulateSubmittingVotingAndSlashingForProposal(govKeeper, stakeKeeper)}, - {5, SimulateMsgDeposit(govKeeper, stakeKeeper)}, + {5, SimulateMsgDeposit(govKeeper)}, }, []simulation.RandSetup{ setup, }, []simulation.Invariant{ diff --git a/x/gov/tally.go b/x/gov/tally.go index b6e42c4b5..d66237762 100644 --- a/x/gov/tally.go +++ b/x/gov/tally.go @@ -23,7 +23,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall totalVotingPower := sdk.ZeroDec() currValidators := make(map[string]validatorGovInfo) - keeper.vs.IterateValidatorsBonded(ctx, func(index int64, validator sdk.Validator) (stop bool) { + keeper.vs.IterateBondedValidatorsByPower(ctx, func(index int64, validator sdk.Validator) (stop bool) { currValidators[validator.GetOperator().String()] = validatorGovInfo{ Address: validator.GetOperator(), Power: validator.GetPower(), @@ -39,7 +39,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall defer votesIterator.Close() for ; votesIterator.Valid(); votesIterator.Next() { vote := &Vote{} - keeper.cdc.MustUnmarshalBinary(votesIterator.Value(), vote) + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), vote) // if validator, just record it in the map // if delegator tally voting power @@ -84,7 +84,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall totalVotingPower = totalVotingPower.Add(votingPower) } - tallyingProcedure := keeper.GetTallyingProcedure(ctx) + tallyParams := keeper.GetTallyParams(ctx) tallyResults = TallyResult{ Yes: results[OptionYes], @@ -98,11 +98,11 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall return false, tallyResults } // If more than 1/3 of voters veto, proposal fails - if results[OptionNoWithVeto].Quo(totalVotingPower).GT(tallyingProcedure.Veto) { + if results[OptionNoWithVeto].Quo(totalVotingPower).GT(tallyParams.Veto) { return false, tallyResults } // If more than 1/2 of non-abstaining voters vote Yes, proposal passes - if results[OptionYes].Quo(totalVotingPower.Sub(results[OptionAbstain])).GT(tallyingProcedure.Threshold) { + if results[OptionYes].Quo(totalVotingPower.Sub(results[OptionAbstain])).GT(tallyParams.Threshold) { return true, tallyResults } // If more than 1/2 of non-abstaining voters vote No, proposal fails diff --git a/x/ibc/client/cli/relay.go b/x/ibc/client/cli/relay.go index ab7168aca..43ad783f4 100644 --- a/x/ibc/client/cli/relay.go +++ b/x/ibc/client/cli/relay.go @@ -117,7 +117,7 @@ OUTER: var processed int64 if processedbz == nil { processed = 0 - } else if err = c.cdc.UnmarshalBinary(processedbz, &processed); err != nil { + } else if err = c.cdc.UnmarshalBinaryLengthPrefixed(processedbz, &processed); err != nil { panic(err) } @@ -131,7 +131,7 @@ OUTER: var egressLength int64 if egressLengthbz == nil { egressLength = 0 - } else if err = c.cdc.UnmarshalBinary(egressLengthbz, &egressLength); err != nil { + } else if err = c.cdc.UnmarshalBinaryLengthPrefixed(egressLengthbz, &egressLength); err != nil { panic(err) } @@ -192,7 +192,7 @@ func (c relayCommander) getSequence(node string) int64 { func (c relayCommander) refine(bz []byte, sequence int64, passphrase string) []byte { var packet ibc.IBCPacket - if err := c.cdc.UnmarshalBinary(bz, &packet); err != nil { + if err := c.cdc.UnmarshalBinaryLengthPrefixed(bz, &packet); err != nil { panic(err) } diff --git a/x/ibc/mapper.go b/x/ibc/mapper.go index 95c88b62f..957ab191a 100644 --- a/x/ibc/mapper.go +++ b/x/ibc/mapper.go @@ -33,13 +33,13 @@ func (ibcm Mapper) PostIBCPacket(ctx sdk.Context, packet IBCPacket) sdk.Error { // write everything into the state store := ctx.KVStore(ibcm.key) index := ibcm.getEgressLength(store, packet.DestChain) - bz, err := ibcm.cdc.MarshalBinary(packet) + bz, err := ibcm.cdc.MarshalBinaryLengthPrefixed(packet) if err != nil { panic(err) } store.Set(EgressKey(packet.DestChain, index), bz) - bz, err = ibcm.cdc.MarshalBinary(index + 1) + bz, err = ibcm.cdc.MarshalBinaryLengthPrefixed(index + 1) if err != nil { panic(err) } @@ -61,7 +61,7 @@ func (ibcm Mapper) ReceiveIBCPacket(ctx sdk.Context, packet IBCPacket) sdk.Error // Functions for accessing the underlying KVStore. func marshalBinaryPanic(cdc *codec.Codec, value interface{}) []byte { - res, err := cdc.MarshalBinary(value) + res, err := cdc.MarshalBinaryLengthPrefixed(value) if err != nil { panic(err) } @@ -69,7 +69,7 @@ func marshalBinaryPanic(cdc *codec.Codec, value interface{}) []byte { } func unmarshalBinaryPanic(cdc *codec.Codec, bz []byte, ptr interface{}) { - err := cdc.UnmarshalBinary(bz, ptr) + err := cdc.UnmarshalBinaryLengthPrefixed(bz, ptr) if err != nil { panic(err) } diff --git a/x/mint/genesis.go b/x/mint/genesis.go index 561768573..ce375d71e 100644 --- a/x/mint/genesis.go +++ b/x/mint/genesis.go @@ -31,9 +31,9 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { keeper.SetParams(ctx, data.Params) } -// WriteGenesis returns a GenesisState for a given context and keeper. The +// ExportGenesis returns a GenesisState for a given context and keeper. The // GenesisState will contain the pool, and validator/delegator distribution info's -func WriteGenesis(ctx sdk.Context, keeper Keeper) GenesisState { +func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { minter := keeper.GetMinter(ctx) params := keeper.GetParams(ctx) diff --git a/x/mint/keeper.go b/x/mint/keeper.go index 6e6a642f4..eba4c3fc1 100644 --- a/x/mint/keeper.go +++ b/x/mint/keeper.go @@ -59,14 +59,14 @@ func (k Keeper) GetMinter(ctx sdk.Context) (minter Minter) { if b == nil { panic("Stored fee pool should not have been nil") } - k.cdc.MustUnmarshalBinary(b, &minter) + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &minter) return } // set the minter func (k Keeper) SetMinter(ctx sdk.Context, minter Minter) { store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(minter) + b := k.cdc.MustMarshalBinaryLengthPrefixed(minter) store.Set(minterKey, b) } diff --git a/x/mock/simulation/constants.go b/x/mock/simulation/constants.go deleted file mode 100644 index a96d4541f..000000000 --- a/x/mock/simulation/constants.go +++ /dev/null @@ -1,31 +0,0 @@ -package simulation - -const ( - // Fraction of double-signing evidence from a past height - pastEvidenceFraction float64 = 0.5 - - // Minimum time per block - minTimePerBlock int64 = 1000 / 2 - - // Maximum time per block - maxTimePerBlock int64 = 1000 - - // Number of keys - numKeys int = 250 - - // Chance that double-signing evidence is found on a given block - evidenceFraction float64 = 0.5 - - // TODO Remove in favor of binary search for invariant violation - onOperation bool = false -) - -var ( - // Currently there are 3 different liveness types, fully online, spotty connection, offline. - initialLivenessWeightings = []int{40, 5, 5} - livenessTransitionMatrix, _ = CreateTransitionMatrix([][]int{ - {90, 20, 1}, - {10, 50, 5}, - {0, 10, 1000}, - }) -) diff --git a/x/mock/simulation/params.go b/x/mock/simulation/params.go new file mode 100644 index 000000000..404a85e54 --- /dev/null +++ b/x/mock/simulation/params.go @@ -0,0 +1,66 @@ +package simulation + +import ( + "math/rand" +) + +const ( + // Minimum time per block + minTimePerBlock int64 = 10000 / 2 + + // Maximum time per block + maxTimePerBlock int64 = 10000 + + // TODO Remove in favor of binary search for invariant violation + onOperation bool = false +) + +var ( + // Currently there are 3 different liveness types, fully online, spotty connection, offline. + defaultLivenessTransitionMatrix, _ = CreateTransitionMatrix([][]int{ + {90, 20, 1}, + {10, 50, 5}, + {0, 10, 1000}, + }) + + // 3 states: rand in range [0, 4*provided blocksize], rand in range [0, 2 * provided blocksize], 0 + defaultBlockSizeTransitionMatrix, _ = CreateTransitionMatrix([][]int{ + {85, 5, 0}, + {15, 92, 1}, + {0, 3, 99}, + }) +) + +// Simulation parameters +type Params struct { + PastEvidenceFraction float64 + NumKeys int + EvidenceFraction float64 + InitialLivenessWeightings []int + LivenessTransitionMatrix TransitionMatrix + BlockSizeTransitionMatrix TransitionMatrix +} + +// Return default simulation parameters +func DefaultParams() Params { + return Params{ + PastEvidenceFraction: 0.5, + NumKeys: 250, + EvidenceFraction: 0.5, + InitialLivenessWeightings: []int{40, 5, 5}, + LivenessTransitionMatrix: defaultLivenessTransitionMatrix, + BlockSizeTransitionMatrix: defaultBlockSizeTransitionMatrix, + } +} + +// Return random simulation parameters +func RandomParams(r *rand.Rand) Params { + return Params{ + PastEvidenceFraction: r.Float64(), + NumKeys: r.Intn(250), + EvidenceFraction: r.Float64(), + InitialLivenessWeightings: []int{r.Intn(80), r.Intn(10), r.Intn(10)}, + LivenessTransitionMatrix: defaultLivenessTransitionMatrix, + BlockSizeTransitionMatrix: defaultBlockSizeTransitionMatrix, + } +} diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index 2506af377..a568997e6 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -3,7 +3,6 @@ package simulation import ( "encoding/json" "fmt" - "math" "math/rand" "os" "os/signal" @@ -15,7 +14,7 @@ import ( "time" abci "github.com/tendermint/tendermint/abci/types" - common "github.com/tendermint/tendermint/libs/common" + cmn "github.com/tendermint/tendermint/libs/common" tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/baseapp" @@ -32,13 +31,13 @@ func Simulate(t *testing.T, app *baseapp.BaseApp, return SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numBlocks, blockSize, commit) } -func initChain(r *rand.Rand, accounts []Account, setups []RandSetup, app *baseapp.BaseApp, +func initChain(r *rand.Rand, params Params, accounts []Account, setups []RandSetup, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, accounts []Account) json.RawMessage) (validators map[string]mockValidator) { res := app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, accounts)}) validators = make(map[string]mockValidator) for _, validator := range res.Validators { str := fmt.Sprintf("%v", validator.PubKey) - validators[str] = mockValidator{validator, GetMemberOfInitialState(r, initialLivenessWeightings)} + validators[str] = mockValidator{validator, GetMemberOfInitialState(r, params.InitialLivenessWeightings)} } for i := 0; i < len(setups); i++ { @@ -49,7 +48,8 @@ func initChain(r *rand.Rand, accounts []Account, setups []RandSetup, app *baseap } func randTimestamp(r *rand.Rand) time.Time { - unixTime := r.Int63n(int64(math.Pow(2, 40))) + // json.Marshal breaks for timestamps greater with year greater than 9999 + unixTime := r.Int63n(253373529600) return time.Unix(unixTime, 0) } @@ -65,11 +65,13 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, testingMode, t, b := getTestingMode(tb) fmt.Printf("Starting SimulateFromSeed with randomness created with seed %d\n", int(seed)) r := rand.New(rand.NewSource(seed)) + params := RandomParams(r) // := DefaultParams() + fmt.Printf("Randomized simulation params: %+v\n", params) timestamp := randTimestamp(r) fmt.Printf("Starting the simulation from time %v, unixtime %v\n", timestamp.UTC().Format(time.UnixDate), timestamp.Unix()) timeDiff := maxTimePerBlock - minTimePerBlock - accs := RandomAccounts(r, numKeys) + accs := RandomAccounts(r, params.NumKeys) // Setup event stats events := make(map[string]uint) @@ -77,7 +79,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, events[what]++ } - validators := initChain(r, accs, setups, app, appStateFn) + validators := initChain(r, params, accs, setups, app, appStateFn) // Second variable to keep pending validator set (delayed one block since TM 0.24) // Initially this is the same as the initial validator set nextValidators := validators @@ -90,7 +92,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) go func() { receivedSignal := <-c - fmt.Printf("Exiting early due to %s, on block %d, operation %d\n", receivedSignal, header.Height, opCount) + fmt.Printf("\nExiting early due to %s, on block %d, operation %d\n", receivedSignal, header.Height, opCount) simError = fmt.Errorf("Exited due to %s", receivedSignal) stopEarly = true }() @@ -98,7 +100,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, var pastTimes []time.Time var pastVoteInfos [][]abci.VoteInfo - request := RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastVoteInfos, event, header) + request := RandomRequestBeginBlock(r, params, validators, pastTimes, pastVoteInfos, event, header) // These are operations which have been queued by previous operations operationQueue := make(map[int][]Operation) timeOperationQueue := []FutureOperation{} @@ -108,7 +110,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, blockLogBuilders = make([]*strings.Builder, numBlocks) } displayLogs := logPrinter(testingMode, blockLogBuilders) - blockSimulator := createBlockSimulator(testingMode, tb, t, event, invariants, ops, operationQueue, timeOperationQueue, numBlocks, displayLogs) + blockSimulator := createBlockSimulator(testingMode, tb, t, params, event, invariants, ops, operationQueue, timeOperationQueue, numBlocks, blockSize, displayLogs) if !testingMode { b.ResetTimer() } else { @@ -138,11 +140,10 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, if testingMode { // Make sure invariants hold at beginning of block - assertAllInvariants(t, app, header, invariants, "BeginBlock", displayLogs) + assertAllInvariants(t, app, invariants, "BeginBlock", displayLogs) } ctx := app.NewContext(false, header) - thisBlockSize := getBlockSize(r, blockSize) // Run queued operations. Ignores blocksize if blocksize is too small logWriter("Queued operations") @@ -150,16 +151,15 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, numQueuedTimeOpsRan := runQueuedTimeOperations(timeOperationQueue, header.Time, tb, r, app, ctx, accs, logWriter, displayLogs, event) if testingMode && onOperation { // Make sure invariants hold at end of queued operations - assertAllInvariants(t, app, header, invariants, "QueuedOperations", displayLogs) + assertAllInvariants(t, app, invariants, "QueuedOperations", displayLogs) } - thisBlockSize = thisBlockSize - numQueuedOpsRan - numQueuedTimeOpsRan logWriter("Standard operations") - operations := blockSimulator(thisBlockSize, r, app, ctx, accs, header, logWriter) + operations := blockSimulator(r, app, ctx, accs, header, logWriter) opCount += operations + numQueuedOpsRan + numQueuedTimeOpsRan if testingMode { // Make sure invariants hold at end of block - assertAllInvariants(t, app, header, invariants, "StandardOperations", displayLogs) + assertAllInvariants(t, app, invariants, "StandardOperations", displayLogs) } res := app.EndBlock(abci.RequestEndBlock{}) @@ -170,7 +170,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, if testingMode { // Make sure invariants hold at end of block - assertAllInvariants(t, app, header, invariants, "EndBlock", displayLogs) + assertAllInvariants(t, app, invariants, "EndBlock", displayLogs) } if commit { app.Commit() @@ -183,11 +183,11 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, } // Generate a random RequestBeginBlock with the current validator set for the next block - request = RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastVoteInfos, event, header) + request = RandomRequestBeginBlock(r, params, validators, pastTimes, pastVoteInfos, event, header) // Update the validator set, which will be reflected in the application on the next block validators = nextValidators - nextValidators = updateValidators(tb, r, validators, res.ValidatorUpdates, event) + nextValidators = updateValidators(tb, r, params, validators, res.ValidatorUpdates, event) } if stopEarly { DisplayEvents(events) @@ -198,11 +198,24 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, return nil } +type blockSimFn func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accounts []Account, header abci.Header, logWriter func(string), +) (opCount int) + // Returns a function to simulate blocks. Written like this to avoid constant parameters being passed everytime, to minimize // memory overhead -func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, 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 +func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, params Params, + event func(string), invariants []Invariant, + ops []WeightedOperation, operationQueue map[int][]Operation, timeOperationQueue []FutureOperation, + totalNumBlocks int, avgBlockSize int, displayLogs func()) blockSimFn { + + var ( + lastBlocksizeState = 0 // state for [4 * uniform distribution] + totalOpWeight = 0 + blocksize int + ) + for i := 0; i < len(ops); i++ { totalOpWeight += ops[i].Weight } @@ -217,20 +230,23 @@ func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, event f // shouldn't happen return ops[0].Op } - return func(blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []Account, header abci.Header, logWriter func(string)) (opCount int) { + fmt.Printf("\rSimulating... block %d/%d, operation %d/%d. ", header.Height, totalNumBlocks, opCount, blocksize) + lastBlocksizeState, blocksize = getBlockSize(r, params, lastBlocksizeState, avgBlockSize) for j := 0; j < blocksize; j++ { logUpdate, futureOps, err := selectOp(r)(r, app, ctx, accounts, event) + logWriter(logUpdate) if err != nil { displayLogs() tb.Fatalf("error on operation %d within block %d, %v", header.Height, opCount, err) } - logWriter(logUpdate) queueOperations(operationQueue, timeOperationQueue, futureOps) if testingMode { if onOperation { - assertAllInvariants(t, app, header, invariants, fmt.Sprintf("operation: %v", logUpdate), displayLogs) + assertAllInvariants(t, app, invariants, fmt.Sprintf("operation: %v", logUpdate), displayLogs) } if opCount%50 == 0 { fmt.Printf("\rSimulating... block %d/%d, operation %d/%d. ", header.Height, totalNumBlocks, opCount, blocksize) @@ -253,16 +269,24 @@ func getTestingMode(tb testing.TB) (testingMode bool, t *testing.T, b *testing.B return } -func getBlockSize(r *rand.Rand, blockSize int) int { - load := r.Float64() - switch { - case load < 0.33: - return 0 - case load < 0.66: - return r.Intn(blockSize * 2) - default: - return r.Intn(blockSize * 4) +// getBlockSize returns a block size as determined from the transition matrix. +// It targets making average block size the provided parameter. The three +// states it moves between are: +// "over stuffed" blocks with average size of 2 * avgblocksize, +// normal sized blocks, hitting avgBlocksize on average, +// and empty blocks, with no txs / only txs scheduled from the past. +func getBlockSize(r *rand.Rand, params Params, lastBlockSizeState, avgBlockSize int) (state, blocksize int) { + // TODO: Make default blocksize transition matrix actually make the average + // blocksize equal to avgBlockSize. + state = params.BlockSizeTransitionMatrix.NextState(r, lastBlockSizeState) + if state == 0 { + blocksize = r.Intn(avgBlockSize * 4) + } else if state == 1 { + blocksize = r.Intn(avgBlockSize * 2) + } else { + blocksize = 0 } + return } // adds all future operations into the operation queue. @@ -341,7 +365,7 @@ func getKeys(validators map[string]mockValidator) []string { } // randomProposer picks a random proposer from the current validator set -func randomProposer(r *rand.Rand, validators map[string]mockValidator) common.HexBytes { +func randomProposer(r *rand.Rand, validators map[string]mockValidator) cmn.HexBytes { keys := getKeys(validators) if len(keys) == 0 { return nil @@ -357,7 +381,7 @@ func randomProposer(r *rand.Rand, validators map[string]mockValidator) common.He // 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, +func RandomRequestBeginBlock(r *rand.Rand, params Params, validators map[string]mockValidator, pastTimes []time.Time, pastVoteInfos [][]abci.VoteInfo, event func(string), header abci.Header) abci.RequestBeginBlock { if len(validators) == 0 { return abci.RequestBeginBlock{Header: header} @@ -366,7 +390,7 @@ func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator, i := 0 for _, key := range getKeys(validators) { mVal := validators[key] - mVal.livenessState = livenessTransitions.NextState(r, mVal.livenessState) + mVal.livenessState = params.LivenessTransitionMatrix.NextState(r, mVal.livenessState) signed := true if mVal.livenessState == 1 { @@ -400,11 +424,11 @@ func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator, evidence := make([]abci.Evidence, 0) // Anything but the first block if len(pastTimes) > 0 { - for r.Float64() < evidenceFraction { + for r.Float64() < params.EvidenceFraction { height := header.Height time := header.Time vals := voteInfos - if r.Float64() < pastEvidenceFraction { + if r.Float64() < params.PastEvidenceFraction { height = int64(r.Intn(int(header.Height) - 1)) time = pastTimes[height] vals = pastVoteInfos[height] @@ -435,7 +459,7 @@ func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator, // 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 { +func updateValidators(tb testing.TB, r *rand.Rand, params Params, current map[string]mockValidator, updates []abci.ValidatorUpdate, event func(string)) map[string]mockValidator { for _, update := range updates { str := fmt.Sprintf("%v", update.PubKey) @@ -454,7 +478,7 @@ func updateValidators(tb testing.TB, r *rand.Rand, current map[string]mockValida event("endblock/validatorupdates/updated") } else { // Set this new validator - current[str] = mockValidator{update, GetMemberOfInitialState(r, initialLivenessWeightings)} + current[str] = mockValidator{update, GetMemberOfInitialState(r, params.InitialLivenessWeightings)} event("endblock/validatorupdates/added") } } diff --git a/x/mock/simulation/types.go b/x/mock/simulation/types.go index 0a7c989a5..e601f2e1f 100644 --- a/x/mock/simulation/types.go +++ b/x/mock/simulation/types.go @@ -33,7 +33,7 @@ type ( // 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, header abci.Header) error + Invariant func(app *baseapp.BaseApp) error // Account contains a privkey, pubkey, address tuple // eventually more useful data can be placed in here. @@ -73,9 +73,9 @@ type ( // a given invariant if the mock application's last block modulo the given // period is congruent to the given offset. func PeriodicInvariant(invariant Invariant, period int, offset int) Invariant { - return func(app *baseapp.BaseApp, header abci.Header) error { + return func(app *baseapp.BaseApp) error { if int(app.LastBlockHeight())%period == offset { - return invariant(app, header) + return invariant(app) } return nil } diff --git a/x/mock/simulation/util.go b/x/mock/simulation/util.go index c408c5328..62b253a25 100644 --- a/x/mock/simulation/util.go +++ b/x/mock/simulation/util.go @@ -2,6 +2,7 @@ package simulation import ( "fmt" + "math/big" "math/rand" "os" "sort" @@ -9,7 +10,6 @@ import ( "testing" "time" - abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/secp256k1" @@ -71,6 +71,12 @@ func RandomAmount(r *rand.Rand, max sdk.Int) sdk.Int { return sdk.NewInt(int64(r.Intn(int(max.Int64())))) } +// RandomDecAmount generates a random decimal amount +func RandomDecAmount(r *rand.Rand, max sdk.Dec) sdk.Dec { + randInt := big.NewInt(0).Rand(r, max.Int) + return sdk.NewDecFromBigIntWithPrec(randInt, sdk.Precision) +} + // RandomAccounts generates n random accounts func RandomAccounts(r *rand.Rand, n int) []Account { accs := make([]Account, n) @@ -103,11 +109,11 @@ func addLogMessage(testingmode bool, blockLogBuilders []*strings.Builder, height } // assertAllInvariants asserts a list of provided invariants against application state -func assertAllInvariants(t *testing.T, app *baseapp.BaseApp, header abci.Header, +func assertAllInvariants(t *testing.T, app *baseapp.BaseApp, invariants []Invariant, where string, displayLogs func()) { for i := 0; i < len(invariants); i++ { - err := invariants[i](app, header) + err := invariants[i](app) if err != nil { fmt.Printf("Invariants broken after %s\n", where) fmt.Println(err.Error()) @@ -134,7 +140,7 @@ func logPrinter(testingmode bool, logs []*strings.Builder) func() { for i := 0; i < len(logs); i++ { // We're passed the last created block if logs[i] == nil { - numLoggers = i - 1 + numLoggers = i break } } @@ -146,7 +152,7 @@ func logPrinter(testingmode bool, logs []*strings.Builder) func() { } for i := 0; i < numLoggers; i++ { if f != nil { - _, err := f.WriteString(fmt.Sprintf("Begin block %d\n", i)) + _, err := f.WriteString(fmt.Sprintf("Begin block %d\n", i+1)) if err != nil { panic("Failed to write logs to file") } @@ -155,7 +161,7 @@ func logPrinter(testingmode bool, logs []*strings.Builder) func() { panic("Failed to write logs to file") } } else { - fmt.Printf("Begin block %d\n", i) + fmt.Printf("Begin block %d\n", i+1) fmt.Println((*logs[i]).String()) } } diff --git a/x/slashing/client/cli/query.go b/x/slashing/client/cli/query.go index 50ccc6c0e..de4fc5d57 100644 --- a/x/slashing/client/cli/query.go +++ b/x/slashing/client/cli/query.go @@ -34,7 +34,7 @@ func GetCmdQuerySigningInfo(storeName string, cdc *codec.Codec) *cobra.Command { } signingInfo := new(slashing.ValidatorSigningInfo) - cdc.MustUnmarshalBinary(res, signingInfo) + cdc.MustUnmarshalBinaryLengthPrefixed(res, signingInfo) switch viper.Get(cli.OutputFlag) { diff --git a/x/slashing/client/rest/query.go b/x/slashing/client/rest/query.go index 99db0df63..e55b6b9be 100644 --- a/x/slashing/client/rest/query.go +++ b/x/slashing/client/rest/query.go @@ -45,7 +45,7 @@ func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *code var signingInfo slashing.ValidatorSigningInfo - err = cdc.UnmarshalBinary(res, &signingInfo) + err = cdc.UnmarshalBinaryLengthPrefixed(res, &signingInfo) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return diff --git a/x/slashing/genesis.go b/x/slashing/genesis.go index 10af155d6..614bf41eb 100644 --- a/x/slashing/genesis.go +++ b/x/slashing/genesis.go @@ -7,13 +7,25 @@ import ( // GenesisState - all slashing state that must be provided at genesis type GenesisState struct { - Params Params + Params Params + SigningInfos map[string]ValidatorSigningInfo + MissedBlocks map[string][]MissedBlock + SlashingPeriods []ValidatorSlashingPeriod +} + +// MissedBlock +type MissedBlock struct { + Index int64 `json:"index"` + Missed bool `json:"missed"` } // HubDefaultGenesisState - default GenesisState used by Cosmos Hub func DefaultGenesisState() GenesisState { return GenesisState{ - Params: DefaultParams(), + Params: DefaultParams(), + SigningInfos: make(map[string]ValidatorSigningInfo), + MissedBlocks: make(map[string][]MissedBlock), + SlashingPeriods: []ValidatorSlashingPeriod{}, } } @@ -24,5 +36,64 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState, sdata types. keeper.addPubkey(ctx, validator.GetConsPubKey()) } + for addr, info := range data.SigningInfos { + address, err := sdk.ConsAddressFromBech32(addr) + if err != nil { + panic(err) + } + keeper.setValidatorSigningInfo(ctx, address, info) + } + + for addr, array := range data.MissedBlocks { + address, err := sdk.ConsAddressFromBech32(addr) + if err != nil { + panic(err) + } + for _, missed := range array { + keeper.setValidatorMissedBlockBitArray(ctx, address, missed.Index, missed.Missed) + } + } + + for _, slashingPeriod := range data.SlashingPeriods { + keeper.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod) + } + keeper.paramspace.SetParamSet(ctx, &data.Params) } + +// ExportGenesis writes the current store values +// to a genesis file, which can be imported again +// with InitGenesis +func ExportGenesis(ctx sdk.Context, keeper Keeper) (data GenesisState) { + var params Params + keeper.paramspace.GetParamSet(ctx, ¶ms) + + signingInfos := make(map[string]ValidatorSigningInfo) + missedBlocks := make(map[string][]MissedBlock) + keeper.iterateValidatorSigningInfos(ctx, func(address sdk.ConsAddress, info ValidatorSigningInfo) (stop bool) { + bechAddr := address.String() + signingInfos[bechAddr] = info + array := []MissedBlock{} + + keeper.iterateValidatorMissedBlockBitArray(ctx, address, func(index int64, missed bool) (stop bool) { + array = append(array, MissedBlock{index, missed}) + return false + }) + missedBlocks[bechAddr] = array + + return false + }) + + slashingPeriods := []ValidatorSlashingPeriod{} + keeper.iterateValidatorSlashingPeriods(ctx, func(slashingPeriod ValidatorSlashingPeriod) (stop bool) { + slashingPeriods = append(slashingPeriods, slashingPeriod) + return false + }) + + return GenesisState{ + Params: params, + SigningInfos: signingInfos, + MissedBlocks: missedBlocks, + SlashingPeriods: slashingPeriods, + } +} diff --git a/x/slashing/hooks.go b/x/slashing/hooks.go index 10c16d199..e09f6c566 100644 --- a/x/slashing/hooks.go +++ b/x/slashing/hooks.go @@ -3,6 +3,8 @@ package slashing import ( "time" + "github.com/tendermint/tendermint/crypto" + sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -36,6 +38,17 @@ func (k Keeper) onValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddre k.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod) } +// When a validator is created, add the address-pubkey relation. +func (k Keeper) onValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { + validator := k.validatorSet.Validator(ctx, valAddr) + k.addPubkey(ctx, validator.GetConsPubKey()) +} + +// When a validator is removed, delete the address-pubkey relation. +func (k Keeper) onValidatorRemoved(ctx sdk.Context, address sdk.ConsAddress) { + k.deleteAddrPubkeyRelation(ctx, crypto.Address(address)) +} + //_________________________________________________________________________________________ // Wrapper struct @@ -60,12 +73,20 @@ func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddre h.k.onValidatorBeginUnbonding(ctx, consAddr, valAddr) } +// Implements sdk.ValidatorHooks +func (h Hooks) OnValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, _ sdk.ValAddress) { + h.k.onValidatorRemoved(ctx, consAddr) +} + +// Implements sdk.ValidatorHooks +func (h Hooks) OnValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { + h.k.onValidatorCreated(ctx, valAddr) +} + // nolint - unused hooks func (h Hooks) OnValidatorPowerDidChange(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { } -func (h Hooks) OnValidatorCreated(_ sdk.Context, _ sdk.ValAddress) {} func (h Hooks) OnValidatorModified(_ 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 647a9df09..fe1531f22 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -4,13 +4,10 @@ import ( "fmt" "time" - tmtypes "github.com/tendermint/tendermint/types" - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" stake "github.com/cosmos/cosmos-sdk/x/stake/types" - abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" ) @@ -49,6 +46,15 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr)) } + // Get validator. + validator := k.validatorSet.ValidatorByConsAddr(ctx, consAddr) + if validator == nil || validator.GetStatus() == sdk.Unbonded { + // Defensive. + // Simulation doesn't take unbonding periods into account, and + // Tendermint might break this assumption at some point. + return + } + // Double sign too old maxEvidenceAge := k.MaxEvidenceAge(ctx) if age > maxEvidenceAge { @@ -80,7 +86,6 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, revisedFraction) // Jail validator if not already jailed - validator := k.validatorSet.ValidatorByConsAddr(ctx, consAddr) if !validator.GetJailed() { k.validatorSet.Jail(ctx, consAddr) } @@ -166,19 +171,6 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p k.setValidatorSigningInfo(ctx, consAddr, signInfo) } -// AddValidators adds the validators to the keepers validator addr to pubkey mapping. -func (k Keeper) AddValidators(ctx sdk.Context, vals []abci.ValidatorUpdate) { - for i := 0; i < len(vals); i++ { - val := vals[i] - pubkey, err := tmtypes.PB2TM.PubKey(val.PubKey) - if err != nil { - panic(err) - } - k.addPubkey(ctx, pubkey) - } -} - -// TODO: Make a method to remove the pubkey from the map when a validator is unbonded. func (k Keeper) addPubkey(ctx sdk.Context, pubkey crypto.PubKey) { addr := pubkey.Address() k.setAddrPubkeyRelation(ctx, addr, pubkey) @@ -187,7 +179,7 @@ func (k Keeper) addPubkey(ctx sdk.Context, pubkey crypto.PubKey) { func (k Keeper) getPubkey(ctx sdk.Context, address crypto.Address) (crypto.PubKey, error) { store := ctx.KVStore(k.storeKey) var pubkey crypto.PubKey - err := k.cdc.UnmarshalBinary(store.Get(getAddrPubkeyRelationKey(address)), &pubkey) + err := k.cdc.UnmarshalBinaryLengthPrefixed(store.Get(getAddrPubkeyRelationKey(address)), &pubkey) if err != nil { return nil, fmt.Errorf("address %v not found", address) } @@ -196,7 +188,7 @@ func (k Keeper) getPubkey(ctx sdk.Context, address crypto.Address) (crypto.PubKe func (k Keeper) setAddrPubkeyRelation(ctx sdk.Context, addr crypto.Address, pubkey crypto.PubKey) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(pubkey) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(pubkey) store.Set(getAddrPubkeyRelationKey(addr), bz) } diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index caf1cf3da..94251ded3 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -34,8 +34,7 @@ func TestHandleDoubleSign(t *testing.T) { operatorAddr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) got := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, val, amt)) require.True(t, got.IsOK()) - validatorUpdates := stake.EndBlocker(ctx, sk) - keeper.AddValidators(ctx, validatorUpdates) + stake.EndBlocker(ctx, sk) require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(operatorAddr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, operatorAddr).GetPower())) @@ -75,9 +74,8 @@ func TestSlashingPeriodCap(t *testing.T) { valConsPubKey, valConsAddr := pks[0], pks[0].Address() got := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, valConsPubKey, amt)) require.True(t, got.IsOK()) - validatorUpdates := stake.EndBlocker(ctx, sk) + stake.EndBlocker(ctx, sk) ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) - keeper.AddValidators(ctx, validatorUpdates) require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(operatorAddr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, operatorAddr).GetPower())) @@ -141,8 +139,7 @@ func TestHandleAbsentValidator(t *testing.T) { slh := NewHandler(keeper) got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) require.True(t, got.IsOK()) - validatorUpdates := stake.EndBlocker(ctx, sk) - keeper.AddValidators(ctx, validatorUpdates) + stake.EndBlocker(ctx, sk) require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower())) // will exist since the validator has been bonded @@ -298,8 +295,7 @@ func TestHandleNewValidator(t *testing.T) { // Validator created got := sh(ctx, NewTestMsgCreateValidator(addr, val, sdk.NewInt(amt))) require.True(t, got.IsOK()) - validatorUpdates := stake.EndBlocker(ctx, sk) - keeper.AddValidators(ctx, validatorUpdates) + stake.EndBlocker(ctx, sk) require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.SubRaw(amt)}}) require.Equal(t, sdk.NewDec(amt), sk.Validator(ctx, addr).GetPower()) @@ -333,8 +329,7 @@ func TestHandleAlreadyJailed(t *testing.T) { sh := stake.NewHandler(sk) got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) require.True(t, got.IsOK()) - validatorUpdates := stake.EndBlocker(ctx, sk) - keeper.AddValidators(ctx, validatorUpdates) + stake.EndBlocker(ctx, sk) // 1000 first blocks OK height := int64(0) @@ -386,8 +381,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { sh := stake.NewHandler(sk) got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) require.True(t, got.IsOK()) - validatorUpdates := stake.EndBlocker(ctx, sk) - keeper.AddValidators(ctx, validatorUpdates) + stake.EndBlocker(ctx, sk) // 100 first blocks OK height := int64(0) @@ -400,9 +394,8 @@ func TestValidatorDippingInAndOut(t *testing.T) { newAmt := int64(101) got = sh(ctx, NewTestMsgCreateValidator(addrs[1], pks[1], sdk.NewInt(newAmt))) require.True(t, got.IsOK()) - validatorUpdates = stake.EndBlocker(ctx, sk) + validatorUpdates := stake.EndBlocker(ctx, sk) require.Equal(t, 2, len(validatorUpdates)) - keeper.AddValidators(ctx, validatorUpdates) validator, _ := sk.GetValidator(ctx, addr) require.Equal(t, sdk.Unbonding, validator.Status) diff --git a/x/slashing/keys.go b/x/slashing/keys.go index 8f4fecc6c..750e8825f 100644 --- a/x/slashing/keys.go +++ b/x/slashing/keys.go @@ -20,6 +20,15 @@ func GetValidatorSigningInfoKey(v sdk.ConsAddress) []byte { return append(ValidatorSigningInfoKey, v.Bytes()...) } +// extract the address from a validator signing info key +func GetValidatorSigningInfoAddress(key []byte) (v sdk.ConsAddress) { + addr := key[1:] + if len(addr) != sdk.AddrLen { + panic("unexpected key length") + } + return sdk.ConsAddress(addr) +} + // stored by *Tendermint* address (not operator address) func GetValidatorMissedBlockBitArrayPrefixKey(v sdk.ConsAddress) []byte { return append(ValidatorMissedBlockBitArrayKey, v.Bytes()...) diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go index 8c35693f1..77c437174 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/signing_info.go @@ -15,15 +15,30 @@ func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress found = false return } - k.cdc.MustUnmarshalBinary(bz, &info) + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &info) found = true return } +// Stored by *validator* address (not operator address) +func (k Keeper) iterateValidatorSigningInfos(ctx sdk.Context, handler func(address sdk.ConsAddress, info ValidatorSigningInfo) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, ValidatorSigningInfoKey) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + address := GetValidatorSigningInfoAddress(iter.Key()) + var info ValidatorSigningInfo + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &info) + if handler(address, info) { + break + } + } +} + // Stored by *validator* address (not operator address) func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress, info ValidatorSigningInfo) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(info) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(info) store.Set(GetValidatorSigningInfoKey(address), bz) } @@ -36,14 +51,32 @@ func (k Keeper) getValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.Con missed = false return } - k.cdc.MustUnmarshalBinary(bz, &missed) + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &missed) return } +// Stored by *validator* address (not operator address) +func (k Keeper) iterateValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, handler func(index int64, missed bool) (stop bool)) { + store := ctx.KVStore(k.storeKey) + index := int64(0) + // Array may be sparse + for ; index < k.SignedBlocksWindow(ctx); index++ { + var missed bool + bz := store.Get(GetValidatorMissedBlockBitArrayKey(address, index)) + if bz == nil { + continue + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &missed) + if handler(index, missed) { + break + } + } +} + // Stored by *validator* address (not operator address) func (k Keeper) setValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64, missed bool) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(missed) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(missed) store.Set(GetValidatorMissedBlockBitArrayKey(address, index), bz) } diff --git a/x/slashing/simulation/invariants.go b/x/slashing/simulation/invariants.go index 5ad2d65ad..0aa0ed1e5 100644 --- a/x/slashing/simulation/invariants.go +++ b/x/slashing/simulation/invariants.go @@ -3,13 +3,12 @@ package simulation import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/x/mock/simulation" - abci "github.com/tendermint/tendermint/abci/types" ) // TODO Any invariants to check here? // AllInvariants tests all slashing invariants func AllInvariants() simulation.Invariant { - return func(_ *baseapp.BaseApp, _ abci.Header) error { + return func(_ *baseapp.BaseApp) error { return nil } } diff --git a/x/slashing/slashing_period.go b/x/slashing/slashing_period.go index 4b1328858..4caf5d7c9 100644 --- a/x/slashing/slashing_period.go +++ b/x/slashing/slashing_period.go @@ -51,6 +51,21 @@ func (k Keeper) getValidatorSlashingPeriodForHeight(ctx sdk.Context, address sdk return } +// Iterate over all slashing periods in the store, calling on each +// decode slashing period a provided handler function +// Stop if the provided handler function returns true +func (k Keeper) iterateValidatorSlashingPeriods(ctx sdk.Context, handler func(slashingPeriod ValidatorSlashingPeriod) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, ValidatorSlashingPeriodKey) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + slashingPeriod := k.unmarshalSlashingPeriodKeyValue(iter.Key(), iter.Value()) + if handler(slashingPeriod) { + break + } + } +} + // Stored by validator Tendermint address (not operator address) // This function sets a validator slashing period for a particular validator, // start height, end height, and current slashed-so-far total, or updates @@ -61,14 +76,14 @@ func (k Keeper) addOrUpdateValidatorSlashingPeriod(ctx sdk.Context, slashingPeri SlashedSoFar: slashingPeriod.SlashedSoFar, } store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(slashingPeriodValue) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(slashingPeriodValue) store.Set(GetValidatorSlashingPeriodKey(slashingPeriod.ValidatorAddr, slashingPeriod.StartHeight), bz) } // Unmarshal key/value into a ValidatorSlashingPeriod func (k Keeper) unmarshalSlashingPeriodKeyValue(key []byte, value []byte) ValidatorSlashingPeriod { var slashingPeriodValue ValidatorSlashingPeriodValue - k.cdc.MustUnmarshalBinary(value, &slashingPeriodValue) + k.cdc.MustUnmarshalBinaryLengthPrefixed(value, &slashingPeriodValue) address := sdk.ConsAddress(key[1 : 1+sdk.AddrLen]) startHeight := int64(binary.BigEndian.Uint64(key[1+sdk.AddrLen:1+sdk.AddrLen+8]) - uint64(stake.ValidatorUpdateDelay)) return ValidatorSlashingPeriod{ diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index 55a6fda18..239ae13d6 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -87,11 +87,11 @@ func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, s } require.Nil(t, err) paramstore := paramsKeeper.Subspace(DefaultParamspace) - keeper := NewKeeper(cdc, keySlashing, sk, paramstore, DefaultCodespace) - sk = sk.WithHooks(keeper.Hooks()) + keeper := NewKeeper(cdc, keySlashing, &sk, paramstore, DefaultCodespace) + sk.SetHooks(keeper.Hooks()) require.NotPanics(t, func() { - InitGenesis(ctx, keeper, GenesisState{defaults}, genesis) + InitGenesis(ctx, keeper, GenesisState{defaults, nil, nil, nil}, genesis) }) return ctx, ck, sk, paramstore, keeper diff --git a/x/slashing/tick_test.go b/x/slashing/tick_test.go index a2a2d9f0f..c6590c94e 100644 --- a/x/slashing/tick_test.go +++ b/x/slashing/tick_test.go @@ -19,8 +19,7 @@ func TestBeginBlocker(t *testing.T) { // bond the validator got := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(addr, pk, amt)) require.True(t, got.IsOK()) - validatorUpdates := stake.EndBlocker(ctx, sk) - keeper.AddValidators(ctx, validatorUpdates) + stake.EndBlocker(ctx, sk) require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower())) diff --git a/x/stake/client/cli/flags.go b/x/stake/client/cli/flags.go index e4948f945..d571bef9e 100644 --- a/x/stake/client/cli/flags.go +++ b/x/stake/client/cli/flags.go @@ -35,11 +35,11 @@ const ( // common flagsets to add to various functions var ( - fsPk = flag.NewFlagSet("", flag.ContinueOnError) - fsAmount = flag.NewFlagSet("", flag.ContinueOnError) + FsPk = flag.NewFlagSet("", flag.ContinueOnError) + FsAmount = flag.NewFlagSet("", flag.ContinueOnError) fsShares = flag.NewFlagSet("", flag.ContinueOnError) fsDescriptionCreate = flag.NewFlagSet("", flag.ContinueOnError) - fsCommissionCreate = flag.NewFlagSet("", flag.ContinueOnError) + FsCommissionCreate = flag.NewFlagSet("", flag.ContinueOnError) fsCommissionUpdate = flag.NewFlagSet("", flag.ContinueOnError) fsDescriptionEdit = flag.NewFlagSet("", flag.ContinueOnError) fsValidator = flag.NewFlagSet("", flag.ContinueOnError) @@ -48,8 +48,8 @@ var ( ) func init() { - fsPk.String(FlagPubKey, "", "Go-Amino encoded hex PubKey of the validator. For Ed25519 the go-amino prepend hex is 1624de6220") - fsAmount.String(FlagAmount, "", "Amount of coins to bond") + FsPk.String(FlagPubKey, "", "Bech32-encoded PubKey of the validator. ") + FsAmount.String(FlagAmount, "", "Amount of coins to bond") fsShares.String(FlagSharesAmount, "", "Amount of source-shares to either unbond or redelegate as a positive integer or decimal") fsShares.String(FlagSharesFraction, "", "Fraction of source-shares to either unbond or redelegate as a positive integer or decimal >0 and <=1") fsDescriptionCreate.String(FlagMoniker, "", "validator name") @@ -57,9 +57,9 @@ func init() { fsDescriptionCreate.String(FlagWebsite, "", "optional website") fsDescriptionCreate.String(FlagDetails, "", "optional details") fsCommissionUpdate.String(FlagCommissionRate, "", "The new commission rate percentage") - fsCommissionCreate.String(FlagCommissionRate, "", "The initial commission rate percentage") - fsCommissionCreate.String(FlagCommissionMaxRate, "", "The maximum commission rate percentage") - fsCommissionCreate.String(FlagCommissionMaxChangeRate, "", "The maximum commission change rate percentage (per day)") + FsCommissionCreate.String(FlagCommissionRate, "", "The initial commission rate percentage") + FsCommissionCreate.String(FlagCommissionMaxRate, "", "The maximum commission rate percentage") + FsCommissionCreate.String(FlagCommissionMaxChangeRate, "", "The maximum commission change rate percentage (per day)") fsDescriptionEdit.String(FlagMoniker, types.DoNotModifyDesc, "validator name") fsDescriptionEdit.String(FlagIdentity, types.DoNotModifyDesc, "optional identity signature (ex. UPort or Keybase)") fsDescriptionEdit.String(FlagWebsite, types.DoNotModifyDesc, "optional website") diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index ad8030c93..28d09df8e 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -114,6 +114,80 @@ func GetCmdQueryValidators(storeName string, cdc *codec.Codec) *cobra.Command { return cmd } +// GetCmdQueryValidatorUnbondingDelegations implements the query all unbonding delegatations from a validator command. +func GetCmdQueryValidatorUnbondingDelegations(queryRoute string, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbonding-delegations-from [operator-addr]", + Short: "Query all unbonding delegatations from a validator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + valAddr, err := sdk.ValAddressFromBech32(args[0]) + if err != nil { + return err + } + + cliCtx := context.NewCLIContext().WithCodec(cdc) + params := stake.QueryValidatorParams{ + ValidatorAddr: valAddr, + } + + bz, err := cdc.MarshalJSON(params) + if err != nil { + return err + } + + res, err := cliCtx.QueryWithData( + fmt.Sprintf("custom/%s/validatorUnbondingDelegations", queryRoute), + bz) + if err != nil { + return err + } + + fmt.Println(string(res)) + return nil + }, + } + + return cmd +} + +// GetCmdQueryValidatorRedelegations implements the query all redelegatations from a validator command. +func GetCmdQueryValidatorRedelegations(queryRoute string, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "redelegations-from [operator-addr]", + Short: "Query all outgoing redelegatations from a validator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + valAddr, err := sdk.ValAddressFromBech32(args[0]) + if err != nil { + return err + } + + cliCtx := context.NewCLIContext().WithCodec(cdc) + params := stake.QueryValidatorParams{ + ValidatorAddr: valAddr, + } + + bz, err := cdc.MarshalJSON(params) + if err != nil { + return err + } + + res, err := cliCtx.QueryWithData( + fmt.Sprintf("custom/%s/validatorRedelegations", queryRoute), + bz) + if err != nil { + return err + } + + fmt.Println(string(res)) + return nil + }, + } + + return cmd +} + // GetCmdQueryDelegation the query delegation command. func GetCmdQueryDelegation(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ @@ -139,6 +213,7 @@ func GetCmdQueryDelegation(storeName string, cdc *codec.Codec) *cobra.Command { } // parse out the delegation + delegation, err := types.UnmarshalDelegation(cdc, key, res) if err != nil { return err @@ -292,7 +367,7 @@ func GetCmdQueryUnbondingDelegations(storeName string, cdc *codec.Codec) *cobra. return err } - // parse out the validators + // parse out the unbonding delegations var ubds []stake.UnbondingDelegation for _, kv := range resKVs { ubd := types.MustUnmarshalUBD(cdc, kv.Key, kv.Value) diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index c43e1276e..3b8d8e200 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -103,10 +103,10 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { }, } - cmd.Flags().AddFlagSet(fsPk) - cmd.Flags().AddFlagSet(fsAmount) + cmd.Flags().AddFlagSet(FsPk) + cmd.Flags().AddFlagSet(FsAmount) cmd.Flags().AddFlagSet(fsDescriptionCreate) - cmd.Flags().AddFlagSet(fsCommissionCreate) + cmd.Flags().AddFlagSet(FsCommissionCreate) cmd.Flags().AddFlagSet(fsDelegator) cmd.Flags().Bool(FlagGenesisFormat, false, "Export the transaction in gen-tx format; it implies --generate-only") cmd.Flags().String(FlagIP, "", fmt.Sprintf("Node's public IP. It takes effect only when used in combination with --%s", FlagGenesisFormat)) @@ -204,7 +204,7 @@ func GetCmdDelegate(cdc *codec.Codec) *cobra.Command { }, } - cmd.Flags().AddFlagSet(fsAmount) + cmd.Flags().AddFlagSet(FsAmount) cmd.Flags().AddFlagSet(fsValidator) return cmd diff --git a/x/stake/genesis.go b/x/stake/genesis.go index 28c5973c7..d44055ea8 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -2,13 +2,13 @@ package stake import ( "fmt" + "sort" abci "github.com/tendermint/tendermint/abci/types" tmtypes "github.com/tendermint/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" - "github.com/pkg/errors" ) // InitGenesis sets the pool and parameters for the provided keeper and @@ -26,22 +26,33 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ keeper.SetPool(ctx, data.Pool) keeper.SetParams(ctx, data.Params) + keeper.SetIntraTxCounter(ctx, data.IntraTxCounter) + keeper.SetLastTotalPower(ctx, data.LastTotalPower) + + // We only need to set this if we're starting from a list of validators, not a state export + setBondIntraTxCounter := true + for _, validator := range data.Validators { + if validator.BondIntraTxCounter != 0 { + setBondIntraTxCounter = false + } + } for i, validator := range data.Validators { - validator.BondIntraTxCounter = int16(i) // set the intra-tx counter to the order the validators are presented + // set the intra-tx counter to the order the validators are presented, if necessary + if setBondIntraTxCounter { + validator.BondIntraTxCounter = int16(i) + } keeper.SetValidator(ctx, validator) - if validator.Tokens.IsZero() { - return res, errors.Errorf("genesis validator cannot have zero pool shares, validator: %v", validator) - } - if validator.DelegatorShares.IsZero() { - return res, errors.Errorf("genesis validator cannot have zero delegator shares, validator: %v", validator) - } - // Manually set indices for the first time keeper.SetValidatorByConsAddr(ctx, validator) keeper.SetValidatorByPowerIndex(ctx, validator, data.Pool) keeper.OnValidatorCreated(ctx, validator.OperatorAddr) + + // Set timeslice if necessary + if validator.Status == sdk.Unbonding { + keeper.InsertValidatorQueue(ctx, validator) + } } for _, delegation := range data.Bonds { @@ -49,30 +60,62 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ keeper.OnDelegationCreated(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) } + sort.SliceStable(data.UnbondingDelegations[:], func(i, j int) bool { + return data.UnbondingDelegations[i].CreationHeight < data.UnbondingDelegations[j].CreationHeight + }) + for _, ubd := range data.UnbondingDelegations { + keeper.SetUnbondingDelegation(ctx, ubd) + keeper.InsertUnbondingQueue(ctx, ubd) + } + + sort.SliceStable(data.Redelegations[:], func(i, j int) bool { + return data.Redelegations[i].CreationHeight < data.Redelegations[j].CreationHeight + }) + for _, red := range data.Redelegations { + keeper.SetRedelegation(ctx, red) + keeper.InsertRedelegationQueue(ctx, red) + } + res = keeper.ApplyAndReturnValidatorSetUpdates(ctx) return } -// WriteGenesis returns a GenesisState for a given context and keeper. The +// ExportGenesis returns a GenesisState for a given context and keeper. The // GenesisState will contain the pool, params, validators, and bonds found in // the keeper. -func WriteGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { +func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { pool := keeper.GetPool(ctx) params := keeper.GetParams(ctx) + intraTxCounter := keeper.GetIntraTxCounter(ctx) + lastTotalPower := keeper.GetLastTotalPower(ctx) validators := keeper.GetAllValidators(ctx) bonds := keeper.GetAllDelegations(ctx) + var unbondingDelegations []types.UnbondingDelegation + keeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd types.UnbondingDelegation) (stop bool) { + unbondingDelegations = append(unbondingDelegations, ubd) + return false + }) + var redelegations []types.Redelegation + keeper.IterateRedelegations(ctx, func(_ int64, red types.Redelegation) (stop bool) { + redelegations = append(redelegations, red) + return false + }) return types.GenesisState{ - Pool: pool, - Params: params, - Validators: validators, - Bonds: bonds, + Pool: pool, + Params: params, + IntraTxCounter: intraTxCounter, + LastTotalPower: lastTotalPower, + Validators: validators, + Bonds: bonds, + UnbondingDelegations: unbondingDelegations, + Redelegations: redelegations, } } // WriteValidators returns a slice of bonded genesis validators. func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisValidator) { - keeper.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) { + keeper.IterateLastValidators(ctx, func(_ int64, validator sdk.Validator) (stop bool) { vals = append(vals, tmtypes.GenesisValidator{ PubKey: validator.GetConsPubKey(), Power: validator.GetPower().RoundInt64(), @@ -118,11 +161,8 @@ func validateGenesisStateValidators(validators []types.Validator) (err error) { if val.Jailed && val.Status == sdk.Bonded { return fmt.Errorf("validator is bonded and jailed in genesis state: moniker %v, Address %v", val.Description.Moniker, val.ConsAddress()) } - if val.Tokens.IsZero() { - return fmt.Errorf("genesis validator cannot have zero pool shares, validator: %v", val) - } - if val.DelegatorShares.IsZero() { - return fmt.Errorf("genesis validator cannot have zero delegator shares, validator: %v", val) + if val.DelegatorShares.IsZero() && val.Status != sdk.Unbonding { + return fmt.Errorf("bonded/unbonded genesis validator cannot have zero delegator shares, validator: %v", val) } addrMap[strKey] = true } diff --git a/x/stake/genesis_test.go b/x/stake/genesis_test.go index 7ee16a453..3f7295a7a 100644 --- a/x/stake/genesis_test.go +++ b/x/stake/genesis_test.go @@ -22,29 +22,28 @@ func TestInitGenesis(t *testing.T) { pool.BondedTokens = sdk.NewDec(2) params := keeper.GetParams(ctx) + validators := make([]Validator, 2) var delegations []Delegation - validators := []Validator{ - NewValidator(sdk.ValAddress(keep.Addrs[0]), keep.PKs[0], Description{Moniker: "hoop"}), - NewValidator(sdk.ValAddress(keep.Addrs[1]), keep.PKs[1], Description{Moniker: "bloop"}), - } - genesisState := types.NewGenesisState(pool, params, validators, delegations) - _, err := InitGenesis(ctx, keeper, genesisState) - require.Error(t, err) - // initialize the validators + validators[0].OperatorAddr = sdk.ValAddress(keep.Addrs[0]) + validators[0].ConsPubKey = keep.PKs[0] + validators[0].Description = Description{Moniker: "hoop"} validators[0].Status = sdk.Bonded validators[0].Tokens = sdk.OneDec() validators[0].DelegatorShares = sdk.OneDec() + validators[1].OperatorAddr = sdk.ValAddress(keep.Addrs[1]) + validators[1].ConsPubKey = keep.PKs[1] + validators[1].Description = Description{Moniker: "bloop"} validators[1].Status = sdk.Bonded validators[1].Tokens = sdk.OneDec() validators[1].DelegatorShares = sdk.OneDec() - genesisState = types.NewGenesisState(pool, params, validators, delegations) + genesisState := types.NewGenesisState(pool, params, validators, delegations) vals, err := InitGenesis(ctx, keeper, genesisState) require.NoError(t, err) - actualGenesis := WriteGenesis(ctx, keeper) + actualGenesis := ExportGenesis(ctx, keeper) require.Equal(t, genesisState.Pool, actualGenesis.Pool) require.Equal(t, genesisState.Params, actualGenesis.Params) require.Equal(t, genesisState.Bonds, actualGenesis.Bonds) @@ -126,10 +125,6 @@ func TestValidateGenesis(t *testing.T) { (*data).Validators = genValidators1 (*data).Validators = append((*data).Validators, genValidators1[0]) }, true}, - {"no pool shares", func(data *types.GenesisState) { - (*data).Validators = genValidators1 - (*data).Validators[0].Tokens = sdk.ZeroDec() - }, true}, {"no delegator shares", func(data *types.GenesisState) { (*data).Validators = genValidators1 (*data).Validators[0].DelegatorShares = sdk.ZeroDec() diff --git a/x/stake/handler.go b/x/stake/handler.go index 1d095f3f4..83aec00ea 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -31,11 +31,27 @@ func NewHandler(k keeper.Keeper) sdk.Handler { } // Called every block, update validator set -func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.ValidatorUpdate) { +func EndBlocker(ctx sdk.Context, k keeper.Keeper) (validatorUpdates []abci.ValidatorUpdate) { endBlockerTags := sdk.EmptyTags() + // Reset the intra-transaction counter. + k.SetIntraTxCounter(ctx, 0) + + // Calculate validator set changes. + // + // NOTE: ApplyAndReturnValidatorSetUpdates has to come before + // UnbondAllMatureValidatorQueue. + // This fixes a bug when the unbonding period is instant (is the case in + // some of the tests). The test expected the validator to be completely + // unbonded after the Endblocker (go from Bonded -> Unbonding during + // ApplyAndReturnValidatorSetUpdates and then Unbonding -> Unbonded during + // UnbondAllMatureValidatorQueue). + validatorUpdates = k.ApplyAndReturnValidatorSetUpdates(ctx) + + // Unbond all mature validators from the unbonding queue. k.UnbondAllMatureValidatorQueue(ctx) + // Remove all mature unbonding delegations from the ubd queue. matureUnbonds := k.DequeueAllMatureUnbondingQueue(ctx, ctx.BlockHeader().Time) for _, dvPair := range matureUnbonds { err := k.CompleteUnbonding(ctx, dvPair.DelegatorAddr, dvPair.ValidatorAddr) @@ -49,6 +65,7 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Valid )) } + // Remove all mature redelegations from the red queue. matureRedelegations := k.DequeueAllMatureRedelegationQueue(ctx, ctx.BlockHeader().Time) for _, dvvTriplet := range matureRedelegations { err := k.CompleteRedelegation(ctx, dvvTriplet.DelegatorAddr, dvvTriplet.ValidatorSrcAddr, dvvTriplet.ValidatorDstAddr) @@ -62,12 +79,6 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Valid tags.DstValidator, []byte(dvvTriplet.ValidatorDstAddr.String()), )) } - - // reset the intra-transaction counter - k.SetIntraTxCounter(ctx, 0) - - // calculate validator set changes - ValidatorUpdates = k.ApplyAndReturnValidatorSetUpdates(ctx) return } @@ -201,7 +212,7 @@ func handleMsgBeginUnbonding(ctx sdk.Context, msg types.MsgBeginUnbonding, k kee return err.Result() } - finishTime := types.MsgCdc.MustMarshalBinary(ubd.MinTime) + finishTime := types.MsgCdc.MustMarshalBinaryLengthPrefixed(ubd.MinTime) tags := sdk.NewTags( tags.Action, tags.ActionBeginUnbonding, @@ -219,7 +230,7 @@ func handleMsgBeginRedelegate(ctx sdk.Context, msg types.MsgBeginRedelegate, k k return err.Result() } - finishTime := types.MsgCdc.MustMarshalBinary(red.MinTime) + finishTime := types.MsgCdc.MustMarshalBinaryLengthPrefixed(red.MinTime) tags := sdk.NewTags( tags.Action, tags.ActionBeginRedelegation, diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index c4a558b91..fcc268f55 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -94,7 +94,7 @@ func TestValidatorByPowerIndex(t *testing.T) { got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) var finishTime time.Time - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) @@ -232,7 +232,7 @@ func TestLegacyValidatorDelegations(t *testing.T) { require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got %v", got) var finishTime time.Time - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) @@ -400,7 +400,7 @@ func TestIncrementsMsgUnbond(t *testing.T) { got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) var finishTime time.Time - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) @@ -494,11 +494,12 @@ func TestMultipleMsgCreateValidator(t *testing.T) { got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) var finishTime time.Time - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + // Jump to finishTime for unbonding period and remove from unbonding queue + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) - //Check that the account is unbonded + // Check that the validator is deleted from state validators := keeper.GetValidators(ctx, 100) require.Equal(t, len(validatorAddrs)-(i+1), len(validators), "expected %d validators got %d", len(validatorAddrs)-(i+1), len(validators)) @@ -540,7 +541,7 @@ func TestMultipleMsgDelegate(t *testing.T) { got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) var finishTime time.Time - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) @@ -570,7 +571,7 @@ func TestJailValidator(t *testing.T) { got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) require.True(t, got.IsOK(), "expected no error: %v", got) var finishTime time.Time - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) @@ -586,7 +587,7 @@ func TestJailValidator(t *testing.T) { msgBeginUnbondingDelegator := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewDec(10)) got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingDelegator, keeper) require.True(t, got.IsOK(), "expected no error") - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) @@ -621,7 +622,7 @@ func TestValidatorQueue(t *testing.T) { got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) require.True(t, got.IsOK(), "expected no error: %v", got) var finishTime time.Time - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) origHeader := ctx.BlockHeader() @@ -709,7 +710,7 @@ func TestUnbondingFromUnbondingValidator(t *testing.T) { // change the ctx to Block Time one second before the validator would have unbonded var finishTime time.Time - types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime.Add(time.Second * -1)) // unbond the delegator from the validator @@ -1013,7 +1014,7 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { EndBlocker(ctx, keeper) // validator power should have been reduced to zero - // ergo validator should have been removed from the store - _, found = keeper.GetValidator(ctx, valA) - require.False(t, found) + // validator should be in unbonding state + validator, _ = keeper.GetValidator(ctx, valA) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) } diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index a5d08d489..d535419e1 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -163,14 +163,14 @@ func (k Keeper) GetUnbondingQueueTimeSlice(ctx sdk.Context, timestamp time.Time) if bz == nil { return []types.DVPair{} } - k.cdc.MustUnmarshalBinary(bz, &dvPairs) + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &dvPairs) return dvPairs } // Sets a specific unbonding queue timeslice. func (k Keeper) SetUnbondingQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []types.DVPair) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(keys) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(keys) store.Set(GetUnbondingDelegationTimeKey(timestamp), bz) } @@ -199,7 +199,7 @@ func (k Keeper) DequeueAllMatureUnbondingQueue(ctx sdk.Context, currTime time.Ti unbondingTimesliceIterator := k.UnbondingQueueIterator(ctx, ctx.BlockHeader().Time) for ; unbondingTimesliceIterator.Valid(); unbondingTimesliceIterator.Next() { timeslice := []types.DVPair{} - k.cdc.MustUnmarshalBinary(unbondingTimesliceIterator.Value(), ×lice) + k.cdc.MustUnmarshalBinaryLengthPrefixed(unbondingTimesliceIterator.Value(), ×lice) matureUnbonds = append(matureUnbonds, timeslice...) store.Delete(unbondingTimesliceIterator.Key()) } @@ -283,6 +283,21 @@ func (k Keeper) SetRedelegation(ctx sdk.Context, red types.Redelegation) { store.Set(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr), []byte{}) } +// iterate through all redelegations +func (k Keeper) IterateRedelegations(ctx sdk.Context, fn func(index int64, red types.Redelegation) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, RedelegationKey) + defer iterator.Close() + + for i := int64(0); iterator.Valid(); iterator.Next() { + red := types.MustUnmarshalRED(k.cdc, iterator.Key(), iterator.Value()) + if stop := fn(i, red); stop { + break + } + i++ + } +} + // remove a redelegation object and associated index func (k Keeper) RemoveRedelegation(ctx sdk.Context, red types.Redelegation) { store := ctx.KVStore(k.storeKey) @@ -300,14 +315,14 @@ func (k Keeper) GetRedelegationQueueTimeSlice(ctx sdk.Context, timestamp time.Ti if bz == nil { return []types.DVVTriplet{} } - k.cdc.MustUnmarshalBinary(bz, &dvvTriplets) + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &dvvTriplets) return dvvTriplets } // Sets a specific redelegation queue timeslice. func (k Keeper) SetRedelegationQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []types.DVVTriplet) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(keys) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(keys) store.Set(GetRedelegationTimeKey(timestamp), bz) } @@ -336,7 +351,7 @@ func (k Keeper) DequeueAllMatureRedelegationQueue(ctx sdk.Context, currTime time redelegationTimesliceIterator := k.RedelegationQueueIterator(ctx, ctx.BlockHeader().Time) for ; redelegationTimesliceIterator.Valid(); redelegationTimesliceIterator.Next() { timeslice := []types.DVVTriplet{} - k.cdc.MustUnmarshalBinary(redelegationTimesliceIterator.Value(), ×lice) + k.cdc.MustUnmarshalBinaryLengthPrefixed(redelegationTimesliceIterator.Value(), ×lice) matureRedelegations = append(matureRedelegations, timeslice...) store.Delete(redelegationTimesliceIterator.Key()) } @@ -349,6 +364,13 @@ func (k Keeper) DequeueAllMatureRedelegationQueue(ctx sdk.Context, currTime time func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Coin, validator types.Validator, subtractAccount bool) (newShares sdk.Dec, err sdk.Error) { + // In some situations, the exchange rate becomes invalid, e.g. if + // validator loses all tokens due to slashing. In this case, + // make all future delegations invalid. + if validator.DelegatorShareExRate().IsZero() { + return sdk.ZeroDec(), types.ErrDelegatorShareExRateInvalid(k.Codespace()) + } + // Get or create the delegator delegation delegation, found := k.GetDelegation(ctx, delAddr, validator.OperatorAddr) if !found { @@ -541,6 +563,10 @@ func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAd func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount sdk.Dec) (types.Redelegation, sdk.Error) { + if bytes.Equal(valSrcAddr, valDstAddr) { + return types.Redelegation{}, types.ErrSelfRedelegation(k.Codespace()) + } + // check if there is already a redelgation in progress from src to dst // TODO quick fix, instead we should use an index, see https://github.com/cosmos/cosmos-sdk/issues/1402 _, found := k.GetRedelegation(ctx, delAddr, valSrcAddr, valDstAddr) diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index 24476ca3c..fcf2f4206 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -581,6 +581,32 @@ func TestRedelegation(t *testing.T) { require.Equal(t, 0, len(redelegations)) } +func TestRedelegateToSameValidator(t *testing.T) { + + ctx, _, keeper := CreateTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDec(30) + + // create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) + selfDelegation := types.Delegation{ + DelegatorAddr: val0AccAddr, + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + _, err := keeper.BeginRedelegation(ctx, val0AccAddr, addrVals[0], addrVals[0], sdk.NewDec(5)) + require.Error(t, err) + +} + func TestRedelegateSelfDelegation(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 0) diff --git a/x/stake/keeper/hooks.go b/x/stake/keeper/hooks.go index 4a8496ddd..74e830490 100644 --- a/x/stake/keeper/hooks.go +++ b/x/stake/keeper/hooks.go @@ -17,9 +17,9 @@ func (k Keeper) OnValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { } } -func (k Keeper) OnValidatorRemoved(ctx sdk.Context, valAddr sdk.ValAddress) { +func (k Keeper) OnValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { if k.hooks != nil { - k.hooks.OnValidatorRemoved(ctx, valAddr) + k.hooks.OnValidatorRemoved(ctx, consAddr, valAddr) } } diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go index 17ee31268..a74f86084 100644 --- a/x/stake/keeper/keeper.go +++ b/x/stake/keeper/keeper.go @@ -36,7 +36,7 @@ func NewKeeper(cdc *codec.Codec, key, tkey sdk.StoreKey, ck bank.Keeper, paramst } // Set the validator hooks -func (k Keeper) WithHooks(sh sdk.StakingHooks) Keeper { +func (k *Keeper) SetHooks(sh sdk.StakingHooks) *Keeper { if k.hooks != nil { panic("cannot set validator hooks twice") } @@ -60,14 +60,14 @@ func (k Keeper) GetPool(ctx sdk.Context) (pool types.Pool) { if b == nil { panic("stored pool should not have been nil") } - k.cdc.MustUnmarshalBinary(b, &pool) + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &pool) return } // set the pool func (k Keeper) SetPool(ctx sdk.Context, pool types.Pool) { store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(pool) + b := k.cdc.MustMarshalBinaryLengthPrefixed(pool) store.Set(PoolKey, b) } @@ -80,14 +80,14 @@ func (k Keeper) GetLastTotalPower(ctx sdk.Context) (power sdk.Int) { if b == nil { return sdk.ZeroInt() } - k.cdc.MustUnmarshalBinary(b, &power) + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &power) return } // Set the last total validator power. func (k Keeper) SetLastTotalPower(ctx sdk.Context, power sdk.Int) { store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(power) + b := k.cdc.MustMarshalBinaryLengthPrefixed(power) store.Set(LastTotalPowerKey, b) } @@ -101,14 +101,14 @@ func (k Keeper) GetLastValidatorPower(ctx sdk.Context, operator sdk.ValAddress) if bz == nil { return sdk.ZeroInt() } - k.cdc.MustUnmarshalBinary(bz, &power) + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &power) return } // Set the last validator power. func (k Keeper) SetLastValidatorPower(ctx sdk.Context, operator sdk.ValAddress, power sdk.Int) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(power) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(power) store.Set(GetLastValidatorPowerKey(operator), bz) } @@ -128,13 +128,13 @@ func (k Keeper) GetIntraTxCounter(ctx sdk.Context) int16 { return 0 } var counter int16 - k.cdc.MustUnmarshalBinary(b, &counter) + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &counter) return counter } // set the current in-block validator operation counter func (k Keeper) SetIntraTxCounter(ctx sdk.Context, counter int16) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(counter) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(counter) store.Set(IntraTxCounterKey, bz) } diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go index 4e859a42a..1dea473f8 100644 --- a/x/stake/keeper/sdk_types.go +++ b/x/stake/keeper/sdk_types.go @@ -28,9 +28,31 @@ func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validato } // iterate through the active validator set and perform the provided function -func (k Keeper) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { +func (k Keeper) IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, LastValidatorPowerKey) + maxValidators := k.MaxValidators(ctx) + + iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) + defer iterator.Close() + + i := int64(0) + for ; iterator.Valid() && i < int64(maxValidators); iterator.Next() { + address := iterator.Value() + validator := k.mustGetValidator(ctx, address) + + if validator.Status == sdk.Bonded { + stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? + if stop { + break + } + i++ + } + } +} + +// iterate through the active validator set and perform the provided function +func (k Keeper) IterateLastValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { + iterator := k.LastValidatorsIterator(ctx) i := int64(0) for ; iterator.Valid(); iterator.Next() { address := AddressFromLastValidatorPowerKey(iterator.Key()) diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index 5879428f2..e7bd72764 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -108,12 +108,6 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn) k.SetPool(ctx, pool) - // remove validator if it has no more tokens - if validator.DelegatorShares.IsZero() && validator.Status == sdk.Unbonded { - // if not unbonded, we must instead remove validator in EndBlocker once it finishes its unbonding period - k.RemoveValidator(ctx, validator.OperatorAddr) - } - // Log that a slash occurred! logger.Info(fmt.Sprintf( "validator %s slashed by slash factor of %s; burned %v tokens", @@ -236,6 +230,7 @@ func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, re if sharesToUnbond.GT(delegation.Shares) { sharesToUnbond = delegation.Shares } + tokensToBurn, err := k.unbond(ctx, redelegation.DelegatorAddr, redelegation.ValidatorDstAddr, sharesToUnbond) if err != nil { panic(fmt.Errorf("error unbonding delegator: %v", err)) diff --git a/x/stake/keeper/slash_test.go b/x/stake/keeper/slash_test.go index aab97b811..9c23576c3 100644 --- a/x/stake/keeper/slash_test.go +++ b/x/stake/keeper/slash_test.go @@ -348,9 +348,9 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { keeper.ApplyAndReturnValidatorSetUpdates(ctx) // read updated validator // power decreased by 1 again, validator is out of stake - // ergo validator should have been removed from the store - _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.False(t, found) + // validator should be in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) } // tests Slash at a previous height with a redelegation @@ -450,16 +450,16 @@ func TestSlashWithRedelegation(t *testing.T) { // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) // read updated validator - // validator decreased to zero power, should have been removed from the store - _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.False(t, found) + // validator decreased to zero power, should be in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) // slash the validator again, by 100% // no stake remains to be slashed ctx = ctx.WithBlockHeight(12) - // validator no longer in the store - _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.False(t, found) + // validator still in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) // read updating redelegation @@ -472,9 +472,9 @@ func TestSlashWithRedelegation(t *testing.T) { // no more bonded tokens burned require.Equal(t, int64(16), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator - // power still zero, still not in the store - _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.False(t, found) + // power still zero, still in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) } // tests Slash at a previous height with both an unbonding delegation and a redelegation diff --git a/x/stake/keeper/test_common.go b/x/stake/keeper/test_common.go index 7978f17e9..af7e688f3 100644 --- a/x/stake/keeper/test_common.go +++ b/x/stake/keeper/test_common.go @@ -3,6 +3,7 @@ package keeper import ( "bytes" "encoding/hex" + "math/rand" "strconv" "testing" @@ -215,3 +216,17 @@ func validatorByPowerIndexExists(k Keeper, ctx sdk.Context, power []byte) bool { store := ctx.KVStore(k.storeKey) return store.Has(power) } + +// RandomValidator returns a random validator given access to the keeper and ctx +func RandomValidator(r *rand.Rand, keeper Keeper, ctx sdk.Context) types.Validator { + vals := keeper.GetAllValidators(ctx) + i := r.Intn(len(vals)) + return vals[i] +} + +// RandomBondedValidator returns a random bonded validator given access to the keeper and ctx +func RandomBondedValidator(r *rand.Rand, keeper Keeper, ctx sdk.Context) types.Validator { + vals := keeper.GetBondedValidatorsByPower(ctx) + i := r.Intn(len(vals)) + return vals[i] +} diff --git a/x/stake/keeper/val_state_change.go b/x/stake/keeper/val_state_change.go index ea057c0fd..3f98f08c5 100644 --- a/x/stake/keeper/val_state_change.go +++ b/x/stake/keeper/val_state_change.go @@ -73,13 +73,13 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab // calculate the new power bytes newPower := validator.BondedTokens().RoundInt64() - newPowerBytes := k.cdc.MustMarshalBinary(sdk.NewInt(newPower)) + newPowerBytes := k.cdc.MustMarshalBinaryLengthPrefixed(sdk.NewInt(newPower)) // update the validator set if power has changed if !found || !bytes.Equal(oldPowerBytes, newPowerBytes) { updates = append(updates, validator.ABCIValidatorUpdate()) - // XXX Assert that the validator had updated its ValidatorDistInfo.FeePoolWithdrawalHeight. - // XXX This hook probably shouldn't exist. Maybe rethink the hook system. + // Assert that the validator had updated its ValidatorDistInfo.FeePoolWithdrawalHeight. + // This hook is extremely useful, otherwise lazy accum bugs will be difficult to solve. if k.hooks != nil { k.hooks.OnValidatorPowerDidChange(ctx, validator.ConsAddress(), valAddr) } @@ -108,11 +108,6 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab // bonded to unbonding k.bondedToUnbonding(ctx, validator) - // remove validator if it has no more tokens - if validator.Tokens.IsZero() { - k.RemoveValidator(ctx, validator.OperatorAddr) - } - // delete from the bonded validator index k.DeleteLastValidatorPower(ctx, sdk.ValAddress(valAddrBytes)) @@ -196,11 +191,13 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types. validator, pool = validator.UpdateStatus(pool, sdk.Bonded) k.SetPool(ctx, pool) - // save the now bonded validator record to the three referenced stores + // save the now bonded validator record to the two referenced stores k.SetValidator(ctx, validator) - k.SetValidatorByPowerIndex(ctx, validator, pool) + // delete from queue if present + k.DeleteValidatorQueue(ctx, validator) + // call the bond hook if present if k.hooks != nil { k.hooks.OnValidatorBonded(ctx, validator.ConsAddress(), validator.OperatorAddr) @@ -229,9 +226,8 @@ func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validat validator.UnbondingMinTime = ctx.BlockHeader().Time.Add(params.UnbondingTime) validator.UnbondingHeight = ctx.BlockHeader().Height - // save the now unbonded validator record + // save the now unbonded validator record and power index k.SetValidator(ctx, validator) - k.SetValidatorByPowerIndex(ctx, validator, pool) // Adds to unbonding validator queue diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 9fd7434d3..c7919537c 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -1,6 +1,7 @@ package keeper import ( + "bytes" "container/list" "fmt" "time" @@ -155,6 +156,7 @@ func (k Keeper) RemoveValidatorTokensAndShares(ctx sdk.Context, validator types. // Update the tokens of an existing validator, update the validators power index key func (k Keeper) RemoveValidatorTokens(ctx sdk.Context, validator types.Validator, tokensToRemove sdk.Dec) types.Validator { + pool := k.GetPool(ctx) k.DeleteValidatorByPowerIndex(ctx, validator, pool) validator, pool = validator.RemoveTokens(pool, tokensToRemove) @@ -189,6 +191,9 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { if !found { return } + if validator.Status != sdk.Unbonded { + panic("Cannot call RemoveValidator on bonded or unbonding validators") + } // delete the old validator record store := ctx.KVStore(k.storeKey) @@ -197,6 +202,11 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { store.Delete(GetValidatorByConsAddrKey(sdk.ConsAddress(validator.ConsPubKey.Address()))) store.Delete(GetValidatorsByPowerIndexKey(validator, pool)) + // call hook if present + if k.hooks != nil { + k.hooks.OnValidatorRemoved(ctx, validator.ConsAddress(), validator.OperatorAddr) + } + } //___________________________________________________________________________ @@ -261,6 +271,13 @@ func (k Keeper) GetLastValidators(ctx sdk.Context) (validators []types.Validator return validators[:i] // trim } +// returns an iterator for the consensus validators in the last block +func (k Keeper) LastValidatorsIterator(ctx sdk.Context) (iterator sdk.Iterator) { + store := ctx.KVStore(k.storeKey) + iterator = sdk.KVStorePrefixIterator(store, LastValidatorPowerKey) + return iterator +} + // get the current group of bonded validators sorted by power-rank func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator { store := ctx.KVStore(k.storeKey) @@ -283,6 +300,13 @@ func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator { return validators[:i] // trim } +// returns an iterator for the current validator power store +func (k Keeper) ValidatorsPowerStoreIterator(ctx sdk.Context) (iterator sdk.Iterator) { + store := ctx.KVStore(k.storeKey) + iterator = sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) + return iterator +} + // gets a specific validator queue timeslice. A timeslice is a slice of ValAddresses corresponding to unbonding validators // that expire at a certain time. func (k Keeper) GetValidatorQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (valAddrs []sdk.ValAddress) { @@ -291,17 +315,23 @@ func (k Keeper) GetValidatorQueueTimeSlice(ctx sdk.Context, timestamp time.Time) if bz == nil { return []sdk.ValAddress{} } - k.cdc.MustUnmarshalBinary(bz, &valAddrs) + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &valAddrs) return valAddrs } // Sets a specific validator queue timeslice. func (k Keeper) SetValidatorQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []sdk.ValAddress) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(keys) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(keys) store.Set(GetValidatorQueueTimeKey(timestamp), bz) } +// Deletes a specific validator queue timeslice. +func (k Keeper) DeleteValidatorQueueTimeSlice(ctx sdk.Context, timestamp time.Time) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetValidatorQueueTimeKey(timestamp)) +} + // Insert an validator address to the appropriate timeslice in the validator queue func (k Keeper) InsertValidatorQueue(ctx sdk.Context, val types.Validator) { timeSlice := k.GetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime) @@ -313,6 +343,22 @@ func (k Keeper) InsertValidatorQueue(ctx sdk.Context, val types.Validator) { } } +// Delete a validator address from the validator queue +func (k Keeper) DeleteValidatorQueue(ctx sdk.Context, val types.Validator) { + timeSlice := k.GetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime) + newTimeSlice := []sdk.ValAddress{} + for _, addr := range timeSlice { + if !bytes.Equal(addr, val.OperatorAddr) { + newTimeSlice = append(newTimeSlice, addr) + } + } + if len(newTimeSlice) == 0 { + k.DeleteValidatorQueueTimeSlice(ctx, val.UnbondingMinTime) + } else { + k.SetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime, newTimeSlice) + } +} + // Returns all the validator queue timeslices from time 0 until endTime func (k Keeper) ValidatorQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { store := ctx.KVStore(k.storeKey) @@ -325,7 +371,7 @@ func (k Keeper) GetAllMatureValidatorQueue(ctx sdk.Context, currTime time.Time) validatorTimesliceIterator := k.ValidatorQueueIterator(ctx, ctx.BlockHeader().Time) for ; validatorTimesliceIterator.Valid(); validatorTimesliceIterator.Next() { timeslice := []sdk.ValAddress{} - k.cdc.MustUnmarshalBinary(validatorTimesliceIterator.Value(), ×lice) + k.cdc.MustUnmarshalBinaryLengthPrefixed(validatorTimesliceIterator.Value(), ×lice) matureValsAddrs = append(matureValsAddrs, timeslice...) } return matureValsAddrs @@ -337,16 +383,18 @@ func (k Keeper) UnbondAllMatureValidatorQueue(ctx sdk.Context) { validatorTimesliceIterator := k.ValidatorQueueIterator(ctx, ctx.BlockHeader().Time) for ; validatorTimesliceIterator.Valid(); validatorTimesliceIterator.Next() { timeslice := []sdk.ValAddress{} - k.cdc.MustUnmarshalBinary(validatorTimesliceIterator.Value(), ×lice) + k.cdc.MustUnmarshalBinaryLengthPrefixed(validatorTimesliceIterator.Value(), ×lice) for _, valAddr := range timeslice { val, found := k.GetValidator(ctx, valAddr) - if !found || val.GetStatus() != sdk.Unbonding { + if !found { continue } + if val.GetStatus() != sdk.Unbonding { + panic("unexpected validator in unbonding queue, status was not unbonding") + } + k.unbondingToUnbonded(ctx, val) if val.GetDelegatorShares().IsZero() { k.RemoveValidator(ctx, val.OperatorAddr) - } else { - k.unbondingToUnbonded(ctx, val) } } store.Delete(validatorTimesliceIterator.Key()) diff --git a/x/stake/keeper/validator_test.go b/x/stake/keeper/validator_test.go index 7acf1cc02..fe806335f 100644 --- a/x/stake/keeper/validator_test.go +++ b/x/stake/keeper/validator_test.go @@ -186,9 +186,9 @@ func TestSlashToZeroPowerRemoved(t *testing.T) { keeper.Slash(ctx, consAddr0, 0, 100, sdk.OneDec()) // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) - // validator should have been deleted - _, found := keeper.GetValidator(ctx, addrVals[0]) - require.False(t, found) + // validator should be unbonding + validator, _ = keeper.GetValidator(ctx, addrVals[0]) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) } // This function tests UpdateValidator, GetValidator, GetLastValidators, RemoveValidator @@ -276,7 +276,9 @@ func TestValidatorBasics(t *testing.T) { assert.True(ValEq(t, validators[2], resVals[2])) // remove a record - keeper.RemoveValidator(ctx, validators[1].OperatorAddr) + validators[1].Status = sdk.Unbonded // First must set to Unbonded. + keeper.SetValidator(ctx, validators[1]) // ... + keeper.RemoveValidator(ctx, validators[1].OperatorAddr) // Now it can be removed. _, found = keeper.GetValidator(ctx, addrVals[1]) require.False(t, found) } diff --git a/x/stake/simulation/invariants.go b/x/stake/simulation/invariants.go index 3b97bdb72..819bc8b37 100644 --- a/x/stake/simulation/invariants.go +++ b/x/stake/simulation/invariants.go @@ -1,6 +1,7 @@ package simulation import ( + "bytes" "fmt" "github.com/cosmos/cosmos-sdk/baseapp" @@ -10,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/mock/simulation" "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/stake/keeper" abci "github.com/tendermint/tendermint/abci/types" ) @@ -19,16 +21,18 @@ func AllInvariants(ck bank.Keeper, k stake.Keeper, f auth.FeeCollectionKeeper, d distribution.Keeper, am auth.AccountKeeper) simulation.Invariant { - return func(app *baseapp.BaseApp, header abci.Header) error { - err := SupplyInvariants(ck, k, f, d, am)(app, header) + return func(app *baseapp.BaseApp) error { + err := SupplyInvariants(ck, k, f, d, am)(app) if err != nil { return err } - err = PositivePowerInvariant(k)(app, header) + + err = PositivePowerInvariant(k)(app) if err != nil { return err } - err = ValidatorSetInvariant(k)(app, header) + + err = ValidatorSetInvariant(k)(app) return err } } @@ -37,7 +41,7 @@ func AllInvariants(ck bank.Keeper, k stake.Keeper, // nolint: unparam func SupplyInvariants(ck bank.Keeper, k stake.Keeper, f auth.FeeCollectionKeeper, d distribution.Keeper, am auth.AccountKeeper) simulation.Invariant { - return func(app *baseapp.BaseApp, _ abci.Header) error { + return func(app *baseapp.BaseApp) error { ctx := app.NewContext(false, abci.Header{}) pool := k.GetPool(ctx) @@ -100,25 +104,35 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, } } -// PositivePowerInvariant checks that all stored validators have > 0 power +// PositivePowerInvariant checks that all stored validators have > 0 power. func PositivePowerInvariant(k stake.Keeper) simulation.Invariant { - return func(app *baseapp.BaseApp, _ abci.Header) error { + return func(app *baseapp.BaseApp) error { ctx := app.NewContext(false, abci.Header{}) - var err error - k.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) bool { - if !validator.GetPower().GT(sdk.ZeroDec()) { - err = fmt.Errorf("validator with non-positive power stored. (pubkey %v)", validator.GetConsPubKey()) - return true + + iterator := k.ValidatorsPowerStoreIterator(ctx) + pool := k.GetPool(ctx) + + for ; iterator.Valid(); iterator.Next() { + validator, found := k.GetValidator(ctx, iterator.Value()) + if !found { + panic(fmt.Sprintf("validator record not found for address: %X\n", iterator.Value())) } - return false - }) - return err + + powerKey := keeper.GetValidatorsByPowerIndexKey(validator, pool) + + if !bytes.Equal(iterator.Key(), powerKey) { + return fmt.Errorf("power store invariance:\n\tvalidator.Power: %v"+ + "\n\tkey should be: %v\n\tkey in store: %v", validator.GetPower(), powerKey, iterator.Key()) + } + } + iterator.Close() + return nil } } // ValidatorSetInvariant checks equivalence of Tendermint validator set and SDK validator set func ValidatorSetInvariant(k stake.Keeper) simulation.Invariant { - return func(app *baseapp.BaseApp, _ abci.Header) error { + return func(app *baseapp.BaseApp) error { // TODO return nil } diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index 8684126e3..ec2dffd92 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/mock/simulation" "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/stake/keeper" abci "github.com/tendermint/tendermint/abci/types" ) @@ -87,8 +88,8 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { maxCommission := sdk.NewInt(10) newCommissionRate := sdk.NewDecWithPrec(simulation.RandomAmount(r, maxCommission).Int64(), 1) - acc := simulation.RandomAcc(r, accs) - address := sdk.ValAddress(acc.Address) + val := keeper.RandomValidator(r, k, ctx) + address := val.GetOperator() msg := stake.MsgEditValidator{ Description: description, ValidatorAddr: address, @@ -118,8 +119,8 @@ func SimulateMsgDelegate(m auth.AccountKeeper, k stake.Keeper) simulation.Operat action string, fOp []simulation.FutureOperation, err error) { denom := k.GetParams(ctx).BondDenom - validatorAcc := simulation.RandomAcc(r, accs) - validatorAddress := sdk.ValAddress(validatorAcc.Address) + val := keeper.RandomValidator(r, k, ctx) + validatorAddress := val.GetOperator() delegatorAcc := simulation.RandomAcc(r, accs) delegatorAddress := delegatorAcc.Address amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) @@ -155,25 +156,26 @@ func SimulateMsgBeginUnbonding(m auth.AccountKeeper, k stake.Keeper) simulation. accs []simulation.Account, event func(string)) ( action string, fOp []simulation.FutureOperation, err error) { - denom := k.GetParams(ctx).BondDenom - validatorAcc := simulation.RandomAcc(r, accs) - validatorAddress := sdk.ValAddress(validatorAcc.Address) delegatorAcc := simulation.RandomAcc(r, accs) delegatorAddress := delegatorAcc.Address - amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) - if amount.GT(sdk.ZeroInt()) { - amount = simulation.RandomAmount(r, amount) + delegations := k.GetAllDelegatorDelegations(ctx, delegatorAddress) + if len(delegations) == 0 { + return "no-operation", nil, nil } - if amount.Equal(sdk.ZeroInt()) { + delegation := delegations[r.Intn(len(delegations))] + + numShares := simulation.RandomDecAmount(r, delegation.Shares) + if numShares.Equal(sdk.ZeroDec()) { return "no-operation", nil, nil } msg := stake.MsgBeginUnbonding{ DelegatorAddr: delegatorAddress, - ValidatorAddr: validatorAddress, - SharesAmount: sdk.NewDecFromInt(amount), + ValidatorAddr: delegation.ValidatorAddr, + SharesAmount: numShares, } if msg.ValidateBasic() != nil { - return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s, got error %v", + msg.GetSignBytes(), msg.ValidateBasic()) } ctx, write := ctx.CacheContext() result := handler(ctx, msg) @@ -194,10 +196,10 @@ func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k stake.Keeper) simulation action string, fOp []simulation.FutureOperation, err error) { denom := k.GetParams(ctx).BondDenom - sourceValidatorAcc := simulation.RandomAcc(r, accs) - sourceValidatorAddress := sdk.ValAddress(sourceValidatorAcc.Address) - destValidatorAcc := simulation.RandomAcc(r, accs) - destValidatorAddress := sdk.ValAddress(destValidatorAcc.Address) + srcVal := keeper.RandomValidator(r, k, ctx) + srcValidatorAddress := srcVal.GetOperator() + destVal := keeper.RandomValidator(r, k, ctx) + destValidatorAddress := destVal.GetOperator() delegatorAcc := simulation.RandomAcc(r, accs) delegatorAddress := delegatorAcc.Address // TODO @@ -210,7 +212,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k stake.Keeper) simulation } msg := stake.MsgBeginRedelegate{ DelegatorAddr: delegatorAddress, - ValidatorSrcAddr: sourceValidatorAddress, + ValidatorSrcAddr: srcValidatorAddress, ValidatorDstAddr: destValidatorAddress, SharesAmount: sdk.NewDecFromInt(amount), } diff --git a/x/stake/stake.go b/x/stake/stake.go index 9d7617722..87087e59c 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -57,6 +57,9 @@ var ( GetREDsToValDstIndexKey = keeper.GetREDsToValDstIndexKey GetREDsByDelToValDstIndexKey = keeper.GetREDsByDelToValDstIndexKey TestingUpdateValidator = keeper.TestingUpdateValidator + UnbondingQueueKey = keeper.UnbondingQueueKey + RedelegationQueueKey = keeper.RedelegationQueueKey + ValidatorQueueKey = keeper.ValidatorQueueKey DefaultParamspace = keeper.DefaultParamspace KeyUnbondingTime = types.KeyUnbondingTime diff --git a/x/stake/types/delegation.go b/x/stake/types/delegation.go index 88b67018d..e73155427 100644 --- a/x/stake/types/delegation.go +++ b/x/stake/types/delegation.go @@ -47,7 +47,7 @@ func MustMarshalDelegation(cdc *codec.Codec, delegation Delegation) []byte { delegation.Shares, delegation.Height, } - return cdc.MustMarshalBinary(val) + return cdc.MustMarshalBinaryLengthPrefixed(val) } // return the delegation without fields contained within the key for the store @@ -62,7 +62,7 @@ func MustUnmarshalDelegation(cdc *codec.Codec, key, value []byte) Delegation { // return the delegation without fields contained within the key for the store func UnmarshalDelegation(cdc *codec.Codec, key, value []byte) (delegation Delegation, err error) { var storeValue delegationValue - err = cdc.UnmarshalBinary(value, &storeValue) + err = cdc.UnmarshalBinaryLengthPrefixed(value, &storeValue) if err != nil { err = fmt.Errorf("%v: %v", ErrNoDelegation(DefaultCodespace).Data(), err) return @@ -139,7 +139,7 @@ func MustMarshalUBD(cdc *codec.Codec, ubd UnbondingDelegation) []byte { ubd.InitialBalance, ubd.Balance, } - return cdc.MustMarshalBinary(val) + return cdc.MustMarshalBinaryLengthPrefixed(val) } // unmarshal a unbonding delegation from a store key and value @@ -154,7 +154,7 @@ func MustUnmarshalUBD(cdc *codec.Codec, key, value []byte) UnbondingDelegation { // unmarshal a unbonding delegation from a store key and value func UnmarshalUBD(cdc *codec.Codec, key, value []byte) (ubd UnbondingDelegation, err error) { var storeValue ubdValue - err = cdc.UnmarshalBinary(value, &storeValue) + err = cdc.UnmarshalBinaryLengthPrefixed(value, &storeValue) if err != nil { return } @@ -179,8 +179,8 @@ func UnmarshalUBD(cdc *codec.Codec, key, value []byte) (ubd UnbondingDelegation, // nolint func (d UnbondingDelegation) Equal(d2 UnbondingDelegation) bool { - bz1 := MsgCdc.MustMarshalBinary(&d) - bz2 := MsgCdc.MustMarshalBinary(&d2) + bz1 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d) + bz2 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d2) return bytes.Equal(bz1, bz2) } @@ -231,7 +231,7 @@ func MustMarshalRED(cdc *codec.Codec, red Redelegation) []byte { red.SharesSrc, red.SharesDst, } - return cdc.MustMarshalBinary(val) + return cdc.MustMarshalBinaryLengthPrefixed(val) } // unmarshal a redelegation from a store key and value @@ -246,7 +246,7 @@ func MustUnmarshalRED(cdc *codec.Codec, key, value []byte) Redelegation { // unmarshal a redelegation from a store key and value func UnmarshalRED(cdc *codec.Codec, key, value []byte) (red Redelegation, err error) { var storeValue redValue - err = cdc.UnmarshalBinary(value, &storeValue) + err = cdc.UnmarshalBinaryLengthPrefixed(value, &storeValue) if err != nil { return } @@ -275,8 +275,8 @@ func UnmarshalRED(cdc *codec.Codec, key, value []byte) (red Redelegation, err er // nolint func (d Redelegation) Equal(d2 Redelegation) bool { - bz1 := MsgCdc.MustMarshalBinary(&d) - bz2 := MsgCdc.MustMarshalBinary(&d2) + bz1 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d) + bz2 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d2) return bytes.Equal(bz1, bz2) } diff --git a/x/stake/types/errors.go b/x/stake/types/errors.go index e8c85800f..1a6ed6a64 100644 --- a/x/stake/types/errors.go +++ b/x/stake/types/errors.go @@ -155,6 +155,10 @@ func ErrNoRedelegation(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "no redelegation found") } +func ErrSelfRedelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "cannot redelegate to the same validator") +} + func ErrBadRedelegationDst(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "redelegation validator not found") } @@ -169,6 +173,11 @@ func ErrConflictingRedelegation(codespace sdk.CodespaceType) sdk.Error { "conflicting redelegation from this source validator to this dest validator already exists, you must wait for it to finish") } +func ErrDelegatorShareExRateInvalid(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, + "cannot delegate to validators with invalid (zero) ex-rate") +} + func ErrBothShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "both shares amount and shares percent provided") } diff --git a/x/stake/types/genesis.go b/x/stake/types/genesis.go index d08c6b899..f1673a376 100644 --- a/x/stake/types/genesis.go +++ b/x/stake/types/genesis.go @@ -1,11 +1,19 @@ package types +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + // GenesisState - all staking state that must be provided at genesis type GenesisState struct { - Pool Pool `json:"pool"` - Params Params `json:"params"` - Validators []Validator `json:"validators"` - Bonds []Delegation `json:"bonds"` + Pool Pool `json:"pool"` + Params Params `json:"params"` + IntraTxCounter int16 `json:"intra_tx_counter"` + LastTotalPower sdk.Int `json:"last_total_power"` + Validators []Validator `json:"validators"` + Bonds []Delegation `json:"bonds"` + UnbondingDelegations []UnbondingDelegation `json:"unbonding_delegations"` + Redelegations []Redelegation `json:"redelegations"` } func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []Delegation) GenesisState { diff --git a/x/stake/types/msg.go b/x/stake/types/msg.go index da7d684de..63d0afb5f 100644 --- a/x/stake/types/msg.go +++ b/x/stake/types/msg.go @@ -68,6 +68,7 @@ func (msg MsgCreateValidator) GetSigners() []sdk.AccAddress { func (msg MsgCreateValidator) GetSignBytes() []byte { b, err := MsgCdc.MarshalJSON(struct { Description + Commission CommissionMsg DelegatorAddr sdk.AccAddress `json:"delegator_address"` ValidatorAddr sdk.ValAddress `json:"validator_address"` PubKey string `json:"pubkey"` diff --git a/x/stake/types/params.go b/x/stake/types/params.go index 5915570c1..699758ace 100644 --- a/x/stake/types/params.go +++ b/x/stake/types/params.go @@ -49,8 +49,8 @@ func (p *Params) KeyValuePairs() params.KeyValuePairs { // Equal returns a boolean determining if two Param types are identical. func (p Params) Equal(p2 Params) bool { - bz1 := MsgCdc.MustMarshalBinary(&p) - bz2 := MsgCdc.MustMarshalBinary(&p2) + bz1 := MsgCdc.MustMarshalBinaryLengthPrefixed(&p) + bz2 := MsgCdc.MustMarshalBinaryLengthPrefixed(&p2) return bytes.Equal(bz1, bz2) } @@ -85,7 +85,7 @@ func MustUnmarshalParams(cdc *codec.Codec, value []byte) Params { // unmarshal the current staking params value from store key func UnmarshalParams(cdc *codec.Codec, value []byte) (params Params, err error) { - err = cdc.UnmarshalBinary(value, ¶ms) + err = cdc.UnmarshalBinaryLengthPrefixed(value, ¶ms) if err != nil { return } diff --git a/x/stake/types/pool.go b/x/stake/types/pool.go index e2015dcaf..4b227aa80 100644 --- a/x/stake/types/pool.go +++ b/x/stake/types/pool.go @@ -16,8 +16,8 @@ type Pool struct { // nolint func (p Pool) Equal(p2 Pool) bool { - bz1 := MsgCdc.MustMarshalBinary(&p) - bz2 := MsgCdc.MustMarshalBinary(&p2) + bz1 := MsgCdc.MustMarshalBinaryLengthPrefixed(&p) + bz2 := MsgCdc.MustMarshalBinaryLengthPrefixed(&p2) return bytes.Equal(bz1, bz2) } @@ -90,7 +90,7 @@ func MustUnmarshalPool(cdc *codec.Codec, value []byte) Pool { // unmarshal the current pool value from store key func UnmarshalPool(cdc *codec.Codec, value []byte) (pool Pool, err error) { - err = cdc.UnmarshalBinary(value, &pool) + err = cdc.UnmarshalBinaryLengthPrefixed(value, &pool) if err != nil { return } diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index 52d30f0a3..ca56e0ea4 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -87,7 +87,7 @@ func MustMarshalValidator(cdc *codec.Codec, validator Validator) []byte { UnbondingMinTime: validator.UnbondingMinTime, Commission: validator.Commission, } - return cdc.MustMarshalBinary(val) + return cdc.MustMarshalBinaryLengthPrefixed(val) } // unmarshal a redelegation from a store key and value @@ -106,7 +106,7 @@ func UnmarshalValidator(cdc *codec.Codec, operatorAddr, value []byte) (validator return } var storeValue validatorValue - err = cdc.UnmarshalBinary(value, &storeValue) + err = cdc.UnmarshalBinaryLengthPrefixed(value, &storeValue) if err != nil { return } @@ -392,6 +392,9 @@ func (v Validator) AddTokensFromDel(pool Pool, amount sdk.Int) (Validator, Pool, pool = pool.looseTokensToBonded(amountDec) } + if exRate.IsZero() { + panic("zero exRate should not happen") + } v.Tokens = v.Tokens.Add(amountDec) issuedShares := amountDec.Quo(exRate) v.DelegatorShares = v.DelegatorShares.Add(issuedShares)