diff --git a/.circleci/config.yml b/.circleci/config.yml index 6aa166229..155945ae6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,6 +8,7 @@ defaults: &defaults GOBIN: /tmp/workspace/bin jobs: + setup_dependencies: <<: *defaults steps: @@ -27,12 +28,6 @@ jobs: command: | export PATH="$GOBIN:$PATH" make get_vendor_deps - - run: - name: linter - command: | - export PATH="$GOBIN:$PATH" - go get -u github.com/tendermint/lint/golint - go get -u github.com/alecthomas/gometalinter - run: name: binaries command: | @@ -55,7 +50,7 @@ jobs: lint: <<: *defaults - parallelism: 4 + parallelism: 1 steps: - attach_workspace: at: /tmp/workspace @@ -63,28 +58,17 @@ jobs: key: v1-pkg-cache - restore_cache: key: v1-tree-{{ .Environment.CIRCLE_SHA1 }} + - run: + name: Get metalinter + command: | + export PATH="$GOBIN:$PATH" + make get_tools - run: name: Lint source command: | export PATH="$GOBIN:$PATH" - gometalinter --disable-all --enable='golint' --vendor ./... - - test_unit: - <<: *defaults - parallelism: 4 - steps: - - attach_workspace: - at: /tmp/workspace - - restore_cache: - key: v1-pkg-cache - - restore_cache: - key: v1-tree-{{ .Environment.CIRCLE_SHA1 }} - - run: - name: Test unit - command: | - export PATH="$GOBIN:$PATH" - make test_unit - + make test_lint + test_cli: <<: *defaults parallelism: 1 @@ -100,10 +84,10 @@ jobs: command: | export PATH="$GOBIN:$PATH" make test_cli - + test_cover: <<: *defaults - parallelism: 4 + parallelism: 2 steps: - attach_workspace: at: /tmp/workspace @@ -111,6 +95,7 @@ jobs: key: v1-pkg-cache - restore_cache: key: v1-tree-{{ .Environment.CIRCLE_SHA1 }} + - run: mkdir -p /tmp/logs - run: name: Run tests command: | @@ -119,15 +104,18 @@ jobs: for pkg in $(go list github.com/cosmos/cosmos-sdk/... | grep -v /vendor/ | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test | circleci tests split --split-by=timings); do id=$(basename "$pkg") - go test -timeout 5m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" + GOCACHE=off go test -v -timeout 8m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log" done - persist_to_workspace: root: /tmp/workspace paths: - "profiles/*" + - store_artifacts: + path: /tmp/logs upload_coverage: <<: *defaults + parallelism: 1 steps: - attach_workspace: at: /tmp/workspace @@ -157,9 +145,6 @@ workflows: - test_cli: requires: - setup_dependencies - - test_unit: - requires: - - setup_dependencies - test_cover: requires: - setup_dependencies diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..dcdf7080b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# CODEOWNERS: https://help.github.com/articles/about-codeowners/ + +# Primary repo maintainers +* @ebuchman @rigelrozanski @cwgoes + +# Precious documentation +/docs/ @zramsay @jolesbi diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index caa541510..521a4c335 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -10,3 +10,4 @@ v If a checkbox is n/a - please still include it but + a little note why * [ ] Updated CHANGELOG.md * [ ] Updated Gaia/Examples * [ ] Squashed all commits, uses message "Merge pull request #XYZ: [title]" ([coding standards](https://github.com/tendermint/coding/blob/master/README.md#merging-a-pr)) +* [ ] Added appropriate labels to PR (ex. wip, ready-for-review, docs) diff --git a/.gitignore b/.gitignore index da467c151..eb10faca7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.swp *.swo .vscode +.idea # Build vendor @@ -15,6 +16,7 @@ docs/_build examples/basecoin/app/data baseapp/data/* client/lcd/keys/* +mytestnet # Testing coverage.txt @@ -26,5 +28,9 @@ profile.out *.log vagrant +# IDE +.idea/ +*.iml + # Graphviz dependency-graph.png diff --git a/CHANGELOG.md b/CHANGELOG.md index e3de08a74..44fa7290e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,139 @@ # Changelog -## 0.19.1 +## 0.20.0 -*June 25, 2018* +*July 10th, 2018* -* Update to Tendermint v0.21.0 (fixes websocket memory leak and a few other - things) +BREAKING CHANGES +* msg.GetSignBytes() returns sorted JSON (by key) +* msg.GetSignBytes() field changes + * `msg_bytes` -> `msgs` + * `fee_bytes` -> `fee` +* Update Tendermint to v0.22.2 + * Default ports changed from 466xx to 266xx + * Amino JSON uses type names instead of prefix bytes + * ED25519 addresses are the first 20-bytes of the SHA256 of the raw 32-byte + pubkey (Instead of RIPEMD160) + * go-crypto, abci, tmlibs have been merged into Tendermint + * The keys sub-module is now in the SDK + * Various other fixes +* [auth] Signers of a transaction now only sign over their own account and sequence number +* [auth] Removed MsgChangePubKey +* [auth] Removed SetPubKey from account mapper +* [auth] AltBytes renamed to Memo, now a string, max 100 characters, costs a bit of gas +* [baseapp] NewBaseApp now takes option functions as parameters +* [types] `GetMsg()` -> `GetMsgs()` as txs wrap many messages +* [types] Removed GetMemo from Tx (it is still on StdTx) +* [types] renamed rational.Evaluate to rational.Round{Int64, Int} +* [types] Renamed `sdk.Address` to `sdk.AccAddress`/`sdk.ValAddress` +* [types] `sdk.AccAddress`/`sdk.ValAddress` natively marshals to Bech32 in String, Sprintf (when used with `%s`), and MarshalJSON +* [keys] Keybase and Ledger support from go-crypto merged into the SDK in the `crypto` folder +* [cli] Rearranged commands under subcommands +* [x/slashing] Update slashing for unbonding period + * Slash according to power at time of infraction instead of power at + time of discovery + * Iterate through unbonding delegations & redelegations which contributed + to an infraction, slash them proportional to their stake at the time + * Add REST endpoint to unrevoke a validator previously revoked for downtime + * Add REST endpoint to retrieve liveness signing information for a validator +* [x/stake] Remove Tick and add EndBlocker +* [x/stake] most index keys nolonger hold a value - inputs are rearranged to form the desired key +* [x/stake] store-value for delegation, validator, ubd, and red do not hold duplicate information contained store-key +* [x/stake] Introduce concept of unbonding for delegations and validators + * `gaiacli stake unbond` replaced with `gaiacli stake begin-unbonding` + * Introduced: + * `gaiacli stake complete-unbonding` + * `gaiacli stake begin-redelegation` + * `gaiacli stake complete-redelegation` +* [lcd] Switch key creation output to return bech32 +* [lcd] Removed shorthand CLI flags (`a`, `c`, `n`, `o`) +* [gaiad] genesis transactions now use bech32 addresses / pubkeys -BREAKING CHANGES: -* Changes the default ports from `4665X` to `2665X` +DEPRECATED +* [cli] Deprecated `--name` flag in commands that send txs, in favor of `--from` + +FEATURES +* [x/gov] Implemented MVP + * Supported proposal types: just binary (pass/fail) TextProposals for now + * Proposals need deposits to be votable; deposits are burned if proposal fails + * Delegators delegate votes to validator by default but can override (for their stake) +* [gaiacli] Ledger support added + - You can now use a Ledger with `gaiacli --ledger` for all key-related commands + - Ledger keys can be named and tracked locally in the key DB +* [gaiacli] You can now attach a simple text-only memo to any transaction, with the `--memo` flag +* [gaiacli] added the following flags for commands that post transactions to the chain: + * async -- send the tx without waiting for a tendermint response + * json -- return the output in json format for increased readability + * print-response -- return the tx response. (includes fields like gas cost) +* [lcd] Queried TXs now include the tx hash to identify each tx +* [mockapp] CompleteSetup() no longer takes a testing parameter +* [x/bank] Add benchmarks for signing and delivering a block with a single bank transaction + * Run with `cd x/bank && go test --bench=.` +* [tools] make get_tools installs tendermint's linter, and gometalinter +* [tools] Switch gometalinter to the stable version +* [tools] Add the following linters + * misspell + * gofmt + * go vet -composites=false + * unconvert + * ineffassign + * errcheck + * unparam + * gocyclo +* [tools] Added `make format` command to automate fixing misspell and gofmt errors. +* [server] Default config now creates a profiler at port 6060, and increase p2p send/recv rates +* [types] Switches internal representation of Int/Uint/Rat to use pointers +* [types] Added MinInt and MinUint functions +* [gaiad] `unsafe_reset_all` now resets addrbook.json +* [democoin] add x/oracle, x/assoc +* [tests] created a randomized testing framework. + - Currently bank has limited functionality in the framework + - Auth has its invariants checked within the framework +* [x/stake] Allow validator to be created with starting delegation by a third-party delegator on behalf of validator. +* [tests] Add WaitForNextNBlocksTM helper method +* [keys] New keys now have 24 word recovery keys, for heightened security + +IMPROVEMENTS +* [x/bank] Now uses go-wire codec instead of 'encoding/json' +* [x/auth] Now uses go-wire codec instead of 'encoding/json' +* revised use of endblock and beginblock +* [stake] module reorganized to include `types` and `keeper` package +* [stake] keeper always loads the store (instead passing around which doesn't really boost efficiency) +* [stake] edit-validator changes now can use the keyword [do-not-modify] to not modify unspecified `--flag` (aka won't set them to `""` value) +* [stake] offload more generic functionality from the handler into the keeper +* [types] added common tag constants +* [keys] improve error message when deleting non-existent key +* [gaiacli] improve error messages on `send` and `account` commands +* added contributing guidelines +* [docs] Added commands for governance CLI on testnet README + +BUG FIXES +* [x/slashing] \#1510 Unrevoked validators cannot un-revoke themselves +* [x/stake] \#1513 Validators slashed to zero power are unbonded and removed from the store +* [x/stake] \#1567 Validators decreased in power but not unbonded are now updated in Tendermint +* [x/stake] error strings lower case +* [x/stake] pool loose tokens now accounts for unbonding and unbonding tokens not associated with any validator +* [x/stake] fix revoke bytes ordering (was putting revoked candidates at the top of the list) +* [x/stake] bond count was counting revoked validators as bonded, fixed +* [gaia] Added self delegation for validators in the genesis creation +* [lcd] tests now don't depend on raw json text +* Retry on HTTP request failure in CLI tests, add option to retry tests in Makefile +* Fixed bug where chain ID wasn't passed properly in x/bank REST handler, removed Viper hack from ante handler +* Fixed bug where `democli account` didn't decode the account data correctly +* \#872 - recovery phrases no longer all end in `abandon` +* \#887 - limit the size of rationals that can be passed in from user input +* \#1052 - Make all now works +* \#1258 - printing big.rat's can no longer overflow int64 +* \#1259 - fix bug where certain tests that could have a nil pointer in defer +* \#1343 - fixed unnecessary parallelism in CI +* \#1353 - CLI: Show pool shares fractions in human-readable format +* \#1367 - set ChainID in InitChain +* \#1461 - CLI tests now no longer reset your local environment data +* \#1505 - `gaiacli stake validator` no longer panics if validator doesn't exist +* \#1565 - fix cliff validator persisting when validator set shrinks from max +* \#1287 - prevent zero power validators at genesis +* [x/stake] fix bug when unbonding/redelegating using `--shares-percent` +* \#1010 - two validators can't bond with the same pubkey anymore ## 0.19.0 @@ -18,26 +143,34 @@ BREAKING CHANGES: BREAKING CHANGES * msg.GetSignBytes() now returns bech32-encoded addresses in all cases * [lcd] REST end-points now include gas +* sdk.Coin now uses sdk.Int, a big.Int wrapper with 256bit range cap FEATURES * [x/auth] Added AccountNumbers to BaseAccount and StdTxs to allow for replay protection with account pruning +* [lcd] added an endpoint to query for the SDK version of the connected node IMPROVEMENTS * export command now writes current validator set for Tendermint * [tests] Application module tests now use a mock application * [gaiacli] Fix error message when account isn't found when running gaiacli account * [lcd] refactored to eliminate use of global variables, and interdependent tests +* [tests] Added testnet command to gaiad +* [tests] Added localnet targets to Makefile * [x/stake] More stake tests added to test ByPower index FIXES * Fixes consensus fault on testnet - see postmortem [here](https://github.com/cosmos/cosmos-sdk/issues/1197#issuecomment-396823021) -* [x/stake] bonded inflation removed, non-bonded inflation partially implemented +* [x/stake] bonded inflation removed, non-bonded inflation partially implemented * [lcd] Switch to bech32 for addresses on all human readable inputs and outputs * [lcd] fixed tx indexing/querying * [cli] Added `--gas` flag to specify transaction gas limit * [gaia] Registered slashing message handler * [x/slashing] Set signInfo.StartHeight correctly for newly bonded validators +FEATURES +* [docs] Reorganize documentation +* [docs] Update staking spec, create WIP spec for slashing, and fees + ## 0.18.0 *June 9, 2018* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..c9831cc86 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,105 @@ +# Contributing + +Thank you for considering making contributions to Cosmos-SDK and related repositories! Start by taking a look at this [coding repo](https://github.com/tendermint/coding) for overall information on repository workflow and standards. + +Please follow standard github best practices: fork the repo, branch from the tip of develop, make some commits, and submit a pull request to develop. See the [open issues](https://github.com/cosmos/cosmos-sdk/issues) for things we need help with! + +Please make sure to use `gofmt` before every commit - the easiest way to do this is have your editor run it for you upon saving a file. Additionally please ensure that your code is lint compliant by running `make lint` + +Looking for a good place to start contributing? How about checking out some [good first issues](https://github.com/cosmos/cosmos-sdk/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) + +## Forking + +Please note that Go requires code to live under absolute paths, which complicates forking. +While my fork lives at `https://github.com/rigeyrigerige/cosmos-sdk`, +the code should never exist at `$GOPATH/src/github.com/rigeyrigerige/cosmos-sdk`. +Instead, we use `git remote` to add the fork as a new remote for the original repo, +`$GOPATH/src/github.com/cosmos/cosmos-sdk `, and do all the work there. + +For instance, to create a fork and work on a branch of it, I would: + + * Create the fork on github, using the fork button. + * Go to the original repo checked out locally (i.e. `$GOPATH/src/github.com/cosmos/cosmos-sdk`) + * `git remote rename origin upstream` + * `git remote add origin git@github.com:ebuchman/basecoin.git` + +Now `origin` refers to my fork and `upstream` refers to the Cosmos-SDK version. +So I can `git push -u origin master` to update my fork, and make pull requests to Cosmos-SDK from there. +Of course, replace `ebuchman` with your git handle. + +To pull in updates from the origin repo, run + + * `git fetch upstream` + * `git rebase upstream/master` (or whatever branch you want) + +Please don't make Pull Requests to `master`. + +## Dependencies + +We use [dep](https://github.com/golang/dep) to manage dependencies. + +That said, the master branch of every Cosmos repository should just build +with `go get`, which means they should be kept up-to-date with their +dependencies so we can get away with telling people they can just `go get` our +software. + +Since some dependencies are not under our control, a third party may break our +build, in which case we can fall back on `dep ensure` (or `make +get_vendor_deps`). Even for dependencies under our control, dep helps us to +keep multiple repos in sync as they evolve. Anything with an executable, such +as apps, tools, and the core, should use dep. + +Run `dep status` to get a list of vendor dependencies that may not be +up-to-date. + +## Testing + +All repos should be hooked up to [CircleCI](https://circleci.com/). + +If they have `.go` files in the root directory, they will be automatically +tested by circle using `go test -v -race ./...`. If not, they will need a +`circle.yml`. Ideally, every repo has a `Makefile` that defines `make test` and +includes its continuous integration status using a badge in the `README.md`. + +## Branching Model and Release + +User-facing repos should adhere to the branching model: http://nvie.com/posts/a-successful-git-branching-model/. +That is, these repos should be well versioned, and any merge to master requires a version bump and tagged release. + +Libraries need not follow the model strictly, but would be wise to. + +The SDK utilizes [semantic versioning](https://semver.org/). + +### Development Procedure: +- the latest state of development is on `develop` +- `develop` must never fail `make test` or `make test_cli` +- `develop` should not fail `make test_lint` +- no --force onto `develop` (except when reverting a broken commit, which should seldom happen) +- create a development branch either on github.com/cosmos/cosmos-sdk, or your fork (using `git remote add origin`) +- before submitting a pull request, begin `git rebase` on top of `develop` + +### Pull Merge Procedure: +- ensure pull branch is rebased on develop +- run `make test` and `make test_cli` to ensure that all tests pass +- merge pull request +- push master may request that pull requests be rebased on top of `unstable` + +### Release Procedure: +- start on `develop` +- prepare changelog/release issue +- bump versions +- push to release-vX.X.X to run CI +- merge to master +- merge master back to develop + +### Hotfix Procedure: +- start on `master` +- checkout a new branch named hotfix-vX.X.X +- make the required changes + - these changes should be small and an absolute necessity + - add a note to CHANGELOG.md +- bump versions +- push to hotfix-vX.X.X to run the extended integration tests on the CI +- merge hotfix-vX.X.X to master +- merge hotfix-vX.X.X to develop +- delete the hotfix-vX.X.X branch diff --git a/Dockerfile b/Dockerfile index 80bbae85f..5dfd625de 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,34 +1,35 @@ # Simple usage with a mounted data directory: # > docker build -t gaia . -# > docker run -v $HOME/.gaiad:/root/.gaiad gaia init -# > docker run -v $HOME/.gaiad:/root/.gaiad gaia start - -FROM alpine:edge +# > docker run -it -p 46657:46657 -p 46656:46656 -v ~/.gaiad:/root/.gaiad -v ~/.gaiacli:/root/.gaiacli gaia gaiad init +# > docker run -it -p 46657:46657 -p 46656:46656 -v ~/.gaiad:/root/.gaiad -v ~/.gaiacli:/root/.gaiacli gaia gaiad start +FROM golang:alpine AS build-env # Set up dependencies -ENV PACKAGES go glide make git libc-dev bash +ENV PACKAGES make git libc-dev bash gcc linux-headers eudev-dev -# Set up GOPATH & PATH - -ENV GOPATH /root/go -ENV BASE_PATH $GOPATH/src/github.com/cosmos -ENV REPO_PATH $BASE_PATH/cosmos-sdk -ENV WORKDIR /cosmos/ -ENV PATH $GOPATH/bin:$PATH - -# Link expected Go repo path - -RUN mkdir -p $WORKDIR $GOPATH/pkg $ $GOPATH/bin $BASE_PATH +# Set working directory for the build +WORKDIR /go/src/github.com/cosmos/cosmos-sdk # Add source files - -ADD . $REPO_PATH +COPY . . # Install minimum necessary dependencies, build Cosmos SDK, remove packages RUN apk add --no-cache $PACKAGES && \ - cd $REPO_PATH && make get_tools && make get_vendor_deps && make build && make install && \ - apk del $PACKAGES + make get_tools && \ + make get_vendor_deps && \ + make build && \ + make install -# Set entrypoint +# Final image +FROM alpine:edge -ENTRYPOINT ["gaiad"] +# Install ca-certificates +RUN apk add --update ca-certificates +WORKDIR /root + +# Copy over binaries from the build-env +COPY --from=build-env /go/bin/gaiad /usr/bin/gaiad +COPY --from=build-env /go/bin/gaiacli /usr/bin/gaiacli + +# Run gaiad by default, omit entrypoint to ease using container with gaiacli +CMD ["gaiad"] diff --git a/Gopkg.lock b/Gopkg.lock index 21e9c44bb..a98a7c86a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,23 +1,41 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + branch = "master" + name = "github.com/bartekn/go-bip39" + packages = ["."] + revision = "a05967ea095d81c8fe4833776774cfaff8e5036c" + +[[projects]] + branch = "master" + name = "github.com/beorn7/perks" + packages = ["quantile"] + revision = "3a771d992973f24aa725d07868b467d1ddfceafb" + [[projects]] name = "github.com/bgentry/speakeasy" packages = ["."] revision = "4aabc24848ce5fd31929f7d1e4ea74d3709c14cd" version = "v0.1.0" +[[projects]] + branch = "master" + name = "github.com/brejski/hid" + packages = ["."] + revision = "06112dcfcc50a7e0e4fd06e17f9791e788fdaafc" + [[projects]] branch = "master" name = "github.com/btcsuite/btcd" packages = ["btcec"] - revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64" + revision = "fdfc19097e7ac6b57035062056f5b7b4638b8898" [[projects]] branch = "master" name = "github.com/btcsuite/btcutil" packages = ["bech32"] - revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" + revision = "ab6388e0c60ae4834a1f57511e20c17b5f78be4b" [[projects]] name = "github.com/davecgh/go-spew" @@ -42,7 +60,11 @@ packages = [ "log", "log/level", - "log/term" + "log/term", + "metrics", + "metrics/discard", + "metrics/internal/lv", + "metrics/prometheus" ] revision = "4dc7be5d2d12881735283bcab7352178e190fc71" version = "v0.6.0" @@ -81,8 +103,8 @@ "ptypes/duration", "ptypes/timestamp" ] - revision = "925541529c1fa6821df4e44ce2723319eb2be768" - version = "v1.0.0" + revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265" + version = "v1.1.0" [[projects]] branch = "master" @@ -125,12 +147,6 @@ ] revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168" -[[projects]] - branch = "master" - name = "github.com/howeyc/crc16" - packages = ["."] - revision = "2b2a61e366a66d3efb279e46176e7291001e0354" - [[projects]] name = "github.com/inconshreveable/mousetrap" packages = ["."] @@ -161,6 +177,12 @@ revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" version = "v0.0.3" +[[projects]] + name = "github.com/matttproud/golang_protobuf_extensions" + packages = ["pbutil"] + revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" + version = "v1.0.1" + [[projects]] branch = "master" name = "github.com/mitchellh/mapstructure" @@ -185,6 +207,42 @@ revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" +[[projects]] + branch = "master" + name = "github.com/prometheus/client_golang" + packages = [ + "prometheus", + "prometheus/promhttp" + ] + revision = "ae27198cdd90bf12cd134ad79d1366a6cf49f632" + +[[projects]] + branch = "master" + name = "github.com/prometheus/client_model" + packages = ["go"] + revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c" + +[[projects]] + branch = "master" + name = "github.com/prometheus/common" + packages = [ + "expfmt", + "internal/bitbucket.org/ww/goautoneg", + "model" + ] + revision = "7600349dcfe1abd18d72d3a1770870d9800a7801" + +[[projects]] + branch = "master" + name = "github.com/prometheus/procfs" + packages = [ + ".", + "internal/util", + "nfs", + "xfs" + ] + revision = "ae68e2d4c00fed4943b5f6698d504a5fe083da8a" + [[projects]] branch = "master" name = "github.com/rcrowley/go-metrics" @@ -209,8 +267,8 @@ [[projects]] name = "github.com/spf13/cobra" packages = ["."] - revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385" - version = "v0.0.3" + revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b" + version = "v0.0.1" [[projects]] branch = "master" @@ -227,8 +285,8 @@ [[projects]] name = "github.com/spf13/viper" packages = ["."] - revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736" - version = "v1.0.2" + revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7" + version = "v1.0.0" [[projects]] name = "github.com/stretchr/testify" @@ -236,8 +294,8 @@ "assert", "require" ] - revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" - version = "v1.2.2" + revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" + version = "v1.2.1" [[projects]] branch = "master" @@ -256,19 +314,7 @@ "leveldb/table", "leveldb/util" ] - revision = "0d5a0ceb10cf9ab89fdd744cc8c50a83134f6697" - -[[projects]] - name = "github.com/tendermint/abci" - packages = [ - "client", - "example/code", - "example/kvstore", - "server", - "types" - ] - revision = "198dccf0ddfd1bb176f87657e3286a05a6ed9540" - version = "v0.12.0" + revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445" [[projects]] branch = "master" @@ -283,40 +329,42 @@ [[projects]] name = "github.com/tendermint/go-amino" packages = ["."] - revision = "ed62928576cfcaf887209dc96142cd79cdfff389" - version = "0.9.9" - -[[projects]] - name = "github.com/tendermint/go-crypto" - packages = [ - ".", - "keys", - "keys/bcrypt", - "keys/words", - "keys/words/wordlist" - ] - revision = "915416979bf70efa4bcbf1c6cd5d64c5fff9fc19" - version = "v0.6.2" + revision = "2106ca61d91029c931fd54968c2bb02dc96b1412" + version = "0.10.1" [[projects]] name = "github.com/tendermint/iavl" - packages = [ - ".", - "sha256truncated" - ] - revision = "c9206995e8f948e99927f5084a88a7e94ca256da" - version = "v0.8.0-rc0" + packages = ["."] + revision = "35f66e53d9b01e83b30de68b931f54b2477a94c9" + version = "v0.9.2" [[projects]] name = "github.com/tendermint/tendermint" packages = [ + "abci/client", + "abci/example/code", + "abci/example/kvstore", + "abci/server", + "abci/types", "blockchain", "cmd/tendermint/commands", "config", "consensus", "consensus/types", + "crypto", + "crypto/merkle", + "crypto/tmhash", "evidence", + "libs/autofile", + "libs/bech32", + "libs/cli", + "libs/cli/flags", + "libs/clist", + "libs/common", + "libs/db", "libs/events", + "libs/flowrate", + "libs/log", "libs/pubsub", "libs/pubsub/query", "lite", @@ -347,26 +395,13 @@ "types", "version" ] - revision = "46369a1ab76f274ab47179c4176221842b8207b4" - version = "v0.21.0" + revision = "5ff65274b84ea905787a48512cc3124385bddf2f" + version = "v0.22.2" [[projects]] - name = "github.com/tendermint/tmlibs" - packages = [ - "autofile", - "bech32", - "cli", - "cli/flags", - "clist", - "common", - "db", - "flowrate", - "log", - "merkle", - "merkle/tmhash" - ] - revision = "49596e0a1f48866603813df843c9409fc19805c6" - version = "v0.9.0" + name = "github.com/zondax/ledger-goclient" + packages = ["."] + revision = "065cbf938a16f20335c40cfe180f9cd4955c6a5a" [[projects]] branch = "master" @@ -379,6 +414,7 @@ "nacl/secretbox", "openpgp/armor", "openpgp/errors", + "pbkdf2", "poly1305", "ripemd160", "salsa20/salsa" @@ -395,15 +431,16 @@ "http2/hpack", "idna", "internal/timeseries", + "netutil", "trace" ] - revision = "afe8f62b1d6bbd81f31868121a50b06d8188e1f9" + revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f" [[projects]] branch = "master" name = "golang.org/x/sys" packages = ["unix"] - revision = "a200a19cb90b19de298170992778b1fda7217bd6" + revision = "1b2967e3c290b7c545b3db0deeda16e9be4f98a2" [[projects]] name = "golang.org/x/text" @@ -427,18 +464,23 @@ version = "v0.3.0" [[projects]] + branch = "master" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] - revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" + revision = "e92b116572682a5b432ddd840aeaba2a559eeff1" [[projects]] name = "google.golang.org/grpc" packages = [ ".", "balancer", + "balancer/base", + "balancer/roundrobin", "codes", "connectivity", "credentials", + "encoding", + "encoding/proto", "grpclb/grpc_lb_v1/messages", "grpclog", "internal", @@ -447,13 +489,15 @@ "naming", "peer", "resolver", + "resolver/dns", + "resolver/passthrough", "stats", "status", "tap", "transport" ] - revision = "5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e" - version = "v1.7.5" + revision = "d11072e7ca9811b1100b80ca0269ac831f06d024" + version = "v1.11.3" [[projects]] name = "gopkg.in/yaml.v2" @@ -464,6 +508,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "3a437ed2c22314c3762584ff52c76a58916fd9e9fef00035aa01ae65bce08637" + inputs-digest = "33d51b55781a1579d2fde1bba70077b49c5804862ea56c72136f013c8498e0c6" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index ff7f2bbd4..0bb13c3e3 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -28,18 +28,14 @@ name = "github.com/bgentry/speakeasy" version = "~0.1.0" -[[constraint]] +[[override]] name = "github.com/golang/protobuf" - version = "~1.0.0" + version = "=1.1.0" [[constraint]] name = "github.com/mattn/go-isatty" version = "~0.0.3" -[[constraint]] - name = "github.com/pkg/errors" - version = "~0.8.0" - [[constraint]] name = "github.com/spf13/cobra" version = "~0.0.1" @@ -48,38 +44,33 @@ name = "github.com/spf13/viper" version = "~1.0.0" +[[constraint]] + name = "github.com/pkg/errors" + version = "=0.8.0" + [[constraint]] name = "github.com/stretchr/testify" - version = "~1.2.1" - -[[override]] - name = "github.com/tendermint/abci" - version = "=0.12.0" - -[[override]] - name = "github.com/tendermint/go-crypto" - version = "=0.6.2" + version = "=1.2.1" [[override]] name = "github.com/tendermint/go-amino" - version = "=0.9.9" + version = "=0.10.1" [[override]] name = "github.com/tendermint/iavl" - version = "=0.8.0-rc0" + version = "=v0.9.2" [[override]] name = "github.com/tendermint/tendermint" - version = "=0.21.0" + version = "=0.22.2" -[[override]] - name = "github.com/tendermint/tmlibs" - version = "=v0.9.0" +[[constraint]] + name = "github.com/bartekn/go-bip39" + branch = "master" -# this got updated and broke, so locked to an old working commit ... -[[override]] - name = "google.golang.org/genproto" - revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" +[[constraint]] + name = "github.com/zondax/ledger-goclient" + revision = "065cbf938a16f20335c40cfe180f9cd4955c6a5a" [prune] go-tests = true diff --git a/Makefile b/Makefile index c0d18c4a3..4d761ede1 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ PACKAGES=$(shell go list ./... | grep -v '/vendor/') PACKAGES_NOCLITEST=$(shell go list ./... | grep -v '/vendor/' | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test) COMMIT_HASH := $(shell git rev-parse --short HEAD) -BUILD_FLAGS = -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}" +BUILD_FLAGS = -tags netgo -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}" -all: check_tools get_vendor_deps install install_examples test_lint test +all: get_tools get_vendor_deps install install_examples test_lint test ######################################## ### CI @@ -36,11 +36,11 @@ else go build $(BUILD_FLAGS) -o build/democli ./examples/democoin/cmd/democli endif -install: +install: go install $(BUILD_FLAGS) ./cmd/gaia/cmd/gaiad go install $(BUILD_FLAGS) ./cmd/gaia/cmd/gaiacli -install_examples: +install_examples: go install $(BUILD_FLAGS) ./examples/basecoin/cmd/basecoind go install $(BUILD_FLAGS) ./examples/basecoin/cmd/basecli go install $(BUILD_FLAGS) ./examples/democoin/cmd/democoind @@ -89,7 +89,7 @@ godocs: test: test_unit -test_cli: +test_cli: @go test -count 1 -p 1 `go list github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test` test_unit: @@ -102,7 +102,13 @@ test_cover: @bash tests/test_cover.sh test_lint: - gometalinter --disable-all --enable='golint' --vendor ./... + gometalinter.v2 --config=tools/gometalinter.json ./... + !(gometalinter.v2 --disable-all --enable='errcheck' --vendor ./... | grep -v "client/") + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs gofmt -d -s + +format: + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs gofmt -w -s + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs misspell -w benchmark: @go test -bench=. $(PACKAGES_NOCLITEST) @@ -133,12 +139,27 @@ devdoc_update: ######################################## -### Remote validator nodes using terraform and ansible +### Local validator nodes using docker and docker-compose # Build linux binary build-linux: GOOS=linux GOARCH=amd64 $(MAKE) build +build-docker-gaiadnode: + $(MAKE) -C networks/local + +# Run a 4-node testnet locally +localnet-start: localnet-stop + @if ! [ -f build/node0/gaiad/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/gaiad:Z tendermint/gaiadnode testnet --v 4 --o . --starting-ip-address 192.168.10.2 ; fi + docker-compose up + +# Stop testnet +localnet-stop: + docker-compose down + +######################################## +### Remote validator nodes using terraform and ansible + TESTNET_NAME?=remotenet SERVERS?=4 BINARY=$(CURDIR)/build/gaiad @@ -160,4 +181,4 @@ remotenet-status: # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: build build_examples install install_examples install_debug dist check_tools get_tools get_vendor_deps draw_deps test test_cli test_unit test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update remotenet-start remotenet-stop remotenet-status +.PHONY: build build_examples install install_examples install_debug dist check_tools get_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 remotenet-start remotenet-stop remotenet-status format diff --git a/README.md b/README.md index 3af98fb4f..0383fded2 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![version](https://img.shields.io/github/tag/cosmos/cosmos-sdk.svg)](https://github.com/cosmos/cosmos-sdk/releases/latest) [![API Reference](https://godoc.org/github.com/cosmos/cosmos-sdk?status.svg )](https://godoc.org/github.com/cosmos/cosmos-sdk) -[![Rocket.Chat](https://demo.rocket.chat/images/join-chat.svg)](https://cosmos.rocket.chat/) +[![riot.im](https://img.shields.io/badge/riot.im-JOIN%20CHAT-green.svg)](https://riot.im/app/#/room/#cosmos-sdk:matrix.org) [![license](https://img.shields.io/github/license/cosmos/cosmos-sdk.svg)](https://github.com/cosmos/cosmos-sdk/blob/master/LICENSE) [![LoC](https://tokei.rs/b1/github/cosmos/cosmos-sdk)](https://github.com/cosmos/cosmos-sdk) [![Go Report Card](https://goreportcard.com/badge/github.com/cosmos/cosmos-sdk)](https://goreportcard.com/report/github.com/cosmos/cosmos-sdk) @@ -14,11 +14,15 @@ Branch | Tests | Coverage develop | [![CircleCI](https://circleci.com/gh/cosmos/cosmos-sdk/tree/develop.svg?style=shield)](https://circleci.com/gh/cosmos/cosmos-sdk/tree/develop) | [![codecov](https://codecov.io/gh/cosmos/cosmos-sdk/branch/develop/graph/badge.svg)](https://codecov.io/gh/cosmos/cosmos-sdk) master | [![CircleCI](https://circleci.com/gh/cosmos/cosmos-sdk/tree/master.svg?style=shield)](https://circleci.com/gh/cosmos/cosmos-sdk/tree/master) | [![codecov](https://codecov.io/gh/cosmos/cosmos-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/cosmos/cosmos-sdk) -**WARNING**: the libraries are still undergoing breaking changes as we get better ideas and start building out the Apps. +The Cosmos-SDK is a framework for building blockchain applications in Golang. +It is being used to build `Gaia`, the first implementation of the [Cosmos Hub](https://cosmos.network), + +**WARNING**: The SDK has mostly stabilized, but we are still making some +breaking changes. **Note**: Requires [Go 1.10+](https://golang.org/dl/) -## Testnet +## Gaia Testnet For more information on connecting to the testnet, see [cmd/gaia/testnets](/cmd/gaia/testnets) @@ -26,59 +30,14 @@ For more information on connecting to the testnet, see For the latest status of the testnet, see the [status file](/cmd/gaia/testnets/STATUS.md). +## Install -## Overview +See the [install instructions](/docs/install.md) -The Cosmos-SDK is a platform for building multi-asset Proof-of-Stake (PoS) blockchains, like the [Cosmos Hub](https://cosmos.network). It is both a library for building and securely interacting with blockchain applications. +## Quick Start -The goal of the Cosmos-SDK is to allow developers to easily create custom interoperable blockchain applications within the Cosmos Network without having to recreate common blockchain functionality, thus abstracting away the complexities of building a Tendermint ABCI application. We envision the SDK as the `npm`-like framework to build secure blockchain applications on top of Tendermint. - -In terms of its design, the SDK optimizes flexibility and security. The framework is designed around a modular execution stack which allows applications to mix and match elements as desired. In addition, all modules are sandboxed for greater application security. - -It is based on two major principles: - -- **Composability**: Anyone can create a module for the Cosmos-SDK and integrating the already-built modules is as simple as importing them into your blockchain application. - -- **Capabilities**: The SDK is inspired by capabilities-based security, and informed by years of wrestling with blockchain state machines. Most developers will need to access other 3rd party modules when building their own modules. Given that the Cosmos-SDK is an open framework and that we assume that some those modules may be malicious, we designed the SDK using object-capabilities (_ocaps_) based principles. In practice, this means that instead of having each module keep an access control list for other modules, each module implements `keepers` that can be passed to other modules to grant a pre-defined set of capabilities. For example, if an instance of module A's `keepers` is passed to module B, the latter will be able to call a restricted set of module A's functions. - - The capabilities of each `keeper` are defined by the module's developer, and it's their job to understand and audit the safety of foreign code from 3rd party modules based on the capabilities they are passing into each 3rd party module. For a deeper look at capabilities, you can read this [article](http://habitatchronicles.com/2017/05/what-are-capabilities/). - -_Note: For now the Cosmos-SDK only exists in [Golang](https://golang.org/), which means that developers can only develop SDK modules in Golang. In the future, we expect that the SDK will be implemented in other programming languages. Funding opportunities supported by the Tendermint team may be available eventually._ - -## Application architecture - -#### Modules - -The Cosmos-SDK has all the necessary pre-built modules to add functionality on top of a `BaseApp`, which is the template to build a blockchain dApp in Cosmos. In this context, a `module` is a fundamental unit in the Cosmos-SDK. Each module is an extension of the `BaseApp` functionalities that defines transactions, handles application state and the state transition logic. Each module also contains handlers for messages and transactions, as well as REST and CLI for secure user interactions. - -Some of the most important modules already integrated in the SDK are: - -- `Auth`: Defines a standard account structure (`BaseAccount`) and how transaction signers are authenticated. -- `Bank`: Defines how coins (i.e cryptocurrencies) are transferred. -- `Governance`: Governance related implementation including proposals and voting. -- `Staking`: Proof of Stake related implementation including bonding and delegation transactions, inflation, fees, unbonding, etc. -- `IBC`: Defines the intereoperability of blockchain zones according to the specifications of the [IBC Protocol](https://cosmos.network/whitepaper#inter-blockchain-communication-ibc). - - -#### Directories - -The key directories of the SDK are: - -- `baseapp`: Defines the template for a basic [ABCI ](https://cosmos.network/whitepaper#abci) application so that your Cosmos-SDK application can communicate with the underlying Tendermint node. -- `client`: CLI and REST server tooling. -- `server`: RPC server to communicate with the node. -- `examples`: Contains examples on how to build working standalone applications. -- `store`: Contains code for the multistore (_i.e._ state). Each module can create any number of `KVStores` (key-value stores) from the multistore. -- `types`: Common types required in any SDK-based application. -- `x`(for e**X**tensions): Folder for storing the `BaseApp` module and all the already-built modules described in the previous section. - -## Prerequisites - -- [Golang](https://golang.org/doc/install) - -## Getting Started - -See the [documentation](https://cosmos-sdk.readthedocs.io). +- [Documentation](/docs) +- [Examples](/examples) ## Disambiguation diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..4eddc8c4f --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,29 @@ +# Security + +As part of our [Coordinated Vulnerability Disclosure +Policy](https://tendermint.com/security), we operate a bug bounty. +See the policy for more details on submissions and rewards. + +The following is a list of examples of the kinds of bugs we're most interested in for +the Cosmos SDK. See [here](https://github.com/tendermint/tendermint/blob/master/SECURITY.md) for vulnerabilities we are interested in for Tendermint, and lower-level libraries, e.g. IAVL. + +## Modules +- x/staking +- x/slashing +- x/types +- x/gov + +We are interested in bugs in other modules, however the above are most likely to have +significant vulnerabilities, due to the complexity / nuance involved + +## How we process Tx parameters +- Integer operations on tx parameters, especially sdk.Int / sdk.Uint +- Gas calculation & parameter choices +- Tx signature verification (code in x/auth/ante.go) +- Possible Node DoS vectors. (Perhaps due to Gas weighting / non constant timing) + +## Handling private keys +- HD key derivation, local and Ledger, and all key-management functionality +- Side-channel attack vectors with our implementations + - e.g. key exfiltration based on time or memory-access patterns when decrypting privkey + diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index e2159aa29..b17e79777 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -7,13 +7,14 @@ import ( "github.com/pkg/errors" - abci "github.com/tendermint/abci/types" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" ) @@ -63,11 +64,8 @@ type BaseApp struct { // checkState is set on initialization and reset on Commit. // deliverState is set in InitChain and BeginBlock and cleared on Commit. // See methods setCheckState and setDeliverState. - // .valUpdates accumulate in DeliverTx and are reset in BeginBlock. - // QUESTION: should we put valUpdates in the deliverState.ctx? checkState *state // for CheckTx deliverState *state // for DeliverTx - valUpdates []abci.Validator // cached validator changes from DeliverTx signedValidators []abci.SigningValidator // absent validators from begin block } @@ -75,7 +73,8 @@ var _ abci.Application = (*BaseApp)(nil) // Create and name new BaseApp // NOTE: The db is used to store the version number for now. -func NewBaseApp(name string, cdc *wire.Codec, logger log.Logger, db dbm.DB) *BaseApp { +// Accepts variable number of option functions, which act on the BaseApp to set configuration choices +func NewBaseApp(name string, cdc *wire.Codec, logger log.Logger, db dbm.DB, options ...func(*BaseApp)) *BaseApp { app := &BaseApp{ Logger: logger, name: name, @@ -88,6 +87,9 @@ func NewBaseApp(name string, cdc *wire.Codec, logger log.Logger, db dbm.DB) *Bas } // Register the undefined & root codespaces, which should not be used by any modules app.codespacer.RegisterOrPanic(sdk.CodespaceRoot) + for _, option := range options { + option(app) + } return app } @@ -124,6 +126,11 @@ func (app *BaseApp) SetTxDecoder(txDecoder sdk.TxDecoder) { } // default custom logic for transaction decoding +// TODO: remove auth and wire dependencies from baseapp +// - move this to auth.DefaultTxDecoder +// - set the default here to JSON decode like docs/examples/app1 (it will fail +// for multiple messages ;)) +// - pass a TxDecoder into NewBaseApp, instead of a codec. func defaultTxDecoder(cdc *wire.Codec) sdk.TxDecoder { return func(txBytes []byte) (sdk.Tx, sdk.Error) { var tx = auth.StdTx{} @@ -165,13 +172,19 @@ func (app *BaseApp) Router() Router { return app.router } // load latest application version func (app *BaseApp) LoadLatestVersion(mainKey sdk.StoreKey) error { - app.cms.LoadLatestVersion() + err := app.cms.LoadLatestVersion() + if err != nil { + return err + } return app.initFromStore(mainKey) } // load application version func (app *BaseApp) LoadVersion(version int64, mainKey sdk.StoreKey) error { - app.cms.LoadVersion(version) + err := app.cms.LoadVersion(version) + if err != nil { + return err + } return app.initFromStore(mainKey) } @@ -180,7 +193,7 @@ func (app *BaseApp) LastCommitID() sdk.CommitID { return app.cms.LastCommitID() } -// the last commited block height +// the last committed block height func (app *BaseApp) LastBlockHeight() int64 { return app.cms.LastCommitID().Version } @@ -192,51 +205,18 @@ func (app *BaseApp) initFromStore(mainKey sdk.StoreKey) error { // TODO: we don't actually need the main store here main := app.cms.GetKVStore(mainKey) if main == nil { - return errors.New("BaseApp expects MultiStore with 'main' KVStore") + return errors.New("baseapp expects MultiStore with 'main' KVStore") } - // XXX: Do we really need the header? What does it have that we want - // here that's not already in the CommitID ? If an app wants to have it, - // they can do so in their BeginBlocker. If we force it in baseapp, - // then either we force the AppHash to change with every block (since the header - // will be in the merkle store) or we can't write the state and the header to the - // db atomically without doing some surgery on the store interfaces ... - - // if we've committed before, we expect to exist in the db - /* - var lastCommitID = app.cms.LastCommitID() - var header abci.Header - - if !lastCommitID.IsZero() { - headerBytes := app.db.Get(dbHeaderKey) - if len(headerBytes) == 0 { - errStr := fmt.Sprintf("Version > 0 but missing key %s", dbHeaderKey) - return errors.New(errStr) - } - err := proto.Unmarshal(headerBytes, &header) - if err != nil { - return errors.Wrap(err, "Failed to parse Header") - } - lastVersion := lastCommitID.Version - if header.Height != lastVersion { - errStr := fmt.Sprintf("Expected db://%s.Height %v but got %v", dbHeaderKey, lastVersion, header.Height) - return errors.New(errStr) - } - } - */ - - // initialize Check state - app.setCheckState(abci.Header{}) - return nil } // NewContext returns a new Context with the correct store, the given header, and nil txBytes. func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context { if isCheckTx { - return sdk.NewContext(app.checkState.ms, header, true, nil, app.Logger) + return sdk.NewContext(app.checkState.ms, header, true, app.Logger) } - return sdk.NewContext(app.deliverState.ms, header, false, nil, app.Logger) + return sdk.NewContext(app.deliverState.ms, header, false, app.Logger) } type state struct { @@ -252,7 +232,7 @@ func (app *BaseApp) setCheckState(header abci.Header) { ms := app.cms.CacheMultiStore() app.checkState = &state{ ms: ms, - ctx: sdk.NewContext(ms, header, true, nil, app.Logger), + ctx: sdk.NewContext(ms, header, true, app.Logger), } } @@ -260,7 +240,7 @@ func (app *BaseApp) setDeliverState(header abci.Header) { ms := app.cms.CacheMultiStore() app.deliverState = &state{ ms: ms, - ctx: sdk.NewContext(ms, header, false, nil, app.Logger), + ctx: sdk.NewContext(ms, header, false, app.Logger), } } @@ -288,12 +268,13 @@ func (app *BaseApp) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOp // Implements ABCI // InitChain runs the initialization logic directly on the CommitMultiStore and commits it. func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) { + // Initialize the deliver state and check state with ChainID and run initChain + app.setDeliverState(abci.Header{ChainID: req.ChainId}) + app.setCheckState(abci.Header{ChainID: req.ChainId}) + if app.initChainer == nil { return } - - // Initialize the deliver state and run initChain - app.setDeliverState(abci.Header{}) app.initChainer(app.deliverState.ctx, req) // no error // NOTE: we don't commit, but BeginBlock for block 1 @@ -317,16 +298,39 @@ func (app *BaseApp) FilterPeerByPubKey(info string) abci.ResponseQuery { return abci.ResponseQuery{} } -// Implements ABCI. -// Delegates to CommitMultiStore if it implements Queryable -func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) { - path := strings.Split(req.Path, "/") +func splitPath(requestPath string) (path []string) { + path = strings.Split(requestPath, "/") // first element is empty string if len(path) > 0 && path[0] == "" { path = path[1:] } + return path +} + +// Implements ABCI. +// Delegates to CommitMultiStore if it implements Queryable +func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) { + path := splitPath(req.Path) + if len(path) == 0 { + msg := "no query path provided" + return sdk.ErrUnknownRequest(msg).QueryResult() + } + switch path[0] { // "/app" prefix for special application queries - if len(path) >= 2 && path[0] == "app" { + case "app": + return handleQueryApp(app, path, req) + case "store": + return handleQueryStore(app, path, req) + case "p2p": + return handleQueryP2P(app, path, req) + } + + msg := "unknown query path" + return sdk.ErrUnknownRequest(msg).QueryResult() +} + +func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) { + if len(path) >= 2 { var result sdk.Result switch path[1] { case "simulate": @@ -337,6 +341,11 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) { } else { result = app.Simulate(tx) } + case "version": + return abci.ResponseQuery{ + Code: uint32(sdk.ABCICodeOK), + Value: []byte(version.GetVersion()), + } default: result = sdk.ErrUnknownRequest(fmt.Sprintf("Unknown query: %s", path)).Result() } @@ -346,28 +355,40 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) { Value: value, } } + msg := "Expected second parameter to be either simulate or version, neither was present" + return sdk.ErrUnknownRequest(msg).QueryResult() +} + +func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) { // "/store" prefix for store queries - if len(path) >= 1 && path[0] == "store" { - queryable, ok := app.cms.(sdk.Queryable) - if !ok { - msg := "multistore doesn't support queries" - return sdk.ErrUnknownRequest(msg).QueryResult() - } - req.Path = "/" + strings.Join(path[1:], "/") - return queryable.Query(req) + queryable, ok := app.cms.(sdk.Queryable) + if !ok { + msg := "multistore doesn't support queries" + return sdk.ErrUnknownRequest(msg).QueryResult() } + req.Path = "/" + strings.Join(path[1:], "/") + return queryable.Query(req) +} + +func handleQueryP2P(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) { // "/p2p" prefix for p2p queries - if len(path) >= 4 && path[0] == "p2p" { + if len(path) >= 4 { if path[1] == "filter" { if path[2] == "addr" { return app.FilterPeerByAddrPort(path[3]) } if path[2] == "pubkey" { + // TODO: this should be changed to `id` + // NOTE: this changed in tendermint and we didn't notice... return app.FilterPeerByPubKey(path[3]) } + } else { + msg := "Expected second parameter to be filter" + return sdk.ErrUnknownRequest(msg).QueryResult() } } - msg := "unknown query path" + + msg := "Expected path is p2p filter " return sdk.ErrUnknownRequest(msg).QueryResult() } @@ -375,12 +396,16 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) { func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) { // Initialize the DeliverTx state. // If this is the first block, it should already - // be initialized in InitChain. It may also be nil - // if this is a test and InitChain was never called. + // be initialized in InitChain. + // Otherwise app.deliverState will be nil, since it + // is reset on Commit. if app.deliverState == nil { app.setDeliverState(req.Header) + } else { + // In the first block, app.deliverState.ctx will already be initialized + // by InitChain. Context is now updated with Header information. + app.deliverState.ctx = app.deliverState.ctx.WithBlockHeader(req.Header) } - app.valUpdates = nil if app.beginBlocker != nil { res = app.beginBlocker(app.deliverState.ctx, req) } @@ -425,13 +450,8 @@ func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) { result = app.runTx(runTxModeDeliver, txBytes, tx) } - // After-handler hooks. - if result.IsOK() { - app.valUpdates = append(app.valUpdates, result.ValidatorUpdates...) - } else { - // Even though the Result.Code is not OK, there are still effects, - // namely fee deductions and sequence incrementing. - } + // Even though the Result.Code is not OK, there are still effects, + // namely fee deductions and sequence incrementing. // Tell the blockchain engine (i.e. Tendermint). return abci.ResponseDeliverTx{ @@ -444,52 +464,27 @@ func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) { } } -// nolint - Mostly for testing -func (app *BaseApp) Check(tx sdk.Tx) (result sdk.Result) { - return app.runTx(runTxModeCheck, nil, tx) -} +// Basic validator for msgs +func validateBasicTxMsgs(msgs []sdk.Msg) sdk.Error { + if msgs == nil || len(msgs) == 0 { + // TODO: probably shouldn't be ErrInternal. Maybe new ErrInvalidMessage, or ? + return sdk.ErrInternal("Tx.GetMsgs() must return at least one message in list") + } -// nolint - full tx execution -func (app *BaseApp) Simulate(tx sdk.Tx) (result sdk.Result) { - return app.runTx(runTxModeSimulate, nil, tx) -} - -// nolint -func (app *BaseApp) Deliver(tx sdk.Tx) (result sdk.Result) { - return app.runTx(runTxModeDeliver, nil, tx) -} - -// txBytes may be nil in some cases, eg. in tests. -// Also, in the future we may support "internal" transactions. -func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk.Result) { - // Handle any panics. - defer func() { - if r := recover(); r != nil { - switch r.(type) { - case sdk.ErrorOutOfGas: - log := fmt.Sprintf("Out of gas in location: %v", r.(sdk.ErrorOutOfGas).Descriptor) - result = sdk.ErrOutOfGas(log).Result() - default: - log := fmt.Sprintf("Recovered: %v\nstack:\n%v", r, string(debug.Stack())) - result = sdk.ErrInternal(log).Result() - } + for _, msg := range msgs { + // Validate the Msg. + err := msg.ValidateBasic() + if err != nil { + err = err.WithDefaultCodespace(sdk.CodespaceRoot) + return err } - }() - - // Get the Msg. - var msg = tx.GetMsg() - if msg == nil { - return sdk.ErrInternal("Tx.GetMsg() returned nil").Result() } - // Validate the Msg. - err := msg.ValidateBasic() - if err != nil { - return err.Result() - } + return nil +} +func (app *BaseApp) getContextForAnte(mode runTxMode, txBytes []byte) (ctx sdk.Context) { // Get the context - var ctx sdk.Context if mode == runTxModeCheck || mode == runTxModeSimulate { ctx = app.checkState.ctx.WithTxBytes(txBytes) } else { @@ -502,55 +497,130 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk ctx = ctx.WithIsCheckTx(false) } + return +} + +// Iterates through msgs and executes them +func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg) (result sdk.Result) { + // accumulate results + logs := make([]string, 0, len(msgs)) + var data []byte // NOTE: we just append them all (?!) + var tags sdk.Tags // also just append them all + var code sdk.ABCICodeType + for msgIdx, msg := range msgs { + // Match route. + msgType := msg.Type() + handler := app.router.Route(msgType) + if handler == nil { + return sdk.ErrUnknownRequest("Unrecognized Msg type: " + msgType).Result() + } + + msgResult := handler(ctx, msg) + + // NOTE: GasWanted is determined by ante handler and + // GasUsed by the GasMeter + + // Append Data and Tags + data = append(data, msgResult.Data...) + tags = append(tags, msgResult.Tags...) + + // Stop execution and return on first failed message. + if !msgResult.IsOK() { + logs = append(logs, fmt.Sprintf("Msg %d failed: %s", msgIdx, msgResult.Log)) + code = msgResult.Code + break + } + + // Construct usable logs in multi-message transactions. + logs = append(logs, fmt.Sprintf("Msg %d: %s", msgIdx, msgResult.Log)) + } + + // Set the final gas values. + result = sdk.Result{ + Code: code, + Data: data, + Log: strings.Join(logs, "\n"), + GasUsed: ctx.GasMeter().GasConsumed(), + // TODO: FeeAmount/FeeDenom + Tags: tags, + } + + return result +} + +// Returns deliverState if app is in runTxModeDeliver, otherwhise returns checkstate +func getState(app *BaseApp, mode runTxMode) *state { + if mode == runTxModeCheck || mode == runTxModeSimulate { + return app.checkState + } + return app.deliverState +} + +// txBytes may be nil in some cases, eg. in tests. +// Also, in the future we may support "internal" transactions. +func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk.Result) { + //NOTE: GasWanted should be returned by the AnteHandler. + // GasUsed is determined by the GasMeter. + // We need access to the context to get the gas meter so + // we initialize upfront + var gasWanted int64 + ctx := app.getContextForAnte(mode, txBytes) + + // Handle any panics. + defer func() { + if r := recover(); r != nil { + switch rType := r.(type) { + case sdk.ErrorOutOfGas: + log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor) + result = sdk.ErrOutOfGas(log).Result() + default: + log := fmt.Sprintf("recovered: %v\nstack:\n%v", r, string(debug.Stack())) + result = sdk.ErrInternal(log).Result() + } + } + result.GasWanted = gasWanted + result.GasUsed = ctx.GasMeter().GasConsumed() + }() + + // Get the Msg. + var msgs = tx.GetMsgs() + + err := validateBasicTxMsgs(msgs) + if err != nil { + return err.Result() + } + // Run the ante handler. if app.anteHandler != nil { - newCtx, result, abort := app.anteHandler(ctx, tx) + newCtx, anteResult, abort := app.anteHandler(ctx, tx) if abort { - return result + return anteResult } if !newCtx.IsZero() { ctx = newCtx } + gasWanted = result.GasWanted } - // Match route. - msgType := msg.Type() - handler := app.router.Route(msgType) - if handler == nil { - return sdk.ErrUnknownRequest("Unrecognized Msg type: " + msgType).Result() - } + // CacheWrap the state in case it fails. + msCache := getState(app, mode).CacheMultiStore() + ctx = ctx.WithMultiStore(msCache) - // Get the correct cache - var msCache sdk.CacheMultiStore - if mode == runTxModeCheck || mode == runTxModeSimulate { - // CacheWrap app.checkState.ms in case it fails. - msCache = app.checkState.CacheMultiStore() - ctx = ctx.WithMultiStore(msCache) - } else { - // CacheWrap app.deliverState.ms in case it fails. - msCache = app.deliverState.CacheMultiStore() - ctx = ctx.WithMultiStore(msCache) - } + result = app.runMsgs(ctx, msgs) + result.GasWanted = gasWanted - result = handler(ctx, msg) - - // Set gas utilized - result.GasUsed = ctx.GasMeter().GasConsumed() - - // If not a simulated run and result was successful, write to app.checkState.ms or app.deliverState.ms - if mode != runTxModeSimulate && result.IsOK() { + // Only update state if all messages pass and we're not in a simulation. + if result.IsOK() && mode != runTxModeSimulate { msCache.Write() } - return result + return } // Implements ABCI func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) { if app.endBlocker != nil { res = app.endBlocker(app.deliverState.ctx, req) - } else { - res.ValidatorUpdates = app.valUpdates } return } @@ -570,6 +640,7 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) { // Write the Deliver state and commit the MultiStore app.deliverState.ms.Write() commitID := app.cms.Commit() + // TODO: this is missing a module identifier and dumps byte array app.Logger.Debug("Commit synced", "commit", commitID, ) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 532d39f1e..00897392e 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -1,7 +1,8 @@ package baseapp import ( - "encoding/json" + "bytes" + "encoding/binary" "fmt" "os" "testing" @@ -9,18 +10,17 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - tmtypes "github.com/tendermint/tendermint/types" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" - "github.com/cosmos/cosmos-sdk/x/auth" ) +//------------------------------------------------------------------------------------------ +// Helpers for setup. Most tests should be able to use setupBaseApp + func defaultLogger() log.Logger { return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") } @@ -29,33 +29,54 @@ func newBaseApp(name string) *BaseApp { logger := defaultLogger() db := dbm.NewMemDB() codec := wire.NewCodec() - wire.RegisterCrypto(codec) + registerTestCodec(codec) return NewBaseApp(name, codec, logger, db) } -func TestMountStores(t *testing.T) { - name := t.Name() - app := newBaseApp(name) - assert.Equal(t, name, app.Name()) +func registerTestCodec(cdc *wire.Codec) { + // register Tx, Msg + sdk.RegisterWire(cdc) + + // register test types + cdc.RegisterConcrete(&txTest{}, "cosmos-sdk/baseapp/txTest", nil) + cdc.RegisterConcrete(&msgCounter{}, "cosmos-sdk/baseapp/msgCounter", nil) + cdc.RegisterConcrete(&msgCounter2{}, "cosmos-sdk/baseapp/msgCounter2", nil) + cdc.RegisterConcrete(&msgNoRoute{}, "cosmos-sdk/baseapp/msgNoRoute", nil) +} + +// simple one store baseapp +func setupBaseApp(t *testing.T) (*BaseApp, *sdk.KVStoreKey, *sdk.KVStoreKey) { + app := newBaseApp(t.Name()) + require.Equal(t, t.Name(), app.Name()) + + app.SetTxDecoder(testTxDecoder(app.cdc)) // make some cap keys capKey1 := sdk.NewKVStoreKey("key1") capKey2 := sdk.NewKVStoreKey("key2") // no stores are mounted - assert.Panics(t, func() { app.LoadLatestVersion(capKey1) }) + require.Panics(t, func() { app.LoadLatestVersion(capKey1) }) app.MountStoresIAVL(capKey1, capKey2) // stores are mounted err := app.LoadLatestVersion(capKey1) - assert.Nil(t, err) + require.Nil(t, err) + return app, capKey1, capKey2 +} + +//------------------------------------------------------------------------------------------ +// test mounting and loading stores + +func TestMountStores(t *testing.T) { + app, capKey1, capKey2 := setupBaseApp(t) // check both stores store1 := app.cms.GetCommitKVStore(capKey1) - assert.NotNil(t, store1) + require.NotNil(t, store1) store2 := app.cms.GetCommitKVStore(capKey2) - assert.NotNil(t, store2) + require.NotNil(t, store2) } // Test that we can make commits and then reload old versions. @@ -70,20 +91,23 @@ func TestLoadVersion(t *testing.T) { capKey := sdk.NewKVStoreKey("main") app.MountStoresIAVL(capKey) err := app.LoadLatestVersion(capKey) // needed to make stores non-nil - assert.Nil(t, err) + require.Nil(t, err) emptyCommitID := sdk.CommitID{} + // fresh store has zero/empty last commit lastHeight := app.LastBlockHeight() lastID := app.LastCommitID() - assert.Equal(t, int64(0), lastHeight) - assert.Equal(t, emptyCommitID, lastID) + require.Equal(t, int64(0), lastHeight) + require.Equal(t, emptyCommitID, lastID) - // execute some blocks + // execute a block, collect commit ID header := abci.Header{Height: 1} app.BeginBlock(abci.RequestBeginBlock{Header: header}) res := app.Commit() commitID1 := sdk.CommitID{1, res.Data} + + // execute a block, collect commit ID header = abci.Header{Height: 2} app.BeginBlock(abci.RequestBeginBlock{Header: header}) res = app.Commit() @@ -93,7 +117,7 @@ func TestLoadVersion(t *testing.T) { app = NewBaseApp(name, nil, logger, db) app.MountStoresIAVL(capKey) err = app.LoadLatestVersion(capKey) - assert.Nil(t, err) + require.Nil(t, err) testLoadVersionHelper(t, app, int64(2), commitID2) // reload with LoadVersion, see if you can commit the same block and get @@ -101,7 +125,7 @@ func TestLoadVersion(t *testing.T) { app = NewBaseApp(name, nil, logger, db) app.MountStoresIAVL(capKey) err = app.LoadVersion(1, capKey) - assert.Nil(t, err) + require.Nil(t, err) testLoadVersionHelper(t, app, int64(1), commitID1) app.BeginBlock(abci.RequestBeginBlock{Header: header}) app.Commit() @@ -111,8 +135,23 @@ func TestLoadVersion(t *testing.T) { func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, expectedID sdk.CommitID) { lastHeight := app.LastBlockHeight() lastID := app.LastCommitID() - assert.Equal(t, expectedHeight, lastHeight) - assert.Equal(t, expectedID, lastID) + require.Equal(t, expectedHeight, lastHeight) + require.Equal(t, expectedID, lastID) +} + +func TestOptionFunction(t *testing.T) { + logger := defaultLogger() + db := dbm.NewMemDB() + codec := wire.NewCodec() + registerTestCodec(codec) + bap := NewBaseApp("starting name", codec, logger, db, testChangeNameHelper("new name")) + require.Equal(t, bap.name, "new name", "BaseApp should have had name changed via option function") +} + +func testChangeNameHelper(name string) func(*BaseApp) { + return func(bap *BaseApp) { + bap.name = name + } } // Test that the app hash is static @@ -124,7 +163,7 @@ func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, exp capKey := sdk.NewKVStoreKey("main") app.MountStoresIAVL(capKey) err := app.LoadLatestVersion(capKey) // needed to make stores non-nil - assert.Nil(t, err) + require.Nil(t, err) // execute some blocks header := abci.Header{Height: 1} @@ -137,10 +176,13 @@ func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, exp res = app.Commit() commitID2 := sdk.CommitID{2, res.Data} - assert.Equal(t, commitID1.Hash, commitID2.Hash) + require.Equal(t, commitID1.Hash, commitID2.Hash) } */ +//------------------------------------------------------------------------------------------ +// test some basic abci/baseapp functionality + // Test that txs can be unmarshalled and read and that // correct error codes are returned when not func TestTxDecoder(t *testing.T) { @@ -159,30 +201,30 @@ func TestInfo(t *testing.T) { assert.Equal(t, "", res.Version) assert.Equal(t, t.Name(), res.GetData()) assert.Equal(t, int64(0), res.LastBlockHeight) - assert.Equal(t, []uint8(nil), res.LastBlockAppHash) + require.Equal(t, []uint8(nil), res.LastBlockAppHash) // ----- test a proper response ------- // TODO - } +//------------------------------------------------------------------------------------------ +// InitChain, BeginBlock, EndBlock + func TestInitChainer(t *testing.T) { name := t.Name() + // keep the db and logger ourselves so + // we can reload the same app later db := dbm.NewMemDB() logger := defaultLogger() app := NewBaseApp(name, nil, logger, db) - // make cap keys and mount the stores - // NOTE/TODO: mounting multiple stores is broken - // see https://github.com/cosmos/cosmos-sdk/issues/532 capKey := sdk.NewKVStoreKey("main") capKey2 := sdk.NewKVStoreKey("key2") app.MountStoresIAVL(capKey, capKey2) err := app.LoadLatestVersion(capKey) // needed to make stores non-nil - assert.Nil(t, err) + require.Nil(t, err) + // set a value in the store on init chain key, value := []byte("hello"), []byte("goodbye") - - // initChainer sets a value in the store var initChainer sdk.InitChainer = func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { store := ctx.KVStore(capKey) store.Set(key, value) @@ -197,319 +239,603 @@ func TestInitChainer(t *testing.T) { // initChainer is nil - nothing happens app.InitChain(abci.RequestInitChain{}) res := app.Query(query) - assert.Equal(t, 0, len(res.Value)) + require.Equal(t, 0, len(res.Value)) // set initChainer and try again - should see the value app.SetInitChainer(initChainer) - app.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}")}) // must have valid JSON genesis file, even if empty + app.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}"), ChainId: "test-chain-id"}) // must have valid JSON genesis file, even if empty + + // assert that chainID is set correctly in InitChain + chainID := app.deliverState.ctx.ChainID() + require.Equal(t, "test-chain-id", chainID, "ChainID in deliverState not set correctly in InitChain") + + chainID = app.checkState.ctx.ChainID() + require.Equal(t, "test-chain-id", chainID, "ChainID in checkState not set correctly in InitChain") + app.Commit() res = app.Query(query) - assert.Equal(t, value, res.Value) + require.Equal(t, value, res.Value) // reload app app = NewBaseApp(name, nil, logger, db) app.MountStoresIAVL(capKey, capKey2) err = app.LoadLatestVersion(capKey) // needed to make stores non-nil - assert.Nil(t, err) + require.Nil(t, err) app.SetInitChainer(initChainer) // ensure we can still query after reloading res = app.Query(query) - assert.Equal(t, value, res.Value) + require.Equal(t, value, res.Value) // commit and ensure we can still query app.BeginBlock(abci.RequestBeginBlock{}) app.Commit() res = app.Query(query) - assert.Equal(t, value, res.Value) + require.Equal(t, value, res.Value) } -func getStateCheckingHandler(t *testing.T, capKey *sdk.KVStoreKey, txPerHeight int, checkHeader bool) func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - counter := 0 - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - store := ctx.KVStore(capKey) - // Checking state gets updated between checkTx's / DeliverTx's - // on the store within a block. - if counter > 0 { - // check previous value in store - counterBytes := []byte{byte(counter - 1)} - prevBytes := store.Get(counterBytes) - assert.Equal(t, counterBytes, prevBytes) - } +//------------------------------------------------------------------------------------------ +// Mock tx, msgs, and mapper for the baseapp tests. +// Self-contained, just uses counters. +// We don't care about signatures, coins, accounts, etc. in the baseapp. - // set the current counter in the store - counterBytes := []byte{byte(counter)} - store.Set(counterBytes, counterBytes) - - // check that we can see the current header - // wrapped in an if, so it can be reused between CheckTx and DeliverTx tests. - if checkHeader { - thisHeader := ctx.BlockHeader() - height := int64((counter / txPerHeight) + 1) - assert.Equal(t, height, thisHeader.Height) - } - - counter++ - return sdk.Result{} - } +// Simple tx with a list of Msgs. +type txTest struct { + Msgs []sdk.Msg + Counter int64 } -// A mock transaction that has a validation which can fail. -type testTx struct { - positiveNum int64 +// Implements Tx +func (tx txTest) GetMsgs() []sdk.Msg { return tx.Msgs } + +const ( + typeMsgCounter = "msgCounter" + typeMsgCounter2 = "msgCounterTwo" // NOTE: no numerics (?) +) + +// ValidateBasic() fails on negative counters. +// Otherwise it's up to the handlers +type msgCounter struct { + Counter int64 } -const msgType2 = "testTx" - -func (tx testTx) Type() string { return msgType2 } -func (tx testTx) GetMsg() sdk.Msg { return tx } -func (tx testTx) GetSignBytes() []byte { return nil } -func (tx testTx) GetSigners() []sdk.Address { return nil } -func (tx testTx) GetSignatures() []auth.StdSignature { return nil } -func (tx testTx) ValidateBasic() sdk.Error { - if tx.positiveNum >= 0 { +// Implements Msg +func (msg msgCounter) Type() string { return typeMsgCounter } +func (msg msgCounter) GetSignBytes() []byte { return nil } +func (msg msgCounter) GetSigners() []sdk.AccAddress { return nil } +func (msg msgCounter) ValidateBasic() sdk.Error { + if msg.Counter >= 0 { return nil } - return sdk.ErrTxDecode("positiveNum should be a non-negative integer.") + return sdk.ErrInvalidSequence("counter should be a non-negative integer.") } +func newTxCounter(txInt int64, msgInts ...int64) *txTest { + var msgs []sdk.Msg + for _, msgInt := range msgInts { + msgs = append(msgs, msgCounter{msgInt}) + } + return &txTest{msgs, txInt} +} + +// a msg we dont know how to route +type msgNoRoute struct { + msgCounter +} + +func (tx msgNoRoute) Type() string { return "noroute" } + +// a msg we dont know how to decode +type msgNoDecode struct { + msgCounter +} + +func (tx msgNoDecode) Type() string { return typeMsgCounter } + +// Another counter msg. Duplicate of msgCounter +type msgCounter2 struct { + Counter int64 +} + +// Implements Msg +func (msg msgCounter2) Type() string { return typeMsgCounter2 } +func (msg msgCounter2) GetSignBytes() []byte { return nil } +func (msg msgCounter2) GetSigners() []sdk.AccAddress { return nil } +func (msg msgCounter2) ValidateBasic() sdk.Error { + if msg.Counter >= 0 { + return nil + } + return sdk.ErrInvalidSequence("counter should be a non-negative integer.") +} + +// amino decode +func testTxDecoder(cdc *wire.Codec) sdk.TxDecoder { + return func(txBytes []byte) (sdk.Tx, sdk.Error) { + var tx txTest + if len(txBytes) == 0 { + return nil, sdk.ErrTxDecode("txBytes are empty") + } + err := cdc.UnmarshalBinary(txBytes, &tx) + if err != nil { + return nil, sdk.ErrTxDecode("").TraceSDK(err.Error()) + } + return tx, nil + } +} + +func anteHandlerTxTest(t *testing.T, capKey *sdk.KVStoreKey, storeKey []byte) sdk.AnteHandler { + return func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { + store := ctx.KVStore(capKey) + msgCounter := tx.(txTest).Counter + res = incrementingCounter(t, store, storeKey, msgCounter) + return + } +} + +func handlerMsgCounter(t *testing.T, capKey *sdk.KVStoreKey, deliverKey []byte) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + store := ctx.KVStore(capKey) + var msgCount int64 + switch m := msg.(type) { + case *msgCounter: + msgCount = m.Counter + case *msgCounter2: + msgCount = m.Counter + } + return incrementingCounter(t, store, deliverKey, msgCount) + } +} + +//----------------------------------------------------------------- +// simple int mapper + +func i2b(i int64) []byte { + return []byte{byte(i)} +} + +func getIntFromStore(store sdk.KVStore, key []byte) int64 { + bz := store.Get(key) + if len(bz) == 0 { + return 0 + } + i, err := binary.ReadVarint(bytes.NewBuffer(bz)) + if err != nil { + panic(err) + } + return i +} + +func setIntOnStore(store sdk.KVStore, key []byte, i int64) { + bz := make([]byte, 8) + n := binary.PutVarint(bz, i) + store.Set(key, bz[:n]) +} + +// check counter matches what's in store. +// increment and store +func incrementingCounter(t *testing.T, store sdk.KVStore, counterKey []byte, counter int64) (res sdk.Result) { + storedCounter := getIntFromStore(store, counterKey) + require.Equal(t, storedCounter, counter) + setIntOnStore(store, counterKey, counter+1) + return +} + +//--------------------------------------------------------------------- +// Tx processing - CheckTx, DeliverTx, SimulateTx. +// These tests use the serialized tx as input, while most others will use the +// Check(), Deliver(), Simulate() methods directly. +// Ensure that Check/Deliver/Simulate work as expected with the store. + // Test that successive CheckTx can see each others' effects // on the store within a block, and that the CheckTx state -// gets reset to the latest Committed state during Commit +// gets reset to the latest committed state during Commit func TestCheckTx(t *testing.T) { - // Initialize an app for testing - app := newBaseApp(t.Name()) - // make a cap key and mount the store - capKey := sdk.NewKVStoreKey("main") - app.MountStoresIAVL(capKey) - err := app.LoadLatestVersion(capKey) // needed to make stores non-nil - assert.Nil(t, err) - app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return }) + app, capKey, _ := setupBaseApp(t) - txPerHeight := 3 - app.Router().AddRoute(msgType, getStateCheckingHandler(t, capKey, txPerHeight, false)). - AddRoute(msgType2, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) - tx := testUpdatePowerTx{} // doesn't matter - for i := 0; i < txPerHeight; i++ { - app.Check(tx) + // This ante handler reads the key and checks that the value matches the current counter. + // This ensures changes to the kvstore persist across successive CheckTx. + counterKey := []byte("counter-key") + app.SetAnteHandler(anteHandlerTxTest(t, capKey, counterKey)) + + nTxs := int64(5) + + // TODO: can remove this once CheckTx doesnt process msgs. + app.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { return sdk.Result{} }) + + app.InitChain(abci.RequestInitChain{}) + + for i := int64(0); i < nTxs; i++ { + tx := newTxCounter(i, 0) + txBytes, err := app.cdc.MarshalBinary(tx) + require.NoError(t, err) + r := app.CheckTx(txBytes) + assert.True(t, r.IsOK(), fmt.Sprintf("%v", r)) } - // If it gets to this point, then successive CheckTx's can see the effects of - // other CheckTx's on the block. The following checks that if another block - // is committed, the CheckTx State will reset. + + checkStateStore := app.checkState.ctx.KVStore(capKey) + storedCounter := getIntFromStore(checkStateStore, counterKey) + + // Ensure AnteHandler ran + require.Equal(t, nTxs, storedCounter) + + // If a block is committed, CheckTx state should be reset. app.BeginBlock(abci.RequestBeginBlock{}) - tx2 := testTx{} - for i := 0; i < txPerHeight; i++ { - app.Deliver(tx2) - } app.EndBlock(abci.RequestEndBlock{}) app.Commit() - checkStateStore := app.checkState.ctx.KVStore(capKey) - for i := 0; i < txPerHeight; i++ { - storedValue := checkStateStore.Get([]byte{byte(i)}) - assert.Nil(t, storedValue) - } + checkStateStore = app.checkState.ctx.KVStore(capKey) + storedBytes := checkStateStore.Get(counterKey) + require.Nil(t, storedBytes) } // Test that successive DeliverTx can see each others' effects // on the store, both within and across blocks. func TestDeliverTx(t *testing.T) { - app := newBaseApp(t.Name()) + app, capKey, _ := setupBaseApp(t) - // make a cap key and mount the store - capKey := sdk.NewKVStoreKey("main") - app.MountStoresIAVL(capKey) - err := app.LoadLatestVersion(capKey) // needed to make stores non-nil - assert.Nil(t, err) + // test increments in the ante + anteKey := []byte("ante-key") + app.SetAnteHandler(anteHandlerTxTest(t, capKey, anteKey)) - txPerHeight := 2 - app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return }) - app.Router().AddRoute(msgType, getStateCheckingHandler(t, capKey, txPerHeight, true)) - - tx := testUpdatePowerTx{} // doesn't matter - header := abci.Header{AppHash: []byte("apphash")} + // test increments in the handler + deliverKey := []byte("deliver-key") + app.Router().AddRoute(typeMsgCounter, handlerMsgCounter(t, capKey, deliverKey)) nBlocks := 3 + txPerHeight := 5 for blockN := 0; blockN < nBlocks; blockN++ { - // block1 - header.Height = int64(blockN + 1) - app.BeginBlock(abci.RequestBeginBlock{Header: header}) + app.BeginBlock(abci.RequestBeginBlock{}) for i := 0; i < txPerHeight; i++ { - app.Deliver(tx) + counter := int64(blockN*txPerHeight + i) + tx := newTxCounter(counter, counter) + txBytes, err := app.cdc.MarshalBinary(tx) + require.NoError(t, err) + res := app.DeliverTx(txBytes) + require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) } app.EndBlock(abci.RequestEndBlock{}) app.Commit() } } +// Number of messages doesn't matter to CheckTx. +func TestMultiMsgCheckTx(t *testing.T) { + // TODO: ensure we get the same results + // with one message or many +} + +// One call to DeliverTx should process all the messages, in order. +func TestMultiMsgDeliverTx(t *testing.T) { + app, capKey, _ := setupBaseApp(t) + + // increment the tx counter + anteKey := []byte("ante-key") + app.SetAnteHandler(anteHandlerTxTest(t, capKey, anteKey)) + + // increment the msg counter + deliverKey := []byte("deliver-key") + deliverKey2 := []byte("deliver-key2") + app.Router().AddRoute(typeMsgCounter, handlerMsgCounter(t, capKey, deliverKey)) + app.Router().AddRoute(typeMsgCounter2, handlerMsgCounter(t, capKey, deliverKey2)) + + // run a multi-msg tx + // with all msgs the same type + { + app.BeginBlock(abci.RequestBeginBlock{}) + tx := newTxCounter(0, 0, 1, 2) + txBytes, err := app.cdc.MarshalBinary(tx) + require.NoError(t, err) + res := app.DeliverTx(txBytes) + require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) + + store := app.deliverState.ctx.KVStore(capKey) + + // tx counter only incremented once + txCounter := getIntFromStore(store, anteKey) + require.Equal(t, int64(1), txCounter) + + // msg counter incremented three times + msgCounter := getIntFromStore(store, deliverKey) + require.Equal(t, int64(3), msgCounter) + } + + // replace the second message with a msgCounter2 + { + tx := newTxCounter(1, 3) + tx.Msgs = append(tx.Msgs, msgCounter2{0}) + tx.Msgs = append(tx.Msgs, msgCounter2{1}) + txBytes, err := app.cdc.MarshalBinary(tx) + require.NoError(t, err) + res := app.DeliverTx(txBytes) + require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) + + store := app.deliverState.ctx.KVStore(capKey) + + // tx counter only incremented once + txCounter := getIntFromStore(store, anteKey) + require.Equal(t, int64(2), txCounter) + + // original counter increments by one + // new counter increments by two + msgCounter := getIntFromStore(store, deliverKey) + require.Equal(t, int64(4), msgCounter) + msgCounter2 := getIntFromStore(store, deliverKey2) + require.Equal(t, int64(2), msgCounter2) + } +} + +// Interleave calls to Check and Deliver and ensure +// that there is no cross-talk. Check sees results of the previous Check calls +// and Deliver sees that of the previous Deliver calls, but they don't see eachother. +func TestConcurrentCheckDeliver(t *testing.T) { + // TODO +} + +// Simulate a transaction that uses gas to compute the gas. +// Simulate() and Query("/app/simulate", txBytes) should give +// the same results. func TestSimulateTx(t *testing.T) { - app := newBaseApp(t.Name()) + app, _, _ := setupBaseApp(t) - // make a cap key and mount the store - capKey := sdk.NewKVStoreKey("main") - app.MountStoresIAVL(capKey) - err := app.LoadLatestVersion(capKey) // needed to make stores non-nil - assert.Nil(t, err) - - counter := 0 - app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return }) - app.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - ctx.GasMeter().ConsumeGas(10, "test") - store := ctx.KVStore(capKey) - // ensure store is never written - require.Nil(t, store.Get([]byte("key"))) - store.Set([]byte("key"), []byte("value")) - // check we can see the current header - thisHeader := ctx.BlockHeader() - height := int64(counter) - assert.Equal(t, height, thisHeader.Height) - counter++ - return sdk.Result{} + gasConsumed := int64(5) + app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { + newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasConsumed)) + return }) - tx := testUpdatePowerTx{} // doesn't matter - header := abci.Header{AppHash: []byte("apphash")} - - app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) { - var ttx testUpdatePowerTx - fromJSON(txBytes, &ttx) - return ttx, nil + app.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + ctx.GasMeter().ConsumeGas(gasConsumed, "test") + return sdk.Result{GasUsed: ctx.GasMeter().GasConsumed()} }) + app.InitChain(abci.RequestInitChain{}) nBlocks := 3 for blockN := 0; blockN < nBlocks; blockN++ { - // block1 - header.Height = int64(blockN + 1) - app.BeginBlock(abci.RequestBeginBlock{Header: header}) + count := int64(blockN + 1) + app.BeginBlock(abci.RequestBeginBlock{}) + + tx := newTxCounter(count, count) + + // simulate a message, check gas reported result := app.Simulate(tx) - require.Equal(t, result.Code, sdk.ABCICodeOK) - require.Equal(t, int64(80), result.GasUsed) - counter-- - encoded, err := json.Marshal(tx) + require.True(t, result.IsOK(), result.Log) + require.Equal(t, int64(gasConsumed), result.GasUsed) + + // simulate again, same result + result = app.Simulate(tx) + require.True(t, result.IsOK(), result.Log) + require.Equal(t, int64(gasConsumed), result.GasUsed) + + // simulate by calling Query with encoded tx + txBytes, err := app.cdc.MarshalBinary(tx) require.Nil(t, err) query := abci.RequestQuery{ Path: "/app/simulate", - Data: encoded, + Data: txBytes, } queryResult := app.Query(query) - require.Equal(t, queryResult.Code, uint32(sdk.ABCICodeOK)) + require.True(t, queryResult.IsOK(), queryResult.Log) + var res sdk.Result app.cdc.MustUnmarshalBinary(queryResult.Value, &res) - require.Equal(t, sdk.ABCICodeOK, res.Code) - require.Equal(t, int64(160), res.GasUsed) + require.True(t, res.IsOK(), res.Log) + require.Equal(t, gasConsumed, res.GasUsed, res.Log) app.EndBlock(abci.RequestEndBlock{}) app.Commit() } } +//------------------------------------------------------------------------------------------- +// Tx failure cases +// TODO: add more + func TestRunInvalidTransaction(t *testing.T) { - // Initialize an app for testing - app := newBaseApp(t.Name()) - // make a cap key and mount the store - capKey := sdk.NewKVStoreKey("main") - app.MountStoresIAVL(capKey) - err := app.LoadLatestVersion(capKey) // needed to make stores non-nil - assert.Nil(t, err) + app, _, _ := setupBaseApp(t) app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return }) - app.Router().AddRoute(msgType2, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) + app.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) + app.BeginBlock(abci.RequestBeginBlock{}) - // Transaction where validate fails - invalidTx := testTx{-1} - err1 := app.Deliver(invalidTx) - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeTxDecode), err1.Code) + + // Transaction with no messages + { + emptyTx := &txTest{} + err := app.Deliver(emptyTx) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeInternal), err.Code) + } + + // Transaction where ValidateBasic fails + { + testCases := []struct { + tx *txTest + fail bool + }{ + {newTxCounter(0, 0), false}, + {newTxCounter(-1, 0), false}, + {newTxCounter(100, 100), false}, + {newTxCounter(100, 5, 4, 3, 2, 1), false}, + + {newTxCounter(0, -1), true}, + {newTxCounter(0, 1, -2), true}, + {newTxCounter(0, 1, 2, -10, 5), true}, + } + + for _, testCase := range testCases { + tx := testCase.tx + res := app.Deliver(tx) + if testCase.fail { + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeInvalidSequence), res.Code) + } else { + require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) + } + } + } + // Transaction with no known route - unknownRouteTx := testUpdatePowerTx{} - err2 := app.Deliver(unknownRouteTx) - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), err2.Code) + { + unknownRouteTx := txTest{[]sdk.Msg{msgNoRoute{}}, 0} + err := app.Deliver(unknownRouteTx) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), err.Code) + + unknownRouteTx = txTest{[]sdk.Msg{msgCounter{}, msgNoRoute{}}, 0} + err = app.Deliver(unknownRouteTx) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), err.Code) + } + + // Transaction with an unregistered message + { + tx := newTxCounter(0, 0) + tx.Msgs = append(tx.Msgs, msgNoDecode{}) + + // new codec so we can encode the tx, but we shouldn't be able to decode + newCdc := wire.NewCodec() + registerTestCodec(newCdc) + newCdc.RegisterConcrete(&msgNoDecode{}, "cosmos-sdk/baseapp/msgNoDecode", nil) + + txBytes, err := newCdc.MarshalBinary(tx) + require.NoError(t, err) + res := app.DeliverTx(txBytes) + require.EqualValues(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeTxDecode), res.Code) + } } // Test that transactions exceeding gas limits fail func TestTxGasLimits(t *testing.T) { - logger := defaultLogger() - db := dbm.NewMemDB() - app := NewBaseApp(t.Name(), nil, logger, db) - - // make a cap key and mount the store - capKey := sdk.NewKVStoreKey("main") - app.MountStoresIAVL(capKey) - err := app.LoadLatestVersion(capKey) // needed to make stores non-nil - assert.Nil(t, err) + app, _, _ := setupBaseApp(t) + gasGranted := int64(10) app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { - newCtx = ctx.WithGasMeter(sdk.NewGasMeter(0)) + newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted)) + + // NOTE/TODO/XXX: + // AnteHandlers must have their own defer/recover in order + // for the BaseApp to know how much gas was used used! + // This is because the GasMeter is created in the AnteHandler, + // but if it panics the context won't be set properly in runTx's recover ... + defer func() { + if r := recover(); r != nil { + switch rType := r.(type) { + case sdk.ErrorOutOfGas: + log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor) + res = sdk.ErrOutOfGas(log).Result() + res.GasWanted = gasGranted + res.GasUsed = newCtx.GasMeter().GasConsumed() + default: + panic(r) + } + } + }() + + count := tx.(*txTest).Counter + newCtx.GasMeter().ConsumeGas(count, "counter-ante") + res = sdk.Result{ + GasWanted: gasGranted, + } return }) - app.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - ctx.GasMeter().ConsumeGas(10, "counter") + app.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + count := msg.(msgCounter).Counter + ctx.GasMeter().ConsumeGas(count, "counter-handler") return sdk.Result{} }) - tx := testUpdatePowerTx{} // doesn't matter - header := abci.Header{AppHash: []byte("apphash")} + app.BeginBlock(abci.RequestBeginBlock{}) - app.BeginBlock(abci.RequestBeginBlock{Header: header}) - res := app.Deliver(tx) - assert.Equal(t, res.Code, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOutOfGas), "Expected transaction to run out of gas") - app.EndBlock(abci.RequestEndBlock{}) - app.Commit() + testCases := []struct { + tx *txTest + gasUsed int64 + fail bool + }{ + {newTxCounter(0, 0), 0, false}, + {newTxCounter(1, 1), 2, false}, + {newTxCounter(9, 1), 10, false}, + {newTxCounter(1, 9), 10, false}, + {newTxCounter(10, 0), 10, false}, + {newTxCounter(0, 10), 10, false}, + {newTxCounter(0, 8, 2), 10, false}, + {newTxCounter(0, 5, 1, 1, 1, 1, 1), 10, false}, + {newTxCounter(0, 5, 1, 1, 1, 1), 9, false}, + + {newTxCounter(9, 2), 11, true}, + {newTxCounter(2, 9), 11, true}, + {newTxCounter(9, 1, 1), 11, true}, + {newTxCounter(1, 8, 1, 1), 11, true}, + {newTxCounter(11, 0), 11, true}, + {newTxCounter(0, 11), 11, true}, + {newTxCounter(0, 5, 11), 16, true}, + } + + for i, tc := range testCases { + tx := tc.tx + res := app.Deliver(tx) + + // check gas used and wanted + require.Equal(t, tc.gasUsed, res.GasUsed, fmt.Sprintf("%d: %v, %v", i, tc, res)) + + // check for out of gas + if !tc.fail { + require.True(t, res.IsOK(), fmt.Sprintf("%d: %v, %v", i, tc, res)) + } else { + require.Equal(t, res.Code, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOutOfGas), fmt.Sprintf("%d: %v, %v", i, tc, res)) + } + } } +//------------------------------------------------------------------------------------------- +// Queries + // Test that we can only query from the latest committed state. func TestQuery(t *testing.T) { - app := newBaseApp(t.Name()) - - // make a cap key and mount the store - capKey := sdk.NewKVStoreKey("main") - app.MountStoresIAVL(capKey) - err := app.LoadLatestVersion(capKey) // needed to make stores non-nil - assert.Nil(t, err) + app, capKey, _ := setupBaseApp(t) key, value := []byte("hello"), []byte("goodbye") + app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { + store := ctx.KVStore(capKey) + store.Set(key, value) + return + }) - app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return }) - app.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + app.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { store := ctx.KVStore(capKey) store.Set(key, value) return sdk.Result{} }) + app.InitChain(abci.RequestInitChain{}) + // NOTE: "/store/key1" tells us KVStore + // and the final "/key" says to use the data as the + // key in the given KVStore ... query := abci.RequestQuery{ - Path: "/store/main/key", + Path: "/store/key1/key", Data: key, } + tx := newTxCounter(0, 0) // query is empty before we do anything res := app.Query(query) - assert.Equal(t, 0, len(res.Value)) - - tx := testUpdatePowerTx{} // doesn't matter + require.Equal(t, 0, len(res.Value)) // query is still empty after a CheckTx - app.Check(tx) + resTx := app.Check(tx) + require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx)) res = app.Query(query) - assert.Equal(t, 0, len(res.Value)) + require.Equal(t, 0, len(res.Value)) // query is still empty after a DeliverTx before we commit app.BeginBlock(abci.RequestBeginBlock{}) - app.Deliver(tx) + resTx = app.Deliver(tx) + require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx)) res = app.Query(query) - assert.Equal(t, 0, len(res.Value)) + require.Equal(t, 0, len(res.Value)) // query returns correct value after Commit app.Commit() res = app.Query(query) - assert.Equal(t, value, res.Value) + require.Equal(t, value, res.Value) } // Test p2p filter queries func TestP2PQuery(t *testing.T) { - app := newBaseApp(t.Name()) - - // make a cap key and mount the store - capKey := sdk.NewKVStoreKey("main") - app.MountStoresIAVL(capKey) - err := app.LoadLatestVersion(capKey) // needed to make stores non-nil - assert.Nil(t, err) + app, _, _ := setupBaseApp(t) app.SetAddrPeerFilter(func(addrport string) abci.ResponseQuery { require.Equal(t, "1.1.1.1:8000", addrport) @@ -533,154 +859,3 @@ func TestP2PQuery(t *testing.T) { res = app.Query(pubkeyQuery) require.Equal(t, uint32(4), res.Code) } - -//---------------------- -// TODO: clean this up - -// A mock transaction to update a validator's voting power. -type testUpdatePowerTx struct { - Addr []byte - NewPower int64 -} - -const msgType = "testUpdatePowerTx" - -func (tx testUpdatePowerTx) Type() string { return msgType } -func (tx testUpdatePowerTx) GetMsg() sdk.Msg { return tx } -func (tx testUpdatePowerTx) GetSignBytes() []byte { return nil } -func (tx testUpdatePowerTx) ValidateBasic() sdk.Error { return nil } -func (tx testUpdatePowerTx) GetSigners() []sdk.Address { return nil } -func (tx testUpdatePowerTx) GetSignatures() []auth.StdSignature { return nil } - -func TestValidatorChange(t *testing.T) { - - // Create app. - app := newBaseApp(t.Name()) - capKey := sdk.NewKVStoreKey("key") - app.MountStoresIAVL(capKey) - app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) { - var ttx testUpdatePowerTx - fromJSON(txBytes, &ttx) - return ttx, nil - }) - - app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return }) - app.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - // TODO - return sdk.Result{} - }) - - // Load latest state, which should be empty. - err := app.LoadLatestVersion(capKey) - assert.Nil(t, err) - assert.Equal(t, app.LastBlockHeight(), int64(0)) - - // Create the validators - var numVals = 3 - var valSet = make([]abci.Validator, numVals) - for i := 0; i < numVals; i++ { - valSet[i] = makeVal(secret(i)) - } - - // Initialize the chain - app.InitChain(abci.RequestInitChain{ - Validators: valSet, - }) - - // Simulate the start of a block. - app.BeginBlock(abci.RequestBeginBlock{}) - - // Add 1 to each validator's voting power. - for i, val := range valSet { - tx := testUpdatePowerTx{ - Addr: makePubKey(secret(i)).Address(), - NewPower: val.Power + 1, - } - txBytes := toJSON(tx) - res := app.DeliverTx(txBytes) - assert.True(t, res.IsOK(), "%#v\nABCI log: %s", res, res.Log) - } - - // Simulate the end of a block. - // Get the summary of validator updates. - res := app.EndBlock(abci.RequestEndBlock{}) - valUpdates := res.ValidatorUpdates - - // Assert that validator updates are correct. - for _, val := range valSet { - - pubkey, err := tmtypes.PB2TM.PubKey(val.PubKey) - // Sanity - assert.Nil(t, err) - - // Find matching update and splice it out. - for j := 0; j < len(valUpdates); j++ { - valUpdate := valUpdates[j] - - updatePubkey, err := tmtypes.PB2TM.PubKey(valUpdate.PubKey) - assert.Nil(t, err) - - // Matched. - if updatePubkey.Equals(pubkey) { - assert.Equal(t, valUpdate.Power, val.Power+1) - if j < len(valUpdates)-1 { - // Splice it out. - valUpdates = append(valUpdates[:j], valUpdates[j+1:]...) - } - break - } - - // Not matched. - } - } - assert.Equal(t, len(valUpdates), 0, "Some validator updates were unexpected") -} - -//---------------------------------------- - -func randPower() int64 { - return cmn.RandInt64() -} - -func makeVal(secret string) abci.Validator { - return abci.Validator{ - PubKey: tmtypes.TM2PB.PubKey(makePubKey(secret)), - Power: randPower(), - } -} - -func makePubKey(secret string) crypto.PubKey { - return makePrivKey(secret).PubKey() -} - -func makePrivKey(secret string) crypto.PrivKey { - privKey := crypto.GenPrivKeyEd25519FromSecret([]byte(secret)) - return privKey -} - -func secret(index int) string { - return fmt.Sprintf("secret%d", index) -} - -func copyVal(val abci.Validator) abci.Validator { - // val2 := *val - // return &val2 - return val -} - -func toJSON(o interface{}) []byte { - bz, err := json.Marshal(o) - if err != nil { - panic(err) - } - // fmt.Println(">> toJSON:", string(bz)) - return bz -} - -func fromJSON(bz []byte, ptr interface{}) { - // fmt.Println(">> fromJSON:", string(bz)) - err := json.Unmarshal(bz, ptr) - if err != nil { - panic(err) - } -} diff --git a/baseapp/helpers.go b/baseapp/helpers.go index 43bd654d6..f3f1448bc 100644 --- a/baseapp/helpers.go +++ b/baseapp/helpers.go @@ -1,24 +1,48 @@ package baseapp import ( - "github.com/tendermint/abci/server" - abci "github.com/tendermint/abci/types" - cmn "github.com/tendermint/tmlibs/common" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tendermint/abci/server" + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" ) +// nolint - Mostly for testing +func (app *BaseApp) Check(tx sdk.Tx) (result sdk.Result) { + return app.runTx(runTxModeCheck, nil, tx) +} + +// nolint - full tx execution +func (app *BaseApp) Simulate(tx sdk.Tx) (result sdk.Result) { + return app.runTx(runTxModeSimulate, nil, tx) +} + +// nolint +func (app *BaseApp) Deliver(tx sdk.Tx) (result sdk.Result) { + return app.runTx(runTxModeDeliver, nil, tx) +} + // RunForever - BasecoinApp execution and cleanup func RunForever(app abci.Application) { // Start the ABCI server - srv, err := server.NewServer("0.0.0.0:46658", "socket", app) + srv, err := server.NewServer("0.0.0.0:26658", "socket", app) if err != nil { cmn.Exit(err.Error()) + return + } + err = srv.Start() + if err != nil { + cmn.Exit(err.Error()) + return } - srv.Start() // Wait forever cmn.TrapSignal(func() { // Cleanup - srv.Stop() + err := srv.Stop() + if err != nil { + cmn.Exit(err.Error()) + } }) } diff --git a/baseapp/router.go b/baseapp/router.go index 83efe5dad..abbbf9e12 100644 --- a/baseapp/router.go +++ b/baseapp/router.go @@ -36,7 +36,7 @@ var isAlpha = regexp.MustCompile(`^[a-zA-Z]+$`).MatchString // AddRoute - TODO add description func (rtr *router) AddRoute(r string, h sdk.Handler) Router { if !isAlpha(r) { - panic("route expressions can only contain alphanumeric characters") + panic("route expressions can only contain alphabet characters") } rtr.routes = append(rtr.routes, route{r, h}) diff --git a/client/context/helpers.go b/client/context/helpers.go index 0229827fe..00ff8a81c 100644 --- a/client/context/helpers.go +++ b/client/context/helpers.go @@ -3,13 +3,15 @@ package context import ( "fmt" + "github.com/tendermint/tendermint/libs/common" + "github.com/pkg/errors" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" + cmn "github.com/tendermint/tendermint/libs/common" rpcclient "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" - cmn "github.com/tendermint/tmlibs/common" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/keys" @@ -30,26 +32,47 @@ func (ctx CoreContext) BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, } if res.CheckTx.Code != uint32(0) { - return res, errors.Errorf("CheckTx failed: (%d) %s", + return res, errors.Errorf("checkTx failed: (%d) %s", res.CheckTx.Code, res.CheckTx.Log) } if res.DeliverTx.Code != uint32(0) { - return res, errors.Errorf("DeliverTx failed: (%d) %s", + return res, errors.Errorf("deliverTx failed: (%d) %s", res.DeliverTx.Code, res.DeliverTx.Log) } return res, err } -// Query from Tendermint with the provided key and storename -func (ctx CoreContext) Query(key cmn.HexBytes, storeName string) (res []byte, err error) { - return ctx.query(key, storeName, "key") +// Broadcast the transaction bytes to Tendermint +func (ctx CoreContext) BroadcastTxAsync(tx []byte) (*ctypes.ResultBroadcastTx, error) { + + node, err := ctx.GetNode() + if err != nil { + return nil, err + } + + res, err := node.BroadcastTxAsync(tx) + if err != nil { + return res, err + } + + return res, err +} + +// Query information about the connected node +func (ctx CoreContext) Query(path string) (res []byte, err error) { + return ctx.query(path, nil) +} + +// QueryStore from Tendermint with the provided key and storename +func (ctx CoreContext) QueryStore(key cmn.HexBytes, storeName string) (res []byte, err error) { + return ctx.queryStore(key, storeName, "key") } // Query from Tendermint with the provided storename and subspace func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName string) (res []sdk.KVPair, err error) { - resRaw, err := ctx.query(subspace, storeName, "subspace") + resRaw, err := ctx.queryStore(subspace, storeName, "subspace") if err != nil { return res, err } @@ -58,8 +81,7 @@ func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName } // Query from Tendermint with the provided storename and path -func (ctx CoreContext) query(key cmn.HexBytes, storeName, endPath string) (res []byte, err error) { - path := fmt.Sprintf("/store/%s/%s", storeName, endPath) +func (ctx CoreContext) query(path string, key common.HexBytes) (res []byte, err error) { node, err := ctx.GetNode() if err != nil { return res, err @@ -75,13 +97,19 @@ func (ctx CoreContext) query(key cmn.HexBytes, storeName, endPath string) (res [ } resp := result.Response if resp.Code != uint32(0) { - return res, errors.Errorf("Query failed: (%d) %s", resp.Code, resp.Log) + return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) } return resp.Value, nil } +// Query from Tendermint with the provided storename and path +func (ctx CoreContext) queryStore(key cmn.HexBytes, storeName, endPath string) (res []byte, err error) { + path := fmt.Sprintf("/store/%s/%s", storeName, endPath) + return ctx.query(path, key) +} + // Get the from address from the name flag -func (ctx CoreContext) GetFromAddress() (from sdk.Address, err error) { +func (ctx CoreContext) GetFromAddress() (from sdk.AccAddress, err error) { keybase, err := keys.GetKeyBase() if err != nil { @@ -95,29 +123,40 @@ func (ctx CoreContext) GetFromAddress() (from sdk.Address, err error) { info, err := keybase.Get(name) if err != nil { - return nil, errors.Errorf("No key for: %s", name) + return nil, errors.Errorf("no key for: %s", name) } - return info.PubKey.Address(), nil + return sdk.AccAddress(info.GetPubKey().Address()), nil } // sign and build the transaction from the msg -func (ctx CoreContext) SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *wire.Codec) ([]byte, error) { +func (ctx CoreContext) SignAndBuild(name, passphrase string, msgs []sdk.Msg, cdc *wire.Codec) ([]byte, error) { // build the Sign Messsage from the Standard Message chainID := ctx.ChainID if chainID == "" { - return nil, errors.Errorf("Chain ID required but not specified") + return nil, errors.Errorf("chain ID required but not specified") } accnum := ctx.AccountNumber sequence := ctx.Sequence + memo := ctx.Memo + + fee := sdk.Coin{} + if ctx.Fee != "" { + parsedFee, err := sdk.ParseCoin(ctx.Fee) + if err != nil { + return nil, err + } + fee = parsedFee + } signMsg := auth.StdSignMsg{ - ChainID: chainID, - AccountNumbers: []int64{accnum}, - Sequences: []int64{sequence}, - Msg: msg, - Fee: auth.NewStdFee(ctx.Gas, sdk.Coin{}), // TODO run simulate to estimate gas? + ChainID: chainID, + AccountNumber: accnum, + Sequence: sequence, + Msgs: msgs, + Memo: memo, + Fee: auth.NewStdFee(ctx.Gas, fee), // TODO run simulate to estimate gas? } keybase, err := keys.GetKeyBase() @@ -140,14 +179,13 @@ func (ctx CoreContext) SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *w }} // marshal bytes - tx := auth.NewStdTx(signMsg.Msg, signMsg.Fee, sigs) + tx := auth.NewStdTx(signMsg.Msgs, signMsg.Fee, sigs, memo) return cdc.MarshalBinary(tx) } // sign and build the transaction from the msg -func (ctx CoreContext) EnsureSignBuildBroadcast(name string, msg sdk.Msg, cdc *wire.Codec) (res *ctypes.ResultBroadcastTxCommit, err error) { - +func (ctx CoreContext) ensureSignBuild(name string, msgs []sdk.Msg, cdc *wire.Codec) (tyBytes []byte, err error) { ctx, err = EnsureAccountNumber(ctx) if err != nil { return nil, err @@ -158,26 +196,95 @@ func (ctx CoreContext) EnsureSignBuildBroadcast(name string, msg sdk.Msg, cdc *w return nil, err } - passphrase, err := ctx.GetPassphraseFromStdin(name) + var txBytes []byte + + keybase, err := keys.GetKeyBase() if err != nil { return nil, err } - txBytes, err := ctx.SignAndBuild(name, passphrase, msg, cdc) + info, err := keybase.Get(name) if err != nil { return nil, err } + var passphrase string + // Only need a passphrase for locally-stored keys + if info.GetType() == "local" { + passphrase, err = ctx.GetPassphraseFromStdin(name) + if err != nil { + return nil, fmt.Errorf("Error fetching passphrase: %v", err) + } + } + txBytes, err = ctx.SignAndBuild(name, passphrase, msgs, cdc) + if err != nil { + return nil, fmt.Errorf("Error signing transaction: %v", err) + } - return ctx.BroadcastTx(txBytes) + return txBytes, err +} + +// sign and build the transaction from the msg +func (ctx CoreContext) EnsureSignBuildBroadcast(name string, msgs []sdk.Msg, cdc *wire.Codec) (err error) { + + txBytes, err := ctx.ensureSignBuild(name, msgs, cdc) + if err != nil { + return err + } + + if ctx.Async { + res, err := ctx.BroadcastTxAsync(txBytes) + if err != nil { + return err + } + if ctx.JSON { + type toJSON struct { + TxHash string + } + valueToJSON := toJSON{res.Hash.String()} + JSON, err := cdc.MarshalJSON(valueToJSON) + if err != nil { + return err + } + fmt.Println(string(JSON)) + } else { + fmt.Println("Async tx sent. tx hash: ", res.Hash.String()) + } + return nil + } + res, err := ctx.BroadcastTx(txBytes) + if err != nil { + return err + } + if ctx.JSON { + // Since JSON is intended for automated scripts, always include response in JSON mode + type toJSON struct { + Height int64 + TxHash string + Response string + } + valueToJSON := toJSON{res.Height, res.Hash.String(), fmt.Sprintf("%+v", res.DeliverTx)} + JSON, err := cdc.MarshalJSON(valueToJSON) + if err != nil { + return err + } + fmt.Println(string(JSON)) + return nil + } + if ctx.PrintResponse { + fmt.Printf("Committed at block %d. Hash: %s Response:%+v \n", res.Height, res.Hash.String(), res.DeliverTx) + } else { + fmt.Printf("Committed at block %d. Hash: %s \n", res.Height, res.Hash.String()) + } + return nil } // get the next sequence for the account address func (ctx CoreContext) GetAccountNumber(address []byte) (int64, error) { if ctx.Decoder == nil { - return 0, errors.New("AccountDecoder required but not provided") + return 0, errors.New("accountDecoder required but not provided") } - res, err := ctx.Query(auth.AddressStoreKey(address), ctx.AccountStore) + res, err := ctx.QueryStore(auth.AddressStoreKey(address), ctx.AccountStore) if err != nil { return 0, err } @@ -198,10 +305,10 @@ func (ctx CoreContext) GetAccountNumber(address []byte) (int64, error) { // get the next sequence for the account address func (ctx CoreContext) NextSequence(address []byte) (int64, error) { if ctx.Decoder == nil { - return 0, errors.New("AccountDecoder required but not provided") + return 0, errors.New("accountDecoder required but not provided") } - res, err := ctx.Query(auth.AddressStoreKey(address), ctx.AccountStore) + res, err := ctx.QueryStore(auth.AddressStoreKey(address), ctx.AccountStore) if err != nil { return 0, err } @@ -229,7 +336,7 @@ func (ctx CoreContext) GetPassphraseFromStdin(name string) (pass string, err err // GetNode prepares a simple rpc.Client func (ctx CoreContext) GetNode() (rpcclient.Client, error) { if ctx.Client == nil { - return nil, errors.New("Must define node URI") + return nil, errors.New("must define node URI") } return ctx.Client, nil } diff --git a/client/context/types.go b/client/context/types.go index 791ffb23a..03dd6b9d0 100644 --- a/client/context/types.go +++ b/client/context/types.go @@ -11,14 +11,20 @@ type CoreContext struct { ChainID string Height int64 Gas int64 + Fee string TrustNode bool NodeURI string FromAddressName string AccountNumber int64 Sequence int64 + Memo string Client rpcclient.Client Decoder auth.AccountDecoder AccountStore string + UseLedger bool + Async bool + JSON bool + PrintResponse bool } // WithChainID - return a copy of the context with an updated chainID @@ -39,6 +45,12 @@ func (c CoreContext) WithGas(gas int64) CoreContext { return c } +// WithFee - return a copy of the context with an updated fee +func (c CoreContext) WithFee(fee string) CoreContext { + c.Fee = fee + return c +} + // WithTrustNode - return a copy of the context with an updated TrustNode flag func (c CoreContext) WithTrustNode(trustNode bool) CoreContext { c.TrustNode = trustNode @@ -70,6 +82,12 @@ func (c CoreContext) WithSequence(sequence int64) CoreContext { return c } +// WithMemo - return a copy of the context with an updated memo +func (c CoreContext) WithMemo(memo string) CoreContext { + c.Memo = memo + return c +} + // WithClient - return a copy of the context with an updated RPC client instance func (c CoreContext) WithClient(client rpcclient.Client) CoreContext { c.Client = client @@ -87,3 +105,9 @@ func (c CoreContext) WithAccountStore(accountStore string) CoreContext { c.AccountStore = accountStore return c } + +// WithUseLedger - return a copy of the context with an updated UseLedger +func (c CoreContext) WithUseLedger(useLedger bool) CoreContext { + c.UseLedger = useLedger + return c +} diff --git a/client/context/viper.go b/client/context/viper.go index 5f262d56f..611ad1b92 100644 --- a/client/context/viper.go +++ b/client/context/viper.go @@ -27,18 +27,31 @@ func NewCoreContextFromViper() CoreContext { chainID = def } } + // TODO: Remove the following deprecation code after Gaia-7000 is launched + keyName := viper.GetString(client.FlagName) + if keyName != "" { + fmt.Println("** Note --name is deprecated and will be removed next release. Please use --from instead **") + } else { + keyName = viper.GetString(client.FlagFrom) + } return CoreContext{ ChainID: chainID, Height: viper.GetInt64(client.FlagHeight), Gas: viper.GetInt64(client.FlagGas), + Fee: viper.GetString(client.FlagFee), TrustNode: viper.GetBool(client.FlagTrustNode), - FromAddressName: viper.GetString(client.FlagName), + FromAddressName: keyName, NodeURI: nodeURI, AccountNumber: viper.GetInt64(client.FlagAccountNumber), Sequence: viper.GetInt64(client.FlagSequence), + Memo: viper.GetString(client.FlagMemo), Client: rpc, Decoder: nil, AccountStore: "acc", + UseLedger: viper.GetBool(client.FlagUseLedger), + Async: viper.GetBool(client.FlagAsync), + JSON: viper.GetBool(client.FlagJson), + PrintResponse: viper.GetBool(client.FlagPrintResponse), } } @@ -55,7 +68,7 @@ func defaultChainID() (string, error) { return doc.ChainID, nil } -// EnsureSequence - automatically set sequence number if none provided +// EnsureAccount - automatically set account number if none provided func EnsureAccountNumber(ctx CoreContext) (CoreContext, error) { // Should be viper.IsSet, but this does not work - https://github.com/spf13/viper/pull/331 if viper.GetInt64(client.FlagAccountNumber) != 0 { diff --git a/client/flags.go b/client/flags.go index 4991b9a77..b96012da7 100644 --- a/client/flags.go +++ b/client/flags.go @@ -4,15 +4,21 @@ import "github.com/spf13/cobra" // nolint const ( + FlagUseLedger = "ledger" FlagChainID = "chain-id" FlagNode = "node" FlagHeight = "height" FlagGas = "gas" FlagTrustNode = "trust-node" + FlagFrom = "from" FlagName = "name" FlagAccountNumber = "account-number" FlagSequence = "sequence" + FlagMemo = "memo" FlagFee = "fee" + FlagAsync = "async" + FlagJson = "json" + FlagPrintResponse = "print-response" ) // LineBreak can be included in a command list to provide a blank line @@ -24,8 +30,9 @@ func GetCommands(cmds ...*cobra.Command) []*cobra.Command { for _, c := range cmds { // TODO: make this default false when we support proofs c.Flags().Bool(FlagTrustNode, true, "Don't verify proofs for responses") + c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device") c.Flags().String(FlagChainID, "", "Chain ID of tendermint node") - c.Flags().String(FlagNode, "tcp://localhost:46657", ": to tendermint rpc interface for this chain") + c.Flags().String(FlagNode, "tcp://localhost:26657", ": to tendermint rpc interface for this chain") c.Flags().Int64(FlagHeight, 0, "block height to query, omit to get most recent provable block") } return cmds @@ -34,13 +41,19 @@ func GetCommands(cmds ...*cobra.Command) []*cobra.Command { // PostCommands adds common flags for commands to post tx func PostCommands(cmds ...*cobra.Command) []*cobra.Command { for _, c := range cmds { - c.Flags().String(FlagName, "", "Name of private key with which to sign") + c.Flags().String(FlagFrom, "", "Name of private key with which to sign") + c.Flags().String(FlagName, "", "DEPRECATED - Name of private key with which to sign") c.Flags().Int64(FlagAccountNumber, 0, "AccountNumber number to sign the tx") c.Flags().Int64(FlagSequence, 0, "Sequence number to sign the tx") + c.Flags().String(FlagMemo, "", "Memo to send along with transaction") c.Flags().String(FlagFee, "", "Fee to pay along with transaction") c.Flags().String(FlagChainID, "", "Chain ID of tendermint node") - c.Flags().String(FlagNode, "tcp://localhost:46657", ": to tendermint rpc interface for this chain") + c.Flags().String(FlagNode, "tcp://localhost:26657", ": to tendermint rpc interface for this chain") + c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device") c.Flags().Int64(FlagGas, 200000, "gas limit to set per-transaction") + c.Flags().Bool(FlagAsync, false, "broadcast transactions asynchronously") + c.Flags().Bool(FlagJson, false, "return output in json format") + c.Flags().Bool(FlagPrintResponse, false, "return tx response (only works with async = false)") } return cmds } diff --git a/client/input.go b/client/input.go index 53906ca88..03140a33c 100644 --- a/client/input.go +++ b/client/input.go @@ -32,7 +32,7 @@ func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) { return "", err } if len(pass) < MinPassLength { - return "", errors.Errorf("Password must be at least %d characters", MinPassLength) + return "", errors.Errorf("password must be at least %d characters", MinPassLength) } return pass, nil } @@ -68,7 +68,7 @@ func GetCheckPassword(prompt, prompt2 string, buf *bufio.Reader) (string, error) return "", err } if pass != pass2 { - return "", errors.New("Passphrases don't match") + return "", errors.New("passphrases don't match") } return pass, nil } diff --git a/client/keys.go b/client/keys.go index 47eb0b9c9..a39b074b9 100644 --- a/client/keys.go +++ b/client/keys.go @@ -1,9 +1,8 @@ package client import ( - "github.com/tendermint/go-crypto/keys" - "github.com/tendermint/go-crypto/keys/words" - dbm "github.com/tendermint/tmlibs/db" + "github.com/cosmos/cosmos-sdk/crypto/keys" + dbm "github.com/tendermint/tendermint/libs/db" ) // GetKeyBase initializes a keybase based on the given db. @@ -11,7 +10,6 @@ import ( func GetKeyBase(db dbm.DB) keys.Keybase { keybase := keys.New( db, - words.MustLoadCodec("english"), ) return keybase } diff --git a/client/keys/add.go b/client/keys/add.go index 7ad9474ce..1bd13dd0a 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -12,8 +12,10 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/go-crypto/keys" - "github.com/tendermint/tmlibs/cli" + ccrypto "github.com/cosmos/cosmos-sdk/crypto" + "github.com/cosmos/cosmos-sdk/crypto/keys" + + "github.com/tendermint/tendermint/libs/cli" ) const ( @@ -21,6 +23,8 @@ const ( flagRecover = "recover" flagNoBackup = "no-backup" flagDryRun = "dry-run" + flagAccount = "account" + flagIndex = "index" ) func addKeyCommand() *cobra.Command { @@ -32,13 +36,18 @@ If you select --seed/-s you can recover a key from the seed phrase, otherwise, a new key will be generated.`, RunE: runAddCmd, } - cmd.Flags().StringP(flagType, "t", "ed25519", "Type of private key (ed25519|secp256k1|ledger)") + cmd.Flags().StringP(flagType, "t", "secp256k1", "Type of private key (secp256k1|ed25519)") + cmd.Flags().Bool(client.FlagUseLedger, false, "Store a local reference to a private key on a Ledger device") cmd.Flags().Bool(flagRecover, false, "Provide seed phrase to recover existing key instead of creating") cmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)") cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore") + cmd.Flags().Uint32(flagAccount, 0, "Account number for HD derivation") + cmd.Flags().Uint32(flagIndex, 0, "Index number for HD derivation") return cmd } +// nolint: gocyclo +// TODO remove the above when addressing #1446 func runAddCmd(cmd *cobra.Command, args []string) error { var kb keys.Keybase var err error @@ -53,7 +62,7 @@ func runAddCmd(cmd *cobra.Command, args []string) error { name = "inmemorykey" } else { if len(args) != 1 || len(args[0]) == 0 { - return errors.New("You must provide a name for the key") + return errors.New("you must provide a name for the key") } name = args[0] kb, err = GetKeyBase() @@ -70,21 +79,34 @@ func runAddCmd(cmd *cobra.Command, args []string) error { } } - pass, err = client.GetCheckPassword( - "Enter a passphrase for your key:", - "Repeat the passphrase:", buf) - if err != nil { - return err + // ask for a password when generating a local key + if !viper.GetBool(client.FlagUseLedger) { + pass, err = client.GetCheckPassword( + "Enter a passphrase for your key:", + "Repeat the passphrase:", buf) + if err != nil { + return err + } } } - if viper.GetBool(flagRecover) { + if viper.GetBool(client.FlagUseLedger) { + account := uint32(viper.GetInt(flagAccount)) + index := uint32(viper.GetInt(flagIndex)) + path := ccrypto.DerivationPath{44, 118, account, 0, index} + algo := keys.SigningAlgo(viper.GetString(flagType)) + info, err := kb.CreateLedger(name, path, algo) + if err != nil { + return err + } + printCreate(info, "") + } else if viper.GetBool(flagRecover) { seed, err := client.GetSeed( "Enter your recovery seed phrase:", buf) if err != nil { return err } - info, err := kb.Recover(name, pass, seed) + info, err := kb.CreateKey(name, seed, pass) if err != nil { return err } @@ -92,8 +114,8 @@ func runAddCmd(cmd *cobra.Command, args []string) error { viper.Set(flagNoBackup, true) printCreate(info, "") } else { - algo := keys.CryptoAlgo(viper.GetString(flagType)) - info, seed, err := kb.Create(name, pass, algo) + algo := keys.SigningAlgo(viper.GetString(flagType)) + info, seed, err := kb.CreateMnemonic(name, keys.English, pass, algo) if err != nil { return err } @@ -108,7 +130,7 @@ func printCreate(info keys.Info, seed string) { case "text": printInfo(info) // print seed unless requested not to. - if !viper.GetBool(flagNoBackup) { + if !viper.GetBool(client.FlagUseLedger) && !viper.GetBool(flagNoBackup) { fmt.Println("**Important** write this seed phrase in a safe place.") fmt.Println("It is the only way to recover your account if you ever forget your password.") fmt.Println() @@ -139,7 +161,6 @@ func printCreate(info keys.Info, seed string) { type NewKeyBody struct { Name string `json:"name"` Password string `json:"password"` - Seed string `json:"seed"` } // add new key REST handler @@ -172,16 +193,11 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("You have to specify a password for the locally stored account.")) return } - if m.Seed == "" { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("You have to specify a seed for the locally stored account.")) - return - } // check if already exists infos, err := kb.List() for _, i := range infos { - if i.Name == m.Name { + if i.GetName() == m.Name { w.WriteHeader(http.StatusConflict) w.Write([]byte(fmt.Sprintf("Account with name %s already exists.", m.Name))) return @@ -189,22 +205,38 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { } // create account - info, err := kb.Recover(m.Name, m.Password, m.Seed) + info, mnemonic, err := kb.CreateMnemonic(m.Name, keys.English, m.Password, keys.Secp256k1) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } - w.Write([]byte(info.PubKey.Address().String())) + + keyOutput, err := Bech32KeyOutput(info) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + keyOutput.Seed = mnemonic + + bz, err := json.Marshal(keyOutput) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(bz) } // function to just a new seed to display in the UI before actually persisting it in the keybase -func getSeed(algo keys.CryptoAlgo) string { +func getSeed(algo keys.SigningAlgo) string { kb := client.MockKeyBase() pass := "throwing-this-key-away" name := "inmemorykey" - - _, seed, _ := kb.Create(name, pass, algo) + _, seed, _ := kb.CreateMnemonic(name, keys.English, pass, algo) return seed } @@ -212,11 +244,11 @@ func getSeed(algo keys.CryptoAlgo) string { func SeedRequestHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) algoType := vars["type"] - // algo type defaults to ed25519 + // algo type defaults to secp256k1 if algoType == "" { - algoType = "ed25519" + algoType = "secp256k1" } - algo := keys.CryptoAlgo(algoType) + algo := keys.SigningAlgo(algoType) seed := getSeed(algo) w.Write([]byte(seed)) diff --git a/client/keys/delete.go b/client/keys/delete.go index e9f3c1abe..944feb4b1 100644 --- a/client/keys/delete.go +++ b/client/keys/delete.go @@ -6,8 +6,8 @@ import ( "net/http" "github.com/cosmos/cosmos-sdk/client" + keys "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/gorilla/mux" - keys "github.com/tendermint/go-crypto/keys" "github.com/spf13/cobra" ) @@ -25,14 +25,19 @@ func deleteKeyCommand() *cobra.Command { func runDeleteCmd(cmd *cobra.Command, args []string) error { name := args[0] - buf := client.BufferStdin() - oldpass, err := client.GetPassword( - "DANGER - enter password to permanently delete key:", buf) + kb, err := GetKeyBase() if err != nil { return err } - kb, err := GetKeyBase() + _, err = kb.Get(name) + if err != nil { + return err + } + + buf := client.BufferStdin() + oldpass, err := client.GetPassword( + "DANGER - enter password to permanently delete key:", buf) if err != nil { return err } diff --git a/client/keys/show.go b/client/keys/show.go index 9051aba16..873c45a4b 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -4,8 +4,8 @@ import ( "encoding/json" "net/http" + keys "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/gorilla/mux" - keys "github.com/tendermint/go-crypto/keys" "github.com/spf13/cobra" ) @@ -28,7 +28,7 @@ var showKeysCmd = &cobra.Command{ func getKey(name string) (keys.Info, error) { kb, err := GetKeyBase() if err != nil { - return keys.Info{}, err + return nil, err } return kb.Get(name) diff --git a/client/keys/update.go b/client/keys/update.go index f3db82c3f..0308b89bd 100644 --- a/client/keys/update.go +++ b/client/keys/update.go @@ -6,8 +6,8 @@ import ( "net/http" "github.com/cosmos/cosmos-sdk/client" + keys "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/gorilla/mux" - keys "github.com/tendermint/go-crypto/keys" "github.com/spf13/cobra" ) diff --git a/client/keys/utils.go b/client/keys/utils.go index 8b6cfcb35..5462597ba 100644 --- a/client/keys/utils.go +++ b/client/keys/utils.go @@ -6,9 +6,9 @@ import ( "github.com/spf13/viper" - keys "github.com/tendermint/go-crypto/keys" - "github.com/tendermint/tmlibs/cli" - dbm "github.com/tendermint/tmlibs/db" + keys "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/tendermint/tendermint/libs/cli" + dbm "github.com/tendermint/tendermint/libs/db" "github.com/cosmos/cosmos-sdk/client" @@ -48,10 +48,11 @@ func SetKeyBase(kb keys.Keybase) { // used for outputting keys.Info over REST type KeyOutput struct { - Name string `json:"name"` - Address string `json:"address"` - PubKey string `json:"pub_key"` - Seed string `json:"seed,omitempty"` + Name string `json:"name"` + Type string `json:"type"` + Address sdk.AccAddress `json:"address"` + PubKey string `json:"pub_key"` + Seed string `json:"seed,omitempty"` } // create a list of KeyOutput in bech32 format @@ -69,17 +70,15 @@ func Bech32KeysOutput(infos []keys.Info) ([]KeyOutput, error) { // create a KeyOutput in bech32 format func Bech32KeyOutput(info keys.Info) (KeyOutput, error) { - bechAccount, err := sdk.Bech32ifyAcc(sdk.Address(info.PubKey.Address().Bytes())) - if err != nil { - return KeyOutput{}, err - } - bechPubKey, err := sdk.Bech32ifyAccPub(info.PubKey) + account := sdk.AccAddress(info.GetPubKey().Address().Bytes()) + bechPubKey, err := sdk.Bech32ifyAccPub(info.GetPubKey()) if err != nil { return KeyOutput{}, err } return KeyOutput{ - Name: info.Name, - Address: bechAccount, + Name: info.GetName(), + Type: info.GetType(), + Address: account, PubKey: bechPubKey, }, nil } @@ -91,7 +90,7 @@ func printInfo(info keys.Info) { } switch viper.Get(cli.OutputFlag) { case "text": - fmt.Printf("NAME:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n") + fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n") printKeyOutput(ko) case "json": out, err := MarshalJSON(ko) @@ -109,7 +108,7 @@ func printInfos(infos []keys.Info) { } switch viper.Get(cli.OutputFlag) { case "text": - fmt.Printf("NAME:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n") + fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n") for _, ko := range kos { printKeyOutput(ko) } @@ -123,5 +122,5 @@ func printInfos(infos []keys.Info) { } func printKeyOutput(ko KeyOutput) { - fmt.Printf("%s\t%s\t%s\n", ko.Name, ko.Address, ko.PubKey) + fmt.Printf("%s\t%s\t%s\t%s\n", ko.Name, ko.Type, ko.Address, ko.PubKey) } diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index ba2937e05..0979a568f 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -2,17 +2,17 @@ package lcd import ( "encoding/hex" - "encoding/json" "fmt" "net/http" "regexp" "testing" - "github.com/stretchr/testify/assert" + "github.com/spf13/viper" "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - cryptoKeys "github.com/tendermint/go-crypto/keys" + cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/common" p2p "github.com/tendermint/tendermint/p2p" ctypes "github.com/tendermint/tendermint/rpc/core/types" @@ -21,41 +21,48 @@ import ( rpc "github.com/cosmos/cosmos-sdk/client/rpc" tests "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" stakerest "github.com/cosmos/cosmos-sdk/x/stake/client/rest" ) +func init() { + cryptoKeys.BcryptSecurityParameter = 1 +} + func TestKeys(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKB(t)) - cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) defer cleanup() // get seed + // TODO Do we really need this endpoint? res, body := Request(t, port, "GET", "/keys/seed", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - newSeed := body reg, err := regexp.Compile(`([a-z]+ ){12}`) require.Nil(t, err) match := reg.MatchString(seed) - assert.True(t, match, "Returned seed has wrong format", seed) + require.True(t, match, "Returned seed has wrong format", seed) newName := "test_newname" newPassword := "0987654321" // add key - var jsonStr = []byte(fmt.Sprintf(`{"name":"test_fail", "password":"%s"}`, password)) - res, body = Request(t, port, "POST", "/keys", jsonStr) - - assert.Equal(t, http.StatusBadRequest, res.StatusCode, "Account creation should require a seed") - - jsonStr = []byte(fmt.Sprintf(`{"name":"%s", "password":"%s", "seed": "%s"}`, newName, newPassword, newSeed)) + jsonStr := []byte(fmt.Sprintf(`{"name":"%s", "password":"%s"}`, newName, newPassword)) res, body = Request(t, port, "POST", "/keys", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) - addr2 := body - assert.Len(t, addr2, 40, "Returned address has wrong format", addr2) + var resp keys.KeyOutput + err = wire.Cdc.UnmarshalJSON([]byte(body), &resp) + require.Nil(t, err, body) + + addr2Bech32 := resp.Address.String() + _, err = sdk.AccAddressFromBech32(addr2Bech32) + require.NoError(t, err, "Failed to return a correct bech32 address") // existing keys res, body = Request(t, port, "GET", "/keys", nil) @@ -64,15 +71,12 @@ func TestKeys(t *testing.T) { err = cdc.UnmarshalJSON([]byte(body), &m) require.Nil(t, err) - addr2Acc, err := sdk.GetAccAddressHex(addr2) - require.Nil(t, err) - addr2Bech32 := sdk.MustBech32ifyAcc(addr2Acc) - addrBech32 := sdk.MustBech32ifyAcc(addr) + addrBech32 := addr.String() - assert.Equal(t, name, m[0].Name, "Did not serve keys name correctly") - assert.Equal(t, addrBech32, m[0].Address, "Did not serve keys Address correctly") - assert.Equal(t, newName, m[1].Name, "Did not serve keys name correctly") - assert.Equal(t, addr2Bech32, m[1].Address, "Did not serve keys Address correctly") + require.Equal(t, name, m[0].Name, "Did not serve keys name correctly") + require.Equal(t, addrBech32, m[0].Address.String(), "Did not serve keys Address correctly") + require.Equal(t, newName, m[1].Name, "Did not serve keys name correctly") + require.Equal(t, addr2Bech32, m[1].Address.String(), "Did not serve keys Address correctly") // select key keyEndpoint := fmt.Sprintf("/keys/%s", newName) @@ -82,12 +86,12 @@ func TestKeys(t *testing.T) { err = cdc.UnmarshalJSON([]byte(body), &m2) require.Nil(t, err) - assert.Equal(t, newName, m2.Name, "Did not serve keys name correctly") - assert.Equal(t, addr2Bech32, m2.Address, "Did not serve keys Address correctly") + require.Equal(t, newName, m2.Name, "Did not serve keys name correctly") + require.Equal(t, addr2Bech32, m2.Address.String(), "Did not serve keys Address correctly") // update key jsonStr = []byte(fmt.Sprintf(`{ - "old_password":"%s", + "old_password":"%s", "new_password":"12345678901" }`, newPassword)) @@ -105,7 +109,7 @@ func TestKeys(t *testing.T) { } func TestVersion(t *testing.T) { - cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) defer cleanup() // node info @@ -115,11 +119,20 @@ func TestVersion(t *testing.T) { reg, err := regexp.Compile(`\d+\.\d+\.\d+(-dev)?`) require.Nil(t, err) match := reg.MatchString(body) - assert.True(t, match, body) + require.True(t, match, body) + + // node info + res, body = Request(t, port, "GET", "/node_version", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + reg, err = regexp.Compile(`\d+\.\d+\.\d+(-dev)?`) + require.Nil(t, err) + match = reg.MatchString(body) + require.True(t, match, body) } func TestNodeStatus(t *testing.T) { - cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) defer cleanup() // node info @@ -130,18 +143,18 @@ func TestNodeStatus(t *testing.T) { err := cdc.UnmarshalJSON([]byte(body), &nodeInfo) require.Nil(t, err, "Couldn't parse node info") - assert.NotEqual(t, p2p.NodeInfo{}, nodeInfo, "res: %v", res) + require.NotEqual(t, p2p.NodeInfo{}, nodeInfo, "res: %v", res) // syncing res, body = Request(t, port, "GET", "/syncing", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) // we expect that there is no other node running so the syncing state is "false" - assert.Equal(t, "false", body) + require.Equal(t, "false", body) } func TestBlock(t *testing.T) { - cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) defer cleanup() var resultBlock ctypes.ResultBlock @@ -152,17 +165,17 @@ func TestBlock(t *testing.T) { err := cdc.UnmarshalJSON([]byte(body), &resultBlock) require.Nil(t, err, "Couldn't parse block") - assert.NotEqual(t, ctypes.ResultBlock{}, resultBlock) + require.NotEqual(t, ctypes.ResultBlock{}, resultBlock) // -- res, body = Request(t, port, "GET", "/blocks/1", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - err = json.Unmarshal([]byte(body), &resultBlock) + err = wire.Cdc.UnmarshalJSON([]byte(body), &resultBlock) require.Nil(t, err, "Couldn't parse block") - assert.NotEqual(t, ctypes.ResultBlock{}, resultBlock) + require.NotEqual(t, ctypes.ResultBlock{}, resultBlock) // -- @@ -171,7 +184,7 @@ func TestBlock(t *testing.T) { } func TestValidators(t *testing.T) { - cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) defer cleanup() var resultVals rpc.ResultValidatorsOutput @@ -182,10 +195,10 @@ func TestValidators(t *testing.T) { err := cdc.UnmarshalJSON([]byte(body), &resultVals) require.Nil(t, err, "Couldn't parse validatorset") - assert.NotEqual(t, rpc.ResultValidatorsOutput{}, resultVals) + require.NotEqual(t, rpc.ResultValidatorsOutput{}, resultVals) - assert.Contains(t, resultVals.Validators[0].Address, "cosmosvaladdr") - assert.Contains(t, resultVals.Validators[0].PubKey, "cosmosvalpub") + require.Contains(t, resultVals.Validators[0].Address.String(), "cosmosvaladdr") + require.Contains(t, resultVals.Validators[0].PubKey, "cosmosvalpub") // -- @@ -195,26 +208,26 @@ func TestValidators(t *testing.T) { err = cdc.UnmarshalJSON([]byte(body), &resultVals) require.Nil(t, err, "Couldn't parse validatorset") - assert.NotEqual(t, rpc.ResultValidatorsOutput{}, resultVals) + require.NotEqual(t, rpc.ResultValidatorsOutput{}, resultVals) // -- res, body = Request(t, port, "GET", "/validatorsets/1000000000", nil) - require.Equal(t, http.StatusNotFound, res.StatusCode) + require.Equal(t, http.StatusNotFound, res.StatusCode, body) } func TestCoinSend(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKB(t)) - cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) defer cleanup() bz, err := hex.DecodeString("8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6") require.NoError(t, err) - someFakeAddr := sdk.MustBech32ifyAcc(bz) + someFakeAddr := sdk.AccAddress(bz) // query empty - res, body := Request(t, port, "GET", "/accounts/"+someFakeAddr, nil) + res, body := Request(t, port, "GET", fmt.Sprintf("/accounts/%s", someFakeAddr), nil) require.Equal(t, http.StatusNoContent, res.StatusCode, body) acc := getAccount(t, port, addr) @@ -224,29 +237,31 @@ func TestCoinSend(t *testing.T) { receiveAddr, resultTx := doSend(t, port, seed, name, password, addr) tests.WaitForHeight(resultTx.Height+1, port) - // check if tx was commited - assert.Equal(t, uint32(0), resultTx.CheckTx.Code) - assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) + // check if tx was committed + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) // query sender acc = getAccount(t, port, addr) coins := acc.GetCoins() mycoins := coins[0] - assert.Equal(t, "steak", mycoins.Denom) - assert.Equal(t, initialBalance[0].Amount-1, mycoins.Amount) + + require.Equal(t, "steak", mycoins.Denom) + require.Equal(t, initialBalance[0].Amount.SubRaw(1), mycoins.Amount) // query receiver acc = getAccount(t, port, receiveAddr) coins = acc.GetCoins() mycoins = coins[0] - assert.Equal(t, "steak", mycoins.Denom) - assert.Equal(t, int64(1), mycoins.Amount) + + require.Equal(t, "steak", mycoins.Denom) + require.Equal(t, int64(1), mycoins.Amount.Int64()) } func TestIBCTransfer(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKB(t)) - cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) defer cleanup() acc := getAccount(t, port, addr) @@ -257,16 +272,17 @@ func TestIBCTransfer(t *testing.T) { tests.WaitForHeight(resultTx.Height+1, port) - // check if tx was commited - assert.Equal(t, uint32(0), resultTx.CheckTx.Code) - assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) + // check if tx was committed + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) // query sender acc = getAccount(t, port, addr) coins := acc.GetCoins() mycoins := coins[0] - assert.Equal(t, "steak", mycoins.Denom) - assert.Equal(t, initialBalance[0].Amount-1, mycoins.Amount) + + require.Equal(t, "steak", mycoins.Denom) + require.Equal(t, initialBalance[0].Amount.SubRaw(1), mycoins.Amount) // TODO: query ibc egress packet state } @@ -274,7 +290,7 @@ func TestIBCTransfer(t *testing.T) { func TestTxs(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKB(t)) - cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) defer cleanup() // query wrong @@ -284,7 +300,7 @@ func TestTxs(t *testing.T) { // query empty res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=sender_bech32='%s'", "cosmosaccaddr1jawd35d9aq4u76sr3fjalmcqc8hqygs9gtnmv3"), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - assert.Equal(t, "[]", body) + require.Equal(t, "[]", body) // create TX receiveAddr, resultTx := doSend(t, port, seed, name, password, addr) @@ -296,6 +312,7 @@ func TestTxs(t *testing.T) { require.Equal(t, http.StatusOK, res.StatusCode, body) type txInfo struct { + Hash common.HexBytes `json:"hash"` Height int64 `json:"height"` Tx sdk.Tx `json:"tx"` Result abci.ResponseDeliverTx `json:"result"` @@ -305,40 +322,43 @@ func TestTxs(t *testing.T) { // check if tx is queryable res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=tx.hash='%s'", resultTx.Hash), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - assert.NotEqual(t, "[]", body) + require.NotEqual(t, "[]", body) err := cdc.UnmarshalJSON([]byte(body), &indexedTxs) require.NoError(t, err) - assert.Equal(t, 1, len(indexedTxs)) + require.Equal(t, 1, len(indexedTxs)) + + // XXX should this move into some other testfile for txs in general? + // test if created TX hash is the correct hash + require.Equal(t, resultTx.Hash, indexedTxs[0].Hash) // query sender - addrBech := sdk.MustBech32ifyAcc(addr) - res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=sender_bech32='%s'", addrBech), nil) + // also tests url decoding + res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=sender_bech32=%%27%s%%27", addr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) err = cdc.UnmarshalJSON([]byte(body), &indexedTxs) require.NoError(t, err) require.Equal(t, 1, len(indexedTxs), "%v", indexedTxs) // there are 2 txs created with doSend - assert.Equal(t, resultTx.Height, indexedTxs[0].Height) + require.Equal(t, resultTx.Height, indexedTxs[0].Height) // query recipient - receiveAddrBech := sdk.MustBech32ifyAcc(receiveAddr) - res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=recipient_bech32='%s'", receiveAddrBech), nil) + res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=recipient_bech32='%s'", receiveAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) err = cdc.UnmarshalJSON([]byte(body), &indexedTxs) require.NoError(t, err) require.Equal(t, 1, len(indexedTxs)) - assert.Equal(t, resultTx.Height, indexedTxs[0].Height) + require.Equal(t, resultTx.Height, indexedTxs[0].Height) } func TestValidatorsQuery(t *testing.T) { - cleanup, pks, port := InitializeTestLCD(t, 2, []sdk.Address{}) - require.Equal(t, 2, len(pks)) + cleanup, pks, port := InitializeTestLCD(t, 2, []sdk.AccAddress{}) defer cleanup() + require.Equal(t, 2, len(pks)) validators := getValidators(t, port) - assert.Equal(t, len(validators), 2) + require.Equal(t, len(validators), 2) // make sure all the validators were found (order unknown because sorted by owner addr) foundVal1, foundVal2 := false, false @@ -350,63 +370,243 @@ func TestValidatorsQuery(t *testing.T) { if validators[0].PubKey == pk2Bech || validators[1].PubKey == pk2Bech { foundVal2 = true } - assert.True(t, foundVal1, "pk1Bech %v, owner1 %v, owner2 %v", pk1Bech, validators[0].Owner, validators[1].Owner) - assert.True(t, foundVal2, "pk2Bech %v, owner1 %v, owner2 %v", pk2Bech, validators[0].Owner, validators[1].Owner) + require.True(t, foundVal1, "pk1Bech %v, owner1 %v, owner2 %v", pk1Bech, validators[0].Owner, validators[1].Owner) + require.True(t, foundVal2, "pk2Bech %v, owner1 %v, owner2 %v", pk2Bech, validators[0].Owner, validators[1].Owner) } func TestBonding(t *testing.T) { name, password, denom := "test", "1234567890", "steak" addr, seed := CreateAddr(t, "test", password, GetKB(t)) - cleanup, pks, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + cleanup, pks, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) defer cleanup() - validator1Owner := pks[0].Address() + validator1Owner := sdk.AccAddress(pks[0].Address()) // create bond TX - resultTx := doBond(t, port, seed, name, password, addr, validator1Owner) + resultTx := doDelegate(t, port, seed, name, password, addr, validator1Owner) tests.WaitForHeight(resultTx.Height+1, port) - // check if tx was commited - assert.Equal(t, uint32(0), resultTx.CheckTx.Code) - assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) + // check if tx was committed + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) // query sender acc := getAccount(t, port, addr) coins := acc.GetCoins() - assert.Equal(t, int64(40), coins.AmountOf(denom)) + + require.Equal(t, int64(40), coins.AmountOf(denom).Int64()) // query validator bond := getDelegation(t, port, addr, validator1Owner) - assert.Equal(t, "60/1", bond.Shares.String()) + require.Equal(t, "60/1", bond.Shares.String()) ////////////////////// // testing unbonding // create unbond TX - resultTx = doUnbond(t, port, seed, name, password, addr, validator1Owner) + resultTx = doBeginUnbonding(t, port, seed, name, password, addr, validator1Owner) tests.WaitForHeight(resultTx.Height+1, port) // query validator bond = getDelegation(t, port, addr, validator1Owner) - assert.Equal(t, "30/1", bond.Shares.String()) + require.Equal(t, "30/1", bond.Shares.String()) - // check if tx was commited - assert.Equal(t, uint32(0), resultTx.CheckTx.Code) - assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) + // check if tx was committed + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) - // TODO fix shares fn in staking + // should the sender should have not received any coins as the unbonding has only just begun // query sender - //acc := getAccount(t, sendAddr) - //coins := acc.GetCoins() - //assert.Equal(t, int64(98), coins.AmountOf(coinDenom)) + acc = getAccount(t, port, addr) + coins = acc.GetCoins() + require.Equal(t, int64(40), coins.AmountOf("steak").Int64()) + // TODO add redelegation, need more complex capabilities such to mock context and +} + +func TestSubmitProposal(t *testing.T) { + name, password := "test", "1234567890" + addr, seed := CreateAddr(t, "test", password, GetKB(t)) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + + // create SubmitProposal TX + resultTx := doSubmitProposal(t, port, seed, name, password, addr) + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx was committed + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + var proposalID int64 + cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID) + + // query proposal + proposal := getProposal(t, port, proposalID) + require.Equal(t, "Test", proposal.Title) +} + +func TestDeposit(t *testing.T) { + name, password := "test", "1234567890" + addr, seed := CreateAddr(t, "test", password, GetKB(t)) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + + // create SubmitProposal TX + resultTx := doSubmitProposal(t, port, seed, name, password, addr) + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx was committed + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + var proposalID int64 + cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID) + + // query proposal + proposal := getProposal(t, port, proposalID) + require.Equal(t, "Test", proposal.Title) + + // create SubmitProposal TX + resultTx = doDeposit(t, port, seed, name, password, addr, proposalID) + tests.WaitForHeight(resultTx.Height+1, port) + + // query proposal + proposal = getProposal(t, port, proposalID) + require.True(t, proposal.TotalDeposit.IsEqual(sdk.Coins{sdk.NewCoin("steak", 10)})) + + // query deposit + deposit := getDeposit(t, port, proposalID, addr) + require.True(t, deposit.Amount.IsEqual(sdk.Coins{sdk.NewCoin("steak", 10)})) +} + +func TestVote(t *testing.T) { + name, password := "test", "1234567890" + addr, seed := CreateAddr(t, "test", password, GetKB(t)) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + + // create SubmitProposal TX + resultTx := doSubmitProposal(t, port, seed, name, password, addr) + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx was committed + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + var proposalID int64 + cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID) + + // query proposal + proposal := getProposal(t, port, proposalID) + require.Equal(t, "Test", proposal.Title) + + // create SubmitProposal TX + resultTx = doDeposit(t, port, seed, name, password, addr, proposalID) + tests.WaitForHeight(resultTx.Height+1, port) + + // query proposal + proposal = getProposal(t, port, proposalID) + require.Equal(t, gov.StatusToString(gov.StatusVotingPeriod), proposal.Status) + + // create SubmitProposal TX + resultTx = doVote(t, port, seed, name, password, addr, proposalID) + tests.WaitForHeight(resultTx.Height+1, port) + + vote := getVote(t, port, proposalID, addr) + require.Equal(t, proposalID, vote.ProposalID) + require.Equal(t, gov.VoteOptionToString(gov.OptionYes), vote.Option) +} + +func TestUnrevoke(t *testing.T) { + _, password := "test", "1234567890" + addr, _ := CreateAddr(t, "test", password, GetKB(t)) + cleanup, pks, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + + // XXX: any less than this and it fails + tests.WaitForHeight(3, port) + + signingInfo := getSigningInfo(t, port, sdk.ValAddress(pks[0].Address())) + tests.WaitForHeight(4, port) + require.Equal(t, true, signingInfo.IndexOffset > 0) + require.Equal(t, int64(0), signingInfo.JailedUntil) + require.Equal(t, true, signingInfo.SignedBlocksCounter > 0) +} + +func TestProposalsQuery(t *testing.T) { + name, password1 := "test", "1234567890" + name2, password2 := "test2", "1234567890" + addr, seed := CreateAddr(t, "test", password1, GetKB(t)) + addr2, seed2 := CreateAddr(t, "test2", password2, GetKB(t)) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr, addr2}) + defer cleanup() + + // Addr1 proposes (and deposits) proposals #1 and #2 + resultTx := doSubmitProposal(t, port, seed, name, password1, addr) + var proposalID1 int64 + cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID1) + tests.WaitForHeight(resultTx.Height+1, port) + resultTx = doSubmitProposal(t, port, seed, name, password1, addr) + var proposalID2 int64 + cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID2) + tests.WaitForHeight(resultTx.Height+1, port) + + // Addr2 proposes (and deposits) proposals #3 + resultTx = doSubmitProposal(t, port, seed2, name2, password2, addr2) + var proposalID3 int64 + cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID3) + tests.WaitForHeight(resultTx.Height+1, port) + + // Addr2 deposits on proposals #2 & #3 + resultTx = doDeposit(t, port, seed2, name2, password2, addr2, proposalID2) + tests.WaitForHeight(resultTx.Height+1, port) + resultTx = doDeposit(t, port, seed2, name2, password2, addr2, proposalID3) + tests.WaitForHeight(resultTx.Height+1, port) + + // Addr1 votes on proposals #2 & #3 + resultTx = doVote(t, port, seed, name, password1, addr, proposalID2) + tests.WaitForHeight(resultTx.Height+1, port) + resultTx = doVote(t, port, seed, name, password1, addr, proposalID3) + tests.WaitForHeight(resultTx.Height+1, port) + + // Addr2 votes on proposal #3 + resultTx = doVote(t, port, seed2, name2, password2, addr2, proposalID3) + tests.WaitForHeight(resultTx.Height+1, port) + + // Test query all proposals + proposals := getProposalsAll(t, port) + require.Equal(t, proposalID1, (proposals[0]).ProposalID) + require.Equal(t, proposalID2, (proposals[1]).ProposalID) + require.Equal(t, proposalID3, (proposals[2]).ProposalID) + + // Test query deposited by addr1 + proposals = getProposalsFilterDepositer(t, port, addr) + require.Equal(t, proposalID1, (proposals[0]).ProposalID) + + // Test query deposited by addr2 + proposals = getProposalsFilterDepositer(t, port, addr2) + require.Equal(t, proposalID2, (proposals[0]).ProposalID) + require.Equal(t, proposalID3, (proposals[1]).ProposalID) + + // Test query voted by addr1 + proposals = getProposalsFilterVoter(t, port, addr) + require.Equal(t, proposalID2, (proposals[0]).ProposalID) + require.Equal(t, proposalID3, (proposals[1]).ProposalID) + + // Test query voted by addr2 + proposals = getProposalsFilterVoter(t, port, addr2) + require.Equal(t, proposalID3, (proposals[0]).ProposalID) + + // Test query voted and deposited by addr1 + proposals = getProposalsFilterVoterDepositer(t, port, addr, addr) + require.Equal(t, proposalID2, (proposals[0]).ProposalID) } //_____________________________________________________________________________ // get the account to get the sequence -func getAccount(t *testing.T, port string, addr sdk.Address) auth.Account { - addrBech32 := sdk.MustBech32ifyAcc(addr) - res, body := Request(t, port, "GET", "/accounts/"+addrBech32, nil) +func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account { + res, body := Request(t, port, "GET", fmt.Sprintf("/accounts/%s", addr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var acc auth.Account err := cdc.UnmarshalJSON([]byte(body), &acc) @@ -414,34 +614,35 @@ func getAccount(t *testing.T, port string, addr sdk.Address) auth.Account { return acc } -func doSend(t *testing.T, port, seed, name, password string, addr sdk.Address) (receiveAddr sdk.Address, resultTx ctypes.ResultBroadcastTxCommit) { +func doSend(t *testing.T, port, seed, name, password string, addr sdk.AccAddress) (receiveAddr sdk.AccAddress, resultTx ctypes.ResultBroadcastTxCommit) { // create receive address kb := client.MockKeyBase() - receiveInfo, _, err := kb.Create("receive_address", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) + receiveInfo, _, err := kb.CreateMnemonic("receive_address", cryptoKeys.English, "1234567890", cryptoKeys.SigningAlgo("secp256k1")) require.Nil(t, err) - receiveAddr = receiveInfo.PubKey.Address() - receiveAddrBech := sdk.MustBech32ifyAcc(receiveAddr) + receiveAddr = sdk.AccAddress(receiveInfo.GetPubKey().Address()) acc := getAccount(t, port, addr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() + chainID := viper.GetString(client.FlagChainID) // send + coinbz, err := cdc.MarshalJSON(sdk.NewCoin("steak", 1)) + if err != nil { + panic(err) + } + jsonStr := []byte(fmt.Sprintf(`{ - "name":"%s", + "name":"%s", "password":"%s", - "account_number":%d, - "sequence":%d, - "gas": 10000, - "amount":[ - { - "denom": "%s", - "amount": 1 - } - ] - }`, name, password, accnum, sequence, "steak")) - res, body := Request(t, port, "POST", "/accounts/"+receiveAddrBech+"/send", jsonStr) + "account_number":"%d", + "sequence":"%d", + "gas": "10000", + "amount":[%s], + "chain_id":"%s" + }`, name, password, accnum, sequence, coinbz, chainID)) + res, body := Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send", receiveAddr), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) err = cdc.UnmarshalJSON([]byte(body), &resultTx) @@ -450,13 +651,14 @@ func doSend(t *testing.T, port, seed, name, password string, addr sdk.Address) ( return receiveAddr, resultTx } -func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { +func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.AccAddress) (resultTx ctypes.ResultBroadcastTxCommit) { // create receive address kb := client.MockKeyBase() - receiveInfo, _, err := kb.Create("receive_address", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) + receiveInfo, _, err := kb.CreateMnemonic("receive_address", cryptoKeys.English, "1234567890", cryptoKeys.SigningAlgo("secp256k1")) require.Nil(t, err) - receiveAddr := receiveInfo.PubKey.Address() - receiveAddrBech := sdk.MustBech32ifyAcc(receiveAddr) + receiveAddr := sdk.AccAddress(receiveInfo.GetPubKey().Address()) + + chainID := viper.GetString(client.FlagChainID) // get the account to get the sequence acc := getAccount(t, port, addr) @@ -464,20 +666,21 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Add sequence := acc.GetSequence() // send - jsonStr := []byte(fmt.Sprintf(`{ - "name":"%s", - "password": "%s", - "account_number":%d, - "sequence": %d, - "gas": 100000, + jsonStr := []byte(fmt.Sprintf(`{ + "name":"%s", + "password": "%s", + "account_number":"%d", + "sequence": "%d", + "gas": "100000", + "chain_id": "%s", "amount":[ - { - "denom": "%s", - "amount": 1 + { + "denom": "%s", + "amount": "1" } - ] - }`, name, password, accnum, sequence, "steak")) - res, body := Request(t, port, "POST", "/ibc/testchain/"+receiveAddrBech+"/send", jsonStr) + ] + }`, name, password, accnum, sequence, chainID, "steak")) + res, body := Request(t, port, "POST", fmt.Sprintf("/ibc/testchain/%s/send", receiveAddr), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) err = cdc.UnmarshalJSON([]byte(body), &resultTx) @@ -486,13 +689,19 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Add return resultTx } -func getDelegation(t *testing.T, port string, delegatorAddr, validatorAddr sdk.Address) stake.Delegation { +func getSigningInfo(t *testing.T, port string, validatorAddr sdk.ValAddress) slashing.ValidatorSigningInfo { + res, body := Request(t, port, "GET", fmt.Sprintf("/slashing/signing_info/%s", validatorAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var signingInfo slashing.ValidatorSigningInfo + err := cdc.UnmarshalJSON([]byte(body), &signingInfo) + require.Nil(t, err) + return signingInfo +} - delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr) - validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr) +func getDelegation(t *testing.T, port string, delegatorAddr, validatorAddr sdk.AccAddress) stake.Delegation { // get the account to get the sequence - res, body := Request(t, port, "GET", "/stake/"+delegatorAddrBech+"/bonding_status/"+validatorAddrBech, nil) + res, body := Request(t, port, "GET", fmt.Sprintf("/stake/%s/delegation/%s", delegatorAddr, validatorAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var bond stake.Delegation err := cdc.UnmarshalJSON([]byte(body), &bond) @@ -500,31 +709,34 @@ func getDelegation(t *testing.T, port string, delegatorAddr, validatorAddr sdk.A return bond } -func doBond(t *testing.T, port, seed, name, password string, delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { +func doDelegate(t *testing.T, port, seed, name, password string, delegatorAddr, validatorAddr sdk.AccAddress) (resultTx ctypes.ResultBroadcastTxCommit) { // get the account to get the sequence acc := getAccount(t, port, delegatorAddr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() - delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr) - validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr) + chainID := viper.GetString(client.FlagChainID) // send jsonStr := []byte(fmt.Sprintf(`{ "name": "%s", "password": "%s", - "account_number": %d, - "sequence": %d, - "gas": 10000, - "delegate": [ + "account_number": "%d", + "sequence": "%d", + "gas": "10000", + "chain_id": "%s", + "delegations": [ { "delegator_addr": "%s", "validator_addr": "%s", - "bond": { "denom": "%s", "amount": 60 } + "delegation": { "denom": "%s", "amount": "60" } } ], - "unbond": [] - }`, name, password, accnum, sequence, delegatorAddrBech, validatorAddrBech, "steak")) + "begin_unbondings": [], + "complete_unbondings": [], + "begin_redelegates": [], + "complete_redelegates": [] + }`, name, password, accnum, sequence, chainID, delegatorAddr, validatorAddr, "steak")) res, body := Request(t, port, "POST", "/stake/delegations", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -535,31 +747,77 @@ func doBond(t *testing.T, port, seed, name, password string, delegatorAddr, vali return results[0] } -func doUnbond(t *testing.T, port, seed, name, password string, delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { +func doBeginUnbonding(t *testing.T, port, seed, name, password string, + delegatorAddr, validatorAddr sdk.AccAddress) (resultTx ctypes.ResultBroadcastTxCommit) { + // get the account to get the sequence acc := getAccount(t, port, delegatorAddr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() - delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr) - validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr) + chainID := viper.GetString(client.FlagChainID) // send jsonStr := []byte(fmt.Sprintf(`{ "name": "%s", "password": "%s", - "account_number": %d, - "sequence": %d, - "gas": 10000, - "delegate": [], - "unbond": [ + "account_number": "%d", + "sequence": "%d", + "gas": "10000", + "chain_id": "%s", + "delegations": [], + "begin_unbondings": [ { "delegator_addr": "%s", "validator_addr": "%s", "shares": "30" } - ] - }`, name, password, accnum, sequence, delegatorAddrBech, validatorAddrBech)) + ], + "complete_unbondings": [], + "begin_redelegates": [], + "complete_redelegates": [] + }`, name, password, accnum, sequence, chainID, delegatorAddr, validatorAddr)) + res, body := Request(t, port, "POST", "/stake/delegations", jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var results []ctypes.ResultBroadcastTxCommit + err := cdc.UnmarshalJSON([]byte(body), &results) + require.Nil(t, err) + + return results[0] +} + +func doBeginRedelegation(t *testing.T, port, seed, name, password string, + delegatorAddr, validatorSrcAddr, validatorDstAddr sdk.AccAddress) (resultTx ctypes.ResultBroadcastTxCommit) { + + // get the account to get the sequence + acc := getAccount(t, port, delegatorAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + + chainID := viper.GetString(client.FlagChainID) + + // send + jsonStr := []byte(fmt.Sprintf(`{ + "name": "%s", + "password": "%s", + "account_number": "%d", + "sequence": "%d", + "gas": "10000", + "chain_id": "%s", + "delegations": [], + "begin_unbondings": [], + "complete_unbondings": [], + "begin_redelegates": [ + { + "delegator_addr": "%s", + "validator_src_addr": "%s", + "validator_dst_addr": "%s", + "shares": "30" + } + ], + "complete_redelegates": [] + }`, name, password, accnum, sequence, chainID, delegatorAddr, validatorSrcAddr, validatorDstAddr)) res, body := Request(t, port, "POST", "/stake/delegations", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -579,3 +837,167 @@ func getValidators(t *testing.T, port string) []stakerest.StakeValidatorOutput { require.Nil(t, err) return validators } + +func getProposal(t *testing.T, port string, proposalID int64) gov.ProposalRest { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d", proposalID), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var proposal gov.ProposalRest + err := cdc.UnmarshalJSON([]byte(body), &proposal) + require.Nil(t, err) + return proposal +} + +func getDeposit(t *testing.T, port string, proposalID int64, depositerAddr sdk.AccAddress) gov.DepositRest { + 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.DepositRest + err := cdc.UnmarshalJSON([]byte(body), &deposit) + require.Nil(t, err) + return deposit +} + +func getVote(t *testing.T, port string, proposalID int64, voterAddr sdk.AccAddress) gov.VoteRest { + 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.VoteRest + err := cdc.UnmarshalJSON([]byte(body), &vote) + require.Nil(t, err) + return vote +} + +func getProposalsAll(t *testing.T, port string) []gov.ProposalRest { + res, body := Request(t, port, "GET", "/gov/proposals", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var proposals []gov.ProposalRest + err := cdc.UnmarshalJSON([]byte(body), &proposals) + require.Nil(t, err) + return proposals +} + +func getProposalsFilterDepositer(t *testing.T, port string, depositerAddr sdk.AccAddress) []gov.ProposalRest { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals?depositer=%s", depositerAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var proposals []gov.ProposalRest + err := cdc.UnmarshalJSON([]byte(body), &proposals) + require.Nil(t, err) + return proposals +} + +func getProposalsFilterVoter(t *testing.T, port string, voterAddr sdk.AccAddress) []gov.ProposalRest { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals?voter=%s", voterAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var proposals []gov.ProposalRest + err := cdc.UnmarshalJSON([]byte(body), &proposals) + require.Nil(t, err) + return proposals +} + +func getProposalsFilterVoterDepositer(t *testing.T, port string, voterAddr, depositerAddr sdk.AccAddress) []gov.ProposalRest { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals?depositer=%s&voter=%s", depositerAddr, voterAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var proposals []gov.ProposalRest + err := cdc.UnmarshalJSON([]byte(body), &proposals) + require.Nil(t, err) + return proposals +} + +func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress) (resultTx ctypes.ResultBroadcastTxCommit) { + // get the account to get the sequence + acc := getAccount(t, port, proposerAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + + chainID := viper.GetString(client.FlagChainID) + + // submitproposal + jsonStr := []byte(fmt.Sprintf(`{ + "title": "Test", + "description": "test", + "proposal_type": "Text", + "proposer": "%s", + "initial_deposit": [{ "denom": "steak", "amount": "5" }], + "base_req": { + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d", + "gas":"100000" + } + }`, proposerAddr, name, password, chainID, accnum, sequence)) + res, body := Request(t, port, "POST", "/gov/proposals", jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var results ctypes.ResultBroadcastTxCommit + err := cdc.UnmarshalJSON([]byte(body), &results) + require.Nil(t, err) + + return results +} + +func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID int64) (resultTx ctypes.ResultBroadcastTxCommit) { + // get the account to get the sequence + acc := getAccount(t, port, proposerAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + + chainID := viper.GetString(client.FlagChainID) + + // deposit on proposal + jsonStr := []byte(fmt.Sprintf(`{ + "depositer": "%s", + "amount": [{ "denom": "steak", "amount": "5" }], + "base_req": { + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence": "%d", + "gas":"100000" + } + }`, proposerAddr, name, password, chainID, accnum, sequence)) + res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var results ctypes.ResultBroadcastTxCommit + err := cdc.UnmarshalJSON([]byte(body), &results) + require.Nil(t, err) + + return results +} + +func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID int64) (resultTx ctypes.ResultBroadcastTxCommit) { + // get the account to get the sequence + acc := getAccount(t, port, proposerAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + + chainID := viper.GetString(client.FlagChainID) + + // vote on proposal + jsonStr := []byte(fmt.Sprintf(`{ + "voter": "%s", + "option": "Yes", + "base_req": { + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number": "%d", + "sequence": "%d", + "gas":"100000" + } + }`, proposerAddr, name, password, chainID, accnum, sequence)) + res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/votes", proposalID), jsonStr) + fmt.Println(res) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var results ctypes.ResultBroadcastTxCommit + err := cdc.UnmarshalJSON([]byte(body), &results) + require.Nil(t, err) + + return results +} diff --git a/client/lcd/root.go b/client/lcd/root.go index c3ec75c96..7406a3056 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -4,25 +4,24 @@ import ( "net/http" "os" - "github.com/gorilla/mux" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/tendermint/tmlibs/log" - - tmserver "github.com/tendermint/tendermint/rpc/lib/server" - cmn "github.com/tendermint/tmlibs/common" - client "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" keys "github.com/cosmos/cosmos-sdk/client/keys" rpc "github.com/cosmos/cosmos-sdk/client/rpc" tx "github.com/cosmos/cosmos-sdk/client/tx" - version "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/wire" auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" + gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest" ibc "github.com/cosmos/cosmos-sdk/x/ibc/client/rest" + slashing "github.com/cosmos/cosmos-sdk/x/slashing/client/rest" stake "github.com/cosmos/cosmos-sdk/x/stake/client/rest" + "github.com/gorilla/mux" + "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" ) // ServeCommand will generate a long-running rest server @@ -31,6 +30,7 @@ import ( func ServeCommand(cdc *wire.Codec) *cobra.Command { flagListenAddr := "laddr" flagCORS := "cors" + flagMaxOpenConnections := "max-open" cmd := &cobra.Command{ Use: "rest-server", @@ -38,32 +38,40 @@ func ServeCommand(cdc *wire.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { listenAddr := viper.GetString(flagListenAddr) handler := createHandler(cdc) - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). - With("module", "rest-server") - listener, err := tmserver.StartHTTPServer(listenAddr, handler, logger) + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "rest-server") + maxOpen := viper.GetInt(flagMaxOpenConnections) + + listener, err := tmserver.StartHTTPServer( + listenAddr, handler, logger, + tmserver.Config{MaxOpenConnections: maxOpen}, + ) if err != nil { return err } + logger.Info("REST server started") - // Wait forever and cleanup + // wait forever and cleanup cmn.TrapSignal(func() { err := listener.Close() - logger.Error("Error closing listener", "err", err) + logger.Error("error closing listener", "err", err) }) + return nil }, } - cmd.Flags().StringP(flagListenAddr, "a", "tcp://localhost:1317", "Address for server to listen on") - cmd.Flags().String(flagCORS, "", "Set to domains that can make CORS requests (* for all)") - cmd.Flags().StringP(client.FlagChainID, "c", "", "ID of chain we connect to") - cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") + + cmd.Flags().String(flagListenAddr, "tcp://localhost:1317", "The address for the server to listen on") + cmd.Flags().String(flagCORS, "", "Set the domains that can make CORS requests (* for all)") + cmd.Flags().String(client.FlagChainID, "", "The chain ID to connect to") + cmd.Flags().String(client.FlagNode, "tcp://localhost:26657", "Address of the node to connect to") + cmd.Flags().Int(flagMaxOpenConnections, 1000, "The number of maximum open connections") + return cmd } func createHandler(cdc *wire.Codec) http.Handler { r := mux.NewRouter() - r.HandleFunc("/version", version.RequestHandler).Methods("GET") kb, err := keys.GetKeyBase() //XXX if err != nil { @@ -72,7 +80,10 @@ func createHandler(cdc *wire.Codec) http.Handler { ctx := context.NewCoreContextFromViper() - // TODO make more functional? aka r = keys.RegisterRoutes(r) + // TODO: make more functional? aka r = keys.RegisterRoutes(r) + r.HandleFunc("/version", CLIVersionRequestHandler).Methods("GET") + r.HandleFunc("/node_version", NodeVersionRequestHandler(ctx)).Methods("GET") + keys.RegisterRoutes(r) rpc.RegisterRoutes(ctx, r) tx.RegisterRoutes(ctx, r, cdc) @@ -80,5 +91,8 @@ func createHandler(cdc *wire.Codec) http.Handler { bank.RegisterRoutes(ctx, r, cdc, kb) ibc.RegisterRoutes(ctx, r, cdc, kb) stake.RegisterRoutes(ctx, r, cdc, kb) + slashing.RegisterRoutes(ctx, r, cdc, kb) + gov.RegisterRoutes(ctx, r, cdc) + return r } diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index c1898ff81..5967b2319 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -15,18 +15,18 @@ import ( "github.com/spf13/viper" "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - crkeys "github.com/tendermint/go-crypto/keys" + crkeys "github.com/cosmos/cosmos-sdk/crypto/keys" + abci "github.com/tendermint/tendermint/abci/types" tmcfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/libs/cli" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" nm "github.com/tendermint/tendermint/node" pvm "github.com/tendermint/tendermint/privval" "github.com/tendermint/tendermint/proxy" tmrpc "github.com/tendermint/tendermint/rpc/lib/server" tmtypes "github.com/tendermint/tendermint/types" - "github.com/tendermint/tmlibs/cli" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" @@ -80,33 +80,33 @@ func GetKB(t *testing.T) crkeys.Keybase { } // add an address to the store return name and password -func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (addr sdk.Address, seed string) { +func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (addr sdk.AccAddress, seed string) { var info crkeys.Info var err error - info, seed, err = kb.Create(name, password, crkeys.AlgoEd25519) + info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1) require.NoError(t, err) - addr = info.PubKey.Address() + addr = sdk.AccAddress(info.GetPubKey().Address()) return } // strt TM and the LCD in process, listening on their respective sockets // nValidators = number of validators // initAddrs = accounts to initialize with some steaks -func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.Address) (cleanup func(), validatorsPKs []crypto.PubKey, port string) { +func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress) (cleanup func(), validatorsPKs []crypto.PubKey, port string) { config := GetConfig() - config.Consensus.TimeoutCommit = 1000 + config.Consensus.TimeoutCommit = 100 config.Consensus.SkipTimeoutCommit = false config.TxIndex.IndexAllTags = true logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - logger = log.NewFilter(logger, log.AllowError()) + logger = log.NewFilter(logger, log.AllowDebug()) privValidatorFile := config.PrivValidatorFile() privVal := pvm.LoadOrGenFilePV(privValidatorFile) privVal.Reset() db := dbm.NewMemDB() app := gapp.NewGaiaApp(logger, db) - cdc = gapp.MakeCodec() // XXX + cdc = gapp.MakeCodec() genesisFile := config.GenesisFile() genDoc, err := tmtypes.GenesisDocFromFile(genesisFile) @@ -132,7 +132,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.Address) ( for _, gdValidator := range genDoc.Validators { pk := gdValidator.PubKey validatorsPKs = append(validatorsPKs, pk) // append keys for output - appGenTx, _, _, err := gapp.GaiaAppGenTxNF(cdc, pk, pk.Address(), "test_val1", true) + appGenTx, _, _, err := gapp.GaiaAppGenTxNF(cdc, pk, sdk.AccAddress(pk.Address()), "test_val1") require.NoError(t, err) appGenTxs = append(appGenTxs, appGenTx) } @@ -143,9 +143,10 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.Address) ( // add some tokens to init accounts for _, addr := range initAddrs { accAuth := auth.NewBaseAccountWithAddress(addr) - accAuth.Coins = sdk.Coins{{"steak", 100}} + accAuth.Coins = sdk.Coins{sdk.NewCoin("steak", 100)} acc := gapp.NewGenesisAccount(&accAuth) genesisState.Accounts = append(genesisState.Accounts, acc) + genesisState.StakeData.Pool.LooseTokens += 100 } appState, err := wire.MarshalJSONIndent(cdc, genesisState) @@ -168,7 +169,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.Address) ( //time.Sleep(time.Second) //tests.WaitForHeight(2, port) - tests.WaitForStart(port) + tests.WaitForLCDStart(port) tests.WaitForHeight(1, port) // for use in defer @@ -192,6 +193,7 @@ func startTM(tmcfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc, proxy.NewLocalClientCreator(app), genDocProvider, dbProvider, + nm.DefaultMetricsProvider, logger.With("module", "node")) if err != nil { return nil, err @@ -212,7 +214,7 @@ func startTM(tmcfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc, // start the LCD. note this blocks! func startLCD(logger log.Logger, listenAddr string, cdc *wire.Codec) (net.Listener, error) { handler := createHandler(cdc) - return tmrpc.StartHTTPServer(listenAddr, handler, logger) + return tmrpc.StartHTTPServer(listenAddr, handler, logger, tmrpc.Config{}) } // make a test lcd test request diff --git a/client/lcd/version.go b/client/lcd/version.go new file mode 100644 index 000000000..4e328b7a0 --- /dev/null +++ b/client/lcd/version.go @@ -0,0 +1,28 @@ +package lcd + +import ( + "fmt" + "net/http" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/version" +) + +// cli version REST handler endpoint +func CLIVersionRequestHandler(w http.ResponseWriter, r *http.Request) { + v := version.GetVersion() + w.Write([]byte(v)) +} + +// connected node version REST handler endpoint +func NodeVersionRequestHandler(ctx context.CoreContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + version, err := ctx.Query("/app/version") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Could't query version. Error: %s", err.Error()))) + return + } + w.Write(version) + } +} diff --git a/client/rpc/block.go b/client/rpc/block.go index 693298bb8..3244e8d12 100644 --- a/client/rpc/block.go +++ b/client/rpc/block.go @@ -24,7 +24,7 @@ func BlockCommand() *cobra.Command { Args: cobra.MaximumNArgs(1), RunE: printBlock, } - cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") + cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") // TODO: change this to false when we can cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses") cmd.Flags().StringSlice(flagSelect, []string{"header", "tx"}, "Fields to return (header|txs|results)") diff --git a/client/rpc/root.go b/client/rpc/root.go index e89972c3c..bb5a162a7 100644 --- a/client/rpc/root.go +++ b/client/rpc/root.go @@ -18,7 +18,7 @@ const ( // XXX: remove this when not needed func todoNotImplemented(_ *cobra.Command, _ []string) error { - return errors.New("TODO: Command not yet implemented") + return errors.New("todo: Command not yet implemented") } // AddCommands adds a number of rpc-related subcommands @@ -36,7 +36,7 @@ func initClientCommand() *cobra.Command { RunE: todoNotImplemented, } cmd.Flags().StringP(client.FlagChainID, "c", "", "ID of chain we connect to") - cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") + cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") cmd.Flags().String(flagGenesis, "", "Genesis file to verify header validity") cmd.Flags().String(flagCommit, "", "File with trusted and signed header") cmd.Flags().String(flagValHash, "", "Hash of trusted validator set (hex-encoded)") diff --git a/client/rpc/status.go b/client/rpc/status.go index 70d6628e4..0c1f41593 100644 --- a/client/rpc/status.go +++ b/client/rpc/status.go @@ -18,7 +18,7 @@ func statusCommand() *cobra.Command { Short: "Query remote node for status", RunE: printNodeStatus, } - cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") + cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") return cmd } @@ -82,7 +82,7 @@ func NodeSyncingRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { return } - syncing := status.SyncInfo.Syncing + syncing := status.SyncInfo.CatchingUp if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) diff --git a/client/rpc/validators.go b/client/rpc/validators.go index f8835d737..b8a6c4cc2 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -24,7 +24,7 @@ func ValidatorCommand() *cobra.Command { Args: cobra.MaximumNArgs(1), RunE: printValidators, } - cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") + cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") // TODO: change this to false when we can cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses") return cmd @@ -32,10 +32,10 @@ func ValidatorCommand() *cobra.Command { // Validator output in bech32 format type ValidatorOutput struct { - Address string `json:"address"` // in bech32 - PubKey string `json:"pub_key"` // in bech32 - Accum int64 `json:"accum"` - VotingPower int64 `json:"voting_power"` + Address sdk.ValAddress `json:"address"` // in bech32 + PubKey string `json:"pub_key"` // in bech32 + Accum int64 `json:"accum"` + VotingPower int64 `json:"voting_power"` } // Validators at a certain height output in bech32 format @@ -45,17 +45,13 @@ type ResultValidatorsOutput struct { } func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error) { - bechAddress, err := sdk.Bech32ifyVal(validator.Address) - if err != nil { - return ValidatorOutput{}, err - } bechValPubkey, err := sdk.Bech32ifyValPub(validator.PubKey) if err != nil { return ValidatorOutput{}, err } return ValidatorOutput{ - Address: bechAddress, + Address: sdk.ValAddress(validator.Address), PubKey: bechValPubkey, Accum: validator.Accum, VotingPower: validator.VotingPower, diff --git a/client/tx/broadcast.go b/client/tx/broadcast.go index 7f55e97d7..21f576db4 100644 --- a/client/tx/broadcast.go +++ b/client/tx/broadcast.go @@ -9,7 +9,7 @@ import ( // Tx Broadcast Body type BroadcastTxBody struct { - TxBytes string `json="tx"` + TxBytes string `json:"tx"` } // BroadcastTx REST Handler diff --git a/client/tx/query.go b/client/tx/query.go index 7673dd38d..dfe626c38 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -2,15 +2,16 @@ package tx import ( "encoding/hex" - "encoding/json" "fmt" "net/http" "strconv" + "github.com/tendermint/tendermint/libs/common" + "github.com/gorilla/mux" "github.com/spf13/cobra" "github.com/spf13/viper" - abci "github.com/tendermint/abci/types" + abci "github.com/tendermint/tendermint/abci/types" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/cosmos/cosmos-sdk/client" @@ -42,7 +43,7 @@ func QueryTxCmd(cdc *wire.Codec) *cobra.Command { }, } - cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") + cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") // TODO: change this to false when we can cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses") @@ -70,7 +71,7 @@ func queryTx(cdc *wire.Codec, ctx context.CoreContext, hashHexStr string, trustN return nil, err } - return json.MarshalIndent(info, "", " ") + return wire.MarshalJSONIndent(cdc, info) } func formatTxResult(cdc *wire.Codec, res *ctypes.ResultTx) (txInfo, error) { @@ -81,6 +82,7 @@ func formatTxResult(cdc *wire.Codec, res *ctypes.ResultTx) (txInfo, error) { } info := txInfo{ + Hash: res.Hash, Height: res.Height, Tx: tx, Result: res.TxResult, @@ -90,6 +92,7 @@ func formatTxResult(cdc *wire.Codec, res *ctypes.ResultTx) (txInfo, error) { // txInfo is used to prepare info to display type txInfo struct { + Hash common.HexBytes `json:"hash"` Height int64 `json:"height"` Tx sdk.Tx `json:"tx"` Result abci.ResponseDeliverTx `json:"result"` diff --git a/client/tx/search.go b/client/tx/search.go index 3ab3a3df1..76c394f92 100644 --- a/client/tx/search.go +++ b/client/tx/search.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "strings" "github.com/spf13/cobra" @@ -43,7 +44,7 @@ func SearchTxCmd(cdc *wire.Codec) *cobra.Command { }, } - cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") + cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") // TODO: change this to false once proofs built in cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses") @@ -54,7 +55,7 @@ func SearchTxCmd(cdc *wire.Codec) *cobra.Command { func searchTxs(ctx context.CoreContext, cdc *wire.Codec, tags []string) ([]txInfo, error) { if len(tags) == 0 { - return nil, errors.New("Must declare at least one tag to search") + return nil, errors.New("must declare at least one tag to search") } // XXX: implement ANY query := strings.Join(tags, " AND ") @@ -107,7 +108,12 @@ func SearchTxRequestHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.Han } keyValue := strings.Split(tag, "=") key := keyValue[0] - value := keyValue[1] + value, err := url.QueryUnescape(keyValue[1]) + if err != nil { + w.WriteHeader(400) + w.Write([]byte("Could not decode address: " + err.Error())) + return + } if strings.HasSuffix(key, "_bech32") { bech32address := strings.Trim(value, "'") prefix := strings.Split(bech32address, "1")[0] @@ -118,7 +124,7 @@ func SearchTxRequestHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.Han return } - tag = strings.TrimRight(key, "_bech32") + "='" + sdk.Address(bz).String() + "'" + tag = strings.TrimRight(key, "_bech32") + "='" + sdk.AccAddress(bz).String() + "'" } txs, err := searchTxs(ctx, cdc, []string{tag}) diff --git a/client/tx/sign.go b/client/tx/sign.go index 2d885b049..bedd202b4 100644 --- a/client/tx/sign.go +++ b/client/tx/sign.go @@ -5,15 +5,15 @@ import ( "net/http" keybase "github.com/cosmos/cosmos-sdk/client/keys" - keys "github.com/tendermint/go-crypto/keys" + keys "github.com/cosmos/cosmos-sdk/crypto/keys" ) // REST request body // TODO does this need to be exposed? type SignTxBody struct { - Name string `json="name"` - Password string `json="password"` - TxBytes string `json="tx"` + Name string `json:"name"` + Password string `json:"password"` + TxBytes string `json:"tx"` } // sign transaction REST Handler diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 627eee763..40e868669 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -4,17 +4,18 @@ import ( "encoding/json" "os" - abci "github.com/tendermint/abci/types" + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" tmtypes "github.com/tendermint/tendermint/types" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" bam "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/ibc" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" @@ -36,11 +37,13 @@ type GaiaApp struct { cdc *wire.Codec // keys to access the substores - keyMain *sdk.KVStoreKey - keyAccount *sdk.KVStoreKey - keyIBC *sdk.KVStoreKey - keyStake *sdk.KVStoreKey - keySlashing *sdk.KVStoreKey + keyMain *sdk.KVStoreKey + keyAccount *sdk.KVStoreKey + keyIBC *sdk.KVStoreKey + keyStake *sdk.KVStoreKey + keySlashing *sdk.KVStoreKey + keyGov *sdk.KVStoreKey + keyFeeCollection *sdk.KVStoreKey // Manage getting and setting accounts accountMapper auth.AccountMapper @@ -49,6 +52,7 @@ type GaiaApp struct { ibcMapper ibc.Mapper stakeKeeper stake.Keeper slashingKeeper slashing.Keeper + govKeeper gov.Keeper } func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { @@ -56,20 +60,22 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { // create your application object var app = &GaiaApp{ - BaseApp: bam.NewBaseApp(appName, cdc, logger, db), - cdc: cdc, - keyMain: sdk.NewKVStoreKey("main"), - keyAccount: sdk.NewKVStoreKey("acc"), - keyIBC: sdk.NewKVStoreKey("ibc"), - keyStake: sdk.NewKVStoreKey("stake"), - keySlashing: sdk.NewKVStoreKey("slashing"), + BaseApp: bam.NewBaseApp(appName, cdc, logger, db), + cdc: cdc, + keyMain: sdk.NewKVStoreKey("main"), + keyAccount: sdk.NewKVStoreKey("acc"), + keyIBC: sdk.NewKVStoreKey("ibc"), + keyStake: sdk.NewKVStoreKey("stake"), + keySlashing: sdk.NewKVStoreKey("slashing"), + keyGov: sdk.NewKVStoreKey("gov"), + keyFeeCollection: sdk.NewKVStoreKey("fee"), } // define the accountMapper app.accountMapper = auth.NewAccountMapper( app.cdc, - app.keyAccount, // target store - &auth.BaseAccount{}, // prototype + app.keyAccount, // target store + auth.ProtoBaseAccount, // prototype ) // add handlers @@ -77,20 +83,23 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace)) app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace)) app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.RegisterCodespace(slashing.DefaultCodespace)) + app.govKeeper = gov.NewKeeper(app.cdc, app.keyGov, app.coinKeeper, app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace)) + app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.keyFeeCollection) // register message routes app.Router(). AddRoute("bank", bank.NewHandler(app.coinKeeper)). AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.coinKeeper)). AddRoute("stake", stake.NewHandler(app.stakeKeeper)). - AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)) + AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)). + AddRoute("gov", gov.NewHandler(app.govKeeper)) // initialize BaseApp app.SetInitChainer(app.initChainer) app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) - app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing) + app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing, app.keyGov, app.keyFeeCollection) err := app.LoadLatestVersion(app.keyMain) if err != nil { cmn.Exit(err.Error()) @@ -106,6 +115,7 @@ func MakeCodec() *wire.Codec { bank.RegisterWire(cdc) stake.RegisterWire(cdc) slashing.RegisterWire(cdc) + gov.RegisterWire(cdc) auth.RegisterWire(cdc) sdk.RegisterWire(cdc) wire.RegisterCrypto(cdc) @@ -122,11 +132,15 @@ func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) ab } // application updates every end block +// nolint: unparam func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper) + tags, _ := gov.EndBlocker(ctx, app.govKeeper) + return abci.ResponseEndBlock{ ValidatorUpdates: validatorUpdates, + Tags: tags, } } @@ -150,7 +164,13 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci } // load the initial stake information - stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData) + err = stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData) + if err != nil { + panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 + // return sdk.ErrGenesisParse("").TraceCause(err, "") + } + + gov.InitGenesis(ctx, app.govKeeper, gov.DefaultGenesisState()) return abci.ResponseInitChain{} } diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index 0523c5499..759fc55a6 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -5,7 +5,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/stake" - abci "github.com/tendermint/abci/types" + abci "github.com/tendermint/tendermint/abci/types" ) func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error { diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 558bca38a..0472c46ad 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -5,17 +5,23 @@ import ( "errors" "github.com/spf13/pflag" - "github.com/spf13/viper" - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/crypto" tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/server/config" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/stake" ) +var ( + // bonded tokens given to genesis validators/accounts + freeFermionVal = int64(100) + freeFermionsAcc = int64(50) +) + // State to Unmarshal type GenesisState struct { Accounts []GenesisAccount `json:"accounts"` @@ -24,8 +30,8 @@ type GenesisState struct { // GenesisAccount doesn't need pubkey or sequence type GenesisAccount struct { - Address sdk.Address `json:"address"` - Coins sdk.Coins `json:"coins"` + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` } func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount { @@ -50,25 +56,15 @@ func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount) { } } -var ( - flagName = "name" - flagClientHome = "home-client" - flagOWK = "owk" - - // bonded tokens given to genesis validators/accounts - freeFermionVal = int64(100) - freeFermionsAcc = int64(50) -) - // get app init parameters for server init command func GaiaAppInit() server.AppInit { fsAppGenState := pflag.NewFlagSet("", pflag.ContinueOnError) fsAppGenTx := pflag.NewFlagSet("", pflag.ContinueOnError) - fsAppGenTx.String(flagName, "", "validator moniker, required") - fsAppGenTx.String(flagClientHome, DefaultCLIHome, + fsAppGenTx.String(server.FlagName, "", "validator moniker, required") + fsAppGenTx.String(server.FlagClientHome, DefaultCLIHome, "home directory for the client, used for key generation") - fsAppGenTx.Bool(flagOWK, false, "overwrite the accounts created") + fsAppGenTx.Bool(server.FlagOWK, false, "overwrite the accounts created") return server.AppInit{ FlagsAppGenState: fsAppGenState, @@ -80,24 +76,21 @@ func GaiaAppInit() server.AppInit { // simple genesis tx type GaiaGenTx struct { - Name string `json:"name"` - Address sdk.Address `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` + Name string `json:"name"` + Address sdk.AccAddress `json:"address"` + PubKey string `json:"pub_key"` } // Generate a gaia genesis transaction with flags -func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey) ( +func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey, genTxConfig config.GenTx) ( appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { - clientRoot := viper.GetString(flagClientHome) - overwrite := viper.GetBool(flagOWK) - name := viper.GetString(flagName) - if name == "" { + if genTxConfig.Name == "" { return nil, nil, tmtypes.GenesisValidator{}, errors.New("Must specify --name (validator moniker)") } - var addr sdk.Address + var addr sdk.AccAddress var secret string - addr, secret, err = server.GenerateSaveCoinKey(clientRoot, name, "1234567890", overwrite) + addr, secret, err = server.GenerateSaveCoinKey(genTxConfig.CliRoot, genTxConfig.Name, "1234567890", genTxConfig.Overwrite) if err != nil { return } @@ -107,20 +100,22 @@ func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey) ( if err != nil { return } + cliPrint = json.RawMessage(bz) - appGenTx,_,validator,err = GaiaAppGenTxNF(cdc, pk, addr, name, overwrite) + + appGenTx, _, validator, err = GaiaAppGenTxNF(cdc, pk, addr, genTxConfig.Name) return } // Generate a gaia genesis transaction without flags -func GaiaAppGenTxNF(cdc *wire.Codec, pk crypto.PubKey, addr sdk.Address, name string, overwrite bool) ( +func GaiaAppGenTxNF(cdc *wire.Codec, pk crypto.PubKey, addr sdk.AccAddress, name string) ( appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { var bz []byte gaiaGenTx := GaiaGenTx{ Name: name, Address: addr, - PubKey: pk, + PubKey: sdk.MustBech32ifyAccPub(pk), } bz, err = wire.MarshalJSONIndent(cdc, gaiaGenTx) if err != nil { @@ -160,23 +155,35 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState // create the genesis account, give'm few steaks and a buncha token with there name accAuth := auth.NewBaseAccountWithAddress(genTx.Address) accAuth.Coins = sdk.Coins{ - {genTx.Name + "Token", 1000}, - {"steak", freeFermionsAcc}, + {genTx.Name + "Token", sdk.NewInt(1000)}, + {"steak", sdk.NewInt(freeFermionsAcc)}, } acc := NewGenesisAccount(&accAuth) genaccs[i] = acc - stakeData.Pool.LooseUnbondedTokens += freeFermionsAcc // increase the supply + stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens + freeFermionsAcc // increase the supply // add the validator if len(genTx.Name) > 0 { desc := stake.NewDescription(genTx.Name, "", "", "") - validator := stake.NewValidator(genTx.Address, genTx.PubKey, desc) - validator.PoolShares = stake.NewBondedShares(sdk.NewRat(freeFermionVal)) + validator := stake.NewValidator(genTx.Address, + sdk.MustGetAccPubKeyBech32(genTx.PubKey), desc) + + stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens + freeFermionVal // increase the supply + + // add some new shares to the validator + var issuedDelShares sdk.Rat + validator, stakeData.Pool, issuedDelShares = validator.AddTokensFromDel(stakeData.Pool, freeFermionVal) stakeData.Validators = append(stakeData.Validators, validator) - // pool logic - stakeData.Pool.BondedTokens += freeFermionVal - stakeData.Pool.BondedShares = sdk.NewRat(stakeData.Pool.BondedTokens) + // create the self-delegation from the issuedDelShares + delegation := stake.Delegation{ + DelegatorAddr: validator.Owner, + ValidatorAddr: validator.Owner, + Shares: issuedDelShares, + Height: 0, + } + + stakeData.Bonds = append(stakeData.Bonds, delegation) } } diff --git a/cmd/gaia/app/genesis_test.go b/cmd/gaia/app/genesis_test.go index 03ff46e50..d0e080376 100644 --- a/cmd/gaia/app/genesis_test.go +++ b/cmd/gaia/app/genesis_test.go @@ -5,16 +5,16 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/stretchr/testify/assert" - crypto "github.com/tendermint/go-crypto" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" ) func TestToAccount(t *testing.T) { priv := crypto.GenPrivKeyEd25519() - addr := sdk.Address(priv.PubKey().Address()) + addr := sdk.AccAddress(priv.PubKey().Address()) authAcc := auth.NewBaseAccountWithAddress(addr) genAcc := NewGenesisAccount(&authAcc) - assert.Equal(t, authAcc, *genAcc.ToAccount()) + require.Equal(t, authAcc, *genAcc.ToAccount()) } func TestGaiaAppGenTx(t *testing.T) { diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 3eee20c85..38a347b24 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -3,11 +3,14 @@ package clitest import ( "encoding/json" "fmt" + "os" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/server" @@ -15,140 +18,225 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/stake" - crypto "github.com/tendermint/go-crypto" ) -func TestGaiaCLISend(t *testing.T) { +var ( + pass = "1234567890" + gaiadHome = "" + gaiacliHome = "" +) - tests.ExecuteT(t, "gaiad unsafe_reset_all") - pass := "1234567890" - executeWrite(t, "gaiacli keys delete foo", pass) - executeWrite(t, "gaiacli keys delete bar", pass) - chainID := executeInit(t, "gaiad init -o --name=foo") - executeWrite(t, "gaiacli keys add bar", pass) +func init() { + gaiadHome, gaiacliHome = getTestingHomeDirs() +} + +func TestGaiaCLISend(t *testing.T) { + tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe_reset_all", gaiadHome)) + executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s foo", gaiacliHome), pass) + executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), pass) + 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 bar", gaiacliHome), pass) // get a free port, also setup some common flags servAddr, port, err := server.FreeTCPAddr() require.NoError(t, err) - flags := fmt.Sprintf("--node=%v --chain-id=%v", servAddr, chainID) + flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) // start gaiad server - proc := tests.GoExecuteT(t, fmt.Sprintf("gaiad start --rpc.laddr=%v", servAddr)) + proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr)) + defer proc.Stop(false) - tests.WaitForStart(port) - - fooAddr, _ := executeGetAddrPK(t, "gaiacli keys show foo --output=json") - fooCech, err := sdk.Bech32ifyAcc(fooAddr) - require.NoError(t, err) - barAddr, _ := executeGetAddrPK(t, "gaiacli keys show bar --output=json") - barCech, err := sdk.Bech32ifyAcc(barAddr) - require.NoError(t, err) - - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags)) - assert.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak")) - - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barCech), pass) + tests.WaitForTMStart(port) tests.WaitForNextHeightTM(port) - barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) - assert.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak")) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags)) - assert.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak")) + fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) + barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) + + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) + + executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), pass) + tests.WaitForNextHeightTM(port) + + barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + require.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak").Int64()) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) // test autosequencing - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barCech), pass) + executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), pass) tests.WaitForNextHeightTM(port) - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) - assert.Equal(t, int64(20), barAcc.GetCoins().AmountOf("steak")) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags)) - assert.Equal(t, int64(30), fooAcc.GetCoins().AmountOf("steak")) + barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + require.Equal(t, int64(20), barAcc.GetCoins().AmountOf("steak").Int64()) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + require.Equal(t, int64(30), fooAcc.GetCoins().AmountOf("steak").Int64()) + + // test memo + executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo --memo 'testmemo'", flags, barAddr), pass) + tests.WaitForNextHeightTM(port) + + barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + require.Equal(t, int64(30), barAcc.GetCoins().AmountOf("steak").Int64()) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + require.Equal(t, int64(20), fooAcc.GetCoins().AmountOf("steak").Int64()) } func TestGaiaCLICreateValidator(t *testing.T) { - - tests.ExecuteT(t, "gaiad unsafe_reset_all") - pass := "1234567890" - executeWrite(t, "gaiacli keys delete foo", pass) - executeWrite(t, "gaiacli keys delete bar", pass) - chainID := executeInit(t, "gaiad init -o --name=foo") - executeWrite(t, "gaiacli keys add bar", pass) + tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe_reset_all", gaiadHome)) + executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s foo", gaiacliHome), pass) + executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), pass) + 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 bar", gaiacliHome), pass) // get a free port, also setup some common flags servAddr, port, err := server.FreeTCPAddr() require.NoError(t, err) - flags := fmt.Sprintf("--node=%v --chain-id=%v", servAddr, chainID) + flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) // start gaiad server - proc := tests.GoExecuteT(t, fmt.Sprintf("gaiad start --rpc.laddr=%v", servAddr)) + proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr)) + defer proc.Stop(false) - tests.WaitForStart(port) - - fooAddr, _ := executeGetAddrPK(t, "gaiacli keys show foo --output=json") - fooCech, err := sdk.Bech32ifyAcc(fooAddr) - require.NoError(t, err) - barAddr, barPubKey := executeGetAddrPK(t, "gaiacli keys show bar --output=json") - barCech, err := sdk.Bech32ifyAcc(barAddr) - require.NoError(t, err) - barCeshPubKey, err := sdk.Bech32ifyValPub(barPubKey) - require.NoError(t, err) - - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barCech), pass) + tests.WaitForTMStart(port) tests.WaitForNextHeightTM(port) - barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) - assert.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak")) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags)) - assert.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak")) + fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) + barAddr, barPubKey := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) + barCeshPubKey := sdk.MustBech32ifyValPub(barPubKey) + + executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), pass) + tests.WaitForNextHeightTM(port) + + barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + require.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak").Int64()) + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) // create validator cvStr := fmt.Sprintf("gaiacli stake create-validator %v", flags) - cvStr += fmt.Sprintf(" --name=%v", "bar") - cvStr += fmt.Sprintf(" --address-validator=%v", barCech) - cvStr += fmt.Sprintf(" --pubkey=%v", barCeshPubKey) + cvStr += fmt.Sprintf(" --from=%s", "bar") + cvStr += fmt.Sprintf(" --address-validator=%s", barAddr) + cvStr += fmt.Sprintf(" --pubkey=%s", barCeshPubKey) cvStr += fmt.Sprintf(" --amount=%v", "2steak") cvStr += fmt.Sprintf(" --moniker=%v", "bar-vally") executeWrite(t, cvStr, pass) tests.WaitForNextHeightTM(port) - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) - require.Equal(t, int64(8), barAcc.GetCoins().AmountOf("steak"), "%v", barAcc) + barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + require.Equal(t, int64(8), barAcc.GetCoins().AmountOf("steak").Int64(), "%v", barAcc) - validator := executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %v --output=json %v", barCech, flags)) - assert.Equal(t, validator.Owner, barAddr) - assert.Equal(t, "2/1", validator.PoolShares.Amount.String()) + validator := executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %s --output=json %v", barAddr, flags)) + require.Equal(t, validator.Owner, barAddr) + require.Equal(t, "2/1", validator.PoolShares.Amount.String()) // unbond a single share - unbondStr := fmt.Sprintf("gaiacli stake unbond %v", flags) - unbondStr += fmt.Sprintf(" --name=%v", "bar") - unbondStr += fmt.Sprintf(" --address-validator=%v", barCech) - unbondStr += fmt.Sprintf(" --address-delegator=%v", barCech) - unbondStr += fmt.Sprintf(" --shares=%v", "1") - unbondStr += fmt.Sprintf(" --sequence=%v", "1") - t.Log(fmt.Sprintf("debug unbondStr: %v\n", unbondStr)) + unbondStr := fmt.Sprintf("gaiacli stake unbond begin %v", flags) + unbondStr += fmt.Sprintf(" --from=%s", "bar") + unbondStr += fmt.Sprintf(" --address-validator=%s", barAddr) + unbondStr += fmt.Sprintf(" --address-delegator=%s", barAddr) + unbondStr += fmt.Sprintf(" --shares-amount=%v", "1") - executeWrite(t, unbondStr, pass) + success := executeWrite(t, unbondStr, pass) + require.True(t, success) tests.WaitForNextHeightTM(port) + /* // this won't be what we expect because we've only started unbonding, haven't completed barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) - require.Equal(t, int64(9), barAcc.GetCoins().AmountOf("steak"), "%v", barAcc) - validator = executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %v --output=json %v", barCech, flags)) - assert.Equal(t, "1/1", validator.PoolShares.Amount.String()) + require.Equal(t, int64(9), barAcc.GetCoins().AmountOf("steak").Int64(), "%v", barAcc) + */ + validator = executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %s --output=json %v", barAddr, flags)) + require.Equal(t, "1/1", validator.PoolShares.Amount.String()) +} + +func TestGaiaCLISubmitProposal(t *testing.T) { + tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe_reset_all", gaiadHome)) + executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s foo", gaiacliHome), pass) + executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), pass) + 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 bar", gaiacliHome), pass) + + // get a free port, also setup some common flags + servAddr, port, err := server.FreeTCPAddr() + require.NoError(t, err) + 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)) + + defer proc.Stop(false) + tests.WaitForTMStart(port) + tests.WaitForNextHeightTM(port) + + fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) + + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) + + executeWrite(t, fmt.Sprintf("gaiacli gov submit-proposal %v --proposer=%s --deposit=5steak --type=Text --title=Test --description=test --from=foo", flags, fooAddr), pass) + tests.WaitForNextHeightTM(port) + + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + require.Equal(t, int64(45), fooAcc.GetCoins().AmountOf("steak").Int64()) + + proposal1 := executeGetProposal(t, fmt.Sprintf("gaiacli gov query-proposal --proposalID=1 --output=json %v", flags)) + require.Equal(t, int64(1), proposal1.ProposalID) + require.Equal(t, gov.StatusToString(gov.StatusDepositPeriod), proposal1.Status) + + executeWrite(t, fmt.Sprintf("gaiacli gov deposit %v --depositer=%s --deposit=10steak --proposalID=1 --from=foo", flags, fooAddr), pass) + tests.WaitForNextHeightTM(port) + + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + require.Equal(t, int64(35), fooAcc.GetCoins().AmountOf("steak").Int64()) + proposal1 = executeGetProposal(t, fmt.Sprintf("gaiacli gov query-proposal --proposalID=1 --output=json %v", flags)) + require.Equal(t, int64(1), proposal1.ProposalID) + require.Equal(t, gov.StatusToString(gov.StatusVotingPeriod), proposal1.Status) + + executeWrite(t, fmt.Sprintf("gaiacli gov vote %v --proposalID=1 --voter=%s --option=Yes --from=foo", flags, fooAddr), pass) + tests.WaitForNextHeightTM(port) + + vote := executeGetVote(t, fmt.Sprintf("gaiacli gov query-vote --proposalID=1 --voter=%s --output=json %v", fooAddr, flags)) + require.Equal(t, int64(1), vote.ProposalID) + require.Equal(t, gov.VoteOptionToString(gov.OptionYes), vote.Option) +} + +//___________________________________________________________________________________ +// helper methods + +func getTestingHomeDirs() (string, string) { + tmpDir := os.TempDir() + gaiadHome := fmt.Sprintf("%s%s.test_gaiad", tmpDir, string(os.PathSeparator)) + gaiacliHome := fmt.Sprintf("%s%s.test_gaiacli", tmpDir, string(os.PathSeparator)) + return gaiadHome, gaiacliHome } //___________________________________________________________________________________ // executors -func executeWrite(t *testing.T, cmdStr string, writes ...string) { +func executeWrite(t *testing.T, cmdStr string, writes ...string) bool { proc := tests.GoExecuteT(t, cmdStr) for _, write := range writes { _, err := proc.StdinPipe.Write([]byte(write + "\n")) require.NoError(t, err) } + stdout, stderr, err := proc.ReadAll() + if err != nil { + fmt.Println("Err on proc.ReadAll()", err, cmdStr) + } + // Log output. + if len(stdout) > 0 { + t.Log("Stdout:", cmn.Green(string(stdout))) + } + if len(stderr) > 0 { + t.Log("Stderr:", cmn.Red(string(stderr))) + } + proc.Wait() + return proc.ExitState.Success() // bz := proc.StdoutBuffer.Bytes() // fmt.Println("EXEC WRITE", string(bz)) } @@ -166,18 +254,15 @@ func executeInit(t *testing.T, cmdStr string) (chainID string) { return } -func executeGetAddrPK(t *testing.T, cmdStr string) (sdk.Address, crypto.PubKey) { +func executeGetAddrPK(t *testing.T, cmdStr string) (sdk.AccAddress, crypto.PubKey) { out := tests.ExecuteT(t, cmdStr) var ko keys.KeyOutput keys.UnmarshalJSON([]byte(out), &ko) - address, err := sdk.GetAccAddressBech32(ko.Address) - require.NoError(t, err) - pk, err := sdk.GetAccPubKeyBech32(ko.PubKey) require.NoError(t, err) - return address, pk + return ko.Address, pk } func executeGetAccount(t *testing.T, cmdStr string) auth.BaseAccount { @@ -202,3 +287,21 @@ func executeGetValidator(t *testing.T, cmdStr string) stake.Validator { require.NoError(t, err, "out %v\n, err %v", out, err) return validator } + +func executeGetProposal(t *testing.T, cmdStr string) gov.ProposalRest { + out := tests.ExecuteT(t, cmdStr) + var proposal gov.ProposalRest + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &proposal) + require.NoError(t, err, "out %v\n, err %v", out, err) + return proposal +} + +func executeGetVote(t *testing.T, cmdStr string) gov.VoteRest { + out := tests.ExecuteT(t, cmdStr) + var vote gov.VoteRest + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &vote) + require.NoError(t, err, "out %v\n, err %v", out, err) + return vote +} diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index 2e24842e3..4ab5d02b9 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -3,7 +3,7 @@ package main import ( "github.com/spf13/cobra" - "github.com/tendermint/tmlibs/cli" + "github.com/tendermint/tendermint/libs/cli" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/keys" @@ -13,6 +13,7 @@ import ( "github.com/cosmos/cosmos-sdk/version" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" + govcmd "github.com/cosmos/cosmos-sdk/x/gov/client/cli" ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/client/cli" slashingcmd "github.com/cosmos/cosmos-sdk/x/slashing/client/cli" stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli" @@ -94,13 +95,34 @@ func main() { stakecmd.GetCmdCreateValidator(cdc), stakecmd.GetCmdEditValidator(cdc), stakecmd.GetCmdDelegate(cdc), - stakecmd.GetCmdUnbond(cdc), + stakecmd.GetCmdUnbond("stake", cdc), + stakecmd.GetCmdRedelegate("stake", cdc), slashingcmd.GetCmdUnrevoke(cdc), )...) rootCmd.AddCommand( stakeCmd, ) + //Add stake commands + govCmd := &cobra.Command{ + Use: "gov", + Short: "Governance and voting subcommands", + } + govCmd.AddCommand( + client.GetCommands( + govcmd.GetCmdQueryProposal("gov", cdc), + govcmd.GetCmdQueryVote("gov", cdc), + )...) + govCmd.AddCommand( + client.PostCommands( + govcmd.GetCmdSubmitProposal(cdc), + govcmd.GetCmdDeposit(cdc), + govcmd.GetCmdVote(cdc), + )...) + rootCmd.AddCommand( + govCmd, + ) + //Add auth and bank commands rootCmd.AddCommand( client.GetCommands( @@ -120,5 +142,9 @@ func main() { // prepare and add flags executor := cli.PrepareMainCmd(rootCmd, "GA", app.DefaultCLIHome) - executor.Execute() + err := executor.Execute() + if err != nil { + // handle with #870 + panic(err) + } } diff --git a/cmd/gaia/cmd/gaiad/main.go b/cmd/gaia/cmd/gaiad/main.go index 5d0eb9030..9c8434eb0 100644 --- a/cmd/gaia/cmd/gaiad/main.go +++ b/cmd/gaia/cmd/gaiad/main.go @@ -5,11 +5,11 @@ import ( "github.com/spf13/cobra" - abci "github.com/tendermint/abci/types" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/cli" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" tmtypes "github.com/tendermint/tendermint/types" - "github.com/tendermint/tmlibs/cli" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/server" @@ -31,7 +31,11 @@ func main() { // prepare and add flags executor := cli.PrepareBaseCmd(rootCmd, "GA", app.DefaultNodeHome) - executor.Execute() + err := executor.Execute() + if err != nil { + // handle with #870 + panic(err) + } } func newApp(logger log.Logger, db dbm.DB) abci.Application { diff --git a/cmd/gaia/cmd/gaiadebug/hack.go b/cmd/gaia/cmd/gaiadebug/hack.go index 2c84184bf..121f437a3 100644 --- a/cmd/gaia/cmd/gaiadebug/hack.go +++ b/cmd/gaia/cmd/gaiadebug/hack.go @@ -8,11 +8,11 @@ import ( "path" "github.com/spf13/cobra" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" bam "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" @@ -157,8 +157,8 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { // define the accountMapper app.accountMapper = auth.NewAccountMapper( app.cdc, - app.keyAccount, // target store - &auth.BaseAccount{}, // prototype + app.keyAccount, // target store + auth.ProtoBaseAccount, // prototype ) // add handlers @@ -197,6 +197,7 @@ func MakeCodec() *wire.Codec { auth.RegisterWire(cdc) sdk.RegisterWire(cdc) wire.RegisterCrypto(cdc) + cdc.Seal() return cdc } @@ -210,6 +211,7 @@ func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) ab } // application updates every end block +// nolint: unparam func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper) @@ -226,8 +228,7 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci var genesisState gaia.GenesisState err := app.cdc.UnmarshalJSON(stateJSON, &genesisState) if err != nil { - panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 - // return sdk.ErrGenesisParse("").TraceCause(err, "") + panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 // return sdk.ErrGenesisParse("").TraceCause(err, "") } // load the accounts @@ -237,7 +238,10 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci } // load the initial stake information - stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData) - return abci.ResponseInitChain{} + err = stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData) + if err != nil { + panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 // return sdk.ErrGenesisParse("").TraceCause(err, "") + } + return abci.ResponseInitChain{} } diff --git a/cmd/gaia/cmd/gaiadebug/main.go b/cmd/gaia/cmd/gaiadebug/main.go index 79045c07b..bff79870b 100644 --- a/cmd/gaia/cmd/gaiadebug/main.go +++ b/cmd/gaia/cmd/gaiadebug/main.go @@ -11,14 +11,16 @@ import ( "strings" gaia "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/spf13/cobra" - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/crypto" ) func init() { rootCmd.AddCommand(txCmd) rootCmd.AddCommand(pubkeyCmd) + rootCmd.AddCommand(addrCmd) rootCmd.AddCommand(hackCmd) rootCmd.AddCommand(rawBytesCmd) } @@ -37,10 +39,16 @@ var txCmd = &cobra.Command{ var pubkeyCmd = &cobra.Command{ Use: "pubkey", - Short: "Decode a pubkey from hex or base64", + Short: "Decode a pubkey from hex, base64, or bech32", RunE: runPubKeyCmd, } +var addrCmd = &cobra.Command{ + Use: "addr", + Short: "Convert an address between hex and bech32", + RunE: runAddrCmd, +} + var hackCmd = &cobra.Command{ Use: "hack", Short: "Boilerplate to Hack on an existing state by scripting some Go...", @@ -80,30 +88,98 @@ func runPubKeyCmd(cmd *cobra.Command, args []string) error { } pubkeyString := args[0] + var pubKeyI crypto.PubKey - // try hex, then base64 + // try hex, then base64, then bech32 pubkeyBytes, err := hex.DecodeString(pubkeyString) if err != nil { var err2 error pubkeyBytes, err2 = base64.StdEncoding.DecodeString(pubkeyString) if err2 != nil { - return fmt.Errorf(`Expected hex or base64. Got errors: + var err3 error + pubKeyI, err3 = sdk.GetAccPubKeyBech32(pubkeyString) + if err3 != nil { + var err4 error + pubKeyI, err4 = sdk.GetValPubKeyBech32(pubkeyString) + + if err4 != nil { + return fmt.Errorf(`Expected hex, base64, or bech32. Got errors: hex: %v, base64: %v - `, err, err2) + bech32 acc: %v + bech32 val: %v + `, err, err2, err3, err4) + + } + } + } } - cdc := gaia.MakeCodec() var pubKey crypto.PubKeyEd25519 - copy(pubKey[:], pubkeyBytes) + if pubKeyI == nil { + copy(pubKey[:], pubkeyBytes) + } else { + pubKey = pubKeyI.(crypto.PubKeyEd25519) + pubkeyBytes = pubKey[:] + } + + cdc := gaia.MakeCodec() pubKeyJSONBytes, err := cdc.MarshalJSON(pubKey) if err != nil { return err } + accPub, err := sdk.Bech32ifyAccPub(pubKey) + if err != nil { + return err + } + valPub, err := sdk.Bech32ifyValPub(pubKey) + if err != nil { + return err + } fmt.Println("Address:", pubKey.Address()) fmt.Printf("Hex: %X\n", pubkeyBytes) fmt.Println("JSON (base64):", string(pubKeyJSONBytes)) + fmt.Println("Bech32 Acc:", accPub) + fmt.Println("Bech32 Val:", valPub) + return nil +} + +func runAddrCmd(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("Expected single arg") + } + + addrString := args[0] + var addr []byte + + // try hex, then bech32 + var err error + addr, err = hex.DecodeString(addrString) + if err != nil { + var err2 error + addr, err2 = sdk.AccAddressFromBech32(addrString) + if err2 != nil { + var err3 error + addr, err3 = sdk.ValAddressFromBech32(addrString) + + if err3 != nil { + return fmt.Errorf(`Expected hex or bech32. Got errors: + hex: %v, + bech32 acc: %v + bech32 val: %v + `, err, err2, err3) + + } + } + } + + accAddr := sdk.AccAddress(addr) + valAddr := sdk.ValAddress(addr) + + fmt.Println("Address:", addr) + fmt.Println("Bech32 Acc:", accAddr) + fmt.Println("Bech32 Val:", valAddr) return nil } diff --git a/cmd/gaia/testnets/README.md b/cmd/gaia/testnets/README.md index b65feeb91..86dc62e74 100644 --- a/cmd/gaia/testnets/README.md +++ b/cmd/gaia/testnets/README.md @@ -1,10 +1,9 @@ # Connect to the `gaia-6002` Testnet -Note: We are aware this documentation is sub-par. We are working to -improve the tooling and the documentation to make this process as painless as -possible. In the meantime, join the -[Validator Chat](https://riot.im/app/#/room/#cosmos_validators:matrix.org) -for technical support. Thanks very much for your patience. :) +Note: We are aware this documentation is a work in progress. We are actively +working to improve the tooling and the documentation to make this process as painless as +possible. In the meantime, join the [Validator Chat](https://riot.im/app/#/room/#cosmos_validators:matrix.org) +for technical support, and [open issues](https://github.com/cosmos/cosmos-sdk) if you run into any! Thanks very much for your patience and support. :) ## Setting Up a New Node @@ -13,13 +12,20 @@ These instructions are for setting up a brand new full node from scratch. If you ### Install Go Install `go` by following the [official docs](https://golang.org/doc/install). -**Go 1.10+** is required for the Cosmos SDK. +**Go 1.10+** is required for the Cosmos SDK. Remember to properly setup your `$GOPATH`, `$GOBIN`, and `$PATH` variables, for example: + +```bash +mkdir -p $HOME/go/bin +echo "export GOPATH=$HOME/go" >> ~/.bash_profile +echo "export GOBIN=$GOPATH/bin" >> ~/.bash_profile +echo "export PATH=$PATH:$GOBIN" >> ~/.bash_profile +``` ### Install Cosmos SDK -Next, let's install the testnet's version of the Cosmos SDK. +Next, let's install the testnet's version of the Cosmos SDK. -``` +```bash mkdir -p $GOPATH/src/github.com/cosmos cd $GOPATH/src/github.com/cosmos git clone https://github.com/cosmos/cosmos-sdk @@ -29,22 +35,27 @@ make get_tools && make get_vendor_deps && make install That will install the `gaiad` and `gaiacli` binaries. Verify that everything is OK: -``` -gaiad version -0.19.0- +```bash +$ gaiad version +0.19.0-c6711810 + +$ gaiacli version +0.19.0-c6711810 ``` ### Node Setup -Create the required configuration files: +Create the required configuration files, and initialize the node: -``` -gaiad init +```bash +gaiad init --name ``` -Name your node by editing the `moniker` in `$HOME/.gaiad/config/config.toml`. Note that only ASCII characters are supported. Using Unicode renders your node unconnectable. +> *NOTE:* Note that only ASCII characters are supported for the `--name`. Using Unicode renders your node unreachable. -``` +You can also edit this `name` in the `~/.gaiad/config/config.toml` file: + +```toml # A custom human readable name for this node moniker = "" ``` @@ -59,12 +70,12 @@ These instructions are for full nodes that have ran on previous testnets and wou First, remove the outdated files and reset the data. -``` +```bash rm $HOME/.gaiad/config/addrbook.json $HOME/.gaiad/config/genesis.json gaiad unsafe_reset_all ``` -Your node is now in a pristine state while keeping the original `priv_validator.json` and `config.toml`. If you had any sentry nodes or full nodes setup before, +Your node is now in a pristine state while keeping the original `priv_validator.json` and `config.toml`. If you had any sentry nodes or full nodes setup before, your node will still try to connect to them, but may fail if they haven't also been upgraded. @@ -74,7 +85,7 @@ been upgraded. Now it is time to upgrade the software: -``` +```bash cd $GOPATH/src/github.com/cosmos/cosmos-sdk git fetch --all && git checkout v0.19.0 make update_tools && make get_vendor_deps && make install @@ -88,171 +99,367 @@ Your full node has been cleanly upgraded! Copy the testnet's `genesis.json` file and place it in `gaiad`'s config directory. -``` +```bash mkdir -p $HOME/.gaiad/config cp -a $GOPATH/src/github.com/cosmos/cosmos-sdk/cmd/gaia/testnets/gaia-6002/genesis.json $HOME/.gaiad/config/genesis.json ``` ### Add Seed Nodes -Your node needs to know how to find peers. You'll need to add healthy seed nodes to `$HOME/.gaiad/config/config.toml`. Here are some seed nodes you can use: +Your node needs to know how to find peers. You'll need to add healthy seed nodes to `$HOME/.gaiad/config/config.toml`. Here are some seed nodes you can use: -``` +```toml # Comma separated list of seed nodes to connect to -seeds = "38aa9bec3998f12ae9088b21a2d910d19d565c27@gaia-6002.coinculture.net:46656,80a35a46ce09cfb31ee220c8141a25e73e0b239b@seed.cosmos.cryptium.ch:46656,80a35a46ce09cfb31ee220c8141a25e73e0b239b@35.198.166.171:46656,032fa56301de335d835057fb6ad9f7ce2242a66d@165.227.236.213:46656" +seeds = "38aa9bec3998f12ae9088b21a2d910d19d565c27@gaia-6002.coinculture.net:46656,1e124dd15bd9955a7ea844ab003b1b47f0998b70@seed.cosmos.cryptium.ch:46656" ``` -You can also [ask other validators](https://riot.im/app/#/room/#cosmos_validators:matrix.org) for a persistent peer and add it under the `persistent_peers` key. For more information on seeds and peers, [read this](https://github.com/tendermint/tendermint/blob/develop/docs/using-tendermint.md#peers). +If those seeds aren't working, you can find more seeds and persistent peers on the [Cosmos Explorer](https://explorecosmos.network/nodes). Open the the `Full Nodes` pane and select nodes that do not have private (`10.x.x.x`) or [local IP addresses](https://en.wikipedia.org/wiki/Private_network). The `Persistent Peer` field contains the connection string. For best results use 4-6. + +For more information on seeds and peers, [read this](https://github.com/tendermint/tendermint/blob/develop/docs/using-tendermint.md#peers). ## Run a Full Node Start the full node with this command: -``` +```bash gaiad start ``` Check that everything is running smoothly: -``` +```bash gaiacli status ``` View the status of the network with the [Cosmos Explorer](https://explorecosmos.network). Once your full node syncs up to the current block height, you should see it appear on the [list of full nodes](https://explorecosmos.network/validators). If it doesn't show up, that's ok--the Explorer does not connect to every node. -## Generate Keys +## Generating Keys -You'll need a private and public key pair \(a.k.a. `sk, pk` respectively\) to be able to receive funds, send txs, bond tx, etc. +### A Note on Keys in Cosmos: + +There are three types of key representations that are used in this tutorial: + +- `cosmosaccaddr` + * Derived from account keys generated by `gaiacli keys add` + * Used to receive funds + * e.g. `cosmosaccaddr15h6vd5f0wqps26zjlwrc6chah08ryu4hzzdwhc` + +- `cosmosaccpub` + * Derived from account keys generated by `gaiacli keys add` + * e.g. `cosmosaccpub1zcjduc3q7fu03jnlu2xpl75s2nkt7krm6grh4cc5aqth73v0zwmea25wj2hsqhlqzm` + +- `cosmosvalpub` + * Generated when the node is created with `gaiad init`. + * Get this value with `gaiad tendermint show_validator` + * e.g. `cosmosvalpub1zcjduc3qcyj09qc03elte23zwshdx92jm6ce88fgc90rtqhjx8v0608qh5ssp0w94c` + +### Key Generation + +You'll need an account private and public key pair \(a.k.a. `sk, pk` respectively\) to be able to receive funds, send txs, bond tx, etc. To generate a new key \(default _ed25519_ elliptic curve\): -``` -gaiacli keys add +```bash +gaiacli keys add ``` -Next, you will have to create a passphrase. Save the _seed_ _phrase_ in a safe place in case you forget the password. +Next, you will have to create a passphrase to protect the key on disk. The output of the above command will contain a _seed phrase_. Save the _seed phrase_ in a safe place in case you forget the password! -If you check your private keys, you'll now see ``: +If you check your private keys, you'll now see ``: -``` -gaiacli keys show +```bash +gaiacli keys show ``` You can see all your available keys by typing: -``` +```bash gaiacli keys list ``` View the validator pubkey for your node by typing: -``` +```bash gaiad tendermint show_validator ``` -Save your address and pubkey to environment variables for later use: - -``` -MYADDR= -MYPUBKEY= -``` - **WARNING:** We strongly recommend NOT using the same passphrase for multiple keys. The Tendermint team and the Interchain Foundation will not be responsible for the loss of funds. -## Get Tokens +## Fund your account -The best way to get tokens is from the [Cosmos Testnet Faucet](https://faucetcosmos.network). If the faucet is not working for you, try asking [#cosmos-validators](https://riot.im/app/#/room/#cosmos-validators:matrix.org). +The best way to get tokens is from the [Cosmos Testnet Faucet](https://faucetcosmos.network). If the faucet is not working for you, try asking [#cosmos-validators](https://riot.im/app/#/room/#cosmos-validators:matrix.org). The faucet needs the `cosmosaccaddr` from the account you wish to use for staking. -After receiving tokens to your address, you can view your account's balance by typing: +After receiving tokens to your address, you can view your account's balance by typing: -``` -gaiacli account +```bash +gaiacli account ``` -Note: When you query an account balance with zero tokens, you will get this error: `No account with address was found in the state.` This is expected! We're working on improving our error messages. - -## Send Tokens - -``` -gaiacli send --amount=10faucetToken --chain-id= --name= --to= -``` - -Note: The `--amount` flag accepts the format `--amount=`. - -Now, view the updated balances of the origin and destination accounts: - -``` -gaiacli account -gaiacli account -``` - -You can also check your balance at a given block by using the `--block` flag: - -``` -gaiacli account --block= -``` +> _*Note:*_ When you query an account balance with zero tokens, you will get this error: `No account with address was found in the state.` This can also happen if you fund the account before your node has fully synced with the chain. These are both normal. Also, we're working on improving our error messages! ## Run a Validator Node [Validators](https://cosmos.network/validators) are responsible for committing new blocks to the blockchain through voting. A validator's stake is slashed if they become unavailable, double sign a transaction, or don't cast their votes. If you only want to run a full node, a VM in the cloud is fine. However, if you are want to become a validator for the Hub's `mainnet`, you should research hardened setups. Please read [Sentry Node Architecture](https://github.com/cosmos/cosmos/blob/master/VALIDATORS_FAQ.md#how-can-validators-protect-themselves-from-denial-of-service-attacks) to protect your node from DDOS and ensure high-availability. Also see the [technical requirements](https://github.com/cosmos/cosmos/blob/master/VALIDATORS_FAQ.md#technical-requirements)). There's also more info on our [website](https://cosmos.network/validators). -Your `pubkey` can be used to create a new validator by staking tokens. You can find your validator pubkey by running: +### Create Your Validator -``` +Your `cosmosvalpub` can be used to create a new validator by staking tokens. You can find your validator pubkey by running: + +```bash gaiad tendermint show_validator ``` Next, craft your `gaiacli stake create-validator` command: -``` -gaiacli stake create-validator --amount=5steak --pubkey= --address-validator= --moniker=satoshi --chain-id= --name= +> _*NOTE:*_ Don't use more `steak` thank you have! You can always get more by using the [Faucet](https://faucetcosmos.network/)! + +```bash +gaiacli stake create-validator \ + --amount=5steak \ + --pubkey=$(gaiad tendermint show_validator) \ + --address-validator= + --moniker="choose a moniker" \ + --chain-id=gaia-6002 \ + --from= ``` -You can add more information to the validator, such as`--website`, `--keybase-sig`, or `--details`. Here's how: +### Edit Validator Description -``` -gaiacli stake edit-validator --details="To the cosmos !" --website="https://cosmos.network" +You can edit your validator's public description. This info is to identify your validator, and will be relied on by delegators to decide which validators to stake to. Make sure to provide input for every flag below, otherwise the field will default to empty (`--moniker` defaults to the machine name). + +The `--keybase-sig` is a 16-digit string that is generated with a [keybase.io](https://keybase.io) account. It's a cryptographically secure method of verifying your identity across multiple online networks. The Keybase API allows us to retrieve your Keybase avatar. This is how you can add a logo to your validator profile. + +```bash +gaiacli stake edit-validator + --address-validator= + --moniker="choose a moniker" \ + --website="https://cosmos.network" \ + --keybase-sig="6A0D65E29A4CBC8E" + --details="To infinity and beyond!" + --chain-id=gaia-6002 \ + --from= ``` +### View Validator Description View the validator's information with this command: -``` -gaiacli stake validator --address-validator= --chain-id= +```bash +gaiacli stake validator \ + --address-validator= \ + --chain-id=gaia-6002 ``` -To check that the validator is active, look for it here: +Your validator is active if the following command returns anything: -``` -gaiacli advanced tendermint validator-set +```bash +gaiacli advanced tendermint validator-set | grep "$(gaiad tendermint show_validator)" ``` -**Note:** To be in the validator set, you need to have more total voting power than the 100th validator. +You should also be able to see your validator on the [Explorer](https://explorecosmos.network/validators). You are looking for the `bech32` encoded `address` in the `~/.gaiad/config/priv_validator.json` file. + +> _*Note:*_ To be in the validator set, you need to have more total voting power than the 100th validator. This is not normally an issue. + +### Problem #1: My validator has `voting_power: 0` + +Your validator has become auto-unbonded. In `gaia-6002`, we unbond validators if they do not vote on `50` of the last `100` blocks. Since blocks are proposed every ~2 seconds, a validator unresponsive for ~100 seconds will become unbonded. This usually happens when your `gaiad` process crashes. + +Here's how you can return the voting power back to your validator. First, if `gaiad` is not running, start it up again: + +```bash +gaiad start +``` + +Wait for your full node to catch up to the latest block. Next, run the following command. Note that `` is the address of your validator account, and `` is the name of the validator account. You can find this info by running `gaiacli keys list`. + +```bash +gaiacli stake unrevoke --chain-id=gaia-6002 --from= +``` + +**WARNING:** If you don't wait for `gaiad` to sync before running `unrevoke`, you will receive an error message telling you your validator is still jailed. + +Lastly, check your validator again to see if your voting power is back. + +```bash +gaiacli status +``` + +You may notice that your voting power is less than it used to be. That's because you got slashed for downtime! + +### Problem #2: My `gaiad` crashes because of `too many open files` + +The default number of files Linux can open (per-process) is `1024`. `gaiad` is known to open more than `1024` files. This causes the process to crash. A quick fix is to run `ulimit -n 4096` (increase the number of open files allowed) and then restart the process with `gaiad start`. If you are using `systemd` or another process manager to launch `gaiad` this may require some configuration at that level. A sample `systemd` file to fix this issue is below: + +```toml +# /etc/systemd/system/gaiad.service +[Unit] +Description=Cosmos Gaia Node +After=network.target + +[Service] +Type=simple +User=ubuntu +WorkingDirectory=/home/ubuntu +ExecStart=/home/ubuntu/go/bin/gaiad start +Restart=on-failure +RestartSec=3 +LimitNOFILE=4096 + +[Install] +WantedBy=multi-user.target +``` ## Delegating to a Validator -On the upcoming mainnet, you can delegate `atom` to a validator. These [delegators](https://cosmos.network/resources/delegators) can receive part of the validator's fee revenue. Read more about the [Cosmos Token Model](https://github.com/cosmos/cosmos/raw/master/Cosmos_Token_Model.pdf). +On the upcoming mainnet, you can delegate `Atom` to a validator. These [delegators](https://cosmos.network/resources/delegators) can receive part of the validator's fee revenue. Read more about the [Cosmos Token Model](https://github.com/cosmos/cosmos/raw/master/Cosmos_Token_Model.pdf). ### Bond Tokens -On the testnet, we delegate `steak` instead of `atom`. Here's how you can bond tokens to a testnet validator: +On the testnet, we delegate `steak` instead of `Atom`. Here's how you can bond tokens to a testnet validator: -``` -gaiacli stake delegate --amount=10steak --address-delegator= --address-validator= --name= --chain-id= +```bash +gaiacli stake delegate \ + --amount=10steak \ + --address-delegator= \ + --address-validator= \ + --from= \ + --chain-id=gaia-6002 ``` -While tokens are bonded, they are pooled with all the other bonded tokens in the network. Validators and delegators obtain a percentage of shares that equal their stake in this pool. +While tokens are bonded, they are pooled with all the other bonded tokens in the network. Validators and delegators obtain a percentage of shares that equal their stake in this pool. + +> _*NOTE:*_ Don't use more `steak` thank you have! You can always get more by using the [Faucet](https://faucetcosmos.network/)! ### Unbond Tokens If for any reason the validator misbehaves, or you want to unbond a certain amount of tokens, use this following command. You can unbond a specific amount of`shares`\(eg:`12.1`\) or all of them \(`MAX`\). -``` -gaiacli stake unbond --address-delegator= --address-validator= --shares=MAX --name= --chain-id= +```bash +gaiacli stake unbond \ + --address-delegator= \ + --address-validator= \ + --shares=MAX \ + --from= \ + --chain-id=gaia-6002 ``` You can check your balance and your stake delegation to see that the unbonding went through successfully. +```bash +gaiacli account + +gaiacli stake delegation \ + --address-delegator= \ + --address-validator= \ + --chain-id=gaia-6002 ``` -gaiacli account -gaiacli stake delegation --address-delegator= --address-validator= --chain-id= + +## Governance + +Governance is the process from which users in the Cosmos Hub can come to consensus on software upgrades, parameters of the mainnet or on custom text proposals. This is done through voting on proposals, which will be submitted by `Atom` holders on the mainnet. + +Some considerations about the voting process: + +- Voting is done by bonded `Atom` holders on a 1 bonded `Atom` 1 vote basis +- Delegators inherit the vote of their validator if they don't vote +- **Validators MUST vote on every proposal**. If a validator does not vote on a proposal, they will be **partially slashed** +- Votes are tallied at the end of the voting period (2 weeks on mainnet). Each address can vote multiple times to update its `Option` value (paying the transaction fee each time), only the last casted vote will count as valid +- Voters can choose between options `Yes`, `No`, `NoWithVeto` and `Abstain` +At the end of the voting period, a proposal is accepted if `(YesVotes/(YesVotes+NoVotes+NoWithVetoVotes))>1/2` and `(NoWithVetoVotes/(YesVotes+NoVotes+NoWithVetoVotes))<1/3`. It is rejected otherwise + +For more information about the governance process and how it works, please check out the Governance module [specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/governance). + +### Create a Governance proposal + +In order to create a governance proposal, you must submit an initial deposit along with the proposal details: + +- `title`: Title of the proposal +- `description`: Description of the proposal +- `type`: Type of proposal. Must be of value _Text_ (types _SoftwareUpgrade_ and _ParameterChange_ not supported yet). + +```bash +gaiacli gov submit-proposal \ + --title= \ + --description=<description> \ + --type=<Text/ParameterChange/SoftwareUpgrade> \ + --proposer=<account_cosmosaccaddr> \ + --deposit=<40steak> \ + --from=<name> \ + --chain-id=gaia-7000 +``` + + +### Increase deposit + +In order for a proposal to be broadcasted to the network, the amount deposited must be above a `minDeposit` value (default: `10 steak`). If the proposal you previously created didn't meet this requirement, you can still increase the total amount deposited to activate it. Once the minimum deposit is reached, the proposal enters voting period: + +```bash +gaiacli gov deposit \ + --proposalID=<proposal_id> \ + --depositer=<account_cosmosaccaddr> \ + --deposit=<200steak> \ + --from=<name> \ + --chain-id=gaia-7000 +``` + +> _NOTE_: Proposals that don't meet this requirement will be deleted after `MaxDepositPeriod` is reached. + +#### Query proposal + +Once created, you can now query information of the proposal: + +```bash +gaiacli gov query-proposal \ + --proposalID=<proposal_id> \ + --chain-id=gaia-7000 +``` + +### Vote on a proposal + +After a proposal's deposit reaches the `MinDeposit` value, the voting period opens. Bonded `Atom` holders can then cast vote on it: + +```bash +gaiacli gov vote \ + --proposalID=<proposal_id> \ + --voter=<account_cosmosaccaddr> \ + --option=<Yes/No/NoWithVeto/Abstain> \ + --from=<name> \ + --chain-id=gaia-7000 +``` + +#### Query vote + +Check the vote with the option you just submitted: + +```bash +gaiacli gov query-vote \ + --proposalID=<proposal_id> \ + --voter=<account_cosmosaccaddr> \ + --chain-id=gaia-7000 +``` + +## Other Operations + +### Send Tokens + +```bash +gaiacli send \ + --amount=10faucetToken \ + --chain-id=gaia-6002 \ + --from=<key_name> \ + --to=<destination_cosmosaccaddr> +``` + +> _*NOTE:*_ The `--amount` flag accepts the format `--amount=<value|coin_name>`. + +Now, view the updated balances of the origin and destination accounts: + +```bash +gaiacli account <account_cosmosaccaddr> +gaiacli account <destination_cosmosaccaddr> +``` + +You can also check your balance at a given block by using the `--block` flag: + +```bash +gaiacli account <account_cosmosaccaddr> --block=<block_height> ``` diff --git a/crypto/amino.go b/crypto/amino.go new file mode 100644 index 000000000..18d67e3d6 --- /dev/null +++ b/crypto/amino.go @@ -0,0 +1,19 @@ +package crypto + +import ( + "github.com/tendermint/go-amino" + tcrypto "github.com/tendermint/tendermint/crypto" +) + +var cdc = amino.NewCodec() + +func init() { + RegisterAmino(cdc) + tcrypto.RegisterAmino(cdc) +} + +// RegisterAmino registers all go-crypto related types in the given (amino) codec. +func RegisterAmino(cdc *amino.Codec) { + cdc.RegisterConcrete(PrivKeyLedgerSecp256k1{}, + "tendermint/PrivKeyLedgerSecp256k1", nil) +} diff --git a/crypto/encode_test.go b/crypto/encode_test.go new file mode 100644 index 000000000..99dd727cf --- /dev/null +++ b/crypto/encode_test.go @@ -0,0 +1,121 @@ +package crypto + +import ( + "github.com/stretchr/testify/require" + "os" + "testing" + + tcrypto "github.com/tendermint/tendermint/crypto" +) + +type byter interface { + Bytes() []byte +} + +func checkAminoBinary(t *testing.T, src byter, dst interface{}, size int) { + // Marshal to binary bytes. + bz, err := cdc.MarshalBinaryBare(src) + require.Nil(t, err, "%+v", err) + // Make sure this is compatible with current (Bytes()) encoding. + require.Equal(t, src.Bytes(), bz, "Amino binary vs Bytes() mismatch") + // Make sure we have the expected length. + if size != -1 { + require.Equal(t, size, len(bz), "Amino binary size mismatch") + } + // Unmarshal. + err = cdc.UnmarshalBinaryBare(bz, dst) + require.Nil(t, err, "%+v", err) +} + +func checkAminoJSON(t *testing.T, src interface{}, dst interface{}, isNil bool) { + // Marshal to JSON bytes. + js, err := cdc.MarshalJSON(src) + require.Nil(t, err, "%+v", err) + if isNil { + require.Equal(t, string(js), `null`) + } else { + require.Contains(t, string(js), `"type":`) + require.Contains(t, string(js), `"value":`) + } + // Unmarshal. + err = cdc.UnmarshalJSON(js, dst) + require.Nil(t, err, "%+v", err) +} + +//nolint +func ExamplePrintRegisteredTypes() { + cdc.PrintTypes(os.Stdout) + // Output: | Type | Name | Prefix | Length | Notes | + //| ---- | ---- | ------ | ----- | ------ | + //| PrivKeyLedgerSecp256k1 | tendermint/PrivKeyLedgerSecp256k1 | 0x10CAB393 | variable | | + //| PubKeyEd25519 | tendermint/PubKeyEd25519 | 0x1624DE64 | 0x20 | | + //| PubKeySecp256k1 | tendermint/PubKeySecp256k1 | 0xEB5AE987 | 0x21 | | + //| PrivKeyEd25519 | tendermint/PrivKeyEd25519 | 0xA3288910 | 0x40 | | + //| PrivKeySecp256k1 | tendermint/PrivKeySecp256k1 | 0xE1B0F79B | 0x20 | | + //| SignatureEd25519 | tendermint/SignatureEd25519 | 0x2031EA53 | 0x40 | | + //| SignatureSecp256k1 | tendermint/SignatureSecp256k1 | 0x7FC4A495 | variable | | +} + +func TestKeyEncodings(t *testing.T) { + cases := []struct { + privKey tcrypto.PrivKey + privSize, pubSize int // binary sizes with the amino overhead + }{ + { + privKey: tcrypto.GenPrivKeyEd25519(), + privSize: 69, + pubSize: 37, + }, + { + privKey: tcrypto.GenPrivKeySecp256k1(), + privSize: 37, + pubSize: 38, + }, + } + + for _, tc := range cases { + + // Check (de/en)codings of PrivKeys. + var priv2, priv3 tcrypto.PrivKey + checkAminoBinary(t, tc.privKey, &priv2, tc.privSize) + require.EqualValues(t, tc.privKey, priv2) + checkAminoJSON(t, tc.privKey, &priv3, false) // TODO also check Prefix bytes. + require.EqualValues(t, tc.privKey, priv3) + + // Check (de/en)codings of Signatures. + var sig1, sig2, sig3 tcrypto.Signature + sig1, err := tc.privKey.Sign([]byte("something")) + require.NoError(t, err) + checkAminoBinary(t, sig1, &sig2, -1) // Signature size changes for Secp anyways. + require.EqualValues(t, sig1, sig2) + checkAminoJSON(t, sig1, &sig3, false) // TODO also check Prefix bytes. + require.EqualValues(t, sig1, sig3) + + // Check (de/en)codings of PubKeys. + pubKey := tc.privKey.PubKey() + var pub2, pub3 tcrypto.PubKey + checkAminoBinary(t, pubKey, &pub2, tc.pubSize) + require.EqualValues(t, pubKey, pub2) + checkAminoJSON(t, pubKey, &pub3, false) // TODO also check Prefix bytes. + require.EqualValues(t, pubKey, pub3) + } +} + +func TestNilEncodings(t *testing.T) { + + // Check nil Signature. + var a, b tcrypto.Signature + checkAminoJSON(t, &a, &b, true) + require.EqualValues(t, a, b) + + // Check nil PubKey. + var c, d tcrypto.PubKey + checkAminoJSON(t, &c, &d, true) + require.EqualValues(t, c, d) + + // Check nil PrivKey. + var e, f tcrypto.PrivKey + checkAminoJSON(t, &e, &f, true) + require.EqualValues(t, e, f) + +} diff --git a/crypto/keys/bcrypt/base64.go b/crypto/keys/bcrypt/base64.go new file mode 100644 index 000000000..fc3116090 --- /dev/null +++ b/crypto/keys/bcrypt/base64.go @@ -0,0 +1,35 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bcrypt + +import "encoding/base64" + +const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + +var bcEncoding = base64.NewEncoding(alphabet) + +func base64Encode(src []byte) []byte { + n := bcEncoding.EncodedLen(len(src)) + dst := make([]byte, n) + bcEncoding.Encode(dst, src) + for dst[n-1] == '=' { + n-- + } + return dst[:n] +} + +func base64Decode(src []byte) ([]byte, error) { + numOfEquals := 4 - (len(src) % 4) + for i := 0; i < numOfEquals; i++ { + src = append(src, '=') + } + + dst := make([]byte, bcEncoding.DecodedLen(len(src))) + n, err := bcEncoding.Decode(dst, src) + if err != nil { + return nil, err + } + return dst[:n], nil +} diff --git a/crypto/keys/bcrypt/bcrypt.go b/crypto/keys/bcrypt/bcrypt.go new file mode 100644 index 000000000..e24120bfb --- /dev/null +++ b/crypto/keys/bcrypt/bcrypt.go @@ -0,0 +1,297 @@ +package bcrypt + +// MODIFIED BY TENDERMINT TO EXPOSE NONCE +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing +// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf + +// The code is a port of Provos and Mazières's C implementation. +import ( + "crypto/subtle" + "errors" + "fmt" + "strconv" + + "golang.org/x/crypto/blowfish" +) + +const ( + // the minimum allowable cost as passed in to GenerateFromPassword + MinCost int = 4 + // the maximum allowable cost as passed in to GenerateFromPassword + MaxCost int = 31 + // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword + DefaultCost int = 10 +) + +// The error returned from CompareHashAndPassword when a password and hash do +// not match. +var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password") + +// The error returned from CompareHashAndPassword when a hash is too short to +// be a bcrypt hash. +var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password") + +// The error returned from CompareHashAndPassword when a hash was created with +// a bcrypt algorithm newer than this implementation. +type HashVersionTooNewError byte + +func (hv HashVersionTooNewError) Error() string { + return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion) +} + +// The error returned from CompareHashAndPassword when a hash starts with something other than '$' +type InvalidHashPrefixError byte + +// Format error +func (ih InvalidHashPrefixError) Error() string { + return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih)) +} + +// Invalid bcrypt cost +type InvalidCostError int + +func (ic InvalidCostError) Error() string { + return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost)) // nolint: unconvert +} + +const ( + majorVersion = '2' + minorVersion = 'a' + maxSaltSize = 16 + maxCryptedHashSize = 23 + encodedSaltSize = 22 + encodedHashSize = 31 + minHashSize = 59 +) + +// magicCipherData is an IV for the 64 Blowfish encryption calls in +// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes. +var magicCipherData = []byte{ + 0x4f, 0x72, 0x70, 0x68, + 0x65, 0x61, 0x6e, 0x42, + 0x65, 0x68, 0x6f, 0x6c, + 0x64, 0x65, 0x72, 0x53, + 0x63, 0x72, 0x79, 0x44, + 0x6f, 0x75, 0x62, 0x74, +} + +type hashed struct { + hash []byte + salt []byte + cost int // allowed range is MinCost to MaxCost + major byte + minor byte +} + +// GenerateFromPassword returns the bcrypt hash of the password at the given +// cost. If the cost given is less than MinCost, the cost will be set to +// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package, +// to compare the returned hashed password with its cleartext version. +func GenerateFromPassword(salt []byte, password []byte, cost int) ([]byte, error) { + if len(salt) != maxSaltSize { + return nil, fmt.Errorf("salt len must be %v", maxSaltSize) + } + p, err := newFromPassword(salt, password, cost) + if err != nil { + return nil, err + } + return p.Hash(), nil +} + +// CompareHashAndPassword compares a bcrypt hashed password with its possible +// plaintext equivalent. Returns nil on success, or an error on failure. +func CompareHashAndPassword(hashedPassword, password []byte) error { + p, err := newFromHash(hashedPassword) + if err != nil { + return err + } + + otherHash, err := bcrypt(password, p.cost, p.salt) + if err != nil { + return err + } + + otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor} + if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 { + return nil + } + + return ErrMismatchedHashAndPassword +} + +// Cost returns the hashing cost used to create the given hashed +// password. When, in the future, the hashing cost of a password system needs +// to be increased in order to adjust for greater computational power, this +// function allows one to establish which passwords need to be updated. +func Cost(hashedPassword []byte) (int, error) { + p, err := newFromHash(hashedPassword) + if err != nil { + return 0, err + } + return p.cost, nil +} + +func newFromPassword(salt []byte, password []byte, cost int) (*hashed, error) { + if cost < MinCost { + cost = DefaultCost + } + p := new(hashed) + p.major = majorVersion + p.minor = minorVersion + + err := checkCost(cost) + if err != nil { + return nil, err + } + p.cost = cost + + p.salt = base64Encode(salt) + hash, err := bcrypt(password, p.cost, p.salt) + if err != nil { + return nil, err + } + p.hash = hash + return p, err +} + +func newFromHash(hashedSecret []byte) (*hashed, error) { + if len(hashedSecret) < minHashSize { + return nil, ErrHashTooShort + } + p := new(hashed) + n, err := p.decodeVersion(hashedSecret) + if err != nil { + return nil, err + } + hashedSecret = hashedSecret[n:] + n, err = p.decodeCost(hashedSecret) + if err != nil { + return nil, err + } + hashedSecret = hashedSecret[n:] + + // The "+2" is here because we'll have to append at most 2 '=' to the salt + // when base64 decoding it in expensiveBlowfishSetup(). + p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2) + copy(p.salt, hashedSecret[:encodedSaltSize]) + + hashedSecret = hashedSecret[encodedSaltSize:] + p.hash = make([]byte, len(hashedSecret)) + copy(p.hash, hashedSecret) + + return p, nil +} + +func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) { + cipherData := make([]byte, len(magicCipherData)) + copy(cipherData, magicCipherData) + + c, err := expensiveBlowfishSetup(password, uint32(cost), salt) + if err != nil { + return nil, err + } + + for i := 0; i < 24; i += 8 { + for j := 0; j < 64; j++ { + c.Encrypt(cipherData[i:i+8], cipherData[i:i+8]) + } + } + + // Bug compatibility with C bcrypt implementations. We only encode 23 of + // the 24 bytes encrypted. + hsh := base64Encode(cipherData[:maxCryptedHashSize]) + return hsh, nil +} + +func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) { + + csalt, err := base64Decode(salt) + if err != nil { + return nil, err + } + + // Bug compatibility with C bcrypt implementations. They use the trailing + // NULL in the key string during expansion. + ckey := append(key, 0) + + c, err := blowfish.NewSaltedCipher(ckey, csalt) + if err != nil { + return nil, err + } + + var i, rounds uint64 + rounds = 1 << cost + for i = 0; i < rounds; i++ { + blowfish.ExpandKey(ckey, c) + blowfish.ExpandKey(csalt, c) + } + + return c, nil +} + +func (p *hashed) Hash() []byte { + arr := make([]byte, 60) + arr[0] = '$' + arr[1] = p.major + n := 2 + if p.minor != 0 { + arr[2] = p.minor + n = 3 + } + arr[n] = '$' + n++ + copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost))) + n += 2 + arr[n] = '$' + n++ + copy(arr[n:], p.salt) + n += encodedSaltSize + copy(arr[n:], p.hash) + n += encodedHashSize + return arr[:n] +} + +func (p *hashed) decodeVersion(sbytes []byte) (int, error) { + if sbytes[0] != '$' { + return -1, InvalidHashPrefixError(sbytes[0]) + } + if sbytes[1] > majorVersion { + return -1, HashVersionTooNewError(sbytes[1]) + } + p.major = sbytes[1] + n := 3 + if sbytes[2] != '$' { + p.minor = sbytes[2] + n++ + } + return n, nil +} + +// sbytes should begin where decodeVersion left off. +func (p *hashed) decodeCost(sbytes []byte) (int, error) { + cost, err := strconv.Atoi(string(sbytes[0:2])) + if err != nil { + return -1, err + } + err = checkCost(cost) + if err != nil { + return -1, err + } + p.cost = cost + return 3, nil +} + +func (p *hashed) String() string { + return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor) +} + +func checkCost(cost int) error { + if cost < MinCost || cost > MaxCost { + return InvalidCostError(cost) + } + return nil +} diff --git a/crypto/keys/bip39/wordcodec.go b/crypto/keys/bip39/wordcodec.go new file mode 100644 index 000000000..074d1393c --- /dev/null +++ b/crypto/keys/bip39/wordcodec.go @@ -0,0 +1,66 @@ +package bip39 + +import ( + "strings" + + "github.com/bartekn/go-bip39" +) + +// ValidSentenceLen defines the mnemonic sentence lengths supported by this BIP 39 library. +type ValidSentenceLen uint8 + +const ( + // FundRaiser is the sentence length used during the cosmos fundraiser (12 words). + FundRaiser ValidSentenceLen = 12 + // Size of the checksum employed for the fundraiser + FundRaiserChecksumSize = 4 + // FreshKey is the sentence length used for newly created keys (24 words). + FreshKey ValidSentenceLen = 24 + // Size of the checksum employed for new keys + FreshKeyChecksumSize = 8 +) + +// NewMnemonic will return a string consisting of the mnemonic words for +// the given sentence length. +func NewMnemonic(len ValidSentenceLen) (words []string, err error) { + // len = (entropySize + checksum) / 11 + var entropySize int + switch len { + case FundRaiser: + // entropySize = 128 + entropySize = int(len)*11 - FundRaiserChecksumSize + case FreshKey: + // entropySize = 256 + entropySize = int(len)*11 - FreshKeyChecksumSize + } + var entropy []byte + entropy, err = bip39.NewEntropy(entropySize) + if err != nil { + return + } + var mnemonic string + mnemonic, err = bip39.NewMnemonic(entropy) + if err != nil { + return + } + words = strings.Split(mnemonic, " ") + return +} + +// MnemonicToSeed creates a BIP 39 seed from the passed mnemonic (with an empty BIP 39 password). +// This method does not validate the mnemonics checksum. +func MnemonicToSeed(mne string) (seed []byte) { + // we do not checksum here... + seed = bip39.NewSeed(mne, "") + return +} + +// MnemonicToSeedWithErrChecking returns the same seed as MnemonicToSeed. +// It creates a BIP 39 seed from the passed mnemonic (with an empty BIP 39 password). +// +// Different from MnemonicToSeed it validates the checksum. +// For details on the checksum see the BIP 39 spec. +func MnemonicToSeedWithErrChecking(mne string) (seed []byte, err error) { + seed, err = bip39.NewSeedWithErrorChecking(mne, "") + return +} diff --git a/crypto/keys/bip39/wordcodec_test.go b/crypto/keys/bip39/wordcodec_test.go new file mode 100644 index 000000000..a821239d7 --- /dev/null +++ b/crypto/keys/bip39/wordcodec_test.go @@ -0,0 +1,15 @@ +package bip39 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestWordCodec_NewMnemonic(t *testing.T) { + _, err := NewMnemonic(FundRaiser) + require.NoError(t, err, "unexpected error generating fundraiser mnemonic") + + _, err = NewMnemonic(FreshKey) + require.NoError(t, err, "unexpected error generating new 24-word mnemonic") +} diff --git a/crypto/keys/hd/fundraiser_test.go b/crypto/keys/hd/fundraiser_test.go new file mode 100644 index 000000000..f4112d958 --- /dev/null +++ b/crypto/keys/hd/fundraiser_test.go @@ -0,0 +1,80 @@ +package hd + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "testing" + + "github.com/bartekn/go-bip39" + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto" +) + +type addrData struct { + Mnemonic string + Master string + Seed string + Priv string + Pub string + Addr string +} + +func initFundraiserTestVectors(t *testing.T) []addrData { + // NOTE: atom fundraiser address + // var hdPath string = "m/44'/118'/0'/0/0" + var hdToAddrTable []addrData + + b, err := ioutil.ReadFile("test.json") + if err != nil { + t.Fatalf("could not read fundraiser test vector file (test.json): %s", err) + } + + err = json.Unmarshal(b, &hdToAddrTable) + if err != nil { + t.Fatalf("could not decode test vectors (test.json): %s", err) + } + return hdToAddrTable +} + +func TestFundraiserCompatibility(t *testing.T) { + hdToAddrTable := initFundraiserTestVectors(t) + + for i, d := range hdToAddrTable { + privB, _ := hex.DecodeString(d.Priv) + pubB, _ := hex.DecodeString(d.Pub) + addrB, _ := hex.DecodeString(d.Addr) + seedB, _ := hex.DecodeString(d.Seed) + masterB, _ := hex.DecodeString(d.Master) + + seed := bip39.NewSeed(d.Mnemonic, "") + + t.Log("================================") + t.Logf("ROUND: %d MNEMONIC: %s", i, d.Mnemonic) + + master, ch := ComputeMastersFromSeed(seed) + priv, err := DerivePrivateKeyForPath(master, ch, "44'/118'/0'/0/0") + require.NoError(t, err) + pub := crypto.PrivKeySecp256k1(priv).PubKey() + + t.Log("\tNODEJS GOLANG\n") + t.Logf("SEED \t%X %X\n", seedB, seed) + t.Logf("MSTR \t%X %X\n", masterB, master) + t.Logf("PRIV \t%X %X\n", privB, priv) + t.Logf("PUB \t%X %X\n", pubB, pub) + + require.Equal(t, seedB, seed) + require.Equal(t, master[:], masterB, fmt.Sprintf("Expected masters to match for %d", i)) + require.Equal(t, priv[:], privB, "Expected priv keys to match") + var pubBFixed [33]byte + copy(pubBFixed[:], pubB) + require.Equal(t, pub, crypto.PubKeySecp256k1(pubBFixed), fmt.Sprintf("Expected pub keys to match for %d", i)) + + addr := pub.Address() + t.Logf("ADDR \t%X %X\n", addrB, addr) + require.Equal(t, addr, crypto.Address(addrB), fmt.Sprintf("Expected addresses to match %d", i)) + + } +} diff --git a/crypto/keys/hd/hdpath.go b/crypto/keys/hd/hdpath.go new file mode 100644 index 000000000..1427752b4 --- /dev/null +++ b/crypto/keys/hd/hdpath.go @@ -0,0 +1,168 @@ +// Package hd provides basic functionality Hierarchical Deterministic Wallets. +// +// The user must understand the overall concept of the BIP 32 and the BIP 44 specs: +// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki +// +// In combination with the bip39 package in go-crypto this package provides the functionality for deriving keys using a +// BIP 44 HD path, or, more general, by passing a BIP 32 path. +// +// In particular, this package (together with bip39) provides all necessary functionality to derive keys from +// mnemonics generated during the cosmos fundraiser. +package hd + +import ( + "crypto/hmac" + "crypto/sha512" + "encoding/binary" + "errors" + "fmt" + "math/big" + "strconv" + "strings" + + "github.com/btcsuite/btcd/btcec" + "github.com/tendermint/tendermint/crypto" +) + +// BIP44Prefix is the parts of the BIP32 HD path that are fixed by what we used during the fundraiser. +const ( + BIP44Prefix = "44'/118'/" + FullFundraiserPath = BIP44Prefix + "0'/0/0" +) + +// BIP44Params wraps BIP 44 params (5 level BIP 32 path). +// To receive a canonical string representation ala +// m / purpose' / coin_type' / account' / change / address_index +// call String() on a BIP44Params instance. +type BIP44Params struct { + purpose uint32 + coinType uint32 + account uint32 + change bool + addressIdx uint32 +} + +// NewParams creates a BIP 44 parameter object from the params: +// m / purpose' / coin_type' / account' / change / address_index +func NewParams(purpose, coinType, account uint32, change bool, addressIdx uint32) *BIP44Params { + return &BIP44Params{ + purpose: purpose, + coinType: coinType, + account: account, + change: change, + addressIdx: addressIdx, + } +} + +// NewFundraiserParams creates a BIP 44 parameter object from the params: +// m / 44' / 118' / account' / 0 / address_index +// The fixed parameters (purpose', coin_type', and change) are determined by what was used in the fundraiser. +func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params { + return NewParams(44, 118, account, false, addressIdx) +} + +func (p BIP44Params) String() string { + var changeStr string + if p.change { + changeStr = "1" + } else { + changeStr = "0" + } + // m / purpose' / coin_type' / account' / change / address_index + return fmt.Sprintf("%d'/%d'/%d'/%s/%d", + p.purpose, + p.coinType, + p.account, + changeStr, + p.addressIdx) +} + +// ComputeMastersFromSeed returns the master public key, master secret, and chain code in hex. +func ComputeMastersFromSeed(seed []byte) (secret [32]byte, chainCode [32]byte) { + masterSecret := []byte("Bitcoin seed") + secret, chainCode = i64(masterSecret, seed) + + return +} + +// DerivePrivateKeyForPath derives the private key by following the BIP 32/44 path from privKeyBytes, +// using the given chainCode. +func DerivePrivateKeyForPath(privKeyBytes [32]byte, chainCode [32]byte, path string) ([32]byte, error) { + data := privKeyBytes + parts := strings.Split(path, "/") + for _, part := range parts { + // do we have an apostrophe? + harden := part[len(part)-1:] == "'" + // harden == private derivation, else public derivation: + if harden { + part = part[:len(part)-1] + } + idx, err := strconv.Atoi(part) + if err != nil { + return [32]byte{}, fmt.Errorf("invalid BIP 32 path: %s", err) + } + if idx < 0 { + return [32]byte{}, errors.New("invalid BIP 32 path: index negative ot too large") + } + data, chainCode = derivePrivateKey(data, chainCode, uint32(idx), harden) + } + var derivedKey [32]byte + n := copy(derivedKey[:], data[:]) + if n != 32 || len(data) != 32 { + return [32]byte{}, fmt.Errorf("expected a (secp256k1) key of length 32, got length: %v", len(data)) + } + + return derivedKey, nil +} + +// derivePrivateKey derives the private key with index and chainCode. +// If harden is true, the derivation is 'hardened'. +// It returns the new private key and new chain code. +// For more information on hardened keys see: +// - https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki +func derivePrivateKey(privKeyBytes [32]byte, chainCode [32]byte, index uint32, harden bool) ([32]byte, [32]byte) { + var data []byte + if harden { + index = index | 0x80000000 + data = append([]byte{byte(0)}, privKeyBytes[:]...) + } else { + // this can't return an error: + pubkey := crypto.PrivKeySecp256k1(privKeyBytes).PubKey() + + public := pubkey.(crypto.PubKeySecp256k1) + data = public[:] + } + data = append(data, uint32ToBytes(index)...) + data2, chainCode2 := i64(chainCode[:], data) + x := addScalars(privKeyBytes[:], data2[:]) + return x, chainCode2 +} + +// modular big endian addition +func addScalars(a []byte, b []byte) [32]byte { + aInt := new(big.Int).SetBytes(a) + bInt := new(big.Int).SetBytes(b) + sInt := new(big.Int).Add(aInt, bInt) + x := sInt.Mod(sInt, btcec.S256().N).Bytes() + x2 := [32]byte{} + copy(x2[32-len(x):], x) + return x2 +} + +func uint32ToBytes(i uint32) []byte { + b := [4]byte{} + binary.BigEndian.PutUint32(b[:], i) + return b[:] +} + +// i64 returns the two halfs of the SHA512 HMAC of key and data. +func i64(key []byte, data []byte) (IL [32]byte, IR [32]byte) { + mac := hmac.New(sha512.New, key) + // sha512 does not err + _, _ = mac.Write(data) + I := mac.Sum(nil) + copy(IL[:], I[:32]) + copy(IR[:], I[32:]) + return +} diff --git a/crypto/keys/hd/hdpath_test.go b/crypto/keys/hd/hdpath_test.go new file mode 100644 index 000000000..58398655f --- /dev/null +++ b/crypto/keys/hd/hdpath_test.go @@ -0,0 +1,75 @@ +package hd + +import ( + "encoding/hex" + "fmt" + "github.com/cosmos/cosmos-sdk/crypto/keys/bip39" +) + +//nolint +func ExampleStringifyPathParams() { + path := NewParams(44, 0, 0, false, 0) + fmt.Println(path.String()) + // Output: 44'/0'/0'/0/0 +} + +//nolint +func ExampleSomeBIP32TestVecs() { + + seed := bip39.MnemonicToSeed("barrel original fuel morning among eternal " + + "filter ball stove pluck matrix mechanic") + master, ch := ComputeMastersFromSeed(seed) + fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)") + fmt.Println() + // cosmos + priv, _ := DerivePrivateKeyForPath(master, ch, FullFundraiserPath) + fmt.Println(hex.EncodeToString(priv[:])) + // bitcoin + priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0") + fmt.Println(hex.EncodeToString(priv[:])) + // ether + priv, _ = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0") + fmt.Println(hex.EncodeToString(priv[:])) + + fmt.Println() + fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html") + fmt.Println() + + seed = bip39.MnemonicToSeed( + "advice process birth april short trust crater change bacon monkey medal garment " + + "gorilla ranch hour rival razor call lunar mention taste vacant woman sister") + master, ch = ComputeMastersFromSeed(seed) + priv, _ = DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4") + fmt.Println(hex.EncodeToString(priv[:])) + + seed = bip39.MnemonicToSeed("idea naive region square margin day captain habit " + + "gun second farm pact pulse someone armed") + master, ch = ComputeMastersFromSeed(seed) + priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420") + fmt.Println(hex.EncodeToString(priv[:])) + + fmt.Println() + fmt.Println("BIP 32 example") + fmt.Println() + + // bip32 path: m/0/7 + seed = bip39.MnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history") + master, ch = ComputeMastersFromSeed(seed) + priv, _ = DerivePrivateKeyForPath(master, ch, "0/7") + fmt.Println(hex.EncodeToString(priv[:])) + + // Output: keys from fundraiser test-vector (cosmos, bitcoin, ether) + // + // bfcb217c058d8bbafd5e186eae936106ca3e943889b0b4a093ae13822fd3170c + // e77c3de76965ad89997451de97b95bb65ede23a6bf185a55d80363d92ee37c3d + // 7fc4d8a8146dea344ba04c593517d3f377fa6cded36cd55aee0a0bb968e651bc + // + // keys generated via https://coinomi.com/recovery-phrase-tool.html + // + // a61f10c5fecf40c084c94fa54273b6f5d7989386be4a37669e6d6f7b0169c163 + // 32c4599843de3ef161a629a461d12c60b009b676c35050be5f7ded3a3b23501f + // + // BIP 32 example + // + // c4c11d8c03625515905d7e89d25dfc66126fbc629ecca6db489a1a72fc4bda78 +} diff --git a/crypto/keys/hd/test.json b/crypto/keys/hd/test.json new file mode 100644 index 000000000..fa9610999 --- /dev/null +++ b/crypto/keys/hd/test.json @@ -0,0 +1 @@ +[{"mnemonic":"measure slogan connect luggage stereo federal stuff stomach stumble security end differ","master":"c177fa4bd21420b6ba2246e4f7a43fbe76545d1204174cae942ffbd79a434d11","seed":"c237f7aa198c5bd560ac8daf5b8421d03855171465b2999b07159671e9186461e7d75dba6c7264b963108431f8674ac8d095b7a22878fa0ab8b582e5d6ea1986","priv":"91bba8805845210665d7a9c5aff63ef69f7604fbeddb485706d31f04458f572c","pub":"0210ddc89abc90bbcbec8c63f5a4ebb016b58063b9d1a77854502042bdfcac5e51","addr":"72e7d6e9cfa899043a0783752a4876423f8effb8"},{"mnemonic":"car taste absurd genius miracle toy earth true glare mobile pig forest","master":"d7e07c472b05a2d668328c148ca07264afb4b53e078a2e1c106470b51c6ef81c","seed":"fda7006f5e9b8d1c02404df6d4e4497ff5c2edf29c801180a046dc54fca2e019554da6e3dd2c26073a7871c5ac529ad465ccf77ec3608727cc47ef88f998ae2b","priv":"1122ac929a0a3b2947ef99c33c20b657cf7342ea09be92869c363cfc52118200","pub":"03d0b431b56ae65aeb5a572654445b3fc968fa9d73995d3b21243cb07ed1ef0067","addr":"63eba204c249c795cd9dbdad58561db56b1d0c9b"},{"mnemonic":"trophy crop coffee oppose pelican help sense note bar faint hen aunt","master":"d1baaea05c34a4bbdc6791ada5aad77fdb0e626642cb884e48d25cd4ef6c9bed","seed":"1cdcfda64cb0e4f5806a48ff374e9f5f7d2939815e7adcce3abd898a7a9204eec4a5822997ca556ad797b4610c4ae6accddc29d80fd651a33d5ed911892479f1","priv":"c4bbb8f1ec60271c1e4f9e019ea45f82ca0ee3c42d8ac268f898fdbdfc28cf48","pub":"03abff20397222f9cae536d6b6776ba48c9b51898f7527dea918058f986fbfad15","addr":"fb6abc72817a9d2c6c1a2fe652434999661e1178"},{"mnemonic":"embrace office author among dream island social gift ask drill polar festival","master":"8d58a57b66a08bc0bd8dde7a718432a01f7aff03cbf999aeb3830d7a1c3d0d54","seed":"65b81faccab573d4a4272ab7c46e5fcc7db66eceeab6796d6dd70d8c54d6c95c3b73a637210a96a95df5ef736f51eec1dd3af4ccca53e98fb7f16d4e22dc3f52","priv":"dec3ffc51d5c9eadfb11e11dcc640f8cb294a21cfbb4a230f164693d57851b3b","pub":"03ef202a875683203e9d9643a2943cb40ff5a69b6efb3b0dedfc8ab1c6928261d1","addr":"db1a5fd30abfe666a049f5c6689a0e3db3ebec1e"},{"mnemonic":"aware gospel lemon tomorrow east wheel member forest walnut virus shallow stick","master":"24075bca853e0066909a6ff06ff02f196d4d641c00677704a6472810b0e3263e","seed":"1a4419cb19e2e7686fe68187aa55a2d4fab8d655c505c5666963d61bc9e92cfb990402d8527f9463b2deb3e86894055e3c6b31cf9995ca5bdf9959db755c9e90","priv":"853665c8f66682e50db33fe699ee72dae036422bee03e37093ed95c871ae8309","pub":"02a4e19e61decd6006bec54fa9c7ada7ee63ca6df12640e9fcf4214faafce791e6","addr":"155dac0e19dc24880b5aed827ebabb05bb563b23"},{"mnemonic":"perfect chief lend clock bracket guard extend medal slice vague cheese child","master":"b594e8d22e4864bdf75ddf29de16b7b207b2bbbdf29132b859f9c71c919ff669","seed":"631fc64164825a9769c4cd376cd736d4dd4191b7728e70b1e84b46322cb8bfb01b5b33cc6707d59bed09f6e7c82335541cd7dfc51ebedfcc2786713a9d141663","priv":"eab18ec583e155fd4ece4a14248bda80c75a6f209a1e4a9b1801e1a707b4356e","pub":"0247995311612868a8d1f6927f18ae5163a9d64a41d8f4015f9dcbbdace9277ed3","addr":"1f42f6538f64d844cef39b941986465076da3d54"},{"mnemonic":"edge early ten vicious piece begin observe ripple neutral farm volume accident","master":"1d6462662cd27419936c1f5b579eb7ebbafe84a6b1a58906c37fc0c2aa612d22","seed":"fb9588a1e3a2d9c26dcdb3cb010b8f868b29c493c95f7fe16761b2a666a8b96ca62997646e8d2cea1ff8ec3b85bd2311741ee327076515ad075623853279b57e","priv":"081847ec4f11a7d54ddd78c6f0fb3cdc32508e4956da14e0d728ddd3862124ce","pub":"03533c9f60afc94de73a0abb7d3e98906fa72cd2aebb202fdac32e2014452779d1","addr":"bcab563beb9ead1a87ab5adabc708502dc4081de"},{"mnemonic":"escape crystal wrist method cave retire wedding unable fringe inhale vibrant inform","master":"8b8597cd8ca42fe10236cc933bf0adda507132be6c41451d6ddda615b4032798","seed":"84582da23a6c4cf3d8f75be55db0d2710e10e31e9d6cee8288d827ed17d6585f19d81831aac12aff8d2724ace9c4220924e7d778eaebb3ff7ac4976b5ee25cc9","priv":"d011566a0c3996c99cf02ee41aa3f24c3328b5773ad0c9ea23f1bda1967a7e2c","pub":"03dcb9dd25915f827965c2d64e258eed1e4cf55f9fa3a9a8b5b411cbccffb11de0","addr":"54eea7e51aec6be8eca7a556c0ba5abfb702b34f"},{"mnemonic":"alter buddy rival question bachelor expose unique menu east sample also fence","master":"0eff009e6a00061a23a9b26f365cc00b0189825fe2eec0a3e78928fbd9f4dd7b","seed":"afa260446549711688b754edf33e313ceaece5502d0dbd8d82bc3c797151741620e403575ba6b54b0071e7122c8ae54893ccf29a43de209c67f7a8c296e47c84","priv":"39be182557862e0b7cf6440fb23568e54772edf734b4710ef571b37a6c27d16c","pub":"032c0d9bdbb97f2b7041734968eae96b3283573b9eefcd3ea1ce286c872bc8df4b","addr":"cbc2321a8e91ea81a56150689921b65a402b5c63"},{"mnemonic":"jar toward train panic unhappy control artefact horse deposit liar minimum pink","master":"009a99faadbdaa139f7ad2297367fdf92103c75194097133b51e0dd57629d06f","seed":"75b28bd52420c950aad1b4270fea0f8816c240e1bb16ad7f35a4bcc234e1c0bce4642422ea359dfae0332417c188a10e1b7efda4a9129809d98a179e8ef618dc","priv":"fbb6c27c23346fb30aa1aca0004f3adff649067efcbcdd58f9e8ed1be9167624","pub":"02463386f13659d68ca10edbecafab3294fddfb989b8012dd8a306c1e716ea73bc","addr":"9aef504159fcefcce23e49b64fe73d64b652751a"},{"mnemonic":"wise undo month ugly case garbage cannon torch sight burst flock enter","master":"e8533102aa8baa896888094c49fd096151adde972cbdd46429095b6081226d88","seed":"c64eab45997b8f27af96667bb8b3e3b1f13cd1cae3f33d357ef8996e958a24f78c6fff1dd21a7aad45a236d02e93722a74827e723415f4c3d2e28b5755ce74bf","priv":"7b28255abe4b5dfc46dd3e4a78aff23fd519ee20d0fccf77fa1f08dc87d4c2a8","pub":"03666213b09e6838c37edd56d858361cc7252aff15c86d9cbb7fd2ffaab2797eff","addr":"8de416131b81c2b4fb347c619e162e5b9d8e9171"},{"mnemonic":"fever hotel cup cover lake cream hello drastic scrap appear device skill","master":"c0c7ae8be6467f73da04bf31893fed60a7d2874a1b8b40cf6b947d3e0c072fd8","seed":"ba8ccb1d6df012c48cd0e97e126aca7192fc49ffe58fe554f2f96b85f7c8ee842e582a7967e053c4ea26f5ee0730dd7b78212b397f5ad89c0691c151c5b649b2","priv":"2d28bdd1c56e6f0c953b97ea571d7ee13f4b53557e9de79de436faee8b984944","pub":"02d4ef64e150ee986d14953bb42d7da5a26e184ae00d33c648e552ec31cc81df62","addr":"1919c6f2d0c93447d4fb8f10b8af4c57a06ad235"},{"mnemonic":"camp pizza test idea attend urban pond gym solution denial coach oppose","master":"e85ce81ff0663a945753449ea5a53a72e429a7badc3c681d7c13bb1b75a4d2ed","seed":"2bc62e90ae9d83635b316ef55f4ada1652dea512da332474598f53f93268ee1e18802ba19e75df651cfe931049ccc7aba7b96d43b62182e0e5b0e21a1cc2f489","priv":"bbd3109cd6edfc583ad1fca84b3c32def7d41e36f1994ac40eb5fd9f72211a47","pub":"032a3e6b6d55ede0c25b78a2bb860adea7ec8b3dbaf7340197ad2d0c07e1673a1f","addr":"4f9ba3935c15ff8ce14f0b09ef498e50693c4c90"},{"mnemonic":"solution buffalo armor capital enter cat banana manage caution loan people gauge","master":"1d894a5d9932bc19f91fa92f0d03f25136a77b4eb961ddd742b74c54edad9332","seed":"b833db5ff34a1bddbbee895d96518261025bbc6a9be596d58435468f1547ff378f504062aab7add0c69763708238d0ab42a90f6af0b790caa1569d1cc089b605","priv":"4e26066e7ff8936e63fb011173f70b0206c1b6ecaa519fb252185e18dbc19705","pub":"03fd00f281ba6065cdc8f6a8cf17cdb22b2ccad62eeb535708381004b2a260837f","addr":"e6247dacab881ff882e412630350eaa6de14817a"},{"mnemonic":"power answer mixture choose course crop decline border carpet absorb long artwork","master":"1d74c0120756009f821899f220d3a9e77291e0549241481b657ddb84af1b66f8","seed":"cd9ac735fee9bbcf45bd79b31196138b3ca002513bd1fb973eae4bffd0c5732611cd67006af3f79dd127ce21532947a0c1355cc774faee82f58cdbef6f8bd7fa","priv":"45f7df4a90d4ed0f58c47ab4839220f50689cb2a5663acd9cbb359d6a23e465e","pub":"02697233abb54eeef1f03a371b0da9a97e85ffe404707b5b7ee3f0e3d2e2f0768f","addr":"0f5addad3faaad7ebaf37c3d3f2576a0a1a5e533"},{"mnemonic":"patrol room turtle cinnamon remain tank butter hazard expose winner rough pull","master":"91eb616d5be757e91052fe74042515b828e2bb14f5666370d7ec37c45018abbd","seed":"7e3b9d910c4b16b06e813b2b003d9c0e8c76ec520702be210d9ec2ef2406e7f473f09f41fda56b6a657c2f1000130e11a88362209c6b4b47362e786b7880916c","priv":"ffc2a31bf0371a095cadbad2e0b5adc5669ceb6faad08da818c3c64e4af53b44","pub":"03b50efecc97cc7d7f101fdcae02662d49d118a63f03180889d8321019f245f6aa","addr":"0c43cd72c09dee746c3af1691b495a8c02286612"},{"mnemonic":"bulb neglect chalk congress cattle found balcony easy tube sick journey quick","master":"a7c632878e900903d4e0e6bc57291f1e64981d703e27b82c2da00dec800d5570","seed":"aaa765dce33f6b289d37fff2e409a540a29f3830a7baa0526eba3317d559ef59e8936e8285b6ea8a62753ec72616163e24963e49d795f38df210836ffc7d8807","priv":"f8b281b658e9a584087e16eab0179747db5c38193cada4fc85364a8f743bfec9","pub":"0384df6f5de3f50793f62eee838d30334501d6339998a25889688ba305998d15cf","addr":"cfe9adc1e454387351d4ef2dfd086101a0012863"},{"mnemonic":"arm asthma voyage mansion hurdle alley ensure team coffee palm spice general","master":"48e37cc4961ff1234938ca5ad6ed22f668b29017067c283ebf2d9642cfc2e57b","seed":"6bec879e82e5d243a6bbd5a0a5dcd21a6403d450f0f498975d5205da7154f62350cf0afa034f921cfedd4dd4dde0b6e3e9cc118b16b6682686211040b3b80b6c","priv":"b0b487d336dbb594947dab6717a5f140056d998f9e8428307491d12b3f9e3883","pub":"039db2ffbad67e7e824bfd0cd9fd8798647390b8c9a583807995a9b49e252279c3","addr":"1353002776f22c179aa208bbdddeab3c369c51be"},{"mnemonic":"spread curtain furnace trim funny usual age floor crunch blue urge pistol","master":"3c44072b545e2e955892ee4e6e5949b748d17d47d3ebeda85fb50a341bda7668","seed":"05376e4a1725d48191bebb1e03bdf76880d82b033f597182becbdcf78f167b260bb5cd6b79681f7ecd7ec75c241f90cb40b2d57d108bcc1b7766a63002af1960","priv":"58285cb1f39d00c6aa84a32a69e0e108ca80e33e75b7b4890e182a7e19485a36","pub":"02e4194b7eab20702d498ee7eb7cc98c3ac8d8b2fc63aa66bc81aad93bb1aa3b71","addr":"9b8ded654ae3336405f0bdf768d4293c462ed51d"},{"mnemonic":"century outdoor world grunt member stuff holiday intact property race mutual vehicle","master":"9b9fcc1fedcf63ae9fd216f9a7253f85cdec90f9b1fdca0c36b03169a5860ff6","seed":"e39f8455cceaf542d4062d998b8de4d7e153e594224ef7d7069abc38acf479fd2485f0821fd9af2116c58180f0069af32d10f0b7222eaef6622de83741824999","priv":"e7b4c942473ebc56868904e588bea3342f761528ca90f2add423e17615ef80cd","pub":"02059867db3a2ab4307a5ff502e9f89c45837927eb2f2b75fe55f0dcf25df9e1ee","addr":"714b9aa6afc10a40d702076990a451dc3ee4fa4b"},{"mnemonic":"world trade divert sad unable plug stereo autumn father ankle biology clown","master":"dae1a9e15469905d0d20172bbaa768be4ea4900594080499a56a69d1ae6d0557","seed":"50169fc286de6432685d85b4352908b079ab4fd11f1d8c6a82093093a9161650c2e10d8407f65f8cd1cf4d9d557590f21f5c717a761e7918ef8ca9b73f410120","priv":"4cd8309f36881d1afbfba66b9fcfa09f948d544db0baa3d81d3bd4d2067950b7","pub":"02a86d26c5b309f49017d4778b13be315cdd1d7c3baceeb61fb91c78bfafda4447","addr":"36e8463e55df56e01fd08b8fafa3ec1704ca5480"},{"mnemonic":"observe minute art scheme pear lava search dove cube entry orbit worth","master":"b7ad8e5b587bf747fae6c42423fc4cafecde0db7c9443d48afe10115d95f9b93","seed":"f3ac2022097390a9a0ae0e47d7746e6bce017d15d97ab74b1f99f70ed1c1e9b3534bdafb69e2aba1a7fa2ad787e9ac6b408f2259259ed4f744054504b05c2001","priv":"63797b9d76fe914825f90664c4a109501be006ce9049d561b9f6798d5e518c33","pub":"028b851a2e41625548564242b16e4bfe07cbf685e2df45dfdaefb4aee9fcd607c9","addr":"16d7f83a219857fe095aff5c31e30333e06b329c"},{"mnemonic":"sphere diamond electric milk furnace uniform tomorrow fatigue mercy isolate judge arena","master":"f88c8bbcf7deb1636c5d96003a2bab945a518278d9d58782cc9e7c9fba4facbf","seed":"5660e5ac88a5c43cf14cdae6929c3894ea5aa3bccf3307f85f1162880562ca352153fc1955b21e8533ce844e79583ab95df0c4199b9bac4520ca1c2821ab4f18","priv":"700fd3176db1c0988d7c35d5ef2cd109c7f52b583ae665011f5d476e5a7b3ff5","pub":"022efb00f6c83d296a12f787239fb7905b7f0c27e4fbb62ae62fe43a91cffa9bf7","addr":"7e4a94be643599be266227f349d4797fb7befb89"},{"mnemonic":"syrup unfair confirm hunt flee armor limb travel hint ski mother credit","master":"01aa87dd513611771cfeee1a39986b13fff29760f0d4c7cfde678e70fd053909","seed":"0e54e0db6ff149c9c572a14c3902fdddd567c8ec361ba3bcccb3a407e3e5c6767ec71eaae2c75aa1fcebaaa95e14920e803d824ae4ce64b13413734dd4526836","priv":"72258e31f535c698fc029b953da4c0a89448e5633cccf27e3e6680bb895dfb99","pub":"02b9e8b06d6ef0ae29f911d48c7facd2528a45f432efb4f2c1f1eeb6056380de2b","addr":"90500208588f51b84399e4ba62ffb653d513cd43"},{"mnemonic":"embrace travel lake capital dream shaft focus kind card hood enjoy memory","master":"04e38c8074b77f3ae174593b3c69cf2f55bcbdc02bde68079691e2d8f4e26e6b","seed":"f9d883ef73a830ccfea41726a150e50c9c51b9acda9e4d022c0b8852a57f6df5d8f26542ef1bfaf83eddbe276b8f16cb7ef151fc512dbe005e473a64a5b84250","priv":"3c1b914b34fb0e5866ef082def1cc4fa589ac0d7be5813621d22fffe68b0d4eb","pub":"0381cc19aa68c4ce5bb656a1ac6d1f93375ae096826f99b46548b69c4a9d07e30e","addr":"2b82395c3c65dfe0c8a82928bb3a53e745e29d26"},{"mnemonic":"rabbit choose combine yellow permit raw puppy interest spring work wall humble","master":"04d834acb4020196f894564e1fe631420ce9e801b27bfdcf221c3068aecf3eef","seed":"1089688a78e4c6e35761aee10a12600d9f48eca57d239eb4573b4ccf28925cf72561c2921b31bdc5b00a222c537ef8c37773cedc7742f61633f1adcf94dee039","priv":"132eb740a216f2e92c7c5f96605ae61824ebcc4fbecddfd17689a1452fa174c0","pub":"03ad014b5a58c19ea616716e555dc7f30c67b5fe06bf77cd91e179f8218ddf89fa","addr":"8e05459a88d149b8473c0411a175c0b070423e05"},{"mnemonic":"bracket what unlock tooth melody wire chapter verb grow direct spin rescue","master":"f64c4e7a30ad534ca260cd280f1e8a6055e63f546d57e61071d5c7937273b4e6","seed":"56ad6c16f5bcdad95f566e8628fcceec9f00cedde68816077e0b6db1c106fc3203052ad334a0fa4b232157ba09983087d853240495fd010ee416a38cd3158108","priv":"37f99cd9868a0e44bf26a0a3180cb13f7e4fc0e379cb035b30bff23dac6518fa","pub":"0218c1d6b479b4a8ed804f62c84397d0ba8405a1a266c5318130f0c8473a60412b","addr":"224d710168a45dea667015231a3b8f910ba91191"},{"mnemonic":"position nation recipe recipe friend defense friend engine that leave match finish","master":"15aef2c47f684437bf88e44e9e518a5c517f16e163efa24f1ed8414d5e975933","seed":"15df2473af5b7147abb3b7d17da3d9668901d37ccc86ff652c9ebc75b596e8f3939e77ba39194276a659bd86947a50fef8ba147058db007232a4648e8c642ca9","priv":"59eaffd27a11231bdb4b39137e6667fe2c3ed81e6ead13818e4cd966729d558e","pub":"03865fbd6db962067b2547f69afd7effe57fb3dd3342bf5255ea6d5456dfea0842","addr":"0a0c1411a031ffc35f95c214e9a580ba4eae6c02"},{"mnemonic":"sight broken hockey empower hair never bean beyond glue small coil barely","master":"b6847c7371864fa8e19e57718425d640a027593b535d779b815e003098294910","seed":"3ad83d292efb99c2fcec6c12433c5fab6d250ae5895e0da6f1db3103bc12d82527be2853711532331280d3defa5f8bfe58fd325d11621c9a10b5c7712b02821b","priv":"9c3f71613d8ca92267a45b562e39e7e12305473be0a6498b3d2b9fa4c65b743d","pub":"02f9461c5bbb2e8ab8281f1384b5f0d412abb970b82e43ef0f1c44b9476319d87b","addr":"501b211909a1647afffe57f1ab7754d4c48940b4"},{"mnemonic":"pause usual cigar festival vanish direct amateur bone orchard cloud fall fat","master":"acdcc1f311b27f850eade92937a5d7a3a9ea1513e2ab5d08fbacba42ea267c30","seed":"cf00b72b84afc0e4b0e2480c6ae55ec1c5ef8eaa0bfa531432b3e9578fdd4ed5199cd24ab22e1cade76f0b763d901becebe686770f3409a8aafd2ee89f111b00","priv":"f0412e04a062aa3cb70cd68fcd327446121c76c33bc440ff93f63e05cd1aeea8","pub":"033708eb3564ba835b779fbce8ad6d64501333c05d0fd798c92f926256464149a4","addr":"92a06b244988b778f0986502d8ffe1804f78d10f"},{"mnemonic":"caution unusual response boost dose jaguar nice exhaust chunk tobacco cable toward","master":"7c584d3382f394a4d07684df15e04c482ba0dd3c8223a97c92c091bc95d2021f","seed":"17e49b743fda3aafe81ad205605dc7012a2096cef2475b7b12e79e54b625de459de8661ebdfacef620c851a11a0237c781ad2a84e7e30abe42f63d8da460267c","priv":"9666bc12bd5dbf4eb4bc1a8acfa021ba8f862e5551d16dd97e8e49381259196a","pub":"0314e1d0b675c88061db3d87de6bf4728ce8ec170bafc754f4fd846341ddad619e","addr":"9b695fc1b1429ec5f697df05a1f4c850489830c6"},{"mnemonic":"dream walk typical enroll knife gallery kingdom dynamic extend course shove power","master":"a0bef19259cafacafdb4854e37f36b332066b5c571538e970a7057ca0d97db1a","seed":"7d2bc8f37a8215149b75577f9efd29e0ada9f5db469e9125883804162dc7b100373f020667c06bebbcc593452dada790237ba11ef638573210d2fe67c7e7f205","priv":"370caa36a7e3a5aead2d4abb58d57b90299b4a65484613e5f39b0b7d1733e1ca","pub":"03bfddfb81e4058e08f3ee57b06065194c252e2a31b074acbb15cc7c5f6487368e","addr":"6299fc81f05eb71fa12dc69b2ca4ddfb6514af29"},{"mnemonic":"apology benefit donkey van target neutral success hen easy capable smart stuff","master":"dd96dfa3d46e0bf70df538f0da3b94cdbd7c20e170c8875032285ed7f6654ba2","seed":"c3e6214d2db9684e4c7022311861a013853761ae9900ea823fc4bc6d70bcb8de98f100664f0a04d32e18cceeba41539f9237e89400cf94e0017fc66622a902a8","priv":"3add5757d65ed58816a2b90411d2019d206b56692205724d6e412a26da88abe9","pub":"033607f86548d8e432cc23c8c995749dbb0bc0731121b90b952465fadec31df521","addr":"e2073969ad58513da3df1225e5266e03c0917e46"},{"mnemonic":"sing eagle hello october action alien equip tongue riot pave oyster fit","master":"03f58c7803af441682b522de573e37b218fdaeef55d9637c1ff8c58e01e57564","seed":"7b9cca7d24f97bf461619b101ecd343455f9b00146fec997ddf3e1c569c615e010cdac3b6643a6e9f4ec45be954bfde0b0320e86b0e869c121ca0adca7d11b5a","priv":"21f52146c31e0a6ab9016b717f0435b36847d5b72d83183b3719890171599068","pub":"02f391dc750aa937a7b89dffc9543f72e73d6939fda1c0d74dcf99f27140656307","addr":"7719d5badc862468acdaae65afef4e1e2ba5fd08"},{"mnemonic":"proof symptom trap diary biology tail cement interest fruit tobacco soccer prosper","master":"a0e4189e097622681654ff2b346e79bbffb8e4e7be139567fba8b2c6aa427096","seed":"557153d485c01cf29622ee3a2e1fdad38c815a7cfe72eb18c85605a96c30e38b16ab37d9a2be07cbee7a211e911bdaf460d47a07d83f385fdae9217eb70487e0","priv":"57a2ee34ff6e6cc2dcc3fe3b907cb44fcce0251013877d46b04e24c9aa20ab73","pub":"026172d3d801fb6e855e2eb0f1171de15e6823bb13002b4e9b01a41cd430da7f69","addr":"a1a953df8d8ff38614bbed7ea9f704ef98a37435"},{"mnemonic":"wire gorilla pupil since trust open amateur evil chicken razor minute science","master":"bb549a48f5610f28b23bff00b8b0de2c93bf4e093400989632c98feb3f45967e","seed":"b05c272f522ec2c53dc26679e207f2c16b6506de02e8fb231175265445218fd8617222a374beadfe1999ed617d5a1f89e0a93c85bc022eff6468c4646f3ea381","priv":"2e96c265f6cafb5912bb2748ff4e0d4b9e960d0b4fbc92967a73591c6d7f54c6","pub":"030d6aa64cba047b2ee2c5af2d3893e17004e66762696e6375b09e32e3675daa21","addr":"e065248114f65902b5cc3aefcc1bbbec4d7ed597"},{"mnemonic":"deer meat share below retire when crash original boy wide leaf blade","master":"47f23c913c2e53d34f7c5d869fccfc07a9a5a7a2e2e811abf9781c69aebb4ec4","seed":"7f1a2f19bab3b121415adf5ffc709749b009fdb90df35b6f3b7d17d8eeeae0d41e0f37565c5320b90edb50076d712aaa5e606a88a44460cea67cb686f1886984","priv":"dcb974cc926c246144d1e6c1c5b3babeb4de3be7dd24ad40d6fbbbc592f384ee","pub":"038d6fa489c22c5a8d95d6931144de5d61748be81076b8cc2f6a0c928065f098c7","addr":"4c8a5b45086b2514f8094d3579b2d420b36f7af4"},{"mnemonic":"accident symbol token cotton enough false cereal verify item army pulse sell","master":"70b049648afff7bafb6ac1d8f5a2e2d78bea9fa93c2924f0cba70d4aee27dde6","seed":"d58819c9862ce8078a2b4db0a25434f03631bac4143590f1a245eed73d41f68f0ab089753064eb879b2edf1a56ccb83d0fa88721cbe2ce05f2c46c040dbfe14c","priv":"ffccb427730c5e0b70f535e6ed079888f64b56b8ff51a7f5be1f1312ab63567a","pub":"03e4300d0181f901e200b71f607729f6db5b422c1dccc6facd28f7c694975d2bcd","addr":"66cff13f2cef89b9313377ef24b4a1fbe2851a64"},{"mnemonic":"modify monster ridge bullet lazy sweet output acid shove hire warrior broken","master":"014daa371dd0446424b0b8cbeacbf8209c5136f184cb97346e82ed155b535b58","seed":"a5c1919d9fab0049de70600a275930979ad0da3a3da6333feccbb1aa41ce461ff1bfc5fe0fa6db126fcd96887dfdc2f531a522fbb90741aea5cf2c7dbde8d585","priv":"b66d283958f01a72683b8c7637dbe3910575fa853c7cdfc395904dc73723fff3","pub":"0298afdd5257542bc7918449a8e22a85c5c6bee8311ebf6c712da73232252ac3bf","addr":"22a6f55327b02536e91a046e9a8347c38be6f111"},{"mnemonic":"economy relief sort silly pizza trick urge harbor coffee flash fantasy sorry","master":"f1ad3c0e0551b62d80ac54f85aa89c9f241f0601397b1b4e12206a67479443c5","seed":"d3abce93f99595d45d0e9eb84aa18cf836f0d7eef60345b8eb2a9d4c87a8eccb7a0304907adca904503d8211dd31ba96e076a4b2eaa91ae575d4d1f7c815e670","priv":"4aab4cc83a2acee81a99bf7fe4fea79cebf133b1a7f0fa3f583912efd60a8525","pub":"03b241333aa683b8319975cd9ab2b046cb7731459e79a0ee9facfafa85bf0a31ea","addr":"9ff29de09deaa15e5050b5156754f9168ddd6df8"},{"mnemonic":"wage thumb correct coffee salute rice trade middle monkey height hint plunge","master":"22c4ec2703eaaf2de49e585a5ac5b7e17340a07523aa46e35503176a3a53fe28","seed":"b93e4f76a12d3cf66af70b58b30a66d5d648918be7d5db33fc3d573d6ca546f39490d3101dd686185e61f4334f4880f1e52d94919b358806812057aeda57bef9","priv":"d5e46c667c3e8c08bec8c7f7dfb1dcff3a628cb39f1eb6b8e117ccfa96551354","pub":"03219d1b8b9d5562e550d0fdd5cb6227988622d664bdc48fcad2c07dc78b82f1a7","addr":"d8f9a7f71ad4a92a07ea0c53a54395895fac7134"},{"mnemonic":"miracle penalty base jeans inhale state roof size tent chair life afraid","master":"82e0e8ce42e1a391f362bd19a0959529139153e6102eb5c947a64ba6b8699c36","seed":"d43f307257db97baf0e458c06b0f509e5534aea2bf8e2972eeef0a5408b9fd52a13df5b73adeccad68ef61017d63243dedcd167185e10402e7a7c03c88087527","priv":"8a82fb302c3b5cf10e119ff3ab01a036e002e9e2d49f0db0a40d5b6828291bc4","pub":"0233cb5096a11a4c79e54cea37e42139a1198a1385cf4e0d37c704352aa3551a13","addr":"e019149a5036bbaadf18788e3be72fa5292a2bed"},{"mnemonic":"stumble army virtual virtual plunge until pudding universe suspect palace bacon dream","master":"32da9154b165c960e927f0b01be8435f2b6ace7dcecda4416a449a50aaa56774","seed":"72bd5c92a8ab4478fa1dad0761118bd3d9a1ce6322c00f81b527701a74e0c4aeea63238017e64d4acaa92bc61af2c41a734d90567799ddfd15deecefba60988b","priv":"63c7d3a6da6149655588c4fab367f187f940101e99618de1d01963aac0dc639b","pub":"03df276b793ee770816ffd90ce2c45b6f24558bb90cedeb19f17264beb53344b29","addr":"03c20b0d41439a374a8a84ed7f139382b6b012f7"},{"mnemonic":"label script force inside outer pulse style nut meadow design receive panel","master":"bb084bcc5df57ad10abb3342888508e399cf723415d35f5440d0eaf808a56d1e","seed":"07029905fd6a538b5ba773384b0084bd11b5ede855a98189b8ca1b3b0457add4b365f98bdc75874ccaf85882c5f1aa59f0f9b77b5a823178cacbb4ff72d0969c","priv":"91eaffd2ddcb6d055c1ab3c7c87e313ba520c263312a6f3d267d41018d7cdec0","pub":"035bbb055ca818cf974da73d5d87d7cc447ff5b9dcbdd39552fb28c925c2077636","addr":"baa25da619099b41aebbbf1282dc6af9dc268664"},{"mnemonic":"nest decorate update alcohol system panel cool grape mind ivory ski dance","master":"64666caf25c290b5f46e27737e61124c34cd428b0d1a09e88ec47cd059765729","seed":"8db4806ada7b82f0811b24aca3b0e11d087cfc27e3e83b77e29c7d5695a126f577304e443f71b3de9239ca743acd05563c1f08f071eca5de4a9961fec463c372","priv":"eadb5c2704fe1eec4f121714b3a6d655d9eb985fae7d535c6543df13214bbfaa","pub":"022bcbc7676ad7693e97a587886293b6190edea96a339e225adaf3f3d6c493851f","addr":"5310b04cdfc366f6268deaaec22d7c628dc4b1f2"},{"mnemonic":"section supreme grit scan arrow flower rebel trend film sting govern museum","master":"29616b872561d14ef56f4761a29da1c5dc768092cf7eeb4ac28aad39e35ab274","seed":"4b8d6961db3f7b123dd71ba5fdc1b7809a19d84dcf25bd1b443b610cdbe44e5d5391aaaa4374ae9d6ca988b5898dadaf5fb79fcc09859851538842cec7acd36e","priv":"da2a60ed2f49ed3b6c1ff78698e3eedb2d9cbe9a707dd91e047f8a17db4f8e92","pub":"0304262df894cfd35a5330eed4008630b6527971f58075282290a070906c0d69a8","addr":"656996e8dc55902fc01bd95e93edb12e1e7dc440"},{"mnemonic":"vacant peanut senior skill helmet absorb turtle regular inmate hidden captain street","master":"7c329d40208ae70f5ecfe8b3a5930adfd09a2f618098e549c972036bea9fa4e1","seed":"b7fb706e9e43c671d401c374984ba22ab5465e15d8572eef3c3302a3fdc77ec8b5cbe7b2a8e42fca582a74fbefe6b755e442e6389592a0845aadf56975a4b212","priv":"29ea2c32ecc076c6e25dd9cc06b9736d730a8306a007df2410143153ec0d7402","pub":"020ee0b300f1714f378676bb533ed50b8336bd288180f4b55a338bb1ada82e0054","addr":"dfba016ba98cae2160c861d8e5117b5afb76a50b"},{"mnemonic":"setup asset fan host pride decide museum fashion disorder coconut school cause","master":"53bdc111a1d4208f6d04234dcdb764ca24c2f42d7dd1de46cb69dd00594f9c67","seed":"0039f1f4e2000001b1af4c47f72dc1f8347a6b7c0559a9625881eb6f7492c2e1fd186b778fe30540ec9dae03de4b8e4ad414ff5692490a547e1127ab03dfadc8","priv":"899611b8bfbc35a6bbf5cb80c193463849fa665aaad680d576f910bec97dc8f4","pub":"031a099725389ba4d2fbe5509cdeb5893a14a7d743a6f87be7af37701e99be18d1","addr":"5d12852d8132e60b6ed5707c4a589107c7c40c72"},{"mnemonic":"debris bus thought unhappy coconut sing sign piano exact favorite zoo title","master":"c2d02edb9583033a0db1b44443769d5c7b9d545aca03ce9d1db0c90c97e648b7","seed":"411ae07dd870bb2c367b72b5d131853088b501476fe0e09896004271dec37068722bd8952aede6c4ea32371361842f9507c5854f7e95b6e1685d8315c4a635fb","priv":"91047f9554ccc47f0c641022c8779e064ccbdf969827d374dc3419fd462c9ca6","pub":"02a6a4280980aa2a570d54d10c295a94acc7d22040a2299c79b9e72808274dd0e1","addr":"f1c8da3f0438f82da710f6e2604b5adc033cc0d0"},{"mnemonic":"stove romance diet season ramp mango swear behave rotate steak alter parent","master":"039f8ff7fd7e498c643ebecc5cd54018fbe4aceb34a92877746471c3d9446517","seed":"2df95585063422c193cafab822373fbcebb38780acb2349ba1e3d5b98f3a5c63f1631fad81649fd22a5acb3673c3c1cc4c93c6fac5e28bf9ce2c2d25a8215543","priv":"251fc43c061854edae6e1bfd0748770965d44ba5c0ba1cd9784aaa07a17ebed1","pub":"035b5a07e862224a2324a8bf3af20fd4dba7157baefaae03124b51274c4b50b48f","addr":"14f3a5e955fe78fdd87fa3b0eb3ef3195b7bad3b"},{"mnemonic":"cruel equip glow protect trade clever charge gadget valid inspire canal unable","master":"e47ebf7c8d4b5f5b80555c25c8fc89bdcfe7eec0d39b2c18adabfc81c90b676d","seed":"5eb48012a0412a9911b697a3b838605813f73a885a967be55432e73870d703f8db4649a59483b1ba26b739fc39af73e4f3f12d78648e78215c98c08428e5cf15","priv":"fa28e24225703620c1dafbcc09015b04b3839f8dbf170e454a8f6216c01f7800","pub":"027b56b3e5859e3df96d60366bd54a5024ec84efbba102ab1b4c85383f34b61d48","addr":"23890ab09f410c91db179e68ea1d942d25bc0c34"},{"mnemonic":"nest wool midnight tragic exit enemy knock oil tenant survey opera sense","master":"7074c03dfebcb8ed77af28e9b01b77d803c6f2ac88b61a17ae902797c82cbab7","seed":"c4740919291e1af1c7986906a374b081f0386c40601e4f832cdd3f5c4a453a0dfafa1aaa02b00d98f49803a16baa8cb64bcb186c4cf045eb803d8ffab66fd54b","priv":"1f0dbcbe498866989dc0e0b55e6f4b83b93ed2191df2db3ccaddb3f789e434f9","pub":"0396454c50b1a4ce149ed251ebfc6ab07f0d4c42ae84a80c90ca1b64112bb3ca57","addr":"8198e949ce71c8a950f39a8a691f2ec180106997"},{"mnemonic":"brush sustain penalty chalk palace slab learn decide agent private pigeon then","master":"3acf99c19afbfe290a99911c085a656eec61af6f976623c9b6d2d1500e7033dd","seed":"38ed979eea68a00495e0e4c045cfdd1cbe5aabefe628538c35a6debec1be144d41745cecb2003dd870dd87c4246b64e6fa4662173078ad7a4fe471bbc14ab9e9","priv":"1424627b12902bbb162cef56ce5609ac356e68e3ada9005bd92541d0d8766559","pub":"036155ad03bacc613c9fbfb87294091a9bdb7845e58231f4d585603153871a8c3a","addr":"1792e857fca4355189ade1f45066dad9c986e7d6"},{"mnemonic":"autumn sudden learn laundry erode claim exchange jelly jaguar vault deal humble","master":"1a616c74e30a912dad799fe8d65d6ffa77245f89e81a367310fc02d08be13522","seed":"976e55f1fbcebbd1e448edbe90d68d407d80bd5ae197082bcd7ae8e7a640f3246e0057c65271e9e403297b3725a895f15c502284ab342ce1416f40c892bd73aa","priv":"701e7206d44ca9269e1bca00dca53d7287d55fc39214ed4c7e8f7201a68d6792","pub":"031a31e47cc6d75f0652de6698757b8fb2ed1ac181697158f0bd50ec6e7abdcf65","addr":"a73ac9e548e47d6f1f3ec200593a6693297a9143"},{"mnemonic":"priority dinner offer glimpse sudden light work absurd unknown process gas tooth","master":"d969ece198e95be043e46dd8ecb45cd2e5bcc09d8e6b00bfd7aeb59eabcb0ce2","seed":"aa9cc6daf79e31c5e3ed594dd896b1ca39161add5e5a7dba0159f4a8fe262c51d742e66a19f28cf01f32a90fafe5195b0b359e87f5a5a60e31d883ebca08d433","priv":"81d13a5954925641ace6c24f38c68954d782b094002a689500f015a401d5c6cd","pub":"029b1e4dfd01689451ea069e8a193de0b506d87faae6eabd9edf5dc49633c0a03e","addr":"bb2704f3f0270cab3a5fc918b58561bc1505f718"},{"mnemonic":"glide sadness text grunt burst balance ask hair include refuse mom advice","master":"5a105765bef30fb458b9cdc3d6758479f003f96bdfc1d0c21ea1e4987f768ccf","seed":"0131a60482c920093bf7b7375a70bea607a2015bd0bc9deb04e99d214a0c3a4966b21c3bac01544f816a8f4c3583920758e70f8991506cea52c9450c1972a9b7","priv":"41505f80b9b0941d93341815a72fb32dea916912e3dc1b7b7c763fdfb5c541f8","pub":"039df863578cf71fd48dccaed45c21d1cf176c924bd5ed8ec5fd8dc1bd6fcd1a46","addr":"396984e26d5b3a4cc3cf4a5a868ac88776541564"},{"mnemonic":"jealous subject gap april eagle goose double door dynamic robust impose ordinary","master":"ceef22480f535024fa3fc2a7cea96559f4fe8133e45301807d3213ff7e49e0d4","seed":"52d4a03cd2da0a541450a4530565a5901953d1f3ea0ee9440748c1796a5b8ec8ff674a8cf3ee2fda215a720f0fed119ccaa97619f6fe76b736b6ff804c2dc299","priv":"308bee98e778b1037cfd69c0691b6cce81b1935c776a8473a1b3a49bef313305","pub":"02ff9d7bab97fa367a753ded100c11ca4bee7173c04ddb242fd928502a58149587","addr":"7bc8cb221d4de586491bee3016d9129a42fe0527"},{"mnemonic":"panther camp coconut patch blanket census able melody bid sketch protect illness","master":"aa867cfcb578d828df3a6cc4ed1496f3af72f94635ea82574bedf2d88f9fb0ae","seed":"1bf0752a4872e6a4bdbf9f15dcda591184c8e1c61910d958642b376086cb9db6902d7ec0997afa3a0872ae3fa4fa7751605a4b3a3ccb3e4fb5a6afe26e2aafad","priv":"ce6de5f65b6a0cc1e9c4f7751230aec5f2b5401686e7e42b81f1d8eb55f77303","pub":"03f6c0da8154df0baeb15a7bccdd79252c38901ce2dc59e6e08d2d292bf6668a71","addr":"38a888c98b1f68797a879b64e5907b909f81c386"},{"mnemonic":"great history exit include clean have vague stick victory else iron trim","master":"6fce540ed685440130d194a3e8af78417f9b73df783721ccf184807b3973e384","seed":"2459cdc80eda19acec0c704f020082b617952f652b109c268035607139ba2710dcb8dfaf546257f09f5a1b19dce62e83f603f561d2cc2fefacdac6fc902d311d","priv":"26d08c2b95d15b5e1313aa62cc275b10544f0aad3c2ca677426551d5a039a551","pub":"02d165d97ddc4cb451d52895521d7c0cc217d1a004b9a760aa3eee40edcba4ca37","addr":"7bccaf7fc14e648667e8d7001f7b0777bef7813a"},{"mnemonic":"pause you skirt bundle opinion soap wall doctor head method tent lava","master":"b97cd03b20895737ec084f536ba06f459831e6bf2eab51fc4097ca943014f29f","seed":"b661487c12002df5c65a204ef12698be70837ec522fa1bf17d789d0d0cd0be443a819e4c6a494ad8d66a8ebe25ddd833d6ef7d9dd80e3e5e276ab94e0d572e5e","priv":"6860cbc7b6017128bff9c6d48abb20200aea06a7e8c9168d46f51260dd7125a9","pub":"03d7666fd2ac98121523262fa6695c719d3ea6fbf8abac8266b7c8550a498a55fc","addr":"6a2c9c329b85c35721b69d2885cb8ffa3006ef1c"},{"mnemonic":"senior liquid release beauty echo castle honey limb junk carry clarify high","master":"b3dd9a9379270f2bee11afa64bd9e49e4d9f44fd997c383d651fce6b54e8a82f","seed":"4caef4408f37ba86781693fb979534b9c9534972c7b866f74318930f180a2a36875960ceab41e8725ee4e25f56d160455f7d372a7d08282ddb5d7c1d24f14daf","priv":"6d9ad9c1011761fedce7f7947b5325622ffb1cd6283eeb22cf5cfa3cbf214e69","pub":"03f173aac293bdfd7a98b5055feef56e70d07d3e0549bf278bcc7fef559c0a413a","addr":"93ef2c58e7de60d861c0dc994add27a914477d20"},{"mnemonic":"scrub pupil rural fat taxi area example spring doctor ripple obscure asthma","master":"bf805260160dc14eb96077258ac64d1f67979f7702c99c411fa075a3813ad57d","seed":"ece2913f94e791b8c5c37dff29cd19f9725bbb0a1d84e7c3958eca1b646858de1b3188c8dfade823903a4b4fd12b892bf55e570ed68286741dd02aa4ccb08820","priv":"d158bf7adfec3d4d7f941fd9ffccc1c089075fea66fca2039dc4e2b2a7d1a000","pub":"02d5c76872253c3890a8e1276ec4df0c874fcd46d1f7b84462893776b6d14fe972","addr":"a98c98b13f7cfe6fea7de31de3fbd325648a9357"},{"mnemonic":"first shallow crawl asset vocal special rural bomb random pair shock live","master":"a0f6894a4375e267aae6dc2d23843375250b44bdccbc63d1436a2f172214f928","seed":"7518a5b622fa57389790c2484ad833c95e5cf2caa12f71ffb0fad4c9fe044854ff1f7e97b188545679ea936816217d2a26204a0bde6eb7335328dd0c86814bcb","priv":"027d54b7a844be318ea4442113e4cecd464455e239c0726236e2cbbd4c559fe3","pub":"03f5c6a2e2a82d331268c789527b5ae6d3f36e3fdcb344fdfd7e200d5243b075a3","addr":"bd4e60a76b171fde2684d4c7690e8feb7ad06854"},{"mnemonic":"famous vital monitor opera strike coffee shield throw emotion enlist east smart","master":"f1a0708b891c80724310e95b29183394390ec56c3a6828bb4ec394a39726c256","seed":"61f06f35b43abe9256e635720f4e74c8e3796ed4222c8d914c3ceb0f26e48d9ff772040475acfefda1a907f24c369190b48f50feb2fbebd3a84c0c9d3850ff14","priv":"8c2d6f4673846749f8d2e2f9902f28f66d33625d0ba04a11477335390f908552","pub":"03f8139b274b297d1d669fa505ab797a7caea82e76c5d7a3341c71133fe3dae164","addr":"bc940ec509f5275207ae9b086be155154aefa3ad"},{"mnemonic":"flat cushion trend region glove humor below edit ticket west multiply black","master":"2e72ddd445cd917369177b7060dfc92883b4b56e301dd6b0c7049517c102ac22","seed":"85ca476c295a5b708beeb6af45aad1eb6bb691fab82fae7c6d4dca48723f7819551d04395581ff769554ddb94e0d065f4c710afa7674f0c3480c1c2c4299591b","priv":"a57387ddae40887dbb0f99e8c84c092ac1fb8e245ddf3c9909e189ca2b70d52d","pub":"02d19eb0bd72884a3d753121d0d4489215a369aaab5ad3bbecf47f2246c5ebe928","addr":"84141ec76f71f2bb4dcfc9f81fb5333b9cd1ea8e"},{"mnemonic":"domain produce address tunnel burst emerge divorce health frog rare cook series","master":"983fc11c91dcbfcb3c1869da88972b03f7cc51f54db49533c012238ba89ea234","seed":"9d7cf4d9d3c664b8e51db516449d3dac01d290d774cafa90326e0464290faaa6ed6b780f792e80955ee5e6d7aa8ce0efdec13c20ea0fd88e5e40367f6df818a5","priv":"b96264f6b7f9d01e386ae43893c2a6b8683162394bbfc8048dedd4ce129e52cf","pub":"02ac9b86bcb1d8c6991572ce5044b58560ddfcc25a26dc4c74b12271c263e9f1e0","addr":"345821fe95bca3c777328a7148106d46afad6861"},{"mnemonic":"dutch oyster switch layer rapid fat prefer bamboo stereo staff spin midnight","master":"47d4c05e953b7636bdaa099a136986544444911d1fa720575872370f2aaec934","seed":"f120c319991b7564a6bf0086d918b39aef44877b361883f58db6f95551a66bfffd1f9708140c07135d40d624ab78bae8969883a2239b4cb6de5270aa5eef59d9","priv":"968356e312506427072e2e628a136e6e0104e3b9f74d8a00f384da0d62e58a30","pub":"03efbe8d47bd3c1c6ed07bbb357e6ade29f46d70d204f4af19e5ccba2dbec7c811","addr":"27bbceb99a49bc927eb0d6434b41c873ecdbe782"},{"mnemonic":"green claw tiny bring meadow tone improve advance federal talent drop fruit","master":"f0d9b5313bed52a853a177c338cdfa1080fca511d1a3157e3aafac06a6a753e7","seed":"8acbd6c36b27634ea9017b1ff25913a3b6aee3ba578272ea70d753dd1390ee31e9b0a7685c76aae5bd0e3cefb34fe6d077a7aa0a1da2fe3a2b4d8592907f7c9b","priv":"633d8cb5c8aac8d9e8f3e540ffae0f9a2fe54ec4b9f95c4378927be49f785bb5","pub":"03479063239cddb13d026158bb3519ecb95590d0be4abe036668f3bd134ce8b0b4","addr":"5a6321ea26d9fe346e9af87801f30f823cae4e5f"},{"mnemonic":"model famous make lawn wrist busy food envelope town menu hint dove","master":"034f244b9742152cc762f84ab5fcd03ea6020e30d766d209240f4a1a4bbf4859","seed":"79c41d550c0fa03c15114feb0492e362030a17a72d2e3d05064d459ff8a5ae877c2d94243615ea0f41bb82030e0401ea6a51eebef49c520cdf9bd923f9427e12","priv":"3dc937a202f6c5f20a3d57c657f16269b84f52a4701da93bb1786ea0711335b5","pub":"03e26c7ba82177671230c877902563ed09283148d9f83f400006e11c735620d94b","addr":"b4faab756175074173121e758b65bf2663b792c4"},{"mnemonic":"leisure buddy divert feature soda reward moon doctor fence neutral bomb truly","master":"9e8c30422c780174c05f3a5c333e116be6d54acdcfa666db96e0bc3587fa457a","seed":"728465a73b67017cff82a9e1d5b3cc77276f286e491e9621c21317485b78766fe8828fd0f03016ec67a9e9a42c355804c875874206b60d474c09e49e1eaec89c","priv":"90c91a0f640add78f765fa475ba8267e5e3533b0175d29150138b659397b0a47","pub":"02c0b78775eaf3515a49cf8ee65dd95e8fea30340601229dcdf2d2dcb3d39b3839","addr":"0c300d4bc57ff741c4c634a063c24823a18b1ac9"},{"mnemonic":"excite cargo radar baby paddle century average nature tackle blue rhythm garbage","master":"6a4cd79ab70b5555d3815424bbb8c232351a14511aaa3708dc4449ea80fa7330","seed":"7356daf4bbcbf682469878ee2a8e7a35d659d8f19095059e7b59fe14d9893c054559bc2beba994c527e8417f86c3f57a69b3f42954aa9a4647c91155fcbe2488","priv":"ae2aca6963de88ac0a4e709c4f84f5ebbc2d1a18f55be9c036e97fd46200ef48","pub":"0304f4343dc04ad6ea1c528296ac17c9d47a42cfb744e311f206bb154abab6ff59","addr":"ed8d1537a6fcf980fd7297ba56f1638935ae0fc9"},{"mnemonic":"must trophy era antique goddess local green rhythm gift van target island","master":"52e55a55dbb99a2b170c489c99d50811c47be205294704a2d6e7f234b2a938c6","seed":"9797425288539f83d965bea50c769e5a681c7fd6860502bc7d7d953a36fe16d8f0af0e6441a92b421cca44ff0fdc17d6118801dd17814308af5f6f1436a2e28c","priv":"8eacce7419f6f8b94e426adcae21818ce6525f0ba624bafdae44f0838d36676b","pub":"0244ae10f79e38d499c76996fd6ace738b1fc3dfa60042775f200315cb1e827855","addr":"15b8bec456e7346a6446c9679afb13a7e4e82079"},{"mnemonic":"day include route train grow number traffic across acoustic render save gadget","master":"553843ecb6e4e0383bea60a245b7f34c46d8d505806f4d900c7344b2dd4e1787","seed":"abdb30b82471a0c81df74e7d8f9fc2315b7ed04580689d5ad91f8947cffa9375ef2b6c315b695e642f43d08edcda3712afcd48e86583b00bdf5085d2920ddfcb","priv":"ea826d758734cf1965b67fe0133a2b78aecf582aef9efa46c96c61cb512ba593","pub":"0304501989badca08ee05c3984c8e299e5ecfc91feea52daf24cca7f236684ca81","addr":"9bd24b970d41b73640a86beb27f3533d2bf35c82"},{"mnemonic":"safe favorite ticket bus picture tomato unaware lucky spy creek upgrade duty","master":"8be9a1fc81f80c427683964bc29c2eeb135a3ddda8bad041399e63053cc37fac","seed":"3b7f10be92522ba2544356227f415046ba8b2faae9c3dbef1563aee039d00afe8b476743cce4495c264ed01fc095fdd09e3c2159b9b91ae01f49e5610f3dc375","priv":"114c0269408ee0ad873b847793743f06dee7be10da15d0db533fa682a644a017","pub":"03876eec35fcf39732629c5b3ea22c2309a28549335b9f221001ceb0097ee8565d","addr":"fc82a1a044603663dc758d6987b8b699d56af1fc"},{"mnemonic":"slam jar adult liberty small adjust right sheriff cover below space fiction","master":"75e789f7c7da1a83a34d2a2a9b54d4af6403292156cc775be81ade0edec4d566","seed":"ce1684c7045cbd4ee4c6c23d52ad78dca82e426256dcdb3a5f5fd9c635e0dafff12612e981dbe983e1ec5d88d6bc3294b84f8705cfb553923e397da5044b896a","priv":"bae08dd84c10433159fb11a08f8e3df9fdc503e5cd543a4e9231e96468094a4a","pub":"02269218f35c468c3fead430880577410b580b9dd979d68c94f6531adba7dab809","addr":"5e44c323c10036262a3b02974a6d69f953d0e359"},{"mnemonic":"real acid movie gadget duty hole apple inner essence confirm laptop duty","master":"132081c3acbf9bcedb0a237fbd41c882232976e1bf5467c067892a80f5d30691","seed":"495b41f5b0f22b227458857db55d2787c126b832dfae01384ad0213eb28d79baaadd13a365e63cc7c250504c5f9a2fc5bf38e174769c33ddb629a012219a38e2","priv":"44b3ef5a4c7005846196bd9b7987308ba1bb7923687749509cfb733b3aebc95a","pub":"022803c31147ee31f5fbcfbdb2313c5ef39b80b505cc21275cc01e0527dc6ed6de","addr":"063cf369f1f4122821f7ba400ba1e8248535fd44"},{"mnemonic":"banner indicate tell thing elbow mechanic express stairs robust rival shadow matrix","master":"6e76cc6fe8099d9232c107ffb8959c3c6a24badc35579975ffdc29316ecb2b5e","seed":"f1224c30cb0141c0da3f5cb31632ef8cbd88896e5ce98a5290ab708f5c0c084043fb19a4e360c17e161a3e6122e8fb9d3991c9ed7129dd5ee26bbb2388e9b7f0","priv":"215dad8e633e3ebbacdca2ba68a9747ec69de4d5bf794748500768272445d516","pub":"0259f48c2e291b1d0b29890c10ca516ae8e186d06bec8dd2d966d0b336b9a68a8c","addr":"7863b183b3f98f4ed5ca402c3f1bce45a9d58aee"},{"mnemonic":"boat minute dutch sadness globe congress color sort feature boost regret gallery","master":"6be1882f4d3812eb5cc92b5ec4ae762b565596a01b1c4216f16337663d33f85f","seed":"dbcdc6e305e0330f5fcaf159763664a8decb12dc27003831213b93c241d886ce26175c726b6f899e8de4f2b15aea6af2be816eda613f00a3f8bb3ef3c162027f","priv":"e30847af4ca7d0bf9f76d2c15906c955de0d89ce994abf7696ebbe0880171675","pub":"02a6f99d68bb927000aa3cb6847e349fc7f0412a75869cecbbcbf223707b7fe8c7","addr":"eaa5c8da5482c10d473be2c62206eb82312f3ac6"},{"mnemonic":"icon echo become average ramp undo actress sea water struggle interest trash","master":"d07e7e040dc9b1ce70fa78ff50d10345880fcabac783c380ed43885532d79ab5","seed":"9498c9409da005532ea8fa71aa658c9b160c6630498514e52aec48589ab60207f2770e4cf0f47a04a2d46a878b3b2a2f0f58c478d708ec9dc8f54b51ed1f0506","priv":"33a881e07441283721c695c58caadeb68e5576a526ae465ef48812b98d66bd9a","pub":"03226330049e98e5513799ded19923809dbed40226e958f9c36218e8eeb5cf47dd","addr":"b02c8d95cbc1417b891ba5d5c912a6e3b8cb6748"},{"mnemonic":"scan book mean egg situate dash chair water surge major sugar shield","master":"a50dba88cae4daa6db49e8cdbe710a305429ed2e4ff43c0b87cb0b88956f8f02","seed":"ca8bf187bbde692601b88a74c124c967cdd17810a5b4db55254a22278add4880b8360a66b5d91c0ce1c105f78873994361d2169a3d39bac0807a6ab7cb6b894b","priv":"4ecb295b77c3b1296fc214e554e77fb47df10cbfb22b980ba6e9436a2d884ab5","pub":"02c302c47bb2ead02062861ddb4c876b797607ed2a775317c7ad258c97139767bd","addr":"6f6f55426f790072f9752fd182a68710c2b3e72f"},{"mnemonic":"hint sign broom plate brown beauty figure song stumble setup polar road","master":"e4721371d82355bc7be6836a7915a19917d2efb313eab36abca855fc00f4cc7a","seed":"7f654f0582ce0c83d46e01477fafaecdaa7321d095ba1468ecd9fd6c04c67471a2e32187c02178522d11d9b9160de10c6cfd74eef79d6c50d013044a5e6562ed","priv":"612986c34ddfc5e93d93337f68645bcfaa2bdd37c8a6beeb7d58cab6e54ce3b7","pub":"0309c0b133a0af4f468285b0399a3c3cdaaf8c2b29ef61a4f8ef19d526b23eae6e","addr":"dbd39ec36261d32e313a82b4745c47489de5e955"},{"mnemonic":"eager item digital loop loud drill script ethics crystal tornado pride cattle","master":"1ed90fe65b73b2b50464a59268cc2a7a9bac67f2fb167c5b4714dfcb354a24ba","seed":"2e5bce8308d9ee56a168da570cfbe30f0dfa341df84cbd7674f198b4e419d48c65e1ec2b8600a2e7608bd148291c54ba29e5e7ed0a39d4418eb212ac6d7811bd","priv":"ab27d9960f7821dce58d2271652b42f8ddab5edbc87a23ce3177a0454b775167","pub":"02e1ed58d8c50877a6133c16e793fb6608515accbeb46ba07cbd3972c274534066","addr":"54b513aaef1aba872f8f521bd3b86c885563cde9"},{"mnemonic":"asset toilet hazard chief boy over trap session abandon jump wheat group","master":"43ca7686bc87728e62a5406222efe3ff40d4364a7904f7307437d1b2b13ced43","seed":"e04b7a46a47cf1c4a9382aacf36a32da16724eb1ba82f65a3c66679d793d410d3952fd7bb0c735bf96a089521acf7ec4e1f41b179b09dd75645b7bdeeb539292","priv":"e7295badc4984f21bc64f9ba723a92799ebb18acd8315686b5040feb603f03e4","pub":"02cda17b0a54b09d9389a06259a44004a569a7d862a9397d33f9fb598fe374bccd","addr":"f69a0e9ead6daf22623a1235062510360f0dc7fe"},{"mnemonic":"flat eagle owner ladder enlist chair next good solar drum service escape","master":"2de24f772282ea286aa716777f39a9233b38a5a8018dcd730b74bca33c5bcbdf","seed":"d571b7aca82b969fff273b5d4e876188c5b1124684d3ab8056f69bd521b1d030b617d201313ee42068f48dac836443ed6e8725ae2369a955133cf74682e6090a","priv":"232c2f9a62655637fc64e4173855506ddb4d3530807f4dd441a137981d636c11","pub":"025996bfa0d3593c05503476f4c340ff2bc32290affa52a130f2038b72deba930a","addr":"ea8a8d16976a6d922be30695d49a670027cc065b"},{"mnemonic":"hamster such legal analyst despair robot tumble embody coin taxi liquid cart","master":"f78ba19a1149f8dd5ff019f40fe5efaf8e0c5f9df5ef2a1b32df2b9a0cdaed8e","seed":"9d863569bb0a87d17b02c10eef8aa06edbd9ed37ecff427001f2e43cd4f101def938da306ecac02bc5ee83b886857e43f6248eca72e65e67828f243c625de0bc","priv":"5a2a5c105c9393cbec8cbdd79022c23407c556a503ede10645d744f20ec9ebce","pub":"03a14ba5b2261911010d2b2a7602b37dee944891b1e49d075b6729e62627a252b8","addr":"73ff0ff918c11d261453041ee18b38194d3aa57d"},{"mnemonic":"gun monitor turkey humble private essence limit member bike person cook ahead","master":"c3d138412cb4861effe93888a20e5072f887b2b3d3feac310fc8f40bb523f98b","seed":"6c79ddee8365c1c293e6424cd0c4ad5fa00256e9f8377e5692c3c60c1550cd4e00d3f08aa61441609b3954c271404888677aa4f7e075d7e93ada2f452984133a","priv":"865fdc61a6559f4ef787eb57210c09bcfd82ba9aa87a13a09e62746470eff4b1","pub":"03777c19e60200e1fd48d07f17c58c29bf6440aecefe503421bb13cd347bcd3e6c","addr":"a77712c1f028f11782d2805950c79e11d2229002"},{"mnemonic":"bargain doll comic scan fancy soap fragile benefit private sword share milk","master":"b26f239a994fc404b1aeb31ffd7f0bbcb625c881585dc57247cea88ed890b042","seed":"928a043d2007258b4cea3a478dd6b1cb133ca290142366af0ac60086f880cdec35541f080c8cc1d8d76cae811b5138d9de189557b37dc6726ac1df11ccc9ef92","priv":"ad650db8c2457981a143a3cf400ef1eadb5790368965a5fb8f28e8e0dae1991c","pub":"027ce8003309913c6f99fc6a5eb2020400118e3e253f618a418816e772b5008a75","addr":"67996609609bda5dfb179da0fe5e6c7614ffa922"},{"mnemonic":"father accident gaze obvious visa deal plug narrow shrimp motor pigeon sheriff","master":"643240ce9abd6d36e330c0cf47343823c3a3d4b533f6887aaac16d737c8a1a4a","seed":"9ebd83a564ca724a559e57e896940ac08d28a71613e9b414170539b29a5187a2ee91e3fd4bf258256e26ec5c41af2a57fd8dfd30f054403599cf9b4f45af5839","priv":"adc7eba12216571143e05626980ef0644521269bbe8870545b7cab11838537a8","pub":"023b065c65ef208f46ff43bd1051b494f60b1a395543952e14a3d53a6d1f347e96","addr":"7a7b0429f9706215b02ec49c205ec4710705a68f"},{"mnemonic":"reflect slab donkey village mushroom style brown pen mother pipe raccoon mass","master":"bfc2912b21b64143fa0c5a924573da251a9ca151aafc79a9038b96e21551dee4","seed":"fc21dffbf43c67a07c5333e57d238f07d0e1b987bf4f0ec763f1abce471a2e0fb27e733d5098fbeb81ed9cb2d4093ebc9c565b196c9900f39faf4da5448a2b15","priv":"d34fe09f482ae273ca07b2623d5b13180db5d14693025b57aadd6c7c51e640ce","pub":"0329edd6f8ab6a57ade9179889b4f54306a45f89b0007cf9843755b3aaab725586","addr":"c69fbd3b6e36a75ac8583713c1e3207253ec53de"},{"mnemonic":"lunch process rice hazard word vault surprise useful fiber trouble pride mean","master":"12caee441e7ec1c4109c53e42c8cad66704ca395108237f68953b137072d1aeb","seed":"19033ccf459d27009a9072905bae12152c877115e145eb5469f20ac8d763570d2d0ef57cfa6a482da6a1f2c38e7dd43fb6260a4826b54c0f03f0501df94e4132","priv":"4a2616ff7d529e66e3fb200e7531b4a08911a338e6a80c80182a0b98803eb14f","pub":"03e996278cd3c0374adb59287c00a526fa8a409a53a2606fb230748cedbed12f14","addr":"0f96e89ff4109143a838165bfaa158dd64603fa8"},{"mnemonic":"boring unaware cram sun mistake rule view rubber gravity humble detect zone","master":"23b29bcf96e849e44c0e8848e86596eb41908018dccdb91b9fd81b35cfe24523","seed":"25d1e2d98bbe47da86d732e8975f5c2ecdf2e107b641c6e9a259a49014dc35dcbb83ded97117bde45b0e78379ab0b7785aa8e45e2a628442be1ccc9a98e78a5c","priv":"d2c91726674b440f3aa7f105c17848c1e63d123bdc6ddaf88ecf0e6db40b7d1e","pub":"0367113b62388057da87fe1c781210074e0200b872a7243e05a8c9f39ced994acd","addr":"264fa5eeac6f6b37e249f8ba03168cd17aa7e258"},{"mnemonic":"extra hobby pond foil below injury light brief struggle gun visual recall","master":"80cfd73c33f507495340ad9370aa922b436b84d3f584d7f65cf6173b367331c2","seed":"d965ec811bf471bbdeb7aeb4c0fc43d710cb47fa63806f29661bc1aef4873455c6a97f85e675f6da0e975ffabb1356d310daaad51da951de68927933937e85be","priv":"67654951ada1ef3a6e15b3941d0eebbd1bcecb831bb084dac0d70d610ecac13b","pub":"02a1ad9bcf4e359f15d3a447b61b9f18bf2dfd56c52c20f267ae13b04bd60b0d25","addr":"13fe548ac9d9ecfd28e5c5b5d0b865b89b36e8a3"},{"mnemonic":"copy thank warfare visa else rally shuffle gauge method favorite dirt mushroom","master":"f76da13cf3b11641e547c33aed72ab423b7dbfed236da81ff3759d4ba3ba3aa0","seed":"24e7ebdd3792e56171fc46aa5112514874b67fce474a0ea4399b6cab23ead2dd6bc5201826f9127bae7419504de04342357c90a34884015edf094e52a7080d0b","priv":"0bd271e0b2adf4aa3f1b0ea862eb4efe72958d40ef2153e784ede4ffed2d4b51","pub":"03b42c815258c481e1e994ce430135aca9e8546585e82927066067a7f3eacf0223","addr":"95ee4a4c887ab69a23000fa3bb062d1bdd96371d"},{"mnemonic":"rude wolf aim frozen bargain false remember circle axis arrive virus echo","master":"98d35496c8e4f142a6be9c704611257b0520a14b328203fcc0b6b9419a71e366","seed":"ca47d7e6f88717ebac85734beded4a18c6a46e6775cb64b7ea5aa167559f90bb5d8eb9254599df6e09999d8dddc1e487c657c95eab703f756199ddc3196a5962","priv":"9dbde9c5f35f2fdd7bb33208c733021a8ed055af962137c23e06e252cb999456","pub":"02ed94e8e6ff816a2b053b959b261e7d8f8809f58bae0cfb84459d885da88355f3","addr":"38a0dc2d9bdffd96930010699ba2f39fd244c3d7"},{"mnemonic":"deputy among shuffle online fence mouse clutch joy away bean hybrid void","master":"e13cc05ed1c7f3415610c4011f994446c9c9c65df5e1568af347d42b31e36d30","seed":"f3b73f8483fd0cf17bba1032397207d4b593f54140391a91df47c5b8960fe5918b0160a18a1a2da8f3acc6585fccc19911c613424c91b47afab4eef5f06bb569","priv":"d323538934f97f44d23c78be6a5f6005a882126475ba4a7c953b19bec7678142","pub":"02e6c50799013ab284a676715c3c575be75806a5676281144cafd4c17481ea053c","addr":"788f0092868345453e40b0f38d44f4ff4af0f2a8"},{"mnemonic":"panda rule subway ceiling awake decorate forum cluster place club surge lunch","master":"f0aa8e87884a3e3462bd3a230081a29739fc62fafca9aadd9353986d27de4509","seed":"7ad436a119093e3862065629b195087e0162f2060d6e2a1e653be2e894cc9c3c4ddc2bae40f447ef87c0f3b2232420fd938c155938b4ff5fedb3fc7bd26e9eb6","priv":"9db3eddb02be190b266cd3bc93f5e45d126577bfdfb1df60ea9f2f3bc21cd2ce","pub":"03c4e1511881edd8d598fa65bfb4f9dfd93dc1f2f4944c790c2abc650492ce0a20","addr":"c61ee0480e8379e854a631caec02572b91c2b23a"},{"mnemonic":"addict foster exclude debris moral uphold couple surge reopen blind trash real","master":"9c7714714335b5d3bc2057f206da041c5b0e74213413c2cb9c98f521d2890209","seed":"4905780290404b998ba3ea764cf22ff5ee689aa9d783a7f6ccf57c9d2b4b27e4312941a2e4328232b6e0d2e7729cf30978b094dfa6ea369b34b6da35ee2ef73a","priv":"4e5be9010cf1a4ec010712d82bb0d18d5b1b1cd3095a56cced0ce00f430e81e1","pub":"02a18e4eb8623e1f0fc37434d1e18531ebf5e2dc9bf93af3f831cc72389cb3ed83","addr":"7acf8d7f9f6fe56e21ad77cc6e41b3b49bb542dc"},{"mnemonic":"bracket dragon until token cotton giant enter lyrics apart supreme pupil fashion","master":"dc8282dcc0f6d147d01f8ac326a3017380d56e242fe415fd17c0b6ea92dfca0a","seed":"b1a0043eff67b480d9b3b2c0b6676dc17da38225428cec06ca7884060b830ae243d50b659cba06166b6e8eae8d9c9d67a38554f5feee3315e90859b544833dc6","priv":"7c19323897d7640d225bbcab428731abe01c7b6160557476fc0bf97799f6cbc7","pub":"03f1a65a290bf4a7ad8585680304d187f5aeb3e51d44c9941443450270c457edb7","addr":"72caa875e043f9f2d3712a87317cf12c34980ace"},{"mnemonic":"catch unfair toy shadow hobby tuition remain leaf put blanket fall diagram","master":"611504571157c7451ce639658fb04945cbcfde1b2e487f9cb97ad430ad3c3801","seed":"b1b9002dcfec6d7bf1d7068a45615abb4aa076ad65e34f15d3f4006ec82384c3bb7d737fe7680e58089281ce9305ffc178bd1a23e95087c2fed495a20316ddcc","priv":"3fabb4604bf9163d245a293a4b50a4f844277afb9b260100ecf406fdb24a6d9c","pub":"032568275e2158b5b88f191d77cfc23742157a4c03863da61ccba04a29d6a5fc43","addr":"061151483994a2a2f420b081148754d5807cfe96"},{"mnemonic":"divert spot love tragic vast error crucial print snow filter fantasy forward","master":"817b4fb465a25b5dd9921ff72634202f30b98ac3040cd5006ca5e18fa851f4f6","seed":"824e5275f61c11ca6db812ffadec349e61afa0313d4d299c911eaba5ddd38ccbfa39a42d323f43aa4999bf95b997958a6ed0522930bff6968dc9cca2adaaa204","priv":"43919dba40cfbbdc53b2f8c097b6c66a6a391c28d9478fcfe23f29daa4f80194","pub":"02c32a7eec00f33e70c83aa4281d312bfba257bbe920f349dd3b674decf43e3aab","addr":"fb3e6b782cd14938a296f8986daefcb9c1424232"},{"mnemonic":"marble start nest primary author steel video science wire sister tonight invite","master":"8d62341b89e0c045322c270870d8c438ab41ca077b96681b9288ce2b04160b8f","seed":"ff79b45850f134169e1d12bc2c95c5f48933815e3062296a93644c8e88ec70f2faac59c5e0e20b1fe8799989509190442356a67596657156dd7b1eaaeb976adc","priv":"75adc55e735bce443ba38fa066c269d20c986d18d31b9c67a6f41215dc6205e2","pub":"0262ad481271c2b42b162d09eb549bf2e09ff953d78cc3b58f9633f0a2f0a1cb32","addr":"289c5796cb64589fc90d10c269093d9525249ae4"},{"mnemonic":"mesh rose keen want voyage net brave bargain session creek shoulder glimpse","master":"d3230ab8890910c45a3bf9d9d21b8c24a6a1aca2b87467b42c75cdb9204e1f2f","seed":"d61ef67df124c9b43132bf496bb3b18220a28c7c540247a11e25c2dff3ab8b7d0dabbfcd8125f03496a8fc4f888d6b8f8df9828ca406a3864881029e95578c2b","priv":"92d844eea97086fb63381e19e56e3b494af47e7cad3257a5ef1b46e21c4cc4ed","pub":"03be77c3a96734aed00ac4a0a18151670a213a911f3a9d78cf6f4e1fc33b7d0aca","addr":"e3a1325741de4de93501261a3850b374423ec97f"},{"mnemonic":"host increase panel pact gorilla alter cause cook voice ugly crop that","master":"22daa7841a31b77e2d006277ba1afd48ad7f066b4cc2b778926b4d4b8931271a","seed":"c1b9b9f1d4097d76288576869071b7efedc756fbc81bc62965c462fb0ac49ede2fba8110e4afaee3d9e187933fc28783aecfc9ceeaf87f63e75f4bc1eefc1860","priv":"2aedb3ffca9d76ee5070f107bf5c0dcb583bef571b1f498baa8708495f8f466c","pub":"027c929945ff29b6a5df3d4ad096849a270ae3052aa1dc74b7a14d4288421e47fc","addr":"97944948d7d679a5c9658168f6b1ea9772c49ce5"},{"mnemonic":"shy garage devote private degree cradle special fee plastic lonely legal agree","master":"b2150fb985a296a6548cd57d30f25bdb2a51c7163e96207353ad064ed0eba53d","seed":"474ce9dae1748e7f1c4096dc603c17696ec21f329b508083ead0651fe00dbd404a1703a7a90f0bd2f66553af28224c6abb93563527a6413732ec636f234b472d","priv":"2c079aefe8f9d45940fa7ca547243a229ac8aead5570a310ff0ea50e9579416b","pub":"03368684efade7cbe5d1017c8cb099718ee9b7ef52e7ec384d507117d1716faf3a","addr":"e0a6539483dfd061a878096e5cd0efd475b9318e"},{"mnemonic":"marine local subway mixed width zone palm lake sort radar matrix pumpkin","master":"5b1142363291deddd7fda0bd7775c7f2b1bdab5959d17d87830d96c3d07c15f6","seed":"c49978eee2d39326d8950c89a69ce6cfd4d51dd84973bdadb5dc517bb60185bb4354b3c5fca5dd0d10a9d8793c205304d1569af0b739bb796cc3f604edfd8959","priv":"84f09a48e36698ca0fa65597368284089059688c2cca84c72a22510f37989c85","pub":"0262798b5fba21efde82c22dd36df8261745c8f7046704e239996c634302efe8ff","addr":"6d81cb6953042af66fc1fa7755f48f3c3b529495"},{"mnemonic":"morning vast nut patrol owner body between neither earth always trouble mechanic","master":"abe0226f5383b2e40ac23a3cb7b486521f7b7af6b1108f24941fcdeef71edd81","seed":"14189de5d7d30504ea20b94fcdfa6054f70ff63f4378cb3a6715126216b571075879cd9fa1c1694ee16c656d8f8a2020adeef01f56dc814cf341aca1ee9e8e33","priv":"8da48fadffb800299535e930330f1c2f9dfeb45772c58764451079696ab3275f","pub":"0283e5b3d662399b88cac949cd41d65e33dac46101c6be4ab6718d7b7d4c1c2df8","addr":"bece5507c72c79ddee993158241c20ffeb20596d"},{"mnemonic":"weasel emerge sight shell cook develop entry truth era radar panther prepare","master":"2233dae87c1e1a6a98e1df4a573f6139dcfe49db3f2fc663b44503ee267b0c95","seed":"787e8fd15da3e7723f91521e3fe4b501b070ff5bbfbcd2b4af147a6067d751d5d1d992716519ad71b59a41ce329eb282a94cbd15bdef52567c8279c327651bf2","priv":"ef6ec780a7823965a871595f4f0c2e2e7ab07409f39ed825b1efafea19f39440","pub":"03a545388da649a8800ff9fff4bb33f70d94000e4b1a767ceec92f5c3f7b880783","addr":"57222240ca41ace976ea1ac308bb027ea4c64d36"},{"mnemonic":"switch sea bomb symbol foster flag grain bicycle case crystal august crazy","master":"32694a657bac54bb8b77e7d0d84014e41adafde6023723aa5779645c7c798d65","seed":"2719e5e2d4ea727d045817bb8cff56c62360b3c1c66522ee74d1b3e5c8d6ebd98700887d532f9d1f17a64bf683622b44df8dca30ac1bae5e296422cbb7741790","priv":"47b1b2ec336e0257687c8837c9aefdca2f45394bace57de882d7eba6c61da49b","pub":"034ad373c9e20284f233959b09432c82b0e7b430eb625c9edd1d5371a7038baba4","addr":"0900f4003360d4641626e75f3fedd19874b11671"},{"mnemonic":"asthma make clump clap tongue away used shift fiction siege tooth purchase","master":"0f28a391e22257fcc97aa8c229209ad0734e9d0ec8ecd67a203582f2c0ce7289","seed":"006b61f87ea0d6f9f87b0410ebb5bb54c9b8af72d3dc6283152b6e66a299b8ae0650ae528dea7b6fe5c6cf1652bd1b2c67b7e1c9ba5ccdd4111589216bdd02d7","priv":"6ad416539a8a969ee72e48e1b20e9502b90078dce0b5a4245d7cf12d4857eb5d","pub":"0274de9f743f31744d6e7ecf30c74ef56201c71b2ab56d03eb849f2adec2e3dbfd","addr":"67e73523c5eab9e70230fb7a806793419d28df99"},{"mnemonic":"nephew kick predict rotate maximum giant fit love float kitten embark another","master":"dd3cc8f83542c0fab49c660198ab42f48681db60661015e15fe4da87840b371a","seed":"5795b7fa476bc8f7b07738d313716d991a7bc2fb303fcd7012dc54e411bf7111d9c9159c9b28d74d5614b1f3a738bcdd63666b6b14b896311df18609521695ef","priv":"d84c0f29a4b95296e349bdc8bc9283367f5f68155fe92192a74f8a08e3c6e38f","pub":"02bc5af041b0710cedf1cb8895da55349d68ef4677670e89ce5da29f43bb063027","addr":"ad020570cc250019ba70e84f13a0b0c388443c08"},{"mnemonic":"filter vendor slice outside win urge welcome behave valley merge gun talent","master":"e20ace834dc8b88a259a3c6f983a8065ec612205e25da42103e40e968d1bbf98","seed":"1cedd43d959db14714c7e6f2f2caac10f548115f54bf7fb10ff4f178bb410530d8ade24d2dd770f92ccb459ba2dcf433d064c9c24f0d59137a2f808421e189d6","priv":"ca111d268b05839d504787ab4173f2d53cc459ea69c4965bc267db40b3499f18","pub":"0316b77b50cc08af7251303e190a4a12a96ce586631188d5f2ead491825db0b27f","addr":"c691a61bf6a5626bfa6b11f79a36c89e5f7efdf9"},{"mnemonic":"salt vote cook live accident leave define home section loop fiction sport","master":"30f08d7392ec4b37ec8fe00847aaaecc975ad1c98d64625b6e83525fd363b89d","seed":"85abff82357508cc583ff2c4cdfda7867467cc6889fb46caba56624f8c14e6141231c6b26ddd373b9e3504d90ce8b01da123ee72ae5a4db46aebf4ac7b8dda44","priv":"297a3c7969c15e6a3b4325ec6b4c768128aeb99e81dd4aa3cdbbd488e124b73f","pub":"02191258d0ce26b1e6adb0b84d19317fba6818f06993a46df1829a61a682a93185","addr":"a1082e1177f32b0b369429050324ae1c06461e44"},{"mnemonic":"model today toward awful apple actress hard reject discover type must member","master":"58168653609c2e12f079d799a7a344292edb46cfb7cf69a85f8a608acc6e47b0","seed":"bf3a70e5a322c0fad6db542bd4ffee2ed3ff85073e6d3ae01ddb7cf30773250c294529d69dc31f86ef390f30907c6da925cac78d10091d9df0fc97a6e48d9860","priv":"0a7c2b48dd3ec606816404246a0e28c36790c3b2f398531ddb7f1b7aa7e7d624","pub":"03f12f02b0cd5d3146eb28fe6a57eafcb542ac30833b7153e1a9121c3817d0854a","addr":"06eaa20c1fb4487be17eed4bcc57d49f1eeba446"},{"mnemonic":"popular among unable purchase matter athlete fever acquire mom install glance where","master":"fedb71f6c870bf049aa1355f4858675ca0c9a6268be575844d2313365df45382","seed":"be3448251aef8d22e97fa58862ff534bbe7a854eb0f1d574ebb49c4197328783c9f7a1b51202e5a37bf5b2410e3b6e11322977898c75241ed4806d1972102457","priv":"3f27d257bf6a625ea3dd1cc4ca9787c29027c105bda25d3fb44df992baa4ea45","pub":"026abcb82486454ef9ef70f5991f76cb263684624b040f9c7b1265569e65087036","addr":"197ff1aedd06d9015acb4712a4a3ed76b420be68"},{"mnemonic":"kiwi rail hen destroy path mistake protect carpet occur jewel pizza lazy","master":"7690775869756887b1866a93233ce2188202d12a4f0e01501e381b8aa120fd2b","seed":"82e3b78430fbad5184c4c7bcb1873d922de0758894d770edcc7a487ad5c10dd381345aee23f7d996dc8fbd44ed171b3e6e9b6dc19ba5c093925991aff3b303be","priv":"b0af5290094803ecbd0076266db8a65837d363be36738f267df6a5c37ea87b26","pub":"03a4b20c1179246419b5b5cd861ed440f3856b616c4bd5a04e0f9d45de91e25daf","addr":"ba2734783c80b277d6196850cfdbb7f1d599fd3e"},{"mnemonic":"cannon hill absorb ozone play derive duty quick unknown divorce history march","master":"668302ce757bac9a084220ac55e22027595a5bbf777dd344351fcfe668e58ff8","seed":"9282bc012e007bb31f889b9b284ed8dd02bcfe5b14528ffa0e128fe0b3d2bc27d3ce5240d14d581b5a530f66ba3c7146d891595a215b8faca2a0093287155171","priv":"82f9994e259551b52e63de92d93f21222b01c9f371c8cb0e9af15a6ac93b3fa9","pub":"030c0ca2aa6353264f3683210199ee7ebc1c051dc657a47f8ed9e95d50ecca2840","addr":"285debf5e963ce3fbd66a5fbb0841cb8d0a67745"},{"mnemonic":"wild saddle spring meadow spray lobster enroll album coconut chat marble follow","master":"14f5a8838df5b6546b7179704bcd40d5a168393bf9a65d0d5b80658e750b9cc4","seed":"565ab133d78d7fe7e8763fd73fd89ce7a5eeabe6841e5cdebb9a86f867c63f1ac7f3e1ea44029409bd48befc187f762480c3555d62fe9ab653ec2f8523b55516","priv":"229e28ae8746b90e772aa80afbe866cc08b0f49e40d4b1a512be21ea728ee0a5","pub":"026f08a2daea40b6a076b79c26de11bb1a5f74b9d127e79e1310d5a40675d5da0c","addr":"b262603b81f2f0ea718ee4fbcc9c807cb0317e70"},{"mnemonic":"firm mystery spoon brown rely mutual isolate degree stay brick brown abstract","master":"ffae4ec244b67e40036dceb4241614869beecad4e84fe6e41645159d3d5edb23","seed":"4e88efeeda99c5136e145dc0fc827d1a8edc96839311ae7c13e80443b4f8e7a907040d9a38be8d789200314110a91404119e33797e4ff5fdc790404322a7479f","priv":"be1343cf39f3758c560e38fb2b949c86225aa5a32af5328af3f476c94186e1c1","pub":"0379e9a16c9624525c15070ee3730fa02a9b8dac0d1847263789ce4df4ad22b533","addr":"2defd48b2cf9556c1bf6fde9307b17bad70018ab"},{"mnemonic":"shell open sing action exact cream scatter slim right sausage victory case","master":"d0bd4329ae716aebd1dbc4b75a06a4e6103540eb1185de24e180af67174cfc61","seed":"e55f447b58f363276a8f67672f8a4cd6a3bb014b5cda555f8d94e701dc775c4092e313e1aa102f36c98edc9f1c76d863afcec6dada643667f8dc612c43cba71a","priv":"31818b15c3b42238821b8a127a33e90100c6599d6c2ea10797f111b5a741860d","pub":"03c749cbf61f0e5b3cf815c454804486f64dc15e73c53b8957f4a1b0c1b3f36942","addr":"586fd2221b5f4dd31469ce689ba34160ecb002e1"},{"mnemonic":"mistake eye absorb midnight box nose bronze joy body trim give purchase","master":"8a97dbdb939254072731791791f1cb5f86bd61bee1b1a7b1c0220362b73f9048","seed":"ff955704f42fc8451d66092b720eee4153c1edfad5ac3b96bc87d0e1ff27a1655eb4c1127aba45906799197e1a92dfbb040304cc5acbb228f20c828d0f28689a","priv":"81b5ed5d49563e225a164180c53a55ee86432ee9e59ff5c6b1ba2dfdee8ecc87","pub":"0214a81bb7326df6f884b465b655f497c20bc68d2ac499d931945a56a20b21bab3","addr":"e4d30cd4f5b383401bdd02a01bdf1e7003ff9e7b"},{"mnemonic":"stadium blouse more trend reopen sick test trash glass bounce brain total","master":"a47cb7d914af1a75ca7ff6bc0192725a7b4b4daf386a039e9aefd8679f8a9f66","seed":"2a9aaff54cc77a16b4ab472b006768beb7403cbd11cc78aea80e19765ba685abadc03dc27c6444960bcffca5e206dc28f5564fdd05292f68ed825bb06ed6fe5a","priv":"7125901a72c60386b41bb65e8bb4aff64334ba8f2e032d2c032869b3b3a648a8","pub":"03a74cd7b3d21dd8698c7cfa4c30d81a454a55ce294171f30aa70f564f4ed71751","addr":"4a8f9540cb406aa75b0d68c2eb40869a8c25da92"},{"mnemonic":"fetch use target promote later more increase logic oxygen provide original high","master":"f06c1e5b33b4d18d65ca7e0b7627c23ae19da34a6aa55ccc8509fbe9fa25de3d","seed":"8ca3dcf901cddd77ad4f6db2a2e3a48fc1a00a81b372fde0dc926c4006c8cf81862013008d9e119d811512b9f98a84c7c655b071d28c295a5dd565491b961d45","priv":"44bff7bd7e57b709b5adfc54ec47d084d80ab9549479f4707a27e06a06083f2b","pub":"028d649230f94064c7837760706a615e7a714cae79a7b2363d493f9d4300b9a2e2","addr":"23b883cc57bdf88831c021ad62b2ae475e09d365"},{"mnemonic":"boat cause tooth danger box anxiety luggage panther engage human army language","master":"2c0c2474241d615780d2a1f34c4cda2a98e138c7bdfd13c97b341ddb8f5e87e6","seed":"98246a445ad873a5e8ac532ad64643c7e4d0ecabe60c7683996d58afbeea843c61cb9518461270cd2786453ce2ef39cac5818cb1aee0f336da3112be523349e6","priv":"f94665aaee7b5a07c328d74e3f5a51bdc0896f899c739c284bb9d882636c9b67","pub":"039f75b5dd06095b39778b0b5126907ad39a9c034dd5fc5e33f53cc00ecbecd8d3","addr":"af2307aaf8e07bc2feb04eaece0063867e64c4e5"},{"mnemonic":"much youth team what empty door language travel attitude rose furnace all","master":"df555390632d0ff6d8e472ef734e8e3f8f83061d5ddbbb2034f9370310356e35","seed":"f450c859e941b9d5e84126dd4710bd0113adabda8f962d383f8ba54bcc53d0e6e1da264d1841547ebd7c1bdfb8c29089a57ce05b64093653aa57e0ba51b0084e","priv":"89705fd7c86cbe5c18758ab9d4dcb7b1b6df12979c77a61c128ec13672b4433b","pub":"027949604d41872f518c8fabd333f8f351f16d68cd7e79e341f1b8368fe1b79842","addr":"36d1cbb6eb2510e0e3c2e76c2400dacfd9eae736"},{"mnemonic":"hamster adjust rally crew clinic skull private oval vast object school liar","master":"face99969ad94de93d7979a246a8e69e85dc6249bfc71f58ab6d774449e4f18b","seed":"ecc06609969bc31a47adc214324b7f7d81d09e0e3cb32eaf6b7be548f74eac5a9397300f0ff5650bff02ca895db55241d49b7037a54cf70e3fa62a3edd7206c4","priv":"c1d424295f77f3eb9f3a71c3f800d9a5292a2df311456daa694f5f6e12a7abad","pub":"0334e88812c848228b83e80283f709bd9827e991db6076a22f7497a3e73b641ee3","addr":"5f1a48e1eab155c3a698b69f11c0db64a556140d"},{"mnemonic":"dove grab grow giraffe pulp lift judge pluck dignity theme cigar annual","master":"3bb034fc63ce92269589e21c4fe89e16081e7cb3a29ec91f5ffb30bc077d914c","seed":"6f44f1782162a9d78af64e243dfe4d44c1ac521dbc8a00977e5260b6ea5d7aba6c205bc51b6c56b5139274d8b07bb31a728702df2dc2efd8f4f07c09bbff19b2","priv":"c2d5188d3db0a2dd9853104e3978c27d490eec8c3601259c4a4bf6e0fc30260a","pub":"03f7de67b6490d9cfe61818b22380450d5b5c815767c4efd821f3962ef4d430b58","addr":"0b5513fa903ae028a8bc3153b5f50b2df555a77d"},{"mnemonic":"wire silver blouse theme lumber modify muscle among orphan genius diary crystal","master":"28064c9af3e4436ebc8e71b424d749bdc6599b56f7ef1ead765606b4920d81cf","seed":"2a132c76e5b88181c24491f83f72b215b7963972f98c3d165c3a452f88165b459bb6e3756a511f40f9de0d47df6a030e91484a2fecb1648b9f194199143ce49b","priv":"232ec9cf0fce12247cc6335c9c8d54418a7db5aae86365558646d9d69b64af02","pub":"033e30397518d760b53821e3c0b3b43b324853c919b77a855250abe95d3924741f","addr":"dc7c6c94e8f7823485edd555741cbf224087f137"},{"mnemonic":"pattern syrup now tired gentle accuse rib cruel cluster wagon sting situate","master":"39f3af4487b768e1f9c7b04b0f713d4c56a5250994850ea4b20ec010a0879028","seed":"387138abf6c255b137a365dec7b8141ada4fe9fc55d75af013270f2df78328048981e69487873ee8740da3548ba611cc90f5a9315efafff651ec41df50732068","priv":"7270c0bd092550db36c7f5787dc20d17fd98edcf34d3d662085af7dfe604c8ec","pub":"026d65397b9ec78af76ec8a6ca1270cd0caded21aebef86160d4ea446b3dcc2f6c","addr":"35566b676261d80bd15792cbb348b79da3b10f56"},{"mnemonic":"blind force awesome radar law patch warfare defy device exist chuckle because","master":"b5eb133b2b7cdeb1bf5e6d3210f6a26b79b544ecf911f6f8cd838c7a6383e0ab","seed":"8a4539eb40090aa2cee32979ca6e8a23c1dcff202c935319db667cef1d2d49cd2a71f9cc54bdacbbed179b20feab254b932bc3c900d24216c089ac4f5c2cbac5","priv":"45e109f71861222311afe9d12b8a2a26d6f3fd0484b97a96bce464bec83346a0","pub":"03fd6c933a364638f867e6fa329d5cbf35aa9701061743ce5f1f6f6daa7137b3c1","addr":"dc3b76c2afeb877b256d7a9375618d0ec526fd6a"},{"mnemonic":"chef flock stem illegal later shadow tragic story tongue also fade core","master":"b7e82f75da29acb35a7714d3bf3aea45196e1857b5597a25b8a7ef6b69e9271f","seed":"7d02c419d5370133b6a0d0bda76a70b784e530c9a0b389cf2f6eebaa07ccaf946859cf4a7b2076814909f71c9c84ce3c0e45ee8caca9a753d5c2240029ca8847","priv":"555a830f9fc1f99debe0fbefcf34df402655484e5054c3fbd6c6a127e3356675","pub":"0238bc6cfb2c3c335b1564fe159e478d700a95ecc34ef23b7af3d571da8d136214","addr":"d3e9edf902c8dc5fabd17c17db492b15bdc33ae4"},{"mnemonic":"paddle appear actress output peace maid foam share afraid quick into erosion","master":"a3eb9cf3115d6b2f3cbe4bcc4f06e879b837d3b8960853a955f1231b8f95318e","seed":"a6b3ea2da0b83408010911cc091f9bb6d296fb13c1607cabb850824591ee5f69d0a2d4576a4361f3b368d34469f53282a1f9adadab3f8ab12956efa6d323cd5b","priv":"a37a2cf232918206e68593863c195d4319548c85d113456a0531b160b1c4509f","pub":"02a129fb596143418f94ec7bb1272aa1fda227e606c99f86cde12e8e7dd9b06755","addr":"69cfe226958b42832adcf69c7c84b734459af9f2"},{"mnemonic":"they garment wave wolf rubber firm toast hidden grit puzzle march rebel","master":"9c20ee12718c0fea98422c9ae0fa861da154552ffeb9e9991443edf224557309","seed":"99880bead4bf15beec406d9e980b46db9cfccd80473fe36d3471a0be1cce8f13bab732ace2e3589b1edbc5900c6641e088dc7a972bf11541ad475d97a66bd61c","priv":"b38db625f8ee7badcd89d159fa867b7d30ae946f199141f63af8e36c59750150","pub":"03ba880b842c952123b8bdb306fb0ec8c3c39e6bae28f2241242858c5c9c8c574a","addr":"24b75369a914827ee1e2697d5f591a58cf3f7be5"},{"mnemonic":"virus stand margin found heavy gadget depart idle guess siren mandate sock","master":"b6489aba56cfbac38041a70c7442bd298928cc37706dd01481fae719a2cc1222","seed":"fe240f7a830cd984b4b07cd77ba9a4f6b25e753cd2c981231e1458ed6775969a6b032be398838d2b5a706563d575177812361fa7cd35798e9a260b5454f3cc53","priv":"801663a31b3fa9f6788c826388b6d7272c53b0c331843c3ad19ab8f97eafb1b9","pub":"03067a334f5db13f9918aca85c5465042a8e994b835d6491604ea7c6b5d8a4403c","addr":"10cbc824bf4e61e2eb4091c0b1afe0f24ffec681"},{"mnemonic":"logic drip eternal frozen equal defense pudding prosper destroy chase faculty position","master":"df13ceb0854b9a18c97c8c972bc42e85231d26c4cdc87dfd3b8870b8ae0bda01","seed":"df990ef85f0d21de059f96b7037b905062b9e098a5f401afe29ae379a60c611a45cdd93f98f4994f231e549f739478566e71e7793fa750e92630aaa661b30e99","priv":"0395a33ce515a68172c9d8a324fdcb81073cd903f092581beb8ca73e8fd3016b","pub":"025a43eb1e0914d6f3c9269123280ec49ad469058d623230885b1f5dbb5f8dbcfa","addr":"6fa784b66d9a3c27073fc463e5a306916bd64374"},{"mnemonic":"nephew august wise enjoy age comfort hurdle cross feel depend south current","master":"9c01203e9921c9cbc2a05aff4cb90757f3d2e615ee6c6ef2d79100b48a48214d","seed":"19b2972d829bab57e706d21f00356d24cbbce556251aec246c36cd7a5243898371c92ef562ab2619244ab5e4854b9f76f76e2886b35cbf591a154a2e915a8a11","priv":"29af8262b1361dad291ed3e682a77a6bb09378c6503037da5c236b5eee1ccc69","pub":"02bc9c65596cc3f49aadad747651387a44d922de3f8b68a9876fb55e50372f1fd0","addr":"c4e1dd670e6c2ffb3a964315b0f7a51b2116126c"},{"mnemonic":"elder badge muscle tunnel sunny ring kite glide post slot injury crime","master":"e5dd0863b56bb3058b0aa457e17bed226f7f8afbc09c7ebf3cde93ac5912d22d","seed":"cd12f2dd94caf3a76292863e9528acd822c915385d8b84a09326b498ad5f1a60a1750686204b2c51093a3a3003ea558bd490cec607837675d2561a0b6f666a81","priv":"0f8717c113f78a6aaf7d57a93d5e7ec3a0326c119f792e8d16dee61b485beb16","pub":"02be9b54e62d1d26d08c1a2126c813a3a3a1a8d399440471491534dba209b3b8db","addr":"dde70795b05c51858f513bb59e2e0aa9d0330aa1"},{"mnemonic":"lunch cereal approve swing poet fresh sphere spray tower scale clerk main","master":"3c7e4e9876b819aad6e94d6decb42f615923873a72a2ca8b6df3b8b0ee1c77b5","seed":"0f9fb7102299f4b02874a785f60009c616ffc1e74e4386e78ab3abe2124257c7aec23b8926883dfd343f7260bad204f1261bb76cf0233dae8785c5e3dc9b015a","priv":"17b7b6f6184325c77195a60bcb15358b318a53ec2990dd46d8f60d0bf7e2e245","pub":"03eddd35bf621d1a943edeae5f5a5242687ee7f99f14be22f694653e449b77c177","addr":"06e8ee97a22bb6b2d458bc408bee7384749ada83"},{"mnemonic":"night law title fitness critic soap runway patrol carry drift damp state","master":"f7cc66a4812f3a394f9d52a0d83777f22eaf8fef4575561e26b0c636c4beff55","seed":"99acca4b65136a2b94c935892e265d125a1e8ef834e52efdb7b2ad09d973806dc318c43b0805924f5b239b74e6e516615ebf42d0379baee78ab63a2a24d03bb3","priv":"14506c9564b199203e8222c7508dac642fc6f15c981a7ed0fd21658c18660f04","pub":"0205f6b2c6ac7c0785d7f0c9aa85258167a314bacafa71f478c2577b5f9a3f9dc9","addr":"b380fefd78145bbe311a87e314ddf3b33d9bb34b"},{"mnemonic":"predict either bean grace myth scout number used void clown route member","master":"c7cba4de1441602a0b8ea2e11a4d72d7a21cae827db034ed4e43cbb38e608a51","seed":"8e80bededc4b64dac814dcb6e8dc5ce3acfb6f59988f13e24106fa7b3205c72eca6a16ee44ab51a54ab0a07d1e7d855fc75e3bcdd5d652e10a8fe33f623b15a1","priv":"6577584c26c25b3d6b11a1f669030008793f3d173721acc811f2dfd470d9e136","pub":"02e7f1c5213aded8dc3a44b14874cd9098a41e5cf0a7c83b7b3e045b8707d1d306","addr":"17614a88c0e044659cb6f99334fd1d8a83390df6"},{"mnemonic":"actual between river curve keep sing hollow bread theme corn mad easily","master":"e5498c15a99ef460088da7513360cc0e8ed3de095027783154425011575afd70","seed":"55fdc52505724e42ec76e0871860226daee30280de52f3d0538b8cbcc426ecd2d44287b4f1f11c01889ef63a6012896311a81f202c41e70725f3dff333f357c0","priv":"05b5a9a68afd24c6970b1686297582b3cb806729a277a056853f8918a706b0df","pub":"021a01e00670749567cd09de2ed8d0a66f0a0cb00cebb969ec43dcc9d6718da372","addr":"fb0c39ecab019c2d259baeba4819d924f8b9d97c"},{"mnemonic":"route aunt shed razor invite board ignore tower penalty claw absent solar","master":"c0f943565964df86bc140e8a8099ccb23417e7b86bf3c9f4d8aabdf7271cc721","seed":"111f3c5d234f0c02921ba5b30149d1913956a6c37e93fafaddbb3bf437b6709599bd623ad6ca97610717db77802f9c591ba352e7b08f4a06a4b897b9ac25c8ab","priv":"bba974522cb69e90b6b10ddd32f3b313bf5dfc129628bd2f10ad84a742a22cf8","pub":"0341c52f99a97637d09d68695b7b28f0c99d3fc5218d297e7bd95b267d2d7cac6c","addr":"5b4e4a18db22862daf7c43ec209bcb61ee139904"},{"mnemonic":"roof select rather approve must trumpet during more outside off owner blanket","master":"840894a95b0850e37a7424cdf10f73361387fffd96783a988a5d0e27012cb2cd","seed":"9eb1782d3ddbca4be52803a2dfdc85ef8ed55bc9b939246b9220dd911360c7323908996bfdcebf51b87c31c55cf2109c4792cf54b60374bff8f2c46bdfb97973","priv":"2db028d5651025a18a06e2c9bf77282f4bf35a8f54788624ffaadcb0caf34474","pub":"031c85b1faa9a2c9b3ad80328c54eb24cf27837d1418dba04fce05565f91e3344e","addr":"a7e4da90a801adcb7b3d9b947e760966fb4aadd6"},{"mnemonic":"album ball volume coffee motion cattle album wing unusual tornado bread stairs","master":"8bdb565fdd586d6aa72a53ecf88e2c81d48552034401bed5d130c862f179d159","seed":"aa0aeb0065b4c8e008fc9b1363fd4b32c0405359dd99f3a27670874bee96e8f1d76fdc807bd903289fb66b33138b92902ecb0d2c1627ef39353eb973b21b0003","priv":"54288cb0b5530aacba3ae1b16945a783e76ffde7e7c523c0c1e1293f00b5adc7","pub":"036974570620961a4e32d0329c851cb1e4218566056249c534ae92d77aabb73438","addr":"d7e65a2d95bb5d705ee894e16e4b6e4aea4821ec"},{"mnemonic":"start staff chalk volume emotion liar route lady sample mind casual involve","master":"aa479da35fc7c15a84e37a118102c41bb62012d872e34fb4f1317b67f9d7c452","seed":"70a8a8b15f77356e12438aef9fc97e6e6279be885fdcb28968db25d5c7bd2a3685aae1ee882d49658414af583c4339fc97f75f70f5b6ab23d184768cf12472b5","priv":"e1f12833fe2e792a0d959de815678a79e1e86342c7a092faba765979fde1d93f","pub":"02f127e13853937be361e47873d965fb8a6d602426c89389ffb45ee712e86e8231","addr":"4fdff2b288741e9f96b6595136c780d979403e2c"},{"mnemonic":"canvas fashion mandate will phrase spend galaxy good advance dismiss inhale holiday","master":"71707593bfe88f2751e6b381282fd71177be82bdc7b930c89c3f30606e0fd356","seed":"c0367432ce6b26e2d0b279b008b1d7d55e89b50b3dc55244360eb611d3af8c4e2dc6ee92f38e5e32309d4ca3b0b5c9eb9f8ba5edff66650dd052ac01f65ffc7c","priv":"0d53e4411bf7c498836412991515876c671967b55704e2928d87f3559a922911","pub":"03bccf2d3a8927babc9cdb6ae16addef1734739941071149122e780c9afd83877e","addr":"e1f22608018d5a6ad02dcac24a9ff28e065ce2c1"},{"mnemonic":"area thumb debate bracket unhappy muffin liar cute subject resource oppose nice","master":"416206edbc49f77c17405abe51397e5d5b547d300d296334b9848e4a844f355e","seed":"a08e97cf029eef0c4bcb3b2dca43e64b7a358199ced33a114c390ea81bdeef23aa7a4d6255993367426d051a56106e485d78f9b4057b4c50dc242d95484b39ba","priv":"9379dd5b24819205a9c7cc6a30ca2a416e6008af93817087b16778e9fa022fba","pub":"03e0796e8edc870070f179c669ef8acac6ba5d745c954762e5738e6db4d73317b4","addr":"ba2f7b307d6c8ba32f9f39174616def7551c4dc5"},{"mnemonic":"ahead face gloom dutch simple battle either hour abandon slush episode view","master":"5c953db2136db4be64531ea6556d5b93f1731332727eafa12630307dbc65c587","seed":"31d751f855507457129f25ecfa2ab8a50db37da0fb13b62ea4e232fdb8c7093c236502da769a0b3474a9436351b87df03a69c70b1b96c61f1675b8fec00f8ab1","priv":"aed4993762d4e8e31a12b500f8beecc1a3e747c2ff290c4e70141de4ed12be8b","pub":"02c922d80cccb9faa955b2ea0a90d3f72a88d8dd203a51e0aafb36c38af1f4c880","addr":"cb41fed3b24d89ef058d25d7264b6113e0481ce6"},{"mnemonic":"van custom clump rug floor what joy live art weekend unfold frame","master":"ab89a9c945f11e8ba88683d2a13cab657a9c938bd9014f19cf552bfd1db49bae","seed":"fffc2e2b6c4e1ff826e0d68ef4a2660417278cfce527ca55e48c5d3286d78ab4d6ca874514d70164e9040641281478b8c3b078bee6447d78262cc0556b463c4c","priv":"cfea30906faf51cc15f5ea6fd96ed92a40dc4bf80737a20ba37280e63ff1ddc0","pub":"020bcb8f7581415d4fe27dc7d0dcb25e4e4883d1290284f9d3028a6d0d618daa0d","addr":"57563ee5328ec10f7af2d0c44b95e5084afca64e"},{"mnemonic":"icon series document grass dutch green blossom report violin beauty window luggage","master":"9a203cb69ec2d8d19660947de9dd0b46ccbb07c9fcccb63da50fd3383c5f31e0","seed":"4acb08487e8a407ea10685ffa35b1c040e4dbdcb7e18a25c94a459bc7a2224246a00b66b88e6bea84948ed07f818e6e1a8fd4ebd102339773e704ce048a359ea","priv":"64d25c6a43d0aae389e2bc5b8f7d260098d85dd6a8ce929e42597490e5240ef8","pub":"03602214319ab01e65bda4711f9d03be60da39e092472d50492397e8f38d90f8fc","addr":"21fed5aa21ab9fdad79aa757d1408c0921d08cf0"},{"mnemonic":"upon legal wear defy path object fold security stem face question hint","master":"efa4834167d9f43a0807a950687e79ccf9327ec707fa4324ce8bdb40ed65e692","seed":"da5371985f4c48954726882bb278971512f9e4ee681645fea98415bd3ed4c7b049bfed3a3105f4d064b61de443500f53b4996c1fd7d2835d6b3e5b52cf67df5f","priv":"963ebebc47e1bf6c8f8cb515d7044f2095f61a8f77ecc2c7d5f8d1900af3b41e","pub":"0267d73ad34ba786e92207f5485f8aed4c634bccefe1a6c6a018dd30a9969bf1b9","addr":"f7b39221ff5352c17537d201c7d8422ea4c2f256"},{"mnemonic":"adjust symbol absorb winter dilemma reflect private all eager naive bundle lucky","master":"f8469edc4fc1bc6f3b934bc6ff8658f03fe2bae4d9be0b2689caea9f23d708e2","seed":"cfcf7299ef520c324a79db53e24326523e1d2346f173ecdd2bfdb38462a83c290cf80260353ceac5089efdfd9c819c3f0f15ed653ac3c4719b0ee6f0481335de","priv":"44025bd2acfbc6710d64d8b71eeec4934e054ab201419fbd553f6427e33d5197","pub":"02bbe2326c3d6c372c22c3aaf8118742442d237830428824160c5e703c268e06bc","addr":"81986a5c30d253a2b2ad8cec56dfd56ee3435772"},{"mnemonic":"fringe betray observe subway nation dignity stem omit topic lyrics index cause","master":"b8656cb35da2ecaf6b5189d8d252a08a15495afbf756ad1365ed757bcb46945b","seed":"cf0cce225311bc1fae42b654561de73cd2720d5a2e280cf39a1391cda8937fba9e69bb5b73b31e929d39a5f58f9505351f752d7666aa81211f69945ad81feba2","priv":"d55bc330b3ab7d09b0da10c1640c6fdf10909e5c6318801c3e7f877654b8576f","pub":"0222c2696c6480e840a7ff5d106f025d87309a0754a3638fcec93a6c7b8ab1f48e","addr":"2bbcd167f45d4f265075a2aac85d0ab7582d435e"},{"mnemonic":"ski labor urge doctor repair purity winter pond virtual salon number supreme","master":"283171546741970d2804029ce72b5848cc479f32f719b4f28670b00729c9ebf4","seed":"4aff8616a56b882083dc3f586afe1128f17aaae7b0c751bd24109094144c0f7aba69dd63cc4490301a72ac950e6a9c71959fd387629429a65178d1d4c7850322","priv":"2a0f5db45dca9a199102d2b920615cf2cd2c3a1d06d3fe72d5a64291fecfa116","pub":"0294a39a5a6f878929b7c18b0df75c7f42318864d84eaaaa68891f075f51906231","addr":"5173ecedd00d5de79bacab582a5b3de6f0534765"},{"mnemonic":"surge move forum tongue female cart foam coffee hole screen short clever","master":"57dd827d6deb98495732fbf12e0098970959ecdf113d441bbe6bd5064abc07f2","seed":"606c47f8d6984ce47270900cadf4cf5df3461f9293beeeaddc537c2e4b1d3ee7456ec7165c51e267d3c02a8cbbb5a2478626e475483f121ca5856005d0cccac3","priv":"3895a34ceb1de14e7e863759517cad044901cc7c8323e8565bbba98f5f3719b0","pub":"03a30052a8169da7d2342be9b0e2788df3e5ba7b4e6fef048662427476d875f537","addr":"9d9d8be022457576a9bf8a6a48f760fe346f22e1"},{"mnemonic":"ladder now flat food giraffe myth tattoo client party address tip avocado","master":"9986ab1b096cc4a635c2f99e7b191ee4d786b090cb1e00bed2cf18d6ff919aff","seed":"2bdf711ea898c4d62fc1c30a4a55dcec4928186c9d118befa4afd8c52d14291b3254ed926b1b1bbbcfe5ae775f74da99fe2112c7e2c64013b8f3b9de5adc18b2","priv":"cd0cf663053d6ac0d9785c3a56ae190fb8d0da670d6529e2f4572c1388dc6d8b","pub":"02ea2969ffa84be7d76fa55f4a897ecc63ebd61858b7561d41e179a38c7dbf8b79","addr":"a640cc723fd1d903e60a9de68e0a009b5e215812"},{"mnemonic":"change velvet mobile copy column cabin mountain shoe hub collect gym armed","master":"f80e68b799c63dd914912454f68a3c6ee5e01530bc48c130f1a4cfe46fdf2265","seed":"1bf846591febdca13c576bcdadd352b4b8b2ab8e346ab7db8213120d5b2f7a8031144adfe06a47f1d200f69c46bf9dd6dfae8e2d8dc94089b4974275960964d5","priv":"370af5ea565ec8725dbb8f63e141b12480bf43d39c219d612ff28e2a5fef031f","pub":"024f70c3096eeaa522240b50c1ad7347f1df1e764a305b8e618489788bf15865d7","addr":"e5226a6737202e5ea8d44f84620317f65fcd4ffe"},{"mnemonic":"crystal impose connect thing cement grow gospel reopen hand frame child shuffle","master":"a8d5b74cb00dc99c4fefff90a2f203b7e73daec3c7edfc71723894db050771f1","seed":"4e7c12a432766826d61cd07bec71147cd4282b2777b8ada1d09be5b873b9c51fb90ff4f4ea75739254fe7124e24696f66313d75289dcfb64ba5f5c8988b27ff8","priv":"7bae6919d134d0f150972cb0d96b0cbb3002d0d3b70efb2f9b03541c3830ae21","pub":"02daefc7677b5d77ec5fd27cead44fb3bd2d089be2b2e9744c1dcdc2e7def701e4","addr":"dd3ece1b6e903523289949e7aff1de580a536746"},{"mnemonic":"seek rain danger defy olive pluck people trumpet wide pave still cactus","master":"9d45b99a256e90347877aa27845d7ce7a259a8b50f7039852c60c16c372202c4","seed":"9c3f7dcb4aada2a8c204bef9a28c039dddd431f3daf5e70feb240be712cbb3dee3e5b65ed1e681255e9bb53bf762d82b95dad1bdedb27d0e18801ef1c5edd7bd","priv":"f43ce1484840f98f862f7d9c835bb3987fbc0413cf9121233bc29e829c779d45","pub":"02d82770b2af7ebb2a5eec49138178dd7899783a1ae1ea43ec3b2a13d6acd90c4c","addr":"49c6feff9794634c9fa66c76150375637f52ccc2"},{"mnemonic":"cat bid just hat diagram mimic field engine light elder resource render","master":"bf8a65e5cde0094db2ec4a5d776acfc944b0b482343bbe167a4bcb0a3492a9dc","seed":"780befda0890785a11f947a7da4280b897ddac841fa65e7cfbb01b09a46af665c25dba88abf759bac86e02d25d90c41347cd872f8abaedb158743dca746e87c7","priv":"b3f9f0ea5881318a1e011283a4e845ef6fa8e1ec5110bcdbb02cd37c4321c0b8","pub":"0214709b38a99a173a8b030bf9392534ae50d157e9e247f83d271be86872846336","addr":"9a7dc12f808fc5e599e2c03f73147317e381822e"},{"mnemonic":"eye thing material display own coral ankle spell hurry empty pitch sadness","master":"1a7c1facdec5151ca8d52af1f507f45c6614d12836d2604421138339c81dee2e","seed":"9c741558ad4c72086738a34898567312930af3221d13b160a39467d966862405a7f9b0b8718207bff6e26929cd33c578457b0d43e2e6358d488016c6ed7754bc","priv":"960704fe746bbf00a1c8d6bada8e500f2a7155af9f8328b7e4da7fc89fe079cf","pub":"0217e48b2a7ca750baac93ffa241b6fd3cd2422989096e0ff5eaf89f23277efcdd","addr":"13c55c8720eb51b5638331330ca19291213fdcfd"},{"mnemonic":"attend analyst stage network secret matter tackle safe stumble rifle lemon minimum","master":"e23dd8723b90971d4e03e21655e597b5f8110f0388392eff572e88db7f644cad","seed":"cfde90f605bace06583d24be20b13f73b3fd0acc60fcd93c2cb7d4eb6fcf70c885da2da7b709ba036ba20c275d99dd3a6af434b00932addbbd7b51a10cb5ddf4","priv":"ee54ee95a349da703f6d192fec6f776db6141a0cfa80372be04ef8b12ab65f82","pub":"020850e7d1011e08d7fd3ce20083b6c2658dcbdfdc067c979bea22d86403da9992","addr":"d834b0d108165f814c2616c91e18ba8b76a0ce92"},{"mnemonic":"student list element dilemma air faint eternal beach approve virtual fold large","master":"0dbc34013e5fddd9a1fc2d3e67a2d6d7445bc31e8343b4bf1a9c4941a24a2691","seed":"61b310aaa1875695f855a341f64b849774e3746db36303ada42eb60bbc91bc0fa7199c10f853e1def5fbf0a9ff2c38dcd803866d5473808a5b6340d88260cf57","priv":"ece1069e2cec7f5d270c3504e0cd9d8806eeb7ce8ce48a138c92d93d68fc9b7b","pub":"037989ae45a6a1f0ffcf9f73f1910a91bdb1ec39101810f3916d29a70b92232949","addr":"10c79559de51b7b96a7e0aa97f567289c41df1b7"},{"mnemonic":"ordinary fade chapter cheese patch injury better toddler rare manual rug opera","master":"ef81a17192ebb6e980676cacc7c9ace6af5d449a3c976d4f9a8a541dcef9d7a9","seed":"5a277523f2cd468b96db9dc30f8dec2d006035e388ef5348bb327eecd108d9e2815b3575b0f6d0da7560fdee6244194eefedb25edafd6f53019a91063478dca4","priv":"86e5fbe3136bcb11f0ed6dd97e3fc30afc0590d38b637fb19589947bd33f26a1","pub":"032fc8cd996500910443186db8432f24eddd6e04e00aac15db80cb5fcd73b7ec5e","addr":"db0aaf4c44693b2b93a8901640b940104d4931c4"},{"mnemonic":"leg easy evil ceiling candy okay afford apology around duck access ostrich","master":"098f864a847ca737aa5866f14fa96e9e5a6958758b56bc205d5c45f77b2f6fa5","seed":"7e6d013d39c652c2d57ccbec782fd0b9da7610d3c716b1c5f374daabf44f0a21726cf39b503850a5fe7daac263cd12dafe5c81a38b47e59b254027ceb298b520","priv":"1b5e814a954729ec4d4d25e01b52e6a48fc3beec84e43153cf9e5b4d5e61b852","pub":"0207214c051cc66259f234100e278a89b7ca3185ecf264d596ac8a4d3baddea28f","addr":"45cc1c7df7fb67a482dde6285b4dff7087857970"},{"mnemonic":"episode suspect cinnamon recall shell search allow hedgehog web avoid hero expand","master":"ede0db7b34ead2b35bd378d62d1b202ec206a389e27ba6d70813e3f98544f7ea","seed":"186c47e08e2fa1151bdafa4c8d16c5f6d8e9a61600a7ed76257cd3b41338a6d158d34ab84448c34c871243e211fc5082d3a6e199749ecf5bcc3fd8341e2b7784","priv":"d19884813f89a780962d2bdb40f36debddb81d132baa29795022b9498f04de0b","pub":"035f028dd1b126d6ea1b8091ac9dee9a3f44589a166d84a88ee982bc040c67d1eb","addr":"77b6f1f39bd0c5c636763b855396d1fcc5109778"},{"mnemonic":"safe run differ offer valve sing stomach table update base pitch pyramid","master":"1f6e07713ac34f997bbbfa0148cf2cfded624ee8eb4c89a66e2a88af2becaa6c","seed":"9c07c30f9b29fccb62670c57b8bc2210851ae9c80a3aee48c03e6bd35357626ebc6af1226f236508624fffbb52a8cc56fd21d8d2ab72a893a931c4891a33bc09","priv":"94c75ada800b2f73bc43625d569caf9f59f3afab6c2a8f9b023abf169c1a50e7","pub":"03263f919a4f79646ba17e1521c15f30d62c545d59c12344d4ce7d7561f8dd8428","addr":"17a63a29abe177f8bd93648ae4283b175b83c841"},{"mnemonic":"bubble bracket panda fury turkey decide obvious nest carpet defense side destroy","master":"d429dd03c2aac3ad66e8ea577ff48889d6ee006906f0d74be45d935c11f95c4e","seed":"d665e949ccba3afe3d3e14c8328f9d2eed1b3838e97a12d137bb188b4480b31f5f8ff388c4ea3aae961e6e98ae40177bd7ae075fe09aabfbab8140da075e8702","priv":"651abfb657e1b400e230b0fa3de8c022fc6fe33c2a80e425b13a3485aca1bef2","pub":"039d2e95484a02e3be5176e31ad41d9d38fd616aa37bc2f3032594f99b1d4225b7","addr":"cdd7ea2dfee41bc45d88e147ca87f15cf0278778"},{"mnemonic":"magnet setup unhappy slide human frequent notice unfold use ticket raccoon worry","master":"6a50fb7353ff7e3eff6054bee3ed8a156e3f4d65d712ef024ebbd58c11b16223","seed":"15195988c4a41524602465915d8c66b71acb07f8681a81bfcc77f5e8ae0071da3d900792c6666ed7f42e5362d1b22ec5de2856019e1d98d34f3bd977e5d6e712","priv":"e2e5997cea44fa966ed05107ee5347fe245ed436396a54dbd7bb0dcd45b02a5d","pub":"0251022b3f87060b140337f8a192c3037fa423b86f761c8e8b9abe13fdc7e954de","addr":"fb33e8b4227a4e31ee6d062f14129d86c190b864"},{"mnemonic":"extend expect crunch wear narrow crater opera dumb poet tone crunch eyebrow","master":"3a18fd4a5605d4a691097909a8fad033a9eacf3a8a6abbffbea184cb43bc4054","seed":"37661f80614a95188f9bf047b8b78201ba0512421b3fdbdb079d36cff88be9fcc6b2ee25a98495eec605c23eca528535adb4381b524ead9327671006a34d81e2","priv":"66b6e15ade42ef203b614a0d4db859263aab55cf9d98ae2118cbae7a2a8ba190","pub":"0318a0e61048878782f581429fdb2acfb382c59ede2a9c9f96112fc0a4dce78702","addr":"d3cbc69596f7f398a77be321ab4b306cad08798e"},{"mnemonic":"genius genius letter space share rich lava tenant drastic today vivid attract","master":"b1cf44f330ef51b5bfc09c224b83f3f678381b54328d6e407368f34f6b96a7f0","seed":"adbba0a0b7e15f2e7a8f95b0cc9e3b212fd3db246c0dd0b2dc41fe740c2d045aad24b9113dadc5b72d7e53d6c681fd702ae46b6bac877303e1ef4f9b1daa2822","priv":"4bc6a6a787283d7110cf4dd8e5f41b5abfbb302a5bd627cdc78cc419bd77ecf2","pub":"02fd610c024bf89e83940d0a5142c1b716786ae7c02e8779de11b72d3474b27260","addr":"f31609b8ee21b0da14e6d7ebab364b57230a8cc5"},{"mnemonic":"shadow ramp scan elbow cinnamon intact flash bulk squirrel pill zero tail","master":"46e4cc1975a8a363b8834ef06ff7a413a6158244a769ef15a9fcfe8981a0a6de","seed":"a8e143c8945640084294cee0afa09fbe4607059c8497259afe64dadc11b18b2d0eea33617b28b0f80419fed842afcb83fd1ab72c163c65e798bd1668cda9ba43","priv":"914a656705241622201d5ec8893d0d2484de7f4546847b3b2b2bc5afff743547","pub":"02f44993307ba52f2a927c5e7f7aa0baa6036a1e396d1d8593be5fecfd330e8d70","addr":"f966ac61247fc88e7d8a8eeaa2e682a669f17e81"},{"mnemonic":"wire list water over regret much ketchup slim capable proud then bike","master":"161946d516fc549a577b76ca6361f97b95ec58ec3bd2486dcef7f0d9ac9de062","seed":"c9af9f1c0f7fdcd56a69faf1274e563dc8ab739fad639e2032b4c0de10081544112b54ffb36fa1f6dc6419a7c073e0f3b4f2fc1a9ed95e4ee7dd300ea15d48c5","priv":"9f7f59143beb62116a592e3884d34d28a845c557be670278877c34f3749e74dd","pub":"027ab6afbdf54f5e952ac1d254cab364ffd91d49838354b02415a97421f9b9bdff","addr":"3824d1afbe44568f2ff4762157046e8f9bb561be"},{"mnemonic":"elder stereo hurt upon process furnace treat tool hole return base protect","master":"331d45143f0f94ba73de22ff235fec62132559735071ef806fb696e36f113352","seed":"4967f2af0a49298099ac49d75a3d99b99edf1165e3507c9055a9cb39a69aa05f6c07c192984e86f6abec6ab9b7fe8e66d22451aed158a6664ecd59b48932d9d2","priv":"7d547ee935de522967f92d0114dd40bb5c25d4f5aa2a1f70ad291601b7d0eaf3","pub":"03caf4c539853b640bf55c9db886e9906888e05fad3e18668f6c6d529434a73f32","addr":"816e255b2ca6837ba06d35fb834c3c246563d638"},{"mnemonic":"faith ski shell chest left entry admit smooth since hire pipe firm","master":"65f1eb6d72553cefc6fa5202583bb6db0af228fc0f008af45a3301cfbd279e05","seed":"2b778002d9097d84bb24e0dc577059a08e0dc24b47d401a37ece9b43c45585d49ccf9fdc6dfd87dfa2c4a70a226b9879f0927b0f3bf33b8f25573c1b6da98679","priv":"249dd8ddb06d455e6883a7126084aeccba0193a8ca0eadc6f924c16922ad891c","pub":"0390281dd22e02560114169d73fd72ae58ac7a83681a8d985cbf25fb39b485ba1e","addr":"9fb024edfc6a4e72e4fee5a4387c7bf09583eb1e"},{"mnemonic":"junior bracket pigeon swamp morning kitchen friend bike question emerge month faculty","master":"7a3d1634588a1d344986c5bdb8e5aade22a0fe74b5d234bce3591edfbc2279e9","seed":"4429c5b3dbd4db57f6ce8cf79533a746db6c28e1494de3610f225eeb00d2a6fef327aa17d68c75ae552c9c76b0dec4e06c583d531e45d6ab1f7d5dbcc259ae29","priv":"20b7253a740bf61bdee294c67b31aae21f90a41b2bd31a02267a773af049f983","pub":"03531e337b7ed416851867015996fe06b19a82bab432137ac32c914503fdf7ef58","addr":"e225e41ab3e0798b3631d71e753179bcd9226ede"},{"mnemonic":"farm frog apple capital foam behind grid project vague rural ugly sail","master":"22f4185158d434b8084c21d62c2fe3e653f9f1c5e2d592d72188bc36e9194e57","seed":"b92f9f3c0d496fc264acc1c04a42bca773f34137c92bb0f8fae52009166f48ed36d81c15be9e8eee8fe5f1ea2e7e0311a2d2b683285730cd6657ff862947ec4f","priv":"696f26ebb007bf4615ffedcb8a08ebc1b92be88955fb34eb5f08210970d566a0","pub":"03bb83731e190bdf7777058603180bc89a85f4bdd68fab3ecbd5c8a43f0e9bdc93","addr":"9c9f3d7c144c33f06ab476c752227c12149d9217"},{"mnemonic":"spare twenty above tape monster topple brush excuse squirrel pigeon maple fun","master":"a1ee9e1be2cb0f318e9cfca44f99ce405115ff3336d1678d94904efdb71f65c6","seed":"e90734515d14ab1b96bccb6f823abf76f21d47b450841ed1fe9ab7bec8a0e673828d09ee61b6a3f9bac0dfca778287abd21d70d45c596cd606261bd4baacc98e","priv":"d8239cc87c27ee5b6aa13d55505a8b0b2785392d26b0945976b575867b3aa068","pub":"0314db00f6e43e36e5f9e6a30cb2bb595cf4abc4cf26ada7d3bc7502b0e6be8919","addr":"fa50a1e20f2f4e490359a011ae4277ec61ca2a61"},{"mnemonic":"token cruel spot firm initial trouble slogan arrive steak inner champion affair","master":"4fd49883fd0f3b32564fa038cb60f762bcf4e65d3e297649cda474a6de9e16c1","seed":"c8b7b6807f2cef122125ddccd237435abeb2370a33db98f34336a28763996b81c336e7405e782d61f6b391b0ab2792b3788e27e67cf467127a50a6b2603820ad","priv":"e57e8f46f2eabf828cd024b4c7935126c95661bcbaf58488ddf7954b1a4486d2","pub":"03d959dd4d72e64be96260d31018ff3b3cc626ea3b80af1d6c3f9f8ed6452f9da6","addr":"e03c8d22c2e4513ffebefa5ed6e89245d36c1741"},{"mnemonic":"gentle whale exist height dress develop beef lounge apple gadget foil sea","master":"ed29987b8768b2f2636556eb1db2a69d48e6969f8b5ed455e39d6239ef376537","seed":"f4a08df5d89dd733e9b73df8a8518a0f3b632debdea655ea75b8b5b6afcc7c52d8bc0a2cf1337226c2e065ca0d71b6854109e360da8037b9c2a01156b2ca2b86","priv":"656e7a54258d7e2f4c493ae80e28c4eb08452175635cf0cf5a9111bdb6f76695","pub":"02f5f6c66a11efe81e76e7a5d440b9ca1b0dd73429426f4a27f3493f5dc7140927","addr":"e01072fa64ebf18099a46ee3799bffb49b990972"},{"mnemonic":"warm offer tattoo home elegant sausage sister cancel rocket struggle insane theme","master":"a58d8e3b8c25c182f6ba0e9e61b8aab35c04d846d1e44ccf3b305bbc03d839f7","seed":"ae94ef9d748fc2b348be876474f261458e3653cdd3f10448af8ee2caab848195cb802050f7d5d098e737672e331f1e48ee0e14c5924665f0aba855b89544f1a0","priv":"4534e2a9248df1bf1882da1a11ac8401b2db9cd92e5c3698d4610d120e6ff882","pub":"0246a4a61fd11a856a818f3726163b7f2f5f41b95f5ec513554f7237d8c85c867c","addr":"dbcf36e66ca99566f38ab8fb86930494c28405fe"},{"mnemonic":"ill orbit raise deliver fat age remind throw stomach country duck neglect","master":"9899f940702cfb78dbb9fe8d743301e2de5129f943c8dcfc4215e937c59280d8","seed":"789537517312103fd88f70345425c402bf0c665c34baf7d3e24c2c969162afae078adf365a0bc732de7abecda4ef5b12e213f4027a9e0ae621dbb6a16532ecd5","priv":"cb4acbee9d41eff9ef210ca73ed0ac71f353464210fc29b30c863a0c23214653","pub":"032779afee4e50503db9c67efc858d521ad8454cd691febc52540c4cf96ca92231","addr":"bc60e5da2c9e96698f32bbd087bf1b3b599a94d5"},{"mnemonic":"fold concert earth alley broom render ranch worry motor virtual industry bullet","master":"3eaddb6d4a2a73a1f0d094957901b9a53f4b36ef00451c9157a3dc159b523cb7","seed":"479d7ca92dade2e47b3f410fa563624cb9a7419921c687e93aba27d82af323bf1d0f04d12faa018732a476fc626df000f705525706a5e5958afe59891f10e43a","priv":"d0e151f6b58c2f48d0311fb2800e66db4fbd9ec14c92f84654907c5018f81c98","pub":"0372820ca8fc7a193815e924dc1e7fd405afd0b03e39ed8cd01a33bdcd35f899c6","addr":"90b134c6bb272ff804b6117fc1d6f3dba538e3d8"},{"mnemonic":"grid street boss erosion spike bunker indicate erase path mixture inflict group","master":"c5063e2bcf9fea3164cc8bacdac300a35b0b1dccef63e1de233ab39356249b9d","seed":"2c98b2b7703e3cc2fbf089d78184f2cdfcabc7bf269ba3b57e7b033629fd149263063e3f08c0d7fd3a785ceeab1f9b2f7e383fd2dffd7d9e982e7342ec90ee64","priv":"b6082dee45243a3fcf3313715ee9a644bb95d550cf8f275a99930372bf265e59","pub":"0282b6a0a9dfbbe648db6afcb9596fed1c02b8d7264925c88ba7007cd5cecc10c1","addr":"e9e9b9eb2c9d88b080af25d6c5fbf425e86c8f9b"},{"mnemonic":"flock entire rent cricket shadow inhale organ pioneer pottery faculty west umbrella","master":"e1beb66b50360f8bf5253b40e1fae368fe8ce267dd992a951b06896ec8e44404","seed":"267dd95caf4834b42cc69941de4dbeb3b37321af83548c865efda2b3cf9eb983c8244dbe65af863f0c88d2e0bb530d7580a0641d23a2bb98c4b64ac4b38d84c9","priv":"b35f776a36af618b48658829c0a914f8ee193070fd9b88ab34f3b4c35197ea97","pub":"03e030c12765cdf97520be291bd25d0a34aefb295f7749904b9aaef1ece99cb3aa","addr":"5c842dfe540f09579539e1fa6e961f929a5b7b60"},{"mnemonic":"all hurt survey laundry follow earn car music online reopen boring monkey","master":"56a9ee4a3e11b98885540ffd3e639369e2124f9d9b7108894cdfcb154279921c","seed":"ad2252df70bfa330e6a7c48da592336d202ff2f91d1654d34e9139ab644d54a04f88f3664822aecf8565453b58ab77a6785e81ecd7156bd7d560ddbfecd8a287","priv":"4181c26d8062eb8cdd8464524bbd2d82b56f423ee8aff8fe49385ccf98130a4d","pub":"0336b154ca3bca67a440c55add8c0ec34bdda413d57e8488ec58402a61eb93f861","addr":"2de1dd6ffa17618df2f9361a427dfc9689d7df99"},{"mnemonic":"obvious spot fuel leaf company birth drill baby lucky oven since fringe","master":"c75aa6398d3568764b147d2a951f2a93a38ca76f3e380e0574f35a6a69d0967d","seed":"d2793c736f85dac48f03f67c39b2b943b297744268dfad708ce9eddb74c649515632a25468a3d444bd07a0b9c4c9d650ae8676737e8373edd1b0f672206d7b72","priv":"cc9cbdf4b34dfcafad90d774b31313b08c6861e6409d659ee77afb75a1ebfb86","pub":"0251043d6ed5e0325ed3d7cf2caa3db8eec2a63c4c5472421a37831e96fc5a2bb3","addr":"9f75fc6e07b3935e5e8f3a39fadba8252e10cb41"},{"mnemonic":"funny online improve knife crumble critic grain vessel wood inspire venture ostrich","master":"83a4036503ba3499d266352b5d7736d5a16519870f9cac08d4706bb3a57e5d7e","seed":"23ab1c440ecfdba702427c5024503232b28aca8de6cba59d7ee1e070e84a4ce15cfa84e06c84c9907fb6bdc9d73c94abff5ce5c4828da89631cf77a3b091cb7e","priv":"e7cff4b876f286e6a8181763e453faac2d92582b0f9caa41b5aad8235f79ee86","pub":"03f2a9ed2bcd20f9ab0b50a3ed8f7ecca7c172997cbc877a896dc9ac740ebe03ea","addr":"ef1badd2e02541f774b7ccfccf8f550a0da31f48"},{"mnemonic":"elevator scan glimpse immense shoe buyer punch tomato chuckle art catalog coach","master":"ce00293fa3230ba3fff5f53d163acb0a06bcebc77606c6c22166f29d4f84d290","seed":"e06c12a0d1f8bb93596839e0c3c566b5b59421226c1523cb54f86e93e391883486e86807472313115c366495a9973d34b2c7cefd919f0023bf2039e95f22ba72","priv":"94e11d645e24aa3653caecfa6cafb3404a6b107c076fa2fc03d3a2d3127c8e95","pub":"036ff6332fa0db2db68b6a74a68ba0eba2cdff43f896de3d4b3b0d26ad1017b608","addr":"82ea6dd1ca5f8bd08633e5b69cffd8dacbdc3993"},{"mnemonic":"emotion super skull trade tell drum lake scrap alley reopen true prosper","master":"8bd8b2834c0aae5a8a207a40ece04ebf9fec51892b8c75dc7e4dfb256fba3b6e","seed":"6d9fa2ef59c1cd981a97e8d3e788b8bfb9738ebb23561d2eb72fd3bf3741e90eaca1c4e821e711756ad8adfb3c5d355f55fa8f7a41ddf260a85288416228d257","priv":"5adc84e56a3d3e6b43e1d42d01f2c7ab1bc340cbf8ce99c30cd94323e1107d9a","pub":"029aecdc08e10d442234814770ddd891241afabbb088e7dfe41f6ee0edc30a7581","addr":"19ce22a905e023e40b9a090b750870bb4d220d4a"},{"mnemonic":"federal math bicycle copy diary bind clap dose clown fabric hover draft","master":"05e5ae86313b56b4083a77339441c22cac4d675f18089028b841e12d0be35480","seed":"bfc46d250c423dfd4f123a91d8c7d556d484f1b8091132cc6e288fffb4b786d721a87d93414f71fb87457ef7b4f17d65d751fa43d7d67523d95997e357141ce0","priv":"760e1221ff1a283d14bc3fef817244affa4787036a46d3009d53911bb873dd4d","pub":"03286bd104bff54d0d01837e72b47e1bb582c13c78311a73a08dcffdf5d5e6bb42","addr":"d03ae88abf654c466a93dc167be85b7d1d172e38"},{"mnemonic":"parrot poem unique trim auto april under pear brown flag critic ship","master":"438ae7958c45b13e2f2d275f52ef71e6ce20d00545051101fdb5be7e235d49d2","seed":"85ca4c5faa1bd0c0a3b74b8a9f5afcc0849be00a0d1e67222a7b70a9f6343c0c637843756750852dd6247d311ae14f581b642e5c3c4cfea3b689dd1c9d4dd18a","priv":"f725a96177c31ae08f065255578e34f58145b921267c2a2e26f09a3fe0d58e61","pub":"02fb792c0736e01eff225a1cfc6f56ccb8ce70fbbb9c050da326ee5475fde784df","addr":"d096954bbd94038190e6aa959ebb8e6d4c5cafc7"},{"mnemonic":"expand enough goose wrestle title hamster purpose illegal peace patch moon spin","master":"fcd43f2a177b9c6f19b22a5864b41a30e9e4ff29cffad7663a6d5e5574910a2e","seed":"0dcfb2c19f6b6b6560db73bcc164ff84e117fac8a11027b0c8c2caf5c97256961e019dc1ae14752af47abd7290cddcb8400084eb59b8ebe5fe1b148806c422ac","priv":"645c6babf5d7abdf9a111ce1ef7a860466b73c47d2afc83d5091265dec741fcf","pub":"039d7f9485265190c0986c0ea74c065beefaf0b8a567c2502c122139f4f7d0f834","addr":"9de7c6791416a501bc1b3dfa5940e4ede894bca3"},{"mnemonic":"property grow toilet hollow slogan pioneer gate fresh still notable happy peanut","master":"9e289e0abc03cfab28e00b885be5328d88886a24e71d88c120cfcf697e1573ff","seed":"3deae69499f305108ec27c7ac5e844140b59316a354d08a9571e671d5f71ed8a35e060b084deb4ac2f55c3968184f7b625246c5635e8ae85cb511ed87582a712","priv":"3765a9eaffda6c370a95ca1c2ecd178a5a7799034fed7d051a37f658e1db0fab","pub":"039bbdd61b3e1570f585fe38ef1ff516a4d1ea27c9d9a2b11775f9f462b5e1e764","addr":"c0a75bc9853ea8ff8070b22b5e6b22c57ca2e820"},{"mnemonic":"grape arena buddy shrug foot fiscal move transfer beach else alone city","master":"903560571ef0d4cc0fc20d79ff830f6cd360a1ff387bcd893b436b8d8cc4a4c4","seed":"6b232fcb4175b92836ad60e8cb9bdc82856d663558adad2fd001fb71db4eecc55d0a9b0cb62c938c8e699f0bd55f3e773080cb3804d74e4c7503420b2f0640e6","priv":"b693be937db9f0aafcf2b68b9c3ad8dff86d250f4d7c7ce0bee4fec7cc164035","pub":"034178d1a76406edcea9b3e501aa11161cdd14d6760769cbd0070862148192ed81","addr":"91379f6876f01696ede09b112da349125ae3db9f"},{"mnemonic":"spirit march luxury planet review rhythm mother food orphan inhale dinosaur ginger","master":"88b72d270aaad97e466b4c6cc2d5376e0bd9d5d4389bf1c5e5fe3cd60eda1cdc","seed":"ae53ffbf1695fe13e7590e3c0d225621f392ba5885387cc5ba1ddbdff13f83051293be338369236cd2cefb39682019b257a4766eef632cb2f805c226a611d5c4","priv":"8cd4d3c8a6a6a18eef4b30056d4c23908d100a30566ee7ed486f96831be8657f","pub":"030dbc527c5e03ce3a6ba9caf81c44f0dd0c0b0374617efa819a71319879027fef","addr":"345000ace68ff89bef2f2c197da7f50329c739a9"},{"mnemonic":"cause enact push shove account orchard grunt sausage mosquito deny relief sun","master":"d66d0216c675ef09072623aa75fd8a9df788af3cf26b16d38091ba5ec8cc8cb8","seed":"07d83a41e284788319bc0e5b9b7b1849485b2bdb8cf408843184f09b1dededf8ca56160aff2f6ed2e0559e0c005f5f0dab7b3e36500db5e45c55612091818f60","priv":"bcd7f078131f5644373c0e0ced2f33822c116df476cfab34c39dc9a8d5b9aec2","pub":"026c0d381a709b557135d33bd1760de196b604b5d720269ba4e8d749b7737da2e9","addr":"7e268fec12b43d71998eba89c8e0df3205c094d6"},{"mnemonic":"faint jelly paper blush cute hope project giraffe muffin strike pistol blood","master":"3e2eece9505cdbdd0e44e237a90a023837b45c6305c01802fa278d6690afe9bc","seed":"7ff4975b81e3165068c8b861a748013e93614bd56c4af844080373a3c60d0e763d5c934ed4d4941f195e97eaad4257eb55ef4842d43ae648ebadfb82421d5b7c","priv":"d2e9c0441d876bc0c189a109a564404e00e3a7540cb2f84095da70e38321adc2","pub":"02a49b4d38662800e8aadabc72fc7fd3ff080c9c9aef5cc3c3970d6411ca92d2f3","addr":"b0bfe3ee05d361865a65678fa612cc64ba633789"},{"mnemonic":"jazz ten exchange horn junior frog surface blur color pill bonus science","master":"037073838661b05747c80ed9a91ef9fc07db4f4b9a89c56a26a517d74615a83d","seed":"92333a0fcd8cc74457ded4de145027c19de4d04d4e791c0418092ff599869c5a0f7f585692c0de272362e6ef3a8d31fd793e83701f1aaf41a0355a818ec061d1","priv":"04cb6c660a8006068ae929e97db3b29cf55093f52bb7292d212774db7b6544a9","pub":"034a39172b568cbecfe119e7c751086392e0a6b8c5ffc755453596ed74a75bd1c0","addr":"bde2747790882434b15deea070cd36c696c73bbc"},{"mnemonic":"chair practice neglect hawk panic sponsor ritual bid sick fiction vendor defy","master":"1f908e1559b37dc8ffde2f89ca0dbba2202260f6a848461d75cdc8791f53a1f5","seed":"0fcf390d0c757b52a520a2fc8c30dc740b0c481adf1e17a6e8d445f355c3c266bad1671bcdba99fba9c69e473a294c5e3f6ac51027a1048987cd3dc7a52dd3e8","priv":"6444a8b09434528113de487508dc7e30cd184c3a9f52ce20b30c7b6f40d5a031","pub":"0344d47049e125a655b9dff7de696e72689ee478b8b4f13a0bcdc71df4737689d1","addr":"3ad17b540fa37171f21fb9d4cf6cc061008ce652"},{"mnemonic":"dilemma main solar balcony differ bring close popular couple educate hobby mule","master":"2a9a19b24310a9019114beb8d3222df214ff8eef4ce190e7017bae4e45861092","seed":"ed8bd81a4515d1c43c24d69aca3ceb065d0552b79ad01db75ad19c2af3cd23a6a213163f9db3743d3b2fab6bc166954b2285bead616d1a50f036bf144856e762","priv":"1c8c89946f2dee6c92348f5d82506ddd9065f83b2eb8f8d173cf9642b971a0c4","pub":"02855f225d510b6b0d64fa5abfc9639488358272957b9003b1bc697d474577c45b","addr":"df365a12508a519bdf095aa5979b93acf59681c4"},{"mnemonic":"sudden cattle enough output make code tonight matter reduce enjoy file prison","master":"ab474b0afda779ae957963eb8643db33d1f2d879a5ebc2a79b5d8f2f9946223f","seed":"f732b62445528f4d96e0c95b58c4459e8ab7528d47ec2a925e3f5515e1459c952abbfa40fa5cd7f8b7c2f059e3df2c2a5a3813b4c37e322c9f2c8f55e66ce3cc","priv":"5d5af8bd1921fe03619088250bd74a2a1f58bcac8158230c0cc0e7ad304790e8","pub":"03ce0673f40df25344d5f2d85a374d6d40b6ab5b8bddeb5f9e2adc9120e2242c17","addr":"a9f1d96cfeeeeb011efc76ac43d3d121a2e7195e"},{"mnemonic":"oblige broccoli transfer midnight display talk jazz wall fit harsh kick ahead","master":"b5e760adc086f5be87b58aeabc63d74d018aaf2f673205620685c4ec32776769","seed":"3fd39274348d2cf5ea5ab4239dfa66dc65b33a7f4975b25e334d26b67e32ccb4865599f9b4fddfe8cf8a2630cd5787ef0188371d1563c8b9880b33201bf259a4","priv":"5e9b5b2a5628a5a57510469db849aea45ee2989c929bb1fc06918736f115edf8","pub":"039e5e293c463f16d26fd845e92d4503e551013864e5682e1c28bc21072589baeb","addr":"a784eadc4951c1e92e63a83204473dd467f5d558"},{"mnemonic":"prosper must remove pulse wink rabbit shadow harbor equal twenty air muscle","master":"ad8244b4d4221944b73939b681b275f280e1a8d42fe6583f63740865dc6483bb","seed":"a3258a6176d098bc96dced14718524f05669cc6c9f3ba5680a56c8c80b83bf50f32345843aab72e13df9bd8727408ec114bdc0099902857ff1889bd94a3a1388","priv":"32fde5f62df796263bd40267e1d1573f73d86d2fc98076008a5ea46092f8d01b","pub":"03ac2d243cf6580c8c0bdfc091c8ef1afffca6aede2ba5375e149efd2db54c23f4","addr":"504cf87560e4ccf69ce6f81aa5b0b3af45ec2899"},{"mnemonic":"harbor focus scare envelope slow margin boss giraffe daughter mouse hazard step","master":"28831264a522026221c9739fd286152e1123809699955d06149f34a0157a667c","seed":"bdb6f459beb7d6627a96b920ea39dd34b38b21cf45bafe88b66823c5ff02babd02b40ce25a9ce7b4d04696294910a967858fca74b18a5cb46f8afe294ff8d56c","priv":"4834d0601e457f6903e3dc08b3168411c351ed97df34a0932ba19f7bcecafc4b","pub":"03ba2538c15c37b3e0969adeabb0e5e1d093170f7e352ce0696aeb9e5e36150295","addr":"a259af3663c6ed69b149c94f73dddf22e4c91442"},{"mnemonic":"elite magic excite apology sleep cable refuse carpet ethics fortune assault hazard","master":"384338593c8198935c4bd0ced828784a7a26797b5e68212f7b142d25c5e8bef3","seed":"4ad678a061c1fa3351872cc3451c1410f5506919afce9ed62e5dfb05e9be36471fb80a5a94312ab603ec397cdb8ccf9acf8ce405fe7b834c9b14eddc570d5411","priv":"d07f63e9ed19bf7c7f89a598727dabde42bbe0e4cbdfc0dd889f8b5c4998a023","pub":"03d5199768e4a175f47831e46382232c217d58e94aa99f5cfcf7e85887865d4da9","addr":"427a4855b7d2222cc2d54dccc2de0b225b09c315"},{"mnemonic":"awake robust situate increase salmon tell roof agent grunt wear license kitchen","master":"c30d435feb0cf93a3cebc4584e94e00ff40ee2950cb3053ef2f7832799bab885","seed":"b49401408240429ba30ebfb5d9afd6fb74e79971be6b9f76e3810b83ab1993958f2d9f9fb5b75dee68607a2510ad621968d384873d16b4a194c939ff1358a67b","priv":"def35e61e636973967024d7cf9ee2ab0c232348c70fa25b945d6d4a6f220b9ba","pub":"032255b26a5ab86ade14eb9acd3c97e774aebd710b00e81719d04d7dfaad37251f","addr":"aafe58c581f30d3a55de219c15724e2b26dd2c5b"},{"mnemonic":"host proud little want shoulder guess acoustic spin since slight myth detail","master":"200f4964bac08715f0dc5bc750261c71ab2c5e5b70c6e20bf748aed10c4e6ae2","seed":"3fd02e2249ce332bd4f4bd3d9c0ab61e169d0f0c011a826d072153001c6f093e7fddebc437bfd7b9dd60093686147e069c4ff3806124cfeb02501b807205c47f","priv":"005e6a885009202c44f2dfd911fbd495214b9c53d2305b52c6bd7c1a2efbbb17","pub":"03a99eeec3b673289bf785c9482ee94fd906f992dbc11b6e0624e0c98c6b9ddefc","addr":"57a1472c9d2e1d80bad1e84b1a88356d973cac1f"},{"mnemonic":"chimney organ announce town worth finger donate hint vehicle sniff defense famous","master":"8059f2264cbba67a9868b20e25cd6ea7d5ac2c77a86b5f36b9890560b8ac9d42","seed":"a2317e376ede3b6bfc464a993815e5b32c7e2d896b462a3f636cad9e3837f7b454cfd3ce17e221a5f46a8b38a8eca74f6f1508d376ac9ebdbd92d6603178e8f9","priv":"e8e7e337feed5b08f418d6757b0cebc3cd2e037fddf8ecb51b42c1f71d83bce1","pub":"033ccaccdda15edadbe824e3340c73e27303df9c1e40037d0108bb2dea87e84235","addr":"13ae50b4bb386c1c5360404f5f43a2dfd95e1967"},{"mnemonic":"wolf found absurd device rare desk average water excite ring chest version","master":"5dec910d707536db8776b138103e97ed8a34d75e89d79c3657320ad9bd064d6b","seed":"421737ab059b231f6bac2f012f4472ec7215f94a5c9f5cafeeb43bb03d8b936c39afe6a712530fad6a5e4bac736b7ca12e488b3c377faf58476e9e8fe4897829","priv":"17e9344eade9c62a67b5d72af06616bc455ae6f9aa03dd48432efa2a242356e7","pub":"03776634e0508958ca0a9cf4f15b038f544a4d2d0497674ac7a1deef315d80ba81","addr":"63fde94bb741395136727075993a876ea1e59336"},{"mnemonic":"disorder improve say diamond present elegant evoke latin lonely muffin main resist","master":"e8e118fdfad2b3eaaecb31ccdb8ef3ef04e904c543245766a7bbc365dbe37174","seed":"c32d703d39d06d3c61464db0345ffe546f020678149496ee357acb0e93f9575d39ed2309f46917f89a2a299f1704958806f65404ad80aacb295242a91c0f6a53","priv":"947651ee70f613036de219df6d42b78dd5d1504af999c5c2ac7cccc656f578ce","pub":"02e0ca79766b0cdbe0cca0c4d4647e9d82740a6db151f0ad56df0881a0799e56b5","addr":"dd0179f1d538a9c67a42ca0fbc262edd506842db"},{"mnemonic":"define lion input friend used deny obvious basic fantasy initial grit jaguar","master":"e749606fd4eed5b54a718207b0c44af3dc32dafdc192d6f26744fe02b1cee804","seed":"2f67dd5c538358009d036e68d2205254ef898826613d01617abfdc83deca452603008226dcf4569f3faa21a18ba72e3f22ba0beedfc89888701ea2005be93912","priv":"59321cf870be317e697723d132c9f52b20ab43a84efa0f53d3af3b2e1bb075f9","pub":"02e681a3823e20e1482ae8888dd3fd54bf8b9843c1e480d36e72f6ec8f9311ef7a","addr":"c13389a74d1657b472efdd2fecfe736872952513"},{"mnemonic":"caution unable glue tissue toast august decide leisure foster session position match","master":"a0ae79723ea7b60a8234be8bae9acd64bad5472f789c7b8854d95862ffc441dd","seed":"9e1e3a5e11633be7f211cf442cf66b9350f2f4d84829ebe6da491e7749f2a9e6a3656feb5edd3584b723a9c4edbe1f2f530d4360f55bb1d32abe8a82d7383204","priv":"b2a560b474971b2ed48e47c0a27858b70c4485256ec288849a97be5d5eae26c5","pub":"03fe1ac82b75d9ce75fec2839d343954429207d4ffd7ad0e037c2bfe50d7f8c68f","addr":"85bf34297752337da3c79c124508daeab88faa11"},{"mnemonic":"pull drive tired success sorry raise top ship visual filter tag upon","master":"e5b018ed831659613748160d29f704367c0933b09ee12a8582f36ebac586ae78","seed":"1bcc9ff7923777f97fbcc49e5653ce6a7397668eb7b08aad407c77b5f7ddcc62323d8a566c15b51dd6032a57be69054c1273dd50e2c0698e42595258cdd05e7c","priv":"1a859101a7582121828f09eb02de30f34f6e1cda405cb36fda0279b25e1ffda7","pub":"028e0aafb274dcd1f7f8154bb6f1a6ee6207f69d8d7720c96a0d73e2fb2277f92b","addr":"ef934026abefbf19a96bab8f59b9f048ba4c14a1"},{"mnemonic":"grant enroll monster verb voice bench when certain kingdom nuclear save stem","master":"e8221f0b15c9ec7cac1bf373219481d9952b907ee29914d4506d079ae99ec17d","seed":"158737e586b7d0515efcf931d780a88fe0dbf150133a9f605bfa9c4f0df1b7d1c4f56f9745fe76b51640d45735b91e2a6391e3a53f1200824d58ea8cd9bf25ee","priv":"cb61489db37dc70695fdfd54887dcb61f17ba29925d6267f10999ef088acc11c","pub":"0272502a28dfa3e7ebef4c97ee7ee9817756e84733c1030a94820db6d25c82fb97","addr":"d941f06898ddb082c3f8dcdac5a455ba88be2dab"},{"mnemonic":"shrimp forward swarm olive laundry team fashion run tiger atom guitar blush","master":"e726598bbe78396d7d21a334ad1ddfb95285f72e5c6f015f6d76d7b018a8486f","seed":"7f59fa24106d19a2fc887bfdbd01f1f0c2e037869cdeddc6543354a29952785a31146ae4de6d7ceec9ea95df088dda0f195d491d8be3a6ff63bd427532a1d979","priv":"1bcf9039d65d76bac2b14698585db46cefc7de88f2724118dbf1b0597bea6960","pub":"021b8a68d6c12165a3c11dd81c93edcae4437c983751e8c2a4dc509f2e3bd4d400","addr":"9be822a10983a2c398dd72cb43eab3dcc2ee30f6"},{"mnemonic":"object lava wise decorate donate panther chicken enforce hood donkey lock better","master":"f2e88ff11892f72f22627d7cc7048909ff7403a121a23ebd9744a9017a013adb","seed":"9b7eef532cce30af1499362b675c2b6c3e6333f672c102d41459152f380661115461b1dee3613ee5be55beed081d1655b70d941e819754de304beed5269c5fc9","priv":"e19e47c990399d9bb11760b2b00847fe8b85d7a73c5c4fd99414ac37822b5ed6","pub":"02d0840b212aa20d204c8946877da8d6307b50c890a463a123d7eeaf877811f998","addr":"b85c15ec5abcbbb0991c18fbc788af88ff36e66d"},{"mnemonic":"blade reward angle predict text salad auto drum alert grace valve dove","master":"935ad9d81bdf740c8cec1414d56cba9a807d0d47d39b0d9169027cfa38491fd0","seed":"7888677a5dbd757788a5fa116a954610d3234aed529b85cce2bbc34413db5b7fa4b25ae8d9f4bbe367ac55b0dca61724399f70e5404e346506ea2e5708269639","priv":"a6f5a6d090cfb99f935d72d79e77d00c7eaac00b9d23ac1a4aacd6289fdd4604","pub":"0308cd34578b15b3f459d9c0507830af364ed359f27c1135ec0dce2fb9d256b65a","addr":"f05e198f7feacf7a2fdb37f598137f27dfbfafa3"},{"mnemonic":"icon aunt fiscal day never reflect rapid scatter exist fit prosper what","master":"9bf8057a5a94ecd164969fa719a846494e7fb8f40d6571ff0f021940bdb5567e","seed":"0532364343a12a5b821d7cb9c88317dbf802d8b3f24359b49ff32d79882f89f489a4f1f63e4c52ef1ba92a3789e971d56f056b39cc9b0821b3570ececeee9c3f","priv":"0b204e7b6e74dbf35602dd6914458af9860593d672f3409ceb133e431b3f6cc2","pub":"03d5efdd40e97200646f202db82895e2bbe0c16f2cb29bff1622169787433ffa56","addr":"472be066059dfe1d34abb3564037b0149137ff75"},{"mnemonic":"mass wing office match couch tent tumble rug beach mass october already","master":"1b63074ed73326c5629aa6bb9863f1a3cc2369f50f05aa1281d6b208a787adc5","seed":"8e96902f01dbbb458201370b5524fda040322026f83c8f77bc2e91e843ce6b63581b979f71368038e81aa627e3a10897692c1a215fe27a446ac914f043ff8f75","priv":"bf5a466ca37a2c297ba1ee4ff4ff26d62ff44b98b02a5713fc4b2f7427ce9e99","pub":"02827ec005329241a597b6c7faa5e92a0624179425549777ff922482d2a1f48d04","addr":"67f3445c15a8f34a0a5d374acfabc072c3fdf74b"},{"mnemonic":"lottery skull bleak sand drum domain rice eight card frown world much","master":"8a09b9b68f5f87795c60d6dc08146a2780f410f44560f4519194eb1dc6ed26be","seed":"f2dc56a6fc9f382d54b3d92785724815c9b78866ad586448cd9ca06e59d713dc136f41425b16b28d45ce094d3f817aed13bda99e73659a77c06064a1fc123d07","priv":"09b6ccac9c9109ada552b0982202838b3c1e53a816748176c2a3f6c04681300d","pub":"028211fce683bfffa2067cca19d0e003e9e86a066b40db4e16756723cbb525c042","addr":"9853d04f14981533312e56c75a32f10e98d543f8"},{"mnemonic":"advance poet beach endless idle castle inherit jaguar wife grief frequent wide","master":"c16f77a5a5931add45a26fb906cbffd87c7e5ef3d146b83df93e5a3d5b9ca9ef","seed":"26b3842ad77946bb585a05ef148b8ebde2994c800d30224adc7dc1110a4605c02a6651d30709f5f8c0a532c6976504344887bff1503e6afb6cb8b1ae0f909f66","priv":"58a69857570ddabf8a5454e97655e1a833194b64ec4421befe3e0d9e45ffe97a","pub":"033a877e03d2b871bb879b53196da3ca45459485eb38eac76e938c62979d23a1de","addr":"0bf6320bffe291c8f1c0676ed864232cc84b4e57"},{"mnemonic":"stove tennis wait major grass network mountain before inhale glare bird moon","master":"b1bb7df0cf5b8143e91787646825f92d175664fe68751c114824d5a374850672","seed":"73f5867400033e088b346620983d128294338a0988a8b4282999a2a960a26caa12686db680247520196fa9a1e34901841d999b4fdad40aee63834dab615cdf43","priv":"37af5d2736154cfef5c59e974f1954e541683331de3555976a9e9efffb7c960f","pub":"032c5fdf3604c6ef170e65007bdb75ded577aae6652f099ee41d4ecfd1020cc065","addr":"7ef3d9bcee0b3e3d131fab4dd7f79d6dad548889"},{"mnemonic":"sting nut gasp like spread sponsor march behave zoo vanish noble dirt","master":"9f74f4075e987922ac30b7f5f43bada33f0dd385a4a30a1ff42718fd43f76c93","seed":"4d6e6b8a41454924355067b754e619b663ea585f305482febb6db99d26d62fd1a1a5441b7fa1d02de242f0dd35c965ed62fb0d9bc7a857ba257dec94ce462a9c","priv":"53afc4f00fe2a5003c77e92b4d0837d7c8fc54b4ec1e76e0434ef38aa3712850","pub":"020f710a1596997eb3a95815ef79efb5a7204d3bc5fcff171e067f4c6b3692f5e1","addr":"f41ded9fb4ec7cae1a9e8aaaec7de6309644a2c8"},{"mnemonic":"tent trumpet jungle into loop target myth analyst symbol ask december rural","master":"3a8fc48e8e08afe8b32bc8e9c25ada84feb1b84922ea10135a590927eed2b3be","seed":"7c9d5d2681c85e71d49392aeb0502fee5643e8a6ed60c4b84bcc202a5bada19a99dad7f88e365abb6cce65985117d642ca1ae4733a0863e1cd15fad4edd1e8e0","priv":"56b266ef3f1a45b33ea1465c15dc6ec5779289522c8b2b6588c8cf2c182d2fbe","pub":"0206b8e7a5350358fd35bbf173d9ce5b0ec91ee8c710837b9198b5d5ef327a11e6","addr":"fb059b5167a4fb3bb07c0b3b3516bd6fc84f66c5"},{"mnemonic":"juice barely airport rigid miracle afraid funny combine address guard omit idle","master":"787086d46e0777b03c3774933f35e4fe65b4ff069de5ca9b09cb84caff915ddb","seed":"ed6db1f257a45468761be9dd26ce88f78ed7ed1c488e4affb3a8f8ff8550602848ba2fa284c930cd387188ec4f057c6865964ca93bd181bda40e4059c4fc4d28","priv":"9d6f46f7c329d6e57b2ae84150c23c983bab43e7e6ad7190bc95dbeac977c617","pub":"03ec60e506de8acf2a518ce07e3394421450db339cdb6b91ca52924b1e1c3afe22","addr":"0a4359701fca7026cfb2d196df28e34337a34960"},{"mnemonic":"uniform enter number remove obey arrive print version cake alcohol swallow mistake","master":"047c71cb3066ee3fc151a4dd961a06595d63b393c54e77c756966fbca8a749bb","seed":"3db076729af5e872e92581e3c8d8812b60dbe4ed85a59f034fed43c9c4e0015fc4363643dc8ee5ade53ddf68af54b3d63d6bd79457eba414f45bb0c809e90af7","priv":"557c62286ae6440a2a0b17ea98dbb4992333093a054b59a37345a61de1b871b8","pub":"03ad3f7e2e2279b486c88e7e12d2a97a3860aa31a3854987853a1d73cde6aa734d","addr":"ff0a8d5fca9824e71cdf434cd868c12957fc61c9"},{"mnemonic":"split lecture predict rich inflict flush bike sound credit girl pudding ridge","master":"12fb50c5a687fa1552f07cc9b56d6f5ecb7b68376c3cdae621190494ea279f9e","seed":"29d6e5b0c2cbb22e6048158990f14be89c6c2416b36761c2f4d4de70a3dee2c7f9e86bf6dcfb07e99ac1d15f66f7393fc48b00195b0981f0699652a9f3f0c340","priv":"b02635341dd14b0b5e701222337dce09c3426a6f80370b06dabc31c417d9cd2b","pub":"0365f2fe5611808dd6fcaea62ad335d9b992d1b8f91333d6dc7b96c0b3ac97b209","addr":"ee3da08bcfb0e2fd4331e8b1ea3c0905c6c2f005"},{"mnemonic":"fiscal deer minor dwarf measure goat enrich emerge act view cancel shrug","master":"4cb7b43c2efcc7f6e6d84d5b47ca352ff8aa3c1c44fd8d2de7dc75b3fcb310b5","seed":"f48a89c3f2dc87ad41069c06d0b8751881c993c5895cb548b06974194cb410122d1268a9f962790ca461e875148cba07c216439df21bb37a06600456f5bba35a","priv":"b77f4a13edd7f5ae33725c340b1f8982fd02829ba2756d70f4086dc4a38a5d21","pub":"029844babe3ab249b969895b2b0c6d3cf7a79104721e96ea3b24784a4593fcb7e1","addr":"20a6d611a9ea918d6697d2e09900cf4fc9f676c5"},{"mnemonic":"expect speak gasp outside word glue fluid neutral casual train left mountain","master":"a774f13c1b5a22667ed1d3954ef64f9ddf3f841890b894660bb291745f8c5d23","seed":"7e8078aab4d3b5d121cb17213ee1a06fe4b0ec7d95dbfccab17cd0cae6917151197385c2f75dfecc7221ad518d5899384c22d2641a66250dc581096ff5326a6c","priv":"663b4bbe218e2f6efaa30e02460985ec708e76abd845ca365de83d2bcf547680","pub":"02b78aee27b6c719f89f3f21c55e8c34b9b83e94864c894188a7e39c348622e5c2","addr":"1c716ff8774a14f48623598e80fed3877d974bed"},{"mnemonic":"grace clinic host vehicle drastic april sibling response laptop escape creek neglect","master":"0f2dd943aef0644bf150f38baddc31c3c2a95b813c1fd893ef936f36c6972aec","seed":"e21b62815782f3954ef2be4e9551f7ba47d6f471b566d3b9db6a07d29b42c2edd10e500c7e701ba60963315f21afc2a4ea0d4c187fe4e08f775d3439279572b1","priv":"df825cb149c95352cf0bb17fb8b12cb686ca246c76f7bdfd8ae4633fa18ec30c","pub":"029bb63c2664225dab9c6a93294891127728b77e7d9a9a8e187531f5f154ad36ac","addr":"55266b5a04683c1d61e2cbcf4180d7087550c9f1"},{"mnemonic":"neither help draw cup grief hood swing task okay wasp angle sweet","master":"5a89607cd2cdd7717fc0918d92e661dc7960b5f92a7083482079468afae827db","seed":"8a1d0d7f2799cf70744d682e734352e2330494cc309b2bf785cfe799f004b67c936f3987f75954e18f4eb3adece8e2a3c57ae24b3c8f747884f473cfdb671112","priv":"30a055fcbb2c88fd87963e2ed60117cdf5a82979a7829238f5093f0c27fd7380","pub":"02181e5dacd8408db30a736957d7b19448d5925417e4d38d487391dcdfb8ac4dab","addr":"9ee3469dbceacd60c741f1530646f7550b94be6c"},{"mnemonic":"fall laugh virtual service grit left clip betray wise correct walnut online","master":"7c11681ada91e9349c1e062d0036a29290bcf0bc1564428f7080ee0b81fc5a0c","seed":"c2ab87d2eab816dd1b81ae4acea81e034f2a7cd2cf7103ad3985e739d696248dc0cbc6ff2864e598ae736b58c6ac4f3898f1ddb7eff76413fa1383a63f6c7716","priv":"13edbf55d44ea6951f3825d67c2486a96030bc58ba15d282df4061a0d644a2a0","pub":"0386590dcb32fd11ada507c55069b5607182a1db6ec02c810c79668a27d60411be","addr":"71bdce836bd62d85f5a53f9fcfe2310d40bacc56"},{"mnemonic":"hedgehog light govern fruit season cousin shy tumble maple promote feature lonely","master":"f4575fea61d7539101b4f89674218bf8fb206563cd07d729a834bd7cd8e3cc62","seed":"147de64775c5b285b09b2f846212750577cd9cf77bdc64f198da75003dfbda9c9dc742bf9ef1de8390641ddf9b03db8958cea480e5b4660236e29ef5d2d6a6a1","priv":"9ce717cacb307400d04739f7431fbf02cdaa3ae9035e00e8a31d03d6cf7e193d","pub":"031b6e5958fd3a5883f0cff00cd6dd14ac6cd4d92611e97c4cdd751d76323ffe0c","addr":"775cfbe90a93b73c27172b699506e4b40020a47d"},{"mnemonic":"normal tuna cry human world wonder suit tribe company pink weird unable","master":"834e035154ca70f5719e0df0dea3581a2ea19c4f9c4f7e1348810e0a47eaaf81","seed":"c1682262a9bb26399355c5a226abf198db50fd6891e48afd60094127db569a3306b5e556fc166907a4ef90c80069d9c0027b74532491ca54560f7a85ee1c2934","priv":"035c6aa4e2ae3ca2d2a356112809945b068a21a4c3a448819d8b29ce406423f1","pub":"02a18ee68f4c04f7c9698d484b9e87a672f05ecb87d127746b67f3caa820406285","addr":"b6f3c63322c96f7cd1bc13195acd2a9485b45232"},{"mnemonic":"school kitten swap accuse vivid spirit team verb border write boss barely","master":"b97687333b05ee2da13ce7c5d488aa076a1cd16c8b21f43e38eb0606fa3887cc","seed":"7a770e511a21a4712fbb40f9cfbab467a97e5165e89f70b27dfc8bf5a6a69fcd8658bb6527d0203e93ac549a89dad65068f06e8699cc9748e6448eb8faafa2fd","priv":"de3f7ccc0001822d6320b5ccf27dd38dfdea7ba8fb5c73afe8b6f71982b06eb1","pub":"029d4b9f7e55bc7fa1e84f403a19bcff9eb9a9afb873476b082374e19d3e7c6589","addr":"2cff960c82af319d91e2b1fed8171fe04d47a491"},{"mnemonic":"banner lecture vacuum metal clutch return maid furnace beef armor excuse mother","master":"e944b1515fcab7dd33ccb6c027535d3e4d5db2798809fa6e9317587a1a9f8a07","seed":"6089209e2ba24a756beb9a5946df522adc87d9d98dab2c528d5469bb35ca2da36662d661b0daf50695322dab3578a5bc3cdd918a7097c0a10bca063568abe8a1","priv":"c0b5d1c2ba77450237956cccc68a162c2e32c62e662a0bcbc7e27f2ffa24743a","pub":"02b52a9589c4a631aeee16e8ce808a2c20c63134407bddd7986a517ecd46f6f1c7","addr":"d79746b3b45903cdefbfc4146489ea550bf9361d"},{"mnemonic":"lottery uncover blood that bench peace raccoon define scale fault erupt bean","master":"faee8490c1b9b5e79da43146bf8697b1aac582410ceccf66d1fa54a8b4e8c1aa","seed":"cb558b5816b5a3ec5d63468859b71f9d2b5c4b7ad07bc00e5e4e3dfeceb12fce337cf1acd97af6f5b8d9277cda0197dbac16c9700fd41df33d9d470cdb35e545","priv":"1f77e09b423c423043b8c41b93060c9e795b6b0e05ef0fe75eae4a53abdac019","pub":"022a4a7f860dc1ecb21faa1f827907b01b87a552f8cb0a085af3a6a0758d922390","addr":"7566766fb425c0d00ad475dcdca95e94b17faae6"},{"mnemonic":"bounce someone uniform blush plastic edge bachelor tenant office report lounge brick","master":"8766a5c2858ac393f9e73c6d842184953eb0806304fdcbccc43916ba70ce8924","seed":"cb6a680c6044a9bd171e97e38f4933a86f94a643a398e54ed042cb7d2039e11287876ec24794a0a078398e14cf471ec6357d02c83009eacbc377cf77385834a0","priv":"a5f252e60b69ac73744b617924ea8158b93d4dbdc2cba19db4506b86d413f6cc","pub":"038d3945d243fd90ab18ce1353e36b3c07143dcce74ae9670fc8e0ee12a2fd01a8","addr":"b184c47600390fb017cf60d2b5c89c28f72d64b1"},{"mnemonic":"alley wide siren matrix cigar paddle spin fame receive cave student tree","master":"2cacce92a105ed6a199a6d881ad7a43a7faa74f967b4e8330a55b263102fbd8e","seed":"c67bddd95507e4a9f9153f7da3de4e5951c53750a5842f11192328994742669c32cc61ed14139ddb72365ec7065436583b360ce6e2aee5c196beaab9b10e9991","priv":"f6dc107f30701827bf1f83113ea3853017e384d6995205dc9fd23176e802ef4d","pub":"02c3dc3ea3727f6f351a12d3f5b3fa52beb25ddcb5ec397042aea4e646d9aee6af","addr":"5ef50803371d9c915ddeee096f7131461323c489"},{"mnemonic":"pipe brisk focus nurse rare arrow seek spike guess enough unlock transfer","master":"ad585f1ab0b6e5f8d224fee37b6c3d1fdab1e6c757ea93a9df059a36b3f96646","seed":"5b76d0f7fa0ed23b52ec73d65d109d7026f7cc4c207988c6a003a142f3e6201fb1770f8e662fa8cf4acfc0f23e0620bf2f7b139fa9d8d71e5a3c5b5c2ab75e68","priv":"dfd55ba52a10f26aad26e7532f6631986acb0247fadb1b86c45ff9537924e42c","pub":"036bfbb102ab379a9dea1cc63dba5944b22d48a941a0d1e534170cca9a53d3fb0e","addr":"af337ff42bf6280f7e518df5f4f064a84fe209fe"},{"mnemonic":"coast opinion view orbit knife december pepper elbow boss junior wreck escape","master":"4eecc11aef13009bdfc444037aa185585c147be182442a33459b5a07b61aa555","seed":"f543bbe5ed92b75d06089c2335fd22db68f41e499634760dafaa6661e23178e7d075b2f8aa3630aa528ed77d85737601d041278d182458ae28c04420ddc25b5d","priv":"0fee3e3e025dc742e5a95f41f2e2c84d94bc8cf068bdc67db76e0378c1ff01af","pub":"03e58501a3f5052e4b7b1a4bb264c5f5a61d60c542a761a015606ffbf97f63c10d","addr":"c51fc7f607bb0e1cdba35f503f3e397346148784"},{"mnemonic":"indoor dinner fantasy cup move solve pride dust cross note raw balcony","master":"6346159218b27d00feda5a8fd63349956636147e058d9eafd60bd94bcd18efce","seed":"1c39176421e5c49e7fb6257ae70dd7d1ec60b3e2a1a4523bc4aec59b040708f0e48099520c0ae4de467da994082c82e0db6f1445c159f625559adefa55c7ce8d","priv":"74d7f00b88798ba5190b71e59dcc4bd7f2cb11e93669d7ba0372f4155308b947","pub":"03a07ad498d80dc84ce120f5518a541b5310e50bcbf0c669455c8ce2a0eb13e253","addr":"277346e420f814e9bb1a8ab16b11af318bd78d4a"},{"mnemonic":"please neither sister wrap comfort omit stamp roast lunch steel night report","master":"1f0fb7b99b38b57321f40b4327cc38f7c41c5688a478e4be908353df70477fcc","seed":"6d383d1ab8febb2334354e9e5261d6d4797bee2e6b9242521d59d22f982525ca87141e713b32cd60b6aea587caca213d8e0524c1d55cb209a6d06f607d7b74d2","priv":"95236694952fae12a92d15ca17c54cdfbd234109aadf6c295bf664115cc35a27","pub":"02f29a1a01ba6702e3a441386986ab5ef7227532704cbcfa08f2339c5273fe5f7e","addr":"048f26f774131d92ca52228106fe61d12e7d28c1"},{"mnemonic":"lawsuit trip betray slam tornado polar demise appear east dose produce market","master":"8e659f66e4dcd50dc7ba617e17e4dc45c75fe957b656039a73f3583944b16ac1","seed":"d982fe48080c3bee97fe1ae2ffdc0c5ff2445817d28b30bccd6f89bee138ef1e9828b04fd99cd70512cc8d0e58010ecd7c4460c0b2614211e7ebd8acd27d2061","priv":"4d053df57043c403659a370f486e8ebfd60fa2804430a6b97d95ecfde846d63c","pub":"035001c721d157c1af6dfa4d1de23852ba2d9be1133f30628588c5d9ec890a7662","addr":"bbd41b5af33ef8bd075e93a01f5377d486ddc475"},{"mnemonic":"hen build sand sister borrow victory syrup scrap rack aerobic shoot pole","master":"a4f3bc4a89dd86f38c5908d229f58a57c11f4a3d6872a0fd448043a6a214f938","seed":"3823745cd7589411813362179d13b2d2e1cb65bad1d53f34d512c349215e3cd5bef97ed86faf0096d2cfd5dd75556bdcbac1ac385c9caa9f928a19cb926082e3","priv":"5c14903308d7a83b6570ad2cbcffa3953b458572a72c3d5148a79a494fd90cf5","pub":"03b18bbcb61fe68f733542afa7c4c02214c8980ae4212caf818a8febce1166aeb2","addr":"fd5a9ed68124fee88f30a8d7f19e65e5ee47e681"},{"mnemonic":"chest hard axis own found relax skull bus frown distance gauge angle","master":"6eda34ed25b73c45cc0465aed51577fd35c45ef7e8d8e137c621c5bd6a726888","seed":"fd71ceb91c1c0c2176d7c75ef17010fca12beafc5a54feb3408a69461e1cbf1ad0421da9f31532aeb1f32ee6503899eb0e20fac3756d5c1d9fb23fa2738605c3","priv":"7ee2eab961362c9fb48f0cec5f63d1bfb0b38e819ae3fc1a4a3c6fe6d4d35725","pub":"03131e751282bed411e12a03c7b2ae4ba4c8f81fe5e62f3d40bba09ed7ff099509","addr":"f8a2d049f67222d673df66844d4355ca77a34db5"},{"mnemonic":"large merry tray craft fat clip album company nation tent increase armor","master":"c442b107b26f9c853ef65260fe813449152c2fc93d28774824153cfb4d67e1c2","seed":"bcdce56a81179b6fb0224a34b26583b1d1dc1d24a06bdeae60eb578df673d4d9ab5d8d639c494fcc024a945284be11ff55dd88d2470bb3740b3046155818a7ba","priv":"f359be0bbc26a121997cf2b6678b41c08d6815a5903c184c7743464e287e8557","pub":"032641e977e838507e61d0e85b1a2ff16f0b8d8a351f7958ea615bc2a8723cf1d0","addr":"924690289cc78854d75ddbcb2b06b241404fea7a"},{"mnemonic":"carry pipe make glory famous word shoot artwork ostrich slice blossom episode","master":"f6ad202d1f7acfe41ce8f97c809d0f8ab9c7959866c5a435faa9d551e98d921d","seed":"5b956478a0cf11e9e0bebfd97b3ca56a6cfd8499843d21f95a2ed58db8abb7b06c672e713a556c35258fba0533721cdeb38313565c8edbaedb17dbc1c8396a6b","priv":"0bf855554127bbd03b3779577449c37dfb82ca4457adaa0edf26f1e5e5d7ee27","pub":"03452b2268005ebdce5fb467e4ee5376624cb0dab31704c4725796dc68561b6f92","addr":"ce486d831a5c2e1b95a254c13393e50bea0cea66"},{"mnemonic":"inject more grid coach cause inner ordinary crucial mouse access attitude upgrade","master":"6556ec1bca4ce356846f9ad603e8b2e9a1ced7e2f875bb6330bac32baba3e820","seed":"c5248835bc868983d65e6009b983ec48b262a0da70dc66ba76cb167b9618730ff79b9da8e2321731b55ff63b8cfc8a2b59aa435146b5d2a21b8608bc14808bcb","priv":"3b783218c59cd664b88380619db7a4bfb4b3343fbf38d0a576849c60cb4eae41","pub":"038f88cb7546e49c3dec3d356b0bc70be0716a1b146c641447326aa077d91d055b","addr":"09e570c6a4c7439d6272bfd611dfe723d2fb632b"},{"mnemonic":"viable material flock borrow pear cup barrel fire exclude vintage economy force","master":"ed54a092853793849e78e0c42ef95242414a2dfaffc92d6b7c25efa46715e23a","seed":"013530be872ace250a72aabb73add7e4cc4568da3aac9447c094c49663be2cb6bbdc1fded3bffd41109eb14cf1e8ee48aa65da60166962aff17451bc86dc9bf9","priv":"669218899eadcca8823c6c10476cd25fe546f918495590051f8d45debeae3cf4","pub":"03f54a4a58259314bea899e9f6126305d13afc7e8de6b346e95934358b524a5b4b","addr":"b38f1873c88ab12cb5c839ab1e70639538b331a5"},{"mnemonic":"injury expose attend toward air brain carry neither amazing symptom number name","master":"769158e4432add662ca03b558828568f25369b5fff971076f7a303b8c89bb3f6","seed":"827d52b757b28ecee8ddbc402de54ed6911dfdf19abd00739766f70fb688f47575da03177ce2610263013c2d22e3ca7556c5aa134f74b8faf61dcfba1a4d5ee9","priv":"2d276c19d260ba43e8de8e4b982bb21e16f52b5c8e231cfb6bf71901a9137c62","pub":"0310fe6ba12f805a118c9052ba2cd3218418217e1e48da3daa5887bed6b2fe9bbf","addr":"0ca37f96efe4af7507cf85213a2aecd7dddfb51b"},{"mnemonic":"seat other color road pulse melody error hire scene lamp best analyst","master":"c4e172dcd2a54bbfd30fdea8363eb10b9f0bbdd0d1ff3d33ad3eb01e820d417b","seed":"32d43e6b794040093d6348fc46a452a8733e49a66a06cc8ed2045359c881630531e282ddd70a4d630a3ee69821764b45ab949650d4276a925cad0c3121c31074","priv":"828b3cddb3d902d31386264f84bc54d3574325f56df7d4e8e06b2145d60a0de1","pub":"0275e251466e97a728d0ab4527e04031e315de4d03d9cee50b5b52210fb697a9a9","addr":"c0e6ac260e64b92beed9f4370cb006e2697dea6b"},{"mnemonic":"tonight nature salute state carpet sad zero heart limit skill vehicle elephant","master":"7f116fc76af767fab661d90b8ce20b51cf5d0727ece353410e06409d79ebaea3","seed":"226457053ec313b3368e877c76f9f8a2cd5a7dbde6cfe91de075974f42f994d195f056d20802fef8bef78cdd49c6383e95933eb2517cde2bf353872a13d87da7","priv":"1f069518222310563fa736833f06041805cc0a9ada1ab2b704e85ad91b980ef5","pub":"0221fd13bb9d0177d081c7cc6b9012b0494ccc42ed79ffdcb2409c252f42b66b9f","addr":"401f8cd411f3c5475482430cd0c480ed383e8455"},{"mnemonic":"window awesome survey swear neutral tackle essence elephant weird emerge gesture click","master":"ffb27c19b7d6241a29a08c54605f0408d7de2af078a3eabde97ba6e973b9c31a","seed":"29aaedc60b8cdaeb799707830413942cac78b57c0fa1a4cae0542411a81efeb36d7e48e222f90b6d42bcae97202772d687c226a13143a9541b0d7a3bf7dbe600","priv":"68ab3dca6b53b32bfe8a1b2d428332415b6278cd258fecd927f9d8b371075789","pub":"0234cb69b2f76362ff4ff138f81499d5c2e0a1912fa47098c0d826f98f1be4facb","addr":"54637f8b0f2dad74bb46e7745d1957bb8b16f4fd"},{"mnemonic":"island pact measure trigger frost flee tape napkin wide kick select sense","master":"013a5ecc8a93bac063e18e79f5654cf0881e8c39fa43c467a8aad833b6a7ea76","seed":"19cee1ab2babcd2cc5cb4bc34acb350deaf24b997d603814a5509374a2f60ba133f498a20a9066cba31b4903a6c9da661dd459343004f69b2dc78ded2dde67d5","priv":"16b0960d535046bc5b95e335cd0326d0b0a902a3c81f70490453f75154a05e8a","pub":"03e6198bef79e7bbe034d1786e47f00fd53aa3132318ed99fef38b6f46bbdd9109","addr":"199fb6c909420f711c3b452df497d03da191911c"},{"mnemonic":"before shed sweet will sunset rapid color tattoo winter foil spring orchard","master":"29289b59bf6ad91e0f9b24900803f57d08b45194782e76102f5377c142c17f65","seed":"7b721e94ae18b7666bf293bdb8eaffd70c6bbd2c2ddc582a0939100f1f131009752be6759491e0006cfc424f88efd97868132fa2e6bee0b05a520c881c632951","priv":"f6d7fbb1fa8957d132b1d43414b0664cbc25020055c35635a5ae005e86e40767","pub":"0368c1403ed0df7c1f9faa90fcd60937094cc975d1ab5bc7a9eb03bdfe764dcb7e","addr":"6657bcebfa3816af66b1a0a92f8269cd3f515fe3"},{"mnemonic":"view forget toss steel right echo life candy fine unhappy document pulp","master":"5c6355a65617952bc05b16623992e81daf07695ee5cd3d9bb2b8fa600ffb2576","seed":"20909264d02b6b88a472678b6d457850ce4f67a228a4f6ad7f45be104029ecb68f25075f30680416e0e43469452ad903a1e7b0486932d0dea4b1c5f56b21406f","priv":"4495c90b188f706dbe1106966e49745c49676e5c54f78176eac166629a4c0815","pub":"03b828ccb3f9f1766cf1d1c1c57e8e5c9c96936adbcac4a8ea47b0a7e3139c5771","addr":"4a382d15c13a06adbf075cd847ae9e80dbdf4ca3"},{"mnemonic":"fetch planet injury lecture myth goat drill sea reflect tattoo story rival","master":"3725eab28e9f4275bf2dd2dd5a94aa550688b8d111798185383a58088d6f8b56","seed":"0d42a4096e20202bcaa4d1939ce86cc6975092d2e6081c149b374f3dbd6cda81188c442e1cc7b67032fee9effda37e20da0d809f0d969977ad2c547e9a075224","priv":"7abbbf9aa4375ec489d3ad3ef6041e9e371f4582eec1ed753f9d7de3b4fcb57a","pub":"0371b9cf4994f36ac2b5117a2daf84d7c072f63e51d9f7c5e547548bc2ce51a16f","addr":"d59315e071460c09d108bfa31e05cf37545b19d6"},{"mnemonic":"close raw empty danger educate metal vendor coil observe response scene army","master":"da1ffa0441ba19153f0f98af4092dbe68eb5ffc9d82823710dde6654684e8fec","seed":"f8ea358ea3a369cf72266a1f30e10c445817766080ba50991a2b10ae1f23d1fd3681ffd4cf4a7ac47b39ee9b04ea246cbf5337eb33e0d6e7c1da921d153e2cfa","priv":"5e3f256a55dbfbf2750e56067779fbc05503bea90e1a999c17a0bc27801a0bbb","pub":"02329640a5fbee753825fc310c06f9761cedfefb880d6e3dfb67eecc68ee269813","addr":"904c7b3d1cd8303fc3c8b0e3b0fd60fcb54807b7"},{"mnemonic":"ship puppy grab also merit doctor fine east venue resist loyal radar","master":"f7e49c26a890aa8deb888da48d82935a69730bfa0af67783b39d209ca803de9c","seed":"dd5c5df895a6663cea37b5e7c58c268420e290e4f20d7e0b7c98defa35fde4d372f52215b52bb2745481b8c63aa08c2ba163dd89f2a9e51a3bc92199be8d57ef","priv":"19a311da10e6f11ea3c91231a7e5bd22097701bb56c0248014958e6df186e596","pub":"023acf91dfb359e537f9183080ccd67f65b1b9efe314f682deebcbf718454d6bd4","addr":"618fb20184dd651d18d2e039ac0fa43e53feff61"},{"mnemonic":"beef hen spring repeat define hurry slab midnight liar either arch original","master":"85a6a3be23cc74b32c40f1dbe431584f8767f4116a98ace1a08c934956e8582e","seed":"cbe260dd75a3f8e7b7b9588f0a1325dd88b839a5536e39facf324cbbee3ae951903e2c17370ef673c770ff2e0fc3f1975a26f45d27c681a143f0e60fac8d18c8","priv":"42c6ccd133fbb7d3bd8bde8309f8202eb155c46829aaa558226b6e53abdb203e","pub":"02d1bd732a69043343e72e22432f7bc309e25e96d49b087572f021996297abba75","addr":"9eab0f06ec4e0a8058e4fde3ebd5a1b2f5ab043d"},{"mnemonic":"lend total guess ski divide diesel tumble heavy slight expose clump park","master":"6ad684d3e30778a46d5af816c3deaeaa0af5c4fc04cd74045dd80947faba053c","seed":"d63dd68a0b8296a41ff8698494d9823d18b4700b0564318f022b1bca641a8fd622ca403b48fb835c848f053557cb91fe4290f37ea9edc4f2428226983802e1cb","priv":"9625f841a7384f3fb64c643ee715ab0c5693d60132c4c9838c7f41e1c9163e1f","pub":"035e82a39dd7b61ad9f79ae31b0694af43378fe1b5cd6fa824ad2427da0834e00f","addr":"b88aca334f2bfafdb66c2e338a7d3b6c8be8bdcb"},{"mnemonic":"toss hand crystal buffalo during more laundry fantasy give dizzy bench want","master":"075697aef6de8c5b0b418fb3faa6038442349ded65421ae1089517423120ffac","seed":"4226da530678cd25633e2f87951c6f8f5bb4e3dda3172494dbc03ea15795b22d1042ba8265d67cfeddbc205b800ba7b7b05ed231845ad54cb3224c7ad0f9d8fe","priv":"8db99c4eb921431ead557ec1fc62bd6a9bc34eabb3516345a46bfe8c6106be6c","pub":"029eee00e90a02beca7c3813af59feaec320d26b585e5620de11efdc09a06d2c5d","addr":"e9a4a29d0e7267016b03bd23d6cc78b9eb1681be"},{"mnemonic":"camera gate trick sphere oven sick promote apology reason fiction item meadow","master":"7af52d41d0e35f2cad60fa2fb6c13d046f8aa4e0be104251dee489edce66a229","seed":"16ce2107f57bc5feff86b9dc81c98838cfb7b6274e835d6e35a46525d5870fd84c74cb528f6e2b87b0cef21bc6931793f472e5430b9582f036630c3b7c66df87","priv":"5f428e529d340883faceda659fdb36193f3cedbe2307dc4b8bee55a6845b9068","pub":"02c71ec0f9fc74db1da6d2a4a9d6a7f24980fa17e656c1d7f43a845a17625b2445","addr":"31dd6642a6d913d82b06c3793ae678670ea9bef1"},{"mnemonic":"slim guilt leader segment actor sick debate enough what rate fork rebuild","master":"674b90a421dab99750e982cd3c693668c45d7a39377cb82e3887552f047eadf8","seed":"115114f315f7af942dea64857c5bfe6a22c80031e6b1dd0c19a42a28526c0b9995b0eb39bf8e78ce849559eebdfa3c269783987bd4fd926e776984396a3c604e","priv":"2fdb1afdc8248d6afc69e387a698ef467dc0473323203445bf1dd5aaf5789936","pub":"03dd070062a8c3e65333572beea40f434d6f1b68e528ffefdfa8dd3d8b4c5a729c","addr":"8938c102cfbb8030cf69c660b04fee696a902f96"},{"mnemonic":"kind prefer arrow industry arrive cherry pupil arena strong tuition wide grunt","master":"c4b509d9b1e60d97cb8b862b96a2dfbf960b0553f0625af7430abd3951c59b2a","seed":"85ce840ce0f6625294d32cbd5f68b3c2abc3151db02b5a84b1d78ebd7c05ab24bd49a519a340cd19bdce6aee845ce59b6b5d2efd7a2b1640c7919177bed27b4f","priv":"ba8628565338b55d2a79b21fb9e47da978611095abcef86f305a0e2988e8128d","pub":"02bbbb0be37e3975749a095a9dcc025bd57368eee362cb896540c0dbebba42f0b9","addr":"4d8e97ff30d95baeb64ced528ab999c72aa4cbe1"},{"mnemonic":"slight bronze prevent civil junk van female movie vibrant soon grain suggest","master":"4d07291eea03d51cf9a3790ab0a77ebef383f2cdf184b0ebd872fdce93b0b889","seed":"4f770292818429f588226c4df9bf6053024a42f11a2499f0c2c5ce56f25d16489a2e554b99105c70fa57657b8d8b724d1879fabdfeea8aa43421d19849a0fad0","priv":"bc6ab78b16b831178cd9f2722bb8afb27664d1967cde464af0b951c3aea935b6","pub":"02673643c7fd4c12f47007a27a56537d152d607225a6d561fd0e18d30064752e38","addr":"3cd7ea7b1daecd3efdd14826508409791d8ce865"},{"mnemonic":"chief drill utility gauge tired alien bid deliver tooth amused busy run","master":"dbf20f3b945820a74898fd73218ed84a61ccbbb28d815d26dea7a7e9a9b0b430","seed":"93d2bcfeea8a87f021002ffadc33b664810eb8ed60342c8dd74a937cee64877d5823c45a4d34dddfb7d4381addeeff63e80a191b92dd7f1b4f0dbf5c5e8006a4","priv":"c38542f0dd692e74e8635dd784fe14d8c70ba931c354d6ea08f1e0b9080d74dc","pub":"0225dfa0e7d37e8903213d8d4900a2e56107e7dd02c66cdb7f86e9d9c0a05a835f","addr":"a94ac8358eca1fdb0ab8ded1cd80b492dda39dd6"},{"mnemonic":"large endless art unusual tornado loan planet excuse dolphin million reduce chair","master":"8f029c13f62099a1d280224f9af2d9458014903b3df1eb65647507e4a5262012","seed":"03d076444f817fb054603f746350c40c0551c3d155ded436620451c4f53ad1254d0b52bb0592197becf9e1fa14bdc65d04c7ab9c5bba7737841d800cb4f7c869","priv":"1cf63efe6800cfee972815f1fd71c68224594557d7894fe8902f56b40163ecd1","pub":"03cab792d05c87c205acd8d78771692507eab35d8c7bf729e2d36551ee0c7aac67","addr":"9f27d874704a788d3e3c3f63f9f1a42c27b83c18"},{"mnemonic":"recycle wine system erosion train make sting talent beauty decide beach pigeon","master":"d702a0785f59e080e2ec13280ec397e3af1c60a36bbf2c92c252da896caa7371","seed":"ffd0dd43668393be9b0949bb5872279ebd606be9ad58f8611ece8dd8b408c0714d5435c771ec87d23b83952af75a6853d94fb0ae88a9b9c1b506e3c0887f3bd6","priv":"d27a5cc824bc9e78ed2c991b8ca86498ed48775aec94dc79332057f2a36536db","pub":"02f85cda3ef9d19e68c4a3778ad99fe008904aa9a80c4dfa8d07355ffc840249a2","addr":"da42d3051e6dc70d85b5fa901e1a4c55ee12a47c"},{"mnemonic":"solve snake loyal grape island deputy monkey art pet candy enhance truck","master":"e866de4a10e7067fc8f6462938f3ea5fa7c88c93e2f630ca81dd07361c646fa6","seed":"82483d5e61c9eb2b0c588b438e24cb0a24922abc6cf0571cf3680474f1eb31496e387ae1c017d5a817c88e48020c5cc1298cfbd78d7175be0a1ebdfbf94853f9","priv":"919e4b231311f40772a47f03948b136879c13f5f0797c939e703b7082d65e637","pub":"0227c29a984f46d5a99c08f31355c3471eea9d231ad414286a25fb91bd8a3a6f92","addr":"e1ef4d05f41df82d47c287ad0347197e8deee0d6"},{"mnemonic":"energy buzz square deliver jaguar chapter analyst chat rare over bottom river","master":"3074c2d8f092896e7a197abaf388828f95eccde75986fd280c9b098586e18396","seed":"70be3c734ae665956abefaa3f94101b5863086c14d8c17ab288a3586bb69aadea314ff47dd19a4dd4295ecdab627a34d9bd56231180b20156b65660082e70d35","priv":"47ed2e3406d068624039f71e17f949c4b7865f4c8d006d6b4a635176b700bf2e","pub":"03786b91b237ba9c1c209d8386ab1fef86e0c667cf2a9a3cb25fbe06927ba3214e","addr":"67ba9f0de66103c6d6f1b799564ec38d4eb9f708"},{"mnemonic":"document move actress food guilt fence govern adjust supreme upon stay sibling","master":"6d110c56993597b6bd473eae083493e1a17b4cb971999b078e4c74c2a675f0d7","seed":"e6a300fc434aca28cb75f2113a8f7788778f2b9398290f82f8e5d989bd002659a645c18057ba64807f0d4ae3c064e56a2947d5aaeb83ff3c593e942fdd0542a3","priv":"fbf0d5b6e019bf7aa6745c4d95c0f9a428d0dd57b7af9e71eb2e39a6628b8d86","pub":"03725d6b7196d57cf1d3b193f1a6661613d02912981225beddd4e1db8de60682bb","addr":"9aad1b39fb3cd3fdef63130bb1a7c8d12bdcd866"},{"mnemonic":"express birth sweet kit special board rose sound place chimney awkward injury","master":"c60b7e5a1c89797d3ba7ce33ba487182144b4203379c04e51ec7ef4ee621f0f5","seed":"48274a66272c61abcdb72d36f16b6ae8a1853d299d9514516374096b443368656ec28f59c9f78f6707d1d1fd78c4aee00e046cbd3df66ee7b143b60e262ce162","priv":"972cf1431308c25e45efc26b86a87c85ade411bf3fe1cb6e88143684910dee6b","pub":"0243447732d68ec3287f154750f1b27df3ade846a0d4cda8e5e5f62f3875de6b86","addr":"c199f50b3c111205cc2c7309c9239393bc2c1e57"},{"mnemonic":"future desk scorpion enlist confirm favorite ill expire evidence cushion rapid staff","master":"0f15c9b1f3210c53c5c408329c3c028211660eaac42f27749a4e8901ee17dff5","seed":"c2db1a2615fb9b849717825a326e5bc2ffb8941755a529cf27903c0c3be806b4261cc3ed7afa8748d87d1ccdd36372fa02f92316f1adc28a354feeda7a0edaf0","priv":"39de89d88ac3f8b23ec57f82c83f9bf12a32762c2df4a612ad647ac9ba287c93","pub":"031abd39702f34958e3366a4d438a7285bb9d5ad5b8a29071ddf77ccea7f9c4358","addr":"a7bd49b9e2804e22072b1f336578334c53b6755a"},{"mnemonic":"interest judge only feel scissors illness quiz goddess transfer person position this","master":"f60677c75a51c5c9e8ebc4fc1320dbbc82e00a42507af218a71812d6f981344c","seed":"6e431ec0f7627bceab5dedf9165968ebfb03ca749aa8d021725338ca8e0e8ab3b00cd0395a68ab955068046c63daea7ba57117ebf7c99b242df8e63d01bfa625","priv":"41af5034dc87e11bb1cca3cca70f80fa057a5839b711927d3fab85048ff8620e","pub":"0321c6b392ae7a49201d0a7c89fc3541bf1c0c1287c051c405cf32a246df97c231","addr":"a71580dfa7773d8a2ce44c7dbbf03d285b46e56f"},{"mnemonic":"foil remember sample infant bread year evoke laptop buzz chuckle voyage gentle","master":"88ba5191913e778f76cbc28850f221e96f380724d29646f7cfd4fb6da20171c8","seed":"64485dc0ccee0c0b13c0bc76e44f8c3a26070a6c08fec558519503ed85769ce3f858978cdcd630b9c017a290ae4a7bdbeb0f66fa5e3fdf3ea227187a165ca654","priv":"117e2b0842cf25a1ee21eb2f244a2c1fcf2a3a6946b3b830707f7c96057616a9","pub":"02b349892030c73203e3fbadf870e1c8325ad0ba1747738706734f7a66e9c66ba7","addr":"1f6488347a7155806da3103c9a82954072a1abb7"},{"mnemonic":"pony super panther surround glow lounge kidney increase actual tray bulb device","master":"54c50d2520ae7023ffdd39740b46d029ae9e31211349193fef33cdaf373030a2","seed":"3d11ec99f173c1864ca6638a27554549c3f619a86e0099e5957972722b2937f5db767d86f5fa0c6b3e0a81d75a9aac5786cbf01f2245b51c9202cd4b4ea88a99","priv":"81741675b625a14760a1ee9e108a9ec6be183c480df05878cb31b45e0d0babcf","pub":"02b0bbfc4e33a79bec82682552fb4c0506528d121ef67a7a42d2ee26bd0b1661ae","addr":"8ec9b249788a123e1301100da2db3db71c913a78"},{"mnemonic":"achieve sail manual vibrant unhappy ripple aisle fossil pledge wrong cherry trim","master":"ca9ac46762b80571c25c2a9a6690181896f86ab3f728b4e372552fc9cf1f7e7a","seed":"4c1cf0279c8a29b8077b5797b6592397112b65e75e91b829a0a9f26b94803a53a02069ef774e7cb5c3cbb0a8e074c69b9afc5a4136e04913403b70046759471a","priv":"09503bd2da16f128b857cfc635cd90064446130c2477004a3870a0c5b1f1febe","pub":"022b34a4abc0a4288805719d72f0b64c3118d924d09a4dad4f26d39ed056349e00","addr":"ef2b6bfbe737261b45e770b5eb42d1950c556282"},{"mnemonic":"injury love grit wait junk bridge myself sister hidden dilemma gas paper","master":"b4781c7979c0b72d0c50c9889432fe2a54d6f08a4101778f88176dc661bb02a1","seed":"03bd824d7f4487af26d236950a69eef9c878361038f0e8be44145e558779d4accd58a8c5550bf5ea86ad169e5258947512c4b72f94bdf60b9135c28a4c779541","priv":"68354a2599b3be8ec6dd827b35290d9dfc068c267f1d89dd57851b22d69c95ea","pub":"028af1d826d45a920dcfcca8d9f86dad10d7356b3027d4051dd4c46ddaf32dc623","addr":"1f356a55f3d3fa48e5d5b36fa31f738bc8f83de3"},{"mnemonic":"domain congress cake cradle express despair devote swing meadow piece border cross","master":"a3ebb93b7fb0fac2b02408c0491f66800c220c7d47190c3c97023f3d6db86555","seed":"15494e78dec34f3fb759000cb0584e42addd40f1794bf2932c6bcbbba20e40a381bb9bc1b8dfd3a231d8e249aa3d51a257590a93188fbf116eed9321195a2267","priv":"1f1ae70c46a36ba70674c7b17e6cf0b8e08bda0a17473d8ae69bc54311239821","pub":"03d7d916b054c003413ac84402e59b976e5515f48bbc30ff8cb6bca7f4ee92c03e","addr":"54d0f98b9dad14550c844bbfce8a4031dd677a1a"},{"mnemonic":"pistol news assist poverty squirrel machine aisle sausage harbor garage truck type","master":"581d692fafd753f080fb83318856ccbf059b74a3f26ba22d4b93916e2165aec6","seed":"49abafbfd7b0c65ccf2e22eff01c78245c764d8ba7bd468fe1baf62a156aaa0a6af143c624241ac753ff8432712ea58f1df9e75cd969f905bea80c15b5fd84bf","priv":"f1d7e572b03a08cdf0ce00908c701053a5804fe566168436b90efd1302bd7f1b","pub":"03882ef7180679169f638a7da20a2b522c948b1979802cf4df83fe1b62581e4d4b","addr":"40bbf61a5ea5b704d02ae13d032f3dff4e597ba3"},{"mnemonic":"field tree license quality chase certain hedgehog alarm melt elbow screen pigeon","master":"dc22c189c264812f22664d39ea1fa2f7d986b981d59f1df2ec6f8582d640e196","seed":"37ab0888876ccb32b9ab73f144ba2a352b31b27fcf6b7f8fcc5a2d4802e7099a199242cd5f7f2a3c3a7d86e8c1abd871ca35881a6a47f605d04a063b589fb0b7","priv":"f82f484f92d1719d3e6832d49bbd493d5f7191acbc07872b345385c32f2d38ff","pub":"0390c62a3315cd02fa64216cfbcc1788d537818eab5cf932a24f9eafe6f4031f48","addr":"8933e5a789211fe61396f29fbc55be6ac7031550"},{"mnemonic":"wish vintage fiction false gap bonus abandon cloth usage hurry toss slender","master":"27f3a0c1791709a32f8ac4fc1e9c71eb24c06e2fcdd8dc959940cc1c8655a5ee","seed":"36932e48e1712c3d03261d432a17e940f1f21d730a3f22d37c1834ca0591b71b438d684c926ebdd51e6431502b8efba8e9c32cf457e68b71079864dcd0d1886a","priv":"bc0037a14a0297d3dd969b365a6d8dde9c6397dd3c8aeba6d701dfb1bc8b24f8","pub":"035ec8335e2f6ef0fa2dc6be7f3271b1117545ab02cf1c8cced03f9cfaaef7c602","addr":"5f64f8eccae09dd614ff9f7224a20eaad60c23d5"},{"mnemonic":"fatal strong idle grief present photo aware various april april card village","master":"ae83fc216ef4c81e2d128d90e91cba0b475be472d95ba77c2ab9cecaa511e308","seed":"3901e5518ac7cf29a42b94de334dc641e1db3b97b44a30d69f3865c050fdac8339dece5f9ed96a17e669d51d59ce856db902e918497ea8523e5ba49fbd06f621","priv":"1bef5a9ab2f2ebb16d9d08b958317a37f252f3de91a70b2c8b5b5ddc0a187ab5","pub":"0250e5876c37987b4189513c7a5dacb31a308c563bd09d7557f2584a3c86bc9d5d","addr":"bb82bd68b31663a75d0d007646e8575934991f43"},{"mnemonic":"error monkey argue fluid afraid hand cactus sand misery close laugh comfort","master":"1b4ea203fb592e2dad7f40b6cd4c8ab53c6a0a7656cb674f9e22d6a0e4e84efc","seed":"3c0544c9b9c32165fcaf9445e49518f8d0a2dc6ad672a01a15183ce1e2188f6fac026cebec0ac4ca58906d5599d781c18b2cd77d32992be9cfb01441d396faab","priv":"e0d0b452b3e1f760762f1bdc692225e7b14277d8227d619bda49bcc608658bb2","pub":"02e7a7156cd53cc7c8b4840772854e25b8c6c532c520770883da9d574c86551c7c","addr":"696d7a61a2ab18eb503cd724f0dd316340f96c90"},{"mnemonic":"demise know punch page upgrade gun smooth fruit machine long nominee radio","master":"b36ae5062ebe21f31bebf7b2078ec651ec579494eb764bedda7169095b73fce8","seed":"0f71fa966c1a56c3e11c54798052789cb0f450761a0227336ac50dc8206f032133937511e93fffd54103132d822b8e717ef15fcd4e68f391a398f14cc795cd76","priv":"6b9a3d2e63d666909216a0420709f127318f809549df2e99869fa70956e41dda","pub":"02d72eca95feaccd5a78567311e882d7b5eb2914d1d844e639b03c4cfa036155ab","addr":"9465a8f3afcc348d661bc198cf8220e74faa5895"},{"mnemonic":"install lawsuit guilt myself arena enable unable thumb domain lift feel object","master":"c033a48b64aff8d6c7fcabfab52087c0f7b9a4228837a7a21d8156358e5eb529","seed":"8075fb6cd8bfcc08f1678c005e9d2d0bcac710a6a62cfea46a1ccbf81747362a0e6a25cea2fa958fd320205e0f95eb8a5884c6f39cc3ad17d58fe49e43feb513","priv":"24e83fd857d2a0ef62052625d84e6aa37e96b14f2c8dbe5294107b647b369a7c","pub":"02d95869834be368c5bc37a45153135a1c4f003be6a44bb9af89e55485118826b7","addr":"631e72e50070f34e23ddd155ca0670c8c8db49d8"},{"mnemonic":"antique insect develop sheriff erosion appear easy fog kind apology stamp noodle","master":"72baea3e04811ee7447249a1140ba62e56bf473d5298b610235ec3178b388692","seed":"e751bd7ff01706f38b8d3a28ef3b1565f20a8ad221d521176347e7791d2cbcb9cecfbc8b51c2e36c303be55df280273711d94c416dd74a7e7a18ae52520cf89e","priv":"0ec2ef082498bbbf0a0686fd232a3b4bec66d867cca50bd0cb0e3221bc9223ad","pub":"03952fb023bb76ea74a6b5b9c4a035b193553a8adc1c0774f7ce4976ee2e8c7fae","addr":"9d63d52c57fd76c7036b882ed40f4a6714164c27"},{"mnemonic":"become tell dentist play light illegal work animal library dragon now gallery","master":"895a420763a2dd6fb934ef8d83762051b00235a83f621f57377a0d63b71bd43e","seed":"05975302d82f413289a1679cf6a69feaf8be733d7fd10deafe1470a08ed5796bfec454adde87e825ea80ff05586c12f5d0de69478cf72238b3e73851f691a3ed","priv":"f94266abb2a93d963a2505da57d66c36d37ea3f871654a0e4221ec911a6b507b","pub":"0256fe9203bb64e250fb0e68a7cd3b0d3c90cfa8f8db06c74524c63ebd66098ea2","addr":"4f73259b06b65db4afde238bf4b42a801178d92d"},{"mnemonic":"average anchor total witness carpet myth life admit gesture inject sustain valid","master":"aa36181d7a04a32433e6f4b1e4830d3e0796ea00b8b03d466087911931c16abc","seed":"e2bf601f15b7edaa2176e6a0e734c72f99ea63086ac463ba2dc317dd70466753fd1334cd27e28c382c99179db6a5b00003124f93a7a93da3a0018838b3b04595","priv":"235e293ad68581b4e24a554704eab59caf513b60b8af6b15fa5d8cfe4265e44b","pub":"03b9e9b4b67e3bd6500e6b0c3f0d0f69a15d0824f236553f7fa12a58111e5aee7e","addr":"3733ba0b6386f7534c7714e9e2382675e0dff54e"},{"mnemonic":"liberty cradle play vague cute borrow around that tongue wave solution share","master":"87869b74fcb6e5d4b0b5c5d53679737f275396d63cfceb1133e49458c9c74613","seed":"c60ac4201963706912996e289e5b6d886693f7277875b46ea166f4506bfc3a2652ae2fe5b466bc171c7c7f09f2920b8ecc6d9c7d2e0ba1ca2cb10b528119cfa8","priv":"a8cf8fb93cdc5c13402976952ddf2ba5c2b37d5756861536df21d02eb7d2c593","pub":"03c12a9f6ffe4d7b01364f266b835be4eda6515d601bd39399f6577c53b1289e12","addr":"cf72bf854dfe493a6c742d6941a4f22fbdabae1a"},{"mnemonic":"hip dose artefact tumble keen favorite truly stand accuse spin opera wire","master":"94ade552d985f2e8625e18360914a5046ad14b6cc5fa6abb4ada333de8973595","seed":"c1d9d325396486e588972df5ed2a281a1cef82184a90c7ed7ff1334e35ae92a15cbaec6d27c47742a28dd458474980dabcbc651d6a2eadcb97dcca08f295359c","priv":"9d3f309f684c2c61a0f05fc88e27894cfec51b738058bdb246f1a9fc65937fbe","pub":"02c1da3c5abf3dc4b52336b362614a1a7a24853759f72fae971b43940500badf86","addr":"cb143b40309bc078f3db3e24009ab54ec4741b9f"},{"mnemonic":"history team sight sibling art over pulse network sausage garbage october slogan","master":"2eacb1cbf92401aa2996b19c77a2f110faa835203cc6baa1c98204ad1740963f","seed":"70b0ab283cce0e7735ec8d05bad6178bbe5a19b747ca1d5d3f5a29c83fb634b43c39f621b11210a7d89ec988b94588b9d8b8f2f4c2a9ccb687cb807e230403ea","priv":"986837f4c2853f751c51e09e514d2d40b36d3995e9561b451474cec6ce431a5b","pub":"035ed85029803a151586b8b3da3f7b2208b257738dc43fd1af08c6f76798e82609","addr":"931b1e70776f50c1186cecec2dc8fcc917974e00"},{"mnemonic":"labor area outside toddler street now same obvious demise mammal puppy boost","master":"720c9f77cdac069406469500511e891b35f1a5782ed330f31ad1dde4bbdfe7e7","seed":"ddce9ca2ae36975072a321de56c51a6ba88b53c226d66006b81d4572af6c8c39303a5a8d56b1626510ad730d578105ee84ac079538118f9cf8e56b43e9da71e3","priv":"8182fafb8ffddf9b157f5a072e57d778e766f7fe5b1bf58ff6cf6bcef46e1d32","pub":"03399e5ef2e4a978be70fe20b7d44201b5130894588033d89e1c0b891576a7eccd","addr":"15a638bbd12f6cce559e0f03312fec99d8f5126a"},{"mnemonic":"coil room slice arrive lady world turkey huge point gorilla course oyster","master":"bf665f511652b19a5ba1b0a51137aa72e392a7149c2df6af31a0244dcdee4475","seed":"2abc97e164bca2a5f45001574f9781dd837304f949c28c102c9de3d713d8cb0443a3be55a324817e53d78b50f0a939346ad414eff0b6307cfabea6023c2258b0","priv":"dd27b678563eac5bd7565e130f6856fecfefe3b1364ee0afdace0408b5ac09a3","pub":"03ff0c0d776d27564be0a465e29923384215370508961a1a7c25c261d09115f8dd","addr":"c4e5450d737defbb8f4d406650c93d79b1820043"},{"mnemonic":"diamond empty network apple radio divide ritual flip correct phrase kidney say","master":"f451294347b6c2035d9cc0c635b777af0d1fa756e1a72c2e41863a753776bf49","seed":"9108fc851316d1f63bf92d68e1db688328b7ae4c5ddec9248d8b5012709dd14dda42ebfef74e61085ae56c64a9125e3a47184320e53ca82684074ea0d359e6d1","priv":"db4465c5493f98c16b002401e28422b630bfcfddf32f8112c10ba71787490e44","pub":"0370a09ad6af977beadc07793db44c7bbb94cdc060a12ab36949bd36a3aca9c776","addr":"b9a0c2f7fe9d2f416596b71538982206c70a38b6"},{"mnemonic":"cruel music theme kid pattern weather become same arch foot host region","master":"82dc215e8155458ad1f3c61deccef305932839a6087250839c41afc36a043f62","seed":"0bf672820da35a6601782bae8a773f4dc6381fda5fa60a3ded12cec851225042769cd147c614f0ad34515996bc1f90675c431a5517c2b5fed80552b2bba59d7c","priv":"a63537932e88d10143726eabb410b3fcf0baaa550892ec17fcef874a2df8b70c","pub":"03b01ab00751243c746766568875b948e12eb1b88e85dbbcf2b538cd65544b57c5","addr":"137ffba20e834d732918f15a7e8537f014d2f3e3"},{"mnemonic":"pass avocado year kick ticket chair pelican finish lamp eight latin hero","master":"5f0d43392033f39e4488d6d9ecf9cfc7cf358fd718c1534c30c67aac21ec94fe","seed":"577d6f62d28192b753fd00bfd3f5af506d7b9d1608eb31cad9f8b08019011dbd20aec03ad7ee731f74811ad060249113b9d2a4aaec0f9d3317a4e72745876822","priv":"5600cbd7c571ddf6d0493f88ea45ac92e33fca9e6644165365cd59f91e68eb21","pub":"036f3194e13694cb0cfa7af94b8a60cd641159d60130fd114cb496f5b828fc2132","addr":"d3b8b4027a87f928323589e9f5a85330a349b821"},{"mnemonic":"deny improve give spin carbon lobster shell lava romance jacket village canal","master":"8b58d86d8564a0f23f0d38e08f50c68ca12a7c55956ca8f34cf790b7260dfb60","seed":"1d5a8348a7c66d8d7f6b5b99f844a7ca23bfbd4558497c1eb72f6679df2e0a03404e99e97f68a3c0a47d56e8fbc0d565f4c1bd0f7da014845038466c7bd8386e","priv":"eacfcd11a571bcdfd73a64a8c0b935fa343b21c0044f8e4d9d2d882c75db1733","pub":"037fda0cd490d64febaebdf19a0236a1f7a1bc6d523d065cdb2626ca122e9d50a1","addr":"9dfc787aebcbfa396a0ac4fe9b1513ceb1cda121"},{"mnemonic":"target tunnel liar bargain bounce lesson silk lake artefact coin coffee degree","master":"86437b39d0a0db7f5b0a27f627146b1ba6ee9c2cfd4fc12663ec3a2fd06d6495","seed":"fe9740cbcde2cc458420431b44c8158a35c172637b748b4051b79dd1ac1d100ba0cc60d4928160ce8eb6cdd5232152f9b90439fa2f4a3aa40f1f884d79f0e520","priv":"5e56256b91d4e255d9a64d5e3fe5fd0cfc2eb558b683f2289d773d09008a41db","pub":"0273026ac3c43ee59dd6d703efc5fac4d31e6b6585e27820bd8d91185dc399cb36","addr":"a0d89229e673a6f6962045c70faffbfefb0764fd"},{"mnemonic":"rail ripple tuition once odor barrel winter antique woman merry nut offer","master":"33ed9b3e5babe3904c48a79769a0d8a8285fcd9b7c1154316429382876726bad","seed":"595dd0a7f36a71e38b37baf88967b9960baacf11b249edf1afa2f2bdcf68a96e867644b712a78173609202b7d8b7cf2a3cc82014ea28bb955ddc46027dd10f7c","priv":"de65d8250e9eba67bcc69a2840187c2ea7c64f44766c1b7ac0b648afa0f297c6","pub":"031a160d7d3b13d9190c2a0a684161bd00d7a3fd6f472368f5cc4bf5ef3f84cede","addr":"a0469ba7da8af387308cea748507999662a7f76e"},{"mnemonic":"mix you afford area also raw water fabric mother industry mystery victory","master":"d458f52e08056473bdc1a73f5ba148da38f58a53be4f7d5d8e5d2974da6474ef","seed":"fcfefea963149047cc195afa589c8a81189eb27bc30d6bdbcadc5a74495400eb12d7fbd3cede6ab7e67c42e308dbcb4f74cf2ba1335b873c8bdbd13ff19dde6c","priv":"d5ef3fde07814e0967f9ec90b44a749a1c2f4bae73f76af2e10a0f73681e58bf","pub":"03388e81cbddfdbad87b95d800bec6e1869bbae59b2098b51e083e97272ef82fe6","addr":"3570a61894c36414a607cc787e0eafa997ed0506"},{"mnemonic":"find fade drip oblige either deputy noodle trial hand clown athlete chapter","master":"1131d43aafe4c9a2314a3c41116a0ccc68080cfcc525fca0acc53d5a2d4c80fe","seed":"207bd6fc9c91ebe2521cb131c261de75c71aa28935feb420a2e9603659826c326e996074bdc78b7075bec54f4268c828ee8a62aafe993cd7cf481665a2303e29","priv":"22ca0a4c09e5d6a96ff37eae29507b62715a643d3ac24389217e35f4bea4d9cb","pub":"02ca3c47a5367b7c9d83057333cd716b44a429d7034bda65761ee1dc503038d520","addr":"64be7f76e0669905f362892b53491230aa4c6834"},{"mnemonic":"anger level recycle lemon material pear robust decide they hammer coyote fade","master":"0efa37efdf167ca18fe15ceace835d75ae94027d7cf4186b4ac7bb5b91d54fcd","seed":"8838fceccf5c30285bfff02ec64ee34d7518c30b9cd722ab8a732d1ced8135fe1919af7e3afab63a55f1551d43d4222f1f62d0349ec185e4d45076b849de8972","priv":"16f41120d317d7387aad821cd0374d715a1ec1049057268b3ee090210a00a6b2","pub":"02b13c5607cfd0675ec1f33f82a09a9cfd88ea6a212a84f9528fba9857d012f5fb","addr":"4cf25877cd8e38cf5a37a9dac81a9222e8cb5461"},{"mnemonic":"fall run bread slight rhythm flock pipe employ fault side kitchen position","master":"89394aaca338752755e33fde878862096328720570957a7c105ddb90599dd87c","seed":"a2fe89e4a22f8ff5dc458ff49870dfdd7be85afb9fdced1165efc1a2bc93ed44e4d0ea1be438f347f8bf7c9812ad6aa31f07c8eb3b5ec7cd40e37540876b12bc","priv":"c0c189d49e3735f5eb9c7007e1e0a841127998d7c462deaba60912a55c9f0de5","pub":"02199a2f84eab6e63c60f7aeaa56b5799c6013df61e51fd2b9e2be56e4d6e5b121","addr":"085ecfb2737f0ffc78f1ce0a15868b24d09fbda1"},{"mnemonic":"judge soccer grace train conduct duty arrest output roast candy total vacuum","master":"a5fe2db0c41ad910231deb8112f6e10998895d48655cd77b142ad9846fe62458","seed":"4ce2ea9a4984030e65ab6eaf3ce543a450881597d8585c79a66d324606c00cdcb2c3cba4fc83160f0426dfc5a89cd27a9c9d05f94e3a04e3bb550bf698076b0f","priv":"b9eceef7003d3258ec2bca498b34d3181b38aa493f8b3b1237588873751539e3","pub":"02bb8de097658d0e711f875089062c135df56e33fe97e2c828da5a5023ba634765","addr":"963bbc3ce38f6bcf2e15a3c300e7eef5cf2b648c"},{"mnemonic":"lumber lawsuit animal member stool claim know frown knock hotel noodle pulse","master":"fee5ba9a343f60a5f0f38b80c3208908e2db6f03c8a37dec5cf55d833e913fad","seed":"e1fbac7d494d3e1def8d193a71872395f24b8d62bc052ef2aa6a09220451ed59b1f74a2bd02ddc0fcc0ab0f4ad65bc17462497c431f2689d6bb9490573ce022d","priv":"8dfa11b3935e4e660e246fa935b438bf9aa9b5efded0e62db64f61cd678e71b3","pub":"036dc946490687b821115c280ff113002a160325f212b2ebde71628ebd27e418eb","addr":"3e6e6935d3d9715f525fc9a43eb1e7465a1178ee"},{"mnemonic":"city drastic advice adult taxi build local olive unknown leisure bounce umbrella","master":"0aecf2bf80c660dc6e9a987d260fd4f38daf6a00979e2e6ff0769907c6cbe893","seed":"f66f4fc74817a4edceed1cbfce8986d1cb9c9f77b23f5b33032616879c452e64b5b598518aad9216ff0b210b38cae0a6d417b7200df3fd7f2c8cac9cde2fb946","priv":"c8bb1e0db0df18c9a4254cac791a309045d9fbe6daee301d431ad9dfbdbfec38","pub":"0376d3fa96698c82b0ca3d704e8754838a80f05d01c6cc99894640da9aad067d47","addr":"9532ab7fb7493cf19a9d2e61a808fba45dea3ca8"},{"mnemonic":"assist obscure army approve salt tourist program raccoon coral economy connect around","master":"27262a815e351c2e583835dbcbb7fce7c5f96d7b8deec45a6325d02b095135f8","seed":"72315d5e5ae5936c29173566fe17da012160290050475356063fc678a8a41530edba5dafdc2404c410c4b5cebe8dfdf5cd0f6fd855d94a08ba7e856c984b7371","priv":"2849c2468ce054e94252fa34a7e704e04042dee4bda5fd82d6b38e17d2451c20","pub":"03a278a4623f9f8d32bc8198c694e961868709bb5f425f6000413e6be33cfab059","addr":"4bb9c78ec04e00e84fd3310d101f3af4949351d0"},{"mnemonic":"limb nerve equip power pyramid nest sure kitchen lady burst pig void","master":"47e667f4fa090bc0f8df1db86c5c7f835d7e43b8151b6ae51ea12b917d4b23c5","seed":"ee38177c8d3f2c0076cc84887f8ec20b9b6a874d67a0eed6dddb889c1c7af30ee9031215b6739d7858a3ec35253f0136217f888c20621b217b8aec2a900bbb1b","priv":"153335c0a20adc5347c99b869b1f1b2a30eebd4e1c71082b8f16a4641c88df14","pub":"02a2f7b5767ee89ba4aed85a5c198b9b7e62118433b61bed2f85c843576428683e","addr":"50c74e9486961c8b0bdf5b2289f5484ebd6554d7"},{"mnemonic":"toast fame primary broccoli valley fit chase critic life absent crucial chest","master":"f4a62f84c5ea1fd074494763e0a541015341f6e96d965b884fee68c0ef5e4f33","seed":"27302ff242190977db86f09e9399621c82a5f953e57dbc3a01a6c38ce8b5da4a61b0e6d5bb79bce239e5fe59f3dd12f62cb634f9347afddd89680b592872d348","priv":"7ebfe174b48e60f3809ee65cae852fbdbdc292fdb934d2dcd101f9f9be11f769","pub":"03b2b6586fe30ab7d00dd7a4da4bdc74c8092284f9d7a3e78eb9f298dc26b3b856","addr":"bfa414a410c1cbe1566a050fccc48c99a9b3c973"},{"mnemonic":"live mistake embrace other bar tell promote derive assist deposit stable chuckle","master":"d8dace20cfcf215f4cee2e55c6b9b8f65225291d629cc8f7e456778cb2f5b5df","seed":"0a5c716e273df360383a4e29cf2e2df63f47012be5f0acfac32d37241bc5580096e1538491b5daa0cb4929807a26b4225afa988057cd195d8180f5beae9a7b35","priv":"15330ed9be835c3ab25357f437b5809dad522ed0c5e1e19dc78ecbace52def0b","pub":"03b6ee1d8c8a1fe25b6982ae76008d65b5a1ee20a3537898d5c3ce528f55544315","addr":"b0a03a30af7c4a79cf777c64a86d66251d0b5b07"},{"mnemonic":"orbit recycle wedding rhythm write come apology plug blur pave job rice","master":"fc498e657b1ba22254ab35dc64173d6f9c790501b8cddd18f1edd625964c8f57","seed":"af14771135c60a6cea8c4790a008ba09c890d826c8606c94c5cc50bc8eb36f06bc8eb374fa18a67d738adab33cd849bf1d8bf744a7b33197055c75e924f8b1e8","priv":"94442c669d73ed4cfce1b2a87c76696f3954ff4fd3a867232c5f878f2fec1737","pub":"03db45a44b5dcd8b34b4a5125e54811b800879a45593af61a28a83180f75817a1e","addr":"20505c7aefd22f78b05bc45cecfae800fa1575bb"},{"mnemonic":"oppose brush collect swim cup age gate forward spirit boat century annual","master":"c156132e600a1d3c359813d2d3fab0cf5ff13d8713569cb3da35ec78a424e193","seed":"8a1ce5772eeba927d2f237acb22b9e86ed2fafc89c18e33d1fa040274d1dbce37bb2e93e1ec8e2dfbd32cb299fc966e43567f11bc0022811e6d43becb43e2e1c","priv":"f2c9a5337186920bd738336ee616ace6f7c21b68e731c8cd84dc023c89198525","pub":"021a7131768f1e6f03b1177153f36815503e411c54a6c33ee6a20281fa8bfbb8c0","addr":"50e772de1a2e1028fc46d8958f96ee5802d477c5"},{"mnemonic":"genre seminar green olive grow close siren sadness farm promote bottom few","master":"ce632e9a0596520a05d8c096928811ded1f9a09b461d2924f9e4ffaeadfed8c2","seed":"dba3b4f2981a61525481c49e34db737274b80b86ebd2eb6cc64b05d8e321f871a97938e56abadb178688373b8c29a7c752d7c53a55d7e26d660ca948c8ea76cf","priv":"14e58e498bf7d472247df7024c6465db0c2f7b390a65dd05028b42576a3d7678","pub":"02ab7825f84bb603bddf4325c2afd8f7497bd1d22b412576e8155f1fb91fb9b777","addr":"a2e64380682dcd5ba6a4a5e7347389da76ab39d1"},{"mnemonic":"radio minute vivid clean cost gadget ritual front tilt other valve now","master":"c1fc2d9a7484bbbf513ce72d4280d4d6f01ee0359c59f29a2910d44bd06b41fc","seed":"8d4a332dca82af94bdc825c3ab49b970c5a2263e8a07e848841392f4274b44cbbc57908d6522369b32a584de1d8fc012f8f7f763421cc309678386f593e2f819","priv":"fdb87e0dd6892d1b7e29e59dd43d7cc5bd7825329e569d5dad839836d551381c","pub":"0274c7216f289b5ace0f684dbd8f9360679909aee0244f5f16b65ae33b1f0d9737","addr":"ed20a72a5800b88c62873ded8f5e31bf0ae08944"},{"mnemonic":"clay verb soccer near actress august like venture goat rug brief scrap","master":"d888a8d4f3c351b37b6bf708f47ffa1f0d56dfa35f039d4c8c6e49531043c4f4","seed":"470399a3fdf3f7f113b6996468b4cd22524b05290e7e91dcedb0f8cb6d2fdd39ded069f3faa72799ed18d0692301a64a214fec0cab41b3bae69d121d06dfb2af","priv":"971aa66792510f25849cdc1a7ccfd7aa8fd82a0f42df4a947ab675fd96db7022","pub":"03f7c82b2ab42b8e221f661ee3eeebe6231f5d98ada0133431715db7302814d448","addr":"2d306db9a75411f5ee640e2778daeeac539b1a52"},{"mnemonic":"rose team vapor oyster embark shell six object rescue village rug retreat","master":"471f619784e65272a58248f67516ec85a69dbff66719eb5bd17948c4e2e27c94","seed":"39dc2669bc21baa81e45013e856e8289b858778d3b5ab601a26115beebacc708236e742bc105fbba968e364745e34a0c9fda0799b79b859c02efab3c3a4c7204","priv":"581e1940af1601b7ffc624a6465519cd6959234d112353d3a6f2ce4ab3c75711","pub":"02ba88784648b8476c55f9cc4be65140c478b835d79fa596abde69038f9b5a2b2f","addr":"bea24bf839a66b47497014c0f29f98bbd506e9c0"},{"mnemonic":"blossom turn knee oblige digital mail phrase fly soldier unaware corn journey","master":"d7f58c53d2b777e2f1162bf97573c03446afc51dc758bfaad0468c0edec615e4","seed":"f13ee4c99a6c39751bc6922c9be96321c7704c36ae6537f13146b4eda938fb8b2900e44e901223b2906db6392617d4e156554da83644731ff6e56b0b3fef24a2","priv":"c4231def1f2510c02388455fd0d5a2ccc80aacf2faefafb3534544f1b75630d6","pub":"02f0e5922755d841b79fb76f0f129ed3d10175ed4c377d02403cbfcc1c9ffd9c64","addr":"8c664b57dfede7168c0bde20acd37674a1922dcd"},{"mnemonic":"crisp joy brain treat actual later nice ensure village bind powder merit","master":"01058624d066cd1e0527da16937d9ad9bee8b7aeb76c279ea53f5c5d7e56f6d8","seed":"389060cdd3a98f5625f373a0a536f70f84cd0cc6a7d5bd8ee20fb94a2aab9652bdf61af9cc532ebcf217fb7d860c30ac1ccef4fa46b0478971653a42a1879811","priv":"7c0389b4564ae01f4c14da09842984f9b9720fca2d09fceb0244623db5cc07fb","pub":"03420010f464371dd52a34cc193d55de94a3ffb3e746c5566bf8a8e6bef686efec","addr":"c9ad4a84c23c753544d4c3972b0c3d2d86ae397f"},{"mnemonic":"three scare horse film rice divert frost gather photo weird nuclear hamster","master":"b5107f515bd7ce629e85b64264b2c9ec80595816d9bd47f04eaa6971dd565d8c","seed":"ee242699f11339f305818f44a171f79b174f45c09f2322f53ddf3094e830fddbf4e418bc3ade62f03164d8435036514791b936ccbf5f405defd33bf68984ca01","priv":"99513bf817cf2726237c70a70cf6bf5e2ffae1494b433e1d77c8b9c59a3e47e0","pub":"02641d322b89339dd17528573b100647bb082e91d7cf24bfc897e90b20f270f791","addr":"d8545d2e2a4ab61af14c04d40fcda9ad39bcaa33"},{"mnemonic":"circle pond sing route tribe grace exile leisure health cluster prevent year","master":"5cdab97cd5aecd22d671964282f6971ba4be01267c90677091d6db6b5adee0c9","seed":"929117b95286a76aa839e45e768b9f5cd23be5bfa73f929ab835df15cc1c96c2c57cad593b7e396296c0223d38c38b03a4ede097bf3b188a11631537dc6223a5","priv":"35fafc87a67c7547f256f7bffc5b19395349e1665e01a89f228f44af8086b118","pub":"03fee2e799ed5232ca3a29238e0140cfbe5f1bc6ed2c1429c0914c4000ad59fb3e","addr":"2bf9cf4c3f34de93078928aa8c6192450a29af02"},{"mnemonic":"rebuild address giggle city tired dice sorry danger onion cry cheap sock","master":"83df9579ad9f629ea3a09976302888929c52527620ec7041fc9d4e36380fa953","seed":"7cd7ab5cd9f450b45515985a2af469765286510461f42d4ee5258608aedb40b7f60365b685a706d0e0d82f775da38f0171bc8be56543113805227c5f9526e7bf","priv":"a1613406d358b4c1db8439a3e69a4d7072dbf78e06c8a8b72c37425761f874ae","pub":"0395d2c96136983d906c50caabd2f61d1c1b19a227549f7bfeea2ee327c5cea276","addr":"b51131cafeb358b0f90101072a0bd70fe5226726"},{"mnemonic":"toward turtle false distance spider plate aim daughter team jar case option","master":"229e0c7738aff8a44fa62ad310f3cb18f6b94610c83faca246c428c26f47ca18","seed":"405cc161455171571df109c3baa2b3ae25a8ab9b3c7affe9b09b32a755ea443ee590f722f9d2f3c39e9e828a2f276f46a6f9b65b024a11c6d9ae76929210ad23","priv":"095f70fb24f7f97994339cf37a6afc4e95f1a421d9b076cd714453ba2dfd29c4","pub":"037fee3b9ddecb153acb32358781fb541283c3a61ce2f25366b199f970a87057e7","addr":"f3e1bdca0ac89c2f4036f07cad45f65ecc94bd61"},{"mnemonic":"mind mirror athlete assume piece surface raccoon struggle inject horn body engage","master":"75156c7a3676b6a756cf4eba160d44d94ecbc6ae06457ca714f4313367e8493b","seed":"c754f1684d8944deeaec89ae5977458ea9843b4bfa0997a7c1213633c6203a154b65f2109e4e2746735c85ffb7dce803bcc57b38cd617b43345c2b486ce44ebd","priv":"c03717d4cef8ebc0e4bd6d78ab5ff3d5975da9919fab3cef3249d329d488fe28","pub":"02638868a04c039201022036284a8916939b6432f4a469c132748955f7e6dc468d","addr":"d7906686be6e6b9e1dc032c9e89d30ca8768c698"},{"mnemonic":"armed lottery garment beef manage knee subway market enough end clever edge","master":"08432ba7a4152d14119d22b52f8913f2e221237f125f58e4df5e5c87da81a74e","seed":"7660fa9d42eaee32aaba664593f1271cb60e092a5758bf3353c03a448360ccd74ef45b01612aae2637aeb86f8d9380bb8ce6b9eef5e0739c85736cc50b612d51","priv":"4f9e5ef095a07a939d5f3296b211fd938ca90d21f52640da2272ab812947127f","pub":"034c3bdf7e1bea7d629e2b1f82371d1d7d04bbce016317bff603a0cc2682544a93","addr":"8ecb13c1076cb132990cbd0efeb072e58fd3d1bc"},{"mnemonic":"city chest trip critic advance sign push patient choose pride lucky cage","master":"5069d7379310d03e01f740871ddb8043daa108e523ee5c2aaca252d079b9b99e","seed":"5459d94456d6fec414efec5630285ec7e05293d92c8290a07b1070b5a7fecb8f7356beffc82790b50da2fbfb28921bbdc64ac0d94e1d565f6ead799c1c04e7e6","priv":"6d334f7f362d2dfb4e7af20f7fe30887e3c91186954a8252078f215ca22e4b07","pub":"03fdb94e64855c4c973fee4157d3949bb9b840ead95906a672c1d08390e3be4883","addr":"bfa8840559e159b5cf6e7831c4696238202171cc"},{"mnemonic":"fuel derive exit magnet shallow salute clown prevent inmate hurry conduct series","master":"0c721f303dc4ba98877e76b3c36d82a82639ff6c02318dfd4547943b39d7fb36","seed":"877d16863699cca3246681de6e1b9f2c4b59ad600341c0ce16dd8c95c1f5cb1964bdafccff0af5d29eddaf9bb564552499234eee8e493549d0f6d2cea0a71695","priv":"b4f009ace3092938c51c2d7557b52900941c6dc437306883a89a51e29af04900","pub":"024cc003cb7be1724456fd703edfb7e339285883088e97247d83cc64e8283f796f","addr":"b2bfd256adb5be91b637eefc2c20efed75659f8e"},{"mnemonic":"soul junior garlic chunk eyebrow betray heart sunny average coast help champion","master":"7c420ff72c69dbc9006c4969beb830d7fbbac17dfedebbb7d7852430f17490b3","seed":"34ec95dad85e66b55bd9bde227e68b86298e4bb51b3b3514ff774298ade39fd769eea0f2433392a09578da35738cdaa31a0d71cf043204083006cbccb9ada2a5","priv":"719c2fbf2db0df066f36fb31275ab75a3905c52b04335f97f778c540e103a7fe","pub":"022333046c912253a37832140cd42243733ccded5a7a13f20ffd6e59cdf19443f2","addr":"048677f2e035a21eb91d40555f0c9ff53f335660"},{"mnemonic":"private outer service journey dry exact thank ability wheel destroy blade scout","master":"6d87dbea0c85b9ce5e67831bfab7e5118ea85d7e9103140842ed4b7aa46c2b87","seed":"d0fdad93d2b727475db3da1a5cee5a4dd8c9a0794a6a73b5c3d6078541df7e7717aaf0053edf1d4a670dbddb96a6085bee0b7bf0c4428f09bae98a46c93e139c","priv":"aea666b18ff888190a0119fd38604a711a66d235bdfd514378e6a52efe4067da","pub":"0275d06c981e89fc560ea25164baf2c238b6fe33bb65e78952e6912b9cf991a65b","addr":"32aa41676d1ab5e54575d0d13f673c352f0f99b6"},{"mnemonic":"diesel suspect urban fatal total motion other opera taste shock prize lesson","master":"6760aa56bdd6d9a29615c425d934b788ccd70b80066c85caa2091e3861a5508b","seed":"bb3df25ef6c4ac87b93991710c0b927a24c47bfe467cc3d057ae3cc5867bff989d6e8df8b790b52661ad3d310730dc55ba8fdbbda118d3fdf75a21750f2f8089","priv":"399d6d98eb0cef3674ce7d155b426638ea4679fddc624cc582e13b7f2927a8f8","pub":"03e6082570bd65f91edcdac9564f3d71efe75a4bbdedc379774ca9879fc8a57cf6","addr":"d3a69b264f967565d19aa3be68b6a20ed054da40"},{"mnemonic":"tide rain throw pink treat sphere soft season network good left potato","master":"a2f3cb3671d233a245fbbdc8b769089035cf13a9312a78826972dbb99eb4e619","seed":"0fa23be9b8c9a11dd03a5d6c1677ef78526ff2ce3e12de5195d2e1b40f2df6b90515bbb6eacfffc2e9b82ff66426fa1e3a86b51e98a733d054c4b58b6b5e7025","priv":"34fd376b792658501ede9f9f2726a94f8763151e20a3325d05eaa7e6b92b19a6","pub":"02f2f3b3ca1654334091af786f28f9b64dd599f188902cc24f2890edb62f0f7527","addr":"1f06acf38751a45fc0734766e3c794547a31434b"},{"mnemonic":"hold forest select elevator skull warm crater help sing liquid foil burden","master":"20bdfc70225db1ad0d9a1f53a1558efe992ceb7001f181a0ab5a0394851dc81c","seed":"8839e0268e51734574d033b2ac42d3de449819e0e6a916b9f59380d15be767f5a491076bb4e9c2c586cf92d07812f7338400962d28a06c2218a3674bffaf586a","priv":"8c1f88886e7ee663a9df9ad3b31871cb864bcc1ecac7e19bff14344423581796","pub":"022dc59925b511f1420f897805f13b684d9e91731c88672121dc59308ab4fd7c12","addr":"3911febb503ec0cfe4bb41bb6e9fd1924a027aca"},{"mnemonic":"power orange shoulder stove sunset during jungle buffalo funny enough immune skill","master":"51384a8d4d886c1f0a7b4b1938d798c0463e44fb0ef05f6392d5f0c3c0c1813d","seed":"ef0f86555bc2d06fb8b676196c8d046aecc752a61a95d4783f8e90cd0a3117e08c11171214fa63a8e86a8280ef98cf4cf780eb384eb78ece725b94a261770268","priv":"e644ce7c5f3541acf3649d8a44423d277301e519147f50be97dadd4ae1d629ee","pub":"0312f8f9435994bd6156ef158a80b721e73dbca17671aaac7f7d67cc847bda7ed4","addr":"82167c6542716e9c1a072228a6159af1b02f8893"},{"mnemonic":"black evoke reform inmate similar wall machine deliver gift photo axis mean","master":"2cd652f239c8ee231e516843dc9bfed097ad33378563e2cc50e07a48cf0bd28c","seed":"02da5a82f2f4a6ac1a4a962d5450899e949e702f96eee80f364faa7751b50a55b438c6e4f4fac1b6e495b06fc55f63ab9ec1d288df3d62f6644bd59dacbcea1f","priv":"f6566def1b32c210a1369b859cd8815efcf5565a2e1334afb0663115acbb5724","pub":"033055d70eb1aaa92d91e04edae35bdcf2dcaf4bd80cb2f798f2be9bfb6a6fcf3e","addr":"dbed2e50e81e7b093ccadb9cb1ec9ad86837ba09"},{"mnemonic":"sea snow child feature gadget oven this lake post entry essay alpha","master":"0ca6e7a0b4fa4522ed7b8ae0834949bf8a35f7fcfe099c237513d7ba59d82562","seed":"94a9ae63647a2b63121725f961c7e56c7c6b6985a169c43ae41434c3155ccc8244ce10d6376fcc473d7663189d51049b035e5e232834b470a404f24e286e88b0","priv":"9b029add6a255727c4cb80b868975e95086487b3a463cb35870a74414fc6c14c","pub":"028a44cde34153397149442c970964c1647e214fc15dcb345c4025cdc26cdbd3a8","addr":"a23cebeb59b458c97abd67a3e32387bbc56545e3"},{"mnemonic":"couple regret airport shy symptom shrug mom sail insane culture detail shrug","master":"37711c29531e793d6a5e2b4931b3aa0240ccef541c9b8408c29bc5b68eded038","seed":"9c054a9047e66665412432f402f94e0dc5f87d7cbc1a32f6486cd928bc2a9a0aa898ab91fd3aabb962af985da396eae7a0d00fd739650bffaf070c9af44721ad","priv":"b9f1026e086eeeac648193494b398770b7226d46221cfd7281e5519e81fd8fa5","pub":"02de158ec50efb1b8567d63331fd7aa66aaa59ff26a621a59a52ad9341330b028f","addr":"aafa96d9be89b09030494856cf6275b8e9eb387e"},{"mnemonic":"mechanic sadness easy credit eyebrow sample health pyramid bargain actor power glad","master":"68c40248a8f7049062f18ada91f3f2166b37865895d4fdc8000268dfe263ef9f","seed":"d28287812cbbf2cb8509eff491ac456ed199dd2dfc8cd3c93e05b0541c154850f09f686a573994d41f163884708db205b37afb02752a8f0b2b9785c6eff504b3","priv":"250d326d0c232bdf26b6eb8e349ae5b49e30b0507fcce0be1be3bce55b0f7645","pub":"02d5713f20005136dcf9d8653dbfd75bedca3f42b2682ec9918b92394f2b685b13","addr":"409ab6606687cf83eb6c2388d6ebe296b4799967"},{"mnemonic":"mercy spell despair dirt board clerk about symbol supply provide rigid evoke","master":"37a0b9c11386b995b71a8c8a5e150ff76d238779b8148a2137e5d4666fe9fba2","seed":"9e3ec2ab97061086be1e02cae7a0a30493c019247411a7e7271c0c5f7f40bf027ad3230a1f113580365c82549ae23c024ad85a8601a30b6f78276744ec803a35","priv":"0385392be6a34bae1ff02570948ce1217fd11700ac753bbbbd47a1ece41ba38b","pub":"03218315e18c8a5128f74186223d32ece9c3987a0427db95ef51eee9acda922c03","addr":"725139ec60574c941c133ad2e09e793c1cd32cf3"},{"mnemonic":"emotion exile anchor charge silent nose family tube view lottery neither razor","master":"d8e841a6199f937fa9bab3c3905db51899d456f9caa5d85d9d930f9981ba6ddf","seed":"c8610e5fa0b9d9a83610c4458129802434d7045fe44917de29366c6d900f123e5414fda889e21284405c4c64fe745a36966ee86fd6f61d1dce6f809e0e63624f","priv":"b567d59ec087a81214968756017868e483e62326bd7239fddb27586745412349","pub":"03182c009fea42d30f31f24b6a5d251b70e72c1262d22f25418653cee5a226bd15","addr":"9f5d2ece004dd9ddd79b164c4b51361eceaf077b"},{"mnemonic":"orphan attract pair execute fresh interest artefact shaft sail advice vanish moment","master":"b58a705076e0c5aeade31776d78d319529bb76ce748aacb266dc910595dcddf3","seed":"a156c3c4b6241c4e99bb63dc221b13fa77219f2419f7c33a7a7752b5dd89d5be2178b6c2de2225292bdd0bbcf3b90bbccceb93ea65deacc866a5a3da4944ab1d","priv":"857564d0b2bfda18ecf561ea17dccd6d1bec2100e79b6adc8a74997b55f53126","pub":"03eddc58dac77538639745d167c174b907a448072ea4ef9b14563217716fb606fa","addr":"b3a656fa40d9a7f6471cf6dbb00fa0e2687df914"},{"mnemonic":"guilt awesome champion wasp execute purchase better mirror such prevent stay actual","master":"ffd1b0b635383f57d0086c1e6e4925d82a9cb9b310d0757afa8d941a13a1d61f","seed":"c36560913942649932b4aa8027520c46155135b50d66b0c6b33a7f63272b3d531946fe3aa0efd22c6e91d5e19cbed5183ac2bc7a18c219ee3e47dfaf35ef15dc","priv":"d5d35667bd3f54b40858f3cb4c6f09feb889f11d6da386935fe8b42e3a37f58d","pub":"039cffe0fa0a30d9ab9b1ca354ace89b5c9cd21d2653eb5a7a8c5b66c03912bc8e","addr":"1f2543b17460c4f7c2d53369fce6383771c0f7d8"},{"mnemonic":"pyramid soul ahead tongue observe void whale sense keen feed today abuse","master":"272cd80e4846e16f0449c68c8ed4c78071613b57660b85206f716203014c9231","seed":"2f342928932ee8e588bb75ca7cdcd7cc61f32eb317401047cca5b4133e963776ad86474999f9013be1f6cd947cfd9cf4add3be93e686d467223864851ea71751","priv":"d52cf3cfd4e6b6310bf47db08d27ebf0109582f78419d369c4c5da596f22bdb6","pub":"038f855f837abe423fcea3ce81efeccc49da313c71630cb08089956afb73e96e95","addr":"c888d0e5e1840b29f6d85a64cd9704221fd3046c"},{"mnemonic":"speak mother wage grid column movie pluck deal bulb stem okay own","master":"60b4e8cd422ac64986d88f4412e0c694d77d09d4469b06bbc8b4d9146ca5e412","seed":"ecddae7245f9ceb3b2eb324a78421ba99d3b39819bf394be90309cea21d6275ee32a068aef8078fe3514cde61a69e31ebe091669392b5c8a442451b895591ad8","priv":"4695ad7f839e5997df9747bb6d4ceffbad85f6bb26d01c328e6c6e8f6e68800d","pub":"038e3e274ef62db04582bdf01f4c443fdc5b590da7d2c0f5f1d1d55abb0752d440","addr":"359c60c1bfe3a11711423effbacf7d857d39437f"},{"mnemonic":"unveil toast planet basic limit bubble lawsuit grunt lonely favorite inherit write","master":"32bd1431071e17e039819b0f3d9d959c5b88813cf49ca69c90fd547a4fedccea","seed":"d036fc1412c23cd5406860b96814ad9f54359e103834f5d9d1e5f79d52b2e0a8432c4ae705d2fffa9d87d4946dbed2c847a4589542f8051cf6e37689617964c3","priv":"93fac9e5a9e35c2138568864525be7a43492e11f715c097879033aa4019cd723","pub":"03b0e30e8c5bedab8f9ff703df5c9440855b96594e19a367f3c1da13aff1f606ce","addr":"0e70b83293cbdc8b5e3cbec03cf2d0602f221bb8"},{"mnemonic":"cereal forum again rail lawsuit mandate above vacuum hour tiny rain mad","master":"1bfbb49078074dcdc191bb798d3c02462d736f786d7e91461000fd644aff7cf8","seed":"2aef95fae13809c1689d4b5a6598ad4e642ec6373dc6a10b6f348718360e3397eee9a2ea24acd344260d4002b2e7857e71daa1e7111f943ba036d4d94deaa339","priv":"75d0ffb17edca54a389371ddfbeed3a930ff3ca88ef166a67101b9091636d7a8","pub":"03c1ab2904a55bcbd56d1f1b2840a4b09f665576e980cdf2fcd45db21c007fdf82","addr":"cedaa1fd49e697c467d8ce47a2d67ffbfcfe1d56"},{"mnemonic":"infant glad torch shoulder benefit pledge social indicate present deer glare hope","master":"fa580a50eb13d1df4efe98cd3e86a1927e4e9b8262a279f2e782d298641a7378","seed":"a3e088fc36097fcbb0286a2103e2b8f8c1b3e0f6d8521c82c2c93fe06a8c1b97748143b02d9196f095ec1be5044aa0d89b39bb131c4488858e549879cca4c8f4","priv":"61c933db24e871ee66fcce148c2ecc3fc85def76b1e6f8ea784001941deffca3","pub":"021922866e34e3d7e28c1f5778c70e7b8742c2c9151ca5559039949f32ff0854b2","addr":"e550531be6f31c18a90bc6add24c02895e444794"},{"mnemonic":"laptop vapor rotate chief frame trap duck satoshi obvious pen oblige bubble","master":"239c6fca9e52e705ba0d2d9190a08748408d3a53ba0d19bd51fef426b56e1ac1","seed":"15a573343b11562370b53ccc9f11be74e183b1a341ee922cdfeb8c84a4dc914d23ed6a7f58ab451cf57d5f610c8ed9f16e2072a71ab8edd82414fb9d24cacc3c","priv":"ac37a60d2ff77750624fdc0d41531ebc6dfc1786f681dcdb436ec920f91a34b0","pub":"025dd6a2cf772db80dbbe6957ad1606603cd5b065177ec93263ab0194505c420eb","addr":"5693d8a59358cd00f21dd8786248538bdfd0edc6"},{"mnemonic":"walk raven camp kingdom defy nice hero copper velvet lock era south","master":"b4292f1b198481120f507ff13017c2756208cbc9652c555807472d872a58b9bb","seed":"6d2f085ea9e128fb3652d78d9e14e38eaf793ef1aa8b455798556482a5c2f3dd43933c26ab9fc38fc4943c82cb85c8c5b027534f005d45b740b31f63c683b87a","priv":"ad8fc7544b1b79028e05e96c9889004d9c5cdeddbcdaf05bead09b2075dadbba","pub":"0285d2a820245c70d9f3cfc930d6c4e467585f3c831cb7bcd46f58d637bc827729","addr":"4c7c3cb79dffd93e81882b609f7424431e55d4a3"},{"mnemonic":"impulse certain olympic advice entry initial bean glimpse begin flip cotton mechanic","master":"b092aa1b7fa22bacb8915bfbee4018484dfa4313c5779142e71e6d80e334dac1","seed":"9dbec0ace8784d915fe1bd3cc88b7c774fdc4791ace38856dc0500927aa02975feac3d82cb9771bd69d5b2f8c11c663e887b27292e3ecb36ff33ef5b3b3a804f","priv":"19497a3611ce0f1fb43aa5c55325099b6fb18811c99ec1d38c743c3338ddf5a4","pub":"03ca79e260e5a06bd3717cd969610c06580f65aa158e770eb418653a95a52cd0f4","addr":"f83b4129ec37643eee9f5d262cfe80087c5c5841"},{"mnemonic":"trip brass eyebrow plug sibling couple tuition tired warm spoon october finish","master":"ba1d19fb67d08931e7fc46b55c02651d374c7edc6df6a3b89fdf9e28670de4ef","seed":"736522d275c19137d354d8899542497b76b304e33c348e8f446970cd77aea927e2fc1d493b5f8dcdbe48c0a535722ba4223ef52925a57db21065f92779ba66a8","priv":"5d7ece46202706ddb8db591c4e125403f9aadb33c96f259105a0648ca1e2acfa","pub":"03791637ee34fa2e798c0cd98ea21cd17a4c02c429f05a9df5197b852fb643216a","addr":"e46722ace3466a38d3b5f66d5f94786d4576390a"},{"mnemonic":"suspect brick cloud coin tomato guilt inflict type lesson panel right still","master":"56a2d9f123ae22001633fda68b5b6f1fce4959ef19dac2c2a88ca5f774cb7d71","seed":"bc3b42627e5dfb76f1808f75e8cce883afff811821dd615262fa62c98c00cc0e0fd97b213f3b386ac00dc38de399e6e56b9216e34e4deabd15d23d70ff94cfd5","priv":"0d2c63fef727f339b9563c1f8bda5edc3755029ed45010e99bff74bf805f187e","pub":"02742f8b32ff1cf54225fd667425daf286705a0852e554e3294d057f5b41bb737d","addr":"4ac1a9bbd5d9d9597587bb809db80b69485d7e47"},{"mnemonic":"innocent interest smoke fortune smoke nothing clutch dust ladder clutch property glad","master":"ef88eb840ba3b3a072860d726b16c5309b9959381db68debc03d9516a7a7205f","seed":"33a6441e74f21794565c5e219b98416a17316a7b9335f8a0f5a5ae45608b315539c9938740266b663f8410e773dbf361f67550e185c0d42fdd4b406fec4cecea","priv":"5360d32f6dad727cc98c0c1b0dea7bf59f89aad2e5127050294adb9d7b116788","pub":"02bb5f40466fe161d106ccbae569709217ce1da9764078d27d3c8375752d7d1f87","addr":"fbdf59b3297e67600290d6bbffbbd39fb6f460a6"},{"mnemonic":"employ ladder boil blossom level pitch vapor gain permit manage coin supply","master":"c550d7336d80488c81ee8f2a8c3fde04784b8f3079d63add93b1b8800f796024","seed":"a80406c58b6958a30c0bb15f3b6f9efcf7e0c8cd0f01114a72f9c446130801c539028c964321ed9ba511831aeb00dba1be82eb28977319f727de1a9f44b1a7a5","priv":"9233c90da7ccf973268310430efa073e4e070cc5b9dfa29612bcc9541ca3417a","pub":"03c070cf8379e2461ab6158755f45d881ab8fa7de1501bcf0530a871abcef79947","addr":"fcb4e5a3f2c568e0d513d1ad97c7b806a96debce"},{"mnemonic":"winter absorb stamp enroll brush paper spawn slide salad special front update","master":"ee7f947acde43c8fc5761af3ca28fd0e87d0883043ea4401f0d692ea818d5573","seed":"0f1b5f166616c83c129d6b2e0d1cf706d46fb56e60698beb77f9eb105bc0726fa7159a4a492b6de462517d692bf26a310528379ce47b4325879772f46ea0d82b","priv":"31f98cc2cdb0cc6d88d91f21960136650d3e7bca690a569edf2ddd6095026e38","pub":"039ad4a48de652c30e015c424c2ef3083f234e5df78791f112eaaab2f74deb71d8","addr":"68306083576ce37fb1bc653ef33e46ade3af0c7d"},{"mnemonic":"focus usual earn deputy execute climb broom save robust coral piece shell","master":"148c7f123552e1be3f696b2f8b0df4885de067d43f9c1e5c671ae6a3ba4dd361","seed":"a4f311017ce37d0f1199253671b0f615ebb09f92648cd33590c2292d4bbe1eb45d6f1a94e71e1438c5cc25974efb3b8ccd03272ea0218f171b01c7fde17adefe","priv":"587f36966f566888d5684aa983d86c124b4657ae02e7d540939c5265ca50ae59","pub":"03cc82a5871dbe05fc52fa3308bbde84a7744780a4c6344910989437b37b59a0c5","addr":"6fcc01f30a9e9733ae95a049ff8b3ae0ea8c97c0"},{"mnemonic":"twin over wire boy dragon drift erode human shy minor panther library","master":"1488ced839d3116ec99c8d4fef9208224ce28d270e4d3242999e8c3d1bcc1719","seed":"099d13da6ce761e666e6df0199a79e95e83d0c911279aebf8037196af6c7070dd08867dea0da8d9148dde2d14ac55864a7dfd09464282d93037aeda70520d664","priv":"1c2871733b9b2fc6224400612807d75387c37b87b925caae6486b777b0dc1e53","pub":"03496d39cdb3d666703b041d5bbb69482537addb552335c7f26d29e09d5ab8500d","addr":"7b89136f5fa6320f53c25a260b4b75eb5bee5774"},{"mnemonic":"potato lecture exit color target shell more push day catch entry erode","master":"a44668b008915d6eb656347398a007187c18cfe766ea167a89e855a77b9b9c37","seed":"815db5e7fd253567cffc54d938a3fd307968b45e95407617c55201d661f2ed581e92f86a1391154e6421bcf04487da1d3c098382a3587e8c5679704407812fab","priv":"de5ca4846103dca9d947e7867ed764c9094d40ce189cd2366befcb557325616f","pub":"0342ebbb943a1f8100441035ea11cf76c17dde7b7298d160ed56e3dc8fcdc7a8a6","addr":"68f2cf5a8b290409689d5e08f99cf1b882582e06"},{"mnemonic":"grit flavor pass apart swallow quick payment view weasel decline travel hat","master":"3a2d8a4e041357a246be12beaade6b6c36fdab6dfbcae0f8230106a09c5157ab","seed":"a69ea95da4c681f004cd5fa6d357c39e50594b66c73a42a93672c13d6dfea0ff0d741bc1b406851c36680895a8137086f6de132d2aaa47af5d4872fe08818171","priv":"502b2d14765f2f80f6ee0c10b98333e498dc7d33a46424555f8429488d57de1f","pub":"02aea2e315932b900ac7e31d5b6b82e9966e45b098dfec8990e2efbf4457e581b1","addr":"c6f8426ba017b6ac4fd45a95341e760f158f0e41"},{"mnemonic":"machine insane tomorrow island assault follow verify spatial pizza major cannon inquiry","master":"be46461a96c733b1add1465d664d74b8e1a28fc1bf25746b9e9d44ddef81ac4d","seed":"2a5d713bf4b71dcb40d0526767f8466c87f04ff5b2fe38ac17d5a7418f099b25761917cddec77d369471bc22a1f26d2d7f6fcfb00b3dbb28f08279ee0ac85f1a","priv":"097be27be2a0c6960b436acd651792cff207d877695acebd02c088e6f3617b9e","pub":"029cde9790e6e1b55d54aacbfc6d13f09fd6b5b04976d0ba5e01cf25da0a2c3976","addr":"06ffc6a5742544cb3a66bf443ec0d8894e5822d1"},{"mnemonic":"plate grid latin above champion demise idea urban raw exotic day diet","master":"9daa2224bd8c15a22941090eac1262e88dd2faeb708ecc00f404c88723bf617b","seed":"e07adaff2724a1af34ea6f3ef3e583086fa835ca126f731875f97b366322aafaa8d39b72a22dbe70bce68d8539f1ac109e3af03db42317f3807e91c35748438b","priv":"423d528c82af34cddc13f8873534984302e072e0aabc9f29b10c70335bd72a3b","pub":"03241547e0145dfbac8bbd5daca05314e2fddcc22aadaa8b49952e461fccce7d17","addr":"2c1ea451fe2233aa3d2120f2b836d11cba72905e"},{"mnemonic":"august quality apology more ring armed hamster raw gentle inhale angle dune","master":"90478d31095fcbbaa751055f7d9fc70a75a8dd34ff825f1b4547103e3367ff9c","seed":"0b944522c472688010098bad62a38a173e0184f64710bfc370e8ffd7a3e2383d630202f52b7ab9b8d41948f534b2bdd0e2664ba76f2e460fc62e73fd1684ff16","priv":"e8228b2ba4ccd5a22bddb4e0a3eaebe6a156ffef061779d264b905c90797826b","pub":"039fe65040d933902f33749f3d43cb924c6df298a969b05760bc4c8fdfb27df444","addr":"b0370814a3a0390cb3874600ff1dd48481725ce0"},{"mnemonic":"argue april pattern jungle favorite shed silk adult file cover benefit neutral","master":"3d5f13e3264550f14ba0edb8b693e7ccb5e74789359a7cf9b7f819654542acd2","seed":"f5fa70f943ebf5b941574820d4b91a1ce706fc5f9849219b7e2e0f9fb410c4bf81494beeb390383e1371817a39f2cc7a9552452d973ea8a15ab42b881c7cbebe","priv":"d0d2a3c92a4b1914064826c1a9205ce8bcb076109ab2524a2818d2ecaab08420","pub":"02040f9ca1b771a4692ff543df77d2fa8fbdd9dc5817789b1ac491a9e0908638e7","addr":"0fa135a93523a80e03603c609953a2f5c7b799ca"},{"mnemonic":"oppose describe defense consider long lesson better kit capable taste feature reward","master":"07ef0cb309577396089c024c1b48dd278d0575aed88e63ac99a44d531fc919f8","seed":"c6ab42822e1fc062f5bfa6e5d7d4049a28be293748858bb2062f4b9a3c20cd30dd02e7e86dbaca2c77bd2e1d837247cad63f8009f9f9410a0365ef7bbe647727","priv":"3b82e67931a96b68e6752ce67c1f971e23c317f8829e2573672edf1d80c68836","pub":"0248ad30caf9f25bf49bafb519ebb3d381ee9c95a6da6502e1cfc88a4a1aea3b37","addr":"17fe8e8d5f6a4a10a2b7f0323cb6ba3bdba9fb93"},{"mnemonic":"blouse patient surface urban stand clarify old pipe where spatial cargo shell","master":"8ea8f4837a7a4a3bc100595fb6dde65f319e8928e61f7da3eea2309169b9dde0","seed":"a7ae3795447825e6d51da632bbba6f8d9f5e141e593d2a78e05f7e0914ed063d629f1f84d3427e7803fd1e317ca0359654bf918724705f523e2a896077c39603","priv":"6fad450edfab3300ffdd65133f2973fd0be3daaf7864e2f357598b63416a01d0","pub":"0211cb5c4316de9b5c08e8764027f9dfa0cae026cd9ff9773dcf524c06f76c08df","addr":"2a51a4f2ba38e779b20b7a4bfba78e8267b6d6a2"},{"mnemonic":"moral penalty ladder step panther divorce bus adult math various speed rural","master":"9641e82d742f458ff81d48d486e38cbb8919a56b2a317d822674ab9e4887ea6d","seed":"8b4c57108bbc2c753be29e0b19ab43b06c7c81e2dda243535f74c08222b23789d3aee754e23ae936df99c8728b04cde066b6e6e512b54fcc1dac28f0b0763074","priv":"043dd7b6ec9ac7f52f1af6f3ff95cd99371c251680c04b3c847da42242d152ce","pub":"025f7dd3fc53ceb3ff87c2bfeaabdeac1d5f1c9b8a897bf4a28ac33b95562e4f79","addr":"9a61f1c15e55009122d52a1c3917a8459fa29238"},{"mnemonic":"pony liberty split enact quick arch mammal velvet cart wait arrest burden","master":"e5fbd41b38aaa3aa643a66aba1c3e8a01709b15f5e1e30d639db03dc87151065","seed":"b6a41b697611cccb9e117b5ce798d47bb37ce1e07341cfe972905a4b66c4b7f8596cd0e6c21e0cdd221be33de8c0a5854c5f8618df36f28d95dd1dfb0f31ea1f","priv":"7e98676864a3649911657e32afe07f6ca86dcac164f4a25e48e33cb147ca4401","pub":"03d61b29c9f5775c6d8fddd6babb2b8e77b65fddcfc14b8c8f6db262285f2d1832","addr":"36c7ac440660d8a8642d669022d621ec1e877680"},{"mnemonic":"summer envelope tumble six bar crazy forget very canvas sign inquiry copy","master":"4c8765857e92661d69dd8f64031d43df4e1043c786f1ad7593d9d6cd7967b9eb","seed":"662aadea06a5198cb230ab83449dff0514b2addd644ff618647ca788cadae3872e19becd5e8de5719e3397783cda6ab0e1496e8a204d3ea8ad524ea93a1b71a2","priv":"a1d833122e4bbf53a23695915b9f398fc8681f72cb68644ac78f5b0ed0b0839a","pub":"03620a954c07b3e3060414ee499f68a904a8afe2f7f3ada8a9db2e039302a1ac0d","addr":"9758f18ede0279601ed2cbabdc5a87e0858c0f01"},{"mnemonic":"robot lift decade april style rapid thought modify tenant wild pottery cool","master":"d43dea8679ea3ac6440623974b253ec2355e97697557c8d62b5a79eefbac54a7","seed":"bc1a90f5410aa2fe22248c19110d3bba802378c5fdf2f3a2eb379c767b3d222fc2ed5d4638876d51fc9b44816c229673694b3f3e8052257f574ea50fd6d7a73b","priv":"fa29d04ce0480d48e6793e227e142330aca85e4e91ee9488396b1b2af13aa160","pub":"0342e19701eeb5ce20b17397457db9105c092d00db7250968ba38a551fb47d3491","addr":"c8ae52e691ce5b507a152f47970456570aa6635e"},{"mnemonic":"invite pool cross silent empty jazz change smooth appear snack cloud lawsuit","master":"0ab641828b6b935da8bfd58e4910c6c3fe8cff9d18548f86bdcf7d501875aab5","seed":"4aca0bb1ece15e686bb9f08a0796eee1830b9d926b0daec50aa9e280ea8eb522f546c1668cc2e2a8beab636eeeb992569a8a8b50e2082b5cb6bdf3195a2e8376","priv":"621bb83e46364ea7bb3a1942dd857213f49077ebfd735ef33095843e11866388","pub":"036a6b24125e8cbb887c0e9806e5feb6555f3ad4042460f1ed85e8a5738a02cd54","addr":"24f1dc90e9321c1faa33d3b2fe82665b402d4e47"},{"mnemonic":"current exit average case novel nuclear recall unaware decrease maple bind acquire","master":"a0a9d482c7bddfd3a8bedc54779098ccb0c54829e7949b52969a76f4a67f1ee4","seed":"35f2474be8f9068d91047c19ea9f1471aef87eb868804c5c205692944bdd1a3ac62ab71e052047961946d9c371da2039e89cfd77aef881d23774f10b24c3e16e","priv":"75684bde10654605f673ba3f704edbc2329997c9b15128b5640b6c4e3ca366af","pub":"038617759898bdb797d411a87d1e55024fde8a1a65c6bc0391a8deda099f6f7fb8","addr":"5785b3440fbc82cef8b1fb97c8f8e62a590b1842"},{"mnemonic":"tone engine property lion used spring enhance need choose grid deliver sister","master":"a0b23f0ebfd072c71a27b964ac5ac2a58fd31fbb1b22c044631fae01b27a425e","seed":"e47ac6612dade3bab1613b016b271eae20948c7934b08d8ba5ad0d14da1417b27512550287853c6c71efbf023b8026d015b063696d562850b991664ea8bc29fe","priv":"bd8279824526377e70a20ab1ea2731f815ac8975ec7ba5dca90d54859143f409","pub":"02dc15779fe271cc197da7035bb0886d2b44026292c137f6cfa8c98624665b383b","addr":"8271679fe371b6ecd1e989a2fb2d7c8d981006f4"},{"mnemonic":"nut solve indicate dolphin crush bike puzzle outer mask bring dutch city","master":"0aa08b93b1e497f4e54d6caaccc4769e921cbf9cc155a5b8877beafc43884c5e","seed":"c7c692ed2dba467ca28d1249f4d39a5c0405a54131245312d05597ef2aa31af3bc6791850693123f18cb1c350fd79c1705e53ffee10a501b82d0cb237d256579","priv":"8f9835b154157f65bed179b6008a352b7ef7d5ce8e21227a22c461294a7410c4","pub":"02da01e33c91da079a39cf256db26b1254e132b894abb9f07bf01118b5b6edf486","addr":"3ca1fa508b336a46f89231ad841a5a14d026f5c6"},{"mnemonic":"inmate snap bullet curtain used blast fatal one join owner route absurd","master":"e4003c062456105b608885d8928bbf183aeef12a48d6e3510dc7f183514a5195","seed":"0402d1974747f32f00e4cc90795f28d6e07a12cba21f31e22e0eb269ad1dc5fc0ad5d6ed1b5d5268dc50e31fe78b7145546de0cc1fe7afec7f5e3831863e21c3","priv":"8e55c05a82e8fcfafdba94b07dc54940cb247eebd1c689b51170f4ed10628a82","pub":"03637d8eb1c87329d9367ac73d1f65e482bd13b072d791f5fc6c08fdf262afdc8c","addr":"23d0d3d1abcb6ec683851d8c21a1ccf5941f18d4"},{"mnemonic":"useful panda fold short about copy hurdle vibrant kingdom draft suit cat","master":"9c99df0058969798ccdfef6b053f61af2b6422e4c691213f1f0a59f4792156b4","seed":"4fa3755fab5aee41e0771c00362701451ac578a9d7f8b139d9fdc043b33fe3566576a7942b904047978bf6a6360181187d9f20bb3475c495985e39ac7fcb8987","priv":"2b65da5f7070729879787dea803a4f50e2df1ae69c39263525bad0b5c994f86c","pub":"03a457cb29097644eb4a8eb1713fdea43d3992b2b88dfd41fbb417845e2d373ce5","addr":"901ed83b7220c552c69c43d661dd468ed94ac9c0"},{"mnemonic":"intact pluck author dish garden frozen speed grit bar narrow across loop","master":"548d4844add02811ea1b60ea7a6c337883e818ead83782a7b5a652ac038310c1","seed":"6b3b40cfaf38ae1ef9938c621264cd373b7e15d196bf9c69ed6efe3f7a7a13900b3c89eb915fbc99457347832ef1b816da26cbdaf8bc48c2b8a634fdd8be2346","priv":"eacaee29cf8bae543732278774ef2244e4da49dac3d88fe11fd7ff50875ffe8d","pub":"03d4d894053c398cb5dacd431a03c673a4f883781d951254fb398a24de018c6110","addr":"0b0840f26af38181ca7f65b7fdc0a3309c28fc4a"},{"mnemonic":"equip energy remove soft alter mammal valid van ordinary motor follow buyer","master":"6110f719e36e14190eb39d4ae66ab2b35bd5c3fd2969145fa3881650556c5384","seed":"74163bcc1522e148a87a95c78072359a600b364eeb558d052b722e5db26b6b317a36219edab7558cfdbdaa6d62c7b078c691fcdf2acc493139a75cbd26ea3bd6","priv":"fea286b716369fef22c1c5ea7da50ad612868d81c50f5efdcbc2891e21328a7a","pub":"0269bb65b5d86befde40cd916410790f9a959c2f3aa3beb5efea2eb358eb5047d1","addr":"9ea73e16006fd30dcdfe1ecbfd2792d5001ff8a6"},{"mnemonic":"limb boring random already marriage team useless polar impact equip rely sunset","master":"20c34a5ae5b9f78bf032d7841469e27002583c800d4aee849860d58213210c39","seed":"848bed01605f3574c16480514ca0cac72c21412fb6ec38833f97fb744e4a975fd06135c4761aad05d73280bf2e1c8297769e5cb69299bcd99e638e4dd542c52a","priv":"59f09a8926377ef066a13627f032d2e700795976230ff2e58806d046af0450c6","pub":"026e3e1e7ff239b39eb4a77477b32d492f99cf1f611558885afc7363f3173a8a28","addr":"61dffa0563d51aabb1e2e585bd444f99cad8d54b"},{"mnemonic":"vapor reflect friend jazz chronic supply awkward tongue venue finger island brass","master":"4dff50c5a87bb1f9d2c0392ee7eee59977bceda605a40c0a111ebb46587a0ab8","seed":"e6f70f78b981254b4e3061270fa96d262d2fccaf8dafd752db2d0fb54f943ca99f49eac07d279b5875432dd7c0450a7bbb776e8852e5a5a82886c65803ec1698","priv":"50273043958c3d7cab07f41dba28080c507f460ff920c6b5b135d1c64dc85469","pub":"03ef40a81e58c08d1bcde1116e9e748eacc38906cec057f2984fd4bc91081492ac","addr":"a8aba313467ada95795757ab9a6661395adbbd33"},{"mnemonic":"top base town abuse shiver symptom green thing vast eagle steak warfare","master":"7ae7456584c905376b6e6f6732b8890baf4d768e82dbde95110effd229dabf9b","seed":"f88b915dc5753ee33855b3adfd292bc56f04e8aaca117023209877ac8cda5df3218c969f1491bd0eceba11ac433016a05a9aa68d6bf3fa59b51a5cbb224c902e","priv":"ea68890e0209f3444eb78727cd3f13dca20c886e8bcd6d31689dbba04d000a30","pub":"02030607d8a2bd18f8af9b67f99db31585b7a879d6f08588aafbd18dd95d1cdb7f","addr":"fa202ccc3db92eb55d45cc79891bd4be395fe4f4"},{"mnemonic":"picnic seek ivory unusual text become room artefact rigid unhappy wall harbor","master":"05638878665f27362c8165adf4b73fcba3cedc983be2f0897a333378f53377cd","seed":"8af253d6d848e9737c8b7807d37558e5a1f09d3a14eaf5794c83c9d4f2021166aab7faacb967eee25714553003cbbe0c0eed9487d4499bca430e709f0b8d219a","priv":"1357da6886941f1c50a19bc3b32a174acccb21a0415dd5a736e5d679fe505f0c","pub":"0333d6d3cbce04e1ac26ffc7c2d4323110a038dc33ce293e95d2d34d11bc1d4efb","addr":"c5aca5f8318f75bc143edda3eb89821e751ce0e7"},{"mnemonic":"faith box profit long entry that alley stereo wife nurse tiny assume","master":"67457c024bde2135b9493124ed03d0f5f30a66530c958693f02e463982362395","seed":"4e22fc62d0a60e2de3ac33de2db8d4a884905ef84071b190f8d43f57040686eb3476fff7a90d19b1bec66d8a2e62532501ac7914585ee5272c352197ee0f0ead","priv":"ba198f97576c66c34196a36a04172bde332ea3c6777ba1af203b68aeae3e23d1","pub":"026aefd0f79219acbe5377bb3578a38f71174dcc9f77fa4d1dde3f3aff35ad2f10","addr":"44c4ffa77990259e15f2b9dc59223c5807c85f54"},{"mnemonic":"brother bar food vessel shine object tape acid opinion brand indicate local","master":"23338d7c853f4e76208d97bf0c47db2dc317cd4501cf00c037e60461eb373353","seed":"e32c6c54ca4fef48630e4dd7927634ff1d21c60fc488e6f1b87755d3e79f4845a3d9f86ddfcf185136150b52dc2f721b04f2d61913d63461956764487fec24bc","priv":"3d410ce752789e05c5e02ba5e95c2765f77208c90cf32f289306204f82d6eb77","pub":"02a795076f39164eacecc80ce04d63e5336edb32d183c06dd81f50e330b6102c11","addr":"b2c8079676ea7d98e10b24d9a09e3d4519d32d74"},{"mnemonic":"broom together virus run require inside output spray day hour cruel witness","master":"3ec636201f4b0a0fc7226003f1744766875a0f8a76e19d69c6c352d86c672cb1","seed":"51bbd8710577f0a2eedac3e341727d975b0e79e2a0777766d43b0cad8b66e302d1dfeda04a6a5ae0683db18955692aebc554c4bcaffb5f9be9f4db079a550f78","priv":"9063c803e2075922b916efc668be57b734212ca8ffeaca221b587912426af4b7","pub":"02b66afbfc48ab4d97a0965e7c96582d2c4cdcae965f8aef72ec98f59b31d5eb7f","addr":"b18bf07d9f82f070da34d8a4e58fd6cc4125d51e"},{"mnemonic":"crash knee output divide essence fiber benefit pig recycle patient strategy already","master":"eb84eae9a804895edbc89020995755aca54a04a4e7b59adb96b2ad0a8b465a7d","seed":"4aab7e57401b6f2dd6502dec26ef36e36d712ce328b5190d73b9055a118c5ead12dd5f18829affbb488a71f18cec672093d58452bba1ec3775739c1169901e67","priv":"0d0ebcf8cd5c075d9b8a2d2e404d44701fc1a5dabfc5b3cb50cae523f3c4c149","pub":"0296e9cf1cfe73a4114a1afae2f05a6a92da168113764431fa57c25afe32258d8e","addr":"8aadd8961827385dfdda784993f8f135a6678a47"},{"mnemonic":"shock assist horror regular voyage gadget cook pipe hair boost release stamp","master":"74b514c25d4fa22f3ac95aae2e58824da6ea181962c52944256a8b80cf86e980","seed":"0a13bbed1d4d3d6128d1eb0bcc0595a4c72701c7e9b79ee6110a104e10e20656dbbaf2993a7a06381e9ac424c3db6b0e98be6d5c2817433660256becf295c70a","priv":"75da3f78a45cfc4e9318d5dcaaf6595002ed037605ca196282dcfc18c5e6eef2","pub":"037187c0d2d573d9c28cebed9214ac37fe20c55ac56ba565fbbf7dd79c4eeea519","addr":"4b79f08bc7daf43ea00b5ad88b7642c31fdbdb17"},{"mnemonic":"monitor artwork plug dream sniff sketch oblige prefer dentist rice degree trip","master":"e8211a763f0b7f2f0085047ec1687f9c37910d27d8580696ae843872a7cae0d9","seed":"0a48851384b8dd7f8c99bfdaee23b143479e589bcced7ea423f6fc5bee68bd5e5c45834e3f2a7a1fcb928781e37e2b3078d683c15dbcbb5032ca31d3425dadf1","priv":"8fc0e10b570558eac1349ca8ef4f47b2f6d3f148a07b94bb2f1040dcb9bf21d2","pub":"0341374d25e701d963aaef0316ca0b4f4c15283f6b8b6932d954cdb7d7af4d7bd7","addr":"9a4cc3bcd48d6998280db5cfe856f7ed85d6b4df"},{"mnemonic":"bus deputy eye shell isolate judge nominee later gift mouse leave square","master":"cf814d6a08c10f4f771f670120b52cb26b6265a5510c5035d5bab583bb7f314d","seed":"9974e3131ea54805d6e9cc124ffedf80b0ebf935cd0908c4d8d7a3b777220155c056f12a7cf3c0c6e58b7adef792215cd471c080e9f11ab6cd4776ad86df9544","priv":"ef857601132267606922590b016b10faf2405f7ff92e3d8dd73ee6843f0b3707","pub":"0317f408e6be956b15a8e9706287e6bed905a008e8d4705b6c9b6e7f478bbc57e6","addr":"70cbfc88189721a264c3d21bd01f2eeee8a245bc"},{"mnemonic":"toe present claw afford agent render direct december model skirt tape soon","master":"88f5eca5b3519ed612f9f7b15ced098565559b85db98cc57979dde0ed818bde9","seed":"d776e445d7541e997584381b5d2eb066b99644275814961daf748a76a84898c21942f23837851d387f0fb6557a0f693d98e4d23214ed34630bd74fa808b36668","priv":"6252b6cd338753e5cb6745e7ce92bb661fdd373e56796722d1766def72b93ab6","pub":"03dc54c14b80e91502c400d6a97562975ae0f967ee0a7cc5618ac0330855ebc688","addr":"afdf4a2de468ee8b2d47de70d52ad9a3239b3f42"},{"mnemonic":"save rich wage off orchard unfold call frown idea jungle boat oxygen","master":"dfff9a17815e298cd92e9a2c5d4ccce14d3df12af9b9c69c4aac7bd297639ec3","seed":"7d25dec6c87f92e685124b901b0ad423ca3554c31db8583d205ec4beb3f42c4a14777ff4fccf830f5641ff4c376c772a8a1273060d58c22b95a51f9daa812a2b","priv":"5e962f6e9e6e9ed76b0f3e3174ec9639996027fb2bec3fb47faed79e66c4fd6a","pub":"020b08249515846a566c2c9616894ddd6c2239c58077ccb2f2e7ec5a8a140ef293","addr":"dbcecdea56d7e5da2a6c96c1f36242e87416f221"},{"mnemonic":"claw ketchup mistake lesson drill crunch unaware captain perfect spell endless manage","master":"bc0c9ff880973352267788cd2e1882dbdf5613a8460414df0e570a099321bcbe","seed":"4b9cc68aaff0b0ad24c531e990f9a9b7d7076dda27cc2f21e8e75bce8f65f2daf1ce6228130bd2c8900fc677313ed4cce1c47eecf24fea0c119f70ad6d3d79c2","priv":"e36db8076b37e0d3785c550e47ccc91462388c8249d160df13391ff06f40a30c","pub":"03895e970001fb0b3566d8abcb1a3df46725daf591e6cf981c1f06a81eb202692d","addr":"6548f7cd7b619dcc8685e59456ecddb223c0bc45"},{"mnemonic":"canvas black between chunk wolf resist again joke club moral fix spawn","master":"374f2b9727601f87f72b70014fc10a38c5fea6cedb36246103e0fde2d356f7ab","seed":"217881b165a67d70c9ed44d81a1100cc829f00a0b3782d8ccd8429806b883eea17f900ed32927a7c8fb8df34d75d5264cb5704208d3dc336444c199b9cd4bd1d","priv":"7e6c83d4d58e7a55e9882e6160238c6fffebeaa89e86dc9785fb1626fd22e8e8","pub":"02795b9337e8dbaebd288d1f0d833d51a4969b0ff995ace05c329fb6d20ad82425","addr":"7618cb9305c15c8ba122949bc298b3d6daa43ef1"},{"mnemonic":"dismiss rubber stadium badge enable exercise collect globe another survey own hope","master":"9da347a5c2a00a44e9bd39e50d80c106a9f885f8c84f5c4d90a0a75dfa5d5508","seed":"7038ff726fcba91488e5fec28d436e7a2e7c462b06747769f79b785aac64164eba1ea5c125e6196b9430139d9e21af8cfe10b51a09577ed510453487cc1f39f2","priv":"86b04f6848114854a1b207b44d9475c216e63a9b051c44bdb7be3eea9d24988d","pub":"02c6e822bfe6255d841eae33f62987f9cbeca84eb8dd1dd02b2daf9c7de7f36371","addr":"bd2a4f087a519a3469cbdaed704221385a07acab"},{"mnemonic":"dignity tired drift feature file salmon vivid wife faith title direct toy","master":"ee03e61a429a846453557f202766fcdd8a3b408c00f3430f7b3a1c9bd88a7d11","seed":"baaeb3a06b419a7a9c49a99e712c11e87a7a4bb34b26700b1d57e95177dc9b6b2d590878111e14f6050e73e5c72239a1562062d1b8e4c8ce9ac158aac282c2ad","priv":"d1dc95b42024dd0c10e8b5f0aed5e5f7c9ad0d56636da91bfa910d6af2124969","pub":"02e1f4ba20c8383bf512073e335400e1a1b1a103cce9450864b9087203dbc0e027","addr":"3de51323a7d40ab752f2b0cda1c17d51cc477369"},{"mnemonic":"legal sword liquid clarify utility depend romance cement trash whisper endless parent","master":"7c61e0c0e85873e8bdf532ec5906b8be283a002114b8dd4b8d0154054b6f40b2","seed":"21ca62d84191c141f6b9890627d6d216d5373ee0819e7c618e4027e70395c2a4ea5702aeee5b844a99eee4fedb85bce72f7d5f57c3d670308e1fa484108c4862","priv":"d6ad713c9dc2cff3e55243729c56485fa34ce8a75c4e011f40762ee000dfdcf4","pub":"026c7a44022f2892f2b2b091bc1488e547b8b52c82c2a780dd6603f40261930d3b","addr":"c6c4dcf4763d9908a46ddde925afd86bbffd4719"},{"mnemonic":"tragic inform salon lamp tree deliver sugar robot torch collect choose nurse","master":"77e88aef57f8be3507ce43c1e47dfb86ce0fded4a6d9a5ed6a2c6711a0a83216","seed":"6670f6815a2865591e6f9ff5c1cb775eed9fd45d53d1ff3f65937425c6bb79403862a398b28c7aca032fdfb14f96b238174cf6353776321631bfe338029ac148","priv":"7907a31525b26f89eace8cc0b988a08f8feff4914dd8f692e24199c4134c6fa9","pub":"031af666b3e8d5da1684fe5d585b8638fe46cff1b1fa01bfde3b2b8e2ef2aa3a58","addr":"792001d0cd191121043ef1434855b85b583ecb09"},{"mnemonic":"common oxygen toward security mixture swim elite issue suit welcome ripple online","master":"f4b85ed364ae42dd7453f56df2731c194a203cc06a8bbf71a2498424d7687631","seed":"9f2217e5ef1a8691aa51e914411a5ef5bf70727cfc7d0a932e1a9f7d298a657db8f2f6eeabe881e8f4f4580a2bf1834ae888cf7b3bd32820448007769b310724","priv":"2f98bb40d987f7e50966b15d2bfc89befc72bb6934aaaa2293dbd9bb33923268","pub":"03b89c51d6a32cdc56e334997a0bbd57deb010778d62024f5012ced0ae184e2464","addr":"2ecc6f88d71e3b1344a7fdbbbbe4fa60b5e539eb"},{"mnemonic":"kite warrior idea engine trouble swing route main like burden divide drive","master":"8317ec95801550a748f5215d6e83cb12424635d5b2f6635a78a4a0311cacc56c","seed":"f406f2ef34c3a4bb64622dfc4496a0b6183f1b7c1f58bd28d3ad863ccaffe171bd6164044ff5e940650a6bef6c290279b41d80336cbd9592c1a3b326314d44f9","priv":"4fc699bea9f32362f74b1cb6e445b1a8f3df81dbf1d5333dabcc84305c355e9a","pub":"02f8b8958f9edce19f8c5a50371dd079ec4b4ad6f6a9746451d4024ccd266829c2","addr":"8fc3062dd0b1063eecaccb75e14faf5b06cbb939"},{"mnemonic":"toast health connect merry resemble slim address pioneer crop orphan keep dune","master":"07ff519f3c3fd08b9de16a26197914867d4e42b67222c9ce863ac6b176bb5909","seed":"5e80b7b4b8b388522fc283784f368f5f7875ff7b412af6c3e526ec1426ad4164e0f4921494ff28a258787beade935d472cbad9f48386c2d7e16f8ca76fce769a","priv":"1158d4b986fa7e05db824694ce3379b63f473161c965a39ca997a8e3f3e7664e","pub":"02a3a7b75a8532df2afd58bd2dd434603c21a0aa4081f8ef9798db401372e32bbd","addr":"24c8f405da27ad41be7b0c62db63f80226fd6b58"},{"mnemonic":"cube shaft clever hard fuel review enhance short nose again banana profit","master":"30f3fe7efee7a0c7eea064bdb63f509182f0dba45780dcbebc5f3374d1b878f1","seed":"93774eb188a6ef898e534aaf67c2ec9aba02f716eabc0d193bd5a035ed8e0eebb2e82fd3ae30bcc24c44d697d46296e5d307060420350a4294f3cb639a48e6e3","priv":"857bcd096012a98022b2d30cb926757f0afa9465058c103b70fc931554f2d360","pub":"02ccbe787178d64d347a4f146facea95d2d01cf353ba04cf7cf7325fba6fad3824","addr":"d3dc220b0c09ac018fffe2cfffb220410af0c0b1"},{"mnemonic":"weather surface use rotate entire foil female carbon once kit seek athlete","master":"68614cd2df975347ef5c7efce4e416821a1f1916ac54ba994f6d69f8f2860576","seed":"478f798d52999208d3c8c0feffa78c6d56406d59800a701c996b8c8a9b193494aef1881e2d1f8aee0a5973b722fc3bb3ba007187a377a64b13a81d0560cb090c","priv":"6671cf2b354b61f6410500e26cbc94afe78392d3feecefbecd6e60c6b1a33a31","pub":"032afb4d170e89e8c89c544ebb404678bc6470d26bf68dfaa1c034edaa85a9bb5d","addr":"2bf3d54106897da2427b1c4c85de761e4546564e"},{"mnemonic":"car rubber ocean miracle vault brave goat eager dolphin various infant receive","master":"fa86484b17c9b62008647587f406f4daa107c6b95e708487778865c76a31e330","seed":"ff824d733d160e50a156021b256e4663f9ebebea1530358d70c205da5e7703d64ca4f3d941043787a68fdade5143d993376b1ddc9fd6037a13520e8cfedb790f","priv":"bad6b6ee0a3aa898466552b2e5df05bccd7d0861e3368f206d8cbfd2ec8e47ad","pub":"02ae2706cc6a96f31da9cd46d544ab142a26ebc6259e5e807065fccc251b26c603","addr":"a6ddada02b29a165a86a8bf4a01e783640e5638f"},{"mnemonic":"segment nothing burden boss obvious find kiss join science dinner clip wrong","master":"64a4c932d3cf62f9bcfe86c3b709ead38ada505e944322b93e14022c3ed26e18","seed":"71966dfe2434f4dcb2cd1bec9090ebd20bbeac2ed54dd8d081a548ce7eb57f1b17cee242581cc893292ff9ae4e5526bf9e11034688299753a6d0bab024ae9126","priv":"44ddd6571ce3582ae17ca9c74ed41811a760c7059dfa7ee88f9ef76960e30328","pub":"0288341429320e6e7cc35df266cf3929e93e843e911f8ae0ebb2634132de1a6f49","addr":"4f32c063b84f9895d4411ece5ba0c9d2e94631ed"},{"mnemonic":"cake volume physical initial phrase false pear poverty bonus steak rebel ketchup","master":"8200ce644e28680726ffeaa7d949e23feef136634b58d504e34cf8b2bbaea439","seed":"8f6577cfd936a64de1c2be553f66b8e5991e2cf30c42181c0f7eb527105c19221268e3268aa243f740733748dea3c0ac16320305c401aef7cd0986f3dc6ed978","priv":"ae37947a92ab4e9ab9b4e24b5f26f710725ecf848f0412b583b8701dce4b7730","pub":"02ba33804bdd8733fb5ae1ce1758ca55c8bd9e15245d646e8f737da2d61758ebf2","addr":"1f3833c0eefb3d46c693b0476470d97abf02a792"},{"mnemonic":"outer meadow velvet fetch claim forget token fee gadget alert post pelican","master":"a991420cafd20f348c847780fcc2128888771f4771cc6ea137099616c617b1e7","seed":"35cd93e8d9c4651799471d4cdf2a2fbf083088b93f1c1bce6ba4394ab6239d05c0bfbf8d7f8976be68042e1c8267dfa7ab3008c967f8d1ae0793190c77077fa0","priv":"e4fb166f29590fb4a5c5484729f038a62aba50fef189f1e3769fa290e1800790","pub":"02e123798016f5eb1f79f633588051def8b59c27fd3973a7f2012db1a8fa508ad1","addr":"45a62d3353b39439d002c307c8789ac70adb69ba"},{"mnemonic":"flip expect rule wrap cousin fade example ugly crowd monitor cattle cable","master":"d5c2521cf1c8f3dbbf955477a993552cf7f6f0ce452bb789806daa0a4dcf192a","seed":"b08a8f7dde39c9680c07ec31ffe80377ea915de328fd38b81525e4f694ef0ceb630e7ccc19bd28a03b7061b194f2b7758f802945a7e556d526f25bf2b4d5fb90","priv":"21a41b9d3ca21569e3b8638188633a7be8e9b54397964a65bfeabc3425d5e4bb","pub":"03cacee92e7116a6012c2b3002fe67d00dd062e937393895703e010d5ada98b4cd","addr":"b3a4f2cdb4cdcb64f1434f3f1e4c20bb8b952116"},{"mnemonic":"alone absent senior wisdom sheriff siege harvest coast van skull want quick","master":"74109213fcb3350c4ebe081116bc7570d00bf49cc7cbb90cf3b09f5d532a3d97","seed":"7d111179778ce63cc39879ae303814acc25afca8eff930d3ddfdc640d7b3d8c1140429a75c6db52db96564aba0bf0c8d9e4cf78fcfd703c1d219c09a70d8569b","priv":"96c3356e90906a9b3c523a3ba61d66ec63d9bad5ccd23edf923e3c5e4c995774","pub":"021736b932aa1eb90716bd8bcf3cffaae73392a37ec114b34628367f8ecf1b35b7","addr":"e8951735e9840492e0589a885968f12b044067de"},{"mnemonic":"fall any bamboo energy ripple flag rich arctic argue decline auto pottery","master":"4b3fa55e01aec7bc6d41a890d8aae2d69e617bab49f8529beb38aaf835d15a69","seed":"aea9842051221d0eced61932fd18a5f3e1ed8479159c40d16e886699a0f923807b91df0763a5d9869fe9a159ae0453f8d651302a186bea4ffc120c2902012d3e","priv":"ca5c56fd1b4e7e7bf623032e17a186e20b22fdd49a4d29d9bed5edff206bf26f","pub":"03a8fd0ffee636d66a64cdcc6032214efd1c069491085053808d03c1ae83b9fe96","addr":"ced9d32eea230544e0f8095b39b0a4e18b59937f"},{"mnemonic":"over exhibit wait carpet pencil record post duck gossip vocal all wife","master":"6fbad4c20428a892a365ee50dc66f8eca7773dbc5ecc34f85619fbf1a73822b7","seed":"f998a57ed4eab4d8425d4a71bb1fa451d7a79c8dcad33e18f90d6a4506f93b2adffbf3eb887adda7669c0108e444548a3566306264a76dd4b0955af27cb9dfda","priv":"bdfaebff9d0fc673539d8be5e59907647d0050a9241dcc4aa24c5c26eda0fa60","pub":"02d32a22a05926844a6b0f2f677520bda4c54e6b2c9269ac91127b626159a9dde9","addr":"60862548d4e283f62ff8aeabec3f25891808a439"},{"mnemonic":"spell illness engine habit bike able chimney spoil know nice tunnel weekend","master":"ac366b29da09441f12742ab837cab2a4636e1b50f11680c4e1adb7d337dfd8da","seed":"59e69d46ac9f2a9edd9b1909b1e5ad4b9fd2b401d48e95eb43a0f4f2bec0e6c60bd2a263e58bec77dac6f6ba45db1345cf9d74593c6e0b5ebe3fdc7c5f24014b","priv":"05d077b8eb1163a1fe2e5eb3729ffc7c1327435d4ac48db86c6db5238d5af45a","pub":"0364973fff68be7b91de7858fb35869decac0273ccadd7ccb45b057ad7d7e9baa7","addr":"c59c857a493b2b81957f4769dd9b7b5a934acfc4"},{"mnemonic":"thunder fabric basket stumble file sudden effort congress process essay excuse practice","master":"6c3de9715b71139a3a352a1d0b272aa0152402db4b8429c32654755430623ec5","seed":"eb8d579bad88d416336e6ed882bd8a57474cc5ba30d4a20e426c29a78bcb27255f6a6cb50b6430fcf905111f9b9fe4e3d4920d563887dbba69ec74856ce4b79e","priv":"a38f2fdb79ac8a1d64f613dfb3cad99c9f06554cb05f39b6f60c52eb7ad888d4","pub":"03d548248b4afab15ed5e91699d6e13fc4401d6779ce97b3bbc833c690302d0049","addr":"fd60d97532ce397f4ae9accfa99ac5ed9f5a77d7"},{"mnemonic":"twenty fan benefit benefit life orange crystal matter index drip mobile matter","master":"08a9533038e137f0149a965d12c1c90d7f6846107dd626027164146406c86fc1","seed":"d3f6d9f156c814e0f77fb7e9aba06ac2c9c3ee171b2d0759e2d91fb5d383f862aee8b7dff8c62cf61286d8f7f374ce3aeae7ab36d551f802523dbeb80673e0f3","priv":"08bd659717169756d9d66e79aa2b51d2b539f5c453ac8abadcfce3d29df49e7a","pub":"02e3885a7d4343012cc76314876fe67729e434202a28525bf69ce27a9167a46385","addr":"f3181c18f63bd74c4ddb37a4c32d0985e0af010f"},{"mnemonic":"scissors foil oven finish exercise typical catch dentist lens dolphin flag warfare","master":"9db47325d363c87e509aa52701131ad236317ea04515f4e24ba4f87cf99b2327","seed":"0246e8cd14b6b8f01d35aac9c09f8e526bf9f05393c5e1e77ddec53477275dbef74add79ebeeca7d081e58272f4a1bf1fa8621894a8165dbc681b967d84ed10a","priv":"6e7e4b51346f8d1997d81f63dbb7fc7bf4f022bb3b5ccda9e5d84c4a5c97849c","pub":"029edfb5c989c002795c283f445ef4d673101736c25617500f8fe655e043ab55ed","addr":"2a98038648503b442fd77f4d05a338da2062cc69"},{"mnemonic":"monitor vehicle bench chaos stable cereal acquire shock image purchase sadness hundred","master":"bf5d7680d5ad260a89229451e6ebcf3c45988a83df6740fff9b557eb3afec827","seed":"5bec19e9219360a8de56fa85d593f5330ddb158205d25d00d8a2307f6b420f11916311e104dcadacb638f5434e98efc8551d768586a0d8d9baf2ceb0759ffa98","priv":"470256a6d68f8de27b0aba26684cea4c075f384bcdf8a68bf5418dee37f08dbf","pub":"03a1193ff722ba3b4bf7d257aca5c184fc776540fb139dd9a404d3743d31030971","addr":"5b4796167bc701e5182f16e2119d7e9d491881fb"},{"mnemonic":"never dentist blame parent sell income parent bullet eternal random seven urge","master":"665961de4995f001085ce2f1058422bfe5ba0132cfc5725f0c5c9d827020663c","seed":"79a95f522e50cb551c7fa70d54c0aafa5b794512be4e91de2fe1a2e9c17d76bd55087621a606107f3fb19486f15dffec5947cf16405304914f511dc43844ce97","priv":"88db51c0d63c95730fad0c616e4d4fde5a631dc3d315fc74db688ea1a7ceb5b5","pub":"020ebca88011035818999fdf69ccbf691d93cbb6bda65c68c0f2ca10757d23abe2","addr":"9de8cf2beaffcd389ea6dcd264444aae075a1590"},{"mnemonic":"noble shallow label region age proof remind panda useful session poverty open","master":"a8e1a3cf36520d5ae852890951b03cef3902f9ca168cf53c4ec6289dd802ad8f","seed":"0cf4cdddf1605c5c895c1b81fd523ee83239d42feb4a130105c2d5d1fa371e00cf34030cf02ef51aa53596253e264da2c3ec220b97885202dd73a7b5824b982d","priv":"5ddfdf68cf41919145c9c8624c245ce9d6d8c380f5d0153b6155e92b253b1b22","pub":"037943f2b34206bfcfc1fe236c77f6e844d98cef04671431a12883e23846e8e7a7","addr":"559839fa53e5b7408f3dbd00ae5e80f56042417e"},{"mnemonic":"hope inherit quantum text sadness dizzy glimpse cheap expand legend kiss once","master":"c307e0a3f22db3e59a7b28b931c1ba0daa0a8e8d743660c48b6babb4b9563702","seed":"6fa72118b297f2bcefab29f19ad4b701791f72cad287681e2af3e92595ec6b9f7a15b08771917d1754053d1e74aa1a254fc3d6e27deb78b7b55f35a5154964e0","priv":"1a6b9948bf4b717756a11e43847be38ae87b0f42d718dcfbded3bca1a8852aa8","pub":"02dc42929ebaebe447d089cc702bd8e29457a2619047a1d7c80cc4f2639a82246d","addr":"bdd0fc12beaef99e0ee5b07c9c2f41bd90163bf6"},{"mnemonic":"smile odor upgrade deer mistake bomb friend ability then segment orient nerve","master":"17c87ed5d20e5089408ac7fa68024124053d46bfe32fc04ba06801c45b6e19e7","seed":"f620d852a56ab0eb7d55d271034b767eded17ed740ada7f8330fe6d9affd8d88e44489de06d6d3f7b84b294a847334ea051ca63f0be4742667ccffc79031b5d7","priv":"8f90608bd45f866e9fad2a6a316fed6bb6342815ed79c967c3eb1be727ab8aee","pub":"03b6b746569b0ce4b97c3035634a2b30a519ec23a78402137c7ec395d35c4eedeb","addr":"179277829c7cabca8286a52ce3cb591ab22fc72d"},{"mnemonic":"black ghost quit birth dial helmet custom brain diamond rally local season","master":"8a6f8c836e3e83ba6ed94a9de24e1133d4b04fc436827d810c23ee1ec9406fa4","seed":"6488a47cd4123eddce9bd31a0982eb0ce8208ed5e331081dc18256dc8b5a1718807bfa267ae6a75bf1601b99c784542c3d981a9350f7f268d2666af3bb8e3680","priv":"1e7100a43845319b4bad268d111eb3746f09c9a005e023c45fd1da984d37e49f","pub":"035ed8c0324e4956ef2fd2b5141260cb6dbc0943708b0f83c0bca0e7297a27daa4","addr":"d2d9e9b6ea946a61082fe98d9b0a808f5f0879e7"},{"mnemonic":"advice uncle change position drastic leopard praise rack match toward paper faculty","master":"9583a2771931234cdf27fc8a96d06766d4c76b4bd25247596639dd21da4c95f2","seed":"8fa5760f5f2d36734e05cedd78260e9ec7f837bf5b7edeeaa8f4f3ecab9a1f82ca557fa8d75d7d779e47a29eb7c974264d98d12fe7af6a22a87a70bf0031cecf","priv":"3a69d90701054f78204f1d3c959f128dbfaecf4ff0cf6c29fe5ada13c96186eb","pub":"038241211180ec347d2459f2c22c01b50fcafd8ec1ebb51b4ba648fe291bd71104","addr":"f9c6730eeb7a00027b3deb720d6c541bc6b0aa89"},{"mnemonic":"carry enter invite orphan file grain pact glance ahead grace action mistake","master":"b6b8bb3d43f27c84f1e3fb1ea462c9a88d8544b18b22d9b9c13379954a3546bb","seed":"a0078d2fa9187ecae2b8ad3d4121e2567695c1cf26bcd441536ce95a6e04b4feb41c1ceacb86ae9ea26db4eeeefaac7e0477eab711b22d5390be3eb3daaf97b3","priv":"7503c22dd0cad622d4337dbd7e0bea66a2e8b52427cbda01a9c5959ebe8b5a73","pub":"032c577bb5610ae703c54444865932db4edef3b8c84f88c71a25731a85515eb69b","addr":"f268151a05642b378f8738ec00b2e486e60e284e"},{"mnemonic":"taxi betray parent equip margin snack spell agent talk possible portion lunar","master":"5bba6a340254e9803fb2889beb84d4bc63454beaba3f10cb992d5823b23472cd","seed":"a8b970f56b960fb9888f03013542e90b547f7d60aa91218105808e595a561ed37c13f259692d3dea5d324d0c4142b30e39cdc2e60697ddb5f52a5459469fb31d","priv":"ae63f7802417743bb8bca410ec64380a407373900e0af51e6acd65ec9d54a0f4","pub":"027410f719925fc55875ccb78092d5e6c31b8543e115ff39bdfb9a26f97fca8859","addr":"da9b8ea1879e13bb49e7b9d179e5b56cf6de6537"},{"mnemonic":"sheriff marble regret shrug shoot tornado coast that obscure sense duck noise","master":"e97a0a9cfbcc28b909f7a5a617af10289965c6901fb06af76a6fada22e41876c","seed":"7a231c5b3b91408a19de9571bcd820f17451562063ad2daa85a1164a952aeaf8d6a3c703909a76aa1e1a2818f8fc36d8dee3c87184b59d6340201402384fb7d3","priv":"d3fbcac181c592367cc527d8f09b54e1a31d3b2c3fb5c1cf4f2ea897c483f5a7","pub":"02b3e3765ad7c3c020c6a166b0113b87279e66a53859ccbb9d214181e2be30ca01","addr":"517212232acd318b376fcface8544506a8f76c18"},{"mnemonic":"connect drum once first trip dust trigger duty situate burger toilet half","master":"073fb31a2785afedaea95e67ac7a3219f2f910aa01b054022a5185c5ac99c35d","seed":"fe3d1ba2740ca127dfc5bc22ab6fdbcc930b957185a0b16b1919e4fd5e637c053b3d356b12152cda6d07f7088f16195319075d0b3533e5082b45f1bbdb7d5a74","priv":"6a19f33e357328d40aeaab5323764bf9e12df8b476100baee9d398a0fdbe3970","pub":"03f639ddf3dae4ec84bdbda49149d2fc3461b2309d043c89eea46bd99762b54a0d","addr":"c8a8a454a5aad713670e936b63c519c843874b3f"},{"mnemonic":"shock later journey twelve any ripple soup climb globe impact poem auto","master":"382a7bfaaf7df9ac5cf8ad1f574bcfdb98e185847a92cc36d272b74a14228259","seed":"aa3bbf8521133a0fba514e54147daa2c6fe294b1af8bee3a6fe46ecd480983d72635520db97bc3631d9bdd55787b07e45d81b052522fb7330f4e0f6aa24fca8c","priv":"dabfdd6c702d104e4c17dfea719ed870c783dfbcc66e8987bccee888658bff76","pub":"02efcce52d6c7ae70900d892e1b24ed5af6012c38c15864f2d15067457a2c55f2d","addr":"fb3af390f0137dcf7d98e2630021061d0fbc9e06"},{"mnemonic":"news book language depend fine fabric idle curious depend plate fold sustain","master":"4c28a9f9d854290f4655fde6a91500dacb684417d8dcaa69435e1ab4b8f04e3c","seed":"2839463d79148eacc1d367f9ed0a2ab8ad939643f439c276bb47f0fbdb4cfe8795001ea2e5c24a6a6da9d35cc6759c37e50ce49f34498ea61a74dd11b14a6421","priv":"d6ec69e742e0146c9811de01cfcb07b3de56594fd4e374722ccf81dd143e9a12","pub":"020f5c54b785c5ca65547b2ba32138a47b8b5b462b304e0cd3ef981b407ee73b87","addr":"2850f467f64992626b23e03d366f34f4e6760807"},{"mnemonic":"equal energy inquiry frequent aim wide exotic tail vintage toy marble spice","master":"0fae6d570fbeb343eaad5bcd272d8554fe27a9cde389685263ca1020e65d4a1c","seed":"62aa467a7dd73249fcad8432d188e64a1d0f1591c66912f906eed133ca0c1e99eb068dbcc8ec37947cc846b1ad4f439821324c61bbb385fd91b2e440d6a86e41","priv":"1a254e7f18b0836975e187a72d0cf1d1b69225067167f44dad6aaed6065e60ea","pub":"02ce2f7840d3f69580538517852dfcb2932285caaab4d11adb98ccf335430fa50c","addr":"53117f2a5d41d6e727db2975c9dabb1d28708d19"},{"mnemonic":"recipe prize resource define enact basic glimpse chalk box prison approve fossil","master":"ee8a619974bcf5cfa95af842bb003ce4f5236abac96eacd51ec8f797bed520a8","seed":"b2d4c119da7c72c24542998b036b2e1c501b0fb990154d1b5abba4c5ab9233c4caf1a427948faa62aa862002f43d115f2e4d715b8c9ea3eb662b2514ea06390e","priv":"0d896d53aeb3f2fbe634ed280225bdbfd10116e18651b3c801a3cbebb0f75ed8","pub":"0320fd7d4c9c548c28d08e587f45836902f14c7380cfc9f8119e0b6e36089369d6","addr":"949025151f752888794030aa47854ff5ee407e09"},{"mnemonic":"hunt carbon lady smile luxury old empower pretty memory gentle public pottery","master":"e9542dc3733e40f7c1e5f540e241dde57a09f76d28e889f69dba48d63f0b6445","seed":"ee0dcfd61e0fe95e1e0428f29c78076f302cc5a55db61c13f9e6d84811bb0d57ba3dc109d4d98fc30c1d2e5b25bab1c646623fbb00e5b3719e4bfc58005fcea9","priv":"e846c35be2eb1e3781e8078d8433a9816d8f8701131e69873290689940617de9","pub":"03aed0cae3a1054d184c820d9dcd5118630febfc450d4972b4963e56de2b1005ca","addr":"af41527e26ef2ed7e1692f2897fc48bbf792c8e6"},{"mnemonic":"captain disagree filter crane chef cousin one cherry able scout tip magic","master":"bf4a3d4839793f7d502e24a4ef4e99bb39fb2aaebc53149eb004ffaca0256af7","seed":"3fbd2b4e5c614a29cfe82ff906e94be976849cfffc6831d67fc47bf2dea68ca06fea30de70ea546adebad96d3fa106950545350e0925425f22a0ba1a0abfa3d7","priv":"046eff73ff79adecbc208e64dd7bfdca16c4593a1780261377ee48e44ea06d57","pub":"0374f44a020872df00e0101f61fb61e76038f6fa8a7d50a89757db14b2c95b76f1","addr":"130a585f378df286341fa93925eb5d630adda295"},{"mnemonic":"inform measure recipe evoke make recall volcano girl obtain flash soap prison","master":"3bcbc5c191d8f5fd59988894fb749ec333a07e909b3c22108500fe08cf7edb53","seed":"cedd1141a4c9a1e349435e247349994c909811dee5770ba3642768518cdebbe7d7e5ab0776af835bcee0913fa1bb4b72205e9d38b637196a624f031238bd2bf6","priv":"9c070a30d5ad6301d73ece1e6e71790327c6922521f93d6e8b7c69391a12d37c","pub":"036ebf4e4d5349613dd3c7375703a1025896ec4377a9635e9ddea2a2dc1e7bef22","addr":"74a413291b799fc2866fc7c7ed2d2becee9f15be"},{"mnemonic":"carpet poem piece silent boost trim famous coin fish rule leader oil","master":"ab7b210126590431eb1c09ead09bef99b689a429cd86be65681debd5deeb1ef4","seed":"133b317aa1f56e79697425b4c94aa52d5b7a95c3fa5ab6d437bb1b310bae6baafdc91ebd812c76b721fb45835f3ef0f61730320a8ca6e7cc18291b84af5ed68c","priv":"1863a0b1d15847102e3198549a59921488a67002e38af1cce7ab2c0e055eb498","pub":"03dfd8ee8642ccb78642d0e373ce9b15ce9384d12b36086d2c64601ae8d18325d5","addr":"b70aef2f2dd6e42f909a5840a6cc7e08fffff26a"},{"mnemonic":"liar hold orphan include gravity nothing erosion offer average now ability purity","master":"71b19fdcc0d8e082eea3d92a5e6d36512273095251da9177b7cbd0bc2e5843d9","seed":"e58ade95c1063890ab5296e5d8a07e20ac80a159afeb88c74dcc6bfa8fcff6c85a2f19af8404d3e457492c560e4f04becb6446e290ec1a376564be64ce1c7ec4","priv":"2bdde1157be2917e98549af370aedc6a98fc5d6df99db71650ef29407512912f","pub":"020a188651de8022da9d74bd298d324c54c88e35268eca0907a5f6366f9ceb3287","addr":"9c4c3c19a81d424ffd69bd29802ff155f52e219a"},{"mnemonic":"crisp security increase topple put engine wheel apology north tent front minimum","master":"77288b3b65bd31029a3791d357521172af895f1085c1996668a1a689f4473a93","seed":"af74884da27f1ce251a75244ac945052c4df5fbb59e3b12eaea5e28c8fc26b8ec95bdccbdf5481eba4b74680e642e5e7ab2b0f6a4d0eb41db3c08c01162520ca","priv":"4d1df90eb70f94aede9dc63cd951a30730364379bf9a0f339d59d5970095eb65","pub":"023f2f6cf79a65e2d25fbff8c422ff519603c2e400c5d3b6523a6db06b969c071e","addr":"42bc49b12a7eaf46695fb1ed32db0d32e8e661b9"},{"mnemonic":"switch shy kid child occur salt cage local top blade cheese width","master":"5755daab29ff030dc4b255171f77523530a659fdd70bb5fddb69d150d4e7e1f3","seed":"dab58ef3ea14f3041588a5411f22cd426ead10057616df799cf53907536819056a21faced7e56aacd120181734640821c082daa86fa8ea4b39ddc40b5bc02aad","priv":"e5c1ebc0e3428c9ca42dfe809495dcf7ce2abf2b149664031b5f5a9d5d2f8b4c","pub":"03fc64577364f254c6f919e1a9262740987217fa2c66ea490ee6d3dca1787114f5","addr":"1b83078e35972e3ce3b1bb062b3924665a6e5c5d"},{"mnemonic":"rival notable exhaust climb disorder enemy desk owner again twelve mystery devote","master":"5daf52423aaf7b7ac4d8ff4bad65d81f7e3b15021222d3d9ec788a2d35d074aa","seed":"709afe1cde759d12cdf4fc3a68793b35490cf0261397754a62dd5d40fe528424e8deef620f89337dbab01bf57cf241a6664bb3696bd76c886c57a405170a7b74","priv":"b171a7ea63d6e1b8aeffbb63ce5179edc656e17eaf85609ab0d2680e35a3d64b","pub":"02cffc5dff2f9896d995c9f9b6d11c1cab3e0d40a25dfb9da5aa010b703c80be0d","addr":"cc673e6d2578bda7d7054d2d53b841c26e15dec4"},{"mnemonic":"defy prison walk thrive enforce match turtle venture margin quote must cute","master":"9d747975a6889f2bab69fecae39db2a10df1545ae418596d97152de0e7cce077","seed":"183b5f3d963036b0e22eca9717769422bc515977c0a7fab66d716318de12d41a01f5ce101bb47ce44501f0908f8c1255f8c3078fd2c2084aac32624c20647806","priv":"a871082f9cd061e39987c76e516a7086f82eedc06a20081161e13dcada541f61","pub":"02726bfeeefc2d5ef521c3800ebc7700e11aba3cec88f8be5c9cf3275f4ab254ab","addr":"6d793f4478d2e4980a140b724cc8012b44097446"},{"mnemonic":"lyrics goose draw review manage recall snow lucky mirror swear fish rhythm","master":"b69e9a8960d5cf2b8f457e6a037654bcc72ec1d26e4753ad34512a877cc329af","seed":"47054d3ca54f6038ae500dd6a7f8b5fa3265e87b384c16a4e68560f56dc9f7945d599d6a71b2020618c9f7bab46bc6817062751f19b46c2e05eab7831fda1cb8","priv":"5c0efe877506f2f46bf345c71c8dc31239f9e23a543193d5ac894faf0aea9ddb","pub":"022b90c4806ce022e81c706555ba3cf9597861a3d7e28b9aef6bdf36211eba916f","addr":"a3ebb9a579f22150fd9b7cf62a5f52369fce28f1"},{"mnemonic":"coach silk assist potato gesture hire bullet piece document outdoor husband panda","master":"541e3f3e4e4e9803da42f2264efdce5b9338d410b16b0e7b66611e5dec5bf896","seed":"4368a638c45b7ba4d2f1953eac650631f1a1e3c23c72b545dafe466fcda597a0b97dc3302072e50a298989f94ea255290d3963ada9e42e064ae300173531f273","priv":"b9462f51e3c20b3d805b54adc6b27c669ba07d5a5c97edb0a8cc6199d772097f","pub":"02dc7826d790d013e76765b03447d2dc92a945268e42611c51080b7ae207906bcd","addr":"e59101c16f77cdba771eaac3ecf416232f6dd1cd"},{"mnemonic":"wet problem raven barrel explain long call civil pact swear luxury expire","master":"1e2704a2365215a83dda9aefb1093e095c0c06623f060c2741e8bcb8480c4165","seed":"fd81b59cb8a4abf789c2fd55e2d9366051f8f64f54a3e08d117d344b633f2ffd63c1a6bce7a4bcbb01261b6e41f6330ac5d0b151c36649ad59c0cca7e4b3a820","priv":"bf9f0440cd63148ea0771d0f892efd3cc0638330c428a50436fd048aeedd7ee3","pub":"03d807ec07f4f6eb00f9e3ec69dadafb3201cdca3e878367ce4245af65b8e4490a","addr":"e43aabb2d20275ff60d591dae8d83b938a0138c7"},{"mnemonic":"unique mobile gospel cost genius symbol online rack round canyon front again","master":"ff441298736365bf07856cae35a9dfc2612a9d015592d5326980225c4d4e4e21","seed":"e05899b5a5a94c860cca584b6065284e961625486b1ce1c80340721fe4f89fac7afb7b665464de68b5c5813e48896210a90d44eaf013ec4fa90ace43e3153d3e","priv":"45513cc23e8ad7f7b5932988637cd6aa85f19a481cdd20c220f0407afc69c688","pub":"036080589a5268143509bba4c4f26ae8a3ad7b12ef7295c9ef416cff53b66b39a7","addr":"68456ee838f224959e5c29649f41eed02b56c3a3"},{"mnemonic":"street bleak prison artefact pupil armed blouse pipe eye brother man aim","master":"6e0891146585834da8d83d0e221c88d53770f3c56f68429565d011ac697238aa","seed":"8af79a74ad501d308be613981a1a25342791288f14f05b7cf3b81131ba2146ea63e01ccfd150c4d2bfc42d64adc899158383d82f3bed0eb772851fdab4f901c7","priv":"86c09e2544c86b883e8de233ee297fa704c39bba463e5a8742c4fefc9ebc109c","pub":"03d8cd38cd2311a0360c84cecc58787fa3916c6c2adb5b2baf12a880549a572f7a","addr":"68c1770edaf76cabcf93c6d94f4510162fd36924"},{"mnemonic":"love wear kidney math magnet share mango also dial fabric near destroy","master":"e500e6d3a103eb11414e5ebc9e27eb96456c48e9567155c6250aeb83920df860","seed":"704e740c6f437a9e8b7e8d7e2321ce81d9132c9a6df7a45fdda99cc66a09c9d73350305aceabc0fcbb589bf44911917be71e9f399c401f86246d0aebd4b467f3","priv":"07c906988f937dc841918a7c3092f5df0cc0a6ce4487631774cf3f703e9c2713","pub":"02d131982bdf285fe3a37370ae0175438094cfe3d6fda273bf24320008fbdeb5cd","addr":"df31d832d2d23502cd0198c5947e8297e6f73eff"},{"mnemonic":"nominee connect useless dutch double vehicle rocket exist mango floor educate absurd","master":"0a66acfa6ac891a588255bef6fb1a845f2756ff0a7dee8d2ff33a19a7b22f18f","seed":"4c4e362fbf0dee5928b21652552815edf27a4ba5a11a09dfd83f3b53a44cdcb6acff4c39b16d2cb9ea3250af3743baddbf6ce90ce1e492fc6bb53f79d646a4ea","priv":"e81668360f562929af7e40c5970bc685ef5aaa7d390c1bbe005b028460c79ff9","pub":"020a16c5a1098493bf1f7242de71bed03779c4eab8f35a1c6859035111514284d7","addr":"c8a841f39aced64bbdebf2f8f834ee02ca33a8a1"},{"mnemonic":"sweet leopard loyal problem pupil perfect power flock thumb wage person secret","master":"6fb555e482a1987c2c0f129ffc040fab9c4eeaa3c23a3370db7b4a5ebfd2cdd1","seed":"b20837f8580aebb15a7a4b035639bc04e27bf33e1f600069ba1378a5dfdeff3e95545e6828f339dfc30eaa449c78b046c21fd99b24d819c735e050f6992f898e","priv":"9d06cb0fa2cd01675e038897a14e6b7719ae84ca6002ed6620a557877ec373ee","pub":"03f49f50e673851d74da1fe24aaaeb75c2cedd25116c003fb4a810025192c3186c","addr":"7f61e30a20df85042cfcda5163867bea46b98702"},{"mnemonic":"slender tilt tell faint dumb seminar cycle tongue bundle case swamp author","master":"444b57e7f846dadf73a0bf77fe918e8716454416501c84a48bd78dacfa87b885","seed":"4345b114d1f3a78c5582ec5469430a8cde3a1a866e64b3c3606b84f62ff48bbff48826cee928374a01aac080b79269f89751a11aa086fc1fcfe74cf05674ee04","priv":"7c6d51ee5199457807c66f22e8d9b7a703a4e61a84cc791efc3cdf5f7038d28a","pub":"028b09c16923224208b6ea2ed777a2e4aa2bca5674eb8f0021e9bfc4e9d3fa0a69","addr":"2486b9fb36e8849e09b147749e445fb515591197"},{"mnemonic":"alone oak satoshi diary artefact virus muffin quarter identify car whisper topic","master":"b4c37472c7fec6dc3f3452d4aaa7c306cb65ff940052b0aed224c12104296648","seed":"4dca6ca310d5028c4cff946edda516d55c7de81309109e6e3586927a8fc2c4095215ce471774df2795f04fa01cc2cc528c44a5968320ede4016d68c24e4bbacd","priv":"fde405fb9a8f0e98feed3f4e996b3fb90193800c571a89a2c681c0fb4917afc0","pub":"034bbb553a8525daf361bf0c831c756a56ec476c0c9a52b0c74dd9eef35177a776","addr":"c890e1e83daa169e0295ff2844edc5ca2a7ff346"},{"mnemonic":"noodle hour inquiry know sort review oxygen observe exotic script mimic march","master":"3be52af1a8430ba7c4610dd4bb1ad67d978d40ff7530d712cb5728bc961edd3c","seed":"e803e685c85a1a0a4a382d1358f5fecdb8821564545d8573f3708a01f0f0bc1da54544ca2338cb988e7d6239d18c32bedd38e1cb9bdf1bbb6dc8cbb323055510","priv":"defabd99910867339a4af3a425caaffad76ccd47a43b05a13ad86f3d23887b03","pub":"02609b9ff36c966dff6399576fec7efa83cf0fdb66bade72334ce2ba69161768b7","addr":"abef04d453ccc6d443cd60d6f964d008eb5f08e4"},{"mnemonic":"crumble loan economy dragon vague library slush ticket flip mixed accuse switch","master":"f085484747a4bf869f38da6e6f56b9ed2be60b137536a2333f2c6818d76fcc68","seed":"b0ed9b4d75a17a53b33a73223fb158d79999548d13919599ae7eb71cfe2fcfc06cedc8f94cdcdfb05ea043069c426d2b78e2f67260644109c42a6d9b3f7d5134","priv":"a64c537c561fc49e504e870eb3608e69afdb2cee4c6f177c63fd1ce170a67192","pub":"034f7f99f2699604d3f9869dacf1e6ddb387bce4e0d13fce3f4aa85713c7976048","addr":"4b51a7f925c0e1b428b1ec75dafcb0e4d9852620"},{"mnemonic":"rifle swing confirm response name void scan shoulder lake oxygen uncle south","master":"3560f172b5c2ad665da648cf0809604522eeff5fbb2b1e0ec50e7197bf711dac","seed":"85386f49048f371171ac3ed2bb4f358b7e22ae406a7f2c9b3f494f2f7eade96f2b6f96fe48bff1754894b8b2586013393f0b9f0365c6996cd251e668ee90071b","priv":"c79860b8ae43fa4dfc570b2d20549b457ebc6e18d38865ec9cb1ba571409bf12","pub":"0253d92439ae131722981eb16924b26fe01421e4222152cfd0505744278f963dec","addr":"ace153f8d13e8cee51a2c08fba9f445f50e818c2"},{"mnemonic":"air floor random smile mechanic exile run auction avoid truck expect old","master":"a7cf0d4043dccdf5cdc835419f5bf2665c8c10e7affeaa2d8cb42e3787bcec00","seed":"e4d315f6daa11eeaea17d513f8d6df6ff2dc13dbd2e09158af03f847ee6cdcac2d6b88906ae62e84355d9a0c0956ed465b3f8081837d45eb01cf785223404306","priv":"5f1f737625360db311accae8b8ceca9d4f2aecf15d6e4b91db4c407bf75345bd","pub":"02f467c86cba2d8d6fa258d16e9b4543006b81c61bd357a2b1d79b9010fa30437a","addr":"458f265c275940d726dd9714413a56efb7f8c4e9"},{"mnemonic":"assist right street define hurry section tide museum faculty glance power aunt","master":"49eeea0b9687360576a3f93cb31eaf0edd3521d2cfcfaa37434d6d51a2f3b5f6","seed":"9371c5276808cbae376bf529372c88e6c5c6178a9b469ef25a7558ad68fd6200a8fcfb2c117b7d99e9c7db907bdb8c69632e7ec058f2b8a4ea16518fed9dab82","priv":"936dff8ec25f22522bec8ddf52535a4320327697a581e9304e62240e5d2f4922","pub":"029c518dc7e8261f2a64104ac846ca90439959f4467e69dddc7c902dfd41fc2177","addr":"5a6600220c96af511e6c89dd9c8c80f1a1b0a893"},{"mnemonic":"glare earth tuition dish elevator mom remind arctic enlist excuse vacuum little","master":"a251df0c9b6925fa78489dc803a34839d90aa10dd35216360fef92173e0bea80","seed":"4a543a76deb55a8df2a58d0fdca3ef32e3f3ca74be97789cf004d1b69608763f8d311c6cccfa76bb6c7b04ff8d7ce20ee064d78ce4e2c05d894053e8a5309349","priv":"0aba88dc12cda2d2f267dbd19ebf0eb390483c7384895b6b7669ba821b73953e","pub":"033ca852e0b43e2ed4c7258c43bcc5be47e621bbe6e0bd570176940aa9b371adce","addr":"983a92fccb0350a8371cb5d5effae7125615316b"},{"mnemonic":"tornado group bring crack need pigeon scene quarter affair purse three mule","master":"b4d54df22150e28595aa5e1f6717ec764ffd8d2cc6338e264dbbb46e9155dbc3","seed":"85c281d21f25c14304aeb5402e717b52bcc0e5dbeaf2d155e11913717c545baf79d4d965670d7645bf093ff79f4793d136d794616905c2e24a8c72ba1af44ba8","priv":"c45a9afa0dcbfabbec916a9fba6bbd9cabc1a65b4ae3358e29943dee9d19ef9f","pub":"0272882ee11e688d04b89252ca1000456fa743b5931ae03e1d29934f79980baa41","addr":"860b69d255bf12d5f725e286203f91bdbdd29bfc"},{"mnemonic":"toddler bamboo tornado season total battle urban mule oval steel field case","master":"af9ea8f11f6fa83dbbd4ef32dedeb88070b3fa9fd98d27e1d9b43e66da7d3df7","seed":"4b95b55884e008f340660a6c9ed4f2b23f45a5e45535fb263e880ad8842e845a62e66720b75d94fad7f47fb494bd32ee70b7a339de5b07bb13c7f0533e8cec57","priv":"643eb65f5c87407a18be41f6c660d4d1ae610c9305833c570d785006b92ae354","pub":"03d1c423839d02f40abf0632a36f9739c6ba59622ea56e1085d2ed54504762b43b","addr":"05625426a47e8c11f27fc55a53121bf748c2d05b"},{"mnemonic":"meat endorse bike truth avoid measure injury polar rely water rocket zero","master":"23b56abfa50026840bf19105ea9a02569d1881e2cc447a582b08a6bf6304a33b","seed":"d8de461e4f04d4657de6b5adf8730bdbb85fd175326671886017c09d8081ddb7dac0b8262fb01f2dde477b3e2bd97653a700d373fa29ec7db2161dcbe418627d","priv":"f409c92079cbcbd9f96b2bf0d84f86d495bd8e6bb4859fff2d62c29558a551c1","pub":"0247908ec215a561a41e2da6ce05e0ad08158bffbef9fdf7bae729592e4d17b45f","addr":"e4cb61da62a6176483f967aad232c0fe89a9f16b"},{"mnemonic":"few transfer upon helmet property draw strategy talent awful goose index lesson","master":"c0e538e1e0b819660c63ff668869a81595107458fdad44459f6ec22b251d46e0","seed":"fad56235bc5f3a7dfa666c85e5782871e7c202bc4363da62fe3771d71b36a9a1502d550d668db69264daedefd7375862b891d4193cf958b6d8af9b39a9fb0e3f","priv":"51c8264448b81467c2c86c003765322f7c763e0854903230a4ff3557fb7afc31","pub":"03bd00d092d311add8fbf191abccf181bb5fbf83e26d6e29fb6f4d70055ad5e7fd","addr":"270429d79b582d835fff0b15b0eb21ba1c98b2fb"},{"mnemonic":"can warm add shell obscure ability mushroom discover destroy useless split blush","master":"611e2b107ee76d6f7b415776c7b0c6f03859366c342a56618850c6e3cdcf46c5","seed":"c9f4b2663f8abc8c8872842c41fe06b5911b412fdaa875ba59366d7a18734aa795092d220793867ed43afd1d79882ffefde417ca68c9ed61a9f52437d7e45a53","priv":"af975acc3008d742a37bd6917f4a5c6e9a5e0fcacdf1e74ec489e675905dd9c5","pub":"021152804b2ac5099446aa5e8cf1fff850cab08e225922e3865ae5b280a230ddf0","addr":"d4baf915a1108fabeaee47ba0b03cb241a12e1bb"},{"mnemonic":"critic length shine mimic gorilla rebuild bike security price differ spice average","master":"418d3fa386032d0a22a3b4553ec662d52ca416af47d0963e288a1bd08ad50d20","seed":"c44a2f7e56ffc9850454a03c40be79555c340b8bb2a7559a664a8e295fb34fe1a38074101b431038c207baa219181046805a67977022d61bff2270c1e27b80b9","priv":"117d57d4f3dd6db8a718e025f4fe5009949139f2a41aebd682ab49f96ed152dc","pub":"034af1a0205014cf22dcf328aa9308e255355dfff65a7185aeb180061aeece5084","addr":"f7af8cdd0592983c065c322efc64bf7c25769e2a"},{"mnemonic":"vast resist ticket hedgehog around fatigue rocket rack carry run protect icon","master":"c434c6e478fbf7edaf97e2c90c38369de3033a98696b1ffc793aa349849c629a","seed":"720f1afc198446db8a107105c7398d3bba7675fe0d01de12f933e2eef7d12effea0e9d93099e0256b9e494607bf45d855fefd68543d9beb4b92f3d7779a8616e","priv":"497b2409c651a41ed36e58e977611292bd665e273847d4d413291c386d0d65c4","pub":"03ba0869f55efde74895a6905dd149bfe140fb2ee4e3eb19c959195d93adaed2f7","addr":"2242ceef764c76680a768594f804e218154ae3e0"},{"mnemonic":"neither bunker use enrich liquid borrow time feed mixed clock submit scorpion","master":"a9c4646a10078a9ca6b218b7da25745a76cd7dd9e38d803d8e840863c9b97449","seed":"22cc85d4e408d0a751b854f16e00fd16b018fc26f95aa7d82323084ebd7d7bf3fc2370c1a5e38f88584e8a17977f17987d75e447f704603c2d71b3ccf806afd0","priv":"9801ad0243910e04b7322f493540801628da575ab5eb18a5e0b20b0ffa498e5f","pub":"02c3f726202db7040a250647799f5791d4013b38da6053e44f502d0c8a064124f7","addr":"fb0ef77563764b393003c137da2dd1ee21d92904"},{"mnemonic":"letter violin disagree arch crumble amount car inherit wasp vintage disease orchard","master":"a24fad07da3e4a04fecb2674abf8b4260ae5dd71f003622bbc4a00292dec3cf2","seed":"890b94a33cb176c697b9ca49281c053f88a8e8bb6f52af5f0fab57ce621145ea59624fd9f17bc4d7ff820bd2998cd1d4a2dcf8f89738c150aabe7af5f87a6b1a","priv":"499b6d6e741483ddccb35f917752e51970c5999900b48e18b2d1edb85927b7e6","pub":"03354167051ad5a01e056f7bb655de9af430fb0b3756e1cbbe6dd0cca65d43ff18","addr":"efbe8995bfb51a94b21826bb93937616c5807d94"},{"mnemonic":"elephant shoe quality dutch pepper math between lemon wood below strong grace","master":"7890fde1f36829461985465e7a7ba26ef80bc9908e5cd849517a999f04b02ef9","seed":"077fbcc3c57bca394c393e9852c3b837ab555cfcc5dd0097c058136bf088fea569d18fe0d447a8bbe53b5f22961de7c5cd0000274930db60ef18f049aaba4741","priv":"0af39cbaff322b7b7298b2605fc762bcfa69ff84a50dbb121502ed1e8db963ca","pub":"0221dd6d3d3c597b982093f5ddc4b8403b3d16d0e084aef37db3526f050c9b66d7","addr":"81ee7b181a5d1902b6aaf42ad0225edfbf669498"},{"mnemonic":"powder marine skate good skull correct catalog parade car length fold clown","master":"dca44cfd6a67d213ff365e5dd6bfab6fe63ffbc999c9d38e48b9c8d32d32f914","seed":"91dda0ed96be1b82432b76bb7dac1e80ed0a1c5413096d4a22437081834cc29f6355dc3e79d7ee00cb6bbe50a83fdd3b652259937beccdf11b844ff023269e76","priv":"25ef3009323160687cc6651338320311894b8260596834e6cd05c1b921c7254d","pub":"024b805e8c768c8914ad8c55d8e1898490b9c71fa68ef43d07f3db496f03db11e7","addr":"57c28bc9cb2f4fea177c34e581c95832256090eb"},{"mnemonic":"client wagon legal favorite donor tag describe object camp network tornado immense","master":"4b916d7a10502f4e2ecf295d826a3248fe9bc45a13f1332a46586833b2f1dc05","seed":"e2726352ef940a7cfccbe95172c1fb07c8c25ddfd7ebf5ed532f27abfd33e81041e816e34aff02530525596654111ab3186fd71bf65dd3dddcbc6efa7944c347","priv":"131de6e55bf3b614153557b35c839f0c869cb10d62b8c34c15fa2cf7c32bf97e","pub":"026a3afcb9ce79972ef910d6272613580b199f90a00035ecca637b09397f0624bc","addr":"b2ac8314ecf16da855c5d7b9ae3aa12854ae4a05"},{"mnemonic":"someone tip green vague sudden element beach people route lawn vehicle female","master":"2f7b9ecb854a851e308e944e6a8b39811a286e718b4ebb9a36e8c8570cfa7890","seed":"7653cad23c600649423fd68b0c7d002feb0327551521059362b3914a63f6ca6adcb1409f7f7f1b01f5c489237b25d0dd9b0aa121a60b1af33cccf739d05e32b8","priv":"4b3d50735f027be5ce5a85a739227b4e86590765e34b3608338497fe318cdfc8","pub":"03b47e6036447632c8ee3d2d1e3b177b51ea73bddd30893818c81059cfd3ec6c5e","addr":"10d976ebd58061a7c6ebe2ccbfc85adc617db2ee"},{"mnemonic":"version borrow decade family away zebra one course paper promote man gravity","master":"ca2995d4590ccdfa716b0f27a3e5e93d084df7039116b06ce5b4c5f2bdb18ef1","seed":"97a3f0fd0c7150526375d5cd1ea192dc0f04ed096e5c4895e0a15a6d491b206b59d52800c2c31ac6b512664788ac79663135f450b5198f358dcfdf1e9cc26e12","priv":"cbf04fb7c5c97ab9980cf82625a612919a85e72c0524cd49c0a237c25940b563","pub":"031f04b34f157a07b6bf50fd54ff972b8441a72d6a5d4fe1e54a1867d07fb61cac","addr":"57488105d27e808a39fdd571c895dd06264d3357"},{"mnemonic":"truck zebra tobacco hair monkey pool label produce leader hope misery cost","master":"b42e61efff413779403a9d5ddfad5230412788de3c0c7da4b020c73a72a77569","seed":"1b9c99349d6388e5f7dcbb8d5b87951a614ae7f803b69b30783035ed016cbcab3c42714246d05039bc6121dba51fbb722c13d2f6ca0cda79a0d816bf7e568cb0","priv":"52dad4ba927ba056a1f0a07515f044c3c9dc712c2019879029ee0b37c7f51b64","pub":"020e6af559d2e1115d8ab76e908e3ae778451dfca6250b996c42d4f3044f0f1a27","addr":"1a3e87568d2f20d4480c153cdd82af21382fdf9b"},{"mnemonic":"dizzy capable mutual cage uncover frozen exhibit awake teach grocery juice reveal","master":"54d15b84b003dd74581cc49909d3bd7ad07e00e745f4113aba6facfdb305aa2e","seed":"c4ae35b24c55af14bf590df97843ce1c9bbb324b845f9d2a08f0ead0d0292f6092e261711aae504feeae862bedd7558a092271455d89a5d1629be8ca43710e84","priv":"d5d476790ed6e46ce12e1573a9fedddada0413d5750d1d18e408c125e43c7923","pub":"02f3b58f6cf3533f3731e5ccc6cf094815d52467e39151d517b9f6a425f8743601","addr":"249ffe5709370cbe54520c162a4a7c8db2b28bba"},{"mnemonic":"menu damage flee oven blame vanish olympic scale pretty valid toy write","master":"91c79127f19318ce4594f4f1045f0f2efc8ecb6a3ee9177ea2cbfcc890a781b8","seed":"fc4b72ba3587fc4b35637186959e3f02460262bbb330c3b18e561faf4cb2ea29ae9f92f011d9d78231746dfae33f57d75ed85f96680a6a3f309a3f81cf59831a","priv":"cc7b3e962031f93079b4d6a9152a85bf927ddd2fdb9f84465220e2cc3ccf9ab7","pub":"03eb09eb7f21c9e411e3660fb49907bc9a23e04bbe75151ec732e066e4e0487118","addr":"20bbafa15fe38bfda08c18ed7a65c52daad01614"},{"mnemonic":"remember hamster power document detail pave inspire tongue rookie monkey board tourist","master":"dd1970c96e30be347b4ec8fe789b4cfc29ee073162d4a2e5dbe5c78c80944af2","seed":"561682af8f69fc045f09d43db4f71a3c7706615ac142078d6aaea287bff00b0bc3213537dd50b783d7f5293b4c2d2a1f6f9bfcd5fdf17f19cb10ecc05f854b1e","priv":"79c341d4178a7a8e1fd364d8a847d97b06ee94d9906a269499fafa63730d04bf","pub":"020540cda9e4957f9480aaedcb38379f2ba60648ae487b741c629a45a5b34870a3","addr":"cef7f07d4ae1da5fe3cdbb37d95bed5bbe53e838"},{"mnemonic":"endorse rescue traffic vapor chef movie symbol scene slide eagle youth tide","master":"8e54dbce9b2fba91209dcbbf6bd272d524762ba3ef2ed05297b01f440e6ed597","seed":"2a0ff0a70d82f948b866090a4e77ddc360a2f52b6abded17f13dd66e79e845bccd2c55ada4d4ed3dfb33d5d10700628bee551ea775c7f3342f6d7a0c3005194f","priv":"c58ed547be839e99eefae49479b3b6345fd7c6ddf390104a544b79f5f677016c","pub":"02d795bd4c26b14dd2aa3c04633f5e0ef787533b3ada72e7c83bffdfd4cbdbbc5e","addr":"ee25d8e4187f6a6214662cc4872b7f7cf7204c45"},{"mnemonic":"humble glove resemble slide rib focus naive false snow north strong attitude","master":"e8b826c5d90453d1af3082a0d5f6a625207bef96bf7e168ddc4091d7f865d51e","seed":"b9bc139c6e97339489cab328b3a362546cc19d0cc060e5c5b969206024aff86f1579d230b4b8b3983b33cba32a58d6dbab5f078704f0efc65d6476062c1d41bf","priv":"4085b496b9a18f4ee5a1d79974be1b1729f51d3c63ce347420d08833d2c793a2","pub":"0251ff80d2e90a37efec614d9fe65b6192179941b772a9468440a2380845996846","addr":"4fc115087277fd7dfcea23e089f5a3a6d7e3bcbd"},{"mnemonic":"fiscal truly seat deal crash train labor trigger kangaroo angry today minimum","master":"1770627ca13a66d0fcfdb474a3eef708abbf9d23d211970395308dd6758bf8e7","seed":"a19680de56046e31637ce714778881f1ec6bc08abe9357804062cb881838c2f8387b52f714377b89b6faa07c4e5b6294324081b7dc521190cd43f75f91029c22","priv":"131dc8ce3551842aebd863eb61e07e6d2de40d548dcd28ab0ed6380e469b5d92","pub":"039ac0eeaf7600231c75fc72dac0b2f58418e1f187aefb3491934120b4fc779d18","addr":"fce14c8cded6d2711301a780a58297c68761dd52"},{"mnemonic":"good dust code sell element tilt host valley climb genre asthma bench","master":"106ae0e9582e9db67ff88c1098d3ef651efeef30d44ff60de30bc0c447217ff6","seed":"1b307d8ffce13f7944bfeee6d6b97de257cd31608ef9059bcbe2cef8bdfded91722fcbf325788d48a5072b9f915670d345211006102e5535f7e2a1dd86bfdfd2","priv":"a78bb63f0cf56b81e228b801cfab1706afb073bc2deb8ce1c8c56f2ab564f1a7","pub":"03fb2144ca5e9dc753c2aa46f94806b35f201d942286571a44bbbb879d2e9e3adf","addr":"5d68a2c8fd5486ba93dc18cfb6a04698250a424e"},{"mnemonic":"buzz prevent cover list flock meat that slab crumble melt prison treat","master":"d620abf2e72b47f061594137194e41c33bd6789799d67b712179c5c9e0dccf4f","seed":"331fe2ce10eb19e2ea13c0ff7abac9b49d43de8d937963e8498da172a46e0c4dcf3e9d276d1e4c7dff5926609936f253e59c814bd4c061429cf1408c45d6e5a4","priv":"8e3b30ec2427f34c463f83a500842d4fb73575e261022720276b3fdeeb20f075","pub":"039ed714c28facded3af8fbd8497b17818cee93d26732aa7258ea61c6f8cf542d6","addr":"74b44edf7f993c72dfa53fbcbcc836c99578769f"},{"mnemonic":"domain tone insane visa much reason tip truck travel head finish grow","master":"3b00a9417e07cbca1d0b80b108cce99089c726e49c267b2e5a2a1d3965c7c99d","seed":"3e2c842cb5d267a2ca3fda4c20d674d048873ca2cfe89e02558a0b585475796979e4bef6e835b17593c53a75f5a4b37dacad0977a6b6fd6589d26502db47a048","priv":"cba9d951fdead64920a36afac46d82241c6084b46681902ad37eed1928135db3","pub":"0321db6f62dd27416d94cc22ebd3abe7ccb66d5e5f57641f79de3d9a1c49f8d0e7","addr":"17d65bc69345ab94e3817738981ea5f9289b81a8"},{"mnemonic":"bone earth country lemon great ridge glue neutral vibrant age doctor income","master":"ddaa76dc4405bdd779045a0b113133f51af43c14ac12281260ea2281a21d4704","seed":"c7edb1f4572aaf510b4b79f18cb68a7464d20babe170b3bcebfa7902f2df2a47d404baf1489922bbe2edb46a2a8ab798a99cb5dfc162b315e4e0a83ddcc5614f","priv":"cae3b56916c64b7303c038294cb1a9c15719e3cb8ac0cb2476e5688b6bbdbf50","pub":"03c9565910b4120ab6a62c275ca0127b717116e773f57eb93d1852ba3d0f2b431c","addr":"81b61de28566c226e0153b1e3afd53697c6591e4"},{"mnemonic":"rotate agree figure style cereal best corn box corn actress toy deposit","master":"ddb3592dc43e595f968e906c72be97c03beb78325f00969311f167e7748bfaec","seed":"cebd7cfb74b2ac03a29e6f4d3dba84d6b47730b478d0ec92f90c9a0ca575d17c581e1f3959b4e0ef5d5f51d43a0fa186ea54420cccc8c2fec8a25f36af549e6f","priv":"b3a7e3147640f66d7be8028a23dceeade18f23bead07774fa5d176014627dbec","pub":"020e2c9e9484acdf5f8a574e9fe912cbf6bf9198579107a48b976da5e4dca2ca0f","addr":"eba352793c5a7ed663a5eb33ef9c44ceb0388677"},{"mnemonic":"kit age click judge decade enable trash noble first grit crop decline","master":"bb39f1d6570a42b31a6b1093d6c1d3d469df3d565ceb2064863e50c540cfd986","seed":"9c8471f0ada1f4e4c4b863811b36036e72818432b8e441ef8234ea29446d2978195da3d63aea71aa5d1a7c4e71e131a90e2db598bc4ca2bad1dfccebeacf963e","priv":"83a532c37858e36cbbb325481e7be10beb2166ddb16b7ea19328e8dd712ac178","pub":"0215f7cac31b44b05f1fac2742ed8a0d6353fbecd06a3abe7946e2dad1feb089c2","addr":"a10254f23fb959df76231ea3061cdde854aee99c"},{"mnemonic":"jeans broom faith border sign decline choose black depend camp nest song","master":"01dc6326bc635da075e100217be8553c30be5c3ee66bd4cb8dc0768777b5826e","seed":"5e7d44489c45338978dcb69035addb83625389d15a5a2284ac888682e7499e8b07aca6ccba965201512ecf7da5a5ef6e22d561583e15f4507eb79ca60e525da4","priv":"07e07f2a2d9eaae6ec9ea7f676124dd14e2f29e43095cc8574b67fe5a8de1fc1","pub":"0295a431168b265c62a8885186b7719298c5791a0d73cb5b6e55cdd7200b5dda38","addr":"9328f2fcffe4db6cc6c8c2a5b0e7f78cac1854d5"},{"mnemonic":"narrow federal feature ignore damp sun brave cram blood crash glory cargo","master":"7e5ad0d0325b2bfc0ac8a2d32b13d758b308fad36c5aa4c497ee3fad4c999fa0","seed":"e680df5400ad6c1f8b097a39a4ce9d9c3c48ea4c21b71795fa499ed641137b50b58f78603bcb2bddae78ff5f96079d48fadd175050ff95784092bc93557d0c13","priv":"eb443a6e96330c1d3c2213e9e345b4cd0e4b2562d6bf13bdf9366f0974d0a98a","pub":"02843de6f3e02767908c2692c091df22867fa783f858a39aa7f6dc786ad5b5d2d0","addr":"5a53f850027b3f303ade321198c74d108e24f699"},{"mnemonic":"fringe fault blind lock wreck one kite ozone system come spring amazing","master":"dc2916fd303a02d0d7ce5d5e0ca55b8c504063c3b54d006f0926a89c52e413c9","seed":"ce52cb29528f78b85c003f5bee65b2d3145ba93963fc4ebc023183618cede1c0add4fbeda85df76e8887004fa526769752808977726bfcc838fe9fcbf4019df0","priv":"5b6302f3548c8a8ca594bf67bbfd44ca98ca84b2df134b0d1edf4b28ccac4340","pub":"03846f7ead64dacb85b25d5279681074dfb9a8c363fc48acc6565c6e142380ae3b","addr":"b32324b0f48ace5119e6d6616b9e613438d92184"},{"mnemonic":"rapid donkey deliver soccer between antique pipe youth science angle erupt leave","master":"3ba5a3b9f7badb433f4235e89359801dbc87b6948e74a61e8555b11dd2f6b491","seed":"eae34a8427228c93f754a3a5ead6241684af42b34047f98b12cb7339f3b7390ca9ebd4373a89ef512e75126de78cb72501d2deb575d356d6ba9d64a8d50951a7","priv":"61a6843aba8ade001ff479a2b768008ff5ef996a5a8576956328815755008c06","pub":"020bb75350f98f845d5ae99ad8720b025d7176c00b6a92a9ea7ac14818a7ae6a73","addr":"2cfb35c09a2a5a458519e036d21555571b88fde1"},{"mnemonic":"juice riot brand example type sign crucial warm bread describe rain slab","master":"9de3651516a0c991dac2dcfa0f95265a6100b6ca605a0d577bc9221151306b18","seed":"5ad029a5964c27c295502b40fba611bafb9b9e626cc8f8df5a98394060d16e53ce41ea46421a615c5a3b5b61c20138466dad1a0b9541d57e8ccf7813ef6d9327","priv":"a54d64cedd5e3b3cfddb182a513a13bfa1cd64627db1fcf5c91f398330010741","pub":"03840dfc1f9d210a632dd38f45fec096c9dad11de2a8523adcc79f4f520faab8a4","addr":"1151a84a341b5b24b687abd2083e7fd150ae937c"},{"mnemonic":"air ill huge miss empty force indoor inside young ball wear blind","master":"76dd3f042a76e88b7d63422086fe0b5242a80775c92ba4dc31b31333c6cf367a","seed":"0df5fefa4beba8bcbdc1e6d4b906c91ca66c14261b543280b31be5e1a37b7a91baf9f34e17143a3e3451d3da18534996a4edcfadeec0971b5b7db225d6056fc5","priv":"90ad41d7cdd253fb10da90c1eb6f9c75120553db484c6e1e2d61aa4ca679d5a3","pub":"0229419c442507c123f161277de5a9beb43fd91556f6cbbf194e26bcf4bcbbd4a3","addr":"68ca6d2e8330de386a3dff8acc4f9be6aed6facd"},{"mnemonic":"electric grape tomato average idea shrug slim certain caught know rather poem","master":"4770c9846aaef6f78e326f3aa72fe236564251b0187e3d8cf2dc95187fe348ce","seed":"d054638cfeac9ab3a2150a49d6854c9fad983e7fc91ccba17d6bc210c976b20336c33fb5f8eaa6e54bd6480487491a1f8150d074b29ece6164c2c99c5427d7c1","priv":"0bbedbc6807a1c2b81f7c1f8b9a8380e27d19a0905eb505630156639d0ea4097","pub":"02be715af6a8583fcbc51f4cfed7e78d93313d7fdd047b198abcdb6d0d2271bca8","addr":"0535e0d51a9f99ed22fbb713b2812e4f2fbc680f"},{"mnemonic":"boat tourist proof point giant horse error trash abandon transfer poet legal","master":"5c366956c526671c4e6f9011872b24d76adcd95de54187d81e3c7c2edc749023","seed":"7455f7f31ee2c7729f7a05b4223bb2b335a0ba5b8ca1ee3f632e04e752294a1f0081e94cbb6763391132562a8f8323b0b05cf00961a2d9a4993ef5ff69d52e2d","priv":"ea1f442448650bf1445c5aa13107210280293e9cbe1b1e432327339db7b68624","pub":"03a80a35d7d599be28524a427dc9b3fb593bd12c7cbef3982bc67523ae6c202455","addr":"c12318aa7559ef40d3b56856a2cfcf89d5f5def8"},{"mnemonic":"number oval section onion aspect surprise romance paddle oval reveal business pudding","master":"2b2fb49728bef8d8301af63af35716b2375071a75b1e2e8d4162afe068e6a7b4","seed":"384e57c3ddb0691d55be1d667dac88873719ba4260fb3d26c263d75d8f5cd12b444255daba423821916bd188df5007ad31c6388b0730de258f543c23fcf74923","priv":"26c77883d1bd7a257d55ff87b562a41408544fb727dd98a7aac093b4a70f75a2","pub":"020ee3bed4cfcfcd28ff8d41c96a1b7851ea3ec4f81fee00ef8ed05def21d37e51","addr":"e8447d8d97950c9a15d3d181a3cb00b0541941b4"},{"mnemonic":"shell lunar fragile unveil dinosaur napkin mountain fresh annual jealous check jump","master":"0bc353301fc1edfb08d3ac53efcfcd96699ffae3174bdc83876df3f9a5fb1a90","seed":"4c8a46510fff2ac0d666176cf77fdde3f2dcfee550260e43bef7f9e6127ad807ed335fccbeb49426aefa8c0f1268e5f602124c2487653160f575cad5ad68a571","priv":"2e783a6c7e1e74645f99752d342aa3b6de689af33c5a80e3abf28f7e1ac0c6e4","pub":"03fead7454b48e2bb68c8fbeb1c807dd4a7d05c6c4c9153d4c516b05967ec56853","addr":"0ff9f27bd866673fb10bf741a221cc8cb4a4f95c"},{"mnemonic":"sniff rule decorate expand siege boat people choose resource topple produce coconut","master":"03c9712b2dc0ed358c17478acf1b3b9c8ea9106c01f0e52056856ed212d4b9d2","seed":"a689100267375fcea1ea18ca798c803fbb6e4b9d82f9f4fc96b3a0873f41b7553589c605b021b4dd4a2db9d3dc5b239053ea5a48ed227fd6db264299fc8d0e96","priv":"8eb04249c00506425b0e414b131ee80dbd8db4f5a873be04a0b1d293203544dc","pub":"031e448ffa6e72016fbcdaa4c2790c8e57145ff051de1d99b69cac4df1f8ccfd0e","addr":"edff4f402b7525e5707a9ebb5b13b2bd03d12ee8"},{"mnemonic":"crumble coach clock opinion net churn cinnamon seminar slot upgrade always nose","master":"8af370254a2887d6d8d00364fc824b6565ef750d3b19fca98078281b040f9244","seed":"1c6241635fcbabb70bdd30d827f602169ddd82e36333aa50a7f7f2de4bbc9a581d6f7718cc94c0d63779f38363a8c45042b76b89610f9b3c2e785de234ba5818","priv":"23dc380f4557ecb1cb779d128309012c409c544c906865b06244e49be877db3d","pub":"03261b2261da80af3a33ca8252aeb21edc608b337761a6aa8160721dc202e65b29","addr":"fe0cb66348437ea5dbf8ca79de9db63f0ff51528"},{"mnemonic":"mind fat sort buddy gap glimpse hunt ready library invite mammal repair","master":"01979edaa3aacf38e619e05cd78ac50eb2729d6ca2d8ca5b476677d30a4414f1","seed":"7fe831aaaacfd5895fd3746b45c0cff02d52923fc3ef55842fdabd02de07bb6bbd07dab7f1db1318f97fdb6141e5ab0142d7d06ae92c020d2be760e08f99661c","priv":"10d3729902b4a8c26b88c84ddfd6201048250294f18a2ff0ab4248a5b4085992","pub":"026cc54074748e623153f3bd4b735d0c7df21cd21fc59cac3ba43292cf0ec3230e","addr":"0b7932471403f4769cfaafe834488587349525e0"},{"mnemonic":"area switch resource vibrant prepare write genius danger extra alcohol section company","master":"dc841ed2cf4ceadffcc8f8f6fbc50dc3385e2b438ba5f985f33dc5c68190c69b","seed":"cc2ee2baa8b309ef7e76fbc14a343a2435d6ce0f7da46d1f9a84782c77e84fd7cc40c503db71f75380cd7e2236c9dd0579b528d84b7deb89b0d4a4926c66a360","priv":"b2fb735d798aec932964121a2a00cfee6184f03bc0d7885a84507ffb1a8f97c7","pub":"024cd279a8812efb672200639f718ef89d9996a6bcd6ae9d50c1548da744bdee7a","addr":"4c56a50381b8edb7e02124e205e170979879ac05"},{"mnemonic":"twenty shift viable behind victory napkin cry gasp own dance primary glow","master":"4519e4b46f8279cefb8b261568638b514f8591a60617fd9ef4c0f03c193b345b","seed":"393b214e7d24adf4912b8b6dab2c779c2896816b1f001d80a702a7dd2c44b5a1efbc85e2554e2586eac150a1a2bf1fe12e08735dbab0ca65a746259bf953846a","priv":"a187f775899244b9a57da9a173614ef2992ce1bd2ad6333eea6e796f04ca446e","pub":"02735247ce372948d922ef85da71f424580a46fcb453cc889411722f8984f301e1","addr":"c055c01a09dfa7b360a99d784d05e715dfb1eb37"},{"mnemonic":"jump end chest decline act capital text puppy empty input possible goose","master":"5e8250d4fb78cdfa191efa589fccb98d2a635d091df8352a0aab57fa13be007d","seed":"fe6e62ac192f7f101fca767a1a24421acda2c23d1c232ccc19221eaae7697d9eb0cdf7350272d93ca37d7e10b3deb739ff0b05ee723a4acc754bb6994d2a5b78","priv":"f400703afc057875c9dd11e046fa53c0edee27c78e2905425a74139f3329c1be","pub":"02c98ed539a5905393750276488e60d851163ea5dbccd25e6fbeccf119b22f88e3","addr":"81473d441525a6374665b98ff30f75b43157b29f"},{"mnemonic":"spend course nephew spot nothing girl wink flight wine hotel harsh better","master":"6d62643bea35847c1ec3b6a7273c42a24832d28092402c2dab4df1004df249cb","seed":"5cca26006b3c22da1622ffd07f0478988c9c97c5f128f78b89fe6b89314ad8c37627f02eb630f376574a38c243a3069c5a34dc59a709d5264f42cb1f80740adb","priv":"dce7ae54553f89302b0aa63a500cee7472a4a33967aa8a1ee18e16aa0e78d2af","pub":"036052be2df30dd8ad7bbc35055a4e60bf520fe962d9c7158d1c5e2b45c4c4f841","addr":"1ac0320f22683aa7d67416e24bf59098933a7e4d"},{"mnemonic":"coast grass horn front broccoli artefact also history screen bleak thunder example","master":"c49a459ec914359418504d3d471fde7fb1a82257347a4bd95997df53da47b2ee","seed":"2f528e97791aad4a82a7af1a68007871a2b4917fa238d69137fb93f64ef1f7af2dc036079ec220b8be11365d62ca11be66e83ba0d829671a55a319c441f6c8c9","priv":"06fea4c71f85b7b161ec6edf5ea0b90cb95a410fda9fee0a603fa350f289ef43","pub":"022756af9a6df41e36de4300027a8a6a6a8f6bc7c2b31252153f3d915299a8129e","addr":"6203a9699c5d1b6bb64d1dae86cc741896792266"},{"mnemonic":"shield liberty wash park absurd inherit suit resist garlic cycle ecology mass","master":"a35a147cfb59e738ea4eaf9e6bcf1102c7d4c15e3e59458aaac528a0635d5316","seed":"fdf1adaddf7f8bec833c3054d079be7a240f98fbf08d051892a02c4b1e1392ffabf84422606d7ef333f99773bb8d51f01175c568a94f5317cf8b225991d7a528","priv":"581e1227a6f32dd51a3bf36e7c4f1504b00a528312f3025f3f7fbecb37b88b7c","pub":"03c0e2eb1d83e266c1c25cabbc6a9d46b0d955f70ee83b78ef0348e44fd6e8162e","addr":"18573866de437d48c044002b20fc5b886cd1dff0"},{"mnemonic":"spider cake process model elder electric mosquito rather choice insect crunch pizza","master":"1c95e3db98b4194020be93e3d4791b9aaa2c7eb1c83b02d7f3bdb07183f4c3fa","seed":"d25f0954443b2c18507a0c71923aaa31857d03187b08a649156ab634c20f29a444ae505cc80515e30feb69c05cd4e133f38b18659ae6189ea27b442705b9089b","priv":"5b9cff23b6a19a67864ec0973e3004da548b5b97400f9fdf2dde3db2469b776c","pub":"03829d931e59a87c6ffb4f8d512374ae7981ccf2c08a30acd787b77c628b727d8f","addr":"aa95e7e0c4fc203ea639873f05503a2d5e98b70e"},{"mnemonic":"include yellow off random knife video person monitor height virtual oven derive","master":"92cc90784cb9a58deb578aafd5b6bddf5195bac720c1b27ad575bddb1fe19487","seed":"7e090c19e128ee76d95f7334361fe7eef356bec53a06902c6c5bde0cfbbc7a7adc540eab3fbaf7d1e4c6ef38f4c29054767c115e6160e6825868a91297e2d5d9","priv":"076608b84151c535a295ccafdd1301e8eb82f779fb4aacad5bc2a1f0a699364e","pub":"03581ea3f2b7ec38ad8ad9717b22cf43192a637eb4d4db0b051f2085ccb43d9e5e","addr":"a68d0470048127c2234f1ce4a70f9f04c72c4dc6"},{"mnemonic":"chuckle hybrid seed portion dress move settle pool erupt segment annual demand","master":"fbad7924603e902337c9f58086a9334562d6f3866c798dece4c1761af9914d3c","seed":"ed3ee02d4b90299b11fbdf6fc7698613aca89749352bb2ecdbd5cf0e0436e0ef702a615ad041bd97bddd4e892582ed9037028002e2bd5c58ae7730c47a44e20f","priv":"77c0167a4fbcf6dc7105af046e2978eddf6366e945d8456c28e13c8f3ae4de57","pub":"039a672d56a39e6aa1bf2d51aa0c3525692117ef33a92ff903dc9611f86550b96b","addr":"d4fcea5b95282e1286e412122701e9bf49bab344"},{"mnemonic":"portion upper maid dash where control nuclear post run manual mushroom bleak","master":"66c9b9ac9c75057c18fa94eeb0f75cc73b90c08eaa3aecb1a83577062be3f6fb","seed":"06d6d3e53986a879befffbf87dcfd6b7570321a071e22092e1c88de34bd44d7ec72c76323fa0a7ca040a7ed90ccd686d13ea027ebb06f8c66154a838de229f2e","priv":"f5141661ead9339621deb48ffa15c4569cb2da05467de78a2eb631a327f5a68c","pub":"029a18777158d04ba09a941ac8ae89366a50dc8253319b71647fc1c6e93dab01c2","addr":"f1fa2abc32e5e9aaa843cb07c37dcd549b76485e"},{"mnemonic":"fatal lend tortoise urge topic sweet spend antenna stock nothing doll face","master":"2d03a7cd457dc67949321ed16a2893792e5210cd4bc0f5ac86af83897094507f","seed":"f48146154da92dab72fc0fbe1f6c7524178301502d72ae7c404b67f8b7e102306bc03f339424b011eb22d15977a0f12ad0b25e7adef5661d65b06dd0b5184dcf","priv":"d5908635bebc92518047b79599b90b1048fc2bf2d3eb36a2bfa5a894927675e2","pub":"02309e322a1c103c44ed7195044842250cd757b061430c55bf1fc8f7f10d660bb6","addr":"946728094cdfcc601a6a496ef5dcafd3de9a6488"},{"mnemonic":"blame margin glass dinner worth right require unknown wine settle make aware","master":"7297101c31d8168aee1fcb025bf1ebcd747ca6d5ba19d2bd28514fe3b0e0be18","seed":"e97413449360a70d3d202dc63ca0670a98aafecbec1de44d8c11f1ae90099bca926813685a429d4cefb6f0e29e207732f29e962aef7b6ae82ea3cf2d7617acd0","priv":"b144f77a520ba1ecd94b1fed5f362cc1fdd7ba9bdc355e9d520c2f39c0a20f7a","pub":"021c6773d45e549503e3bd6983655510a46a873e793c6cdc989356f657f3e5bf20","addr":"f259947fedfbb0ac039a44936e901f3622b9323b"},{"mnemonic":"gate make bird oak brave hole elevator tuna gauge type school disease","master":"1c1407749a31dca67aff767e4b78eca5351ab6792dca8aaa3b31a766147207bc","seed":"e079a04be32619bb57910662e11a006f27b02743e4440136ba02d7915502d0fe1590667a555fa76313452747a1384596950affa367c4a3e6f5b179e750018a7a","priv":"4319abffdaef48761ea2bcac50eaf80551baa1404b27b48fa379172f2fe8e1f4","pub":"0229358636cc2b80f63c7256eeb51b9e0bd3e1df0cb5e69ce87f15d71e16fa4a8a","addr":"d0134881f0c1f11cf9938920472f3d37e9365565"},{"mnemonic":"crumble danger tell frozen define mixture sadness strategy village mass junior kiwi","master":"0dc426f3c99fc31a73cd91f5c261fad7b70e2a7738e6312325686c95dd9df2b2","seed":"773955324075bed8d11f971096456a8557f8cf76750e48fa4423bf5e604943b5ad17788fda1afb47f03cbe208c79c8b6c38105538d6028b1ff4359e7f5a122e7","priv":"86f1207640ee12c8175e9b34cdc6fbdf2c200be9e03697fa82f8cf43dd8a6cc6","pub":"022ff8d5a67f1235242e9e143736bb950d391bfebc29173cdee50fd6826ca895f5","addr":"49fb80faca4a0537aab1d0b7ce5674d4490ff8b7"},{"mnemonic":"reflect mass fall habit mountain winner logic box cook between drift donor","master":"350db5a525a544ff7d82978817ce35f33ebb6c148b9ff6a9bdbd357a16373e97","seed":"4511bc8cd2b64472dbd108250e8d0efd825e763790bdbd44f4ab700c5038ab129e417ee574716e1b895cd660c72f9bc4f1a6a2846e7b09c635db2a51c22356c1","priv":"f2e5782037ff1ae1678ca2baebce674b973aec58caf7aa1aff830b372a6d6436","pub":"035d54ecb4a48285dc13346fe46cadd907e443c03e91339363b81c3d1988b09a38","addr":"6e8114f26eb00d95ea09ddc7d4d5cc104bd3ec41"},{"mnemonic":"enrich corn rose chest can figure canoe used pattern margin usage increase","master":"d38a5d82ce728c441f707cbced8d7b08d896ef9f37d0b563476d5500ca0008ef","seed":"ea926f98f2efb3af7bcc514969bdae01680e237424554b732cb37257e0b9f0ca1303af09dad82b0c967ce189ac8173901937ff42d0163310e5fcadfac1710c56","priv":"880b645884e27fd9205d82ad44c18fbfef02f205455ef818fd3beec79ab08e5c","pub":"0229eb380ec659c8964ca2d19196eb59804855b0d4226fbfc48f2373466380e1ad","addr":"ba2ea36646e0806378faea7ae0d1a691d91b54e4"},{"mnemonic":"coffee labor involve dragon upgrade route south guitar uncover enable soup sustain","master":"0dd39ec2f51c819042ad3fed701d1f982f599383049b38ca36da842bf5d3663e","seed":"f81aca2d700fedcc07990f1b9ecbaf194c0df5dbfd240437c06728bebf0022aac77b8b44f7863b13613b4bc5752f0f19e2a21617e5e0578e53f9198c2ae853cc","priv":"12c7b8c875fa9d0117b53e2e539584c9c524fe1519414a13062edb33eb712408","pub":"029b8bea854aa1d99e6564e5f4c9627a95d1fbda000a59ad3049baba92a474a5ee","addr":"eea20101f17d14b6bef65b393a49b9c558f5b87f"},{"mnemonic":"dash clutch smoke chapter lobster quote fat negative desert labor rent basket","master":"a6dae26374b6e165be90ae4f1759fff4dc4da104493a21c4e062d42a50ce1b48","seed":"2c4d251fa5b3a634ddafccb16393730cbdf1a1f20b44ddfa0a25ba2e5b6a48e8338e5e5a8a0b65bafe30ae629126789264c98a4b02ba96da5eb3b15fe5a296a0","priv":"5f7246020bfb4ab5146674df950ccadb3b014cb8956e4bcc3b575f710b4cc4f5","pub":"03f2712cae90744dfdec3a37990221e86063620b7298778ef4cbac6f438764acaa","addr":"7d8dbcba23c0e04ae21d250c585f511ebb825ff1"},{"mnemonic":"toddler juice raise depth club rebel weird portion void minimum game typical","master":"d56bc0aa70e0e583c13ea0edd4df5dd6f3b02a9a0434b0614f5fd9c2ec029b58","seed":"373286db33542c525710b01bb881158a7a1c723512596414370f1547edc08a11738d855569109b2249f927839c53361c8e0c8c468a62421c998b97fee5dab6e6","priv":"3db2ff05ae17ae227e78096b9803281f9332857f555dec941153805377f38520","pub":"031a9ef5c0a80f32c99f8c3af49b8fc51b3902ed13832ffcbd0f9527e057c0d56f","addr":"e5b70525b5b3c325b9a2a044ef05452ea0dd6821"},{"mnemonic":"legal lizard easily dream push business village network bread solve predict surprise","master":"4495fed7ff0239ac3a3ce75f1ec1e81fd3c8f7ac2c3a2a9d94f0f82a31042796","seed":"a709e3741759029624f534c66e8d8e56bb03fd8df960fa4776daee1c61810b96c495bbc3dcff634ebf140dcec9f7da9151e09856ee16ba5106cd7c1088032ebb","priv":"5e3e0925e33311aacc94b1d29f359f449d443cbe618e0a689d0c40cab692af21","pub":"03e2b6a2f99cf742f7c1fd356a6948df231a75fafb3cc471f9ca4ad00ddfad8c2f","addr":"c0980871c3f7b15830fa514167c90999279d496f"},{"mnemonic":"draw boat debris humble cactus method duck ask author soul busy assist","master":"8bbae4af997d4735d14c988cba637bc041b4b0907c5c53ecccc1456b43ea79b3","seed":"f05a91da4ef94c81e0bdd30ed6d9cd09cbff7013c693d6e8261bf2eb3cec9568fd3934551344ca8c768bdfdc44fd1b30a082bffa2c1611654320948883785e05","priv":"659c3413372935b99cc972629253f37305d258459c1a92b7fa81883ceb6dbb54","pub":"025a5af583aa8bf4459c72c9665cac772cad9fd49518b9080f635a4de283542588","addr":"36d01ca7b9f3c37ccd529033c15b1c089b877d8d"},{"mnemonic":"between equal crush suggest spoon material poverty diagram forget click company call","master":"3f2d83ceff7b98467d9cb2523820f55d22abd1700651f4b71ad6856c029ada7f","seed":"34e2764bed365dc73de440475c23b18cd69750977a4197a2efe374ef552a9c56eadd6ccad970c422730cd51e5ab4e051e4c86ef4db2284256186fdfa0ba92434","priv":"62febeb0325ae75eb0c71c4a3ae889a4a274f4ccc361cf0829743d5add3b084f","pub":"026e528216c24173250fb71c73019ac45e6171dd4f9d8fd97b0933fa964b966ac8","addr":"22aeb188b3a21507a27b35d860b583d9d844cb91"},{"mnemonic":"cram palace round input disorder armed wool cram over abuse order grow","master":"5c040bed4bfa8520ccdeaae710403b3ee7b77e069536bd87babd93c09177f9c8","seed":"df3911a2a01087b8826327ae368abf4dfac62258a1a7a91cd4ac64067d926b3ab89deda5a0e5539d8c0071d14815e2aebbb913e2e506c6574ad973f950049d91","priv":"87a97c3f977b1ce0b9dea5f2ca8671ab6830f9dd4b3da8195e603b7549f529cf","pub":"029722cdb8ceb0be8456ce4dcb24a4fafb1028ebb4d35b4af757bf22e8539de367","addr":"eff0f2517523745b0b404f688dc048fee89815de"},{"mnemonic":"donate view volume faculty anxiety degree brother cheap film eternal hurry debris","master":"762647d305701dbcce0a5bb079077eb514ad72981fc8489c6cdbd71991d24179","seed":"566c1fbaf6ce9a0c7352e903069b332aafb438ff086eaeccd1207b6fb8a9f0a0a4279307fa987a905ece60a04a52a59b3db45ec15892ba76b2308cf9db8e36ac","priv":"c0092fbde92e9ad29a52c825b8187f40fc5e4ca7a586f4b5b4a8805f0554a0b7","pub":"0340718652a6d3d2b791e79fa91e96f60eef740d10e3db8c8b206457f31b1a0aee","addr":"315ac67c85646a6ff00192c6752301f11e19e409"},{"mnemonic":"pattern much pilot choose prosper spot shaft camera unknown target wing movie","master":"1d04a744f4e0ca2b180f5f20ed34bfc800d0c874d232b5eee1018142fa612b15","seed":"2e1bed0a31e13e8f13a85a35ec1531a93ef433c96338f3e6533dd13d5bdae15bacdbeb954f9613fd4dfb6a941357fbcb5908520096502ab4f6edbf0c93e6f559","priv":"ec42f7fcebafba29c6d692ba732e8060d7050d7c57ef67c0c9d8ea152494a804","pub":"0295575d1d162b7599175bbb5b213ec3e79d6b632f5e50b6ba84e6a2ca15d70939","addr":"ddaffeebad79dfb4df2e2e35d9b6928900d38179"},{"mnemonic":"cruel tooth rescue step entry shock uncle crash possible inform inmate such","master":"f02e38437185794196a7822f0e4badce8644354fcb5d03e337ecee7ca0241df6","seed":"6dc6d27c01e91879638ef2281c904f2b3b0df18b0231c16f1518b63b39ce53c59335cb858b692f50decbe6fce2617434c927cc09d428a98820563438cda13c50","priv":"339120a922594b577d1f64af46c9dddbe0239a1ebe0187569c4302c035df4996","pub":"023e91521223a9a95c1df315c36ae543e606f426e1dc0b4938b374af5fa4967fff","addr":"c5711e1ffa2cca8f18b9be910c4c31330b8d60d6"},{"mnemonic":"evil height seek edge session require obscure begin skin outer foster able","master":"71495c08143478276d1107b9a496ca0befb1663524e46ae824b65e979b14b22a","seed":"2d209c72dd757b948bae9ac1d69014bda27b76dc890be357f542d994ee79329ea51114a1f721d17751152a1bbdd5c5e836771902ec6113c716fa016e30309a31","priv":"862d6335c9d39a65655c3fb644d71f9e4a902f085741ea4df75cd36afb94b0f5","pub":"03004f2c4133b0ffd92bdb3fef5eedd75bf04b6e7e18c0dea3204989476ad3762c","addr":"5d422b55f2091fa9e350655b2e550d209489318c"},{"mnemonic":"gravity sustain speed quit clock cage cupboard radar injury unlock sheriff alone","master":"44e4470967f781784b98f1e33d8fdd16568d1d43b1de044541a8e0821715060e","seed":"6c58333728b07bcf95ec884cb2ccdae5ec12dd67c3442044f7680a87c47da325429bb280727efbd848ee7f8c5fcdd433da0e9de39b21acb66938427ef6ac47df","priv":"37a9522e106495f859db94f5b6efd4e98d49e352b4e82cb3fd6ccf03bbec4b90","pub":"024b03123748ed0b4fdd8f6a351d1ab6a7167396fdd583aafc15233f5e79e3134e","addr":"62994a9de9dc8b9c9269287b63d847e90f519d59"},{"mnemonic":"fetch uncover sibling unfair brass explain sorry wheat talk web news exotic","master":"1ef8d0946b982a3233328040bb94e8e8cd0d5fc24b345f55535a8e7a92c22b67","seed":"f5b3db4d9c5686aff4eef0b0eee3cf88c4ffecde0e9b1c6772d9071fa433d5ccc9173f2f9e95114b27a37a1d91869227f8cb46b3ba26323e237bdc7a2365761e","priv":"cbb0527d80293daa1ccbf26c5e8f117123a63c0adf772bb80700e2334690ae61","pub":"0271bce67767a922d141970501f7341e7ef0f5d877df908c0c06e66000b94810da","addr":"368f8cd8319772499a4f2f109d440c2e1ea55038"},{"mnemonic":"follow actor noise creek afraid normal exchange universe swap draft cake forward","master":"7a443b3ca6069d981559e8cb1db37aded7d3e25d06f2f5ad35867c2086ae4cbd","seed":"3189fb459fb30288ea67a8cb2a46eecd8854db40b7f5b828254997301dae372d05b3eef58b0c670b510abe11ef7fa506b74a4280be94ebc55dc891776599fa3f","priv":"17ea39b63689ae32dbddaa2bae52e8fbd79d725a6a2ec0b56c432a90a1036bbf","pub":"02c3460e957acd5e228400055e9e1b22fc5fba87b16212382ec78b6131d05060b3","addr":"4e981e57f9e23760cadf381d28eb56d225f8b785"},{"mnemonic":"pulp fire pulp salmon finish question aim unusual casino super label soul","master":"3041f6724fc8fd54913ceac5487cf0b2a1c95e1d2cc65272f99d1d0918d73c20","seed":"424b5c69d42e95405b4d78c60f8c4541fdfe0600214623631154e144cd2ffcb3d6996fdf8838a07947a6b08d9792e6e883820399429d2427eaf4ca3cf2311e89","priv":"19026cc4be5629f8580b7cc5f01e36cea0cdb35a933d9871324b53eb9e4d62cb","pub":"0355a17ad503b793fd459e58e7cd85e13724e155ad487d9bbca7e49f939a89d514","addr":"e26908acb2f197f61f0036aef79f9c2b8e67cbd7"},{"mnemonic":"draft clap afraid census image debate pizza display sweet square today floor","master":"146c029ef74c9c9d00538cb03e70ffded59641ff8cd695e44abcbd71dca0aea0","seed":"a000df2590dc0b74a6b1bdc868079ff3172fcb9f758f7f61e9963aa49bb9ef1715985f3b88b0d1b22fbe5d30b2fa0dc489c19ab2fa42c5e62734585b6b35f212","priv":"4e39817ee6a106cd3b58666b9bd419c246caacd755c302c5440f7ff8b59a3e20","pub":"03532d53d02bdc71b0ffab4f5dd3222bdd8b49117ea8c10eed11882a21d604c72f","addr":"4ca53567a3b12f8bdc8cd66b1338c3760cd9def6"},{"mnemonic":"virtual search upset mushroom bundle vicious soul fine decade reflect basic nurse","master":"1e7e15314e1cac1584fc5d6fc5609db315a3a63c1baa96afa57475c7e05b77c7","seed":"183399406bee3c1b8c11ec94a8528e0c26a7c22926f286e320461809f84976cf31900b436022acf4635066c55bb793659254c75d4f18d692a07e20026d409986","priv":"8a07be43ff390d981394dfb29638f2464bded05d5daeb3bb54a93ee949f2da36","pub":"0336378e1b67b1588c6d5e279fc3fe58115cc24dee0cabb5f728acecc8ac6be8cc","addr":"e12910be135809fb5a6314212975564422d98445"},{"mnemonic":"venture wrong wheat oyster buzz cluster abandon rescue alter frequent way scheme","master":"15d4683cb037b61ab1fc64adbff7bea44f515683352cdc3b7e8c6d97700d66c1","seed":"0ea9eba247cce6f936ca4958c64cd6f8f19c3f68914018ad61ef9042a4fcea4058f32017ecd0f672cc8ace9af4d06b59bf2064983fb4615edf912f73343a734c","priv":"47006c978ed707415ac9da1eba861546e6a6150170db9699b585648dda4c863c","pub":"02dc09d31bdae4560008e82db56cd81fe95b6a6dbb87020117a98ea071753659d6","addr":"4a5c5ff9661ef26136b09a8e6af9d6aae0188e2b"},{"mnemonic":"consider sorry keep source session simple solar acoustic crater model hurdle champion","master":"2c3331a974819d6f38071aa2c790c42ad7e97111f5092b00f972859867cfbc5a","seed":"e4f33149ee0eda7a52f5f64d1c8ea945058b2f3b2cd0a0d8a85fdfaa2aa0b559bec8bdcbdb5703dcf2ac54ba7066f4d6c95b03140680b1f1d30a6305e4cd0b7f","priv":"3030acea65637e530cc39df608f467b4df6a47aba4e241c7579a608d8d53f12f","pub":"0340b77aae056f3e3e7e7bcd670c05a3c8975efed4e7efa0c0932a48031a242778","addr":"5dce4e49e053ec6443fdc36f11f1f9f233644cb4"},{"mnemonic":"salmon allow rebel eyebrow siren remind mango round task ritual exist course","master":"7393e3b637917023a724ff52b1f651f39da8a0d860bf20cb810eb635ca348eff","seed":"657dd9b458793c826944ce50c00f6cb97877543ed7dd6b113cafaa7971704a8ca5896fdb5118aa09c52cfd6b28bced9446a86258cf432339549e31b3abe0e279","priv":"46c0e6f6d527a46ca2b44d0d6892dc6c816bc0f1240b10ab2ec9f33050e35666","pub":"02cc668b114070164065c79990db45a64e4bcc5052ab0ca65a68f0dd3c107e6937","addr":"0da2a43e892435f79c2af7d62cc823adfce0355d"},{"mnemonic":"camera casino cigar harvest prepare satoshi close ill suffer chest parent urban","master":"1f6eeefc59b6600a739364a6606ac2c3a151b4234f1edb6efb97da193865402a","seed":"44388f05937f5cb3b47893b75aaac78ae919e4aba29f0a6934ef15d6c7d1df6e5afe36743320856181b5f0a8bc02e731ca83fec8dd501c9c845a1f0325d74a20","priv":"e6492972ca82c2cd62006bafe541c0ad5c6bbb01257600e8168702de496f3e37","pub":"0219ea8444509e7894ca706edc46a522244acdd643bb09d3856db3d9fb1c9821f2","addr":"593e42b50fa2f4491c831039dc1b4489333531c8"},{"mnemonic":"staff myth increase tornado student scare awesome fever promote vault write head","master":"5ce65c1772d4ee171fc0e843ee7b6124868d25c2cee48eddc73e92b805ea2239","seed":"88c5df5805a91765a045019ad39767a4aae836e8c0f53542c661934c3791f94b9d35cb9b1fdc865deaa6f749e583176ef9f114774fb0b59c0637d1e66dd6694c","priv":"066f7c65cb33bac2ef5229764c316ed587710752d456881dab54bae2b1ae55f5","pub":"0263f5acf96f7e6c02dcf040fa765db8fef8ff317400344cc92fa3f5d58f19755a","addr":"fc00f9a09aa3316fb67dba9e08c66d8569f055e9"},{"mnemonic":"during smooth snow please surprise winter measure weapon bench possible write night","master":"0af6eee6b3acb361f5743fec61f7377ca5db5e90a303484e7f877ade8da1e0ab","seed":"104ab4eb787b2da19876c47e1372bef860de1535ed30deec9887075d8f2c56213647f0e8d6b472b23f5ab6dcdc1f97cc32ed2842a0745589c196883967f2fbc4","priv":"0eab899fad8229fc681fb7c86d8102d4074c68e6503c644790125bee07669d76","pub":"02d13bfe147b234f2a81e3e2ae0e630f6be4bd56fe44dbf59b2336c689024b771a","addr":"86a7e52857c14411ec4bbb6c850822b7bfc8b7bb"},{"mnemonic":"equip clarify march rhythm supply attract dress sell clean galaxy index river","master":"aaa85635e77e16d5393f4ff5c73b5556b36ddea446f41c5cc3fd4cf6da2f324b","seed":"519f319e5c87b49923ef60a172b731c50da1da2eca2dc130ec732beaed5dcf161c2c4c98ec20b539259862456a7ab2c5f63d7789da98dbcf01ed23a3be7c00a7","priv":"b42830645c0a8c5fb59994bb7588e8fa80b6a3b95ec223d459a6652a9f3bd641","pub":"030b9ba15ca8ae95d10eeabb8b203b6253c0d08489c02dbe255221c42b16ecd043","addr":"5ac5bec20480d7b9007c9bd184d064ab6b1c9c75"},{"mnemonic":"session hint oppose damage illness metal combine bomb cargo unaware gun artwork","master":"d6322887157ca3852f75204cb3eb60ff3eec39450913387b03ac973f5ff09ab5","seed":"09900b51d5c10504fe63272bebdec8c2fde49190039916839b106af4b7340595cd5ce7a6be9866df28d0a40618b23d09a6f04bb963b05ff1b7e272bd0f0beced","priv":"3f012cc4faabdba2abbb64b9313d0a574b8b31c1d22f403f1d344d87ba3246c6","pub":"03b4a66483e09364f2fe2d905fe5d0d897ca32752df20ddbed0206e383bbd68810","addr":"0bc20be767a401a01d697c5afbf331fd7b3f4acb"},{"mnemonic":"gate credit cage whale razor come opera net speed dinosaur fold witness","master":"8e48eb7d42fbc6c30601ab45da446ae6c0ac2e9b709ac428fe1f98b5c8adcd28","seed":"be8b77e358eefcb5610dae1b36380d25983f1aa458c208e75a9117b507fed28975787d4531cf87edf366c29798a4a6d8b44d6dbd090da8eefbf6d97477b64ae7","priv":"24ef322d8877b9f5f352f8063498527685de6b7d056ca7776dfb1ee34c0696fa","pub":"03389070a550617f89cf46c1df8269a154bd4811ea7d519445ac1e7e3e3f9ecbf9","addr":"45d1a0a5f79d849577106135f7065782e972ade8"},{"mnemonic":"belt girl tribe accident machine library verb link host wire hurt elbow","master":"d604893e69b68ec73d7abee4d15303863ae766b867400eb6cc0c771e8eaa4a16","seed":"ed3677dc17838f9720d5c574a6c70e80c185a5df1faebf07c63bd64a3cbb43818f7337469d48142df4e6d0254fa73e8a8105c6306568bd8e3a1fffd2c9d85885","priv":"cc6d78f6b72176d96a3425cab46866499fc57a8de3a49d5b30030378982911b2","pub":"02926ca73be56e04b363daa330ad51514595250d48d8983ddf29f74749c1a81797","addr":"00cc426c8061740249ff1219b322f104f548449c"},{"mnemonic":"kitten monster sugar game word lion census purpose defy rich either distance","master":"d697b6319c74c77415f784aaa6b341a84ec08eaac0a3bbe094d5cef9be94f673","seed":"f6bcaac87b81723ee589e635ff4e5a5606cd28113eab6846f1e7d5bcad758c1af1c8c77c111ba1cb502f33418f1436220a0a080ee81c5dfd012be5dfb0bec360","priv":"cdc283846e3c5be0b327e48a83fc72d85521bf51321697f281333b6eb7fa83cf","pub":"03711ad3d292efc08729a820344bdd1efb9cd87b083d0ffd65b49f998d1808e520","addr":"71f678f7808db66273dcaf51fe4875fb4c47c80f"},{"mnemonic":"fury tackle glory welcome gauge rely spice profit east science nominee expose","master":"b04bb080a9192b41dd7ee5f9bbfca1396970ba10c54faa1b7a511950b2ac85f0","seed":"5c6ecf5aec8669d62d5db9e39a5c94167bdb21e9380f8efbfc967420a71e0be1d72553a2b84bac9b8fedcbfaca522b82b7b80b531615b9658379c236cc4bc5bc","priv":"05fd2f8cd2ea1cd0ddfe2acd25e498ebd81b902d51935ad3a094a2f2c9c62ca0","pub":"02c4730d47f1976f2a6412681ae6cceb97b899c4fc514b67b9070bf236e906bdde","addr":"bf3e06c5713080e1eb287d2eadf49de120aae6a6"},{"mnemonic":"young chaos must mom ginger hard symptom ethics father rain join time","master":"d9e68439465f5375de6e132498239c9d0f67f7caf16cede036da3fd1c3905522","seed":"4101de8d7cb1dc3176ac0781e94594a37b894ebccdb9d934bac104d7b22bf68d863fae68df1734ab10302025e53357b33ceec095e59505a1461aa751ca77e5b6","priv":"e8512f2fdfbf6bc1e125323de28f9f35931ab760bfbb2a736f750e3bf116bba1","pub":"032f51ba96193a6e24dffa85d748ed2b86e3677112b22da1d6dd5e2d98f987f0c5","addr":"7adb4deb8d4ee9aeca2dcc4dfcd5d1dffd3a2cf6"},{"mnemonic":"dash ridge ten when erode certain game rural canal race surface envelope","master":"fa20b9bbbaf883069a6a282b30870bbb02c8aeed3c611dd3c931bc217326c210","seed":"276e9b97f203454480976e1d86d4abca6072d05e4bd5b1eb972fba03cd6698afb7212e5fd77ff84bb8e68d45c641063550bc8ca208c00604f609a5770fd69b15","priv":"1d2ddfd078682c4d3950ec841b8555fb37981313d0ef69a7bff16c110171f71e","pub":"03f0d1613f82a900455d6834f6a02e037e3d6d7fbe08b1f3a278b2a5958bd13490","addr":"b190466866cfab3ed499547c88af1372f84ab61a"},{"mnemonic":"spend echo addict brave reason tell valve emotion gaze seminar entire game","master":"3d139abc58b8022d8fb2f0c622531874064a80fb4ad795b024226420f30f8ff5","seed":"d9d04d4207827877636a1a077468d2396dcba6ecd3398cdda7d4c15c1365518a5131fa12caef14a2b23702957b282903dfd96df19419e4ecdccf7c38487a0c9d","priv":"cd5f726ece1eb6653950ae42c9fecb053274b692ce6a58a5f630716fc9cc172b","pub":"03e79f46caaa31bca56700d91cb366428839fb09d027d13001ae723461e8575bb9","addr":"35c04d25b3205e9cdb5283b94f328792466ecbd8"},{"mnemonic":"moon argue laugh point unhappy long bridge water naive suffer curve duty","master":"72c22a1d7ffb152045afab5ab7831efd0910c0a4796b20fe9b741e2a06a0562b","seed":"c77d143d6a040b08fdd3a085c9fa097508137cd801e480752dfc55f776179dddf01199050893ca833af63f6595fd60b4d14944e31cdea626ee30777b681ebb66","priv":"af0e3a2476271d7a31943b44dbe9affb5c04690c2f9e1a559674f028a8cdb9ee","pub":"038beb9938f2f7bd600f8b0b48d85ddb9a09054534ccab4e5a3cfc36523dfbdf74","addr":"a7799a4603c1cad8209f6de4cff709ffb5ad240d"},{"mnemonic":"ceiling mother twice mask pelican glue such giggle session absent sauce copy","master":"1ab59e2748679caf59d4646f51b8cd7cf27c9e62160bce71a2da081d57e90a53","seed":"f46d771930a5a1f69674aa4459b0120ffc52f961d7f05a4f0629e580e3027e9112b81c582a74f92a63a0235d275be17a3ea24e8f3d86d2f460142cc3c075f352","priv":"2dbe6c2f1f3389a29271ed7ddb5d5b6aeb44cd3d762b9c5949f981f130c096d6","pub":"03da1b7e3745edb58ea53dda791d7dabd4945343d471f41b706ef2159f4895b234","addr":"14a8aeb393e7a99b53cc3661717b6a550f4ca30e"},{"mnemonic":"siege welcome clarify bread decline educate off snap canal north glance flash","master":"fd57e9d99aae7bddc7bf2218267db21683280d1c1fcbf1d0713d35c872dab359","seed":"d1771068274d476b8e3953ae1dbbac392157fe72dc01d9c1cb66936f26b07fe9de52c257f1f8a89ad5677a786a32b231bd4415a7fd6dc18ebc70d53c16d91f9d","priv":"8d0c094475b505e3c32bba7ad6817460ac20c5e75e4d5d0899d96fb2dbd8f43d","pub":"02bb4a1051ab8df516ba2b5bf42a99dc1a153127312a81f93b16121d2c60dbba1d","addr":"43b7281f4f0495f6ffcc129e1fe90cf772cdbb00"},{"mnemonic":"total wood brief sail top stomach phone silk soon flag beauty equip","master":"2d3e2c155708e47a76ee013fd8bd3d62b17b467fda08869fa5b474cd622c7e87","seed":"98cb2c832f8a2e59601b5dd85c1321251fa576f5e8b9ac250d18e45dfea7a492c80918d864f32d1fe0f3dc24fa1b8eec3cda8bfc526d4826e94a7ac352edc705","priv":"5de85e11fa81b39f6cbd36182259fa33ecbdcf309e2256a1ce91500a1c28fe91","pub":"02dc14302285378aa1783d6395e45b37aeec4456845bb90dd2436a15c601abf050","addr":"bfac65e04a1d9f0905e0d0cf87ba1531bb49bbf2"},{"mnemonic":"junior side human tomato army twin tired mammal sing evoke tape fall","master":"0a491a1d250b31b44ecf38584c65a733e785d65082d693847ebff6f7b749aadd","seed":"65c7faabd749b66f9f16b234142acf734fdeb1fe7c9170355471c008a3224c40c5c2b77aaf516f91fcf17e73f2905f021354561e56ccf08a9eedbe51ce8e8fc2","priv":"40dc50171a806155da73f7c5cab57f7db270e225c33eaf0665c0067e1d2f7743","pub":"037325819201f9eea88ab9e6856d2e60f02c41d3a07c09fb7161c42e05b9db824a","addr":"9acab1c1329b243a5959cf716dc5f29ff1a9bf33"},{"mnemonic":"fitness wire sun minimum novel scare response ten profit breeze equal wealth","master":"0292d1ccab93ad054ee3ff95f3fde715cbfb9b896894b56193003861f4403763","seed":"ab34f068d14286ef45af4f3339ac19d8704d84a3f4af2c00092f431f919252487759c7aa09511271336b3a40f446087e7a63d1cd5603244124fb746160ffe872","priv":"39ef24672e90e29f268369bdab5aaf09786efd7360b535bb4846035b12d1aa27","pub":"03db46d9a6c338affda7db18355bf17ddeb8519a59e193bfaeab4fa522b4124288","addr":"c3cad6c638d4f7d0a75dbbf9fb2d170f79f1cbf5"},{"mnemonic":"quit present half acoustic uphold buyer measure matrix uniform black play vacant","master":"6f8c86f53dab7d379e621daa00a25f6123d43ef2452ee6b7c20e43dff1379409","seed":"bee800b88e86d04b9e755c63669dd4d324c0640e1c121427cd08d1bff6897fe622320644844fd06bc16fd9bd7b981e473cca7cadebf7f50ebe3f917c0721b988","priv":"6070835567461840e0b216338b4a08313cbc3d8e805cd694f379fa0112975523","pub":"0296271e75096d7bfb7393b449318613c522774e7b2d4e0eed6161e80a9376bb91","addr":"3885ad9e25c274d8e461f4b685ec921aeea25b80"},{"mnemonic":"endless blade swallow bird faculty group rookie betray crucial amused gesture pupil","master":"f62cc9bd1a5d26309ff51867edac4548f63e30630afce94a59d94d77f6df34ee","seed":"f4100b5b36d63b0a87ffc828277c8eb2f0055d2ef500b402572d30cc23418960b831c84566153155d33eeb8ce65ac191882810a1281e87c7748bfb592103582d","priv":"14125462458c4e981d9dd963e67965ee70eaf50af376daefbd7f89a3d0cafffe","pub":"03b64c625b503104cb0f9e8de837c741c4952140348cc5d7d1ff379e35ae37df6c","addr":"64d529a1cc4a11c305c8ccbe24217f52be17a69e"},{"mnemonic":"evil lend heart deposit index prosper mammal vendor expose sun arrow school","master":"d3402de3b79b24291822c2737da8e7d760267adcbcff5af52fa7c33ecf199acf","seed":"5062aebf0c68ec4ae51b8f9bebe4cdca4a07989ed9a96888205a84d749ca90f8d9f2c992862f7fdb4bfc3fb2c590431c5fec415abd9a2d4ad04f7504b90a9081","priv":"ff7fa17496fad3c29afa98d427186cd43fa9aee71e2a81d2640fec87ebca2ffa","pub":"037eb47194d9fb1e61a65e399d06bc7044d02097a0e5939581fa9b15346eea38ea","addr":"8e9b1eef983e035d41aa63ec276454a075c52db7"},{"mnemonic":"various cruel sunny human sponsor seminar stumble romance hire cabin blast rich","master":"25f93ffcc752c263058f195fa4b2f80c46aaa057b399de9241c0fa8cc2d3ec8d","seed":"89ee6d4b8a328ec819556eb043c769007e332ec4ae0309c6b6f82ed70b725e9ac8d64cf1309ce0ebb0053ba515f6e42d1273ac4d0d1b9008b9375844a9e1ed24","priv":"4aa30facb3367721c7e39c3052e7221f0a926a00d00898699c483cbeff39e407","pub":"0312c6e2800eb33da7459a510d02e0fc5c91778c115c535eb429c2c5797675e2a4","addr":"9178f5d44bc450a7a1fc5746d17440670fa0eda8"},{"mnemonic":"kitten rabbit warrior replace dolphin banana aerobic arena elbow leisure slogan panel","master":"28b0f611c4fa230cb47936546853fb9f0e13bd6a4417b5bdfba9c85eb80313a5","seed":"88a78a4f882820e729d42557ec5db0984aad2aa40f535ea33416b0b89d25511fe4b6e37cefd23acf1ab65eed77b0c6ed2e453a2c7e8d509311cd802eb267200f","priv":"3af577c50bf57b6f44686af34c65b683c305496d1799c7c0d9febf9dacecade8","pub":"025901819b5c97d505616f3d1b5be0923564d5aa627885851bbef7b4efbac4f95f","addr":"9898d22edfe40425e714b877a5edf2d9ab5d7172"},{"mnemonic":"shy abstract heavy silly excite response figure math travel use cram dress","master":"558875deb7ade8202aa21350c8a98aa64d383c4b8dc6315a5bc149823398c8f4","seed":"256d310717830704992b0767827ce796310b3f561f5faa152b97c6bd9ca1bd10505049005da5026e64377dfc9b9a23b8f71b2dd8e4ae0644055a04b650a721b0","priv":"80c1b4808495fc23f7fba2bf9ff39a4b084371807c61ffa6f6b60cc863bc8fa8","pub":"02268631554db3ae0b7ac58b2437556d91aee4c2ff2eaac5e4a3fd6ca6d96eb960","addr":"e1b0517748e58df2cde812d45c251492008ae499"},{"mnemonic":"tag aisle unit arrow neck knee border plug order decrease corn hint","master":"67412592e636701931c0b0723451ee325db3b1b7d63938c83ab9b4aa5630be4a","seed":"590e676b0362db643cdac3687e67274fc7f67a2f3a6f274da3e8629893b1f474fc608eb2928fa2be01b9760807055ba11a7ca4b52a28b2f66e05bef7f09845f2","priv":"1cd36df351f7dd9b4746bee1d59c42f12b21ce84b9979e39b3b810ba95d13e56","pub":"0217468adf9b7f74d88b312883787e6baadeacf6436986573371bf19c359ce047c","addr":"a719fcec68779b1f0f92460e511739e3bc704be5"},{"mnemonic":"urge order bomb glance dream diamond cute quit napkin swarm oil taxi","master":"e7c99156c46a51a308bb59a9be9cec9d45a55a8f10908f2294b4a35734c24cdf","seed":"a31e29edd9da883053ab020e2058d482cf2a63b39a7d99f47a5972820820d5558f24bd4d86470f1b8f6d1707c059e2353c65ee69e0e2924376f84f36e77571e0","priv":"e204a5c8377829edb3a8866e028294d6fa34c6dcd76d10cbd857afe41f9f6852","pub":"03de7949f7a90aa8f21731e2979b684c6424537283dd15881f31541f14a0837042","addr":"fa9f6d11bf1fe75282dc336ac24f790086794ca7"},{"mnemonic":"drink combine way human stamp code chaos honey decline reopen candy stumble","master":"902a5423ff40734c67ef9ef008e63a232d1c0e30a8b8b75d911a5e5c31be5725","seed":"c5ee4ef92fab9bdfab9e859bd603a9b293509ae3b99427a3db2b3eccbeca2b2d717ff5d5ce010db0cafcf261e6addc949bc3c0187a121e303a0246125ffc249a","priv":"59e235dd2d86967f6690fe2171eee062c5e786ea6a0060069fab9dcdea5bdeb7","pub":"039c4adb01d0bffdd50a34a458ee58e19ed81d3f22f740af2414594e42447c9de8","addr":"3ae7c41fe8a65e527a850a44a5124597f5e43f6a"},{"mnemonic":"will confirm boy glare draft hospital grit marriage hen radio coach cram","master":"cd54fa9f850f2e90ead19946218c1c03ba1491d1273f6e2e59816e50ad80137c","seed":"1ac5cdf381522c28ff90b993d3a3612e0f91c08b89b11c83315ed78b7d89e7a342621344ed75a7bb1d996487da8b60ffaa75ba3ef504814636471fa017f76b06","priv":"c63babfa6c2e301910bb8cb846259aa0062e25ac8523dee9449354e9f44ab49f","pub":"0272029d11d7a1998e79230a4e82aceadfbe29925639f6ca6dffd237b7c12a6867","addr":"eba6a2907bdcd422c68a04093fc5127ef2945cc8"},{"mnemonic":"sister mouse nose morning remain bike wreck reject flee depend foam oil","master":"480c68e256ac710a18ff386a76b2a3ca50ab4214a23d2e162fd473f40e48ef88","seed":"267c7df08090f9d95d26c130af19c37adeffeb28644cb449a39fdc2038a5a87341fc9fb78a91c9d19b8a4843146e79e57cae43b9d1d65042cdbdc8f97c22f02b","priv":"d6d76df9474b038eb47c93ce8bdee2163ed5906475bfe0d40b31bc19a501a2a8","pub":"02a70a232ec5b59daa30873a4c11a65848d6d90b5775e488b87d8829a368fcd2e5","addr":"dfdf9f283fb3e21240437a994ccf124089782718"},{"mnemonic":"mule parent ritual earn swallow visit jelly give sun family scatter lend","master":"946aee32749e7c2527a9d7066df275f6a58b4d979778f565e87db434a4d82ec0","seed":"919d5d8ecbf7c9ee555aec51c4079aa8a97ba7c6207be27a119b307ecf5433e0e44856f21808b1a7c65ccd7efff25eaccb445cdd3d45c682aaea9d8358fd398b","priv":"1ff4a74557f15dc78cbe8c3fe3b760c23c19c2ffa12e6fcb0c452fac97a8c1c7","pub":"02b880cd243da4622fd4084c0779a34c117cac73c193de6691cfb9783c23a13751","addr":"5157180c3b43d1127614fcee3c772b45f438a73c"},{"mnemonic":"modify puppy army pull trust icon suit submit fade game near machine","master":"5b3dd4912f293341f8e62b55d25a7f01debaafa1a8ae6d7d9e34afce8d534c7b","seed":"e73259b5532099bfdcf2196ba80baa86886343ed2a5a7dc081128e5d94232b52d3914a8fb9ee17fcbbdd0d2199b2febc3ee1fb430729a376f8106439d45718e5","priv":"5a502a6484aa0eb1f7d963d950574d1cc348f3f64b1064f0d6ae8526293b9f89","pub":"0279d6b5231ca7729c450b055d51f350ef370544b83d10e80501ff5063961ec425","addr":"54a6fb03ba980c6efeb0e4e6eefa8d5c06f68dcb"},{"mnemonic":"beauty fetch title weird machine section remove better kind unfold candy risk","master":"b9c854671b0922cd1c27f876e2bb2748e74d7e5c56f71b7bb24e0f121c312c51","seed":"223ad6102e7f9e49ae732fd584519ebb6c139fd5cba630b78d2e5872be280c5c68eb6ed3b5b09fedfde8cbf6dcc1624f3380ae464f1d30c2abcd8d8ff5157e32","priv":"9c0c7cb874257ee95038077d5fb1be111685c5a54c7eae74da353fc2d476766f","pub":"02be94467d1120e497f16254d1840c431ef8254eb6a0b1f8b6e63b9f482ae37981","addr":"1c3965d6dce5a86ed7e176420079ebd2f475871d"},{"mnemonic":"broccoli ring road spell bubble slush cable column boil wisdom glide again","master":"dfe7a6221acf8d8f8b29530792efa9ac33b94e352f64ed3244062651596b0b1b","seed":"518f8503f16e8e5810486d1ea5b70951fe6f17427cec5e7f68584d7c7f101d6670ca755f81501146f3969f1869faf5efc6aa639ce47cf1d337367ff2d5336358","priv":"b7c6e7af8ffb55f9282d13c51736270a9b488191d8e9a4999a2735d5dc27ae5b","pub":"02ad6e0ed23179b9a12019db3afda6555fb3e088f2cb91eaf53db823bb284e7681","addr":"cc65b77303a5d3d42de2ef404a05ffd8b0fb73aa"},{"mnemonic":"settle jar tag talent bridge onion jazz vote certain job high lounge","master":"fd3df0c73c84b19831fb4fef9b6e8dc4eec01c845c38276f369ae73805cd5bbc","seed":"6944f80e097b237b7eb501d2d6d735c644c7d9fe0de4a1d12665b27a1567d3d619d41a9a469622c57f2e7998af4f8784dc1bf30b715b6c867d61902103ffefff","priv":"93d91fee61fa1f6704da23dbd1ee4b5513399d1d7ad52c3a632d5e3ad5884895","pub":"0331248b1a2b3ac8919c5654220fd21a10d2f700b2bca6a16d94ab25cbf60631e6","addr":"8219e185c9dc66aa8ecef927a93e884494251762"},{"mnemonic":"say unhappy visit follow rocket bind capable lunar age erupt depart leisure","master":"bb776f6f57d90a587b76509d6788c1afc54b7a35a06ace60d7d627605e462d9f","seed":"e20ebb6b86a37149a20e0f07a637de61af6d37f76d9bd84c210c8c06f92d23e167acd0098be525e943e2c4e7378903f8b4572190ba1c6b37ed40fc023b355e93","priv":"0bb90cdd4f552b15991e6e9c1546d78b4445af65e60d6cc850b7d4f64a6406d5","pub":"0342b5a2ce673cbf8610f049dba20a0c7d92091b7df857951aca402e56acda8a8b","addr":"1af59207b0004314be04dd208cc2154e06476ed4"},{"mnemonic":"next broccoli cluster someone just you crouch monitor crack tribe slim cloth","master":"87d2484fa935339252c8b0db3b4604f42e12a48ea00d22abccfc8a4ae9afd60d","seed":"abe12329f69d107732e3a3eb4ca9bd693b1ad465a042530031d0cf79e4d32a42185677250418809d0cf7fe7e47e0deeb76c321a8b82060666d36f8a79d68d2b1","priv":"9fe6b21141e8035d253dd8fb9e6009ef14f1680f58629dc880b50fec649bc00e","pub":"03af8824d512a243566015275cc2a8af24fae7acc538fc292806c2c95688797e9b","addr":"02f4e6e52415b7e25431c0af1f43f7dce05b08b7"},{"mnemonic":"scorpion artist craft enough whale maze copy ring search sugar rhythm few","master":"8700b2c3bc7b441f6600850cdfa90caa3fa8e4aa6c3091d9850c42644187c053","seed":"cecd5fef8caf60fdbab007cdcfa920867c0057d0c65b55ee569577427914e0e3227ab8cea26f50c1596fcf79a88a521721257f43dd84ef1d81e2ae4a8b73ebae","priv":"8dbc85572bf9b0f91c5e4f1516f3056c6a3608fd78e6b37e7dad0a0a5e4f6c2b","pub":"02947910def2d55c81ae2fc03727c62440713b0cfbaa854f3c7e9cc5b8f245a2fa","addr":"a77f593385c2e978d99f0a6a3d06e427f01f1c7a"},{"mnemonic":"improve help region hamster then raven eyebrow account sleep cliff copy pull","master":"e479782ba4b1b4f605fe2e892c6449a91a03aa8438668e22df211275a5644c92","seed":"bd788f3b47b98d37521fb5d51e35d42489fb83e21dc4f0fcf55dde6c2e1b561b63ef22f51cff2c190ef8b18cfab7af33df8e83d14d066c6b114404ba809869c5","priv":"a2deecc348654217762f8e5a422b3ab8aeb07738a75c49de9a1aea6ec767f4a6","pub":"025ee0f9bd4bfb85e42f918e91294fd9023831385e73ed06aabbbb7234d1707def","addr":"b80dcc0683ebdefb2a30398b0ba1478de6742050"},{"mnemonic":"bronze fix prison velvet sponsor woman vessel crunch forward sunset similar wedding","master":"608887c856e38875ce02b2bb7e6899698cd395db9ca1e19ebdaa0dea5dbdc1c9","seed":"66a0fc313ac72cc89a85bd4144b69061ca1e65f7d1968824f0cffe10b10380278428af5b6a114d6ce2a88e3626dfae38133acb02096d0b6ba7b9761a8c4a0eac","priv":"47de7838c74b2b1ceb138fdf96d455deaf8fc2550246a479ff98aa706b1d0211","pub":"02202887af1c72f72abe2754b6022a163b2abe5ee9f288126dfd1756067b03b5d7","addr":"8756dc9521ce9ad5e22d2612657e552ced185071"},{"mnemonic":"fame answer typical zoo key rail state chest snap stove urge collect","master":"377482e11aac4f724c3a1f94c8abef488f1807e3ad1a92821d7dc3f6377a2c3d","seed":"ffb372372dce3d525e923ba290828f0b5d12f65b241d5e3c36d431c1e1b3ef737f666abffc9d7e60aac3645a8f385262f2295b05c42a4c2f8bcbc3aba9edf5a7","priv":"bad40853765d64cdc729c94aa5e114fae54ee014517dc5231be8321bf7d93266","pub":"025d64a940c5b4dabfe233b552e43239b69dba164eac66b48a36f4e06915539575","addr":"f67fd1e04384628a47a01b88c399b30df8e5b226"},{"mnemonic":"expand machine nice damage roast slam example resemble tonight enforce salon benefit","master":"2e9614bf212cb179d91a224f1cc36fd4dc0a5be13eb5ef3ec614cd3f5db5be18","seed":"97c59f53a69f280fcf28110dd05e7510fad1d00e26d30847bd1dda99324ea2efa8550e5504f2f2648fef7f9c03ec58b2666b94269b3b5bbef1577f17af22ad1a","priv":"bf9962586c0636a20519a0eac17870ed0fd09862d4ebb563b3f445a179873551","pub":"027eb40d841c1028b75f21d593300ef57c3defdc258d17511ab086988240619261","addr":"d107cca0ed49d72efdde4b9369855d188f37a65c"},{"mnemonic":"debris scatter erosion dad quantum almost zone off bless tuition garden phone","master":"706940b567f1ab966a5f2db1d3e5eb832cb827a89c5bf2d17e9f3628ac6f88ba","seed":"b5caeb5e19c5c3e1e7a1074c3e4f366431d57f5ee5208aad341020fd33b2be38c2a228201949b6812513b28523cd82594a2db67177bbee6472bc5528a0fc01eb","priv":"671bab5dd3060bc813898cbc63828aacf50819c35981793fa9dcbe26c3427dc4","pub":"0283689f70b17659b51e2a5f345ae95e3a23fcc69941a5a842e992167dd958ab7f","addr":"aabde1e66f358e648444fa2a10be7404f1d6094d"},{"mnemonic":"expand fence want silk inhale web seed slide pitch fever online puppy","master":"2f3f69643bf40d05ff4be223ad7f05f0942166b629b6bddfe022df650b52a235","seed":"025608f609aa5f6662cea4323e4a4fdb4115f68d8cfd903886fbd561bf9bea74ec1e0c5d871ba46a1821efc769b2447c6b8d8f242fa0dfec8546c814f97cd120","priv":"26b51c99c8bbd84af26776dfb9a244114a66929f24fb782f195267fa7b35bf89","pub":"028d38aafd513f554facaf66eca4907f01ca8c48807898b6e142b609e93c3521cd","addr":"f5d92901099f9516401d963a0693f4d173cc2138"},{"mnemonic":"pudding mean volcano leader grain general because hockey mask solve frozen canoe","master":"a7a2aa088f904b0c44d1a307a17be86554cd9862f47b08f58205a7f73f93a011","seed":"d4e9852e81f9162b8fd6d1ce152e96a52b70d59289cbb8382cdb7bcbea5e5c5b6649aa6a2d138343092886474816c8e9c9f8c198d77986d2d6f7e1476f1499a1","priv":"31535d53290a02faa05f04894d6381b6edddcb7a374b19f8940d4581f9582cbd","pub":"03f89337767bf64bdbbca6a53184c126fca28f7613f2668a77119f62124f52a0b4","addr":"bd54d3175097fc470fefd66c7e5e63e7aa28cb70"},{"mnemonic":"make protect hungry coach tail female capable swamp alley one nerve nothing","master":"f171811d3c1b278279379244f480bdc165f5b645521749b2bf7b5bccb91e64a6","seed":"f8ebb80407da43b794542cca52f76b053175290dbc00751dfcb4539101bcf34369f76f686642c46728aef4b93f91f5ed2896b2a102aa3cd45e54591fcd0dd919","priv":"ea4ce4340c0eb740a33fec8198c22ec0064d72dcd74f85405f6d7efd1b1d0538","pub":"03e06202e127c7996f8367bcd0b28f779207cf222ec3d5d4190abae78fee5ed57f","addr":"54ef6f20caf4216e10a99ed446c842e3e9d90ed8"},{"mnemonic":"royal behave retreat force stable law cabbage home episode valid similar amused","master":"b2d8324833e37ae4f1a9a70dc33d070223b6313fe60e31a0c240b03f644ab78d","seed":"2448c418181d981fa42c83da24dab9d73b6a89dc0f86f624992ec9a982ab04e84989ff541364f4a289969d458364eabb08d51249c55ea16c29c6b65b5b95d7c7","priv":"473b6c4e1192b52fda7e64be4c34ee0197604c0e4c918d2a61e98f35fbd3f9e0","pub":"023bdc3332835493ed0258dad839d90c8503cd7a2a7097893170c6498dfaa2d534","addr":"d77a39d95f6204755a78c3b996c7bd84d1dc6a42"},{"mnemonic":"major dentist hurt task dinner exhibit soccer disease bullet knife hair nest","master":"9202dd767a3b88e0162803d54fb1e251ae14be8337829efc06770dc83aefa0b6","seed":"4c2d56edca18105e69c59b683d76034ad69006170a595c5648c84237ef0568862d5f95873334769be9937b83c5c96881569f232dec873e3947bdeca6831b1952","priv":"4db6777136a67a8279bfca36d0531a93bdc9e6eac4496fc20acbf78268e35d69","pub":"02d35a568677e4be8cfa051c190e54af69f8124daf5f39a288bd7a0baf1b1b4b13","addr":"6b8223d76a499b98d5bed2b146768569eaa7ab64"},{"mnemonic":"ketchup urban occur rain cheese bean need august autumn clarify kite chuckle","master":"8aa0b8b32ccae13b15ea9c808fe3b1185026c52a9f3b39546b355aab66d7f9a3","seed":"c910c33e6e009667a3d64ef0d10ce9738ff80aef32326d27b8f82bee0abf523e2940fc9ac6dcc27d10ed602fb60a99409688142fc18a9fab36fdadb6d76f1693","priv":"4541e2bafa8a4b3fcd1c9b49f646b3950292e0e18c2f1accda6d53197aca0d02","pub":"0336ad51e9c8d24af44291449aa86d4cb798181b2bdc9a2a6ae6a7c857a5d72c4e","addr":"9a37a2e3c1b6763a580a3b2aea95cf237bddbc68"},{"mnemonic":"shift anger harbor behave icon boring zebra sketch kid human nature learn","master":"139db45e5f8cbe86f23952b1558df3924dc55126119abcab216158285a645f61","seed":"1cea92117d8c90c554ae3ff517be704caa5c0d16369f68eb061154d59fb42ab9a6ea7ddcc62325df2c63ba0349e73a50c291edc1ceb0184012b71f2f5892a4c2","priv":"638fc2fa201498e1d76356acb3f4c5b42984c2c4b8619b0138426183daffb725","pub":"02272782c09fd51800d483c408ade4509c5cb83573293b873663a8492c1bf6711a","addr":"020ca2c2be203a95c0f7eade606d9c27bb572835"},{"mnemonic":"chat grain wisdom yellow rough swap notice range collect maple actor genre","master":"57363529ed9e9ae59ed408390581f344a7b974d130b23fc1da952983604e2cc1","seed":"d4b51a786cceb10f907941092f90be911c3ae2eca9924dade05aec85218baadc6b8137e86f4723b7500189529bc1da0b4cc95d1a8553cada6beceb71b12acaae","priv":"6245ecb65a10ed313dfc02843ac8e848a17aa37f6c3aa64007ff0b54b591e37f","pub":"032c33f1bc81371f8e599fa64736879a9fab286469e271d2fcff6f97b1b4829e46","addr":"4e076a5900d2016f08628257ae798a102c007e94"},{"mnemonic":"feed good spring pulse avocado smile surface conduct copper chaos dizzy describe","master":"003488e5bb876fb831b5ced5126e5c85ff6f98203dce52820acb139e31859ad0","seed":"e71271c88e445005f5bab28a4ed6c9d46f41adc6b646870c4097252e6f01d51ff9720d2960c61b9839cf29b559232b086ee5694252bd1fd73d601a27d78f8787","priv":"7702987362194e4b70c66b9d0d330a383e2710cdf863e5bb472c31815b6d9fef","pub":"02b790b42543e16b9c30a23baa39da3ed1d48402e51920187eaaa6796767ebe49b","addr":"778d3554fd4323ae321b6fdd0d907d28a4f0a886"},{"mnemonic":"brick wool tired another milk slight owner year electric later because response","master":"473c6a0dade946ed832babc01cac18b33ed7ea2303a362dfe6671202448a1f2c","seed":"ef22617d9f1ed62a89eb986d9addcb33c98d3da5e205c4cdca8c9b99f6267e0122d86be0b5f60c46d8c9f4c569c3ebade2376b04901bf8bfee5de83f6f818a21","priv":"7df1f72c7a5cf60268e760c29246e6e43f8168ab56b9c83d23df9923a3e51380","pub":"03de70b3833b9be36c88018dc6296feed7ae17d995af5b6942a67bd39689129498","addr":"27f48112f2be720f42706681bf2e97de964a009b"},{"mnemonic":"poem rifle october destroy castle scrub yellow brown soon this share radio","master":"bd39615405d3b2c25d023095975e07405228878f004b48be0a32fba278394020","seed":"578f2a69d418ed6bbe4a7639c6094396821dff23d1c26ef1a5452e91164e142fdcea44d63ec2eaa8d491f7b3757147f371ddb624fe10d6f7bae502e062671100","priv":"7cb45a918ecb28afef642f002cb9627381e1986ad90cbbe54aebb118724f1417","pub":"02ad197d4780b28d57ca50799d59fcc6956b2bdb24c8e2c2a221d5cf65007d2536","addr":"09c81b6b7dc3d57d239ca18573720e063144916a"},{"mnemonic":"hedgehog october thunder frown game aware reflect art favorite elite urge pluck","master":"0f2082a1d92a2d2ebdd605f33eb470568c7d82d0edf73e2be8a766d060dd61ad","seed":"9ecc3fc4516a37cafa09405c306bf19f53b085088a1d1cb0c54bacab3f21d4edf4adcabfa616137753a427c2269d3b3334a952cc5f4531980034433e4bbeeb9b","priv":"d3e3e9ee84d32091f08d7fed384fe3ec0465ec460f90a2749c69883dde03ec65","pub":"03b5af12849876b7c9c0e64833ea79c5b58f4450e4d67a99a90b3c9ab4ec3c8155","addr":"c5a46d31f9e363e348dc4ce803d44a676ce811de"},{"mnemonic":"goddess wear mail poverty exist pole advice blouse famous inform wrist pelican","master":"8c09d67d7e8691a66d2271093d7f74f5eb37729df23bf98525874b6d600158b5","seed":"093b6596b4986195e484e2d8977c8bdc20529c58527d9181c4d4e993495bba922fad305d8d4f13d9b5ba20c5e375dde147fda5aea8accf634d522e488d163c88","priv":"b028bdd6652a16380c61134fcbb095d372e63a117421fbedc46754266edaf916","pub":"03bb1f45e672d983a8856631fa8193422820747e3f8706e55d761d94bf091d7d6b","addr":"528f7468425b08f67e90058778cfb1efc46182ee"},{"mnemonic":"increase debate symbol swap raccoon crucial noble benefit kingdom prosper proud medal","master":"eb8bfa1409b9b28fb7c826f884d7eebaa6203e7bccecbe133e55df59db2ab8f7","seed":"5521921a26a36b06e62a681e2accb9a1577b6342085e9c9498f5693ce6b79fae98b9addd2da01df263445b5e45d2b4db45621517f96b86cd43b0ed00f7797fe6","priv":"e1927e2b0769daec6f1e7af012f1a14d82d52b89bc83a2fb9d007be1a51be4bc","pub":"02d41fc73e6624af22813b072d72a1d31fd628a66ae61e5d191c374be419c4a586","addr":"cc942c7413a8ea0db73c6cd3e2bd4c5e46ceb395"},{"mnemonic":"upset dutch damage thing maple ramp marine leave audit ugly walnut cricket","master":"012beaf52cd1e042c96753770315d25444e18cc63446a4207d57c2287d6ae028","seed":"dccb359dc9c2c2000e0fc23e100ad2ffcaaa0ea9bc1cf036b14c9c1f1542a4f9747097994ec5dc4071ea6863c01b2a5bfd6f747faea05dae4c3fa03eb2716b14","priv":"391710f6f67f54bb50abc86e349b889d0163a4c91dd1727dd3a6ae0912defc72","pub":"02bdbcdee705f81a4764a8e62de4bf6cd26f748be843fa36acad975980c5ab1b58","addr":"888ac8af30c1372d6c243363f6a8d97d3874eb31"},{"mnemonic":"truck stairs excite range van electric alarm sing provide mesh jazz cloth","master":"ef39accf9d3261de50dc985194a30b1cea9a861e2e48bcb47b8ba4542f855259","seed":"088e1bc0f701ada0c35c77f0d17ac9cdc3fee9747410407a1748b2c7e72bb37f2a9382e1108bcbaac1fa2db5aba2bf34574f47fe46450aaffa7712e5fefdd40b","priv":"e2e2d2973b52c1da6098e31018dd238b02ecc1d22236e631ff9d3009572ab1e7","pub":"03bd54aaf28542c6d155ebd24c032dde7341005e69a5ef67c3de5138b546d9f462","addr":"246b883c299597436993fcc8b22d9154db9d8f1b"},{"mnemonic":"admit penalty clarify isolate decrease draw virtual rhythm trim brief vocal box","master":"4df1d8bb85983df8abb7af56086dd76becdde81861462889c5a0809a470125cd","seed":"4340047ec19d4f318596cbe510ec11f233193b72214487cf3e8dd90c11f1466004810ba433261962b9c412b22c98655b06421b3bfe73ecfb1bd95aae30142cef","priv":"965f07d8304bab4719f071d1e3b656165a70eb31fb57d5cbc27e03780416d304","pub":"02e3a67f5d360f10b1786406cd2f1dab2abbf606fa014502ac9a7bb56616597776","addr":"90c64758c772184848b10adb3f841f14c86096da"},{"mnemonic":"citizen fire fetch luxury tumble analyst pitch van sketch measure effort danger","master":"e3469c68ff7e9f4e68af468ae8be59df246ae039ab8ecbfafc9677d6fa0b3d5e","seed":"bcd37194bce09d9704926235b5c83323ca7a71ca1b3b257c5436267a1e14b7b37fb44ad7fae1b818189240e9006cbd920b36aeb204ac00f98faf9e1b1c2b7034","priv":"d3f4ebbfa161b543e250ef0b52c027e49548d5a36a8bcbd8cecfddf0f1ecf92d","pub":"038f2c2418d2a68827be4807f06bc9c59ca291b34ee5284e860abdeff90197d40a","addr":"a44c929bb81a0dffc76e8659a44f85873578c005"},{"mnemonic":"claw stage toy keep crisp memory donor surround bullet ball innocent faint","master":"1025d24a465ea26b933c44575b283057daa066d0f5f0e12c9411ebaaec29d26d","seed":"effa6fca939b041aeb89550f138159090189a0ab3c96fb9151bd3ef69d29b457946695331804e437353d134f0d069b28092f122f35e74eda8a2d643c3d1a0f31","priv":"a81c4bc682ef7609d7ea9bd370703eef25006557166b9e04e589083bd7cd65c9","pub":"03b4d5e3abb4a6eff0409b20d8a1a88088e591a6ac3e6e25fb66f172303f55e590","addr":"09e4b6c1a32ed0e0b671e0f241708806a46fcd71"},{"mnemonic":"rigid neither track gesture effort venue vital bike spend unlock special swallow","master":"33fb6294ade198c457bb6d318b2eae4d2757977af36b988e042f1c3bb42afb7b","seed":"58b191573da33c6a4d94b8589fb32f8c5af326f5f40ced92973c352295e71d7a07251c8b42355b0360fe3ee2cc42b3217fedb914ebc61faae8a0f8cfe579fa98","priv":"db272ba1dc7e8aef500e29a4acf750dcc6fd92e4ee2f2e4235e7f82034a1857a","pub":"039d506e2c16bffda4127fc073a6e1b43b2c0ec68cd6d86bcffe0f0c4663b5033d","addr":"5485fe5a806040bf059c1e0d90822b49d14a51f2"},{"mnemonic":"sun ankle door drift sustain task home retreat light say wash pretty","master":"b0a3d174b40c1f2a723e3a62f878c1d4d19673c632a19fbea460d4d0c35d14a1","seed":"63e84de6c5f71a4b9dd5fe8998f00d0f8d0f2bf6da159d82bc8fe30475d0e909a939d7498936ac183e8220acdf104aa5dd0f721e65eb06e99baa27e81314a2b2","priv":"7e2d6c88823a8effdb5266c8fe605968ce231150169e0174e140643780bbbfa8","pub":"0280cf1699ed3cd91a57b8520c1173c8e8ff036cf759d4e076df031115c67e62f7","addr":"c31630d1643d32f652d89791c5de3a4b671773a8"},{"mnemonic":"ball symptom nephew dwarf mango crowd radio shift degree envelope science job","master":"449cdf41d10e6dc749aa52839a9c23a74c41cc7be85bd76f7ef0ea8347a76db1","seed":"303c978ed8b692e5e334bd1dfd0ece8615af24457129e7127e2ddc69f783628cc48d0d31ab522ffb6e70761dafb755f5878b45579911d52157c37c1b14b900f5","priv":"f6d3c4eaf86ce6c5c8d640edd4a67ff7dc852b6fcf36918f2b9e36df710b2049","pub":"03b3e239dc04042dc6c83a8ee1973917cacbe375786c73aae74a6b46cddba54773","addr":"f0eabf6d01285343afba4b7005268ed3af052d90"},{"mnemonic":"cactus trend empower board street stick animal replace media unveil recall proud","master":"17b6f0a996810ab534ac366a8890f7618f372ce0168b00655740f9698cb8595e","seed":"6ecace1a5b927d3058a08f1a59bb8943d214ce5e48e3627be8995814640782de30243f5a552e5600a3e1c62b3a46edb558434ac56bd105e4fe23aef8a1c0e4f5","priv":"69bbc1e6bf17262180141a0b00e6c410c3ae5b10b69b20d6321aa3893250cd6b","pub":"03bdb7edf4aa8f5e6bcc5bfd6a898671b0fd687bdfd9f8101f4ca840a183ffb226","addr":"c5fbfa7d4e0356d88cbb5e5bf47a67e877c1b1a6"},{"mnemonic":"source note bundle laptop pistol business only dust bright govern illegal melt","master":"9e7f939a19be0a48a3d9a740d8082b968fbd41065b1f5d425cd20d73d327097a","seed":"22b6512400907bd050684ea2ef220c4bcfd35d59de2c4c5dc3527436c417b68cec186640ea1be40dd00ff27b13884cfe7146c84ee3d568b8f74272f0edd91574","priv":"3f378db3f2004722591ed742f0d802e6c4eb51f357c1d77180a777d083b2ce35","pub":"034c05a98ebddfc54812ccfb797a99f3cc09e525759b7f40746513d58ce8577177","addr":"d920c5b1bd40b6a0c3835e5d32a8c8d5897a18ee"},{"mnemonic":"pride mobile flock dash second emerge seminar theme lift motor pave grant","master":"786638e9248b3b0ed4196533a1a437e1db7c2a83908c71823e9a8cf720b71975","seed":"59bf3e8f03faf407d8427e9b53093cb3708192de0c979c2e0a572a905af89d5ac43c2d1c2480cad58c63e3dcab5b5679a4bc12a4341cc618836d15f27c2ca623","priv":"f7ca55ff74a0f55f08cd8efc515ecc69d9cc6bf2ca389a3116f906d641de1f2b","pub":"03f538159091c45fadcfe4ada5d2a1a3827206bb4fcb756b9573ddd381b39621fc","addr":"14a320df092aafa1fd0a5207b6408daf532072c5"},{"mnemonic":"obvious man chaos alarm symbol novel accident label engine noodle inject empty","master":"df206751043497e26c74fde7e068376bdc0771e3e06a1e8bf17209bb9e1880b5","seed":"2178b8972916ccef4040e2c50cc9dc039a6ef7f799f2287d8d16aae3edb6ec0a20bcc4798299d2569cf6183d7aedcb426dbfca1971aaa31dc4c202f40c906a55","priv":"ab864f86be6e1970df2fa336835986e60fb7040a2edf05a196eed2ea9ff42627","pub":"02c8db2bfc3c7377ba3ec38f9aa7bb1be7d70ed6b15d76d3d5980f983552ef6fdb","addr":"c595c4572aa4b833719c935db03afefc56c0edc2"},{"mnemonic":"client increase remember size asthma source evil venture column truly risk clerk","master":"a0f072fd237c6f30dfa920940d04657e19ebe30e3fee5b0ad8e63cfbb6256d86","seed":"0ecdd247615ecba50ea404632b4f8e2a6babe067cf7d395b038935bcab013f0581c69f2d07ad330cb6c7660feafe8b21af062b474dba193f1121a65f83b71b99","priv":"4b54d2f02c43241d7a45efacbd48616f6293629fc0b539487865dbef626c6789","pub":"0336182aab2751a5399b14e0ac2a856143cc679405fa55141606ec3b2a09c3b1dc","addr":"48d6d2441e609507ca554f8f79e4340f257565b6"},{"mnemonic":"latin scrub oval capable drink common hundred ghost candy avocado stool dial","master":"3c8762364ca16a70f174584f1f276df1c432e0b315e08cffb0d5874760e8c587","seed":"40991c77f0fe52213ce2ccf4844ee6587b3595aee4e79f34cb00ccd0813f78f0955ab8e40a5f369522f7ae3ba0290db31fa1b224baa6ec5a91b57da8ad2b479c","priv":"5242eb4e71fc9715ce4ebf1ce08fd81d362fec9dad2be045ee0d6687e1662df3","pub":"02344bbc8ab1313883fee563a175cf98ba192e8570963fb7c99bd0dbf6368fb656","addr":"3db9be587f22ebc28ae33b4cfa2c86124f450440"},{"mnemonic":"envelope van become minimum electric inflict denial thrive congress visa jelly play","master":"e94c4ca894e31aa091dfb2ef9d7733fbc67f1dd45e5db389a037816e4ae9ebef","seed":"01a5a19c5f75ec7ebc2cf0d96b7665c4c79efa0cd0b7dd5e21c2cf8954f5dc5b1d23daae831a6e18e5157c200a603fb2ffc87a068d1dbfae1d4443eee76a1f53","priv":"7ed896005729b7de5c6c56a19d37f5352ad6329e948c256f11eeefbe8c0c7dd3","pub":"02025c5b38b96fa00b506cb0360cadcfade6aebc6eddddb71dd6efad382b0f6b3f","addr":"1b7ef9f1184a0da787b1231a0f44eaf4035c3ab7"},{"mnemonic":"grocery embody emotion wisdom picnic modify opera lounge impose junk scare ocean","master":"8091cf08fd5f50de4ef91fbd4b9eaaafe8f1c17624fec94cd47de656ce0444ab","seed":"ced20dfaf3a902a7b71ae459781e5013d215567a617a2300b49dac4d3473e624885ba0ca8df6f13c91dfaa9829aaf06c4ffa9ea22f6f17826a1629a597519507","priv":"f0ace0c797c7f3a93bf1f9ae64dae516fad77d18b11c0e32b5d0781d7a231f02","pub":"023c38bcace9489242f81a74168475ec29531c58523f476bd068195d18044f80ce","addr":"859850c7548250084438b5829b4255ebf5fdbe6f"},{"mnemonic":"oil inch eight notice okay gravity dynamic trend neglect bag earth predict","master":"b235b5a4e01f0e64d68b063f09a878c8b0785b84c5106431f4bf9250874e9d38","seed":"96096619deaf5792701545255d539f120ebc2ab8c23ee72de058874b6413e51774b7c2b61be1b1197784fb63fa7e509374449d4c812e7c8f8bccc711c73ba703","priv":"7d05f98b2cfedcb506c68d426b298b3450445a1ac89a3407f4ccef1ddff8def2","pub":"02274745f11a0de371600f1ab8b5ddfab91372f044ceda14c968b7e5bd953dad37","addr":"098fced832e9cc1c962630fdd2dbfe262728f018"},{"mnemonic":"oyster ceiling cupboard quit today neutral repair genius pretty gospel dress rent","master":"e669becb784c8ef38698f778cd27cf0b55ebebb5828531b65330d51ea2b1acc4","seed":"af8030276cfdf70d129e66467a09321eccd9505f662865a4776e1f5a05ba1ff6614b37b54583333e3ce931c9d44378f636efddf5189284f5fa566d8db01801b8","priv":"c0ddfbada817464834555e46ed3eb3d50c1023454f42826463e71754b9e6051d","pub":"039f5c467338b3cf8ac16e6677c8242a67caf89f1201f0f22d99ca1e7beb9f1a32","addr":"53ff0701507c25f97dd1e6721b218184dea2e3d7"},{"mnemonic":"utility angle dynamic fire canal current level trash chase praise witness mix","master":"54ef0de540f88777efd1f3c37929789620eff87bbf98e61f9b2e0783d75450cd","seed":"8701d5ac962b68ab8c34092b44210cb9c11c2332515815223537fcab99909b44a0394436074e60285c7b306917c1165a2f281bca911798b23832723abaf18971","priv":"bb5602644b1af5bfa019f2f81ed0d2475c1492661e55c8e65d8d5e94ddb33e52","pub":"0314f5e5d817f2235e3a6fdc0e86f231825165777c69314de63bd88738e7e7ab46","addr":"6d596a5fa6b1818895ee6fac51915a489da283a0"},{"mnemonic":"cabbage sauce filter identify potato coil bicycle river observe candy maid monster","master":"4e834379e58e480e0cde698f9f8e47b3b6e698b4c1325a995504980e43689706","seed":"a441b8fed73bf3bf06a27aaac9355a79d645f0c7d5d47ec216a8d42d6edf97e6a00dc48de9d9ad9d52279ef26f34990581caaba9d15342935ffe857c10456382","priv":"40e878ef025dc6424f2822e38c7a906b3bc39940e8db21262e6bb99ef3de98a2","pub":"030e41c103c49d2e54e5ba6e40b1dbf04245dca7da0738ce9048d0391da872365c","addr":"9d4e8dd66b5e16369be39000c11b03fd5054e0f8"},{"mnemonic":"method surge demand ginger pigeon chunk because wisdom stadium salad excite stool","master":"b8f4a0a87e42871e927b2d5775fd212dbf911351c525b92f5742ba15650aba63","seed":"3844f4bc75163d8dc39c53cdd0e9ff31182a87ad2d630286897b35b39a14afc5e5ecd3be988c9754ef31f14452290ff04c84faddfd195a3eaea076e48bde47c5","priv":"db0bbe779583c012d78f9b60d6648b72d699c2e6b5e9eb90d293852553649a91","pub":"02bb40395d5649363ec19874f2fdc122bddd8748234c1c1ccb37e38468c6e9da4b","addr":"bf52f59b2e4f01f23b32f502acddf96bda564682"},{"mnemonic":"stuff extra hair depth angry jar drastic jazz diet olympic choose legal","master":"5e09c2cd0aa6504c4a2e8bea31e0dcb7f0b82f040567b681271493e35ca910ec","seed":"ce48715916de04f9c4f9a84e4ca25900ca0292de3a7217a42497c0bab2253693ee8b5425c40ffb2700a65a772e98989a2adf4329a93596a4d6695f6c7f236e64","priv":"7f06d4f3cac1e4b2a7e0951a55f78ba85aa6e6b03872dbc0c930086b8e8e25c4","pub":"02b4ba9df766e58ae8a5164b4be48159901f421b8e8f396593b66dd3a715f268c6","addr":"fb2f7eb9e28187a7b68b02b7f55b5dd46fe9ff49"},{"mnemonic":"leader now arrow loan eternal prosper myself lift limit capital aim twist","master":"8a4e78bbca5c37b40feccb9bf053a24ffd6d0809fc2b89440d467bc903c03244","seed":"25f1e8641b6095446cb3d7f94bdd7d81d92d78ef808b18c48050985564543efcb99f28b94e2e935d31d973a848666b95bd183e05860bd0950cdf0ee8d7e04da7","priv":"a2fdc724702230d79b0c564c34fc4e68e8a10c336ca053278cb3be419313e07e","pub":"02e2206433e526fd4fff47b58ef262eb558e79870c0aad14290d40e912ecde8213","addr":"3389c22a794df27093c11dc80b6096dc3ed0d175"},{"mnemonic":"increase month viable teach spice pause safe mouse allow sound access grace","master":"919d9b6fd918d7f223acf91ea5abd831a1a3568d60fd2e43f0d97e000ec77f47","seed":"f4cec05a2bf6a00a7079213c03c36d765e49914166f497d6e38a522b1cb43fe24260008dbbad00a10d998f7963dc8dc09fd539570f157c9bcff8248987d38626","priv":"f145657458462f994a4e972cbd42ef4de683da47b5b6f95b14ea417b39e702ab","pub":"0289419eb27318299a51fce4f0e65827d0426d72543fa2f05d0c5f21bef86effbe","addr":"cbe298ebb714dd583e7c4d541ec9b38c56710226"},{"mnemonic":"move catch empty dilemma only online ginger rapid enough turtle flee retire","master":"39d365c85d0ae4f31811c665217b718bdb90a4f2d328587bdf430ff622d019e9","seed":"402b62437898f76f87f3e080c3f33f1c6b8ebbb848efd467ee4ae5d3303768c57a15fb1cb03a6befe486b0c02733c5c892ed9878ea7713cefc58b7a616d7b286","priv":"7e54e61877b8413d06251167b239c1a6213ed23e79c1ec8da7408e23928d47a7","pub":"03d25f230b5d12ba142046fa80c23e51198987254bf7f7f8a009b712783d9311aa","addr":"173f71a2ef94e964185c474eb310a363241c4621"},{"mnemonic":"audit energy setup mimic dilemma amateur evoke tornado machine artefact shed artwork","master":"40bd9cc381e632be6f25b822bb13bc7cb5c9697b09d7ec725aa21e13cd3eeff3","seed":"721de71ee0b84620bff8de317edf7c4ddadcc2dfbf19f3cbbbf61bf26157fa07c46119cd2f8387d67fb0cfbb98f62a3e711053a308496a0ccddb944ce517d90b","priv":"825587d79a33405a3c7cbb8a25e89cf1b3975de3763e1928e02c3f4d0cc8d44e","pub":"036ea297692c5bbd85ac06ca8ca238bad6db83d1acdd02fc012325a1369df4447d","addr":"5e45e1683f7c34754771340ecd629014297b72ba"},{"mnemonic":"vast noodle pumpkin sugar time iron screen begin among orphan glimpse deal","master":"cdd2cba6ca23c96a7967d3f3f5739009f2fca93bda0a36b85bf7a1cbe0d8af8b","seed":"6288319e4e2c7471be7088e9b50db78741c1da423892da1bfa289bbc62d4a014a6d9b7cce396cc3471531342a61d92345a3b043224dd271ba46cd716a0952af4","priv":"1a746edca60363f3bb4118e316ac549be3161be28c4151c9fb9cd63728abede8","pub":"03cfc321906acb176564624dbf1855f00188f563b14f07075b1d5c7e4dc25b4419","addr":"5007b4c128af77e8dfcdd85a925044438def1159"},{"mnemonic":"evidence element stereo crunch romance trigger ten ceiling cigar wire first frame","master":"9aa2f09eb1b6094ab52e39873444f04cc682e60305e9fd4f485af27d7c6e85b8","seed":"9adc22bf0cd328df0f54c6da7f5e43e9abb4e9a607aa02c312de24c12d9a18f38945da0188834e149aaa5798c8594fbaa9109d47363deb53e17553f2edfcdd2c","priv":"281e729635633787eb40b08d43d806a5e2ce178abe659b37c91c6c073e720448","pub":"039e0c68668af7289a7ab4c7f9b24347f10d38eca095e991207cd9ee55bf61f0b3","addr":"49b25bd31b9fed1ca7e2c32d663bd414d6c3705c"},{"mnemonic":"labor capable alpha robust suit trial media bottom pig hundred verb hotel","master":"b3e4d6c6bd059bc9548d33f6ce0aa6674160ce38bb231fe354950aab11651403","seed":"e1470e44481471f4c947976fdd4fc9119619571cbcc90d0d419f4a8c4e1102f84246da2c2fd0c02bccb34a23708cb6bfc25b23972c03fb6a70f5018ecc5dd2dd","priv":"9a9501487f7f9844ba521b3ef949b12b30046e927bbd8054c831434a4309131f","pub":"02b25d30e144102b7500d57f3c501093f4dc8708afa3ee2c9d772d5672802bb9fc","addr":"49bc8135a631475412f0b4bb0db7d437ef226243"},{"mnemonic":"resource pencil other subway knee small shield excite forget such annual opera","master":"955fe71377d76742d6bd94d49457e6cb768e57517ed010648edc058f65d6ccd5","seed":"28a7a3080633dd2485cd88b9c1ffe92889eda9d33be7fab5b8aae3448170282decf20587d613cf06f0d3a7da6acaeaf5c9918616b4d22f8f23bb6a17c67bbde8","priv":"ddc57bfd0d27c98a1d088272ab481407ba95f8556f5603245466f4f2d83c3168","pub":"03065ce8033103d6fb42bb382a59053797beb33214c9d16f2b4ec3634cddf857c3","addr":"b0ca9d5f91fa03b1350a40e4ffa84e99167e4115"},{"mnemonic":"dinner tone antique away vivid lesson frame floor rent field monkey race","master":"21cde220eeac2883dbddcc6927f73423af840e675eca3a6cc82bde4a5cfdfa39","seed":"16c220be97967c5fa8ff2176a9e0d748e0aad37c8ad32e590c5a35ba08247e2176369a3561c5eb0cc4ebc9a3d65bd132e31c2640968eea822ae76c6c34d17373","priv":"13366b2855dee63a394c4ba89378a35b75ee07853c78765dd1c5c8927e52d4a6","pub":"03cea336f71a793319bbb298bb3c546046e69b6abdec405302739345047cde8355","addr":"251a5f4b01da75091e938d782ab116e0e4ac37e0"},{"mnemonic":"peanut potato artist define merry brass nose brief dinosaur planet year blame","master":"87c46a1f19cc7d1e59887f445dab57aa835bf116db621e9df97c5f3090517e7a","seed":"f8b6aa2a59e9384933e0a88c76c5fa889ad82238ee7173a41a57638bbc82e51e7d89c97bf3cd2f6031e65edbeafdcad5760cc6d37a527eb0372b03281e7806ad","priv":"a7e43f8b98838fcc06ba6257cc64086fd1b6d0eef12eddf9910181bc6c0ff204","pub":"02db3f433577482b62d057404b61d9d10f25df66b0742c2a3471d2e80380355cda","addr":"571a2abcef5e4f690174e2d81d9ca035742e17af"},{"mnemonic":"orbit have attract dove horn liquid social maximum penalty slight mind mountain","master":"5da625838972cb1684ff4e45326b14bc1d73164f82527934fdf8b7a03b9891c2","seed":"abd2d4cdfc5b8330f58b7757efd4fece7854e461e22c71be5aadd735c144a954594fd8da84df6b8db3ea14c5db03a6952f093346ae94b6495a8635e72e4a38a1","priv":"cfb3afed34d7e53d459cad8623401f0d6071d77f70a96c75baa511179ca59cb3","pub":"02e9e07cdc8d57e321593919431bbb6c0761910d00e4a242ecd85d0b99e9ad3f36","addr":"78099b6cef2d14ad8f3ba7db73bdc9538e382464"},{"mnemonic":"squirrel motion glue best prevent fault goose evolve shoot decrease panic very","master":"2fc7364fc6537a38839ddc9dd41e0e58b2e972072e92ab8ec9fda67360f2a042","seed":"78718bcb53ba532316fc641cbe2027fdf4ea649563b36ae94a95415b5923397585b4d10b1026cfc8a00069cdc02c7ea8c1ebe04364bd68066195c748331104b4","priv":"7d9073ba21f72f91ca39d1f13c05108dc75b56d3b09bd08b7b8843137fba91b2","pub":"027827c957ea1c98f76a076bcb75ab8faf534dbed44beb234ebe9f60086fcee393","addr":"10440fcf67f5b2b6f4e81a0b6fe267941b437811"},{"mnemonic":"pudding tooth market kiss exhaust donor tattoo olympic uncle child bean nominee","master":"34f62b6ae3c01c03d64e0041bd50a9cf7027fbbc0b0fa7d13d08908422f72520","seed":"4f02768404f6da2b7654bef1e4d35a42386bcdbf167d09212194e20a93070030eb0a471945248a36a8d062e80130008cc16a001ecc3c05a27db0aab0f04c6411","priv":"1601556ec13ac16d4b38ee73080065756ef276473e7649c713ea1ce69367c087","pub":"03382cbb99d17b144dec0eaacaa33d1cd2041bd30cb658fb97ef2654e24addbaa4","addr":"e2bdf4c0101d49df4d8aa0782c4a1fc354fdbfaa"},{"mnemonic":"earn shock finish ritual segment post then note dizzy diesel jelly horn","master":"309b4a016a064d4bbbbc220c0fb0727114b0b83b600db73aeb4347a04ce52873","seed":"bee7d3de1274abc8fc05afc839de0b14166b86f1ea0a1f2a6b6484f40654aaa7cc4574a14996812974b905da6eed437576eb7f8fea704afdaca3b146b761f0b8","priv":"a269a409951e783c280a5d8afb0bc232f303618b4d83328ee1196cf86f2d8f04","pub":"03b79d6727f3d6a0c289f2efac4938caa94ee57a36a3ac70e3f8e5f85f7e4f52e3","addr":"8ed4fb10d7b8d66f4244e83d7e6cc89fc75460e3"},{"mnemonic":"venture bitter tongue learn whip palm race pioneer satoshi wait hub genre","master":"e01f7f5ad0a782b56a034b68230df72cc8440f6e4cad89ea0bb9b81a71b430bc","seed":"b6da5129dcad44188c654c9c50ea640543b66c0c98edb29099770eb6d89ff197f5e9fbb7725ada5b3b0191a9cea89ebd8f35698d5ffadcf3ec43f6b92795c1c3","priv":"e3ca3c8028c514ecacde54c73338ef5fc78e0ab05ed8260d4a70553c68390bfd","pub":"03913989f1771a015b6594b60de23017fb31b96a740507873cee53770684fdd5a6","addr":"db8e914a36120702031b4498a27d6bcb8c64da8b"},{"mnemonic":"opera wear actual virus pause topple leave video garden guard obey item","master":"8fa7f1c161b47e625e351c775c662eef0d5cf9d9c229a780ce0adee5fb045ccf","seed":"be4f43f613da6e02ae174310181afc71462ec108601eab58d369f452c4133d14bcbdd05462676d872b7aabae01e7e6e22ad38261ce70a2f107ff9f42ace4411e","priv":"506a98b80534c824f67233bd1840ea968886a57ab500fc0521da20fa4f24fce5","pub":"030a5a585b4db663ea11944e5be0bbb630b6b1f436989c8e7128784f7f59676370","addr":"5ad5c8981762b720bf74b376205f69a91c6f3cfb"},{"mnemonic":"captain tray course secret betray bag disagree boring umbrella liquid door memory","master":"b7974cd18de7e9e6abe2280e567fc51efaf3e80986c98901ae2ab7378eefa08c","seed":"3a3c2b0e7b237e37e7c92c12960ae5b8856a8a5795386d243c6d9eb7dae1b98170489c47b197a794e8f9ed0c323bbd722ca052f76d3cf1ac04db5f7ef6bf5b26","priv":"a13d741052c66e487ee376cd392096d9e56db28d64001cdd807427678b765016","pub":"02fac937135a8b0593c50d06a7b113c372d8c9d02a338673e3e07f267725536417","addr":"e5d9166b331f86253444f3198f707a495055b768"},{"mnemonic":"dizzy guard goddess trouble absorb pupil stove mirror nose couch diamond must","master":"d3bdf420266d0c5824a621cd631ba014a8ea614b85c628cf64bc3a77cd3be3da","seed":"485d06d18708ced0c344c4025d93cc194f7c63e2aaa1d19970a96db8dbd7e2f802d39d319338b0a92faffc2af408f69f1522481fbefdcbd3842ede957c663b0a","priv":"abebb4b15d2d9e3a3dac2afd4423d5da2b2260da49bc45d44d014e8d740ba064","pub":"02e39342b82a9b4bd73b165ea9b309e20b0cf9c8d5ee93756cf82ebad0b00e0cda","addr":"19f6fda807d1a0f99fb730923277d4f36601e2d5"},{"mnemonic":"rescue drink absorb brief unhappy rocket subject receive green echo marriage cactus","master":"a6ce8546f2cb2a62c573b3bb5b1e7437f70a44b7cddd7171a41d2c4c3dccd641","seed":"0605b45920c5894d2d351c091c7c31af1b6f0d77576f73716ac96dd7a22dddbf42155aa19bbc3c2cef3c757ff25c3e7998d0ca536b696a498c9a206bc53e8ee0","priv":"00a6e55ef1853ba31f2b7e76acc7e04649a1a2037a9bc33c74cf82261e956ceb","pub":"03de1a877576f6e77bc06a53e7b2c56afe7b58c460341b74c469e344d2b87f2e62","addr":"a4014c9bd6c662a6d4b20cbfc0524e125a5963a2"},{"mnemonic":"destroy naive mother audit tackle coral crater sponsor viable clump bomb bargain","master":"c0cc3cb63a702200dd06ba3355314b332385c3a0e1ba73bf428243278825a6e5","seed":"fdb9a430d0fce5ee93e6b0ec23506f65e76cae8be1f773605af71687d2f9fc14a9a1bbbb769a5c38f654398024159b48de392cc4a88e29030f4b7699a3ed6824","priv":"8b73cf6e42c216a7978f63a28abf839d0e2174cb10ac2abef6870c9a8b938462","pub":"033257c04174083656be1bee39ddf01b34aaceb538ee86c7df35a9ea4b3a3c282e","addr":"5ced7fca93a8e097cf58cffaf7c89141cc00697c"},{"mnemonic":"paddle image cash sadness barely sport employ slab keen riot solution switch","master":"be10364b52d5cdc0b8eb51215301e8468c82498f0d7b2de44013d382c0441e80","seed":"138194b9dbb5c35fc66755736c385e12ea8783c3208d85c58bc140a5edbbd709779d4b42a2c56b94d82b5edbb9de212f2eb315edeb20a5725f3c24a39ca50f51","priv":"06b0b15085f8af9cb90eb081a7b73d4f8b781467926c2dae68a0d300f2d62a71","pub":"0286afa72dc5ce3416e8996392d0cbd63a0f9b9d60701561d3596a1c9d5fa18499","addr":"bbda492cb736571a6bd62d8f5d8836b0e0c761c9"},{"mnemonic":"gossip choice orbit insane honey hobby split exchange uncle wash tower lyrics","master":"7b39b437bb8d0b66c3989df6debf35846dac9fb69d2f570c4738afe92a57d708","seed":"d0897a14b1958fa31a898e38fc95b6dbb2a9a5019175bf8e503149f9d1ee4958edce3f1f570c3e015b797b475f5eee882ce54f68c7f64e310571e0fc6296da62","priv":"816f5cd94acd51821291561d6aa30951d89221027418ca697aa2b621f733b2ac","pub":"03d4081dc8446919e4a949c323b4c8d6b76282325cc7f35c18791a57200577f082","addr":"fc5a9ef94fc8de5ba7407669ce04d8190864252f"},{"mnemonic":"ceiling shed aim oven emerge year sock achieve ask melt endless lecture","master":"ff1480b11529ec8240e79bd9e35275e1805a3e117a60f9b6b22f66715b4258e7","seed":"1aa288b6bac68936d8d2a89396f5be1659a12fc91a9d7edfbb45446de8bb39327044b6ed7451e820317882f162560fcbb134449e85d517a7f0eace2da2cec272","priv":"216833b820c607646ae504dfa7fe28b364741509047b89eeff48545fee07c11f","pub":"03b3c3ce98ff9d2340849c220a5127d8ebe2bfd320c9db41e94f57b4e067eccf0c","addr":"78e3043b376112452a22ce49f6647a0b1d20180d"},{"mnemonic":"deer defy door jungle sauce summer sea globe crane abstract minimum diet","master":"f7440871bbff95cea261708c1cd06220a5aadae4a86c00f53b6f510a50e8ad56","seed":"a1868d8a30b498e1d397838821bf762c5f448d3d8fb68bf12cb0e2e26cc69f95be698efb101e76ad6ab84447c0c2b2170fb2b201315f5e5023bed35490f43986","priv":"b64d5f61a5503b366e7296a5b060e42d5db420e4bb8c4d0248c07ac25d2a0b60","pub":"02f4e32b1b8f2ed1b75ea26def62a6e69c7cce521788bdc182663ccabe4c1da4e1","addr":"32a90701856a1d942f58f41f6cbad21a64494658"},{"mnemonic":"position course virtual distance fashion oval slice round rely key spawn shy","master":"255409af8fb802395b9433c062310fda250cf8f9df7d79bd16381a7ec7b4d27d","seed":"1dcf6512b003b07fc299098ad2b8d4ab3eb1be11cb8949636ff2dd0baa0081ba78a9fc117ac65666c0854387ce0adbb903b482b85a20b9e591f0ff156a59d30c","priv":"a9157f3dc6ce3889c4d8dd4d2434f0e080090e4a0c0c75cf58bc03b41ea507a8","pub":"021b68e6fc74dbc45d326f9d2c7efe9c718e7c3c1a084e92c1f384c273b2d41513","addr":"21101638d066b561f8c43f6e9061f3959e3f89ef"},{"mnemonic":"model absorb cart athlete helmet next hen indoor involve ladder engine tone","master":"4221074ff1f662276f167017a5d43611b1d6e666c0c5592a35c81d2f5389c93f","seed":"dc9672d8e648fb4da6a5de519d08fb677c0d583a988701f2ac57e1fff273c3c832d31c036fa36d33b029fe0e92e8bbae350c6b96eaec463588f1ee04209add53","priv":"c4662a26101f0dd36e56ab0528a3861547015022b9e8a30ffde845d6207ae8c8","pub":"0285c5ad35fd9063e8cea4d034628244e790261977eb0225d4da90c5a2d11ac25c","addr":"8fc4bfc728a5e5fe2423b8c380aed1f2fcf4d2c4"},{"mnemonic":"armor happy become inner fossil exhaust upon under offer sheriff coach huge","master":"154c74bbf5601f7fd35c183e77ba32c03eadb2fea62563d2221199cc694a5fe2","seed":"db7910b6fb6f406f146f857b5c116033356cda61099165a7b1f782b2201878f9a4104efa15c2bad266db1802f9d15719fdb9953eaf3e245f0aaf9f5b0ff8d296","priv":"ee1d3af46fafe868bc1a89394c068dfe05cfcf7578c10bd57750511276e829db","pub":"03db053e44816be4562be9df13d8ee1853c1336aff0d13cc516b0681521d96a9af","addr":"fa65a0099684906c758eecf10f7a2b6dda7c5767"},{"mnemonic":"long output ketchup question hurt drama bamboo globe manual chest luxury abuse","master":"b180100cbc124ed4a9812ab4ff3c632bd7df77209598d4b26af9d31fdb8bbc28","seed":"61b7b436746323085cda66fb4c0e21079e849bc4d2bcc67cbcfacc97008abeb7ee57c2ce751e5c63fdb676f0cfec68442277d4a0e5db1ecf82236bb2f2c9380c","priv":"1ed983d1024d453e2dfbed2278c3c8ea3abda96836db2462da4738f20ba1d772","pub":"02f9d9ceb9a27e4626d09331f645d40464c99975ab0b7344be3f6cece6cc0de8c9","addr":"d092b8b49065f0698b483e416f4b1b032b416dab"},{"mnemonic":"ecology over output enact system curious want oil denial ivory hamster approve","master":"f304cd809c371db0fc37fca212810e75134773494e5c1d20cd22753399ee6433","seed":"6693bbddb7e758825bc3c2096108aecca0414cd1b523460a14c8dd80c15b27f5f37cf4589e710a39fe663392874a6b17892fbf473da8d00d86af17aa19ab0cb4","priv":"8b13913b6f590abf399aafa87fec44961218fe45ece0f9ee8b22810839c46e5a","pub":"034738f122cf61a102148156d40aeb129bc4c54fc55c5b85462917f4013d7e3024","addr":"1657b328a5f1ddef43968178fd8034c685e9e312"},{"mnemonic":"uncover climb join country march border rack ridge track true master useful","master":"b1f2471d8e6e712e20343f06220f847caef645d9cd9972ed17684b3a44b25de9","seed":"431e56eae671e0f1ae12aeba623fd4ed4c7d3ed102588d3b39b3c0e20c104aa24a435b9ca504799214f95b7639e208dfae17fd866b2f58c0d916cd744b7e5424","priv":"1b0e22bfd60db8880827761411390dec4799efba98348052400deec1838b04d4","pub":"03c6d27aa4dae3b3e5754cb3bf8c4638576f113876cea4da0d915964cdff0cf7c8","addr":"0056ae65805ceceb8c17c3c1246c5f70dde2be6c"},{"mnemonic":"rally cool artwork leopard skate battle shine text ritual rookie sun way","master":"72c5b3c5022b033d71c7abea54feea3d3b37dbeadeea5db4c8847a7cd0e20d3e","seed":"d7fdcb354a8946fc3449107361e4bb3bfc569c0d3617b450484e5a7fcfd5c690cde3a511415e08be23672789d090a3c679a06be36a2a20204172e1d61433268b","priv":"dd57130e658e43c9adde730ca57aa6d43d31efc3697a752075a7cb491a7ff390","pub":"03b7cd7259877064b403af9a65ebce47f210141356a67efee77ce456d46d948db3","addr":"a8db1e5ce299462e4943d0eb05c146591fcc3297"},{"mnemonic":"room vapor anchor forum stage corn credit jelly level cat detail auction","master":"fd531ed5d65637f6031aa02ce031cdb366bc8a07b00fce1104675d1a653e7995","seed":"f42ae529e339d9b204165f2b5556c1637395d9598cf5a59717d048d7d5abf831e60abdd457f3432ed2ced4c13a5a73625f716f235d51bd7892cc16c294925cd4","priv":"c2342924a2792e7c4bb8523045faaac7a669fdd00b2621bd38077f58a32bb0f6","pub":"02df2b027f4597da0a2e8f46932929044dbefbcd5c5ee38c2147dc1ea061dbcf8c","addr":"642b32cbc358915bcb802eb30690951710f597bb"},{"mnemonic":"have offer number egg canvas uphold memory snow know orchard cause wife","master":"8ff4d3e7e6ff972e24682d1f1116c7b83bb94ca8cffed1db9ddb4adf89e2bcb1","seed":"14cc60691b49b9036c0bb64ed24da69478e01deb36184f58049d694ccaabc41d95b1e6734d9d06e7009c394d54d80fbd65cde6ea49165879dc306c0125b44681","priv":"c57093fcc3c593fec4002a9040d90485016346a9f4f7d896a105ac268face5ae","pub":"0252fef0dc82142ec121c4b01275b254d6a78b4600f417dd0f09639fd94a23c0af","addr":"af5cea50c72de4e54216fdcb9334a5f5a8a0393a"},{"mnemonic":"garden nature witness stable over move among pledge woman cotton worry behind","master":"48d57d0dc1abbb7142aad3371d68c41ebf078970c5fdd4bfd7294fb6f8d32b45","seed":"bdca43aa42eb2a34dccfa4fd48202cdc386454683850a12e7eb7eaf9998ebcf57a01a6fb1a6b9cd8aaf2910b160038fa6adb42daaa914c7628e27f912687e52a","priv":"63dfb6b77a4286875868db4877a3d0f3f0ae8c2766b9c5d9225f48071421d832","pub":"031e57ffbf90b85823505710bfe5be2200f1d880171e8480494b50a04462b98795","addr":"a7619c6704a7341472229bb7ace1709b88ddf175"},{"mnemonic":"pizza sting refuse olive final hungry kiss abuse nature junior opera tired","master":"397240e78b72dea36dd17ea2ef3a15f18f8fe64c9e31a30687d2fb013e1ac6ab","seed":"1dfb236c6bbfdaa74b02300a0da7b2d57a2fc2f736a5144186cc89b6f20a23d74ae59338fc04367a27e976e41f1d1bfd4232a3f386cbf74220c9d0c6fc5c7077","priv":"ae5a8c5bfb71adfb89055f61e82a6ef5258f6bae9056313c360ea454bc30c052","pub":"02756acc7cd398fda3d485f6aaa15e7e4e558be8725d9766119e570b2b64486372","addr":"c4c6e359acc30099a524b67223da51dfb6557de3"},{"mnemonic":"deer easily marine thrive author despair puzzle super agent life rebuild soup","master":"7efbef221b4d50acacbd20a10c1197727e9fbef29bf2418f51141fb8a5368618","seed":"2ad829ff702c19a138945f61d1449c32d6b16ebdfa34350dd434bca3899b6eec3f66a3dc8de7243539f7d6d07f85689a66b0e9f6481a4767a5630c2c693c3ec5","priv":"b6321fb4900455eb1986073f5f705d088e795d26ec53c09b6eedadf439fdcb6d","pub":"02e9c7d430122627396412df161a58813327f436bdaba34f3e855263044e1fcc77","addr":"47f0fa963518339112a65e54948c5df8c23daa72"},{"mnemonic":"gate calm appear symbol make can alone wonder cherry couch loan firm","master":"9a727a9574790f29c890079fc9e83b0bfeab84d9e79ae6b273ea8266f4a754e8","seed":"f37c4b52afedefc86a24bf4edadf6a2f151aa9d503fdb3da440d53134fb040f7ed60c29407e4b74a0714f5fde8d650b93d71da52568d0e9b2056412092125c76","priv":"41e13797f2f7c41a44db247f9da73e8d7893c2be4a689fe558145f2bbb88feec","pub":"0242c6981834d9c7a14b96c39c6f902ab0a076e4a7ed29fd7ea559aeba3727da4c","addr":"dad747d0295dfdf151b6267e701f8d889770b4ca"},{"mnemonic":"opinion spare hurt three insane sick place sphere ugly sad mobile icon","master":"e6b6704a86504c2552e7db9928c6333372fa5630983e5a9aff32b0ecf56c8fc8","seed":"bbf2fa3760c82ac61467265053828c9cac4bdfd8aa43ff860ce50a094bdc448468fc6d0e6c53368e42b95aabe4f0098f9af46018595badb4f8cbac787d5c00a5","priv":"496c6fc85e550d78a931b51e6807dbfe843599401dae033db98e308430939d34","pub":"037ed7cca0717526969db9101c62cfa151cf97544ae66408317fd798d705a30138","addr":"a1fc3fc66094cabe3f7edf5b630003a524bdcff7"},{"mnemonic":"include relax onion depend animal stock essence basket local result rocket panic","master":"5830b22230ba22060e22bc4ee3323c8b1e02e5f53caeaf90759f4e846720b3e2","seed":"8fc74e989ab5b4ae04472887a59bf89b501e1813e687cd27a06bbef858c9a63b35531864b177d27841abe4999a7b1901d95303a395f5614a176667abe8aefab4","priv":"99a7444212b0176324718171a318f74fa5721fa46ed18b3677934408b30218b2","pub":"0213e7320395ca9eae0bd4f550f7f5f65a801066c12d95c3f9b443556011b13944","addr":"a4087ec7d05b08362bab9c8fd9406ab3bd88354c"},{"mnemonic":"cushion hover joke lottery gym muscle drop scare purpose share food strike","master":"dcc4792ae42c425fe278db48211f6bf54551f9dc58268282a5a61cd620832d4b","seed":"0bb05d59704318da60a1697d09562a05e4133fbb8f5a4750f378f65a5f0d6b9c0fc668dfe08673d6b10bfc4557250759c9f70aff9df1a5418821756f4017dcd4","priv":"741dcf7d371948cb17723b1b58759d8a0520a341a29d581abd12274014e945cf","pub":"022dd390a8c2d4a826ba8e725f54f5ba65438c97519537701001bd7a68eaef91ff","addr":"931f12464c90ac2bb3b25fc00f13c56ba1c49679"},{"mnemonic":"point action veteran fragile outdoor vote timber cattle cube deer fade razor","master":"ed95f0d3557ecabcc04fa2160560dde68517d4fe7ebf88584d0993a1a861c6ed","seed":"886bd48b9c9e337480538f1119148ac4f0a8d00cc3ba17a34cd7076bfbe85ba2a1a8f410ca44f210d9cbf6751c10bf15b6f52a725979e613f4294813bdf173b8","priv":"c489a82eeab534dac53396b71dfcccdd6880bd441b89386baf9700589d92f169","pub":"03e2e7deb2c03412bb42d4b32cae6591e4e0db745681900436999ee51ef422cd35","addr":"f9f93bbebed5d2859f83fefb58bbfa5f93032837"},{"mnemonic":"ozone pulse whisper cry ordinary benefit enrich cry name bargain muffin case","master":"ddea83ac1fba7d6c79dd073d29c913a767315bbae6c3648617b7fee73a04595f","seed":"ee93e7ea1bfe194ffdf6b0f70c2d5b5759c223620c01647fabdd1e2a94575a61b211b301cd6d4e979761febf15336c9bd1301406e338c9f6c0b8b938863c1cb8","priv":"d344fa49eee22f5cabe1362f36a026bba1b0419c126da13c6cf335759a962d36","pub":"028bb9fefe8418dbf0fca6857526f286d9db3321d12c6a88ffe8e2bab7004b4a29","addr":"b9bbd7d328f0947669fd1c81cf8a842a721206c6"},{"mnemonic":"huge afraid venue bid visit decade hurry put swarm couch gadget book","master":"8755848478c71a1c3345cc855a10962382a6c27e6c639c4a4ed245bf32bbb2d8","seed":"b04392d13e629b759aec3c28ac4cd7eb0b7b846f1e241eb79f862d6c30e79f3a5cf5ed26abcad0dce95ed6d639e8088c169e49bc20df609b5555b0bc3f3e5e6f","priv":"f3ab0ed871c3feb01ee18fd374568aa378651815b6903a2b42eb43dd70edb9d3","pub":"022d2a9a829f992b3d16f9374b6a6946649630bf553c8eb11208a68acd03ec3c7e","addr":"c61f53c2b19de0ef11583048fd19404473065bdf"},{"mnemonic":"update ring situate blade term enough evoke balance absorb pupil wealth era","master":"fc22f89a860ffe871a7a45557fda9f38369f99103e02a2a97066517190d10e0e","seed":"d18f179ae9c4767501a8238182d3fef3f24fa3259d41a07b601179f67fdebb6b47a78ddbb056170c0bddd3e5f5a884a3d4046964c0f4ffbf7b37c3e6debeddbb","priv":"32a6d9270cc30fb5bb11411313af52a3359249c1b5b38b753a83ebf516a1d21c","pub":"03a579f47d1d57202e02e7f445d68d6915a3304b256cc37c5d474e699405deb613","addr":"8501c773ed9240be9fb0f83ac2af1bda6511b1ad"},{"mnemonic":"horn coconut protect box cargo switch ghost among energy easy deposit fly","master":"df4a2a6d35731478a3dfdc0e1595486bcc245f83214a974b31bc20e2c0e637c8","seed":"454ec603e16c8c5c522192885768d941316f0736929046aacda1a93ad1a658e70c21a7e924974583d63567acf26ac15725b1148a75522c6bb26b37d9f92fec22","priv":"447e4c3e530ee519973a63c6ddc63b9e5a474761eae12e8c1fdc6d0cea806604","pub":"0359c72dce4c6c9edd2774af06545c8996e4832ae35cde19fab2f7dde55316de1d","addr":"5a49f88b1672cd9102caa80ce55454030cdaafd7"},{"mnemonic":"price envelope myself under helmet dove uncle below extend gadget limit mammal","master":"abe0fdd5148a6b93ff8b281d084e4bc92277848c964e3698a48e5d50d6503d54","seed":"f8c27b37871dd5aea7f7bf5fef1c561beae9d029b3fd937a961ad39fd09100993efe7b84699f7abadd49b81d16382e0677563d28daeeb08eecbdaed50b27eed2","priv":"8ac79807c2a1bac4cedddb4768fe92b7a51e0cdab5cb79758f5876511a9b09e1","pub":"023277dccb7b0e017b461bf483c2d3a63273e83f3acc4688733776ee3ac0f649ef","addr":"0ad65e63beb8258df8ab0f17ce28afe3bf19a96d"},{"mnemonic":"toy grain guide crisp error then group clock coin civil space paddle","master":"7c02340aafc6a15a7c6accc312960631cb3a92c4a7c6d98c43897a1c4c55e59d","seed":"a08642010ea9779615e7a55b973f014ae8bd492630f9fc134159501e53a12d402842899978c75f553cc78c061b59210ecdb20648a1187ea55d85b7ef1b206c6a","priv":"83ab8e2ebe208b1f32ece9530ddf03e0727d3313903ef13b2d09a3952dded8fc","pub":"028582791e93e6e6b4270353aac091a58922c0067fa440a645dbb4673858e9bf81","addr":"4f37f8c3b16183c245ff4a101bc290c3ea9eafa2"},{"mnemonic":"atom border bubble lift day soft rich rely gather ensure that arrive","master":"e6936ec3f9876ca2c44973ee13fc02a08739655b71a9880b0c7959f9c5c12286","seed":"00fcb87f619a1033adf5c9311bced58ebd5fe56a70b69f65b61228732337ac46cefc48b1e53ed5a6b0ef658b002fc162ce686529120a486230f3845250ca7f70","priv":"4e527b2fbf28914ffc78f84031c462f3b33ad8d2b62357ac816bc4c06c0767d4","pub":"02b77c89d89bc757872025a0933d39ee65f5ae037031359cc1e692dcda3a290c90","addr":"8c8ed73961a3055cbaf4ca0b5ab0b0f56bfbc32f"},{"mnemonic":"require panda tackle valid tenant treat boss oppose where thunder zone rude","master":"fa9c61113d7c7ad38850ebccbdc2301c451ad272d86a7b6ac7b384c4560fbba8","seed":"936d1b0d443630cdbe8c98731761ce80f980728cdb122b95753ca3549670dda6663bd93d03a5cf601edbeb0b835ba3e152f940706aab856682b9de0a772e219b","priv":"9ca97c10d757f03eec5165df506d1809850b1d8f9369a89b0bb118d1ddff91cf","pub":"0368a22ead8cf1ca5b0bd3b6b4a680d47de8342b47d7205f4ed41e85299d2f9260","addr":"f03eaec270d04d368df8a3a5408b9c010f283b8e"},{"mnemonic":"thing gorilla large fog mutual hair gorilla soft venue ahead neck remain","master":"4b3929a08e30aed05400a3a7fcb91fafa085de08402ab546258c10346a21c14b","seed":"af471c7488c0046d2eb29429369a14b2b3bd6de6e89036285dcc9a121389323bdedfd7072ef9bccfc1ea472bf8a9d3f938bce5ff58b303f9c0b24dfe8185d917","priv":"13d5e5fc0f6c184e4dad19c4537603b811186a3a5aaaf272762ee236bc826367","pub":"02ecb5330e932bc89bfaaa8682e044f6a1c64823887aab000b2409b467fa94da4d","addr":"c6d1e41d9fc95706c276d85b5d93d657d59c045b"},{"mnemonic":"ketchup blade myth tackle neck gossip obtain write script grace mountain glass","master":"3d2d55b2b706361eab66a2724609c1ae6e7bde50f3ca78dfa72be5e784798ac4","seed":"ca3ff2b26ce97cdf280654a05e388ba2ce5ff909e3bf253b35c66c4b9f8d630a81c6367537039ad09d05ad7fca337c479db1b34544c26331c5fb860eef84f953","priv":"968bf9c285110ba33def9cfc3f47c82ff8050b854568cee2ac509191885c4c7f","pub":"02d8719b0bf9e5296ff70bd88c8eea612b5161e80be5315a9cbd4ba84eff6dc788","addr":"868855cc81bfbc80fccbe3f1fd2d526db7cfe298"},{"mnemonic":"violin toy phrase advance banana timber viable half nuclear friend bundle injury","master":"494774c8eec7a45b2e54e69d4ca64766ada0b90ab1d2f6913690b7b43f489b52","seed":"d11d7a22d020dd5e4ce9a9c162637f47223a4162d5b1920d7ac83d99d73c40ee6b67fad2c534f8100d2529afbdaedf7d2a8cae2e921e8c6e02e3bb8d7417205c","priv":"57827aa1804ff699ea31602eca3ecdef41b722bc2d321187b48c5780962c0f73","pub":"037fd67f44683a2613f7b049a87f47cdc7710d0a834658091675497600d07b485b","addr":"6b13a26e1a545c77cedafc8592e55363742b55fa"},{"mnemonic":"forest unfold spy harsh pitch alter bottom album renew siren find bomb","master":"f19401a9aea13a1daaec39107b9b860d4159b8c66118bf13bca736937eed4e61","seed":"606f8fd5651b2b7cd9e5d9974ca44c8dbee170e6c60ca3d7a4764bd2a1a31ba79b60d0c32b40f2998a3fbe784edc40f93a917f726d2d0a3a07a3871358bc4f7e","priv":"40bee09d892253ebaaa28579840239123740d952da7bb782e66410998405c2f4","pub":"0310455b5a6e7c5d4d09a3ea71e2dc7aac95e8457e6db3bcd605010e6e8f96fa7b","addr":"5d40749e1f1e8aaf38b91c12907f561668f3c691"},{"mnemonic":"goddess isolate speak arrange tumble ivory give message palm wrist slide drill","master":"424c0dd576d73827d765d558616a5c5abc92ff548c82cd6e5a277a5a316e0245","seed":"36d58066ea3378f1851c1e762f36697248c2799dd98b19d74e69aee16e497e4014244f62f696f34dbca202f78f7b6e21585f91531bfb8c3576f910eaaa64b524","priv":"f0c0691851dc50e5ec0897cb29a8e05032b7d02afd774aad6904ff44088f2850","pub":"031bd47b8bd9a56d24874a1df5c15834ccd5bf553e7858e2cc1df2c045966d5ea6","addr":"eb4f13a437254adfcf46d5cf7fafd4b2a059a99b"},{"mnemonic":"penalty trigger panel ring menu toy behind forget tunnel various giant melody","master":"770d401afbd35db8afb7a1864b794ea5a59f2c65127457d4675128b039638e73","seed":"81256b9fb40b02feaf3228eb300d1b002f9a8c0ef03a2b0db187b265b09baa4fa28ae7992c1ffa893381e572331a5016e6fddcf3871d1d836e92ba94a44d92ab","priv":"380085bbd1a2e2f50eedff2c758fb63f981adfe46a25db54ce7f1d7a68b6b7a5","pub":"0367f233e57390fb166e45ad792a054ba908fdbebf11c20a3c199ddb134244b7d9","addr":"ca1e2a50753d833d16c10ac03551839734b5e3c7"},{"mnemonic":"usage real inform betray ridge call coin warm meadow gossip river predict","master":"8aa38fef2d5a72f6e97cc57244f0baf53b976ab7263373dceef0b46ea52651e7","seed":"9b806b194b9afe0f5f46b4b40c4658198445b53462b6aafb2b1e82e1c98fd29731d193681ee43f3d9d70547a5f512344809e02d15ae441916725da5e63cb3791","priv":"10835079af496655bc2dd80c40c0b10f18788fbb9350a7c3f2abfa3e00ccbb2d","pub":"02a3077b5796d8cab9a90186be1c1456d75b3076760f2720225bf4f38eae7c0e1b","addr":"f7deda6ddbba1cb091c8091d2e43c01408fe0d8a"},{"mnemonic":"mechanic share spawn limit panda quiz glance dinosaur gold skill initial replace","master":"0a84289f0c2aad1de0e750a0312a2a362f99b8f0c7a15b1e11dadab1df18593d","seed":"c45a8faf2b5af3ddf883837378d03bf6566c6369005df0c7995ecc9718e2e1482fd42d3411b58bb4a94a637c41f310a8faa0209117031ffe993c0f04b2878935","priv":"10bb410b31a37b51b19165dba5171d33e826e06f83b8c590a23b9b2c8bf11b45","pub":"0331188abce7c6fc99ddee0d7335e872b7cb2f48a7ba7dcbd9ef093c30fa16a5a6","addr":"d3bd3dd9d928d4bdeaeefedd058ccf7047fe82ef"},{"mnemonic":"velvet renew spread mercy volcano always snap column loyal possible ordinary sock","master":"a2fa675336bc532f8e1c253861eccca1909a195238dd202e45f2f38ff421e721","seed":"89c8378d8a7c2a2ef02bbcbd2da9e39f6ef138db1d02e32cfb492c09a132e726c69268f9743e464a8421fcc0d5a0bbdbd00dad61798736c673df81515330ac17","priv":"ed486dbbabe3def74d052685a75e65d25970d8d134724968941a6b8e94fa2352","pub":"03a06dfdfe1fcdbc8e9ae8d6c55ac974ef4075c8aa0a5783bc87f9d9c2d66fd5b2","addr":"00c4fc7760ae8f7986f35efc66d18401bd352978"},{"mnemonic":"secret weekend fatigue anxiety second ramp tribe employ quality yellow game choice","master":"0f326ef21360ac2929e4f8becc2e8320cb49970ed229f987f15b9f58ad383d96","seed":"a29c1dcb8899cd716ced74d2d81cbfbf81eb8b7ca7ae0214ba270be71e8f4a77d235a607079897f6ba6c5aef44da1005f3ba8195775a562e7d243b2428ea288d","priv":"db24d2fa242ff540009bc15ace40783c1535812bd0c79cb145187e4d648627b3","pub":"02ebf506214798f631572bf5f45321adf084d457de1425c929059bbbc328948c5f","addr":"27b810c5c8307a20b949b3945b82c0ef594bf192"},{"mnemonic":"crouch gentle hip then measure brand enroll camp vibrant lunar still action","master":"f10577e0eeb7b2385b797acad2bb4e4028bf1ae52cd59f0f53ccb6eda0727166","seed":"955eeb623726361c94806c10334bf9b1df08737c2b09fc3bca332d9f8008c5f357dc69e07251fa2cb1330c1099352e76ab445f824361533a8e30175cace06812","priv":"2e0602e9dafe830a4fe783c1b53f9c9b2f7dc0b69a52fe701f0bc6f88beac1ea","pub":"02ca1fad1e6ee189fb124863445fed2a9a923d7ec23348771d3789978d30a275c0","addr":"51cdfbd7d00e618d63a64bc662cacbc183fb2b9a"},{"mnemonic":"cluster slice enlist shift world satoshi mesh disease season ready can strong","master":"74d52d1a33a53a722c7319883ee0f47f2f76f39ff17e26b77ade53d45df83a5e","seed":"cf8c965548281a449796c37a12dfacdbf395e474b5336073985e5d7e794d80ad0ff5059a3df31381d9eade948dbbb898f4fb7ff697d2f398655573170b416329","priv":"5b8824e9f9a0cb0c0fc19f4d06921b32a76e1baf98c249dcf01c77bff2ae59af","pub":"0309e87c2c54481db5cb4f7802d2488872f07fe08dfc3a984b932246155dafb6c8","addr":"dd84fe8fef92aedb045bbf091b93c7abf4f34352"},{"mnemonic":"series element media flight next blur fox impact joke review token emerge","master":"1841af9c8f47747ac1819c178e62673e51e2957292ee608557fa38e2ece61d6c","seed":"02de6354734c96841c8fdff0806fc5a0237d621ac98acb7eabdcfc94e458bd2e7d71a9de2f5107e188e153946798775450aac5c62cc293adda42a03e0f50edb8","priv":"a118a4e3a22a82e48a4847a644c452794946169a39d8919c02f131dc177b4c8c","pub":"037b3f5d214185886313379351ac7e6ed054a7db85b9f5aab74e2bb78abb519a2f","addr":"f70fa3d5dc91e442851e6cbcc827fee336bf267d"},{"mnemonic":"engine surge protect goose warm bottom frozen menu royal fox escape sword","master":"9ed672fb3cc919f26c925be30c6dce07dc101af5baad5b6dc2264f5580819b3e","seed":"21bec398901ec8b5b4bcbae5aa2e261f96190a3e9350c03a0e23da3755a4d36ba0532ac17fb7a41266905e76f2d72e90b577e86bd6b8f7ab62a4b835292a94be","priv":"0c386e0854877e7a34bd8a9c9794f107ac2114e05130861f77353406fe9db555","pub":"03a3740fa2bf35d6248bbb7be20d6c264dc526f382f72696a6c1c28ecd2bf25944","addr":"d59ee54eca22ff7873b68b429be08245b836141d"},{"mnemonic":"dignity almost rebuild habit visit come carry deposit stone page soda room","master":"f4eedac55ceae98a3bb706c442d3ceea7caadde17ea5a2d0c81cefae9af754a0","seed":"5ec26bdfbc0d587473ceea390a99616a9c8c4a03527627983cfc9cca8f7779d1ef7c08abfa0969eab1da7c8128cfe536b6d2d5a07c99cd1afac0052e8a753d7f","priv":"4ecd8b9d596eda21b65ed39c8f53dc6ef2e19726d6eaec60da340095aaa3be91","pub":"0392dd87d441269c56e78939eaef320b6a9c0f0ddf5758e57663f9f268dffccb8b","addr":"eb71c884b8e3c1b215445ca2ab5d5b51a6bc0be5"},{"mnemonic":"input sound bacon tower fun evolve song pear various laugh code sausage","master":"b0cbb2481cc2a131867c6514b194146b937a6e46e497d6b4c329d6146433b13c","seed":"1276370b1081d2b48f45f8df73af1eddececf2076f8425fb0b593557392f3853e5298ab5a55f0e367a578932f8daf640d40d8465ffd33ab4bb6c41fc9a4ec9f4","priv":"c358f0d976f10618d3d0a600c0ae754fb2d0a2a97a385091436178ef4bfa332f","pub":"02b2e45327f09ab88143f72f6bcf5f4fdcab9101d55e015022a5f7b63bc13b1a6c","addr":"ea33c880b7f4cbb849c551f61ba630c4d9b97155"},{"mnemonic":"tool lens message country slim wide fresh front merit science argue cupboard","master":"17e462a6a8c9e854a6a7443f0f903749ab8967d8fb3a42af27ba3c2819d0c750","seed":"506cf27425374254890839b40a5a05218cb5e8d7c143523058f74d2f955c5cfdde30cf034589fb7846b9ba3194bfa9fc1eedbdf2b706b75c9b51719cf7bbc663","priv":"995d94dac98165acbec37aa586c52d9c7f0a65394027e3350338eeb69a801155","pub":"022f8ae9595f1f48654fb99985148c5c041dd4a1da396ed59cfdee3f09d7166e93","addr":"23f909e775cadd631b1b3c75df7027382b702167"},{"mnemonic":"beauty fury random thrive grab merit powder wreck affair bitter style girl","master":"2b5ebd0f07795968a7fe8ba12da106195f1fe540d417190ddaf4b11527e5f2be","seed":"c2df6f955aa168424697c97325016298ad6361c3a197ea9b446e790d0b2e877f03692a86dbba512e4afaaaa975484969c14cf0034cbc3f5571374ade85e6e714","priv":"ba6c8ef770c6258ddf7a395670a13cbf661deccc4edf5601e70bf44d1d40d63b","pub":"032f8907f58fa7592e6a8ac556f605a981b7cc80c94431e78bdd20ba086b22c2d0","addr":"9fc0563bf0185e0e99f266af339220588c655280"},{"mnemonic":"code aerobic tent hero install segment truck foot employ manual dice time","master":"bb99c30775706beb704fb7a7416c778c295b82d93c8919d521ee915468143401","seed":"bbf14d684a22d95920436c3133dcd0748dc6e3a133fc61cd78e9f1bc9aff37c473f0ced555265f961d4b7e70b6151c9c7aedfe6930e6a352d2da531275833a4e","priv":"6b766aad48cd0ac49d4ea1cb1bd9961d0c0d2f38f241c1b1ac2f1a8b1f7f86f7","pub":"038450b54d2d0dff23c557b3c66613605dff4df0afd3a5b22dc1c93711c9718d01","addr":"e50e364ca287feaaafc0a6b1cb1bc8f3f1410a02"},{"mnemonic":"media custom wolf spider bitter require blouse soldier barely pause upon inspire","master":"83ebfa6de22ec8362e519d5430a1479c2185bd44749be80f3f5381cc7051d125","seed":"ec99f944a7043eb3da146ecf22086de374cb1b55c64f436f9489b7f4f1e0ac6adb0f937080bb20405a4b225bad173729abf5517e92c3bb75aeebb516893d54b7","priv":"0771719a4060245c49152ce121c01ca8fefd04eb559a610a5a084a772741141d","pub":"03b17977e89ee44c699e018352461bb244a783c4e2187ed49fdea72a479969e745","addr":"493f3d15f460402da46eea1dbe4b767273eb15a8"},{"mnemonic":"build key hope learn place romance parrot ask spy whale input chimney","master":"d3734ac55147344c1aa411a2a161f42710308a5463ed2183b9cb561a8f1a92bf","seed":"5065c3b0a1b3273acb090c991269ff667513cf41e34a11cb802b8880591fb5f9e89ef614306261b0ae2148e5747649b6d5a375ada3749e75e084c3b28faaae4f","priv":"ed51941c62cb63e1e232dc903894fd4c8797d69f501f41f85e1763745ea7247a","pub":"038ddc7d19d6bee458f791b34d2acdbadbec78a228066a4bd5434ae538597689c3","addr":"ee83ae098ae348e6c5464eff4d177c04e97862c2"},{"mnemonic":"habit pill guard artist void crystal various owner shadow inspire plunge price","master":"c0a23d72eacc59b5a382bbe6b59d4541f5c65abf8840a16255a1610ac56df1f2","seed":"d7d1bc83db47fd6215a4e193f654c8246957f7f62b6f8b4766ce867ee58d69b01e749abebb2de94f607e2c442554047a65570c4ef1e764c56295a8326fefc232","priv":"f2cdd0461e210bbc69f810f796189167a71aef2fdd769487c772135924d35060","pub":"03a0ff432ff63da4b30ad757931bf41927c0311f632715aaffc41f1a39dad92208","addr":"a9fadf07acb53dc1003c0de03f40f73767cf84bc"},{"mnemonic":"miracle chest bind idle bachelor science catch scatter current argue fiction ethics","master":"fb1730aadf636789c12f30bebf66b5c9b7ec1138e126b9280491f61013e094b3","seed":"2139c6cd3f1788ca6f91147c60d14498295daebfcb65f0732d3c2d327a1ec8e01cfa011e840503f5c49c89e9092ac88e5d688d0fa945b604a851fe4f4b48e404","priv":"a4d77dc6678d78fab38419b72857914e5848c4daa7bc8e47e960a74708fc30c1","pub":"03b946212019343e657dd0821422044234507602641f389ce1698256054e6b194e","addr":"c67df9937478387372ada7cde57712ed05e6cfde"},{"mnemonic":"garden box bus under chat pen connect access police mom crunch elder","master":"c49d46b19d0078803cadb2a63572295a9cea729fb31112b48cdddc9311ce874a","seed":"fab44285c884dcb44b1adc9823cc021c0560a92bb9ebe23ddc25a08c68935e64733aa1f7b7a4ed30494d8f74cd8373a77e114884f74e6804696cf94a6b0012f1","priv":"79ea8bec554ff6ad0f20806224d7f0402a07fcac87db0b4f5a6cf84221cec17e","pub":"02492778ff035fab9ee383b424a90d694121df7cab7ca984aa8a665693c44cf726","addr":"2a899dd4387375c42068e42fb90793e5f689d413"},{"mnemonic":"morning street camp meadow moral chalk pluck winner light same grape drill","master":"e5a773479add64869e28f1d1a01fd6200de25fd53debfb000504a6231d2ab263","seed":"5e24f51f1676b996b470c01db9db032958f58e1d22bd7efb2bffc55baf73700d948c5f82eae0f6b82f0b9158141b173187ffbf4fc792e43af194c992884d41cb","priv":"4794fe7f9410adf7f567728f98daed1eeda0248d7d0607e57c4ec184bafd8504","pub":"026145b6582ac105e06921c3856db99ac7ff518621d984214cf34f5c155f0c8736","addr":"f263e7676644a577d7b535df98f7eb6d3f45fcbe"},{"mnemonic":"annual very slight slush tomorrow stage mother girl forget castle emotion move","master":"53725daf3dc76a16b54a2086106ea4eec394802f70741e2895a06655ffb4ad34","seed":"fc6f22026780d6255cc7a21746c7ff48fc773a350784471350837ca92d9131e9bccaf78ddc60a5bd8dfa9838755dca0c22e6efaaa757a5bc4b2ad341e4cf5d15","priv":"5296ceed1517058d022c8fa5c8e076851745b98465e0fbbc834a0aa872222bbb","pub":"028250ee9798cccbba647bf41f7b338ab4d15f7e5dedfea1857213e125fc8795f3","addr":"82e76dc9270c795c2316a618abe828dc2f09c1c8"},{"mnemonic":"novel swap prevent funny lyrics erode wool pilot kite junior alley inner","master":"dd900d54ceda698e6ade447839e3eb3f64cdaf8f006d41ee8e1a2f3b66b55754","seed":"8b6b71539ed5ce60bf33745f1172fe3de811affd5a494050c4672f2acda9b4af9aa57fece9b8981c94b1b9701eb27da0f67b8dd216418ddacfc5b84f0c835c03","priv":"4fa69bc81f76265d4df75bc208294244c5191ba7c8d6350bbbf4445e90b9b4ec","pub":"0281946e059cd4dbf81328a3b01ea285e106a390ec6a1b938327a26b85b3e2432f","addr":"12e1ca832d7826ea5c85db514ca80723f83a59ab"},{"mnemonic":"film erode thank mobile file ball inner wasp unhappy borrow polar mass","master":"61b10af9d2c25aac6bf1b1d4fb2e0899c86e40b5b3aab8a29986d241aaf6d414","seed":"f1be2b5ead5055a4446bf8d608c754f4b112f25780aefc7859e9697954910d827969b8ab2d78582ee6f91985c7f740e3d5d5ad08a7f3db9e265aa977d4ccac58","priv":"816f805d3076c855f114b1f8fbe1a1476fc02a71b7b800d6c5fb9d3b1094adf1","pub":"03f08001ebfdcac89f9d4d4cd52f8529b957a6e1ab500d541edfa086ffaf219054","addr":"ed2fc0f401d326f2ea61375272ab741d648b690e"},{"mnemonic":"aim owner direct pizza enhance range need raven divert marriage gap hair","master":"6ebc5c412f961584572008b5d531c275d45e403f3e3672c12457fcdedab52be6","seed":"b07c83efb4979dc2978756f0457d7a4574f5ca3c06c133f2b9cae206f12282c29b8fee9430b22a347aabca5fa91ba9a742901aa4ffbf2a1fd5a30b7a258e0566","priv":"16e205a55aa370cc54cd05ea3805858561f4213c315a4cc91450097d3bf75d45","pub":"0309613fee96c3c5bd9af994d9805aef90ec3b4da315a2e7731f6aa1c25c28fdc9","addr":"ae3778cc1c0df866034a0ba6fd3e4d9308ed4429"},{"mnemonic":"base effort water actor slush space three tuna walk people proof siege","master":"96ed3fbe9c3d5647a24b899dbb2c67d69a629fde37f4973c6032555a48de4706","seed":"1dfbfe4c352e01a0d72fcbb4cc82645a81e02946ffdebeb465d424f92719dcde887566f784be14784742eafec3925160e87c4861663ac1ff722b554e2b1cbd37","priv":"7abe37156bddcd043fba717f9be6ebe3f9894f6b814390d0556053be00566e84","pub":"035621b9ff867ef93278aa007087ebb30bf8a045ea4bde45d14969bef6054e806f","addr":"b8ac9814855c57ddf5d9dd367af8d74bd7740f67"},{"mnemonic":"violin naive wrap margin glare sustain suffer nasty whip loan tuition car","master":"df6afd71e01d947df8fac4730e6569a23002ddeb2ab53ddc23c35349cfd55fb6","seed":"ad8e656352e03c098c0ddd1a5d9556b92f013ef9853264189968677656f0b12d6afff540bb8b548671ec2ed60a5bd61204c463a80c10e5d44380db05fc9cc4d7","priv":"607de82b3e17947b94b43b0dfef04eebe28acd497e6d5b7773feb983c0067b6f","pub":"02dd39ee3d3456e77be05aead5a1d0b71b03237929a8f085760d2e1496c3914286","addr":"2063185bcca6c0602cefec61b752ca5cd47982af"},{"mnemonic":"rug pulse coconut equal coconut soon helmet trim peace fall please robust","master":"a47a965d564ace3708293df5216eeb7b963ef1c814805acf55f76a9f44372ef5","seed":"e099210b6d5259379dfab4cbddaa188fd5d61cc9bf9354f6546db1eec25eba33a34ff535fa3adf285e7460aac8d724dd5208d08207064ff6d2f794f34c514747","priv":"c8cc7b2a9f01d89708fbebd824fcaf1251fdbe0dd4953f43a9079947cddaa2e1","pub":"030361dd18f40d337500a32de0adcc92a2044481876e17562863dc32412c8b9fae","addr":"90165eae1989ce6ab3edbb2d8d35cf843138eed3"},{"mnemonic":"expire company bean basket weather apology lottery energy column used goose advance","master":"66e2f9149b1c2ac8f5f9b139a52567d1284f8484d9cf338b016c4d361dbeac5d","seed":"a4218fc8ca69a1661bd0cd9d9a458c996c48502cfbfd72b0b018f7432ef41c8431f70cde82e7bed983e49ae7c01bae7de6d7bcc590677e166456e5a6e4ccc137","priv":"d7377837e6f73b9093b13741c27c8b25a0a323aa7046483b3f13e9ebf4d436f0","pub":"0368018fbe6931afeda06980639c2f5b532245eade554f5c2ec73f3a532d056718","addr":"a41364a6daca7cc6536aceb4a32e657f3ad22a84"},{"mnemonic":"track essay chase camp denial ski worth retire expire amused paddle ivory","master":"d764799a1d97bd937a1b9dbf4a0f7cc91bcd06c6d7c1a1dd3d25e2dc8d63b0a9","seed":"e3e62475c7d13f79f2005f61abd2f674bbb1613587b3e694c70f24f6403f0299140b68567608420af5a7d540a3707eb1817fef98aacc3e861c17172e3a6eaf53","priv":"b2369fb324c5ef9e7e7313a87470984de6ada9dbef59def90c594c374cecce93","pub":"036ecd0c3e3312f15b3ad399ca5281546366e33368c00d7066bcc371919df9aa40","addr":"42a6d89451dbd00904633f43389133bf199a090d"},{"mnemonic":"mean hello inhale jungle brown cause curtain arch orphan spy aware deer","master":"a66b0528350e9aab8e3f7985a658d50bc31d3cc88d145817f77f889de8afcd3f","seed":"3e240baee99e3e3c0030af994981ee7c10311faabbdd685cb41b971b647aabda05df165b31954506c1dcbda0d15bc9dfb3837d8561e8b346439d059431552c00","priv":"f516389c1d667c77455bf3ea25789d8fe6cbc52c62fdb54849f857fcd35d7f88","pub":"03fc762324c6a0ad341118a95cae96d737eb0194e05272b92160ad3412482d6fb4","addr":"0ee6e446b5713f87b9a8384c3fe356e45a4d6e49"},{"mnemonic":"clog just stomach title gauge park couch coast fault demise march smoke","master":"f964ec3cea5791c86c3740626bd8316bf3373dc874af01e2ee844c0eab7b9ccd","seed":"8175672d9e9e2c8e9182f0d6520eb1b0ce82a17a56d51019acd5edb9eba0c9abe7ba77a895717eae9bd565ff0312c65209c250ea48965be46292a4cf7c1ec7bc","priv":"445d84baed03c168e29c5167e6257198fb2f7fd6893b4323e692bf3514364719","pub":"03b43255f28016c8961fd0cf5dc49e427105f290b7025b6138ec444491e80e9be3","addr":"633349488a087a2789bd2a99feb62554bb35a4d2"},{"mnemonic":"silent burden donkey napkin churn equip artwork lyrics wage enemy fade sunny","master":"754eecf77fe825d148dd76b15601f36ea3c1a23b1535ebbff1be9695fe293bb3","seed":"c61d6da1ddb2a00797ec94d1bfe43df5d012c75bbb916e0ce42672f043a979e8cbbc4187867ae36edeec02c5526eb540f1ea4b9778c2a0b534d0b0e9d8a42286","priv":"530591f5c65acab37d4a98df46cd6c8ac259dd019dfb9c97fc34b1c9269cb42a","pub":"030e423006cf8ee7ac079b31591ffcd73f6d63b8e26e765a4b9f3dbe1022b2af22","addr":"e4da6fc6965990f17c8c827af0b0c2a2d9d87867"},{"mnemonic":"quick toast trim image exit clinic then leopard slice recipe acoustic meat","master":"068b8703650bc546dfe41908b04a7cc430add80d2c247784d41b4241a321b339","seed":"73126b2fcde7ca4509ee115803d5bcb22162748ba465c71767e32d5b271ac53c63168d2a16032947c1dc87d5e78e5117c0171abb1bcb4912de0331d7a7d7b171","priv":"a9251969a43a62c2b25f10aa0ee4d621cc1d5aac612b6027fab2a505a5dfa0d1","pub":"035a135e39da761bac2346f7b896ee0d2cbceac71d9679a201bf2b75e00ed047f3","addr":"f7f4f03d57d3726337478e5216c680c4543ca9fb"},{"mnemonic":"innocent struggle abuse math try bright craft lucky excuse fly nation include","master":"631066b05d71d61539a24b7cb2aa659f1a8d0e3dc76c5ea28d4702b18546bd71","seed":"6ab66876fdb7658a98dcd39fbaadac319ca73e280cc471ae3954e6899997f35576a4b7a506ce3c2b447ccca6203092a2c47f1018ba6d75ff58c416a3ae693f57","priv":"4c6c4c01ea2511f4ec731546f35cb469d424fe6af5dbd8ef3f4d27b7013e014a","pub":"021d951f161be476c1795f1987deea93eed12181b8b1a251107d83efe2d96c3a01","addr":"eb09dac4658f82aa173e5388281877403b124b85"},{"mnemonic":"weather fine valley envelope convince cheese primary scheme invest rely science pitch","master":"ea23ee8d99adbc168bbc367b10cec018bcc7f15717b19fbd05de7ab502701ff7","seed":"0c485f94abbe0e9ee91dd661eb2c6670588c712d7790e9d6357eb7dcc1f2bced5d0c8db8dd0b7aea72a6a057ac97a12980d753ebba11c2fb627163e729a74a8c","priv":"272a773d44247e0d0579731593d568c31017567d6dc29ee9cb2324598d1d7a6e","pub":"0305d1ee431078056588a2d2e503c40d1ec6070e3b06a78b2deb04c6de9f4b00c2","addr":"bbb37db4d3d3f8cc16178fa777ac23200c05d78e"},{"mnemonic":"portion visual market hurry enroll stuff upper pistol tired question barely surround","master":"997415f4d0711a371b522baa2a003fb6485f4979e170492c3104a18e7fb90cde","seed":"51a7489d52481c5fd36a703c9e832550f10bdf24b06f9ff023bec374c5089de64e6d06d61ecd509fb44972a92094b4cbe1dc13fc1ce1fa6ba04b03ce803305ec","priv":"8c8acc19030ae9e9ede5111a41637a7f69e624be9f098660247cf0154579018f","pub":"02ae3bf3047431b9d799b99d56bead936c5459039b965a612673a70ab9209e87df","addr":"a2d63ec58cafd042ffac57128bf9ed704d9735b5"},{"mnemonic":"intact elder sign valid party enjoy trophy mix hood bachelor near sadness","master":"fd40da840f8bc5eb4fe8e83611c5ffc897dc315583daf95a9e255bb8441e8605","seed":"e9f3376502c9e4506568876341ab89527694bd9f0184650c3f30fed6fe8b8b8705860e0c63276f0d92db398bfc2cdb4b816d58dc48a5b8ce9325139c1b96d682","priv":"bcc54e226725e41f96f81587653d60b736a8da21af6b24d9306bd6b94d1959e5","pub":"023828cc58f153451683a39c3cef165e8f3a5499eefe018f945b63062fe05dc82e","addr":"cbb390ec8ede1eaee76a40100c1b551872eed89a"},{"mnemonic":"unknown order purpose spice master small inch isolate loop frown page nurse","master":"b8b282b74a246171e39a795778958bfed4fce9aa6040c3e9013b1afc712ccb40","seed":"403e11937a99ae4a4aa3ba4e0f682c158dab10cc5cdd3b79be276e5283b0f91fb7478b6b33e30841b356cedd2091417a1fa09925e40c1ed23a9cf54484883129","priv":"c22ff234c4bb00b72005e1e88909dbbfbba148c90305eb81de0671a5d28da59e","pub":"024bfbef04401d5a5938c64619a6c566011b3cde0c1418f2b62c3a921f3d3f14ba","addr":"f0fe3107cd932d601abf273b7547be623bce653e"},{"mnemonic":"angle blouse weapon upgrade enhance frost fine pear install method duck okay","master":"aad7d48511dac2782ad61a97e3ca94352ad24590f1ffca0e9efc3c6ee69297e6","seed":"8651c2d20d4af238121b528fa601c290efc255a569c8db892ca8f77d634fc02f7098efb7a9a45262c7e86d3d71cd92c7239fe4bb780e69ded7782c00b1ee0ca1","priv":"4b8f93833d5896f673ed59c0bd75a34917a61d521cb8b8bdb22814a0da0eef19","pub":"02e17e65c69b471a9fdb75515aadcbba673dac78c0f34df920aa46ad65623400eb","addr":"c404a9b79ebfa5ed9066e06aca8e091c488bb740"},{"mnemonic":"trust huge gauge culture garlic fluid type drop arch camera true claw","master":"b74303ffd6d878d1ed69a0e406741b51bc725887df08a1c8efac15a959b8d0c3","seed":"715486a3cf1e3db172fce32a23868a832fe7d5ac2844990fc14e951a9a357f16e2b5bdc669e415a40efc7ad14020991497496e980395639a68c6c7b037690cb9","priv":"2c0d6847313ed5c928480a302bf3fa0bd9918a5debc3ddff43cbb4c9e3f12802","pub":"02f79fa697f78fbc6b154d7b14a3b28dedb6b746b9de88c0e9982a54904ef124d7","addr":"c15d6b60ea16407697665acca000d5f792fdd3b3"},{"mnemonic":"shadow sister evoke wing radar what trust main improve moment flee erode","master":"cb1d183803a2de896f83834d19c3e1ef518c3fb6382dd68ab310696bd1d69b7f","seed":"444b9c8d29447c024f11e3c6c1618e767d908dbcf380125a062ce18a552f39fa108361fd18561406eea36e63415d286eabe5d5a5fbd23ae5bfa846a3e8bbaecf","priv":"dd1acc9e8972c3bf4363b3411ed96f41477724b4353e514b339baebbb03a65bd","pub":"028658232d2a075b2c68a519c47d2d74636fbdacb8bb31c9af70a967682816ca14","addr":"f8db02ece9c267f8f5b7de9c801432e694e22731"},{"mnemonic":"guess beauty gift repeat desert oblige pistol pottery lemon clutch discover dragon","master":"67ae574f5ec3766b3a0e1699dfc188f0ec78b51e69faaba76dbb08cd3a9e6382","seed":"395674e7b70cdf97f6072be527bd79a6fcbfffcb4322786f8d17611a3bf87947a87fd59146a59e7143a427c4065759ed6973a00f6aa522e6b13058382eb8b080","priv":"170df49f85c0a741bd5a6647d33410f944619de34e9ebbe7387fe8785b470960","pub":"03dbaec7aec40764a0365a6b2b00384c7966e9b2c97432273abbdfd089c519fa4b","addr":"af3c030ae37cd0ce5d3c41012b904738c7905887"},{"mnemonic":"fire duty baby kidney coyote arena bonus convince soft party blossom brave","master":"2054fc16a8c8b5955c76150c43fe447fe7a6d083b323eba70784687f94780a75","seed":"c16d9aa8d945a01871a46a92881c10ffafeebc0b1183e97812169d8f6e4f094e70f82576947917f3f6e551a9b9dec573478180053f6e0d71c5c48eeee95b54af","priv":"fe7f533210200354ea9c5c972cd0a4445fdd8a2e6e6e830cb541fc894e2e0ddb","pub":"02f9fa53e71e4a2a180d6daba5ae9806ed5e338aa680e2bac8fe25ff1a1a151d0f","addr":"2f028ee2adcec5023013e7a3756fe18c9a87b669"},{"mnemonic":"poverty inhale unfold strategy insane usage festival dizzy drop divert august head","master":"148e592f2c22b1db9d693e3777470b27ed0fcb2de1c0ec2d1f9f47421ff98cd9","seed":"412368afe4a71e2a37b1f87b7ce82f64dbef5579b16a6c189fa2ca29759e410b7c1e13a7b591711fb3d5bc8fdcb3292b3c828e52a2c1cd20aa55c830b8506a39","priv":"7e070b5f714cc7e0e031be68dd03e6b3e18673b5966d9c17616554df4e0f3288","pub":"037227c68b192bd6ed034570d5b5d532354dc47891acae0815d0acbd8d0346ac2a","addr":"439c830a56e6f8299c4d859c2cd5fd4a821e6dc7"},{"mnemonic":"gossip quarter proof require develop about olive boss fiction quarter rely build","master":"8b7580ddbbb3b766cd08088792cde2429c003b8b0fa5a9165747275685223a9e","seed":"a3c24f6f125702480e2d8856095a96f3e6d596678f15817414631ffa525472a01bceb58a0790e1df38dbcceefd73dd9b6bbc5bab841503daba8d73033494c254","priv":"5bb1bd6cee64ee4cc9b08f27b34dfd3da9ea263cd7fe7b4d3fc8a6a2db4e0ec2","pub":"03bcbabf0702cd56f459b8cde03db89d3c953faa3c108e90b75c65eca10485b5a4","addr":"c3ba63ce6c961c11bb3f7cfbe7667ef2c1ff19ad"},{"mnemonic":"dash small ensure soda crime wheat future style guide repair diet panda","master":"468770a80bd9e0190ae3a7a695da6a7bf30ac9bede3805c7d1dd4f6052e4d7c4","seed":"59eeaf79145cd625c266dce31faddd42eea93d15e64be3a430b9fbefdabc85cc3366115a07d819bb5ee026e118ddce46a2d906680b59917ff7adcb3314d6b077","priv":"99813e574552f35d2ae337f8754afb37d8387129c5e714461a0b72cdb30d0bd2","pub":"039e8088eabbbe966cfe54264bd1335697ca335eb55d56a89ae6a2af0bcbe2fd60","addr":"b3926244d741397940f6218c6db7a7c71cc012af"},{"mnemonic":"profit brush exist mobile diesel keep oval guitar cover maximum media file","master":"f402407479104621247ca60a34f75391ddffac32511b61e83324d85d873f0da8","seed":"6d538602ce7d97d7cb5469aede489a9a4f8af829d76712c13e7851d9e63077dcea3233ad51a7c311d367d4d982bf963c016c23fe39604cdc3d1dfe25baf1f515","priv":"cadfc276bea3a62afe66fbb5a4d117aaaca562b272aca37a0ddbd5f5a20c95da","pub":"036aaa723e425f879d4b2e731fda45dd95294608daf9d553a40f59fc4cdc37c996","addr":"0b6a4c2a542c624ee95719f53c99e3f6c6f7df19"},{"mnemonic":"oyster twin legend bubble bacon wisdom affair bar search just daring teach","master":"722c8eada15c60706ea1a9fa5aa6225142a623d6f5bd946589b22b3c9e69f56c","seed":"2f3967f0ef31b8d1bfe4b125678ab07328ada854f5bb589b5fd2c5479c3306eba54233f3386409e4b68f95be7f4b20c054f7930ab022f720979c847bbac99b80","priv":"73b2d8daa926db7e9072d4576dfbd511017e1ca7876bfb453112c8308ce70fef","pub":"030a96a78ec8dbcc9d62b9a8ce221b0f740358e75ed7ead277b56a2c56bd93a468","addr":"0ab19ff6566e8294c16e734306415bed696791d0"},{"mnemonic":"say poem daring wild toast force fall bread cup crane next patient","master":"89a87aafde53a9a8fea6b38549b6bfccfc950dacacda40f83b613a6322299164","seed":"b86bdd449d73412f10419a9534fc545a5f062d615ae197b6ce5143a1b00ac838b09e9db2cebd5cb556f469a4eaf883b8d9683b222f9b8843314134bd718b0887","priv":"60b1a4533e8afb1ac5eb5448db1540b859fd3650dd4e7c52b8b1a98ad3eac169","pub":"0255161266504a58a273b413e75fba8ae3c49046a3bf8334b2bf4a203f837ed383","addr":"2c390e3e85f391607962fff3ef9c3a7ca4ab5704"},{"mnemonic":"bone path relax awesome planet exercise road reflect life ability device frozen","master":"fd23233c87545130d43a46064fa11deef11d56a3cbb0a2e52be1aa21eed24f58","seed":"c72634cdc1fb47955eb7d9fd2391b3098d407bb5da35c0a88fcb38746c4d673ea113a96707828b18549271af8cd0f9be51f5caa9718ba31d783a3b26d450e7d4","priv":"d946ac6ec053521443b3d8d2dad6e947dd544b58912fa7b87b0b7b55f8570981","pub":"028b9c6677d3fce87d4b324ad336e486d382f7b56c2a1c8723d993d247ac1e1caa","addr":"6ed322bd71b762e44bf4554937122492e039380e"},{"mnemonic":"true solution afraid clip year symbol burst alcohol orbit village arrest balance","master":"3cdf923de0656e5340979c614d12ca869b97bcb476f8ddac3c10162cd23ceaa9","seed":"13c3eb909b83529db1d166eca7cad6b918e1f58c6be120aae8b7fd2a2c848d29550551ba3d710cb4d4a8597485e20535d6e65a941d206c69774cbdb04113ac17","priv":"1432f702f33b6c318f79d2655da268dfba6075126108cab3c8d88f4de2a6a0e1","pub":"03d100bcb7a2b81bd8e49af05a92c9a874f4aadcc836be590d63ffe8a08b63a0a7","addr":"3527160f146f542703309ee02b4ef783416fc9e6"},{"mnemonic":"family trumpet pact beef stand naive book escape ensure puppy envelope dune","master":"ecc1fb248992b1402eafb7d3120569255268dfa5ace3d04fad48b1917a42c3bb","seed":"f0f55ed35029e7f123dd153df36cfc9bc97c633b9b8752264bbbc2c33bb5932ffbd1d657bad87f8ffd7d4d9a9355226d2155fff92109adfe573ec67a18657f3c","priv":"8cd151b71c1f1424e2f53a35af5d427965f453572a211d8b4315894d370184b6","pub":"022ec5ca227ab19d758ac5941b9e7e7e13a06ee9a59c94d42dd6d013ee7c3d1cf8","addr":"93f08bbc6109187bcba87f2b6bf86e73eef60483"},{"mnemonic":"feel grocery tone drastic clap great pizza artist current true diary suit","master":"cf0614735905c88cc0f2236dbf71fc61accb6fcba39fabbac4ba1275a1ce6e9c","seed":"75bee915c2f3216cd023b878d06aa4d45241a49d10085b851303bacf82c36b392e01478359e59b54c77882948aae3026539d687298b85c3517f31ec0dd80e213","priv":"03e47eec1c8f7d3ddd3bf1997bdec4802a5b997e80a9a9fdaa8ed93f2641b146","pub":"024adba9bba7c596189ff4b057a2329740e03166cf6416612676d68a6df32c56ae","addr":"ee63795d783ab1a27ba8fa2a64665c6a563134f9"},{"mnemonic":"health across rare laptop broccoli verb course bacon offer hockey vendor venue","master":"a80307838f0014f827d55ec51e5384722d7a6a24703d757aabde32dca9f6dcd0","seed":"5fb4a16684f338f15b5fa6ea48975ababb129b3c01c726493abbfea837757fd27ff0a70bcbe2d08a7174fb7848169f7e22f483b8976adb0a4b2600657c9eb912","priv":"255717f5e26bd51d607b059c9a7ddd153d9bab0e832596df648fa671d8535d5b","pub":"03ca70b2fe6fd3def827767c0b26b25534bfe22ac74f77c0c4c05051154a08ea69","addr":"c4029c4186f3e762228f4d43b4d8fe19753b99b5"},{"mnemonic":"begin step squirrel window liar lock endless maze health galaxy swift scrap","master":"53ce62b273658f9ee3ef5b78a4e9d8b9a84f383053492d86825cd9d360763406","seed":"93d7f29ea325b084210dec914666f70ed46b83b7818d2dbd1614352134dcb021d539acfb2f4d916fa5ea48babd11d95f12eee97860c19db1bc2f369c6cc96b93","priv":"50455038499b153d77f388654037cdf614bb85ba5470d8937fb108a123241521","pub":"03f1f12d396494513c14a7508a9ad1dfeb1577af519a250b48798ff59f5efde3c4","addr":"5c46de48e642722b4325afe8446608a60e7ea89d"},{"mnemonic":"snack fall math great upon wing casual acid clown despair cage heavy","master":"c40433c68e968ff1081d3ffdd47249f1bb3cff2214f4e0f19227442f8e7be4ee","seed":"6962c13ba9d9b79b31480c117cd1613f6bb9dd6613e42b17b7434547694c96059026355a2c58b682ebccddc5f84f50c05b60b517e27eb693f1eeff24a038eb0a","priv":"972881fe79c997d86f11440cefc6c52a46a1b3b9953a255da6488e7ac278299d","pub":"0276ddb2041eef2c3b487a4afb36de7cc5ce5ee693461aff375131177dc4d28b40","addr":"17ed4db6f0bd80e929e048d526dc55a74ad58eae"},{"mnemonic":"vibrant valid carbon wish bicycle adult hobby lawsuit problem toast clever lock","master":"903e9b75fb472ad89da3b1bc74937415e877c1973910cb9ddbc85fbb465c18e1","seed":"b175935f86b62ceb93ed1bc1d2f3aee77a628aca7def8f6761df8b624a29d495a150b373d12a5cceec7d394aa91d0e68c5e2464a99820ac5add276d655be28d7","priv":"dd46bb3b1342a5a3c3844ef7fb4bcb247f9a81a48e20722faa1e03ba59808191","pub":"02bacfbf1b8b6d4a6e8a36b913d61831668cdd3dc74710c0ae0851b87c65789edc","addr":"e0520de38f2d2c2d3e461b1e2fd3076af5b9e927"},{"mnemonic":"obscure false outer neglect transfer other index debris bread crawl morning scout","master":"601edf66610cd4b9467bd73418a3dac8b937ef2d08c09ecbc89110443c3c8869","seed":"9d6168c1331c952ba5c2b19a0894bddbcd3d6a0960ccd5fe685db487ab8caab4109e2dda7c487d6161427895a9803ef3fa857fa2106e48083587032c6d30a217","priv":"2389d4e6527d9190a9ad8720debd487a00d47d6ad6228d643e1307aa727d1757","pub":"03bc72643c1c9a13f31de0a61815f950aae5e186328d07bfddf8e1dfe952fa8b49","addr":"add81ed8c06032c52445dea0db02a9f56b738742"},{"mnemonic":"minimum coast chronic damage page antique mechanic material worry alley nose tray","master":"d7f00c2baf2037008e9d1fbc1acee85127db59ce939491aa5cc19033f55f51f0","seed":"0ce817dd3a344f8dcce47336d3bff28da5cb9f5a71fd26e54c4229e9c0bf943077826d72c68e7570804b1992646641d79f8ac1b0fe157b7e12c70413d9625e22","priv":"76e0ffd8727768a428fef6d1e4186459d6b3664fec801ec542608c955dd8974c","pub":"02c0fb2dacc13de0d29fa26144d0974f90b97ed706c16ccdfac02eafbadf9e4e0e","addr":"e28e2564870c7fd74244b111e188dc82c27755da"},{"mnemonic":"evoke example emotion suggest apology uniform giant empower future surround pizza scout","master":"ae3bdaf7acb6660fd151ed30d5d299f82556bae13873f6cd9e65111d991b4d00","seed":"840044591bf305fed33beea796f2c5a8ef72a5005797762ceb8528e1c0343696c02012ef81a8e964548af3293454fc6e3329059e40a95e9225fa254fa901f3fd","priv":"2ae6395640b6827f7b3af5d2a4a0f0df46d4c6c5bf28528eb3a896d60c80e0f3","pub":"0378059ff10a1d3e0ba1876622e9d45a9b75f01216bf14737a68b48138920e7adf","addr":"c1333d4372eb2dea3311829c10c6d12f17437cb9"},{"mnemonic":"endless come happy cause skate short list laugh amount marine glad pear","master":"c1643f331f86b0cbf7297eca946e44d0d1a1b3e704b6dfce6d53d9e9c6eeecfc","seed":"f3901b079e9987dffb0378a76f099e39349cd6abb9c59c23ac3f1cf90b988e82b71bcb3466b7a2f9e04e0b5b27bc945a3b4b714e0272184cb64e9b4f8d6a638b","priv":"e80f64abaa7b55214d94b2bea74d25a45bd6385bb2447fc53adc4be4820edffa","pub":"036d7029b6f83bbbd1238bfe9a2b8ee654ebc1b2ea9d00494345783342705ef7ae","addr":"56fc7b9a42b8dcf74932b92934321f2568606240"},{"mnemonic":"boring math photo park connect wash captain drum kingdom replace exist beef","master":"4853a21045f669f43eddd0df88100e473320d2f76641ce5f9d010d654f4896c9","seed":"65e6412f40852556186217c0ffddd4291b77651f8568d97434f1ce3bafd4784642d9eda0c401f9ec57ffc80c161832afd43ab89080fecdb8d6a2570b27b2f0a1","priv":"67f6f9aed318eaecf6e80b3a89748900f3dad77502893e133024616c15418799","pub":"02b0f15eaa2f0b9c09a03280c9689aad73e3e60e6c971ada22b9352cf381d9a7ea","addr":"741b1795cdbffea0b144b22ead2d466413bc232e"},{"mnemonic":"true also hockey holiday increase humor main lonely invest modify penalty soft","master":"c1716c4c6563885c995c00990cedcf3c90734ec9cd0bcfa327b19349762d1bd1","seed":"e192cb10ec4d6813342e8ae74c60ecba75c8f9f23cd60a92c8abc44211fbabe82f4f11e30bf68bd447c8f0147be7ff9ee9b64cb8c44ac8697f2bd1ba886f8427","priv":"f72b0a757b8ceb4bb80b47704eefcb5e0448a9bcfb7385fa3b6492628c36f2a8","pub":"020584eefaf032c19e853bdaad1d1bb8cea4001b8d5bc919d888b1cd55f6e88b04","addr":"98ff8a4e963a8e8cb4a09e10a83cfb704abf92f7"},{"mnemonic":"inmate mistake detail main sugar find maximum crack soup tumble helmet caught","master":"9b6e9657a31f9baf6f303b2df638d84e549fc35e23f4cd7b8b96ba608c474657","seed":"fea14199acf507de04127191e47dc5d20d60ff473d5aa600ab697b7854a7351bddfc24d99057671273b62111990d5bb6f0088af026b38448609d3a4ac8f58b74","priv":"2800c0aa8490524c5add4d3a2def895d8d54e9608a05fd21a7bb5373f594999e","pub":"03d49899c17ac5c0a8c3720c1a9593950d2a8a591c8a8fc24812d198ec1931b5a9","addr":"e198b4a329be8be7f67e4c7d63bdeedf9ad5b45b"},{"mnemonic":"embody ocean catalog shrug valve extra potato subway casino father denial oven","master":"a24ea5416ed8d137670224056db9656e8c1a70f8e602bf6a49cd2f7e6968fc5a","seed":"447121225f8095e806b284e249c1625d925999fed396dcd7c36d9fbff5339659530fe2df0760a9d1d15dad32e1a4c1577c518bbb378272b305434d138e748831","priv":"548afb052cd083e9504a6d602614d085c7a20f37556030550d13619cc39241c8","pub":"0370f25b9cfe827ca110760fd13eea09082573b534411d1719a12fbc9867982e4b","addr":"e6abb5bdb479ce55759bbe1d0331db6046f1aabc"},{"mnemonic":"brisk present attend rib envelope keen mosquito athlete guess ginger budget interest","master":"8e34652bc5848d8fa141e06899c93928c5bcbfac5e1daa60c77af837d7771b96","seed":"4305389939cf757a57a7ecdcdfcc09f7699c315704a3d23243968932386f01dfc8666b7dcfee1d7ae26a747907c85a949843ffc91c9abf3f98007d394b1ba2ca","priv":"65e554264f67ab679321ab7fa516b5988e6049847de929d6e1949032adf1452a","pub":"0358503d0c6dc10689a3705838741f20b83bd5496c4befd2403f69760533f7b28a","addr":"80b6d095ea92c035fdd56adbc4d38f209de13000"},{"mnemonic":"never gravity problem draw arena very panic patient clutch trick calm topple","master":"a6cf02c1407405402557a745a96019f2516fd7ed03677871da922282358b23ab","seed":"ea44a019240e09c4c642351a7aa04625c309102032b9f77ecefb4d33c25f31179fc52c300b4a2e57603c08b5ecb77600bf9f4f9fbdb07cb0dc7e822f709ba7f6","priv":"38ac40ab37d588c0b7ed4a6f4adb378c9e1a6d8a3cb220853dd56abd7327df4b","pub":"039f5f8a5513c74284314c5c5775ed39c77ec9081b978a3b096af6670630046730","addr":"c6a7dfac1a85311dfb3a3c6191a6124ce8ba39dc"},{"mnemonic":"aerobic slush coffee volcano tip speak across evolve nothing deputy limb arrive","master":"d0b24358e5a1e4cb2fcfbb10d9b32330e02be92f3397e70c8a81b04f065958b8","seed":"927a69f86c0f796ef2a6f89698ebcc6c7d2df24672d1c597c10f2b3dac8123808b46cd63e726eb6f1699b0790f14da3a82d9da2cf0fdd32a9b936c1763db0522","priv":"1d736f96fb08caa2ee8c9d4c1506bbdf235cd3ce076a976f8a7be2bd621e5a23","pub":"03f6d508014c45fedbd92677a8dc94a72a7ada2c893d0e2d508c4c9be4fbc669e0","addr":"7ccbeb5e8927c714b3d4c1047e66757bea289bf9"},{"mnemonic":"aspect glue bronze steel idea trend win collect ceiling puppy gas flat","master":"6320f492fc27070bdd7b99436ebfdb25f462b588f50915e5c79578613b53e41a","seed":"e0c9803520833b770aa6febcf2a0aaaeaafa6d39400141908c6577d99ff4ff26062451a4f1b2ed4dd91eafa345f7efa1d565fa101cf0ded88188e9d07ce62e5f","priv":"f87d3a46e925d395a8c4539469e0474cfc0bbed8005b50d539a28c85e3a44a28","pub":"03343c7d2cf83e568ea0c9b92e766c6e021d4a95ccf169c2c9d085693490d66f0d","addr":"6dba7380eb165ea86e2b07e30c5fe1022e046da7"},{"mnemonic":"curve better narrow reopen vocal gossip fall tornado fossil scrap goddess direct","master":"3fed34f65702bb4cd41a309cf81ae4a8c13b569e1de5420103bea9cb264a0ff5","seed":"c3f0c08138f32640b3fa1e6d9fde51163aab66318a7709933339874fe8bad22e75f6c19004af5196fe84348a1f3eaa37add1b645a320acf8a1fda40fd788b428","priv":"c99eb1a2f56c8ccc3ee2d2bdd3d849b6a60e1e82200f72e55d236ef1b0a9fcc6","pub":"028c3c8e1902ee965f55211362b5277748a5da54b43f0807a216581e6d686158df","addr":"507e551fa72ce7e9da2b19f4258c2ae79793aa2c"},{"mnemonic":"insane screen heavy sphere turn drink direct exhibit turn pattern series pitch","master":"b060bffb238a046de32305be06a806093f62fd0d52d1ff5b44b70ad45f502996","seed":"0f75c0f282ec504aefabcc1cdfea3e4166b4f6d4782771a905683e26b05dd5603178cc3fde6fe4febba6542350b15a3c393927ef1e371ec450af6ae6f4ed5a5d","priv":"3f590738a71a45fb5f111181747b44e342212ad15350db1d3ca475b9ef4914f5","pub":"02e50487e89882de6fe3ff87c7b12542a8a6a8a8cd2b4a08be5071aab04b2d461b","addr":"66284b62f4b7f506c6aa0f935d034b00f35fe66d"},{"mnemonic":"answer mobile swim shrimp jewel smile security shy decade sign physical width","master":"64cb57bff4e190f89d2d7564be94cfd1efa0014ef403cedc6946eb52a4a78813","seed":"3f645e90cbdf7eb6aed9afb802fa22f1c0baf57a79c813fe21478a7f2d658dc83c3498c75b0ee4e01a02cb11609473782065c7656083d7986df7bcb320603691","priv":"12c7e6691ffe251287754234d7b98747f12183b5535be6c886f86268b28e9175","pub":"0375b4071b801b389cbc66de9443623ea5b86d310297369305d869e885e187a0c4","addr":"b6fee98515f241c4ac41fd1382a1d7211dff3af0"},{"mnemonic":"match car rice essence try reopen wrist party choice creek inner comic","master":"60b8c48a8c8cb2d68a92559d1ead30a2cac313df4f5a84f94d0b64a3d5569047","seed":"17a91ddd485e6becbb822434a723c5143a11aaedbe9275b36080f92303fb3ef4d934abc5d36bc51b35946a4a683f932373e4a3276feb0393c499a961b78fc18e","priv":"1d9e0692e0c3e8d06606423fd6fe3d336b893c13b38ae3339aa3d7635e2c0046","pub":"039de765de0080a12871f7a278fd27a21ff6052d864f36a01aab4895a3e20d13ef","addr":"6a1216c843cdbe2010e5f024e265b31aa01592bb"},{"mnemonic":"leopard garlic permit menu opinion kick clinic upper start vote truth okay","master":"9db65a69d3ee0bc07c5ee34244faa0f6e8008c51c5372ccad6b9d54497c27f94","seed":"f125a9285412d9aab52baf0045d628d41e870814cc1ee92746d0cd0fe955bd30e3f5519521c6c7d172d07d53f318cacfc5b9d4b0a4e508f9a1056526df594283","priv":"da4acc9d2c281a03f904b42962e021401602b65c815b9659bc4a344f6f35e934","pub":"02744a41865188a5b3a37032369fa4ef214dd2ad836a35dc738b501736806b6606","addr":"fd86381be60f3ed78fc53abf84a8652b3c84b7b8"},{"mnemonic":"vote divert tower history staff finish lyrics empty bless alien walnut hub","master":"10f25de189ec007f9caa97230456a55ae87a807178e8cf23e84f761ec7c69cb1","seed":"ec42f0f2043a01fa768a6c5e889bdc706747abae60609511755f21b7798b7e3c476d38d0b2894f2519f2fad6e279d77a04e1ffa864c9059697da274b32bf3b88","priv":"4073cbb35009f9e7ad4bb18a0c3c7178ece1b9e07c9df1d595f5142c51a92bb4","pub":"0340a0d2e43896e6cfc4d0a7ff49afb1ac28a5ba112af2882f86510865f84bb8ca","addr":"21e3b66a23dbeb25b3b0e2372e2fc7b64364ccee"},{"mnemonic":"debate between rent foster stove powder truth remain cancel pupil object romance","master":"aeb2d34623bef5822150858dfbc2c0c53986c363297e9c3fe1ea64613c6701c2","seed":"c95a64fd29b7b9f15d9077e6a67a8f754bfa55bb952ee35ab94ca001caaec42b45ea2ae7223fd10492dfb02604fbb87de28b4202c49f72be52b7c55db0c8edf3","priv":"aaa218916efc3fd6ad5b948ffa4a3239984b06592c3e7809df3bce89b7c50465","pub":"0221a21829f0dbd3043b9a7645c182e835cd14c95ce9931c1e17284b9172b77130","addr":"853104a703410c5faf86381d5d2adb4f3254abee"},{"mnemonic":"dismiss fire ghost eyebrow salt report pupil include case east blind filter","master":"636ab989611298c004b6ceca6cf887c25d44df49260a8f755cc159d74fa971a3","seed":"5ac06b4e40354a95bcf97270de3db0ac2c9bcd44d1a83c40e9f2557490cf794dc8ec43e71d3dcf3f13e6a7202d84b835c124405a2dc79119288ebfd6352a1d1c","priv":"ce591b790083433d60e3678d5ae488f6033c8327b494789f457a4a137a99bb33","pub":"02607f14149c2272136dfac3ba34476203424fb3eab4282fa6e81c0bce0357e81e","addr":"9b38671825a0fa55e15833ac583fdaf3fcce3800"},{"mnemonic":"awkward slot wrestle sudden core clutch again pepper private reveal shift baby","master":"868dc5c7376cb2ccca274744e97e1b6f8390c1f2fb669862cb624fda1e8d9408","seed":"065aa8d9dbe5769799f29e7a99e663a6e622f0463c6b8093486728d2ff2e8dfe2d9d30e357ac6416515f043b9c96267f3b0de08f3b88c6bbe9486c3c78cdc44e","priv":"e6d6be1b041e0d52d576fa52f4987cc2c8490d86a49184a231703ca0f3d1fc16","pub":"0375e417a16b94bbb1e8cc1789535fac4abb78b466d2a354045da76405ceb8c35f","addr":"280ab94c67d7bddc3ee6a83571a2941b06c83676"},{"mnemonic":"whip tired like merge crush bird venture verify grow fiction provide coffee","master":"1a1a931d51b74af1b4ba19a4ad97b2d5d8c79396cac5d7dd7883d83d79fa81ae","seed":"a03ee9671bd73e717e93830e97dce58b4b5786321989e9f446daf96a6f328d70f5d5dcc4c40d21b7dd4b27aa6809be2a3aece2f3c2469923b8e3d112c899e3c6","priv":"8bc19d1e811c334e4417013c3c85a2040cea8a3b61c18bec0c7520bab9dc082d","pub":"0224c968015439332e83d1737b7429c7bcece458a00a94cc83aff758c5386e3878","addr":"a069c8ae758f12a39e86b2858865d95e953afc67"},{"mnemonic":"prefer benefit sound knock exact pull clutch cabbage local burden small report","master":"9d8ac0f2bbe4caafc11f2cbd9e644e62ec5f54003701453e9c08ebb5b252aa10","seed":"a6d48540efcd54706017c3a5c47773a4dbade3044bf445f8e811b2b2a1a584230d070c296fec26c59deeef37a7487926783fc96fc593c270e90d7da08766ed38","priv":"bb6d7a3a4717541b7e33146b75789999b5d763d14f0962fc43ac30193e711b38","pub":"0288eee81a2e81977eb56b163ea601470d99b9024b5b99db2af88228842fc6d4db","addr":"62c96ec870a47a2cd540758e27fb19fedb11d9da"},{"mnemonic":"case choice castle monkey awkward kid submit layer bargain reward ahead type","master":"c3da04cb84f0f98ea8723f2b6d7e6a3c83b30e5ab8e0ef75bff51ca0b4a00707","seed":"c65366f8af99f805aad4f87c4f73c074d1c99e69fe65bd2bb0ac6a1062bf9595051133c32d5abf4910cda20b9e8a8cd76e22e692e31c02f9327eb703602a9ff6","priv":"71a0279874cd44a61fffb0e65d7fcfa0f4f7c24db912f18f587d6393f8fc1154","pub":"0265ace29b8dffee0f31e6bb3e42f91d90eceb13357e6d324ee2cbc41528ec1c4c","addr":"cd772b14963185ed989f1085142b2b0ae2e5f980"},{"mnemonic":"educate meadow confirm subject tube urge guess black friend later cement spirit","master":"28cd56e6a857ac35c617c4665c143583e0626dffdd7a5e0ea55ed75215bc45e4","seed":"bec29b984cb4df491eb1154fb2717662606fa63742febe91e10d19057c9134b4d4b8677ae23e006b450d6d485a67ae09a9c34fcf8cee5b22be821dab855cfba9","priv":"fc3c5da684c39ac6ae5a91a3b94f50680f6562187b5a138551d20149113d03f0","pub":"032eda5239cb98993519d44e7838c971f9bd8ef140da2fa4a95a70a2488f78f1ad","addr":"5a9adf7df64dd7f7e9e0d29b9fb90069e8230090"},{"mnemonic":"way butter chimney fantasy rebuild delay shoot toy ugly festival resemble promote","master":"599622e23b3db7ee9a6f8f95f5e39b2562170fbc4dc0d84b6869224b9304f134","seed":"a5e0489af660e70db67649c03622aa66b1a6c57d2593204cea859a146b1676893c94f4390740021a51a3a076b27239c7870db78ab7eea48da0a0177a10b8e16b","priv":"e16a619f8355c4cc276583b67fb2b2616d23d09557539fefbe915c6922e80ae4","pub":"021e6f57790a3250f24b6323cda9f648cd0b1907e5c7a81369d022748af8a9b0d1","addr":"9c7b9192469e256207f7758a906d0ab3c50e4fa2"},{"mnemonic":"sniff rely wine book guitar repeat civil elevator olympic wasp dumb often","master":"43a2904bc971e275f33f12109c2067877acb6b00cec32e3913725114b48bb2d1","seed":"cdb3853a0160f7ff9f3b6a9d10192c11ba3f74d45f4bdd4366c9153728105cee7e117de9e8b13be65d3a284fed6866ef4850898c9c2ac4821f55a9c6415f7e16","priv":"970e0aa6e521b0d938c41143fdd75983f5a315ab51fe0e2c79f5c37053e7dd70","pub":"0378b468aa83eb47cceddd2ec8eca4004a9e99ccca959ede92d9b03ce3e38d5099","addr":"9b285c714fbf2d0d9e39395b08d07e3eb87ed87d"},{"mnemonic":"salad print drop winner chicken despair bleak warm misery buyer clip glory","master":"77c5e3581d686b79e863b82f41e563fa5a487e11a782f609b4559ff5c52fa091","seed":"494e915c3897e9bbce29c8af192e7360865be489b38efb5f6484955a525d7120d7637921f7ce7cfe820fcc996c9d9d0edd76f8376a48549057267fc7193cd66e","priv":"a7e988c4a3e661ec230fb359e8f6f7b9593ef5bf33b9086c1460fabacd162600","pub":"02f91a2b6f0545a45ee7d50d6899de984dc0d29ebe6f75cdb3dd169c696daaa9ff","addr":"8bbb9c4b435d0f42ae033e2819a600ab49f3a21d"},{"mnemonic":"inside alone salt borrow fuel stereo toast hotel want arm happy marine","master":"0bbbbf5948912d5e7243baff26886acf316b6ce9f450ee0d60d262b568200400","seed":"afbec9326da69fa938e71403cac67aece539c5fe1156d34ec737531b6ca38b68e60529d589f887ae24899ed8ae53b9f8329f64d84fb62c95a7cfcad45e83f6c1","priv":"8e2532f0440ecb83316645940e29d0b522adc421fb9aa49a8016b6bb1ad34e16","pub":"030de4b35fe61c0fc2fb4fd295a9352e98c7080cc99a6f70d6f64229745c161be1","addr":"519f731ed0412ac24588381879f050bfa9b01591"},{"mnemonic":"cousin drip gap gift ozone omit road parent square lawn crucial color","master":"742d494238d59034ac2026b9a94ad7cf75df5e1b481de734c440a8507e2236a8","seed":"82a0149396a6cadb670d7286506e1a0fbdd771751b39562eee0531d1a2353d29229940395cfb0e00fa1a6d26c9d117039d2a1738ffc93b34161e29de73ee41ca","priv":"aa04e7953be44feea3ddf077d01644f5b4f1eb85fa89521dafe85c341896104d","pub":"02f0d80a4cef0f71606ef476aa04ccd34182bc12b0c45708b0aac49fc2781fdd35","addr":"4a2358c67e4faad28e0d50066a9da25912c1cc57"},{"mnemonic":"list power fresh cricket must expire globe noble park eternal tongue thought","master":"c5210503dee7e7530a84ebb0ca5ca9f15670dcf987e82d67ab2213ae76275b95","seed":"7e482fcbe082d1e9520fa44c8ad117f4d2fad839c8556ea3aa4096a7b940009021611938b4cd2cd82434f5ea37afb4dfdc3548af496f0afad91bfab3117b241b","priv":"c14825a7bf41e5daf44cd9917f33c682da9fce939bab16421bd7dca1e04644a9","pub":"029a20080d20312ead4612ee0e9cb5a2d641ab929865cfb19c3234c838e6f75c44","addr":"750b5d9c00adb243177506688d9bf4943a3ff666"},{"mnemonic":"bless goat worth boil street wing quality visit honey couple upon sheriff","master":"c66453fb857a3deb4ff276dafe0437ba6dcecdcfa67b095053a060f9d5e67c26","seed":"b29cfcf2aaa980c88ae8e0b151601e40ccd6dceda39b4ae324d6d906bdd3342db7ab0f20ae75c1f1b424bac7b679b252532b4d5990d36d91f7612120a5c22b25","priv":"dd96f23418a459bfe23f676db88839e63d1bce7ab58b76d28e494abb1da9616b","pub":"027f81c68bec50476477e5d5524919b7e40509818a16da7724c62de26f23fc2d65","addr":"8856e5d456716486cc794be572454bf41f013404"},{"mnemonic":"slogan outside park during produce key meadow cricket glow choose pass tree","master":"4d6772450ecb47b86c562d5accee0f78c09b182ffe324fe0078fd324474f94f8","seed":"a050602af53f0857804abe575f05a664c292abe43782589fbee0aafbb9654988b6a46ac26d93ba14166d9c6f887dcc1693c2aab5a782f4277c0f153e3572936d","priv":"92918d979c61e8c6a256548502714cdb0094ab6fbc364891bc094bdee708357c","pub":"03dddebad5131d93de5078fad3a380a717f2d2dcb65743f05a84dcf7f29a26f9c4","addr":"0c34fd5317283be4b9ae5a953f539028a5f201ba"},{"mnemonic":"coil burger alpha grow lyrics check page spirit sea mention course fame","master":"9b4a2e05f533427551d7e44ccef49057b2fa0ea57a5890480133a8cad67319fa","seed":"013af1c7f10ada279b632bcaf0211006d13e62165646ad3af69da3b60f044ce62e9bdc7c1c6758da037664f18f1a1189683db16fc2ed39192a7737154984f417","priv":"1a9a114d594bfc3e41c3f507114ef3b872b70563fba390dae992317f67fa0241","pub":"03c5ce908d87792e067ca19d4fdfc15e3abda1993f3ad1f1c8bc5168c58c3c4e93","addr":"cb513df0682dc11374466aea70d7cd9a45d4fd44"},{"mnemonic":"napkin mystery powder birth crowd fun disorder bike lucky core dance punch","master":"8fb55d8de947c13497a83c9631a4fe9b0b491cca2743a6a056214c1daf5cd87d","seed":"f8c5fa5fb7581ae597edbf214a2755ac2bdd58dda03927c21c983b545674de67d69e258a196ef36e3f3e319863a54b521ee1e23807df9f2432b5004b563c2509","priv":"3846cab8eaa4af163f79dc83e5968ce0a59b7cc2f6f725767f70fc1780c00599","pub":"0399eacd20a2a7426a0946fba205891d22fbc041d264372c6bc981c3e7200995ab","addr":"3fc8f92413e731f59ea2768ca40393a92aa8dbca"},{"mnemonic":"sock real audit mind page price rocket home economy glove water rubber","master":"205d5de977b27ef90aa092c70456b516aedb62bd164fac4e4ff638be56d9b9cc","seed":"129afead267c2a9556c3cd3618a29b659e98d651f52259acb1eb4ffbdd6e61d87ecc487123994fe4eec4f65808d9cab6228f8054f8ba68c59a6de8a1997bf420","priv":"94f20ab0cca31de6ed426a818e8cf11299b8112905d0bbdbed79de137337983c","pub":"02ebd561df3cbc137540c7b9bc0c919021d4c4765a9f602078b028e469dbae397a","addr":"0103fbf9c8f122fd4ac6635f9178154d66b619ad"},{"mnemonic":"option faculty depart orchard foam orient seminar detail castle warm pink pen","master":"90dc715ea43f91c4777eda4e9c1dfc9f909c2cc811b55575410d21c0a9fddf4d","seed":"94c306f3e3aa6e991b0f9a8d42af67d058bd2ef0ee233467ba0d23936bb0e982029a887e6b1f9bb9e537ace50e2235a9976dd79b03555cad90da5e73aa0516ea","priv":"ff6cdb1cd48968c64608ac08be92e28f9808fbaccda86c1f5fd6d905d49dcb5e","pub":"035162c7e9506539401c3a0c181643ca621f5abda005f6aa14792fbaf44b862edb","addr":"d724d1f5b4a987109f0c84cda0ed290d88e7c668"},{"mnemonic":"jacket goat equal source proof congress potato solution rival lend finish bulk","master":"cdef2991efb462f091fd3c4e092f8926a4f5e18d6013c5545224d3ecd8e61821","seed":"01e84e54b7faf2374a4b4bb4e56f07d8b13f0dbb0191c23fd41b4520c8123f26b526a463404ce264283c0f3d41c5927681b6fa388d9a91274fd693fa265fab69","priv":"feebee90172106d859d545187adc06732e188580754c96311d9acd825c1ec9f4","pub":"0305697df9f3fb6796d40145316d8f90ded12e8ca18a5956651ee96ab26e69001d","addr":"d0955fef013a29bc7666f1a4696eed57441e2633"},{"mnemonic":"canoe tiny raw account weasel fork still mechanic cricket address squirrel void","master":"9382c4fa09665993b8803f3cc92913c5564be40b63b1c19c81f8f9dd415ba83a","seed":"5c12270e9fa3ca2a008729b1d5fc1a8dfd52dcc6461969ae71d2f3fe04a7c59dae4faec17f84567afd7bab3c5934382ef3d8bc6dd6879fd3af22bbf30fa84486","priv":"9f1d4cffae3c7ce9fce5f987e1439167273c83b6b2dacf1a11ed66c6053c2091","pub":"030a2c97fc8aa57c7fcf7d21c29fbe00ca0866e58f64fa56a85cba811445e0ff78","addr":"48b6bf075dffc6f03e77c67b3f2f573cad8e28e9"},{"mnemonic":"topple recipe surround useless elephant fan gentle wall since street enough harvest","master":"353c700ba61c0b56bd7a3a65d29d53e17bf99abeec68fb79c747ab006592c331","seed":"9fcd29b6b00decb8c8e2a14d5695941854cb391bc25351d7030587a09f64e92bb409c4bb7429329504c19c7619212c731216fd5568dad8e2a1330fbe6efebc7f","priv":"c900c7134a5b92a2a0d3bb0c117c08dbd67f47f7562cf330d619aa3c64b7ab6e","pub":"03e60ac1d0a9ea441a821a780d894ccae6ffacc207ab39ff905122d435acb17a31","addr":"4202687424a7c883cb1dd96353e7dc4697f35f3d"},{"mnemonic":"any myself dizzy magnet hand drift garbage reopen fiber great stuff burger","master":"74aeb0c2e7a7655668238f6a090ce3657def8df96d7af744ddc52d4900451a6e","seed":"0e0a18cbadc5ab76ef4194f7eb069f266ae34c854933b3fa9603b4fa39dedcf219d73e3b283e08a067fc994d46bc59e1c42c18a6a5609111b8fa690edbcf5d01","priv":"f426009813fdcee2b4cd38966e6214f9559426705621ca8b862d2a4cacbbcd4b","pub":"03b75491e3a05b32aff6972e720bd694ef496f6a753ae309cabf4e9b5adc2fa6f9","addr":"5e8c4290a5ab9cb6af1addd261ef37ebd0e6bdf5"},{"mnemonic":"spirit draft dilemma cause main admit thank slide barely diary humble enroll","master":"cd538c907349401ba6605eb1ca7923df44a30a049d5cb9d5a42f0f66a5cc5f29","seed":"baab841c07bf90c8b1bb82c56e355fd6de5a2c93e86f0f804a897ff9c391712d2ec5281582ec15337aadf1f0e835a8c98c42e86a4869e0e1ec47c063fd19f04d","priv":"cacbe0003d96c8263f09f328663ab1b5c74cd6635504b778ebf2d7d30dbe524c","pub":"03e71d9a3ca15f3d481ceeccafe14e6ce1214cf854aa245bdbe28e2b27d2d40353","addr":"f4b073ca68bc00077027a2793e7d6a24dc4b9434"},{"mnemonic":"state subway clip baby novel broken stand soldier among fringe track wrap","master":"0fc4edec9e5891d61e74dd1ee6b52f9601010cbf2bb8949c1d38339ec428bc88","seed":"46ff0c2553e5494666b6de0fd8fe734a8bf523bf6953dbe8622c66c7e9645670624f85a3ef2e9d090e9b153cb5923460cc1975c481a5c2868e51cddd15a9ca35","priv":"4f92a517fe46051132e74178a69f94a2dcd9a1120a54e09188e7d243bc97db67","pub":"034c0b78b452dd0fe61464dd0a44facac7ba28f126fc2a60b82f82efb8cd153349","addr":"2da2b5a6ccff13b5281582aec90f4c9ef28293b2"},{"mnemonic":"nation shuffle siege easy birth motor also family infant siren upon warrior","master":"506af6e687482c47562e38ac6a41e46e2291d777fa0f7abe53ab698065f03f17","seed":"6a4cfce60d546e03f17f6d977ce6d9dec6f4cf8b6a85dfa3070a34ceaca022c5ca4c6f187f1c23d720078c41d540fd04c56bd5061efd027eafa9dd8e650e745a","priv":"b9354ed6da16a0cc9b4404d3226de88048b546f5a01375466aae09c53af3cbf6","pub":"034c3ef56d2471b16f17ecc3e1d8695e0b44057fc8ccf76625a271487781c735ef","addr":"41b9aff0432990ba791b76ebc13af27c634d4a51"},{"mnemonic":"snake manage joy opera creek dwarf wreck merge oval rule elite coin","master":"02a8c9db54ea6d54186726806f81935dcbf6b5fcb9e91d03ad618fbc9f9d3879","seed":"7d6db7e19c66ac1f54395a5a4f7d878e19c24e0cb720ab2a146e2502b4c6832efca2edc2d3f1351cdeefb53a53d66fe64455790fb687234381c017fcbf692265","priv":"76d273285aceb1d4686167749496ae591df991af93a8c29bd627747bb2dd811a","pub":"0208492167c3478e65f8a31f051961d2ac809faf7777dfa7296c54cc3639de71ff","addr":"b18dde1cccf8095ae14353ff4ea995b5beb01dc5"},{"mnemonic":"edge aspect sleep design jelly vapor car blame dad pizza own possible","master":"7d938db55ab464a9ca1e9bf65fb4de0e25addf0ee2e0faff2fcdccaf209de40e","seed":"2d2530b934297fdab1c193d6aa9b93eaa1ea353e776a8b89118b31273f93c2ae78d7b98f02ce1cdfb8465fec0a97b29b563764409813bb4b893a3683dc9d9bba","priv":"c183cb66c406d72e64eee84dcf330e61514028a82d56c56490c4aa09e26dd987","pub":"03e7b33c7123aea9ee99d349eec2603ec7c32c2863034fcb87df8399b73885c97a","addr":"2e8c251e27ee90d6b8f05fdb2c7c51f92b277f1b"},{"mnemonic":"cancel confirm rent key glow confirm indoor chaos flag curtain indoor float","master":"0b49f5cae01e50cc4c05bc466ad91d84ea13896d93d468f951666726de9ccf1a","seed":"aa4561254b44e01ec6b00048e4346754b6626000cfe8e566180a99cdf8739c0c76e16729ee4c73b055e7c59f5ad30abed0ecbc1499fed87ee75cf0cc89f9a087","priv":"7c4e98bcc55251dee796fe3960a8b842322c51da57fe5cbbccb997714392016b","pub":"02ffec93ac53e470bbdc2dfa89c2389a1db404ae6e57e2586b5e638e1397a85f00","addr":"c69d4ef8ebc35e8a418b00c7263295052191763c"},{"mnemonic":"become search judge nation inherit short history resist luxury moon garlic protect","master":"72a0a669e55a3058f1d20be19571673ad281e965667c6bb7afe83e3e89f0f30b","seed":"75518eca5861396c14bce066dadee8eb5f9a74e7099cde11cc9b0f1cb1fab1df87b5fbdfb675761868c81dc7dfc805bd2e425678b0a562447a6684160eaa6264","priv":"276b9a47745eb9266100e62f8ed8f8fa2ee3e42e36ef8e010109acbfb667c1ea","pub":"03a596424477e9bd2500b20cbd0290feb0d30b755562561c52973edf826bae3324","addr":"933df39b013756091a3455104d61244fd16085eb"},{"mnemonic":"profit execute broom clever wing noise crew purity steak assume april leaf","master":"bc7e3bda0564fd300d6f0033fd13d75f7bec7464ef692bc96f83e7ee9427c6ff","seed":"353344a63ebd9a6e78ded4cbf52a5f29a64cb7c1d90b001d764f5f4fa419f569181c07f8bcca22117c970f65d6f9c06b44e934ad21257e0f1bb500f8a26db987","priv":"9120e0b2f502a6b5b5804fb4eece7754ed92a59fe482a7a1b4190213dfb60457","pub":"025f809c0f6a054c4b875c7bce8791f23cc9daa6a85558d6cd48818f29d6b978aa","addr":"f25a6479cee5506bb53b4b06fb326a508ca77abf"},{"mnemonic":"menu library ten glass coral taxi prevent learn pelican garden magic busy","master":"7f4687dad9ba781a99edc31d78a2c68875f40afced8adc8a66d55efd3f68c169","seed":"607827fdd6fb239229b116e73be3993a6596a09cabfd19dfb9b03482d052568c5443f4572393b0e9d460a05832c5fc41e811311d9bb501c43ec75e234c61785d","priv":"5605ca7fee44c088165aa3474327c53dcc13ffc73d8b4787a8b0dcdcd7fdbf3d","pub":"024d754ca81f064cf6dae96fd1344cab8e917e46ec639f677eea33a21bb34462c9","addr":"663ae3370a5b68058dfb1b2efa62f4a2222b3a89"},{"mnemonic":"green useful pair bulk excuse mixed train talk genre inspire much eyebrow","master":"318169a8d80848ddaaff93bd0fc68b1282aa8f5e6f058044de6ff82c62446005","seed":"2095e4cf4ec5ea142d9300cf431e47c6ad401ab1693a6518d5cf7950d1d5d7e0c6feede5a29b3721924074ccb527cb962ddc5745b46b794dddb4ccfdf31ff1fc","priv":"e9c336d9e59b1f4c822899ec39d256174521097060db24f305e3d4c3b12c35c4","pub":"03f44d7d4269f3aa14466483d80ad483af1307387170f563222e843c554f215b00","addr":"359955417cbda69e538cc001d5aca0bc59a077f2"},{"mnemonic":"follow half desert universe canal guide pony two fuel nuclear pull tag","master":"1f3ffb60e2293ecf146f414b28ca6a81dc6768c34450f2907eeca2851ebd92b8","seed":"e692e2810049aeeb25672c3d07a411940a7a7308e9a2fed2a67210d14dbc1de2258edba62437cdef1f913cbc65cc8040bd52f601c2edc51cc7d4744dc1781147","priv":"3534e3fc0a973dc314ff3610ff02c07a1a9e5f2a2003f8ce754c691acf172565","pub":"03e7f0b65a215a8d78831f05f10db86874dd0d900059bcbd64d83d08b05d8a5f7a","addr":"dd74030f9d82c0c40d2798e731b7c5eec61a0213"},{"mnemonic":"enjoy chief later rebel avoid emotion kid reward find patrol leopard destroy","master":"0c8eadc9ddd4cfe9b2c1759ed1372c513bd5226c1aa4afea02c8273d226182c5","seed":"d522815571acf5953d4bf6894c9bb5274e876a5ecb13310dc3db73f2d55544174d744cbc7d63a6ea07c8b7bf8bdcbcfe161ac57dc0ee488ed9fbe0d21a070d95","priv":"92e9da2b0ffdfca29c83f64a685f6f512eb291b5b130c4c5c506bf713016859e","pub":"036161fd5b9ff9bafb22e61860d1d3130e007839939eb3f609352bc73376552d17","addr":"015b10da504e82eaa290f2f5057a2581db032f51"},{"mnemonic":"giggle medal hat isolate congress else guitar below chase federal absent photo","master":"eb8a28df4360243f9dac597fa92de914aae4133af8f3a18c2506f2fcdaf4945b","seed":"95e8d77d1709418b4ac0b8cbafa302e2019644eedfbe52233ba988b55f5451781583ecaa8b1be51ae18e17ee5bbb35d3826302701588555dfc8c851844161acc","priv":"35da7e9dd8200ed89f461c484e63ff3ab68a9b1cb162bbc3331394f4f2ab8793","pub":"02bb24e03a89e6a6a7d318effa8ab645c46e5dcc4f25c3e4d75308144b5d2a29c2","addr":"2c271833daadd196147eea05df28dc41314a27ff"},{"mnemonic":"use protect bag nut fault off absurd involve ramp cereal symptom biology","master":"0f5a77192d1a941ac59a41773ea7d6b4e87295f4f82fb0f43c5f12d605afd56f","seed":"7d74703c2442bef350254d3dce2303a8a56fe4c96e4ff80ed57756dd7b8266e566ea2cbc3abba9c724deb9c45fe4cbd6a8c8bcc4bcfea2665e5193b781aea933","priv":"18c64949d8fb6fd392fb4846c119eb71a6021cc1c22991706fb776e8843a381b","pub":"02b94512126ed2d6412873f61bf16592f02df176cdd4c29fcd70085fe3d03127f9","addr":"15e94aeca2bbd591e337d57152663e25bc094747"},{"mnemonic":"tool twist jaguar excite borrow social sustain normal echo fork alley hybrid","master":"d6c9dbbbe44afa97a5f6365066a11a8fff48793d4c6720ecc419bfbb81329202","seed":"164785fe64846a802a9c134d094447035eaaa6cef2eec9d5fea9aa74d333ab31433c7cfe4b712b6a3b04048d239101a8dae5cb4ed37699658725bc2186ffbe41","priv":"cc5c6aba1301195af2626001e3d19660043377c75e7990063aa45ebbd30ab266","pub":"02ed0554dd8861043aab2105d7dbc83809193c59952e829421f7b9b6e9e56d6f0e","addr":"7db5745b47c147e40ea9fa2cf54488974d0bbc4b"},{"mnemonic":"approve bright target voice relax control knife again special future anchor crystal","master":"332ed66acc5c24c6a19c450775329c946197872435d77b0ea594d4632adb04eb","seed":"da2c0f3433cdb37ff151465470bad3b227114c46ddb025542133b4c10d942bce6cd34726c85e638fa82c4295d8fe93959069d35253ee528fb1724be1d95447f1","priv":"41a2cdb57b5cf352256bf9c2b155016160c7eeeaaa5a3ab2f06cd1df3e4bd695","pub":"02e76d02ff3fc76c21a995add6962e08bddec4abe23994668770d6cfd02cb82f42","addr":"39dcdc76bbd5c2ad8821cc5df8e816c7293a82b3"},{"mnemonic":"like wet pig pause steak name tornado else limb february secret subject","master":"e7dea9fa8e88520065302d4227d84a3d4226aac4b7c04bc78dc7e68efed73002","seed":"3926c7749d953ea66414c369fbe3f26a7a3f442ca98a277dfb362eb74432cbee32e44a6174f1e3fd16aead63e798c894d88ef260747de219c1e8631d800c13c1","priv":"804cc7215bdd8073b9c7f8e1ea2648724ec3e2593f1c2213875b6e4022513ae7","pub":"02770a43f589045b52ee29065b2cbae37ff65a0ace98c88531b4cf88c94c3f977d","addr":"cf82c749e61e9961ab72a76cbd3f71e0c5a46221"},{"mnemonic":"memory core ice box board flee ivory apology acid warm sugar resist","master":"4ae1d18a2c3cdc0c4b383d20846866f1a2ecb799e7d2c5c125e81c30856a2bfd","seed":"5922aa04496cda9a17e2f474805f095fa2bb70cce0857dcf92acc7737af2978145cdad0aefcf201fcb5aa2666a136b2eeffddb5476d9d49c0cf0d76d14bb332b","priv":"fde667c98a06017967b9169dc380a7393797486d07bba6575629f0661465a67a","pub":"03f8e990fabeba27ea70082b69628f95e5b86cedc033578426995bc1358949466c","addr":"efcaf3eebd6ec30c44fef92eb59327758eee3334"},{"mnemonic":"vintage rocket today version chair avoid mean play decade sport frequent magnet","master":"d167f9af9105ff115790c4d31ca48e4ce5cb3b571b4596b8ee78be5a6290e3c8","seed":"af58c0eb0e62201b926ab4c04d67b942f6ff40147ccdccc02a3df295fe69ca661b2a23fd2cc0f9148c4e92165ca9725014ff0abe5cb7acaeb513cdda359dced1","priv":"33b248b4a292ff6f26455859924eeb646290cd54a2cf587c5527e2f432fcd6d4","pub":"039802bdb2bedaabe4201344c1e9276d1981f403151e2cf179e4cd13861a54d568","addr":"c9ed9bc2778a33e5712b90843f1b0f0252c5c3d6"},{"mnemonic":"crash lazy cruel match youth note deal rack gym robot work pioneer","master":"13a82f0a499ea45154151ad95d52c3f15d84436c21d734b5ea105c902f85559c","seed":"c10e386ea97bef67bd97e59c5da37442ffc968893fb80ade82097b87a76dec5ae128962a4d69737a31eba4f1606e030d4c85df6733dcbb03e209a4b7931660c2","priv":"eac145103c713225ce05ffb862cb921797f9f88d48628321162798117e37494d","pub":"0260d3fc9e0ef5e8c38e3de4da944d59cc575aa530dce9fe8d95fb8eb68d6a4bd6","addr":"07aa44afcad33d8abe6e83da72d6d556a3720924"},{"mnemonic":"distance desert squeeze eyebrow episode enough sorry gym still label mansion thunder","master":"0f686b29302ac0d5532794598b98cfba275e4fa3dd0bef15c0b0866bfa0c9c0c","seed":"9fb57b9bb255323d11b7479bc34dc706e42e6ae8bb3f3af212b0807ebc9fe56a90a36c92bd17eda435fd0ce87a0465cd10a371b44dfb2e1531c14c29cf8ba466","priv":"7a140972fd7e2499dab8e22a52b0cf79c33ef58d1c5df578e30515e627f044a7","pub":"02332ac4729e52ba3ff4344ca262726bb58595670057aa967451302bd19579e8e7","addr":"f7c0cab3704f9ba669e318d17e33b1b5a7b3ac3f"},{"mnemonic":"zebra useless blue insect radar century lumber clock daughter slight quote stadium","master":"1168c3de22b717bd3e840b4ac6334ea707764d25bbf7ac14cd5d63ab127fe2a5","seed":"df9f562a972569f8dde2ba35e53c97c536ca3bffbbdc990df8cdbca812747887904a4f983d661035de0b3a85677d0092edad32ef528c926bf31541ba63b8e019","priv":"282da5f2191001a4bdcdccbd0000317ce62b09c0a48626b4fc2b45da6d7873be","pub":"02790696da42d2b21cb031e9b77bf855e2f5a214f80cd05618181433eec02fefb7","addr":"68f483b9d998b9250d320c43278e423d0394e8cc"},{"mnemonic":"laptop pepper arrest tribe glad keep behind gravity ketchup scrub bottom parade","master":"06a978fb60c517a506d2d8fa17956f144d1a5d57c0c9567320d1b21b8e7871eb","seed":"38c05886932cf2bca3cb7732e92b4086ca0575db2deaa241a0c9295eb79d50765a4bd92a03b76255144f71bf6f197c4b5eb6e3de8dd6e6ddf6d15ad18c90de35","priv":"62c37a4349e0d4edeb293e50adfac737ce73e3e0370439c561d117bd4ef23f19","pub":"027d978c9355669fab1ac4db17cf12a103f7501ca2011e022110d5b4398810039a","addr":"6e0cc4997d42b970855685f8c9a196b97b2d5ace"},{"mnemonic":"alpha slogan usage lemon cancel cruise rice crazy laptop donate city fade","master":"e3bed02bc801909cdfb1c65b6648f4df19b8927199e3d0f069f6713b13f10123","seed":"9b73467192fc857c552d60dc0223e3c4b604cb1cd50adcb61c9aa723d245e3ad44cba7f99f0f24fddd0c78b1bea41a86e682c9c482b115d575b9e3aa27f5650b","priv":"7b8d2c22c398e4a046da94656232cc75cfd6fc0e0f8191b673414383901b724c","pub":"0279989cb1f22fc08f871d023632a379d038bd9fa902a6e46f8bf0a780637b4efa","addr":"a4d3a5579aecfd9790dafc6b4e89972933c46d1e"},{"mnemonic":"chief runway cloth craft depth firm task ready credit multiply program smart","master":"d5d076659b0e0bb061736956cef361aa1a6a0ad6a337a6ccbf4b76a40184f405","seed":"8e60f22168de4f2a6c9db805a6485379d2facd7c99dc36981d0e209746766233d5d5012b03f8556195737bd71b88fc54eaea0f68e4fae4afc19c133f2ca5fba8","priv":"adee70b6b1dfc264606e0b1c848f7b5deb46de4af91c5eea9eac962dd085bb3c","pub":"0226458b01eb1e4f0d7901841662b2046f17e0945230a3b1b2d8a65aaf5ebdbe8c","addr":"c9d9042f15ecfb29595ed4769b1c858d1f627468"},{"mnemonic":"invest glory debris naive pill detail sister cattle physical favorite squeeze rice","master":"2ffaac06321f9aa3783155de5197448783583c027d8aa9d9b787d2a07b7a4c2a","seed":"3016cd4934eca835d3246cd7c05ff35a0ca5e3e0c4b74bc70aa31582a6da334c0f7e91458bc049ba3469a8aa7f216d27d44ec8ca4cc5f376ea366a38106c7fab","priv":"cfa4b9f12156ae24e632f318be54072149006db7a4a611403afa97c978351900","pub":"029e3e54ae265087ab8066bbb7e935af291b3ef5120b00f44019d9dfccec8a4e9b","addr":"3de09d0c1ee046365da74449b6c98ce3205e20e7"},{"mnemonic":"private alcohol loyal depend force sand twist couch beach report miracle abuse","master":"8354e18ddc5b38a6e26f9cfefbfd4b27ce3f65c91174c88143d947104ba91faa","seed":"507df1232f5cddcbee60de019cdba03e9337896ae2fa0b26855d48c34a9a2cdd1e93f2b66e965cf7666bf7c7ad11f00b1ff764b675a5c3df8a1a17b253cb9e54","priv":"d486cc9a23d56e1dd8d865ba042aa5cb9f8ba06107988e1a0d999b14da3b667b","pub":"03b75ef54ae8faa3f3565fd6301ba7a70114ddd184eefc74e716fa2e3c2df6c2af","addr":"2c82b93644c227bc66fc06c922ec1566c3e11c9f"},{"mnemonic":"memory achieve vault hip prevent category cactus aware venue dutch raccoon fresh","master":"16bffabbf3a08959dc9c1780781133e6aa096cef9d9542606c52e8ca2ea0230b","seed":"a4824f3efa0c730e3669acb08b1cadc25f3f63943983bc4f4f1505cba5c85959ec16c2cab98008d10e22f3ed6239e3182bd9b603ae1536755d222fc60f91419e","priv":"7c2b66b79e2e99ef0f794147aa93d3da8e62ebab7ebaad7813b586ea5db28f9e","pub":"03298a67eb59a27d636c2aff41d73ec658c87b136ea53a534ace18fabb76486dd7","addr":"89d5007339cb57fdb72ec629027583b8f3aaa5da"},{"mnemonic":"alien floor comfort six demand helmet light actor distance success buddy burden","master":"36ed5350c3672be815eb365f61ef0d0d42870b178aa34fa5c5b7397cbb7ddcf9","seed":"ff46b78794944b4372affad3f096339f8d9c03631335d9eaf4e4fe68a8fd9d58a6d6f37ba31cc06bf57c5b66aaad6ba64ed28ae4188195227fa07bfa587f2f1e","priv":"3bb1328f115390ade3c08de4f9672ae96bc3d5b783bd9d26d7e4ec12d5818b8f","pub":"02b37bccbe6c7173937b3af482d4a92280b670d715e2619fb1fc906973bcdd4d7e","addr":"935788847e1517096b9f31ccc2ac48c2e5a738ec"},{"mnemonic":"soul upgrade rebel wash hour gain lady side own double immense merry","master":"039c6373307aad9366dbaf9bc17e70ec1b5fedbe382b9bddc55ff606baadc0ae","seed":"37f9cc349c21182a3b34c66ae099de84bdcf30ed20160c7251f43f831279834dccb95745207defbae856c0a38dda3d7bb251b97cd2150c155ec64c680c82ba73","priv":"377f589beeb549285eee37d34e5ca85f4e00af8483027afb655e47ae05d1089d","pub":"0266f715310d9fee40c5c95da1b6a9a9aa51648df99326e29399b10e9cbcc51f7e","addr":"89a2ac362b8804981ecb6043bbea65587c997f97"},{"mnemonic":"stove pyramid vintage defy belt high have leave comic brass online caught","master":"eb9be6fc059732dd9eff612f2e4ed7121093f07889b87e8c66e103b83724e1f1","seed":"2537a9ac9dfe5c31c0f40c69c9fbf6a8ea186d2cd6c6d2134aea2f3ec2f28f5339fe623ff593da08857ee84193c54e204049009f8c86ded026a4cdca633b2b9a","priv":"a8f4ea6ba49c421a7d310b83bec05902cf2f2629a3845e390261c3917af3a17f","pub":"038a6d8d8947723f80f65dee0ef8579dea5678b67bd258ca591d0ce5d2ec060bb6","addr":"38d61629cc3e3ce7a614825cefb125ddec897b59"},{"mnemonic":"uncle seat rate acid ketchup frame marble limit demand exile wealth dinner","master":"fb1b8ac2f5bb56231fc93615bd8e25720818819d3df4ffcaf63dba0e7e7a0815","seed":"8e0bad11dc7784f19715ab90e8846e84155680027997fa0314fe32efdb3c026ae2918740d362744dff754e75b1d54050f5f02237cc4e15aad165ba10cc7b046f","priv":"cd20e39179c66285edeb6df646de54f3510f166bc1821349d779fcc103f13839","pub":"031a9529a2b5da261bb540f43b860b540490262b10aa09d419dc3b96d2e9aacc75","addr":"d1b6e238bc4a7e63e0a15f034deaf1a5b5161928"},{"mnemonic":"human benefit idea stone hospital misery warfare door goose unhappy crowd stadium","master":"b8b5781ae12ea6db767cc01d644cc52179363b72a7b67ee8d7d20fe8ffe4f41f","seed":"fc3f6e420f688e8553a2cf8d3a6a7198c5523aa6e3b00b3e1e4bc352288f0390951bcf698bafc12334522e04979fbcfb36f12fa789a85dada9909304de559193","priv":"ca41478acdd4f0413a843cec85b2abff69aa53b4f900da10918810b89127faa9","pub":"026096644513086624a2ba26daf7da0e4f5c7455bf852336416737fb72dc1137fc","addr":"e57448e2edf9d63574db45ae06510a521bd80522"},{"mnemonic":"violin cave ask subway session gaze settle pledge decorate alley swing evidence","master":"4c099f1692bd7137bf8b50e51d2f6fef0236f88bacfc4c87f83fcdd43a3a1aa8","seed":"2c3cb4cd88a4de52badeb41b1e3e1d61daf61369d5badc36601c4baad611eec459a0a1edb4046cc21ff50f396688ba6bd37fedf24b17ae447b6f7ce4b0a7e81e","priv":"5f1d6278c5d244334eb10d71e4e642017c7d3d63076507b7efc9c63c8c7e5df6","pub":"02c26860be7973c41ef183ad6ffb901d07bac225b5fae444df8abdc605c7a1efa0","addr":"fa055b86c6a1da4666ea4345e9d98ea32198805b"},{"mnemonic":"dance hip come square bread reduce ask riot shift twenty scrub derive","master":"964f2b5fb6c3f11f33ee1bc9db915d820834cb1ddf9cd35c17effe93ab3e3e14","seed":"bd4b3ee7b0042661b560cf39f8a9f8fdb70bb75c7482b09b296cf660e2b6360b52263cc38a1c546d0f935f0a156a36089975a7fd5a65ddbc960e4b6418e1d337","priv":"6f9b31f5247941f3a0e3954b05f77d68c469aaf25f0755293b1fdb17bcba6478","pub":"03a3cffd72d5e6360090e4a0bfc8e1c691add6690614ee5fcc7867780d686259de","addr":"95ed911acfc3d1332ecd9c8b93a4372a27911cb2"},{"mnemonic":"excess route notice shadow post business cram kind ripple odor enhance future","master":"ab1d6e993bb9b533e854f15591af9e8a74bd29d000d5c8e77b98e3e34e96fcd9","seed":"4ab45c5fa427c0c81853c3b5c2754ab1f54873b0bba3710204fe40ac69c13d21ae011fe9c835566db313947749dba53496f84709b71f4dba08a7db209922ee4a","priv":"98e0136920ca7d3844e7fe1cff54db7229be2412de8de831b94235e8ddc2696f","pub":"03714b295b752de7f1cefd195578fa7257d5dd8b47a39e584fc4d2637a8f730d1c","addr":"9019db50e3ad4d56ece94e7d5067b27a33400a4e"},{"mnemonic":"flag maid oil survey first force grant equal brick dog where strike","master":"85c3230463b78c924cb57ca637b5af3f5910d962861ead4b16af5de1b4be274b","seed":"6203eede1ed8164b3ab32049def751b55d82ba6807d97b3eb094b4e6327ce732a54de31a9fc6178266a4fa7e6452b004d89875694ec13ad05475d7ad86ec3241","priv":"1aff198e964909f87d5b15c1693e5d191b9444109a7ea4bb58952752623e77f2","pub":"03921265a585b1ec932828f58cbaea6ae709c7f2af0691ba3f2f0636286119bcca","addr":"7f38e5412e9011a5ca91fdae448bb8b15a6bc4bc"},{"mnemonic":"jaguar panic thought mimic voyage able inch pole vacuum twist velvet pulse","master":"f7ded49975e280cc791687730225a70a89c79f5245ca72a7e538295aac1ac710","seed":"52f058e84e0809ac44c37485ce9da088c1617f05e92553a073378a0a0b3c9cc95aabb6d1dd2c99d084b03afb98024f9381d958f6ee5f516baef2db97c493009c","priv":"615bd1f706f7a88d9816200f4c3085ee2c748f9a42b25b444393f8cf2b46191c","pub":"02635816dcc956bbd8d9d2ebe94fb117ca20907a238c99245a9635287404d30e78","addr":"78105acfd135889bff6f0f70c92c38e0eaad72dd"},{"mnemonic":"pond stairs still avocado rapid peasant direct debris spice cupboard pledge budget","master":"6b5a25d678b4fb67cfa80ca3ab21cd6bd554e5434c4fd362ddb01cb46bdbaff1","seed":"69d5e57322a46cecbfe1f895c1b22e9bf17d418a06b73d49e3fc83012ca2649818bce2310f1b195c5b4873d31d6e797c5edecb8f30377f5e638ba1b4ce2f3823","priv":"9aea611859425145441b6455a0ee206327c2de06e2d26974197873f2ae9366fb","pub":"0236498052412d5c5dc3ef0d195a2a537359fc44246fd9c8fb39478caf29e668cf","addr":"faeac1dc66f281714303d637666c09606504a227"},{"mnemonic":"december cram web moral spell comic tag lawn since helmet moment act","master":"ee67281243cf2f77274bdc4b91a2ff1e660197bbca2fb42d5aee60e6f55ab4ba","seed":"8052120f630a33ce44aba16f1807236b1330ffa6170f985c630532fdd2713aef0e1d681699c4f67a9b9a72fb0432335c243828bea29489c92d1b01d016257ef0","priv":"a98bf3a99fff927ec2b0f208bd8a3a586ccc2571fc6333c57f2f1b7c22d2c8f8","pub":"02743cd05cb536e2384da4e0f110fa4f807abdba3e5e25e426e32fc4b5c65f761e","addr":"a1f0b4bba0be62c6b2497826612303892ed77b05"},{"mnemonic":"basket tool pink steak antenna process accuse brown walk pretty only check","master":"ce30c7daae3e0a5293ece548d66f07852003afcc80874474f5a94eac4a00c928","seed":"9572350a5a22b4c0f95fb2ce61c1f5e5846a3c2fd6e4cdfb2aba0d05e04e5b67f0fb394521b73836bcd4555cdda44acf0ab7ba43b475795868a6056295967822","priv":"edcff07e74dc83738075605d76a0c6923dc475ab122da2789b5a195a421c354d","pub":"02c03e024e6f53a1676dccd7850cbb2bc1f6a9a85b1da7cf0ecbf1c653a60dae1e","addr":"ac3c8907bc31fe66155a47a37306ff7b75905133"},{"mnemonic":"since equal mobile pond island before crew boat clean behave climb good","master":"1d1eca07fe5058a9dd7b26d1afbf2f4f71acf1420dc4ce9d2e3959734c442f36","seed":"4167279cfd447453ee7f46328bc40b4961f814229cd72762af278cf56a8efb18211420cdba43ccb06dfdb207716390df9205fa880cc647bb48455f437ec8e4da","priv":"60622d64880831974cf62d8f65be9e4d89e6579e6e0f1e5351a10b7574f3615a","pub":"03ba031ce6dd8976cfb72a2121165ea4bc0a8cdb14082504a05a917de09bccda29","addr":"50203affa88d87f3b104e237058807366f33c1f2"},{"mnemonic":"inspire immune poet penalty remind mule crawl drill rare fork track creek","master":"7c581d1907fb46fc46e6e9b81d83fbae02a75a2ee50a8d723e24700b530ce917","seed":"3822ac35fca2e3b5904e3284deb5c178b7c64ada6d7dd31347ea302e76ecb46d4bf926158016b76106ca61ae068fdc594808a56aae5e39da7893592310c33d2f","priv":"6f87d364a299b42c40a641d121a255228b62884d6f2d09068a384f7c6b515831","pub":"03f217a49a2068b2807a4f0500a08dbc7bcba6ffd60b0fe3e4f381a089b74506f5","addr":"c4a20e80863522069f8059e91bb1a162a7af7356"},{"mnemonic":"inquiry exile okay attend enroll author forget essence script conduct apology rather","master":"609da212416c631e8701b1fcfda5c412e0758270daa7bc796ac1492045a93a5c","seed":"4a75975cb2852f8dea2ef90535307e0a0063985a1c2347db1edc25bb96dffbd880ae88e8e5a21e91b6c26cdc98673effdf0ca84f416a45950cbb0ec74cb7fbed","priv":"c2e4db33ed61ed9f7a2b1452515b971a1bb20dce3b07efeb9437fe6ed481c9b8","pub":"03fb6407710ca5408d137faa85f2837879c4b0f4df59182a3d5f002e49c34c3bc3","addr":"c5217c9cd9af8ceff8235b2d127ef1cea8e94e35"},{"mnemonic":"entire diary route emerge moment only lounge situate expose hurry hip mosquito","master":"a919ae0bb832affae8e5269efc47936b6bf3e487740f8bbd2c286866a2224762","seed":"e97e0456756e343a7d56b1edf7468419f8b431c6c850299b382d3d957fff7c48f86114eb5640aee13d245c2a34d390850ec43c427bfec555d40a489397376340","priv":"eff22ef90183b533c5267d9c19f29f7a6fc2c44b45dcfd4b440181b977087585","pub":"030cb4259aefba8429f5e3ab0009e080674e887748e51ba2e0a055d2df3bc17589","addr":"d5db282d076f9d474651970abe5d2e3fece873ed"},{"mnemonic":"ginger glad purity aware siege tornado seek orbit sniff spawn process surround","master":"5498d52a17868a0fb5f1d2416277fa1a4220ac3a498f18f15e750d1140c57f86","seed":"3674479b527c4f27b578f0609f800de86a46591eccef6d40d3ac4436ac8c92ff12659e1e404af552f1058e9f17e90efc160903fd985f5b8069c66c9f6708abc0","priv":"5fcf38e09f4a3665256644715fd606ab60d9cd62f4c22962ec94b6fc5fccdfc2","pub":"03da1b1b03fde4241b05adc19515eda3ea96febdd5ea0e8f82dcc7cd98f1b74029","addr":"1186fc924724531d8ccc9f9ffd49701e62056c52"},{"mnemonic":"laundry reform post update crime page snack another boil pet cement primary","master":"3b1c3ee0d63328bab055d691bd29179ce5abb933759fcf83912adb41ff1bd9c9","seed":"60771d2e964301990d7ce6de87becd62e65852a4da9b819ce3fe28d72ab0e4619445d934172c1fc9361d2176f4ccc8d5b9175f4e933fd5e9681d8e8b177fadf6","priv":"755dcf792c67ba8a9d8b0694d21fbd33c51fb7aea67f05d2c8b0f38093ffa7ef","pub":"037118c561ac7773ab212b899b57f5527b77577a39a4f413232c8e7c6a33f9091a","addr":"bc2e19256932b58fdbbe5ad809e30a3ab2b2685a"},{"mnemonic":"pool bean dilemma foster reveal renew exist bar sick spot bulb trim","master":"4d1399a0181de627ea555f388952dd5d2c7a7b83e1a618fe8b07b03c1bed18e3","seed":"d788888f57bc052bf0d35abf7be1175152aa607b901d1040f4a28c8cef343f0fed2031e3bc9e1eff77eeef4e024234466a55f70150388824d6fa10051133a464","priv":"00b7f71d77cf0d4e13e51403c92aa66ba543e9cfaecd810049d17794a4c0ded9","pub":"02319a6d654cb42a231f7d021e2cb7c9ea7624c37d6ee32b218d5ef79a31eaeb46","addr":"f6d78ff49d6f12b82e24bdd77bebb536566a39eb"},{"mnemonic":"poem leaf scrub bag bomb palace discover pear rhythm treat negative system","master":"4bbecc45fde87cb80a4cad7a4b08068265eb8c12d4ed36d1c9a9a7e5e73c39ac","seed":"efb6bca601760ffb2b183bd4cd2e84a864eda3bb4b8b1369095d3df69fa7ead0b8bfdf5a097f37bb4b1ccc452cb8935baa165f7ab039342af64e9e50bfd51b36","priv":"c4988f397df2821ee196f98e62e4a1542e53ca1e46f966a4d13c4e3e389d8937","pub":"038c8e4571f8f411827ba778340beb7bbc29fb47af998a852da29b4f1583d5c600","addr":"67b94e1d532cd8e42e5a74cbf20f7d079f42055f"},{"mnemonic":"punch twin village ostrich frame goose dance history valid mountain volume high","master":"f9ba8580f4d53e34d9bfd3b6a2e17c5d8987c879945df9bd20407d3f8144b108","seed":"07d40cd18ec6abbc351aa23172dfa6582ed83d5bb24d5cd830d7cefd23d8a2ea6272efc6686606e61507d5a793b7fa06f83bf73504a072e32a002750f5f240d0","priv":"10222b81cc386d25eaf5037fb8fbfa9c401c1d192fb715397f6636dd94e9db3d","pub":"038b04836ce19b454a0af260bc1271ec73f1a0998d8659e9cffe38fa5e4d696a77","addr":"55bd4a574aaa1be2c02aa2eb40254f8cdda7a682"},{"mnemonic":"ready embrace photo find orange curious essay pulse frozen earn wild travel","master":"361e9e44283fe7d3bab83395ecb8373dff5d0a4d60274695637cb02955523741","seed":"ab50c5ab74b1ebae85811108c372651ca8d275e7c2b5d0fec9f261966395482f311a76dd297f2d2997c6c59247807506ed20f524a32dff90637956cb7868aaed","priv":"1ffdff9fd05b67d22f10076595e0025800a8d60f022f2bd99ceeba93681372f4","pub":"03b168906d5b255a896509c8ecaac4a8141d22ebe005e6dfb2892f6f12970dba2f","addr":"a7d30e12ff906b2ca0c6df3fc68274e7e30725e1"},{"mnemonic":"pass paper cable blanket meadow talk govern admit busy base hold broccoli","master":"db0e45010140df4a4514284d7ba78ac7a5771ee15820856afb1d15a4e3b0af7a","seed":"521ec955279d92fae9dd8dd43162ee216798f4a0276a2c98b29dec8dec9bb26f17e6a49ce999ec55e061379166f25116c01b8cb48ded23c3b3087dbafbfc5371","priv":"b5bf00005304278d5e4aa792de142a6cb46bd6283ab02222de50cc2f01009e09","pub":"03c188a8581f09d9eb14252f8d1f5cebf1de8189526992889115c6edb1cd180090","addr":"0f8fe4fac4fd85d649d7e19d1773897aa6e75f43"},{"mnemonic":"weekend infant spin powder hurdle pupil disorder always excess unhappy price decide","master":"b1bec76f3cb15f82e5c9c65aa23d0871ec06a590a3823285a412ec9a243b1c1e","seed":"5a8d16dbd0586561cc81273ee320c6c3d51bd90bdf08119ec81596fcab24335193b33fdd621800daefb49e038ee079184e569b941ef5d839987dc1e24f578039","priv":"90ffc2ec625f528045a432ddae8d20017c9b1a883ed22ed403783e10801077e4","pub":"027d4213cf485f4eaf3ea06c61057f92ae8fde8362c3b2f4b167639b74153db673","addr":"c9b1073f122b5ebd155d4904cbc32da4281bed6d"},{"mnemonic":"agent seven dry cheese rhythm raise junior sorry spoil tag opera bench","master":"8cd8ddb8ad3f12fa347780c4614e38198a51c00bc61ecb572616382d6f8388ed","seed":"ad6641ad541b411261101e5f76036fcddd8af62de1966a3b9a1122edd1e6057e38fb5e129c86427a68590c87cf9410fc14ec08dff184742a54461c205018f43a","priv":"f15efc08c8a578f7035a672fb51c2f6929e169e5ff37802ae8b50f1664c025a4","pub":"039926cc43000431dab0f0f4818fa024ddcec0ffe9c483f8ec294cc863f5133574","addr":"89846353e3a3becad919620dae0a2d22d0533559"},{"mnemonic":"floor sting insane slice pistol blush follow muffin acid coral aim make","master":"f65fa0d49225f5f373eed6d7d6f9f8f73f80840c6027c3871645679e51f2dc81","seed":"8782d6395c27d8b9a3b0fe29e44093e5150c9b82bfcb6344e6134e954c1240cb33fe8c9489b3ad3117f5c112f02f0eecb0e30efa36db18b27003df5e542face0","priv":"de11f15f7cfb0038dc07ea2007019c17747e5138cd83b37ad30621c9f41b1799","pub":"02b662800bbc56c144f6b39773bfb4d496c9f1442900b59641aa94c6b50a783fa0","addr":"70b1ee56807b9191f77b1d18ddfd181677e91654"},{"mnemonic":"canal today stadium spell accuse cream chuckle method unfair regret sibling old","master":"90c0af3882fac84793812f44eacb1d4cf13b56863c70ccfd6866bbde1195036e","seed":"c1b4ea5bc074259d2f25ad7338b423c5ccd2083b60f0ecce88f7c5d4cb39806f2da873c05cd52729ee07512cba3af26f21354a3769350d5bdd10868ff46d8e28","priv":"9ff9156b1f137d20b486ea125515fd7a2f057145fa8313203606ead961ebed84","pub":"0225998b8535e9f2e269e86caeca1ed90a86414d8c449011ea2fd9a488b7097528","addr":"40f33af234da7de748b02a01687a5e60429ee07d"},{"mnemonic":"object sure already seat attitude great winner session cool addict direct popular","master":"86a35c7ab59d48a897f6b4a78221c8acd0850010a063ceb912488352fa19048f","seed":"fae4348ff7c42cb1ff8542bd19721823917037d1cb017ffaf64547be8ad4b296212d6e952b2af66db8541c7e881136aa76e17e4fed926064bd5edad8f25e0dcf","priv":"2df0f9c67615ca9370f8ab957db6ad6cbcff1b5353f745dd24125ad36d7c77c3","pub":"0367582dda754c5345514b2618d0844e83f8fb49ca39a70d00537c70c86b6519b9","addr":"128a4f8786a46bb7815e59abbcd85bffb526e953"},{"mnemonic":"sudden more spawn have pig observe embody title collect junior sing bronze","master":"c71aeb48c73d6ecef04597f248dfef9ebaab37e9df625a483db1e883a972a36f","seed":"eb1dcdc3ecd77652223ccccacb85a255f350028a843bed72ff96d4e3de5566f667af1d5fd48aed32d2e580f625bd258cdcf6760d1089ee3f9653375bb1139f9c","priv":"455da9b7df9d00ce53973439b23a26a75463c9ce0bdd73eb16a39e019d7786ae","pub":"0327c673e375a95e13ab6a940c18a35874331db2c0abb283736890b0ed1a09b79e","addr":"d173a47de381e714e3873d1e0ac05ef526b0eeec"},{"mnemonic":"world gown final mind dinosaur stamp unaware apology foot flavor hockey bamboo","master":"040827137019c4a9d29c1d65849cbac940ca0dcdac2ba8def0421c990c90e2e2","seed":"48c5532c17475f0a4d63d76daefe7cf21681b32cbbf4ad8cbb62b6fa872411fd2b8821f8f2ea97cd9004fe4585e5ecfa27ee6c46caab89145c3b0b4b7ef5c073","priv":"d66195156c08032f912a71128635b25750335aab186aec63ec9a83e9b24c55a6","pub":"022d8790106436a60a3b74d10fb07e06cb48b2b13ebfb81a608039d80e9d7c1e64","addr":"fa08eb0d89958302d67d4de8b99fe0c53ddebae7"},{"mnemonic":"bag bridge trip improve blanket machine asset boat mask flash vicious pig","master":"21df138f80902d1ee79948ca3836ecc29a0f9093b06a240b29ac7c3aab89750a","seed":"99cead310304d8ef58b23fd2b9ce7fb71386111ffda2f609898bd59289862b6f464ba261855b1bdbfad8f7ea25e43adce10f7386cb6cbb744ee05772b883d176","priv":"c82490739f0d969706299c3ecd89e4f47d0247471ad2d3f7ebba531b8d6804d4","pub":"03dfb5455095553fe24c1474c5a5dc0fb69aba612b79568486345f73fe27d36205","addr":"020c6ac4996d1c91b2b2894c426dc8e32b083fb2"},{"mnemonic":"auction okay cousin sick accuse steel limb staff tube gold hero add","master":"5bc8d22635bf6ce1494c820527e26a94c57f6987d6cdf6d40825138c3283e6c2","seed":"c7df0872664b4b4b0a7deeb3c26545380bc61fa3734a20a82c96e73707ae975b76b12f31da65e863040b2215c34a4e9f525ef38e555757a9e8a42a20af2f78de","priv":"0bc4a9bd3b88ad78c34a309183da9a0efe4d8e27d098282e3bb248cb960e018e","pub":"0200d5e1fe0707ff11dca2c30a0ac83bfa40ebbb7e51197993c6910312fe91d75c","addr":"72ada6e822ffed23ac5ff5147a22537b984bfca3"},{"mnemonic":"trip below tourist offer burden hungry later grain quantum universe service group","master":"7990e7b10c0c6bc95f45933e8f5be1e758e7b7b2ee39b8caa00e8ca50cc66855","seed":"a96ab2ab3fa2bc2547cb20cdb3e39eab2ce9dedb053277c7802bc0bdc4ba9d08a3860b48325c662de187c935419211bb64ca07448988f903cde04eea9f0f6e68","priv":"6d922bc8eef11400f93de7ce47b55d3e862ffece29fa666ba05c778ffde8d91f","pub":"032567a2ff9a50d1c4e6cc525dfb96da1edefa91fc09c32ddac3ad446056f1ad7c","addr":"abaf696230fd241bfed2c76a0452456bb608c17f"},{"mnemonic":"bacon together open trust hood detail liberty bean spread awkward antique minor","master":"b96abff8fade8664e35d68c78f7609f1c8553a02b80c81857583df0134513370","seed":"772590bec2194f5b552ffdb2eaad756555b22e979ef0463222af6711c53d77623ad9c34abe48b7389e67b10e56aae448e9d064e4be58c5d45c4de16ad56599e1","priv":"532a98d8be2812f88057a91510a20ed4e7fdcad022162e3d3dc14a34104859ce","pub":"020020a4aa457c2944b3c333141e9dccf1c500bb277b846d85a77141f69332a315","addr":"3b5c6eb43438c07d8591e7c4aabe8d4540490bd7"},{"mnemonic":"bulb jewel comic drop copy wreck unit engage meadow hurry acid thrive","master":"c0755ca5e91d4ec8f6dc7b2582d65dc70786a3ea67cd40dd68462ce13535ed61","seed":"e808525cb5c66bf4080ace7b3ff7d6454d07723ec23fa16fc0b3a346d7fdbf71dfe5f383c13d68de5b5f04cae47503ff352ae340700bcb75f2ed6f0eac3b7c5a","priv":"4f592a1d10867d00b28197be6207977e13c9fc551449d05be284642c43597132","pub":"035ec6120eeb601d6b3b444e066252a4c917cefda62c49c0ecd6384c1ca4db826e","addr":"35f84051b0a02090acd509fc801b21864c45ce91"},{"mnemonic":"devote guitar planet kick now above act sponsor account blouse divide horror","master":"1d5329254a9cffc9de60eaedb1735bc8b25c77142a9ea2098e8227ad289b967b","seed":"462b9b3d13f1b1e9e802525e3bd5e22f97a31b352240c9457c1f6d5f194c6e7b7bc37a4fd2607a27adbb77dd837b9114606c8dfc54d78a1a4c8f703889a2e50f","priv":"d5a648822e5c20ee1c56ac5b08a54e561a392d540d3e964499a5a970362c3829","pub":"0381ee04ec2cd440ee26d5a37c3775047e0215b2919736e092c2aaa7152ae448fa","addr":"daf4d242b80955a2269006d530ff6826f718fca8"},{"mnemonic":"youth error panic brisk panel hobby mimic nasty address little original sense","master":"57a151782b47da723e7bc39b13200bb9ee0c3621e839e179a92f99ced03d15ac","seed":"600441626b7035ce2b4e1a90a872ae2279cda57d8f8f44ae3c99edfd0e23ed4afb3e1f4e9b01eb5cba5cafff6a650f2d586d1f0ad85254cc99caa47abab30b4f","priv":"08030c2dd269b5b60f12ee6bb27aad68d3490ef128ca704f390e283ab608203e","pub":"03a38f69571f6187ce334833b798fc0d32065fef197f5812e4c392de54fce4bab1","addr":"02bbd2f05a3c4979db5ac72246d7e620e3edb769"},{"mnemonic":"age excuse wall torch dove enter movie almost build shock blame object","master":"cb4958fe22c8250f41998f6e869eb6cff240550af2db41d0b92e9bb64041469f","seed":"bd585927d17cba54ff5b2b63f2a98267d197ef07caf6a82b3dadc45bc5348d99dc7dac59e45561e55c3513a62ad1942ae2763168562c74ed808f1a59b32a64e3","priv":"7923cba208f6bc84ba7eb134836b96b69bf1f75330caea0be218df31885cbfb1","pub":"0369f14dcd228c164170d5187c21d092b4238386e0c8231a132cbe41e4ba315df1","addr":"93a1e4c78f24c08c48572169fa949001049a6528"},{"mnemonic":"expand shine stairs delay calm soldier deny volume whisper island topple cross","master":"0467b91a321c4326fec8f39cecfbb2d273e628837044815cab899f8debb66376","seed":"0597787fa9ed69ce29f6bfaddfbda091c7b35b6dfec6bcec23957e6a547819c320504afb3ed7a7c5636e4496271d2681891ecafcb9a93ec71bb7b5d9dd06c710","priv":"ab2422cc32992afafc497d2aaf8e4ffb193aa02a5574b0be59cca80566ae8a91","pub":"02348311d8c7310491aaa1f3bfba7945844cff5573ac8b5788872f594e20746830","addr":"a1d27d518ffacde5dcc9a10ade764cff42399996"},{"mnemonic":"sense palace seek enhance hold exotic sense pyramid program fuel deliver heart","master":"715d3a14fdee63848d94f9269d97fa562301f6899487a5ccc9d987c8eccad3ed","seed":"e80766a81744ba87d4faa9199a2b6d1b829256aadfc46f3b647be1788cdf324f8a4ec4ef52af07bef0d60ee090d3c2f45ebad582eb326b688366ebd7390df61a","priv":"7cd5560b3928e4048efe644e39bcc1eaa9e8b783c0f3f0ef5904e030bb6d63bb","pub":"03c7759ec461940a6a3f90b4380286aff6307c2ff6acc234df44aff978fdd7430c","addr":"0d43f6bbde0c5687561d8f5d457bfa78581b407e"},{"mnemonic":"poverty defy mistake begin dizzy behind annual olympic glad behave coin rhythm","master":"eb342d1dc891e285f0d1822af5e00ce3472b869c73102f87c008f73099448ab8","seed":"710a0f8ebbefb347f83eaf54d2977cd64e1f90d4f8de33c8ff041f807d64e5444707432877ffb4697e2e554d1e5bec48ff7428d13ed83433c4200b7aa6bd7c22","priv":"6c36c88b1168a478325ad77e220a77f10a2c8933fd72a2babbc918cd683cb265","pub":"027934c90cc3c75daf4971ccfc56f672fbe33f86c852f648afaf260989961be283","addr":"d60c6edc1bb9b47b0f2baeddeba2723e61aae00a"},{"mnemonic":"artefact vanish security tooth rail claim drive black push ocean special uphold","master":"08d1f1528ad6dd4424ca11825ac1b47111969f21b0285b3bfca9a2e28aef095b","seed":"add463ba53586dcf678b32e6285d40dc5502f91c3dd1128351380f52a28d08b9ba747c28aa85164b342e95b44c1ee03ce9c0d9029979f346ab6d9dc3a614f7bc","priv":"e22dff0eb96c0599766b81a0399e62589bf6e734003e45bbea048ac37c8c9bda","pub":"029954b4e87d373729575ca9b1606c566108753e12080de2739f64ea0a9b881ed7","addr":"0c670a5b8b1fe0a5e43d7efe0a905f67c5ae47c9"},{"mnemonic":"supreme correct virtual guess funny grocery assume dash blush rubber wage chaos","master":"09eb5b5fab562805b57ed0f1a96e66d56b8e132d885f39cc5d76094a4e169d91","seed":"994fbf31e1213b7c113ba0b47563afe37de2acab7bd17ee071c04b1616c6cb50d853bcd5203a9a635fab9dcaa07f0d9d4168a7303fdb53ef41cf5ff15db83704","priv":"1b56d4fd8c5fc994f22d4c7fc856aa18814f269c0f43fe65b52c914a186ec265","pub":"03941aa1d30a3122695e69b6a7f8c94edf030255255a33d82484f90c491dc77f1b","addr":"ebb17b37332f0079728e9de0f2b40951a233a9ac"},{"mnemonic":"pyramid insane eternal man arrange earth predict panther atom fashion claw present","master":"25ad3023a8597a1960b1e6411d75a821d86000ccb3e92a66646017f56dfe65b8","seed":"56231905c46f23f005085a4618005b1851055dc3f639f6b1169189c9bedf1bec6efa219631c333f0cc488e06a0b5babbd781071b7327dd8667bba0467b6630cf","priv":"0a4413be4769217c1f03bddca1fa921ec7935b473e9f13ab78f2b377c08198b6","pub":"02f4e8d8f9f165017e917b9128160fcc8232e03a7d4905583e60a76d3e532bcccf","addr":"d7356ce66dd427267edffbc7d90b1499edb2fb61"},{"mnemonic":"enlist pelican teach loyal like false property ski salon kite thunder gossip","master":"4b0285f521bf09632466c22873be5543b1c228edfdeb25838a3100d72c9bab6b","seed":"9c0e2337f5f02183d2dccc181372e47f8d89c3ba580e144fd6bbcc4d852e49d4f3f093eb1e29e9c0a58f84c6339033b800af15f212b01e7c60f8b89c3525f180","priv":"63a88432a7998b7af6962bff643d46a1d8fd7d4076923eb4a0f76a3038a7f26e","pub":"0284e4cfc7f7513515d97a68efd241f25c47d0b88f57b5308e508141b27b9ef2eb","addr":"2ae41ce0a0a995d865357c09749d94564f91ad65"},{"mnemonic":"congress like unhappy run unusual citizen flush force welcome arena clean student","master":"478229d977380296130a0a3d61be2ed2f8423ee7021544c32372beedf9e8c955","seed":"ee65535652c8733c881553f9ee7b9312a5ffe999ab40e12fe5a7b54101bdf8bc078de75d8dbee815604ebe1fce0eaa7104114780e074d4907297d29c1eeebbbf","priv":"caf650347982b40bd75eaa1830cb5d0089bfc011156c927ddec0fd90b333f590","pub":"037d33ba4e7bafde02969dabfbfe3397bd6744f1552da1313ce129f49eae410396","addr":"a00609d65a4753b3888416518cbd4c25282f355d"},{"mnemonic":"science reject slim wife shove laptop pull hundred genuine method canoe globe","master":"c011d8e6c42749fe6f7d1aea4b04b0d6e618650b0253710c717ece6d31005d05","seed":"40209df29de5baf4dd6b82df091404ec2f1b61f47bfa5e9a23e6d030848d8bd958138192458d82dc60c6c46ec01bf77b5c447e7b2ed0dace4c668d08c86719e3","priv":"058091efb05990cc436342cc3ef6efb2d0f02fed6380370d60255d37a2844939","pub":"034048a3e9bc89ed545de8f2131437838e7fd9477a7c3fba98032e2305732cead0","addr":"dba1207953cdf07ef09213624b99ae24780e8051"},{"mnemonic":"hint where immense cactus sugar entire stool accuse suspect cinnamon pipe car","master":"620259cc620b3b806fec360f2e9b84c4c5b94f9ad34c53b40debd1181e2da675","seed":"3332bd304e7a5b1120a80ce1fb74c1e0554c2fc5d3c007fa58af862c4c2450d9a1fd12c90ca6be5dbf9b69131eac8804fbe0e45ff433bf4d2f634b527319458d","priv":"536ffb48ba8ba6872cd9065c0d76b511a522a7e3b38c84ae16bc3eda2ef49e6f","pub":"038d3d8f70aa9ea108a19076d0e90cdde8d302b709aca1129eaf9ec281672b8aba","addr":"0fcf72bdee5071ce4f241e3c908ab5b0b969a37c"},{"mnemonic":"load loan mail real squirrel gossip fluid wrong amazing own post exclude","master":"c1cfa046473eee67b03ab8be8519a26c8cc72c01324d6a7e6108f3993a76d937","seed":"0f20d1b5e5e93bdb2031411be4c5590f977ca1ccc4550d7c8d8211a62221e6a5c6f34f7f99edf1f785811703a3540bd2f3dcadb15947cd4181b6e80fa988996c","priv":"b0dd0c8d5a07124f574ed87b406f4d51a77491d78f05ff5e80569741c8f044b9","pub":"03c403433ad539d2bfc212628180632b8d860172c6ce4ad46a3e94ddb1657ae889","addr":"145995cd3905204ca68f23c3203d5fb6d6eb8fb7"},{"mnemonic":"attack zoo topic note remind direct inside budget section outside permit inform","master":"ffc2f55b2c123430dc29c09fb431b125b5a3f321ed0ab5f64b179fd3358b0e02","seed":"693b14c3a0b6c0854971fdb5edfe2b5e5d3d37e524a96186d15fb0ab23f6d250b250f5c23a44d28ef9b94210df582d85c17208ce468d09029039ac5dcbd1222a","priv":"76f689bf1d6faf5456946bca3b048cfdefe39b74eb3e0e9d85c2da386db80d26","pub":"021e00bf6a0a71e0a37a8ffac002c5d5cc6350fee3dae6fd197a18ec880309fef1","addr":"7249de82b9250af13b7c309d32688ff19e7ed001"},{"mnemonic":"practice hard harvest gift bracket panic weather crucial have play reveal athlete","master":"cbce18216889b4dd4d2887fc924e98ccb5aae1e9b5d7433c120c79ec3ecc0aea","seed":"02bb9503765066c4e711e1a78be25e7b49689e107f58a2acb252b1c16dbb259db566a06213848fab833a32e34ce565788d7af30f584c21a21f02ef30dd2878e8","priv":"e6e7cedca2c3b1306cb06addcd2a861d04bc383c1579ba9d044a5c752b1b38af","pub":"02b11ef0b566fd9f429f88385507d63575094bd21caf8d3653a8b829c394de0920","addr":"8c0edef9f59b632305c72112f160e9c3616e3ebf"},{"mnemonic":"frown mix normal orient tower hero attend occur blur ship ball motor","master":"87c9a61c0fe1e8760393b18cc556b8f6d1d4bef0355444aabda127b342865d67","seed":"8e35626433f0913c1799adec7b23de7cf6d0689822d3a2f8d1bfb6ab041c80db3e5703c96aa09ed0db4e4f72097cb652e6d01d065bb6ab86f19e498793fb44cc","priv":"47b7699713fc59ddd67a556639c3f1ec0783a3dc3424cdeae4335707a549181f","pub":"0281ee7c3669207a68e189aca625e1b6eaabc909ae611a6eb2072f51a6f26d545b","addr":"ddd6d8013e5c4f99900cb22184075afb7842acdc"},{"mnemonic":"beef apple unable rocket balcony tag tooth spin sunny innocent clinic dice","master":"6b08fb53a185b5da79434bff6de9b10c558fae63beaf5f487c36760da44cb28d","seed":"f06397170fc337caa0278b2324f05095d587887217f5f10b13b04e1286158e4fe6a15da54c7dca729744e268255f4d04530763580ac7d660b323a8e8fe163753","priv":"923e63fa8400088c1f464357746b9e95560c23a755427d792aaffbb73551fbd1","pub":"03046ea68a2e1930e531b00376a4254da2a1ff0ed278aa800af2fa17cac40021d4","addr":"3ed4b79d2755b7d95fe1626d26a2b674a927b3be"},{"mnemonic":"sadness frown tape chase swear cushion eight funny sight dad solar civil","master":"af805962cf1c7bd97d0e4e981d3157834a17d3dce23a426c299b128685f28bec","seed":"a6292e7e8f3a502c4eefa0cdc94fda1d528642e084f45b30840e2ae9e7be636bbf2d67156e3bd352d1bb0750fa4df04dc5b8b6479c7c98c116c05ad5fe2d4d83","priv":"0bc87cd2cbddab83294a804fca193a6bbcaa3a07583bb2dd35619f325b70b7e4","pub":"025811f4948be5336572285ce58c97da84e5a4b7c542180933a1f4b6200ee4ebe6","addr":"9c6867cf41071e1f7d84ace3d2802ee46a1c08ec"},{"mnemonic":"bulk topple anger gift matter soft dune hotel dutch guard cycle close","master":"816ece50d6fbbee470de36fbcf646a83d224dabf5ca206786202f59ee7be8831","seed":"4980fcc86c5112625b63300e007a26287fec1b2a9cac0e254a8f43c91d897c03a3663fee0b351fd877171ce708b3fa471ba67290d6ad32075f0f6be6d3f70ec6","priv":"51cb46ea5db7c500388df8310bc5c8db7bdfb7c797030aa8f4a262b53f82a904","pub":"034e93f88a8da10122de23e2b25eeaf19aa1df514293b1e8fbdae2ad9fc2782779","addr":"c81ba2724c2e99a7168bd74cc79e222dd845187c"},{"mnemonic":"account lobster inhale champion relief scrub poverty tornado romance build amount exile","master":"202f11a0730b769f90d0fa68b81e9fdd78be483e38a9a39c447e8315d8f9fba3","seed":"001ce5c310547070fc2169bf0cb7353b90e824c051ea9f285eca426cb7f2527cca995865b0466c86a0fb3e208dc1ded076d2b985c8557dc7a94af7c6631bdd93","priv":"d142d2616dfe9e3aa43634a94a05707a8f0fa58ae0186bb565ecb87c96f648a2","pub":"02ca5eb73da42320161564c42520ab1a4b5823805bdb4dcb44a4abd88a235c6b2b","addr":"4481e257f1cb7d8105823e54a1b8d0254745874b"},{"mnemonic":"defense knock simple usage cook shoulder alcohol muscle pigeon balance become sister","master":"26df035303f63f356b451e829e154cac27e3f0c1686679c784d405455adfe5a4","seed":"44955b360a1622c1a69d20315b5fbc982502903b45736acea4f146c2cf69632a99630195b1166be96e4c90449abe1cddfacb3be63ff2b7fed02846b0d9244022","priv":"b9dbdb8952ce34d5f575b8b012a5e3041521efb893004f45eef2c01eb4f5fad6","pub":"02e9a6d02ce5cc1c14421c3d6db53d16a048e0421128f1ecc1e3e41d7df9d4e609","addr":"4e78dfe3fc045b8f65760a337ba354e01851d82d"},{"mnemonic":"bonus surface unlock noodle genuine stumble juice purchase grass table call piano","master":"16474b5955a793884279a9936804299a953427a38d5bead30553ec709bf42c74","seed":"da8eae0d3aa27a6ef6637dd17d57fe98e75619837c3dac48b3b546152d5fb1dcc4c59a86f65e8716c82e3014ccde5779e6e33c2a2658c71424f4ff9ea6ca0065","priv":"c9bdbc26bc788147d12c4bc3b13ff3774a7aad1ac66ce45e742c7a96eafa2a5e","pub":"0398102ae1bffc71eaf086b652c9bb847dace83d0b3f87d91ddacfb1702762cbe0","addr":"f0ead6caf66523b7e655b4ffd26a8b35f0b09eb3"},{"mnemonic":"envelope scheme knife muscle museum town valley until quote interest film keep","master":"bc01f4cbe286c6ad87553e0ece0e142f49984c2676ed1fc5142abb75637d47af","seed":"89867eca31ccd75983b064d62a1b4061d1f64c1731e25e08c65259139590490827869749ec30e8218e593e2ecbd4303984dfdb7b15775a487ba0eba84953a940","priv":"b5c899498ecb94655e622f999bd7ef9a2c4d26c3f555ee6888e5b4e1abbce1e9","pub":"0316d5c61149c0f83944192a788b5b64b9e6b1ce49f2604db67e8e4f16416ae153","addr":"b2a48800c35e6592ebe5003a46883a00360e2b44"},{"mnemonic":"also genre defense valid point smile hybrid radar lesson sniff one recipe","master":"3cc6fb6c4ddaa98e3daa2e984189417c55c1bfec4089397855f809659f9316cc","seed":"e962b4cbed0c3d5232f75595e7aa3cb33f1b2e093bcf9a21a4f8c6dd9677aba42161fd8eafe38fd12ca0ce3cb58d4d26fb7d349b1a160bf7f6f6b6c56284ac6f","priv":"420f0121df8cbc367e966344fa28d913bd1c3a1032efe5a3627d94039281e98c","pub":"03a61034ea8ffde7a239db255e414008c8e913a1405a34d9a606108f1943a7fa86","addr":"5976e21c1f85766c535011aac79e8a5f8b0f08a6"},{"mnemonic":"flavor spy elevator parent theme become slender recipe media pudding suspect melt","master":"4ce14ced0d4121d342d2c514755ba73b17eda70cefb20efa5aea71ec27ee47d6","seed":"0040263c29e5d965556054dbe7a738ea35b46775bea5859bc2e5b3872c89574e4f18de3f6fa36df65f47b13417763dc8c020b498ed3e9f11f46f019815bcf9a2","priv":"d2a1436a12fc9e4f2acce77564b8ddd67c1107d525eb6128c1eda078d3d8d845","pub":"03246bd048416493a970d97130d54db7e263af82dff75cc9fd0cf1e061f5d3f8b1","addr":"7eecbc8b6082d62d5fe28846a8ddc20b16e6a9c2"},{"mnemonic":"path number useless federal birth such peanut rate swarm flee current grief","master":"01d57fdf15aae5c1d45349fcb7bda6ab271c15be88429ebd1122336b91937540","seed":"061d0906c096a3338ac0877a102b4bb3b375ec5b34809838dfb47b186051a0a66c98e0d4a4dfac2c02b86266c4574c141f7a7c9365a57449ade60e28aecdbca4","priv":"9eb30cd9489297f0c4793df375a4a497162c5b75c36feb0a979da41000cc842e","pub":"03e036866bb847db7570ce0e2459d009dd1a89eea3fea8c3ce4f1f47c4781e32f1","addr":"139b3c7e7cb476da573548067d0a29b5abc111db"},{"mnemonic":"observe layer grab few fiscal omit clinic manual climb cloud song million","master":"cdaa9864952fa51a5a10de9210ea9e0199875459304876fdce150f042a415583","seed":"fa2c9dc476532ac66ec8e219c69883cb9645129765b4698b7f2010cb4b86b2d25ebd45e6c582a9f886c5fba4cd4d7488e81d705e63508e67c1698b77b8e6a676","priv":"238ae189645958242ffc9b3b868d78bad1be7d1351573298aac47ee78a5dac41","pub":"02ba7dec8131970a8f4a837bc225fe1e8fe20ef90b6363c058946930fe6b8cf629","addr":"4aad44c69095bad1588eacbffcb5ec324dff7563"},{"mnemonic":"solid business era limit engine fringe recycle harbor organ like tiger avocado","master":"afb951280a835f1ed21d00d478a644451e254310701d8a8025f5c716a5843731","seed":"2692dff98bb318f1f583e4f5d87a453b9e1472781b38f3b6a19784f8fd3e9b156ac6a292f8ab56e1da0967e159438b87b73042cbaf3e9886ee2641e7e97dc821","priv":"4b4bb7fa92a26e738da657b31ae57c52f057540f1371066b2570fb247b2862dc","pub":"023ecdde05d734b6ac286c9b14e1577f691f114b6554d0f1b389fdc37e05af9a55","addr":"5402fb7a4751cb34b261a2a2be940d7bdd3608ff"},{"mnemonic":"convince thing assume midnight debate end best cause laugh sweet unusual present","master":"7cf2f1baf94a8487a364a742db15bef15d1d1a7583ea26dc575b0181357e31dc","seed":"1e267ef4dc1099a438a346c7ca4c546149d6a57dc4cc6623ee160677711144583245125cfdbe45629160f3da711fbc57171564c4cbc771b1fd2a21236751eea4","priv":"9eea5ab5b8b22018197fd3c9256cd21be7dc26a111c60bc3d853bd977a19c90c","pub":"03e17aadccfc20dc41e65f7256717c54f9eb3898fe335395f25a65d4a96cbd93c6","addr":"85d9c9992d5351d84d40790e65c29b52b929f7bd"},{"mnemonic":"property youth river offer mention cloth mention pride opera repeat hidden orphan","master":"7e0faa6a216e4ae162547f5c9639530abfd56ff2026b4f18219d1c82934ab2ff","seed":"dd9faf30c2ddece69bb5d937c9ee58172564ec51d760fbdfdebe4dd445f1819bd6cc21a2a4cb70fa94a0d941d994d3ff58331be94f57a16589cd7a93c19ff10c","priv":"fa343044822d07308bc523eaf778bf697302cf8b88a3431bb5a0a51974a43199","pub":"0383e1d8186c1b3d0c567c91966a5c7aee8a1492feba372be735f9a612c1910ed3","addr":"efb2221453dfd951ba456f1c5ac164d3faf08032"},{"mnemonic":"awake virus civil cash clump equal phone wet friend attitude ask act","master":"87a61b5729fc2e294da66541b79085aa3f67c03a3eec40559111e68fe5a80412","seed":"8fa5a84240149f39dd60a5477ac59586aa05d6809adffb8184e4154dad74c2ab9a446f296098c44fac8c839d3405e0a2c1edfc26ba3aad9665e6b6984d320fbd","priv":"726210437d3054875e785b12d1aa23a8cab85a0a809593cafa9c63c62e6e3c55","pub":"025494819085e1d08d8ee2d421e4ca873682c69446ec5a412d7c7a7520900397af","addr":"e26e023a2aa67a3bb5e5988be46ff266722c0153"},{"mnemonic":"snap control problem flip minute huge lady elbow plate fall cabin trouble","master":"931052717250728c04dc605a8cfcc6a49a4c520f9fc343607fce57f5ae7a9e66","seed":"f55dcbed51a9db2ddd39d1164a81049af71116896e79ced722a387e878d00633830ab8cbfe7ee8e16ee612e1e047fa3d3c2a0fe230adc6a84f0178fef6aedc61","priv":"72aedd9eca0a5f17b3c20866d6d1e98bf01a1b8b9225bfb1d93f93c594726ee0","pub":"03c07579c66433d1b676630a0ddd343167fe91419a14a7859092b6b7f81341dcc2","addr":"37b2d898f165d5cca507de65f103231ea5128788"},{"mnemonic":"wine foam depth wise faith parade tuition search demise skill boat survey","master":"5001f6ff67ae2a32088e0ad93a227206bd66f5ccc7be991a27b2809354050b03","seed":"d889ec2bfbc930c78f9a3fc356e0dcef9d636766af361c8492189df1779bf76f4890d738106132cc5202ee97b849c9928a8934fd0b40d610a38f514953e3b02b","priv":"c19f1ce95ac35938d19d41e45d7122e8faa102e219188d681559d4db3f6aa66e","pub":"02331368ed143ec22cb1af2794bea8dd5e5661b9c6b1407bfbbdf1c1a151250b7c","addr":"a70e3677cb94b449a400c870159dbfa448e137cb"},{"mnemonic":"picnic basic you priority gown town oppose penalty various half follow nut","master":"59f5a86a5f6cf3254607f5e9846c7725d7cda493ac9850042de3bc7e8902583a","seed":"85645734c0caad6d0f3be793f71f08f2b87cc92b515e75abf94a202941c45aa78f663b455c91b6d51de40284d9c26a5336d444346b408bc12d8690e541a43583","priv":"17efc82e851c2938c22527638f4f3ccc2ff16ab95c24429c583b6f68d79ac948","pub":"02e534acf511fc86761ccde5eed25d793831cca6069f09694467a3b7434cb3fc95","addr":"d75b0c916f2329006221b040901933b2bc309838"},{"mnemonic":"enroll shaft young ramp moon message echo piano advice time blame mandate","master":"02f99e44bafc4e61bc3afff77696bd72af71ef05c86dffb2bb3bc241dd60861b","seed":"6e1cccef229d10e15ac6f1d2b2a579146ce011411a6e807cd227f3215c32aa394428f9a11900fe21699ecfd09d5ae6e4e1a30f2778bbdc15fe30927e32e1fa5c","priv":"8300a73d51449f909e8d0dfd843aa4cbcc055259f7deb54f40bfcc41acbcbe2a","pub":"0251fabe51d7eaeb45a7c82ee6cdd100f338692d796e7f1effe6a705cfeb21cb71","addr":"ada0f17f1854fcec548021ce617ea90a05a2fc8e"},{"mnemonic":"remind lizard alert mercy badge sustain judge van spike obscure account twice","master":"50a01f4e2adc48491e54c26a8c30c38f451de1d2d8195661eb50f6e5ad6e68ec","seed":"1194a8c42df528afd1b1eb41b2721a9b05f9456b09d042f15884d136c1713788ed8a32dc2153f01b560ad904c93327855450bb93ca8db384cda230eaec68ecbe","priv":"b78c8076bc4378ef0ea80af91addee35557f0fb059c1b434c254ae6c76716ff4","pub":"0251109362a0885fa59ad162e24c8f55cf4895bde354cfdbac7278d69124f77022","addr":"6655b6a665acc146984fd188c3c260668fdeef65"},{"mnemonic":"chief report afraid nation silent tower defy display suspect dice switch finger","master":"472175c2f86fa94d913108eb3842ff03fad950e4c7714cab8ce0c46e8e674968","seed":"c3656bac565a87cf18a1cfd8d488905e4b18436f93d0cf1d61572741b6e0ddfa794ce94a8b05e7796a6c9757edfecc6212e8b0ffba1e71064f67c340829b36e4","priv":"f16f039577d1fd9d6bbc18dc26c03cb37ad303ee1b85468df3de138ad927d098","pub":"024fcbc106c0b515838ce4d32c50ca1ef6134c314668d98a3ee30bf27e18e2fad1","addr":"a88574f43c1bbc38c2db68fae3f73f4244973b3c"},{"mnemonic":"now renew same wolf host minor obvious tape install barrel retreat cash","master":"a75fa702bcff39de09bcdc8f55cd1110c396ffca982d7c48f6d3d2f98ae29938","seed":"6d62aa254ec89ab960c04b4f72b5fd9acc135ad79ce4c9b3fcf25e0caca06ccf05e63d2aa47630b6c7fd3dc274dea540ceb4808eea2032ec3373d8a77b336338","priv":"dd211f6c5037d095da723423be7c1ead08f09d44fe01ffc0b2c0b83cccba6ab8","pub":"02ee58e7d2d8abd00c101024aff3da181d54ab84165e0b5c02e9d53dc336f0d11f","addr":"d9c4235d8222253cf41236382326190cc8f3983c"},{"mnemonic":"bonus message butter logic disease coast transfer explain away swim taste draft","master":"ac79c0b183965a1d918ec19217ce40c6ac1d84e3d57c8d0dd7a0c6ded35f057e","seed":"3fd63c9c7e1745a0f2526a70de96f251f1f61db25e808e3a6ba500e1a1c8e0693b888837cb9ddc1e8d09acc296e908eddb9386b97859bacb10561d12e46056dc","priv":"842e49ff7bc56dc113d532e7d7b4fd4d721f4600af0e67088b6ebbd491dcd4fb","pub":"03176edc37f7b671f46248531479b45b2b6ec77edb92001b46c64c7b88e7e588be","addr":"8170b83efdeca9e923de156cb5da2e3110852c0c"},{"mnemonic":"quit culture vintage misery pilot cheap wealth buzz false donate edit lemon","master":"b2fb8551d9d84f229e26e7874d1ed77ae8066ae80e04abae48ea84f96ae448f2","seed":"2cad64c36d24dc0cb94a2b2dac3ceec1498860ef0faedb78f702efada34c350f6b7f7760b14efc177893797160a838ead20f5a890ab05c477ee70cfa0a5a6c8e","priv":"38d5623053b64b8df44950a9f305288e3af0c411238ae3a89ea45f28f8375c54","pub":"02963af6b3f4add138a3b45b5b8f01bae5e6ba0434d558bed64fff01c6750ac053","addr":"9aecc7332529b0982d8ec14350594438ff5562ac"},{"mnemonic":"paddle mountain rent siege length essence negative amazing soap pitch blame sorry","master":"f179f7a22dd44ee96dc443f7fdccadeef1afaf9deefc8819cf254a554a3332a5","seed":"b408f67dbcdb8089f6ef0b241f026f6cccf85753ae137888e80c47b3267f58902e791675cae2854f556b067915821516fa04e00bfec6cd8e474d29639972741b","priv":"1fd4e1444a9c49ab77235d823e24d70d18b5d37cf9feb550c1e0763c7f9980b2","pub":"02bde455cf78c59409eaa2d98a105b537c9aee34c53486a4247299af90be3cdf38","addr":"46269ed379006a40bd873c79a9526ef076260eeb"},{"mnemonic":"abuse office gather ginger member carbon trash detect mammal shield like wink","master":"bde4ba63249cf7e6ea92a5724ae2f34477e42a8303fbf5c100c85c3d3ab8fc6f","seed":"ca4615dceb4adac1b16195899d537faa0d6e66a243c30accbe4902d26e77004da08873b0899ba21e4233d048c2d171cc8f9e657971f2ba0dc87b843f782ac619","priv":"05d5ce699cfc49b676ee843c9f85e1510f27cd65a76ba4d46eb6db36fc033c47","pub":"03c465fdcfd7441ab8d34244970daacd29a30c0d972b92a961559906f736d4cfb0","addr":"e60e2b02b46ff358acaeba84fadce61d7361f482"},{"mnemonic":"rude depth finger strategy wagon cherry mixed assume sense aim diesel urban","master":"ac83ffb8b0b9bc49758a81e567d179fe060301062d332bb40e353b788bd986ce","seed":"368f7051cfe851c638722e3691048292a1f2a94a83f44ac9e7d6d5a882c822e75ed5116cb059f0a91550b8b2237d32b9a0bfe556217455044b3b44962f4f162c","priv":"4af19a58ee5ea6c6cbaed88cd83c13a57da1c8002b816c833fdda7388adf8662","pub":"021ba0422b1fab7261983106fcaa20a43d8629633c95948e42aee12777181c1efe","addr":"aaccb6d212ff003c8754147b89b0718a58c002c9"},{"mnemonic":"mountain bitter treat hour purity swim panda damp notable beyond rough spider","master":"e48be94ec031c9cc4361550d88facfffaffc67fc0fc5adf495aa18e17c431007","seed":"fce2cfd110d408ed37c75fd0a88fd7513a09a8dc577b38176150f7d2835959f8170438903a78a5d84b32923bbdec6d5069c532621df387c65bbc2a879fe162a7","priv":"dfe58c17756a93be86fd81d3c874bf4ff6c45cbfcf049bbdf999a8e798e448c4","pub":"020d3913b431030aba9c57920708c5648ecdba1fb0111ffcf44f91f67ae92bf176","addr":"1ed6bca6d4080f039a12e3b0e772052231818121"},{"mnemonic":"protect chalk chapter dream icon boost layer couch soccer tool taste congress","master":"c5d0b996ee26e80cd48849482caa435956d87476d3fc5087a900a7e666ef3bdb","seed":"e40c57922c038fbfeb1ea7dce70c0807a55a2c62f58f947d8f05cb66ea7b26131e8b2b81ba9add4185bb8fb72176922190ba2e15de9caf3b4749ad7840ff1acd","priv":"850e9d1840b28dcea6088c9a4837effa1254f48af181d63f660d18f5062964f4","pub":"037859452c974613068cb16147826addb72f11101db03017612d1eed3e5352d010","addr":"3b58443eca15b2061bd5d7b3240b93e012424579"},{"mnemonic":"rubber twelve wire exile shrug will alter violin crucial border drill pattern","master":"15c68b495a571a78a09687a86495141f94b2c015f4c51a9dfbdad96e68c6d871","seed":"ecb81c59dc0464e8ff470db36c567301543b2bd1bdeb94512db63bdfe6632d9c88c1aef02bbbe04176ac7a0789c9579431e732adfbf038c89aca077d14582a8d","priv":"00ec93d9115439212ee358244b5128b48a67645a4f5bf94fdcbff2c8e93cffa2","pub":"02e68bb8dcc584b4e54da27ca229a66d6a222dbce0e187a9330b1740713addb417","addr":"14f8f956d84c53acad6e5eb43e8fd7362d341363"},{"mnemonic":"loud ski diet extra rely melody space canvas humble rain park fossil","master":"1e09a38b1538e950b8ba8472c07b619334973f9075e41732851606645e318d51","seed":"a2d3ffc96f020904af3760c11e9a9e87e8824c1093b07c92a8e6d98c6aec16adaa8ce6995d5d6b58a04824a765d0232ff995183ac38a5cc2bac14f734ca8496c","priv":"18e45a61f9e6ec29019df10abca1878045553d9b924c859fe57621f212133edf","pub":"02a0053e91ce99b2c401fc40cc2892023b30c2db914403ea0da794bb77f69e93e3","addr":"bedc6716f1649fc2ae412516cb0a1d68ec2868e4"},{"mnemonic":"potato tackle almost kiss unveil spring smoke nature agent carbon swallow final","master":"5917e819604fceb3e7c11bfdf6b85809f509f89b3bad6ec7069e8e29258df791","seed":"76d3089d7214660adbbc748f9690b70f7787e9e028fb429086caba19c3b412f931b2768a843ddbdfbb6b6bfdc6349aa0fce1168d124a13d8eda61fe37b407f37","priv":"13c35356aea2324ac57e4c2d605c45020adcd4be336e097dde0b23059499e947","pub":"02e9c1615b710594269b4e86436bae98e4e9c00e7ca0405b89be47c02060705ae1","addr":"e9699c41d2da37635fe657c557f54f51d8e4a503"},{"mnemonic":"innocent cousin jungle elephant speak rent arrow maple danger menu misery breeze","master":"c2dd7273c7f703e2c66974dfd25a1e7091d83a7e6f8ef64954c8b61cfe4e84c5","seed":"32f96dc9661f448f82e2100afa787fa09afe19d90171a3e2d7b5a007316690198a3da22adeb22a2fdea43c3ee98a6ee2bd33baf1e84034d559e6019a30c2e87c","priv":"1e9f48b65f2c57f6af4797a41afba8b017de41ccf6be00bb2a6dad09a72df167","pub":"0268e685815391e6956df39750aa3e9ad8e4e558cba04ed4c136785e2bf82478db","addr":"3ecb6cf97afb26e1e0272df4fbad2e38be15287b"},{"mnemonic":"shoulder lock swamp goddess length trend tongue razor heart donate curious social","master":"c71c518c4d97855fbba7c0d7df9ea8f4e6211962ca9f5378b493c6c1d3b6afc4","seed":"e575bae2e1ee08815052f98f5c4bfb9873f171b0dedf4385e378348310c5c7f6d1cb021696e5913aac6b631ea1951bb977b3bcb548579458807cd565771945a9","priv":"2fea7aaea02fd5d5a5918ebd643bdab939edd18ef406ac5163714fd731e1157a","pub":"0363e2499272df247aa216ab8b274e146fe677bb18e706fc5377f8c921ff1c782a","addr":"de719e1abeaaf11712931d05ee39990e1331055c"},{"mnemonic":"bitter yellow post enable knee tag spend base blur gossip lucky lucky","master":"1af25d9330946339620183b1f1209c4d1ffc0aae754b82d71268400b934ea426","seed":"9e07914810a7e2d91a3ff6d4efb0686c14e59506dd9dd3c357dd5a30925a3f57c0a4983589ff113833abdb7d5cf7dbf79c08172888539126dc286848c1e49ab8","priv":"6e1ca5a3a31b7f805e412f0bcde7f82bb3fd984661a58c07b7967e327e664e47","pub":"03cc8ddeed92731c6b475a964c885865c5a11bf59ba24295d1e1117feb7f1f3e5f","addr":"8b516445842b755f23d0e671f10a5c0b1c2e48f5"},{"mnemonic":"forget organ violin inch panic sword report clean garage sauce math man","master":"29156bb726af212233cbc6c191e59a96d7ce44b6821538ea03053f36d51a919e","seed":"66c639ef4de2dec82b99ad4ab7bc73c0a4fa79d28d06ee393ccdbcc27ee375e9dae7672055dbf6d0d5ed67f2e99d4840614223d85aec64dd35607aa9b266d9da","priv":"f5e49029f7141b33f51703add1f4495e04715674fb62ad2d781f8c66269fd32c","pub":"034132d2bc71934628f435b93a9d161a3ce242e7d82dcda0a3a8c2f304e3057500","addr":"cb0a4d79fadb82d7b947889b26f8515db8fe46e6"},{"mnemonic":"under depend door lens retire faith scheme dolphin orange maid carry disease","master":"62ecf318e91f233b59acdb4000665338c8d61539b7b079716c85ceadcbd6300a","seed":"30ec1c26fb1dd7e41c5959ef7a3615e6d4e5bc21fd8af5efe1f8c0634e765f63462d7017d5a62c1e992bbb626ffcaed002208ac5f8867a97e454c1bf0bcad8ce","priv":"2babdc8642c9c9beeeafb728e948bc8531dd066679b85a9b58676c0b4247cd89","pub":"037f9e2241008d25844a96272632255802c7cd3866c5a83bffb3e2f724eb59d385","addr":"10eba132bdc71c162058500af51c53074ea1947f"},{"mnemonic":"swim topic sunny exist exhibit nurse ozone guitar trend page machine pitch","master":"5a30155bbaea42d54e9a27fa7587c712a19ad6c1c31052f68418f235b7bd5de9","seed":"673d936ed3502149d68172a22785c2aa2bbb3707d319fb692fdc6a4ace39ec3596fc406b60720ab5993af07227f42608b5a43e8e732c72d8ddfc82e680428925","priv":"e6759f20947caed563dad40d46206c67599d4d622502e46199fde1e17dc3d76e","pub":"02dd5474a52b623514948319697c646b519ac6e63581d27b01f0780af8af085e66","addr":"85fb1118593dd8c12acb0c2104438faba5dc236d"},{"mnemonic":"dragon square define chronic round romance scrub gaze powder visa please symbol","master":"1f1a35ac699063a47220536ddc3e988c055cee5e08303b867fdc16d5488861fe","seed":"dc2037120b143c919a3f0d71c6822f29b9b0ea098d286f8d4e40b3240bc9d19d4f45da3bdb983f49c304c083f534c7a26471296211845c13b49df97be61055a4","priv":"d99a6b75adef1455ff85fc94e8a4ad400a326662e01e9c0152c797b842c060e7","pub":"02eb2aa36f19bb90fca43f34316a18bb80053faeaa909cd6222128286af6c18bac","addr":"d24f7932d14e493a54a5384dcba3682e3d6c55be"},{"mnemonic":"first knife erode dice talk coconut act example sudden garment scene income","master":"1214f622b52ddf15c146c74bb0843b653117459901214a8a198a0eb59b4153ae","seed":"eebdecdd33e9ed6dd2e825c12a73e4325593aa14a7faecaea48d6764e6ee6d9d4140ed9e105247b2ccc0a1bda976fe7fc7ef8483c58f465cf393c4f833e86d94","priv":"ad3894b4f558a2e7fe5f0685e4ccd63f57aead98863617b081ac620cd25ffc4f","pub":"039de7903629baa0b6ef2cfa85dbdcb51af3fc9218b01bc57bb1f7eeba5267a858","addr":"fe6dde2516232383dc3382a75e86be3b82d2d8c9"},{"mnemonic":"danger expand toe talk course mask quit virtual reflect vital address artwork","master":"54c66d5d95de00c23ccdbe2defb6cfe78d809d55e457a0b35407ca74ed33eda0","seed":"060b5853c7f3bbd7bfc7b5d532f39b74137c2b6273a30682f768ad62800744e1c6a1db920d151d7e553553cd908b2878a31fc6c682caa05af62eace45a897b19","priv":"0ab2d63d607a2c56041fd953d9be60f5f7ff5e50f3c3a606dbf54bd799e64190","pub":"03f8b555aa7005be93d03e98b6f2a8870b9cd4ef654299be64b13b89ad839038b8","addr":"526ec814ef1712329c1cd43244e92d3c1d5aef91"},{"mnemonic":"custom flock clock grain absorb danger universe achieve decade habit phrase together","master":"ffc14bb5a243dc703a5afb1f904f8aa4f21790b470c9e05fc21cf872bdfce41c","seed":"71a82a4221bf48e1e97c8e144de80e80a9dc5808d579da185e75f6b88bb81038d88210c252921a2d72dd38c5bcb4edd442d2b481dc1d857965012d9249f77477","priv":"d9bab16080df04ffe43c57fa80c441a65b4e49a4425a0db06f52d6db77595d38","pub":"021f074f1e14552ea091e14e680814661b1a75148f8bd149fb8f6a13f6f5e7ffdc","addr":"d63213b298b92b726178855680001d79ece9271d"},{"mnemonic":"spare absurd book liar green humor alarm income sadness shoot radio indoor","master":"f9056445390a09170334e8a225d62719fb259a0c159685c29ec39e348b742510","seed":"a023662c39608142068683c030ad51321ecae6bf954b72465dfa8feb34f1e8ace0d46a072b855e33f7c5e8be70420a4f3baf7ec3ba370e975da88890d4cbb2fb","priv":"e5b8369724ab9aafb2bb44a1d3cceb2568fb0350b45f123a7cd0f26a27e70b2a","pub":"03b9196b842ae4db2b2409389a0747407baef8b19d502156a809ae02161cb0de38","addr":"658f62b688afb4773a0dc4c81925d7914dad5d22"},{"mnemonic":"anxiety salon have exclude build airport decrease unable measure reunion monkey private","master":"4f708611377bee900ca76817926b905bbca28bccced3ad0e211c7609c0562fef","seed":"cdabd5c6b9a64c302fdde1e7cc5e6ca15811e52e432e7019a81945264b3e52d89456171be0e24d4dffea0136a12a6204f2958c303f8c61af84f4b5ed135685cb","priv":"7086136c92846f7deeafe05dddd8461028edf62e947fda55b4f1ac584a78c5be","pub":"03227f8b0d42ee20d3c5cb4524686d19ed43bfa5595debe2ac4840eeeb92f3a411","addr":"6d11946752473bad3a3d593f85e97bb4664c2984"},{"mnemonic":"frog youth swear now annual mushroom stuff business excuse frost cause athlete","master":"0390c3c986b8d724d344395860504caf17a7a347d10aed2f4a0ae7ebc0e07f13","seed":"25b6bc7a888b35e1246bf8c7a289a79ba589cbd949047ba6690b47bbcb12d8c87a4d14d585fc0721217891396ce0b0f75852187a9fea4feacf320f9ae4eba3b5","priv":"ccd3755ba9b7638c2ad6815d34a03869650bc1c35b75c8e1a1cc9c2ad71d983e","pub":"0284806c57cb6013ca5ac1027a07784d05b23bfc4e6facaef108bc5af7ec20e026","addr":"d512ff91fe37ab6bfc605cf5470ff684d6dad743"},{"mnemonic":"bridge rate pen range tip web bunker magnet blue rocket dune evoke","master":"38d760387d07ffba1ebd6ef9fa9737eabb311bead38560cc464c7585b90d6915","seed":"953c78a493fa99014a542b2740b5eb2623f0fbaa765a5aa79e61a5344867dade60e05c4293df4076e218ad61c66b1112f54aaf7bdc6000b059b2c88558f582b9","priv":"d6d617aacf6ac20090788e43a7eeb58d1b44c36ecd4fbdc9ba287c8bc7f3b24c","pub":"03ee39cb612e75ef609467ed6250b602b90ee94b25ae43be9593d2cf34b3cdb938","addr":"dcfbe07630e2a9156f0787ad4e96de1bec09b3f9"},{"mnemonic":"volcano exact weapon couple logic cactus carpet raccoon state rigid ball tent","master":"8c60a1a859b5684bc6aa996738801f47adb8392ca3f92865b2f2b3a48c69425c","seed":"fc6ede285bb6e6fad778234a5f6c5bfdc23dffa2975667dda6c5470c70122f8aca9c63bd77c8811c43b4d4b2ab4ca55db7b4d457f393436ca1626ae6fbde03e2","priv":"74a1b74d0f11c8297f0d315f9556a96ec5e880bc59fecaa1283ec2a693cd9ef6","pub":"03f62cef4ff1147df7ca38cf1231c48632c4c4b2618e6c7d2bd001e10509f68f05","addr":"8b8e006c70eba973ef8945d89e33b6a5422e5742"},{"mnemonic":"match quote pet worry pizza stock flee tip kidney tomorrow giant ship","master":"02f8e676d2c4dec8e6a8430c22f8adeaf3b25054c8d22dded7e2bb6af5327a63","seed":"4d29deced03c572a5fddf55539a735228b59cbcfa80381a54c5a69dae73f56484318e511fcf46123dd8f390e2527de882252f3c0b69f49b9dc83c7059e876767","priv":"049f7fa5733f1c674a29721a025d410bf35891984608ce1eebc7ab48626ec3d0","pub":"026912ef70835b247c3fee238937842fefebdebf046b7f2afdd4f350152765fd26","addr":"4d221f1336ccedb6b2a9850a15bbd13c62ce0fd5"},{"mnemonic":"swap amateur unaware glove biology frown vintage dream prison bulk gauge bacon","master":"8c370390691fe152f7d6f5220704c9fe6b27009c1218a07d529768f40fac692f","seed":"5e3c155797cecf5509151218e30101e33da6058dad04f89dcbeaba0d1d2310482b278dc747298284bbf7e29a71cf4a869e0c2755f6144cd2a5f58fd581551b48","priv":"708a814272dd4efdf60665c491730ddb73d3d5eae3d74ba8ae632183ed014569","pub":"02e06b607086808b325b7629a95ba7b945e611c3062f967402f04966bf87111a23","addr":"d343acd452e5118b6e4c1a83cb08a521735f2a39"},{"mnemonic":"shallow chapter special riot dial sweet armed imitate rotate south cliff shoot","master":"bbd697f9078e47816c986596fd2af08818e0e84f3c2bf52ff52cf72974add88f","seed":"bd24f3a066e4a994bbd613d12f88268d16d2439f9331932a9b30da17c7fee4fbf5aba4f2f333103c6bc1d0bb600be1c86db54e323b6e9e9394b93eee35a65359","priv":"1ff3375c7c35230fdb97b8b8a2401a59278b383c355dfc1773024fe38acb4a42","pub":"02d3fa09d1e5929fa37218d16448800c9eec188e31614396648b65f3303bb3ce80","addr":"8c3027c978b9be60970e98790b073f62cd3615da"},{"mnemonic":"illegal hospital witness leader wagon appear trick mixture refuse kidney total lizard","master":"9fdb13cb834e01260d56ad2bb7c8db55b768bd0e76464d52dccb77ab806a1258","seed":"a96e21fc0177a251d86cb6ffa4057890112cafd32a7c270e782616b407cda51dcd914ba2bda90c80acebf8969825e8044ec77965e264e085ee6ce78fcc698f61","priv":"17804874b6872c2df6c5da2ae45830f752a159dd03ab0b2ef22c64018e91c14a","pub":"0221388d8dfb7416ae88426999f1e07d41aca9668b10262e995cce20448e315e54","addr":"4feb63cb3ca2d1f6b0add8ce1bb2db5661dbdeb1"},{"mnemonic":"unknown update couple lunch demise sick brisk market area kid service again","master":"26f5f1d04e6f20ae2a9cf5631a1fda6f119e3c14dfb35ff75ced5091ddd072ac","seed":"72e5f09a5c9e56832ae400cc69b98932067a81edc9c0bd0d97d385b0cd7b04f70e51f496054c7d28af2853064982c9625a4fe745ccfe8bf4d690ffe9368867c4","priv":"e59d9190fa1f4f5b37842807ec8c886c9850c7946520f6fc764c232222500f9f","pub":"03da97ef93bfdd16433aad5ae6cde252c062a9875063fa3b7ed2615077e4446c34","addr":"4302a5c24f1ef5cbcc1e286f498140769f3fd667"},{"mnemonic":"leg damp bulb final remain off lion fold run buyer help safe","master":"4b64059f74a801c81a66d7ab2e25597a3f3c97fffe760d5ca70a8857fcc5fa55","seed":"24141973b3a7aae69d48656fc4e535d1a2e094d9ae5d6325f54d6288827dd32602b128ef414240fd88c9a72f790f8fef560efc023a6695f133d8b46eed51cb1c","priv":"13e840acfed4b533303974eb1223a9d4fd400902041be3994101ca517aedeb6b","pub":"03ed7c577c28dd6936bff7eba4569d681abf5bd59fb97ba5d793a3f65ad54f8d21","addr":"3e404c844010458e56726cf7d0881a1c4047ffa0"},{"mnemonic":"observe budget library column blade soldier apart panda express frame version coast","master":"dfb6154ae9a50d8853f5fb0fac07d98cdbdf00fc4511f6f940490e48ec1858da","seed":"a16b567cc1e2c96ef202c9d49b9d14cb5ff14c7c514c8e2bfb214a9e6371fb6f4eff3dbee51e7de2e781dbd815d1e76af525d9766504208949475866abe29766","priv":"b16e831a50e39788755684d0b96f9e4f11b9e637d9e43c27cdd5242091c0a8ac","pub":"030ee1b1e2c7afccfe7f667ba52dcef130de06b35655deeb33291ce6118eb0a31a","addr":"c72adab4e3d6d5d8a9a72e36bd2614d0b4b6e78b"},{"mnemonic":"exit pulse poverty scrap relax umbrella rug clump cruise biology magnet area","master":"0d00e0698a0958ab8ac891db675471b3f7a998f4947115313bdbfb189a8d2308","seed":"a2831c38659377da9fcb2b76d62b76a7f23d59083f33f8c6778eabd804331d46f2f0f4e167b013e16b2b6ce066816641b2bcf47dce75522cd3d1e6d9fffdb28b","priv":"d0c7f92739b274899883175525b6443c265c288e0835233428b97f6f912263c4","pub":"03740d02a36387dc52d66d96357240ec0e185007bbb4457b3598d96680de6309ac","addr":"c4dcba01a757bdf253ed47a41a49c996657d7f16"},{"mnemonic":"impact random cancel imitate base purity devote panel vintage group pool twist","master":"ae715a39fbf43bf9d848024d3eb93d9ca4313c384374fbeb3508077a29017462","seed":"da887f0a16dbc3fed38134acb53466bf1c1d18d179c2f857573fd4a43b8e52b16e598306dc199fa2414700b350174ffcbf4508951c765680f4b3dc7255270efd","priv":"512494ad5290d4dbb69d9247e563188a8705dbeab8dc319db39743c319389535","pub":"03a4eb5469006a29621ea5321f35e8ed8d839a9be99be7efb9caf5e5389dc708e6","addr":"4ab6b8d6bdce81c88b093698870645e7a1f92370"},{"mnemonic":"egg usual identify trap involve winter card eternal subject unknown obscure scrub","master":"6b54ed0110635ad73c4439e189e594cadb02f22d84bdbc7b94027edef2658a83","seed":"f23aee9d83ccd474ac819ce7dc8a57cc57ce082f8032c242511ccd96a395ba8c769d07a7115d25b53cee1d12d6a703f1c0204ee152bedaec1b8c15d5d5b78edd","priv":"0f1f52768a3bc3ce2810109ad40da0126ca5527c494b226048382a9513e11be1","pub":"031d08dd21c95576d1a1a028ba2938537d1c604679fc6e59cd13ef08e62e566a4b","addr":"a0c1e6d5d63341a8707be1673a0dc34fc054a14d"},{"mnemonic":"crunch drive cereal switch sketch gym hint fortune install approve device gesture","master":"4b4f8682bfb429ef36e7ea355276981be015fb78b90c50c19665b6804f7954ef","seed":"6ca97b6725834344247fe5dd5fa0d2a20c31130d6b9c60047578181867650f8e734cafab2158f57b64a92c3f77672352dd71d9df03dd6d592a137cd19e67f3c5","priv":"fac29029531fdccaf85476aae13bb3c3c9f0083265fe6d5e6e612386f3be3ec5","pub":"03683e7d7472d8fe91cc67bea096aac18dd00ae858b8912ca9b172207b89639b80","addr":"aced36c9dad879576f5904d67f504e704726a21a"},{"mnemonic":"congress nurse found frog crane toy pact write manual version repair embark","master":"157ac37c0bbc0732a71858377479fe8bb236664322016fdfd62786ae716f9ef1","seed":"8f8269a5211027a2c6480e9a9d910c385c2e772e12cb756802931b169b602d0eb7da4c603b901f55d4f5c5cb67d7fded0b71f5f9120ee1ce08e74c3bee1f745d","priv":"ae12502606695e34c1a97dbf15b70e1637c4d537cdb0a21896cbdb17277181ab","pub":"031077cb5bd6bea01a470524322478cb5997c06fc77d7154c92f3b7273a36c739b","addr":"6ef29aad62d003f9927be9daea218167fe14822f"},{"mnemonic":"verify service fashion camera deposit expect snack memory proud opera flower casual","master":"7df339fc4837633a008a512dfd0ebe4f9a1236f048839025d33c574c4af0cc42","seed":"151551954b5b4719de472d6a96d5222c1eb688c476f190bc263d7370baebd0740e58f2c3fa00e3cb4615389bd306ed994d3da60808d1d08dd005c5292d3038d0","priv":"771fbf2a2c24130bb356f2490ca67799ea8be2d37b3f291323d5e9b079681cbc","pub":"02f6c6d95852798faa803021107d6a12d56bedcfbccb8110fee698b800931aa2dd","addr":"00b958acb44eb87590ec60f63bf352ec8248fecb"},{"mnemonic":"artist gaze ginger just pride royal strong absent wash figure adapt decide","master":"ddebae65962a513c384bd902b230fdb48f6315d6645a947d79b63c4d45cb6023","seed":"8f7914ef479cab13a658b773db99f56a3ebc267675e1a91a3b595c2f0616f2793cb862cf6038858d363576f994c28c630eaa54299480f168f05d7564261a2693","priv":"b416f93bfe3495ae2ea2dfcae8e587bf19650ee08194962b43ff97deb85c1bcd","pub":"03c12fd1c2e653e3990d15b59d64cd3c34f3f248fc2f1d52946fb218d3e94912cc","addr":"06c7ac564ab51a452c3fe76c0cab37541ae1b3ab"},{"mnemonic":"case mandate call napkin cash test ripple bounce page dilemma usage stay","master":"101248d364cc296423da1839cc3d0774ed090bd9e73f55f355963272129cc184","seed":"b5aa29ba3342da94663315038e9b6c2d61acde2e4cd7d08971d79576224d12af8478abe891de819771a6ddee37269a71b13c825f8ea056f7596d301b00a9c0c5","priv":"4b97318dbb30a0b86d6497a97f3138db00d268ac6165a0fb80f5b8e5d714e619","pub":"022487dbfb8e3061c3a342d75ceb25ad9a6acc6b8df0b224f3f521c8105a4036ab","addr":"984ecec7727a1f812b63b6a6899979b36bff2e9e"},{"mnemonic":"neither divorce grocery stadium narrow electric observe that six diagram hero toss","master":"59cb4dd0ccefad583b8c79c8dc1c5e1d1d77149cac9fb26674076287105a0936","seed":"9c5e31547f43116891fba52294c90c80a583c0375ed100d69e2b72e1453b591b1072ac402d24e011ffacfecd2c8fea1c2b1cf93a435996df42e1c8b19aaa3fb2","priv":"f490ea8e2e8011d7f9b9c57d9143606d470ff00bd51ca69a44c2107d7de7123a","pub":"03fe9d9451c9505075629309bca9b361edb59483e417f07a2759aad704143ad778","addr":"65b28d0f6e78c0743903e41e915de6231603eb5d"},{"mnemonic":"van lucky present hobby property front toy document report switch strong say","master":"5517d9a3e2996bdc9e317a447f0506c33dc20c3c36e6565dd8af76ac2a0158e0","seed":"779e44dd0d1401cdd9d84061044158fa4036d66192c5059bb7f33810e776de434e24518477bd4092a0c775bf7801887a72fd94f0d6bb46757ecc3764f8e17e93","priv":"bbb4c89a597315ac9524df42b17498d334d93b8d48f19188f5ed2327fc2800b5","pub":"022e2a217432ce25ef42196530550d177aff02cdf91332e4a86c8d1fc4f34b07e1","addr":"ad5ea6aa0d47a084f1d01c547165eb51d067871b"},{"mnemonic":"swing shell april silk tenant swim clown beyond under tool envelope private","master":"bee0c171d74d809a83d0a7f70ec1d8ef320efd80541127c9e3b2dca85e344ece","seed":"da69d4582287e64756d8bc49979952716991ebc878764f7c8f93649f5b721c35f5496e7e81f5f21cc987315f53a747911363a58790eade4c43c7c24911cf8b89","priv":"a6c35d6f727685419b4c3717f0dc31d33340536319963186c64f18932bb3d921","pub":"023465c95f48753fd4546f6be7b3c5959ac2de74445c2352e14d0c57b058ab0ae6","addr":"7959735517d011fc100b56f25b6115982d2299e3"},{"mnemonic":"carpet sketch depart tenant immense sock because caught close canoe insect crack","master":"ab35e8379ede1615b44bf8b1a8dbcc3ce88b6d6d0980cd63ea4f783340a1c372","seed":"379373dd202ad95fca8046f17a5f989268cbacd2697a02ee5efc2f022f9019580e7fc452c551d97c9aa2247c566313e454768e38d6585ad9561a09710a279ef2","priv":"a7165228a05945d4b01a1ad12911666504f8fea8c8819df414995bc1eaa9068f","pub":"025a7afe792c05bb3d94c7996bf5ce1fbb02bbf716593b2049f6e42a0328311f2b","addr":"1fe2d6a254c104cc38f1466355501890d370b30b"},{"mnemonic":"world sweet evolve club speed desk double verify gentle account youth column","master":"b2309b5c1e0a5c590859c5ec3bad4b94aaee3e2e7ef21080cfe0780617eff220","seed":"efa3dba671c0c4e35072d8958b4d7af280d8e20d3b6752b34a6b1e561d0f44bfa7829a5d540b52e02dc1b06735dfe18b2073a784aa307253dad71b9146024bda","priv":"a2a6288f9feff9b408fd6b056309dcde5cbc40489afb85413202443d7c283466","pub":"0307429d4171f929f79c8532cec5970c17d6f92ae014e14d99fb1f8fbc0637c9e5","addr":"1ae1b848027b0d1276a85ec2946bb7096755017a"},{"mnemonic":"patch cave blush omit calm habit autumn load focus tobacco barely switch","master":"972320ebeadc9ce9da0a447dd469c95bdcbbda09e6887f64dc9c6fcb8d6c91ac","seed":"dedd6610c32b2e5a3fb93de12f758d92041b0d1aee0bb2626dc0e6e8cebb8ecfdf87e25dcdc4f9646f2e37fcc071ff4137d914df7133a6e22fe8669ccfa7cbb7","priv":"17cfca7dd65152e0a3c77a2bf4a15b0a99b7e889dda49174acefc06d390bb255","pub":"0286d1e4ac0c7df1e1e6ebbe1b7aea5f0e0cef8ff3336a1c2f61c80d135a382234","addr":"b184ce8d18aa006e93ce901fbe879d3d8d6e6417"},{"mnemonic":"extend install wolf token genuine mother swift pencil scrub annual wealth hip","master":"4ef0a071d94253f9a60bffa7443918f43865f131a579ccf93d2ee16a518af2b8","seed":"f66926e6837305237316d7a69fbe18911f73a4f98278a2f6e6b1a8e06adfc1d189875ca401387b74c0198741619030187368c2a58e6b844aacd45fe89fcb6a93","priv":"f4c00a2ce7e9217e5e5e3f5398114cfde57f25833e920f14989e450645087bc1","pub":"02fb09ad3c161919060141c50b70c144cb991a553a57b25b2688e56ad163c3bcb1","addr":"556ed45bf7463ad502fc2fc07ac05fc073badbec"},{"mnemonic":"canal pink undo modify apart bachelor movie coach december exclude scale despair","master":"ebef42622df1bc744290f0c5d930aeefe2764dca7efba491b78ec958a5acc171","seed":"056347f396399c75761136da001671c6097fe21adcc920952db079afa5debb60d1c3612742d96af16de34a44c792da1ad995757e6a25e62953116d3016639c86","priv":"6a83065d2777eb4674e19f33a08b46c44753cce58902a41cdc3b8e3f0883050b","pub":"03a928c2c2aafeedb59d1588a86c42428b70171eed39b94f68f2097d9cdaa4c283","addr":"28053af43e4d7e35b32f9d174bd205206f94028a"},{"mnemonic":"brisk clip brass tunnel credit hip clock deputy dial hole song soda","master":"965fc6288cf02a8aa3081de292a513fd7b23fd010d373045e2a5cb37f38b2393","seed":"553cbc6a6599410913b6304461c1cc8b4a2c09c8bc1091c231d7167d27892ffb84b89c3c001ad40d4740d97224062c974ab01ea2dedfe1a3322f1a041964fb46","priv":"c9fe5b03317f7e7496f8824f24b782c09b5b801b3ba7a08d5bab508005f88aea","pub":"02ecf7641a4b3436c91ff3fe5b9f700cecf8cc3769945c665ff6129f6a60e77a7c","addr":"e578b715e9e84da6beac28486ed9492bea16435f"},{"mnemonic":"senior example flock banner pumpkin salt silk soda funny input puzzle ready","master":"7b0b3ad8cec24080ad6fcb9b1ba4b745820543666d362a188f4aafa52cddbfad","seed":"367a466ca4069c50df5571db657cf4394e843973e5801db4d7072c51453ce04b574b01e07ee8ef0836448f7fa220863e6cc7e6180618efaa3868e78b807b4b74","priv":"31f5e11528a372d7948cbdfb0519f974168d568128e441a5840a02c1ebc71f50","pub":"03b507120d46abde399656100fb664c736a2476a588f2c893d4be9a5f9acb0460a","addr":"941deb14e567c6d60446fe5767a0bb780be7fd0b"},{"mnemonic":"say leg next pelican brand topple blame plate snow month van cherry","master":"82a9d68ae21ace7c2e5d7d8ff58e4f0cc3c7e5db8031d4f83008d06088c477dc","seed":"69956cc226c79bca915b24c65112962205eeb161b79f66246b493d53eb7e1dc56f9dbe475d9650c0e72d7befc0b899f75f7bb3b3c100f7d6128b21cb4cd88396","priv":"cc512c35a0edcc0efc15ca9659bb536b3957420f00b32cd9e3a0d0e1cad5360a","pub":"03c4c82df36b093deca7eabe03ede5c5ed3639dd37ce8598ab8a6342b86142b07a","addr":"d0ae8f85e8c95b4906de99ed528331173f251e85"},{"mnemonic":"develop odor front thank mobile subject then cupboard human high crumble rabbit","master":"73704bde13f4867719c71fac5b37f16f30b69581a53e7f09118ed1368610b1c9","seed":"88f30341bc3ab214f1aaf379a8fbe498825029793d4ed2b50d12ef666fe45f06e39f68583c4cea8ca6a88e5ea62d8aacb499da4814fe346e020a78bc636a3cbb","priv":"53f91608a0b1825b05ae68992f97fbec8dd7adf877c085d1f30981d6f5262653","pub":"025f5e6339f5b651038512ddb5073eaf10c962f6e82bd2bd94c117cd4cf4de4fc6","addr":"d24cd460bc42f5222b18d5632d53163b648c982c"},{"mnemonic":"toward weasel copper blush mammal window weekend exhibit pizza element impulse valley","master":"3fba4e4716bc0b0f99f44a2bc7df34bdf60bbc5c1e2b76ff4295f21428465dd4","seed":"8051dd06bf13a9b79d82138ee3e5cd3e417c6d203ad0f6ceb81f38890d4c18b56d29c93ba76591c499c9cbcce1bdd205b26ab792d506d4903d90104a226422e2","priv":"0379555ca3e00311dc6949659afba1cf6019ab5690f14fe8368f0fd07b90a44d","pub":"032446f977ee3324c946b0d6409a4b7401becffbfcb868827e6639cb686bb2140f","addr":"87bf7d0cff36c282a9140657a4420f67b7b7433e"},{"mnemonic":"lift piece elevator remain horse virtual fragile dynamic whip lecture perfect fine","master":"bd16309fe9a58eb87ddf1b8338ad0ffe4b39706c4d568cfdf6f5ddb79064ecdf","seed":"8433dee7ff8aa73f785d23b09d8ce7f4e1e5cbcc7c20133d81f87c1d47b94f733631d5406c01f756ba7bbfbdca900eb2fa03f6b0a0bc1f06b9f3c3c9ea4d6d61","priv":"c4ce940b7e5cc0606f023daa3dd724d773e886d48af58c785fef7605b2524a16","pub":"03c89852febcb140ba3d5e9c95ba6ee843666ccaa69492ede61dec94e4e6f73949","addr":"3c04769875dca7cb3c6faddbf1960e1738118d7e"},{"mnemonic":"dash output lesson topic butter run ancient claw enlist approve chief observe","master":"8d37dc9da62db3dab7a0f209d8997dab75793704d6c18056f02b6768db414121","seed":"6ca57fc186a6eddbb0e8ac6dea4c1eb5d8361ca08d7b909a8e863284b38180cf8ddd2d9590f7611a5cb80bf1abcb679adde5646e69f62208557ab1a37e80394c","priv":"db5742d8734235964645a018fc4cead9402a113a60de0d7f55ab3816c9780e8e","pub":"03710b31603eef3e10abe51c57c62818ae65fc7f0aa4a91cba5f178a0cbdb313cb","addr":"742d17ad66f51524f28deadb3366859f14cf02ff"},{"mnemonic":"blouse maid inflict jungle yellow believe arrange cruise seat dust endless benefit","master":"d13daac6580c68186cb049fbb63c05bf2282919865c4018acd8446828dbf07fd","seed":"5b4132806e12b669813f1ab295ec58fc472fdb46769ea7181faf6e6b48f3cf4d8280e8ed5951306ed71b40d016afa3ecb71476a23f455ab6e41233ec2929ff0e","priv":"a8b701c1abda145e723d92fb246b692e44693704b52198ba33128267bba63155","pub":"032c532be31e8394d98571f3623372abafce8bdd0ef02c198544d0dc6a6fbc3cf4","addr":"4675d51ea6dddfd9b296532f3f2c101229cee094"},{"mnemonic":"only enlist indicate practice safe romance orient gallery cousin silk eager what","master":"2bee5e0de945af3aed16b2a06423d3447dc0c91af3ab8fb20b4fc0dfdd74bad6","seed":"92a6a62d9b145dfac6f66b7dbdc7bcd7b9ac10163541cb95e18e77742e3f2ce1773d74b0b290e40ecda2aff147bf81fa8997eb02668891a68912cf42cc666fc4","priv":"6a8e812a13716f00c5139220f1afc9a7ac584ed7553fbe8a13c50add54e5446c","pub":"0341a641547fc87d6a44abe879aef7b9f7c36e3491f221f667d10c568eee0fe99f","addr":"0fbd95b59a63b2a4238d6703ff49746a5950db8b"},{"mnemonic":"oppose fan kit prosper message pass fiction machine duty antenna planet ribbon","master":"6464d66007d3609d21971ad05877316fd8fc73a4b61d971676fcbdd32eec832f","seed":"36f65f329a478fc6cf1c9be171f9de9255affd1434ccc3420660a156b6cc0a85a10d5eb1547bdb5849325f5972b332afea7973449dee8a72db256479301337d4","priv":"94c741202799e22867b197c900c11e25855196e9e10e2f74f1939aefb7ddb233","pub":"020b86916271d84b2ea8f8b83ffded3b8f2bfbcb4ac17da7fd59c9ede27c00926a","addr":"d26eefcc75de9069e99c2e98699823b45effa9d9"},{"mnemonic":"acid summer coach step inform jewel secret tornado abuse kit shell guide","master":"4a10d5e6783e00d6f3453d8948dc5b39776de5690775aaa0a8429b2f11679ea0","seed":"46c698780417975feae4b6607e7728a00600e0e8e225d5d8f36ca076521238761c5b910759f70498e7dbda012d4325f3fe81011ca762c2eaa06649a18b79fc74","priv":"7d6a286693fdf1e51c26a64e5e5f8763b328934540b18001898537afa49e8b1c","pub":"0319013f6935be2eea7cdfe0544145708d24ffe9742a64b1a2d22f24bc5e380bf0","addr":"d53d22924ffcad0359a177345c32723e6260c7c4"},{"mnemonic":"general shed quick sea amused piece air infant jeans page eternal lens","master":"7b435a6cead581f339c1008061cadc687c40156312a8201c823158a6028cdb93","seed":"0843ac840c79c9c7dd85cadcea3496bc7a376592b07d233c35bdcfdd09725eff59f0ebc1d0cf255035ab5c7344adc3581dc543b134a5e70eca410b46596ee8c5","priv":"e61236f6e4557b45a6e39058b8606b7e3041de297cb0d39ad06c2e5414199d78","pub":"0241be261330b87c2388ab4077fbd4fab3a66e7d039d3ce4faad319c42b8e886e6","addr":"29777a50646b5f08eb7a428805a9778851d3116d"},{"mnemonic":"view hood trigger weapon dress roast load artefact dumb biology sadness receive","master":"a619455307c5edc87edfda01059cfdbb6c356e9f639b16b80b78e2421f1989c0","seed":"d89adefef230e418d7462194e5e1a817a0ade01435763ac5030ffe94fcf4b1f9d8a06090ff8a742d22d7a78e4a6392e35e8c318f5d0e495d0f8773ff126ab56c","priv":"1f9c2085c35b5fbf1a096174e7f11537b4f6d6327018dbf3b3311488865e5135","pub":"0208e02b8410833b43ab7674e25efd7081d6516aada029588f72a5478fa5c0aa20","addr":"cf4138b64094d8a9f58cb9068305a69ac50c4f0a"},{"mnemonic":"salon diary era brain chat panel design lawsuit behind practice please budget","master":"fce0a096c14774cc4bb44a4e9c65cf075a2ef5eebf96f08cb684455218f4dbe1","seed":"b154f70e78908ca26852b659a6f6252d83f3bbc4261cb1b9faa4ea80dbf1feb27958164163bf7ad82de0ff8b1af07c60f23b376910a24608c8af4b88aa0065ec","priv":"156d45c8db9bb83613334020e9b9486eafe16c1341aa05f4ed2095ff5a11dd66","pub":"0389bab1175f1cdd60a54828f8684c99c603d6b877597d1ab0349ce6a974fb9e68","addr":"f60cdc064ba139ed5ddcb6cfdf6c89ebdc378962"},{"mnemonic":"text horror panda eight mention victory wonder orange error few you illegal","master":"fd6c8375c6c3458be159f4c2fea8d25688163c7d306e17aa2dc32951eec2342d","seed":"ff314a8722d3291744c0ba07d569373771357795f72307366b0ee0c84b71ebd60a54d6f1c1c974bad1150e85415658f5d30fe76062d80468786fcc20ee8ee903","priv":"943fb32cbdd79c2e34ea72a9c31cddc3ebfb1a353808e471b77f54cd108f487c","pub":"02d17d74fcab59fac7d1cbea2838d5db3ecb1c24219b0b086ea1ea69559b8c2099","addr":"996916a8fe8dd544c1906a0afe4b48df43abb409"},{"mnemonic":"apart beach sibling debris chat hope toy happy bean same auction brother","master":"9eb93023f619ad35b6aba1051b791dbde9618bb74059632813b4e0f72b16c773","seed":"4fa270abbc1443fe7f593af42da0b40c17773eb7b9efc09c621b59424f84be65ed1ac755b88d71632a5a384c6dd679887c021dd91aa06fd9547f5c52bac8618f","priv":"3adc47b4297474a18744bf61f18e3030bbf58d68eccd4b0f9a207fc513edb851","pub":"037f6fe821009e2e043e66b167e7708d9534a543391b3ea17abf449f2807931462","addr":"6b9668671f23172dd08382e8db996dc9df687f80"},{"mnemonic":"tag bright bitter hedgehog stable level outside alter uncle hold have voice","master":"63157c3bb0a525d611de7539718f3b8de9f4f7a61bc9884d2f03ae0d30e7c5e7","seed":"e40b20a383e582f304c7feb315ae1bb405e9a5e12797706042d82918a19cada05fd28ca8615d929dfe7ea331d588ae3506cc3cae09f560bc13d544607e075ba9","priv":"8675ee046186c61b46576c02a8a7e5dea1d307667ceba7f6a56bca72fcd5452a","pub":"03654985961e5509210a31d1f54b15b37ff975717ef1f5ea7c2aafc76ced1b6326","addr":"972e9f2582e7ed35767b1b23ee73af4daeaf0b3e"},{"mnemonic":"wave fiction coral polar enough gauge whale magnet science second squirrel answer","master":"e2f288c51ea9bb3befc3245bc0739998db6199615869b4f27db9b613aa443549","seed":"2d9a178a3292c3f3987c35d44a3fdef2d849630ff49343bc5c3456a6701310c56cb0688d7a4bb7c3d58c40472d8a71121fb54f0108f6d682c648694f9ee85aa8","priv":"4a4d6db92c8cae67cb80b4f8fc7e63c94d3ce9ea67026c2544325c7c14c8318a","pub":"023cca73403af0fa0a0faba0545c21472996381917db6e481de9fc0de210dd4458","addr":"fdf8fbd1473714cc6dbac873822a7e0c7ffee153"},{"mnemonic":"process visa stuff pulse shop ability test aspect royal pipe protect fiscal","master":"742d13461d14c18707e2828f69b8deae3e13a9d6c90895993a788bf51f5f8eff","seed":"fc0ffaec77037a17923401aea05d7e37f09d2e7d35e9ad26a5dfa3de6e5da7c4b8c1d16e5ff6ca18570b069e1e8e49ef9ef475665e00cf189eef652e40d3553c","priv":"ef5da20d14c6d948536faf4ebfec5f11e430795986086371afb5a239befc3410","pub":"03a03b2dfd694a9d95ce1fd0f9c97f3db35c02aa001d26dc313c269f8a97bc1688","addr":"0d76c54b8af88721dcc4968dbe47607d7040d3cf"},{"mnemonic":"jar draft slab quarter during hawk grass monkey mandate prosper hour fortune","master":"98e2ccbe3470de355e090d69247d6506ee9bf82473d83fb98c20506543dc4fa7","seed":"e6bae0e46c8e66bbaedd465ed2669f9ca5b2bb68d829d237a1846763129932588a1aa4e69979a4eb3f73010d7c093b20a542efa2fddeaa00032abb847cc7bffd","priv":"9d1e402404867001cb24a3f501b676effb69865045e6ac2b0fcfdc34f9df494e","pub":"03f0bc42295a11b4ccf18b44659cb6d0e0eca3d2034c98f485f5ae43e07a82d90d","addr":"e1c21227bfe3c09188066ae189c7d499d855a600"},{"mnemonic":"camp vocal absent ostrich sleep version puzzle afraid execute clog eyebrow tissue","master":"fdabda4c563e3d7caec47b62f4f06ca53015f98b498676116e7b3df18a4b4412","seed":"b752500a4695f6cd66edb640021b2f590e85f4df2af298388f042cf7b665f0d6e643827bf705d3d1d9786e64bb5e184fb16e270ebffe0ce4ff7f37c285d28db0","priv":"32ade388afcf551e5b9195941ee3d290a39ab041dc9a2b748cc36c53cd43061e","pub":"0346e927eeb5549f8f38fa6280c0932947ef78f63b55410e872e0bf6988e64d03a","addr":"462344dcf5eb679f62d6470927d227365b208524"},{"mnemonic":"kite slogan all win burden staff actual oak cave bamboo advance uniform","master":"43888096d253b010da1f0369f75bc0d6a3b5f14dfbe5bccf7c8fe9bd78242205","seed":"b770074f2868e3aa42bfb76b444f782e4a109f2ed51677a79a69bda83dbdbff3a64b9ee163c57859f143a726d10aa73b223f0cdcdde6497fb95e0fc4a810f583","priv":"26533617dd8d7399f399d59f0f8a50c798668f04f44b52bf50728f20959c68e1","pub":"035ce21f82a71a8580d4bff49e520ab9815ac4d5b6cdcf6eaafefa4457dd9dea27","addr":"178918f829f9cd67d701c9a64b5bfba373a7c820"},{"mnemonic":"bind fire final soon dilemma analyst train ecology say drive slot bacon","master":"9aaeaa7f7a7deb8a976783f523695466b78117462804405e200ad1677e547125","seed":"155349996ca5a6fadb151c9ec425ca1e39df10509a8577f6e63096681c13b3980285777e515176feb7e4aba096b4d9d81a26b4d3477409432629a862669bef40","priv":"a890f151c33c87340a9021d3186c4df062822a3088fb7447bb419bbe5dbeb53f","pub":"0253d42cb2ab28f7fcdb2011e547dc88902e1e4d588e9261aff0869b118bcd4dc1","addr":"19b05c71780f27ec2e44ecab4c426108ea5a0f6c"},{"mnemonic":"image wool rug little visa embark poverty shop shield parent odor farm","master":"c68aae9896977ce21f5f547a4d0ea9995c9641c2a5f2cf89fd5a804485ec0e23","seed":"5c6885f90dfc1286f17b0079ed90dfc3974ce6b60d4193784b9668ec93725328ee5c0d027df1d551541a62fe2d87cc19cbdeed9a235a4510b738930dd3eb4eaf","priv":"d807cdd58fa84e4bda7fa998c8bbea12b7ecb5147c5edf841dcbce2c8663be43","pub":"0259b24a008d6ff4ce4d589e081ff100c28c3f8eb5440c8643257881f10da3658b","addr":"a20d7d609e6b2c855b9da18dea5f7ab3d091aee8"},{"mnemonic":"enrich soup all neither easily combine police ordinary review room keep swarm","master":"8265329602cac11df40567d54c8d4759bd9013c7a325c3d628b81640e95af3ce","seed":"3998bb700715a777bab05afae33a6e6553af17bf8c35eb67c7d4afcc38d6f35875c12f8602f381f797157102334f9c01c483f8fb41abb5f439ebefb76fe260c7","priv":"8956ffbb87e45ba0ac8f4142e179e6f934ca9b88d754fd5347c340a598878088","pub":"02af92f546047de11b15fb4a4c13c77ec6034248b84835f421826a44a68477fec6","addr":"7a6dbb8c88319d3076eb0b387dd2a9e4209b3229"},{"mnemonic":"tribe unfair track nephew erosion spike machine exist wine banner horn document","master":"c8e67f2accc6f9d543e1619dfda891664d349f11eae5617d32a9132c015b964a","seed":"ee1bddbc1eded07dbaf52feac014380400de9e006bbbb74f0c12095b381ba01a696ee3c3f62eaf4324e49b397755cef44667762ec46321f5762ef0e269dc9cc2","priv":"4caaf1ce46e2a6bf8e058fbf063da8683aaea07af831456522ae501d4d09d517","pub":"03df708621a45e5d5fa1108caebb51c513ed1616529aa6f89e3442a840aa37d73a","addr":"4d5e8ec1737a693fb2820994f4dd69291c5224d9"},{"mnemonic":"assume wet effort damp similar original salon aunt grant peasant fatal toast","master":"ea31d3203ecc33935024caac7c319223ff0f4b694cf011538521b318716cbdb3","seed":"11b227c8cf099bde463f72b505add695254eb9fc4714231e09e337f1499791ee2441bd5a148862265b6b5414d47f767398fa97a392a82a0c70648b4a52c1c2a7","priv":"f96a9c148945df0490611dfd06ca16f485a153e96857d746de0f2817cd2412c9","pub":"02b7b1c5e504b37104fd92465cdfda7261a60aad61b7b19b040d074fd07ec8ea39","addr":"036687e670428aab8b8057483c02ca0ea8dcc8eb"},{"mnemonic":"celery inspire ritual apart bring pause sugar monkey leaf tunnel minute alarm","master":"fd42c88307af74bce721f71af73cdc15eba0a186923fb9ff122d86597c1578ee","seed":"46042a350e5932c11cf735b60382d9a331a21f199cc484cdb9df4d8b9be0400c94d58ba1bc4d41826e605c625c7301bbc61e71f3da650d2928e83e07de6a9097","priv":"23c351e14d35d01f62da3cab73dacdc1c0ca052a76a41b58ae9d5c0a242e5fed","pub":"027ef3a78f366db4396cf1da6d124e31b254e90ad0c517267fb5a20a0f4034fc93","addr":"64a202ed14304b5f143d5d10a08167decc68fc53"},{"mnemonic":"text blanket lake damp doll guilt select torch midnight tumble toast dove","master":"c9653205120cceaf6cc768674a6389ee961403a4364e96220134acceed105299","seed":"79d5619f5dfeedd623bfdcd7d201f58ba55a2fbfd0424f32b9a220b4a39cd048e5cc82686e578b420cc35924e8fa31f1a4a29307dcedb35a4c69567da300b07f","priv":"7a7205d096a658f41be1d4d6b8a8644bf370c34a10a55f4a63ce1117d71715e6","pub":"029b2f32b98281d644232c4d603aaf89a98083edbc75b61f8aa5ad2c7ca910079d","addr":"ee416c1d1471a6d06b755fc92535f0bf1a2e3f4b"},{"mnemonic":"crime level shadow chronic rotate era reunion fossil boat shrug patient artefact","master":"fe8df8e3413387b50476e0ae97fe9f3851d5645f90b0bcf0b47b18d16387c057","seed":"0be8bda7509b72cc2d39c38609510fee439dc03600883e39f596cfc30cc2f48dbf14860ca33b7b7dd6f5f41f3d2fa71d20fe3995a698599605c5ea23b95917ce","priv":"4462e7a144223c97450b5d6728c6d017ed953490f7e762563993fa309152ddb0","pub":"0278a9fcb45bb02944c9cff032c013168943c6e0193c6ba6299af4397e8113a340","addr":"a925672e895976c8783d48180cedd09b2edd6108"},{"mnemonic":"chest flat tissue cousin defense ramp burst once rocket time super retreat","master":"7a8c847ca8357ab65fe99b5cca5d0b70bc8da05adc747c4c2f201fa1e22d753b","seed":"96003a7547f32ae6d8e5ce1bfbd5e580cc8c81d3eca4397e5e1453824b672a6d966d97fdd462ee8c0553d832c28c42fe61e78f01d056b2952a6589273ad57514","priv":"e4847fb09c9438d765da59b51c3dd825726ade929294007f9ccab300c100a2a6","pub":"032ebdaeaa817d8b8bd206839e18d17a85bc44ff4d7f0a5367c4f48098b511b43d","addr":"449402b089274c0b78b40e6f45e8297badfc1bc1"},{"mnemonic":"elegant dune all subject country diet flag universe gap ability hello torch","master":"fba87eced090b1d991c077467db81d22e2691f9b15ea2e8ccf03a51a556ee13e","seed":"1f1cd5b090411b96728a6959a4639e3b2bd68d4ea5c6ceb801a5849af109650237d2a6727ac80e7b317c625faa593d7a8caefae3ffbb55f110bd66672cf516e5","priv":"31cf4c4c7e75e35e16e01071e2db66a51758c8cc2b5df3377462046e4d87f3d2","pub":"03722bc559c95cdd7baf08a6965a47a7762fb974a854d290295bd853f6be0a83cb","addr":"b7f0dfb1c5136bd9bc267e9c7b33c047d6a1429e"},{"mnemonic":"give citizen chief eagle horse alien below flip vanish eye sniff bus","master":"c3dc631ea1b0226bfa3572fe9b95d72d08bd160cdd5fbc2fc96866adaa49a431","seed":"196d5c0aa7839623842c51f2e5593695ddb596bcd839c70af2940e8d00f31dd68b45876ae7aeefc6032e8b939b4feb8114ce9c403e4b83e28a49ea69a97360ca","priv":"9fbb966c19808bb2b7d07c420f4aa59f7a41cce08499c4da2bc216d857ce86d0","pub":"02650905aba484a16f7c1a2b1bcd0ed4f16f45d1770cce32ed127bc046d5f2c3e0","addr":"1441b38310ffb1135c2c48269746894accf627a0"},{"mnemonic":"gym term crawl benefit kite various pepper noble into slim try then","master":"e5826174e32bce315c7baa21ca97c613b4eb095d74651867671afd3848343668","seed":"198b6d6de96c726477cf8cdde784fc43a89dd46ee8fea27933ab8daf09826ad0d7bdaa0c50f8d5c574f6f2e6ef7adabd9c2382feaae061c9cb10a3ba22c5dcac","priv":"ec23afe15c13d5f0c86a47b18ca81761976442cd7c07975c88d6ad475771694f","pub":"0331ddb4db50ff58ac1709986111cbacada08344bab6210da4a2f23b0c3208e100","addr":"625e5b098af6c98677a9a2cba960e84c11dfcee1"},{"mnemonic":"pause web there legal eight plug wheat convince adapt radar rely inquiry","master":"b13d46f4a0c6ef2cac56cb9c522dc35b8d02f4ed4f09f524d206e9ea6e6e6082","seed":"5f34196fb26a7bf0a9063284f702d934c03eb2ac10f3dc230a999e9566d3561b9dc0a61d199e7d9642b06474c08f9e12e618ac5cc1d805ec9cb9489d460f377f","priv":"b410cd330abf2a25f36b24fcbf72f73e5c62786cb5c0c24fdbc3cdf6ceb892f6","pub":"03755fa255c3f111f778c270f991a5cc0fc24c0068d78355ce340258aa7a3b81ce","addr":"170438465b203b844f98cf54b49cdf17094afd80"},{"mnemonic":"thought morning dad seat payment ball club goat man urge forget remain","master":"af52a1f224909be5cd5e7be0b960a995c1d25652cc0c6791060b98e1aab4ca9e","seed":"1e9bd47b5411fcdb8145b9eb1e7092eb218774c85da6efa3756e9a55e5ddc2153eeb568139c638af44cfe6cdc2a570451ed68c801dc2ff726b45ed43e8fd0691","priv":"5e42b1c08965620e1e647e89fe87c9079060121f5a0b65a5ca8cc462fdd13b20","pub":"03a7ca8e9445a67a6884f58e0e79b1571b34b90dad6df4c0b8dfd103e500bcb20d","addr":"f44393294c3f1369012836ab432386f1af2cce44"},{"mnemonic":"twenty memory ten own runway grocery polar dog curious program gap country","master":"f08104e81f54a7bdb17ee647c06009667d4d1ac6efe286495bcf2327c229adcd","seed":"10d3111f871ce9bda4a790e148657f6dae8586d0d80379d40e56cbf180876ef7ea5f2a66f006a8a8a46c7de99a4e4f8f5fa4cd1a3756a38a71971298af917d21","priv":"db3cc8657599d42858ee1f1a7480e68ee8cfecc0fbcca01556c1fe5e8f3a0025","pub":"02add58efeceddf43db9f3d12b54dcc1f1369f152d427a7ad5e3c8de5de23a16e3","addr":"eb30fff2557ca87addcffc02d3a237fd28511799"},{"mnemonic":"elevator sock worry budget skull bitter faith taste skill physical mass heavy","master":"bb2b406a20bc8b38709251d1cabf4631428e2ac053154db69e2f1c6d236c4716","seed":"71fd4f050ec95c4147aed32ea34bc065e67635315b1077a0e9453d883b0a4519015ebd5de2ad0f2a628c20881567532f3b679541e23892eca14d5ebf4456a8b0","priv":"ae56d8b12dd0cc788ec4d5eed7153b108aad7606d2f3653ba5c61bae06dd1cf2","pub":"0347f3d0ed7552ac8d667036f4403911b7a98f71cc1730223e74fee4194d0aeebf","addr":"98198786f5e46f72ac47e88168ede1378404ee70"},{"mnemonic":"message power uncle glimpse repair coin wasp flower alpha claim federal tourist","master":"28f596f188bfd9a0f1ba2729260fc20ecc1c1b67a20bc8a4f7bc0e810a625ec5","seed":"5a23114eb877a016fcbc82146957d2b9880ac79582776fecad126fd6b5aa81343cdc69c46f29ad17e19411f0f4e78817338a9fd39a72efcbad31ab965f8782ea","priv":"1376caa21630e355fe38924afbacded452c9552eafbd5b60dbf090fedae3d793","pub":"02713f022ea80f4807af048cd254ed4dc5a1276c21e4a7436306f83731843a5402","addr":"8a1cdab91bb44b089a51f0a736e914a37cdf20e0"},{"mnemonic":"infant walk right wave helmet punch alone path since output fashion isolate","master":"0de852a741c097122fccfc397d9ec55223ec1a6ef395c1848097a45aa6a4f0c5","seed":"d1fbf777f64ac5ab0375876dab5e934794782c73e84dfcb19a291a6a083d6aa6d0d3de7e8c804a8c02875ea5fa54a06159538f98f9174bc6cf69ec94e3bc682c","priv":"0fa223b1f217baf7a296e483ded5e6ee97facd5841fb85c78b5ecc4db7681838","pub":"0276da67ef9f098a2ee3a3a18d4ca17705448694f895c6ffc97959fd6507e904cc","addr":"02aacee8fb5e56f4b39fcbf7253b7a1302b0d8d6"},{"mnemonic":"type squeeze vocal monkey baby scale behind knife cherry luggage struggle what","master":"1e156273aae1d8c0b58558726d2f19924b1d06659adbe3539134f58a8a1ff82a","seed":"333847666190d4b5c9535920c47a6f68c586aa3f587cb7349542c0a30950f7208b3467ed437cd815007b11a4326f20b34f45335ba64cf9bcf17ef71aa10b9b86","priv":"c307afbc58b3561c7d276502e1b81de8b01c196bbdc1ccbde5855e00707f58ca","pub":"031a16f0dfd054f5d97233ce07cf647ad598a84177baa82d2f89a7f23cd0f2a0d7","addr":"3b1d109cab9545dbbe82631dd4f903b43cd79bcd"},{"mnemonic":"elder shallow catch anchor slice hundred kangaroo stereo myth proud clarify open","master":"babfd17c1c9647175a7b2739eb700c3385be4948f97d1dcd50cd17fbd605d6d2","seed":"eeb063599bfcd2520f7f07b3eb885cb83953ef9931926db8d1e76b689954ca902c6edb785af6d579ffe89f9253f5864cf21d3f7373986a6c1259e4bd0bd16437","priv":"652f38771bbddf64d2040c1300c317c42739f50839b604cc8f34e050b4590a95","pub":"02608acfabfda5579dae5d9efd4a191925864aa656e55b039da9736a874d46b8cc","addr":"f14a9e2a34c1cd6b95fdd473ddd799429fc03def"},{"mnemonic":"screen slim squirrel fox stairs still close club day satisfy either sentence","master":"a0101753f4e05c8ab9bfb6224120ef9c1f99ebaebc31c5aae114f68387676f5a","seed":"c1ec85f49b1928ed7ac6dc781703a66ef39692ba39f5296d9da33f93fd68cd85b0eb57cfb6ea115ceba67df15e4d835d813e3bbeea0363ef513220b98768316e","priv":"91af59d9bb62c2a2f0bf1b5c8729f2be88cc82e141fa045e59748f2b156b9a03","pub":"03a76d83455b42964c021906265a4e006d2c81555c8591a641ac0c495cd0d05cdf","addr":"093ad2a9be7153222002d94eefadef04ca7ea811"},{"mnemonic":"insect deer physical dog aunt metal super soda antenna broken undo athlete","master":"d3a4a655aafd0dd18e196b2f4e57bd4b1d27fe521f9312104ab75ac92ad92e62","seed":"9975a73239f3e2543108bec6d75c5c9a24261c9b2ffd0e088f6bc8369804b70ce99ddbced3c4cc2e1cd955be824f5d881af09e5ed403d7da2a533c73d9d82df8","priv":"0a313ab091f7fda44e92ba2482b8fe1294646bc9d27865bfb34ccd702376328e","pub":"02ff6999ead46c520026b1faa27004851fe1b2295e5f8b79ee1e87b330c45216d5","addr":"e3ba9de7fc81b75eaf4c6ff126b434dfda0a5008"},{"mnemonic":"layer embody trade slice odor deny please small clerk device ceiling right","master":"2b39b4a5aa164609c2734c00c1029534c5d7f3bc6b94268758acb506bb139608","seed":"1bd10cd1d50db3dd8f9e76ecf39b846d3a2aa44be383868921b155c03ed3485d52236c49783f5334c60e473458be3b3f588b9ac3e814d144128d96fbc002393a","priv":"5953e09dbaa56a12eb5e69231835924c0e38d5c322a43c354152a0333f26756a","pub":"02254c4d6976965f7c6122efdd52f65ee9e25b87549789499b72bf5d7213829d04","addr":"603d5b893cc41223eff641fc1a374a3ce97138a7"},{"mnemonic":"token heavy lazy little dry glass riot hobby keen blur wise okay","master":"7d84cf25bd5f7dabbdb7beae83d1fd128390b3a0fbf2d56b44fd34a7e0d11539","seed":"8f144bce2e5c73f44e5dd62ef4695c6e3457ddc37f64d1552e5b46a771120ccac23cd592b27a135c3593f8f21eff22625ca7bae8581f4f8ed223e813f5d98a6d","priv":"37d2c3993b275606241307c7b2339e304180555df260f18f524abcc75abfb0f3","pub":"02aa996bf4d889c9b37aacbd07d8ac3ea69e535c065a4b4258844b10c2a08eab8d","addr":"4a3860dc280c75a00382f3d8f11cfa01fda90188"},{"mnemonic":"tray drink color across armor spawn vast skin debate huge surround play","master":"e0cb1eb09b82bcd37fa63260e49222cf13b0b07cc1568aeb8849b538bda65a1f","seed":"faab52fca3060d1903e861706b57ca50ca71befd2d5363031128235cbb80e53ec943362f2288fcd1c850e94cf4e7c38029dff1880dc3f8c8bd440b14ed15fd01","priv":"747aaf84f110aefb34026ca0492894ccb8e0d1b34c5b0f9a9027320289914b52","pub":"022d3e35a5f51a6be1d0aff686e66e2fe282e42a51044ddca65b8a7659b6386f3c","addr":"43057d3d99231163cca42fe248b9d350ad7ae146"},{"mnemonic":"ring act drill burden sunny someone riot spawn uncover sauce today jeans","master":"676a5a20f35054b4f7ceb22d38226df0b2fa192b6bbdce9a96fba2b51845322d","seed":"979a3caac9130fab128a59243447f5add05251ad385f0c0ecca60920cc954ec516479aaf4def728770606db840088242a8bd5c3758f1ecfa7d3f8d54beb5cd6e","priv":"0e3f2306a70147cb332877343ff260e18c37d807d1ada3db3545dc21cb7adf71","pub":"03374f7d18335d1677902e2fb7c7afb6c3646ab18bc76d12793b86e0097d2b79fe","addr":"4f65a8364edc65cc430f4916af41cbf339c34a4f"},{"mnemonic":"title reopen welcome pottery stand safe scrap little razor rate oval rabbit","master":"600c330dca29a426ecae0e5e6dfcb13e004efcfcab0fcecab676e42462caded3","seed":"7554d02d091605c47acf117773dfb96821e28c551c2c5e855803f79f7ec9b9e5a4a7273c605c2a53771e045bcada466d3b4a88d03a7fcc1644d990a8dcb21ff8","priv":"79621755dac86e6026853bcd0d4c032b48b7c5eabf81cbf8ca4e9d056fcec5a8","pub":"03f63693949fdc2b62cabc47059e8b1d9ba748800bf08a30001e19aee33627d189","addr":"ae0c6ad31b94b29d502a846e1e371356a96a4f2d"},{"mnemonic":"hat stereo gold maple advance wreck poet dawn excite midnight kind type","master":"661d8b63856089b6f3d6bb37558d4deba395c7ea7797d022db398c3224696316","seed":"b13cc53d42e9726cdd9fda4279a660d987dc3d302409a0ccc844731ae1b138495200bd1ddee3912124dbe826e62567a9b9fb57a33b638cbc4a56f8b55df3182f","priv":"548da8b9469029522189b9d4e30f6b58c75f234f18b99d4f4dffd027b6e022ed","pub":"032df4ae6e9e3ed444bc1fe3eba670f2d877de7e579309fe3e7a8ea3b5176999e1","addr":"f94c66a0851fbdb52cb7b6774c736cfe4f99edae"},{"mnemonic":"image wolf among domain where post bicycle perfect cloth october bonus plug","master":"1751c464935d8be5b1f43188a61f14d0f509cbe289d5e586f1c5057299c05d56","seed":"80bc37cd3dacb7020735655be8a001ea26bd8a5a74d950a5ba3f31603bd929531d6da39d06499927e5acaaef4845b3403e73e3bbc7a43434242dd2b432f17529","priv":"38db8c107323379d07ec59fa7b76ceb436a5430468562fbabdecf4bd0a06d58a","pub":"02c5e7d3d224db89285ff2f71c27a68b267f9fd799f05ee334084edd4b91d4071c","addr":"40551f9071d46824a15fba194d98425698145795"},{"mnemonic":"squirrel grit cross witness wagon defense marriage tonight absurd organ load sing","master":"d5739288f3d99e1759690e2d68e5ba543151cd58a0d55d0be52f0a4cfcdc2a3d","seed":"a3e09aa4755d4a4c48f4611a7a8c22040feca1694415b2a6976d2ce161102557f5e855d5195b0132c9e7f23e01b825465eaa10b19ed323647ea5beac8a635e75","priv":"055f00180e4bf6548ba7b525236f63444808c0052fabc4eb14cdad2debf499a7","pub":"03f9831a1213aedf436bdd9e898f1d571ed6a7858f0168d60afce7ac389dc27d86","addr":"8a1289c49f8d83275e4c2201c87ded060ab38a36"},{"mnemonic":"cloth diamond test stay also risk music unit beauty escape trip draw","master":"6d16a7ccf9577b8948afab2ac83cb12ea124e9cf86506e6d08f3fd685ba97a71","seed":"d67dbc3d1e00b7703a7a44b91ddb891d89128f59d2f298b2a48f464aaea9852b97fd3002569d9e4b8a1cebf0d7eef3b708a944b1cb745844fc01b71829bbb2ac","priv":"d52b78224b5e2b9d074b4141dba1956b33f66ec7e8e271a20786d04136d1755a","pub":"020fd792486b8d8777b9910bcb1c28c52fdbaf239e07885b94ebf85edca05f3609","addr":"f571651ea3de2e84b53da4af619b1c51acdf9c13"},{"mnemonic":"vanish slush budget question demise bargain inner push weasel clay project project","master":"11828853c3e89d3515ac8c838d2a5107079c93695c430e2d5383fff8a0a98de2","seed":"9f838e1b4140cefd6857ecda55690fdf36bf4bf0247833801a481130844a89d0483b05e9cd1042a855714f67298a3f9d8cc093e6bf9e86b4c7bf16223f4f89d8","priv":"2e5945a384de4ed1030a299e8dddd2c9ca20aff939641e3cdbb00f5619b186f0","pub":"03a52a0f153c92a5fcde0f004c4ab3166f040b6e7e609afee6c2d0e494c18e7a75","addr":"09e3386e3636e365d8bfd15e822b0cb50136b5ba"},{"mnemonic":"blind desk mobile debate orchard nerve crack tower little scrub twist fantasy","master":"4d677a558cc1d0911ccea1c14c817b8fea57668f9a81af43ed036a1bbbd8b6ba","seed":"eb67d96512b6136ce060c7b7800fe48a8fc2ebf50a6051f4be4255a5e974fa0443f0c5ce1efb0b0a87f227b0da5d740452313d86561a42262e5681864b529054","priv":"d54dea50a4dbc94530ed3c04771c6f4c71487cf3562e89b79684fd6ab7843846","pub":"032622eaf55666e30dc53d73a88517f24a08f6ad703f02db30e87368c5344cbb17","addr":"eaf15d884294d6b72b618c55206db3685e1b0532"},{"mnemonic":"thumb nurse there alcohol legal agent abandon sight pill street speed catalog","master":"1a041414d1258b30a589e61e6d8ba19a34ca2b2e8bca68962c0c9dad5863f5a4","seed":"1b539679974f7355ee02c9d6609640acdae476ea23bc7d5982604e20009bf3ec153f9ec82fcae5de92570d2bf94672047557a36b3ad3f3ec03b910038658b816","priv":"5ee3c0258920c32da893a0fa051593544fd3547c6e37123ef9d6edaea940dfc3","pub":"03e8714f4d3e9c3e342dae646deb62c31cf3a4bda119a820b0ff6810b8e3d85383","addr":"a3eeef1bfc965df2fe15abf7cbf19b381a458478"},{"mnemonic":"crew venture wall front gesture jaguar often arm fly scare lunar guide","master":"656a72d27ab1a5fb50e27fb598037dc2ebf60b1b9f0bf7612a8490c1efed6b39","seed":"1cd2a5e644dfe51e367da6784848ec64ab3c4965818dd3529f00efdc102388b02dedb2207052bfaabb32a4039f76594b006aec871dbe9856f2b71fe6f426d75b","priv":"67cc3ba8f7d83c6d08737e1b56dfe469c21500f20fac09e8bc096a091682ae73","pub":"03ca0d6eac7a2663451d29012c25bcf469d12c556f30a57a34fe6d66d27d506db4","addr":"5acc54542938330e6961c6ee770760c135ede717"},{"mnemonic":"survey never crew section reward cotton praise ketchup treat suit knock text","master":"7175659726fd0ffbbd37fd2c7e309589d32b271672ca5459b7e1daf5f2daa105","seed":"a5a37f251e3c745169bed474c8ae6599da847ea473da32b8ff922f2c710a3c31034dfc45dee514af90246bbb1317e21669ccaa0666d3c88c8f729fddfb95d240","priv":"fdd98704f53d7ad0e43ae71437626048fe23f71c24a67f31b9491b60c4db3c9e","pub":"026936de280e31c060bcbc777bf28ed1ccd0128be0c25f9d074378f72db1de0f4a","addr":"997434efd190bbf98541832fc48283ff736a067e"},{"mnemonic":"lonely physical plastic final cool beef cloth chaos dizzy enough whale farm","master":"79842f02a7d39d8f19373fb1ca3c73f1961c916628279a00c96a980cdeade871","seed":"f957d8197386bd4ed6c5000bbbd02f70d6b4b76953dc6a3e36f021d488cec54293198f64fe6d46c52041bd34efbceb1e6cce3184a42c8fd37903b924112ba2f6","priv":"d58df19dab225b29ea77bfbcda84c2f1982ccb6e1eb79011bde7adcc03b95050","pub":"024ed971b9763ad9aa8be27d9b75d9ff2f443594da5e3e0b7a598c71f6c2e0d759","addr":"f4d72185f769724b50be932caf960a1a3951ea84"},{"mnemonic":"can three trend breeze spatial puzzle stool cherry raccoon sauce bounce balcony","master":"e76d9d768fe3ead9a6c1b561649b292d8659a6778442f9451a8fdd52e35637d7","seed":"85aae0e7a3e2efd67741cb0d752a078b250deb53927bea3f384d72661889409389188f267d7be61629c6a85aa7dc5b2607a066f32ac39a2c5efc4720b4a72e7e","priv":"57a934f4fdbb619a40394cda13acd1fc61ed9f17e4b4fcbcac2248b5703eb71e","pub":"02004c3736772a2cb507231b7ab75bfc7c1d35ca9e49523f1076e7d0925acce8ce","addr":"859915a9f70f142a41b0d169e7ec342061fbebdf"},{"mnemonic":"gaze example quote much tourist pitch victory common lazy message random glad","master":"6fdb41e8f54d6f123c75856c1c13caccb01bbd7e84b1c3385fb5fdb885f72f09","seed":"bdb5196003c1d1d272367b12affaa4d0cb37fb3aeea88bde6992dfd3519af34e94729ebc9e8057b9e462c064292a7cac18eeb838ec0f71b082cfd66bece3c5e4","priv":"1f264f1e86de3b28ea8f4827dee679787e60e34e81271139a8e74870518bce27","pub":"035e75a0beff3188c9d5aea7a760b879413ef712d98354b702aedc3385d58c903d","addr":"67c21921fa2c47c5657ae2434c29f447511fbab2"},{"mnemonic":"taste panda wreck guard trend blush armor giant innocent palm trouble silly","master":"8f3c32829998fdb3355b3a88caf484eaf7d67122c991c2402d77eb3e5e4f8416","seed":"fcf9f308ac99d81e74329f60787fd311848a2e56384202c37c025bafeb7022e4b47ca24a680d07d073566c487f39c8c0dc4bf43569b73f30ba4a2c260ce98e28","priv":"463194496ba851f6455e8298a0d4c118341bb0183c73c2d7739ef2c5bee38e0e","pub":"034a87ade739d3da37c8f15928db9b064bd1d1af0c10ffbb9f2027f42061c9bdf2","addr":"b0afd62cbf962672d092732872a6ab2795c9132c"},{"mnemonic":"tragic enter actress believe nominee floor vehicle dinner bulb useful case bamboo","master":"66f8cb0425dd278236de1352e9b2e47fbb8662d1de9d31227c4f18873b6aaf4d","seed":"5f0f5113b93be9036f009972fc49d9fa444c0dc4f89ad2e5d590f33b2aee250194494bfff047f8c9cc0261208d0d842f4c8a320d68f852c0938b6dd472a9c819","priv":"3fcea63ea530b9539af1ea66f5ceca1acc508fcf21a0d5ac21a378af5aabfb37","pub":"0256ac4246f19dbd12af232b2fe00efd158624ea48728b3a647c8237b7b9c1fe56","addr":"13804f3f5f0ba3c8f769b2e3428962f4ae88e4b0"},{"mnemonic":"between grocery tool polar already roast cash budget fatigue museum obvious notable","master":"c13b5bb1840f804c9b1da51fdb788f9262fad64b4c576370ede2d9c4001de2fe","seed":"1e5a866e10ee3ed7e727f59673328eae03808cfdb1bd3c9f155a1ce8f7c9bee61fd8d68d6b995588c0264400780467313ba92a9c359edc48ca59dcfff62567c4","priv":"08d7d4fa44f89fddc836ea265b71e9985363cf4e9ba0cb5b1ef0bfcbd00e0aa4","pub":"03514e8e0c990a6a60b1138351255827c7c46e15c3bc25f55946276b5fc56d9068","addr":"0c759c31f5f142dac57e712c7a09c99a3a0875ba"},{"mnemonic":"combine unique pig mind pair area chase idea abstract grow into result","master":"e7d06819461fbfccd1e05b6d63507b179854fd75fe7fcce2cd04ad6eb89fd11d","seed":"a1863842b2a6d82dca20ece807c12c58f6c4d2d1fcfc2577baa368a3a45a7ccb0650009ab558bc8350b77f9a03f6155e8c35f76ebf9b30133dc732ef5abe69cb","priv":"a545f01810112491c613a35c829157cc6a3dbd30b4b51f1d1eaa53fefb61e09b","pub":"02008752e98ab20e0fc8c9859884f97423c2315f5f35d1d757907b0affda7da9de","addr":"353ac98ae22d3161ce81bed93d7a0d77abd9e11a"},{"mnemonic":"bonus voyage inject they glory wreck soldier anchor also job cattle random","master":"cc5593732d2681a1aed33672cfe74f24238443d1b118235550834e14bc720235","seed":"ae6175dae17f8ab7481092eac1ee7d4c08d9ffeb69850745a1507c63426aaee1ef3bc6ae7fad3152d9bcb58e77a14a9570a4c550078c963991b215db64a2e9eb","priv":"45118b84fd5c7802b71fead3d78e5953e1841ccb4d4cddc20aa154e9fbfcf467","pub":"02a0378a67714f50eb383b47f7ab67b32c2adc2b8376b12da9e656ef11da9172c7","addr":"aa4e5ff5d78486413eef80525d20fc0a46befd5c"},{"mnemonic":"axis grace advance retreat fence prison traffic shield visual evoke upper they","master":"b0f1e554c9c4c38355939ec8641548df255dc8260ee2beb02eea8436359731fe","seed":"42d47d86e0eba00ac90f92e464cb034b25e3e261618ccb7893524e6103b8ab8edac9262e7417ca55cec0e5b90f0405d6b43b920317597d8b67c34f909b308ad9","priv":"0e986b99de5e1403218a3d6e64eda2c6e980002fc9345795464c6e100c4d214f","pub":"028193d72358fde04b71d553794171a3f6800d2d20d1d4821c2d411d9dcab044f0","addr":"68ef0232b6199e173a31b522f73b8740807e7991"},{"mnemonic":"speed advice script dynamic company foam mimic conduct wasp soft stick insane","master":"b0f61b56e248b14dc116231490779949a829920a9caf582fe25df4459e65108d","seed":"7b8b72a1bff2551f2e704e68f364b8d578e24bd6c51fead9805c2c33bf463dd462c793f73b88ca6353e8385a5baeba2c91e4efd6ccc2b80351cfed9d9a6d7738","priv":"32e5a08edf79a3192b221f978e209fee7f7d64f83c28ff57f47ff5757b727b14","pub":"02fa4ba71ba618ce2f1508b0386ceb0e09e202722353d9724a597cd680130c53e3","addr":"713ae3fbba6df605c843147183dfbf3a3fdd86c4"},{"mnemonic":"punch fame nasty actual betray melt find wink cross hobby double there","master":"a7bb2a4a7cc93d7dc12efe29071329febba1c999c4e7766172d9e53fe29cb0c8","seed":"6876d29989880fd1bae26463cd01cb148c68ea842603822657d0bdc708a8987cd33453087febfeda2f3ea53f2fcd65ee1c5f89b0cfd8aefefee2c81ed32c91e0","priv":"731b30c783b7cfeb7458db1e0c0db2586216b6ba661f1adff761977c782d24ef","pub":"03ee75f400c95ecc4b852cd1ea2b434f5cbdd2f6eb8a881f0f24a3d1098a6ce778","addr":"96ce0c791931f867dc4fddd2f091a552f1e57d26"},{"mnemonic":"feed session repair glad envelope pepper faculty earn hour slender cost boat","master":"d8507e0bbacaa494dd65a38d82c02f00d168ec8bdff9991f78ed5fc13ba81d38","seed":"aa9ac398fd8c94bef20e9b9a94bdb81fcafaec2453efaa2929730145528495515d7632de9cf558313a35d50c0428a15f01efc831379a696f632705bba7394ce8","priv":"ef95d3d563a0e774c8d486adeb2d22f4fcf4cddd80cd2a05471b691c12cfe0fd","pub":"026b711c8ed8ea414276c9d7c7a92739a350cbf01372d41c2160c046e1459223be","addr":"72da6e869b7d435ff2507e658acb26e0a50e6303"},{"mnemonic":"person affair gesture cherry intact day wrestle crew okay express throw large","master":"84a10e3f12a6d24e76cd66e934116f9478d8b1bf912fc61c9e00058f7c489f6a","seed":"5dbd3c2fe715a4dd9ca5129df8fddac1f699429636a58bcc2379791af11dcd77bd9b84f3d62539bf5eb41eb8fc966304cf334cc46133b7e42104a18733514777","priv":"337a826c33caf41a74b465c6981986d629c13a71aeef4c0e7c2b78fc3dff613b","pub":"03edadf784d13c4e22b33b61222e20348cc68e245d8b143dde09d37963e9e01eef","addr":"916fd4d732155d78fd412b4cd78abb49bac56c19"},{"mnemonic":"nose talent woman drip regular endless bar resemble friend mean frog cage","master":"1beb38f144a90141600a34813e98ac3cca43009e23c542b3009f3e22988b9705","seed":"aa324508ec3951c30fa4b32ca7a4ea48773c572652076a2a7e6aadec3faee23c3388663002b086483f39a1b08fd50ff233ddefded05f583211686d0323dd4d64","priv":"67109cb296022e46fbd12569cbbaeecec1843479bc716f7ed07ff996561f8196","pub":"03239409138ec1b5a60e5d72f894e95d599be18978d9f43bf201c85bc20210b473","addr":"580cdfe5a38f37a644204cd1d4b0094dffb982d6"},{"mnemonic":"undo thrive relax ancient menu milk sand race bargain evidence steak tomorrow","master":"1db1b33c9f26783cf9743ec719022a1280c8257ba11b2b03f35455cdfeb10ddb","seed":"b9905331927ef09ae2dbf5bec382cf3f18bd9cc03bbd4d21e8f63cbde32cd22abf07bdea578a201558f2a1f09f2fec8e3c7557f731a57439f1e348bd7530d9f3","priv":"c91c34d1a5218ecbfb63990b636d8bcbf6d7ba4723b70c960597f1b4ffb2854b","pub":"03c7d4b726c2ef316d8e60f0941e2af09c0cc1a9967aed825c322b629f74ebbab0","addr":"cbc5acf5001cb4d88fe88404cf3d1a919b127aa5"},{"mnemonic":"relax owner update nice speak office assault pottery uphold clump since ball","master":"5540b4c874952470d93f8a94689bd58c03c8bff373c74890e70b63caf9b5fe48","seed":"c3e8c04ce0d472761c468cdc13d04cd8fee5333a806259657923aad79d9bab8333329353e69cd3fc67833a7041a2c49f3c2b0654a4d243415f3f2761a4f7fa29","priv":"882ea08c9157b6bbfd686007ad668de373f34dbeb08e5d983a447a12c9efad3e","pub":"022f8b50589f2413d294e33de8fcac751c4c6ae54b359ba428c4a1ae28cd2ff57f","addr":"6ec1ae2736cb2a9e1dae6b5d5c7d408347db2dce"},{"mnemonic":"immune find story sadness depend debris popular swallow egg gauge firm drop","master":"fa46f28a8f32770393d3a3fd934141823d48ae561d8952ef3ac87839867840c7","seed":"6b5234df2c1b9ecb85d8493849e66186f98a828287e133cffc210140d63bee412ec58ede1968c2999485ef80710a83a99e7ffe205c404c5bda7346369c16f6fa","priv":"77cc4d765ee74267ea50ce3637ff9916370802a4940bffc8f7b76aac869ef80d","pub":"037d1e64685d2229c9b49baf2245511b3dc26733e1275c36f60555e2eeee32aead","addr":"80d9e410145ca65c65f143804e7e13f0572693af"},{"mnemonic":"airport refuse lake shuffle borrow depend agree oven huge hen brand reward","master":"b21a47b26023f1ed1963f81a2ec424c88520055cb2d69c329c76e6a354cccf91","seed":"7ae802a81c37abaf146f9c7606409640c53a3502da921347dbc260fc4fa5a164ec458480038ac10a3dc2c5ec15c600e72e011392413e13b9daeea82c2e9ef611","priv":"748f3e6cb6caafe5a188d528557ec1c1e817f990394bea8a011e3cbf6d00a3ec","pub":"029f0f4e5bf957c569e9cbb814295acaa1bb8bbdd549844246f86656ab700cd35f","addr":"005b3c2696248d3e45b43c88e258b2dee69ae542"},{"mnemonic":"slim trap alone fetch people skate middle since yard frown during smile","master":"1db54486ad20e5fd82e7c5bcab652cd01632014d66ccf0f7d6c39bc0a337fd77","seed":"30c3948e518b8ca06625fd7a1318984997ff587aa865207b54d5c9c469386e66e06656fe020665501a1f0a07636a250ddf5c127d5bd9ec497052b9e4ff2dc37d","priv":"0ce3645a3f7a1ae02535852fbfee9c59609eeb226a975ae24c94b7a13acb4d85","pub":"03b3f40812780bd4f8a596c0131ced87b9b981e4950f5217f44de7f777b4d29b35","addr":"7777090b6e5ae18d4a146a0c9c17a1a34cde74da"},{"mnemonic":"vague melody fine buffalo spread nation reduce spirit humor fruit spin diary","master":"7a5951d167793eb5d42307c68a3997fec7c34d56562dcab752c2559b97507e3c","seed":"aeb4a2ee7a25712bb76b26c654f5a79b4e92c25689799de2a11283a668aabc3d967d111e2bd98691dc2b72adb66188888999e0ce03ab2bf1b18e8d5d7f8c528d","priv":"d2dd273a93786422dc68e61e67dc0c48402af5964f85d5d785476bbcc68e99c5","pub":"0347a23bf9c802d6d826367a34219ff155a720b068bb0cc4f9c679d350f35a978a","addr":"88679cd2c43e562c21738ba5ffc8bff82575bc6d"},{"mnemonic":"ahead glad fame hip era kite pyramid endless sponsor tissue winner kite","master":"eafe3d2af7a63b644668dfd23b7f0868a1d6527b630860f79838fc520896e64e","seed":"91f06eed5517fa2367c272a18362085775cc9d8f13b2358ab32d0c626abd87aefb8ffdd1f37712d4c39fb625ff4aa56cfe06a3054e162bb62fb03755a19f970a","priv":"c8d580087355904e691ad5292775989853e59ce1d41d224f97d2d4b96f8514d1","pub":"0342c4be0ea075609aa181fb83ffa189ec9744c2a7c54f7d3fa2273ba01ae127e8","addr":"ebd946e96a89c8932ec788d66f3fe8c482622831"},{"mnemonic":"clump earth diary ostrich section column practice rapid hotel mom client adjust","master":"c6be4ea8b2810f07731b7258f2a2cf4a4bd0722e43081957bd74ccb5955bed55","seed":"04c2178510fe5f7afe60cf8718bc5240da95cc2a79d28fb2bce756a2fb0995f25c5921addae1932dcc4b62ee68b330d3abfe190f212f809816ee7c6d5b816095","priv":"d2ecfc9fffd24c8299647fc2f23a16a2e5b35ec6092563ae731d4737159ce03d","pub":"027c615b33b69f5599cb800a85073582bb2180f20bedd1bb3e8384bada13ab8f92","addr":"df913518315ba257805f9ea9f78c859428d101d9"},{"mnemonic":"teach salt dinner dinner escape cheap gate major bind armor dash gallery","master":"954d52c8574645c888e4eea6ca661fd1ca4764ca9e4243bc75610571a24ec5ac","seed":"7c3753fb498e2a48af89e5a39153382eb9591eb8a5bb2cc5b8bf690bb6d1365162bccfbc9d5e3688f48352f1f8915c376dfacc3c3cad9315fa60cb303161ac77","priv":"c3a850ce632a7ab061e378596cb4c66a6880d41f6b09b0868d8c7e96bde490fe","pub":"02a615c68dd2bb66d3cc9d7e46a0fa00510542471a19b1d48fe05355acf38903b6","addr":"db50721f5b6e0907490990182793ae796756bc7b"},{"mnemonic":"cement casino erupt clever gasp benefit response invest diet banner month hill","master":"97c777e73bebf40d39c4efe8fb74db3cedcf2e1ebd366520515ee2823a9c4d28","seed":"9344a709e0d53322b928e3128d1bc707216eb0e1ebb137f74fbde6cd066f88b86088793a5794df8cfbd6efb12c843399e8ccb958446fa558573b013d33236436","priv":"96464373c492c388bc4a2c421ed0f5017fb426ac95e55f0f322703b28012cf90","pub":"03e71c89b2629c032b6c9a35b46d572dd0db3dde47bd5919bed8457885553148b1","addr":"493af641ce946561f6cb3116a135fe6e6b1b0f13"},{"mnemonic":"supreme news pyramid sheriff attract volume zone innocent hat grunt gorilla mango","master":"2ba0cd68329e2b19d5c24c1d3fca44513151fc4a634e52756290967989883b52","seed":"efbf7e16af697b4dd62a876f62f08698e29531c05018bb6da7ce441340b0278353d5d08a88cb93c4fd9bdb14fb85ab857ea3e8ef13b49dabb20beae2f13af3fa","priv":"15b89635f69b28cee0aa00d0609b99f1a1dd3c83e3bd57032987146cd47cebb8","pub":"021ae715a05c4849519acbe8dbf7ff4031d18f57de1bcf9cb1a06d50e5ebdbf661","addr":"e3087b309db8490b23629cbe4d113c1ea5bb525e"},{"mnemonic":"way chat van put negative minute tail dress spawn crunch beef moment","master":"e0dff85e10808c3ac560957a46f5f86abbfc62251c8fd4c61cb5e089a0841e7a","seed":"eb14b38f90886c1a1d47ac42205b564a649a0cf6edbd88cda1c488ee700aae8b735d7e947efc5711e2cc02b2254690ff4a69d149d2e13affd6d9a3915ac9dceb","priv":"7fb5464c8bc2e1583105d34c6850fb332a578fea35dff9528422c8b7f3156242","pub":"02745f0fbe0b23688e9691f9c4a088bf1342100fe3b9f7e405df79bddd57f8e6e1","addr":"66d90273421fadbab93fb9b05ea6e572f8af7050"},{"mnemonic":"tattoo strong tool weapon blue cricket kick holiday medal club stage reason","master":"bc82d196675dbad1c24d78f4e400a8956297c6113d4004356963ca88617597b3","seed":"9cf6193178f1a6391105c79cc6d3d2e2a4fe97f44738f8f716fb5b17a3eacd4061f9bb7f0411b08d77dc10b16b9599e45e8ffa55ff7f8e5c9274bab71bfe120f","priv":"5925547ee089b4638743f0fabe33f925780772a65b87b8315acf7f523d044482","pub":"03bba775f325b1ad28ec0adf66c8e15f90d70368fa021be5db199a5188c0621eb4","addr":"1f63ff4af01b5b5f33e60b82646a39917b6a7305"},{"mnemonic":"rug diagram spend zebra occur neglect choose relax face when spell stem","master":"d7932ffb8f6b751543caed7158318a05f4686646e636b5725b9b0419fb66912b","seed":"b73957a412d9e4c677edef4cb3a376c7355f78bba347a61f44c85f76b2c8a3d8ac2bd6c3b70643bf19b8487dcdce3055aaf86921e29e5e6f89700e0e2bf00546","priv":"c827dfe5f1880d9c462985b915ff78a1ab615ff0840f3f125f618cef25e14a5c","pub":"031bfd2158660168795f38f980951921f6fa1d522ad460ac5f93dfc4a2eac91f7d","addr":"b866f1e9b5b7b00fbb6076915a0a2931cba6e029"},{"mnemonic":"faith bless there mountain stand cannon sick abstract fiscal theory begin easy","master":"9af948584294b3951e6c664423a12fdfe29a31d41546b911f3fcdf664399aa48","seed":"17b6451f4b9765fdc3ed280fb5a6141b8003c8c9d8c73afce3192e15012fd18431850ebcaa07fa08da2c648f0f97f45e33ebdea6c99f2f23a9e244a297596499","priv":"e759b35d18e8b60726e882a11ea830e30ddff6752a19bec6983fdecf655726b0","pub":"03151da31179657f296184543a1360bcb92983a12788b268db0c20d895e6c449b1","addr":"54ec389ea177e1a4fe2f98cd83f27cce218b9081"},{"mnemonic":"combine fine crowd enter check mule toward damage fortune bless topple source","master":"b831b7a1b85edf1945c892dd332419e7f3a5125229cdfe1c73cc0e964abb4b5c","seed":"d9456b80f01787607d9c3adecdd6fa299132d748ae4fab843b3802c798ea1bc0395f9a1dc29e1badccc742f3c4fa081bdd7b6f9ca8980009b75c892438020b9f","priv":"d9d037b9baf0796d8a151935c172b1f775c1875b3811bcfd6c3f477e74c5c54e","pub":"03c720f00c921ab13307d3790f742f0a40436fad9d999087c267107ce50f8fe2b9","addr":"4b5a1c53f8442c375e9af35937ff0471155c0bee"},{"mnemonic":"shift nut floor scorpion property sun either total follow glide special apology","master":"1a4520e80df55ad59d95fca8dfab5f09753f6e26f7f38f654f32e4fa6f3ff963","seed":"a9df829ec832270d1ef0b0b04542e351ab9145baf086231fbe9636120c18c8d545ed138c37c0612d296c915340f982b1d83bd408301de79c08e4580ce0ce8fdd","priv":"27b1742305c3921c0cc041dc79d6e3c83112828f3fd49e18f31cb0b422bc1c80","pub":"032b322eedf502edf5e6c24ad80ad4bde37e04de012b9bf9c8caa41151a9ccf74f","addr":"2ce4a3aeb7e0fc52c31121651346959d1cfcc1be"}] diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go new file mode 100644 index 000000000..7cf43eea6 --- /dev/null +++ b/crypto/keys/keybase.go @@ -0,0 +1,382 @@ +package keys + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/pkg/errors" + tcrypto "github.com/tendermint/tendermint/crypto" + dbm "github.com/tendermint/tendermint/libs/db" + + "github.com/cosmos/cosmos-sdk/crypto" + "github.com/cosmos/cosmos-sdk/crypto/keys/bip39" + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" +) + +var _ Keybase = dbKeybase{} + +// Language is a language to create the BIP 39 mnemonic in. +// Currently, only english is supported though. +// Find a list of all supported languages in the BIP 39 spec (word lists). +type Language int + +const ( + // English is the default language to create a mnemonic. + // It is the only supported language by this package. + English Language = iota + 1 + // Japanese is currently not supported. + Japanese + // Korean is currently not supported. + Korean + // Spanish is currently not supported. + Spanish + // ChineseSimplified is currently not supported. + ChineseSimplified + // ChineseTraditional is currently not supported. + ChineseTraditional + // French is currently not supported. + French + // Italian is currently not supported. + Italian +) + +var ( + // ErrUnsupportedSigningAlgo is raised when the caller tries to use a different signing scheme than secp256k1. + ErrUnsupportedSigningAlgo = errors.New("unsupported signing algo: only secp256k1 is supported") + // ErrUnsupportedLanguage is raised when the caller tries to use a different language than english for creating + // a mnemonic sentence. + ErrUnsupportedLanguage = errors.New("unsupported language: only english is supported") +) + +// dbKeybase combines encryption and storage implementation to provide +// a full-featured key manager +type dbKeybase struct { + db dbm.DB +} + +// New creates a new keybase instance using the passed DB for reading and writing keys. +func New(db dbm.DB) Keybase { + return dbKeybase{ + db: db, + } +} + +// CreateMnemonic generates a new key and persists it to storage, encrypted +// using the provided password. +// It returns the generated mnemonic and the key Info. +// It returns an error if it fails to +// generate a key for the given algo type, or if another key is +// already stored under the same name. +func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, mnemonic string, err error) { + if language != English { + return nil, "", ErrUnsupportedLanguage + } + if algo != Secp256k1 { + err = ErrUnsupportedSigningAlgo + return + } + + // default number of words (24): + mnemonicS, err := bip39.NewMnemonic(bip39.FreshKey) + if err != nil { + return + } + mnemonic = strings.Join(mnemonicS, " ") + seed := bip39.MnemonicToSeed(mnemonic) + info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath) + return +} + +// TEMPORARY METHOD UNTIL WE FIGURE OUT USER FACING HD DERIVATION API +func (kb dbKeybase) CreateKey(name, mnemonic, passwd string) (info Info, err error) { + words := strings.Split(mnemonic, " ") + if len(words) != 12 && len(words) != 24 { + err = fmt.Errorf("recovering only works with 12 word (fundraiser) or 24 word mnemonics, got: %v words", len(words)) + return + } + seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic) + if err != nil { + return + } + info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath) + return +} + +// CreateFundraiserKey converts a mnemonic to a private key and persists it, +// encrypted with the given password. +// TODO(ismail) +func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error) { + words := strings.Split(mnemonic, " ") + if len(words) != 12 { + err = fmt.Errorf("recovering only works with 12 word (fundraiser), got: %v words", len(words)) + return + } + seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic) + if err != nil { + return + } + info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath) + return +} + +func (kb dbKeybase) Derive(name, mnemonic, passwd string, params hd.BIP44Params) (info Info, err error) { + seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic) + if err != nil { + return + } + info, err = kb.persistDerivedKey(seed, passwd, name, params.String()) + + return +} + +// CreateLedger creates a new locally-stored reference to a Ledger keypair +// It returns the created key info and an error if the Ledger could not be queried +func (kb dbKeybase) CreateLedger(name string, path crypto.DerivationPath, algo SigningAlgo) (Info, error) { + if algo != Secp256k1 { + return nil, ErrUnsupportedSigningAlgo + } + priv, err := crypto.NewPrivKeyLedgerSecp256k1(path) + if err != nil { + return nil, err + } + pub := priv.PubKey() + return kb.writeLedgerKey(pub, path, name), nil +} + +// CreateOffline creates a new reference to an offline keypair +// It returns the created key info +func (kb dbKeybase) CreateOffline(name string, pub tcrypto.PubKey) (Info, error) { + return kb.writeOfflineKey(pub, name), nil +} + +func (kb *dbKeybase) persistDerivedKey(seed []byte, passwd, name, fullHdPath string) (info Info, err error) { + // create master key and derive first key: + masterPriv, ch := hd.ComputeMastersFromSeed(seed) + derivedPriv, err := hd.DerivePrivateKeyForPath(masterPriv, ch, fullHdPath) + if err != nil { + return + } + + // if we have a password, use it to encrypt the private key and store it + // else store the public key only + if passwd != "" { + info = kb.writeLocalKey(tcrypto.PrivKeySecp256k1(derivedPriv), name, passwd) + } else { + pubk := tcrypto.PrivKeySecp256k1(derivedPriv).PubKey() + info = kb.writeOfflineKey(pubk, name) + } + return +} + +// List returns the keys from storage in alphabetical order. +func (kb dbKeybase) List() ([]Info, error) { + var res []Info + iter := kb.db.Iterator(nil, nil) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + info, err := readInfo(iter.Value()) + if err != nil { + return nil, err + } + res = append(res, info) + } + return res, nil +} + +// Get returns the public information about one key. +func (kb dbKeybase) Get(name string) (Info, error) { + bs := kb.db.Get(infoKey(name)) + if len(bs) == 0 { + return nil, fmt.Errorf("Key %s not found", name) + } + return readInfo(bs) +} + +// Sign signs the msg with the named key. +// It returns an error if the key doesn't exist or the decryption fails. +func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig tcrypto.Signature, pub tcrypto.PubKey, err error) { + info, err := kb.Get(name) + if err != nil { + return + } + var priv tcrypto.PrivKey + switch info.(type) { + case localInfo: + linfo := info.(localInfo) + if linfo.PrivKeyArmor == "" { + err = fmt.Errorf("private key not available") + return + } + priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) + if err != nil { + return nil, nil, err + } + case ledgerInfo: + linfo := info.(ledgerInfo) + priv, err = crypto.NewPrivKeyLedgerSecp256k1(linfo.Path) + if err != nil { + return + } + case offlineInfo: + linfo := info.(offlineInfo) + fmt.Printf("Bytes to sign:\n%s", msg) + buf := bufio.NewReader(os.Stdin) + fmt.Printf("\nEnter Amino-encoded signature:\n") + // Will block until user inputs the signature + signed, err := buf.ReadString('\n') + if err != nil { + return nil, nil, err + } + cdc.MustUnmarshalBinary([]byte(signed), sig) + return sig, linfo.GetPubKey(), nil + } + sig, err = priv.Sign(msg) + if err != nil { + return nil, nil, err + } + pub = priv.PubKey() + return sig, pub, nil +} + +func (kb dbKeybase) Export(name string) (armor string, err error) { + bz := kb.db.Get(infoKey(name)) + if bz == nil { + return "", fmt.Errorf("no key to export with name %s", name) + } + return armorInfoBytes(bz), nil +} + +// ExportPubKey returns public keys in ASCII armored format. +// Retrieve a Info object by its name and return the public key in +// a portable format. +func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) { + bz := kb.db.Get(infoKey(name)) + if bz == nil { + return "", fmt.Errorf("no key to export with name %s", name) + } + info, err := readInfo(bz) + if err != nil { + return + } + return armorPubKeyBytes(info.GetPubKey().Bytes()), nil +} + +func (kb dbKeybase) Import(name string, armor string) (err error) { + bz := kb.db.Get(infoKey(name)) + if len(bz) > 0 { + return errors.New("Cannot overwrite data for name " + name) + } + infoBytes, err := unarmorInfoBytes(armor) + if err != nil { + return + } + kb.db.Set(infoKey(name), infoBytes) + return nil +} + +// ImportPubKey imports ASCII-armored public keys. +// Store a new Info object holding a public key only, i.e. it will +// not be possible to sign with it as it lacks the secret key. +func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) { + bz := kb.db.Get(infoKey(name)) + if len(bz) > 0 { + return errors.New("Cannot overwrite data for name " + name) + } + pubBytes, err := unarmorPubKeyBytes(armor) + if err != nil { + return + } + pubKey, err := tcrypto.PubKeyFromBytes(pubBytes) + if err != nil { + return + } + kb.writeOfflineKey(pubKey, name) + return +} + +// Delete removes key forever, but we must present the +// proper passphrase before deleting it (for security). +// A passphrase of 'yes' is used to delete stored +// references to offline and Ledger / HW wallet keys +func (kb dbKeybase) Delete(name, passphrase string) error { + // verify we have the proper password before deleting + info, err := kb.Get(name) + if err != nil { + return err + } + switch info.(type) { + case localInfo: + linfo := info.(localInfo) + _, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) + if err != nil { + return err + } + kb.db.DeleteSync(infoKey(name)) + return nil + case ledgerInfo: + case offlineInfo: + if passphrase != "yes" { + return fmt.Errorf("enter 'yes' exactly to delete the key - this cannot be undone") + } + kb.db.DeleteSync(infoKey(name)) + return nil + } + return nil +} + +// Update changes the passphrase with which an already stored key is +// encrypted. +// +// oldpass must be the current passphrase used for encryption, +// newpass will be the only valid passphrase from this time forward. +func (kb dbKeybase) Update(name, oldpass, newpass string) error { + info, err := kb.Get(name) + if err != nil { + return err + } + switch info.(type) { + case localInfo: + linfo := info.(localInfo) + key, err := unarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass) + if err != nil { + return err + } + kb.writeLocalKey(key, name, newpass) + return nil + default: + return fmt.Errorf("locally stored key required") + } +} + +func (kb dbKeybase) writeLocalKey(priv tcrypto.PrivKey, name, passphrase string) Info { + // encrypt private key using passphrase + privArmor := encryptArmorPrivKey(priv, passphrase) + // make Info + pub := priv.PubKey() + info := newLocalInfo(name, pub, privArmor) + kb.writeInfo(info, name) + return info +} + +func (kb dbKeybase) writeLedgerKey(pub tcrypto.PubKey, path crypto.DerivationPath, name string) Info { + info := newLedgerInfo(name, pub, path) + kb.writeInfo(info, name) + return info +} + +func (kb dbKeybase) writeOfflineKey(pub tcrypto.PubKey, name string) Info { + info := newOfflineInfo(name, pub) + kb.writeInfo(info, name) + return info +} + +func (kb dbKeybase) writeInfo(info Info, name string) { + // write the info by key + kb.db.SetSync(infoKey(name), writeInfo(info)) +} + +func infoKey(name string) []byte { + return []byte(fmt.Sprintf("%s.info", name)) +} diff --git a/crypto/keys/keybase_test.go b/crypto/keys/keybase_test.go new file mode 100644 index 000000000..c91084986 --- /dev/null +++ b/crypto/keys/keybase_test.go @@ -0,0 +1,382 @@ +package keys + +import ( + "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" + + dbm "github.com/tendermint/tendermint/libs/db" +) + +// TestKeyManagement makes sure we can manipulate these keys well +func TestKeyManagement(t *testing.T) { + // make the storage with reasonable defaults + cstore := New( + dbm.NewMemDB(), + ) + + algo := Secp256k1 + n1, n2, n3 := "personal", "business", "other" + p1, p2 := "1234", "really-secure!@#$" + + // Check empty state + l, err := cstore.List() + require.Nil(t, err) + assert.Empty(t, l) + + _, _, err = cstore.CreateMnemonic(n1, English, p1, Ed25519) + require.Error(t, err, "ed25519 keys are currently not supported by keybase") + + // create some keys + _, err = cstore.Get(n1) + require.Error(t, err) + i, _, err := cstore.CreateMnemonic(n1, English, p1, algo) + + require.NoError(t, err) + require.Equal(t, n1, i.GetName()) + _, _, err = cstore.CreateMnemonic(n2, English, p2, algo) + require.NoError(t, err) + + // we can get these keys + i2, err := cstore.Get(n2) + require.NoError(t, err) + _, err = cstore.Get(n3) + require.NotNil(t, err) + + // list shows them in order + keyS, err := cstore.List() + require.NoError(t, err) + require.Equal(t, 2, len(keyS)) + // note these are in alphabetical order + require.Equal(t, n2, keyS[0].GetName()) + require.Equal(t, n1, keyS[1].GetName()) + require.Equal(t, i2.GetPubKey(), keyS[0].GetPubKey()) + + // deleting a key removes it + err = cstore.Delete("bad name", "foo") + require.NotNil(t, err) + err = cstore.Delete(n1, p1) + require.NoError(t, err) + keyS, err = cstore.List() + require.NoError(t, err) + require.Equal(t, 1, len(keyS)) + _, err = cstore.Get(n1) + require.Error(t, err) + + // create an offline key + o1 := "offline" + priv1 := crypto.GenPrivKeyEd25519() + pub1 := priv1.PubKey() + i, err = cstore.CreateOffline(o1, pub1) + require.Nil(t, err) + require.Equal(t, pub1, i.GetPubKey()) + require.Equal(t, o1, i.GetName()) + keyS, err = cstore.List() + require.NoError(t, err) + require.Equal(t, 2, len(keyS)) + + // delete the offline key + err = cstore.Delete(o1, "no") + require.NotNil(t, err) + err = cstore.Delete(o1, "yes") + require.NoError(t, err) + keyS, err = cstore.List() + require.NoError(t, err) + require.Equal(t, 1, len(keyS)) +} + +// TestSignVerify does some detailed checks on how we sign and validate +// signatures +func TestSignVerify(t *testing.T) { + cstore := New( + dbm.NewMemDB(), + ) + algo := Secp256k1 + + n1, n2, n3 := "some dude", "a dudette", "dude-ish" + p1, p2, p3 := "1234", "foobar", "foobar" + + // create two users and get their info + i1, _, err := cstore.CreateMnemonic(n1, English, p1, algo) + require.Nil(t, err) + + i2, _, err := cstore.CreateMnemonic(n2, English, p2, algo) + require.Nil(t, err) + + // Import a public key + armor, err := cstore.ExportPubKey(n2) + require.Nil(t, err) + cstore.ImportPubKey(n3, armor) + i3, err := cstore.Get(n3) + require.NoError(t, err) + require.Equal(t, i3.GetName(), n3) + + // let's try to sign some messages + d1 := []byte("my first message") + d2 := []byte("some other important info!") + d3 := []byte("feels like I forgot something...") + + // try signing both data with both .. + s11, pub1, err := cstore.Sign(n1, p1, d1) + require.Nil(t, err) + require.Equal(t, i1.GetPubKey(), pub1) + + s12, pub1, err := cstore.Sign(n1, p1, d2) + require.Nil(t, err) + require.Equal(t, i1.GetPubKey(), pub1) + + s21, pub2, err := cstore.Sign(n2, p2, d1) + require.Nil(t, err) + require.Equal(t, i2.GetPubKey(), pub2) + + s22, pub2, err := cstore.Sign(n2, p2, d2) + require.Nil(t, err) + require.Equal(t, i2.GetPubKey(), pub2) + + // let's try to validate and make sure it only works when everything is proper + cases := []struct { + key crypto.PubKey + data []byte + sig crypto.Signature + valid bool + }{ + // proper matches + {i1.GetPubKey(), d1, s11, true}, + // change data, pubkey, or signature leads to fail + {i1.GetPubKey(), d2, s11, false}, + {i2.GetPubKey(), d1, s11, false}, + {i1.GetPubKey(), d1, s21, false}, + // make sure other successes + {i1.GetPubKey(), d2, s12, true}, + {i2.GetPubKey(), d1, s21, true}, + {i2.GetPubKey(), d2, s22, true}, + } + + for i, tc := range cases { + valid := tc.key.VerifyBytes(tc.data, tc.sig) + require.Equal(t, tc.valid, valid, "%d", i) + } + + // Now try to sign data with a secret-less key + _, _, err = cstore.Sign(n3, p3, d3) + require.NotNil(t, err) +} + +func assertPassword(t *testing.T, cstore Keybase, name, pass, badpass string) { + err := cstore.Update(name, badpass, pass) + require.NotNil(t, err) + err = cstore.Update(name, pass, pass) + require.Nil(t, err, "%+v", err) +} + +// TestExportImport tests exporting and importing +func TestExportImport(t *testing.T) { + + // make the storage with reasonable defaults + db := dbm.NewMemDB() + cstore := New( + db, + ) + + info, _, err := cstore.CreateMnemonic("john", English, "secretcpw", Secp256k1) + require.NoError(t, err) + require.Equal(t, info.GetName(), "john") + + john, err := cstore.Get("john") + require.NoError(t, err) + require.Equal(t, info.GetName(), "john") + johnAddr := info.GetPubKey().Address() + + armor, err := cstore.Export("john") + require.NoError(t, err) + + err = cstore.Import("john2", armor) + require.NoError(t, err) + + john2, err := cstore.Get("john2") + require.NoError(t, err) + + require.Equal(t, john.GetPubKey().Address(), johnAddr) + require.Equal(t, john.GetName(), "john") + require.Equal(t, john, john2) +} + +// +func TestExportImportPubKey(t *testing.T) { + // make the storage with reasonable defaults + db := dbm.NewMemDB() + cstore := New( + db, + ) + + // CreateMnemonic a private-public key pair and ensure consistency + notPasswd := "n9y25ah7" + info, _, err := cstore.CreateMnemonic("john", English, notPasswd, Secp256k1) + require.Nil(t, err) + require.NotEqual(t, info, "") + require.Equal(t, info.GetName(), "john") + addr := info.GetPubKey().Address() + john, err := cstore.Get("john") + require.NoError(t, err) + require.Equal(t, john.GetName(), "john") + require.Equal(t, john.GetPubKey().Address(), addr) + + // Export the public key only + armor, err := cstore.ExportPubKey("john") + require.NoError(t, err) + // Import it under a different name + err = cstore.ImportPubKey("john-pubkey-only", armor) + require.NoError(t, err) + // Ensure consistency + john2, err := cstore.Get("john-pubkey-only") + require.NoError(t, err) + // Compare the public keys + require.True(t, john.GetPubKey().Equals(john2.GetPubKey())) + // Ensure the original key hasn't changed + john, err = cstore.Get("john") + require.NoError(t, err) + require.Equal(t, john.GetPubKey().Address(), addr) + require.Equal(t, john.GetName(), "john") + + // Ensure keys cannot be overwritten + err = cstore.ImportPubKey("john-pubkey-only", armor) + require.NotNil(t, err) +} + +// TestAdvancedKeyManagement verifies update, import, export functionality +func TestAdvancedKeyManagement(t *testing.T) { + + // make the storage with reasonable defaults + cstore := New( + dbm.NewMemDB(), + ) + + algo := Secp256k1 + n1, n2 := "old-name", "new name" + p1, p2 := "1234", "foobar" + + // make sure key works with initial password + _, _, err := cstore.CreateMnemonic(n1, English, p1, algo) + require.Nil(t, err, "%+v", err) + assertPassword(t, cstore, n1, p1, p2) + + // update password requires the existing password + err = cstore.Update(n1, "jkkgkg", p2) + require.NotNil(t, err) + assertPassword(t, cstore, n1, p1, p2) + + // then it changes the password when correct + err = cstore.Update(n1, p1, p2) + require.NoError(t, err) + // p2 is now the proper one! + assertPassword(t, cstore, n1, p2, p1) + + // exporting requires the proper name and passphrase + _, err = cstore.Export(n1 + ".notreal") + require.NotNil(t, err) + _, err = cstore.Export(" " + n1) + require.NotNil(t, err) + _, err = cstore.Export(n1 + " ") + require.NotNil(t, err) + _, err = cstore.Export("") + require.NotNil(t, err) + exported, err := cstore.Export(n1) + require.Nil(t, err, "%+v", err) + + // import succeeds + err = cstore.Import(n2, exported) + require.NoError(t, err) + + // second import fails + err = cstore.Import(n2, exported) + require.NotNil(t, err) +} + +// TestSeedPhrase verifies restoring from a seed phrase +func TestSeedPhrase(t *testing.T) { + + // make the storage with reasonable defaults + cstore := New( + dbm.NewMemDB(), + ) + + algo := Secp256k1 + n1, n2 := "lost-key", "found-again" + p1, p2 := "1234", "foobar" + + // make sure key works with initial password + info, mnemonic, err := cstore.CreateMnemonic(n1, English, p1, algo) + require.Nil(t, err, "%+v", err) + require.Equal(t, n1, info.GetName()) + assert.NotEmpty(t, mnemonic) + + // now, let us delete this key + err = cstore.Delete(n1, p1) + require.Nil(t, err, "%+v", err) + _, err = cstore.Get(n1) + require.NotNil(t, err) + + // let us re-create it from the mnemonic-phrase + params := *hd.NewFundraiserParams(0, 0) + newInfo, err := cstore.Derive(n2, mnemonic, p2, params) + require.NoError(t, err) + require.Equal(t, n2, newInfo.GetName()) + require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address()) + require.Equal(t, info.GetPubKey(), newInfo.GetPubKey()) +} + +func ExampleNew() { + // Select the encryption and storage for your cryptostore + cstore := New( + dbm.NewMemDB(), + ) + + sec := Secp256k1 + + // Add keys and see they return in alphabetical order + bob, _, err := cstore.CreateMnemonic("Bob", English, "friend", sec) + if err != nil { + // this should never happen + fmt.Println(err) + } else { + // return info here just like in List + fmt.Println(bob.GetName()) + } + cstore.CreateMnemonic("Alice", English, "secret", sec) + cstore.CreateMnemonic("Carl", English, "mitm", sec) + info, _ := cstore.List() + for _, i := range info { + fmt.Println(i.GetName()) + } + + // We need to use passphrase to generate a signature + tx := []byte("deadbeef") + sig, pub, err := cstore.Sign("Bob", "friend", tx) + if err != nil { + fmt.Println("don't accept real passphrase") + } + + // and we can validate the signature with publicly available info + binfo, _ := cstore.Get("Bob") + if !binfo.GetPubKey().Equals(bob.GetPubKey()) { + fmt.Println("Get and Create return different keys") + } + + if pub.Equals(binfo.GetPubKey()) { + fmt.Println("signed by Bob") + } + if !pub.VerifyBytes(tx, sig) { + fmt.Println("invalid signature") + } + + // Output: + // Bob + // Alice + // Bob + // Carl + // signed by Bob +} diff --git a/crypto/keys/keys.go b/crypto/keys/keys.go new file mode 100644 index 000000000..58a58a95b --- /dev/null +++ b/crypto/keys/keys.go @@ -0,0 +1,12 @@ +package keys + +// SigningAlgo defines an algorithm to derive key-pairs which can be used for cryptographic signing. +type SigningAlgo string + +const ( + // Secp256k1 uses the Bitcoin secp256k1 ECDSA parameters. + Secp256k1 = SigningAlgo("secp256k1") + // Ed25519 represents the Ed25519 signature system. + // It is currently not supported for end-user keys (wallets/ledgers). + Ed25519 = SigningAlgo("ed25519") +) diff --git a/crypto/keys/keys.toml b/crypto/keys/keys.toml new file mode 100644 index 000000000..f9eb95e1c --- /dev/null +++ b/crypto/keys/keys.toml @@ -0,0 +1,2 @@ +output = "text" +keydir = ".mykeys" diff --git a/crypto/keys/mintkey.go b/crypto/keys/mintkey.go new file mode 100644 index 000000000..cfba18e09 --- /dev/null +++ b/crypto/keys/mintkey.go @@ -0,0 +1,130 @@ +package keys + +import ( + "encoding/hex" + "fmt" + + cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/crypto/keys/bcrypt" + "github.com/tendermint/tendermint/crypto" +) + +const ( + blockTypePrivKey = "TENDERMINT PRIVATE KEY" + blockTypeKeyInfo = "TENDERMINT KEY INFO" + blockTypePubKey = "TENDERMINT PUBLIC KEY" +) + +// Make bcrypt security parameter var, so it can be changed within the lcd test +// Making the bcrypt security parameter a var shouldn't be a security issue: +// One can't verify an invalid key by maliciously changing the bcrypt +// parameter during a runtime vulnerability. The main security +// threat this then exposes would be something that changes this during +// runtime before the user creates their key. This vulnerability must +// succeed to update this to that same value before every subsequent call +// to gaiacli keys in future startups / or the attacker must get access +// to the filesystem. However, with a similar threat model (changing +// variables in runtime), one can cause the user to sign a different tx +// than what they see, which is a significantly cheaper attack then breaking +// a bcrypt hash. (Recall that the nonce still exists to break rainbow tables) +// TODO: Consider increasing default +var BcryptSecurityParameter = 12 + +func armorInfoBytes(bz []byte) string { + return armorBytes(bz, blockTypeKeyInfo) +} + +func armorPubKeyBytes(bz []byte) string { + return armorBytes(bz, blockTypePubKey) +} + +func armorBytes(bz []byte, blockType string) string { + header := map[string]string{ + "type": "Info", + "version": "0.0.0", + } + return crypto.EncodeArmor(blockType, header, bz) +} + +func unarmorInfoBytes(armorStr string) (bz []byte, err error) { + return unarmorBytes(armorStr, blockTypeKeyInfo) +} + +func unarmorPubKeyBytes(armorStr string) (bz []byte, err error) { + return unarmorBytes(armorStr, blockTypePubKey) +} + +func unarmorBytes(armorStr, blockType string) (bz []byte, err error) { + bType, header, bz, err := crypto.DecodeArmor(armorStr) + if err != nil { + return + } + if bType != blockType { + err = fmt.Errorf("Unrecognized armor type %q, expected: %q", bType, blockType) + return + } + if header["version"] != "0.0.0" { + err = fmt.Errorf("Unrecognized version: %v", header["version"]) + return + } + return +} + +func encryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string { + saltBytes, encBytes := encryptPrivKey(privKey, passphrase) + header := map[string]string{ + "kdf": "bcrypt", + "salt": fmt.Sprintf("%X", saltBytes), + } + armorStr := crypto.EncodeArmor(blockTypePrivKey, header, encBytes) + return armorStr +} + +func unarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) { + var privKey crypto.PrivKey + blockType, header, encBytes, err := crypto.DecodeArmor(armorStr) + if err != nil { + return privKey, err + } + if blockType != blockTypePrivKey { + return privKey, fmt.Errorf("Unrecognized armor type: %v", blockType) + } + if header["kdf"] != "bcrypt" { + return privKey, fmt.Errorf("Unrecognized KDF type: %v", header["KDF"]) + } + if header["salt"] == "" { + return privKey, fmt.Errorf("Missing salt bytes") + } + saltBytes, err := hex.DecodeString(header["salt"]) + if err != nil { + return privKey, fmt.Errorf("Error decoding salt: %v", err.Error()) + } + privKey, err = decryptPrivKey(saltBytes, encBytes, passphrase) + return privKey, err +} + +func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) { + saltBytes = crypto.CRandBytes(16) + key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) + if err != nil { + cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error()) + } + key = crypto.Sha256(key) // Get 32 bytes + privKeyBytes := privKey.Bytes() + return saltBytes, crypto.EncryptSymmetric(privKeyBytes, key) +} + +func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) { + key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) + if err != nil { + cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error()) + } + key = crypto.Sha256(key) // Get 32 bytes + privKeyBytes, err := crypto.DecryptSymmetric(encBytes, key) + if err != nil { + return privKey, err + } + privKey, err = crypto.PrivKeyFromBytes(privKeyBytes) + return privKey, err +} diff --git a/crypto/keys/types.go b/crypto/keys/types.go new file mode 100644 index 000000000..0e77725c3 --- /dev/null +++ b/crypto/keys/types.go @@ -0,0 +1,146 @@ +package keys + +import ( + ccrypto "github.com/cosmos/cosmos-sdk/crypto" + "github.com/tendermint/tendermint/crypto" + + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" +) + +// Keybase exposes operations on a generic keystore +type Keybase interface { + + // CRUD on the keystore + List() ([]Info, error) + Get(name string) (Info, error) + Delete(name, passphrase string) error + + // Sign some bytes, looking up the private key to use + Sign(name, passphrase string, msg []byte) (crypto.Signature, crypto.PubKey, error) + + // CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic + // key from that. + CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) + // CreateKey takes a mnemonic and derives, a password. This method is temporary + CreateKey(name, mnemonic, passwd string) (info Info, err error) + // CreateFundraiserKey takes a mnemonic and derives, a password + CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error) + // Derive derives a key from the passed mnemonic using a BIP44 path. + Derive(name, mnemonic, passwd string, params hd.BIP44Params) (Info, error) + // Create, store, and return a new Ledger key reference + CreateLedger(name string, path ccrypto.DerivationPath, algo SigningAlgo) (info Info, err error) + + // Create, store, and return a new offline key reference + CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) + + // The following operations will *only* work on locally-stored keys + Update(name, oldpass, newpass string) error + Import(name string, armor string) (err error) + ImportPubKey(name string, armor string) (err error) + Export(name string) (armor string, err error) + ExportPubKey(name string) (armor string, err error) +} + +// Info is the publicly exposed information about a keypair +type Info interface { + // Human-readable type for key listing + GetType() string + // Name of the key + GetName() string + // Public key + GetPubKey() crypto.PubKey +} + +var _ Info = &localInfo{} +var _ Info = &ledgerInfo{} +var _ Info = &offlineInfo{} + +// localInfo is the public information about a locally stored key +type localInfo struct { + Name string `json:"name"` + PubKey crypto.PubKey `json:"pubkey"` + PrivKeyArmor string `json:"privkey.armor"` +} + +func newLocalInfo(name string, pub crypto.PubKey, privArmor string) Info { + return &localInfo{ + Name: name, + PubKey: pub, + PrivKeyArmor: privArmor, + } +} + +func (i localInfo) GetType() string { + return "local" +} + +func (i localInfo) GetName() string { + return i.Name +} + +func (i localInfo) GetPubKey() crypto.PubKey { + return i.PubKey +} + +// ledgerInfo is the public information about a Ledger key +type ledgerInfo struct { + Name string `json:"name"` + PubKey crypto.PubKey `json:"pubkey"` + Path ccrypto.DerivationPath `json:"path"` +} + +func newLedgerInfo(name string, pub crypto.PubKey, path ccrypto.DerivationPath) Info { + return &ledgerInfo{ + Name: name, + PubKey: pub, + Path: path, + } +} + +func (i ledgerInfo) GetType() string { + return "ledger" +} + +func (i ledgerInfo) GetName() string { + return i.Name +} + +func (i ledgerInfo) GetPubKey() crypto.PubKey { + return i.PubKey +} + +// offlineInfo is the public information about an offline key +type offlineInfo struct { + Name string `json:"name"` + PubKey crypto.PubKey `json:"pubkey"` +} + +func newOfflineInfo(name string, pub crypto.PubKey) Info { + return &offlineInfo{ + Name: name, + PubKey: pub, + } +} + +func (i offlineInfo) GetType() string { + return "offline" +} + +func (i offlineInfo) GetName() string { + return i.Name +} + +func (i offlineInfo) GetPubKey() crypto.PubKey { + return i.PubKey +} + +// encoding info +func writeInfo(i Info) []byte { + return cdc.MustMarshalBinary(i) +} + +// decoding info +func readInfo(bz []byte) (info Info, err error) { + err = cdc.UnmarshalBinary(bz, &info) + return +} diff --git a/crypto/keys/wire.go b/crypto/keys/wire.go new file mode 100644 index 000000000..18e8e0f17 --- /dev/null +++ b/crypto/keys/wire.go @@ -0,0 +1,19 @@ +package keys + +import ( + ccrypto "github.com/cosmos/cosmos-sdk/crypto" + amino "github.com/tendermint/go-amino" + tcrypto "github.com/tendermint/tendermint/crypto" +) + +var cdc = amino.NewCodec() + +func init() { + tcrypto.RegisterAmino(cdc) + cdc.RegisterInterface((*Info)(nil), nil) + cdc.RegisterConcrete(ccrypto.PrivKeyLedgerSecp256k1{}, + "tendermint/PrivKeyLedgerSecp256k1", nil) + cdc.RegisterConcrete(localInfo{}, "crypto/keys/localInfo", nil) + cdc.RegisterConcrete(ledgerInfo{}, "crypto/keys/ledgerInfo", nil) + cdc.RegisterConcrete(offlineInfo{}, "crypto/keys/offlineInfo", nil) +} diff --git a/crypto/ledger_common.go b/crypto/ledger_common.go new file mode 100644 index 000000000..39f15464a --- /dev/null +++ b/crypto/ledger_common.go @@ -0,0 +1,19 @@ +package crypto + +import ( + ledger "github.com/zondax/ledger-goclient" +) + +var device *ledger.Ledger + +// Ledger derivation path +type DerivationPath = []uint32 + +// getLedger gets a copy of the device, and caches it +func getLedger() (*ledger.Ledger, error) { + var err error + if device == nil { + device, err = ledger.FindLedger() + } + return device, err +} diff --git a/crypto/ledger_secp256k1.go b/crypto/ledger_secp256k1.go new file mode 100644 index 000000000..1ba36f69d --- /dev/null +++ b/crypto/ledger_secp256k1.go @@ -0,0 +1,126 @@ +package crypto + +import ( + "fmt" + + secp256k1 "github.com/btcsuite/btcd/btcec" + ledger "github.com/zondax/ledger-goclient" + + tcrypto "github.com/tendermint/tendermint/crypto" +) + +func pubkeyLedgerSecp256k1(device *ledger.Ledger, path DerivationPath) (pub tcrypto.PubKey, err error) { + key, err := device.GetPublicKeySECP256K1(path) + if err != nil { + return nil, fmt.Errorf("error fetching public key: %v", err) + } + var p tcrypto.PubKeySecp256k1 + // Reserialize in the 33-byte compressed format + cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256()) + copy(p[:], cmp.SerializeCompressed()) + pub = p + return +} + +func signLedgerSecp256k1(device *ledger.Ledger, path DerivationPath, msg []byte) (sig tcrypto.Signature, err error) { + bsig, err := device.SignSECP256K1(path, msg) + if err != nil { + return sig, err + } + sig = tcrypto.SignatureSecp256k1FromBytes(bsig) + return +} + +// PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano +// we cache the PubKey from the first call to use it later +type PrivKeyLedgerSecp256k1 struct { + // PubKey should be private, but we want to encode it via go-amino + // so we can view the address later, even without having the ledger + // attached + CachedPubKey tcrypto.PubKey + Path DerivationPath +} + +// NewPrivKeyLedgerSecp256k1 will generate a new key and store the +// public key for later use. +func NewPrivKeyLedgerSecp256k1(path DerivationPath) (tcrypto.PrivKey, error) { + var pk PrivKeyLedgerSecp256k1 + pk.Path = path + // cache the pubkey for later use + pubKey, err := pk.getPubKey() + if err != nil { + return nil, err + } + pk.CachedPubKey = pubKey + return &pk, err +} + +// ValidateKey allows us to verify the sanity of a key +// after loading it from disk +func (pk PrivKeyLedgerSecp256k1) ValidateKey() error { + // getPubKey will return an error if the ledger is not + pub, err := pk.getPubKey() + if err != nil { + return err + } + // verify this matches cached address + if !pub.Equals(pk.CachedPubKey) { + return fmt.Errorf("cached key does not match retrieved key") + } + return nil +} + +// AssertIsPrivKeyInner fulfils PrivKey Interface +func (pk *PrivKeyLedgerSecp256k1) AssertIsPrivKeyInner() {} + +// Bytes fulfils PrivKey Interface - but it stores the cached pubkey so we can verify +// the same key when we reconnect to a ledger +func (pk PrivKeyLedgerSecp256k1) Bytes() []byte { + return cdc.MustMarshalBinaryBare(pk) +} + +// Sign calls the ledger and stores the PubKey for future use +// +// Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, +// returning an error, so this should only trigger if the privkey is held +// in memory for a while before use. +func (pk PrivKeyLedgerSecp256k1) Sign(msg []byte) (tcrypto.Signature, error) { + dev, err := getLedger() + if err != nil { + return nil, err + } + sig, err := signLedgerSecp256k1(dev, pk.Path, msg) + if err != nil { + return nil, err + } + return sig, nil +} + +// PubKey returns the stored PubKey +func (pk PrivKeyLedgerSecp256k1) PubKey() tcrypto.PubKey { + return pk.CachedPubKey +} + +// getPubKey reads the pubkey the ledger itself +// since this involves IO, it may return an error, which is not exposed +// in the PubKey interface, so this function allows better error handling +func (pk PrivKeyLedgerSecp256k1) getPubKey() (key tcrypto.PubKey, err error) { + dev, err := getLedger() + if err != nil { + return key, fmt.Errorf("cannot connect to Ledger device - error: %v", err) + } + key, err = pubkeyLedgerSecp256k1(dev, pk.Path) + if err != nil { + return key, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err) + } + return key, err +} + +// Equals fulfils PrivKey Interface - makes sure both keys refer to the +// same +func (pk PrivKeyLedgerSecp256k1) Equals(other tcrypto.PrivKey) bool { + if ledger, ok := other.(*PrivKeyLedgerSecp256k1); ok { + return pk.CachedPubKey.Equals(ledger.CachedPubKey) + } + return false +} diff --git a/crypto/ledger_test.go b/crypto/ledger_test.go new file mode 100644 index 000000000..997dfbc3b --- /dev/null +++ b/crypto/ledger_test.go @@ -0,0 +1,64 @@ +package crypto + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" + + tcrypto "github.com/tendermint/tendermint/crypto" +) + +func TestRealLedgerSecp256k1(t *testing.T) { + + if os.Getenv("WITH_LEDGER") == "" { + t.Skip("Set WITH_LEDGER to run code on real ledger") + } + msg := []byte("kuhehfeohg") + + path := DerivationPath{44, 60, 0, 0, 0} + + priv, err := NewPrivKeyLedgerSecp256k1(path) + require.Nil(t, err, "%+v", err) + pub := priv.PubKey() + sig, err := priv.Sign(msg) + require.Nil(t, err) + + valid := pub.VerifyBytes(msg, sig) + require.True(t, valid) + + // now, let's serialize the key and make sure it still works + bs := priv.Bytes() + priv2, err := tcrypto.PrivKeyFromBytes(bs) + require.Nil(t, err, "%+v", err) + + // make sure we get the same pubkey when we load from disk + pub2 := priv2.PubKey() + require.Equal(t, pub, pub2) + + // signing with the loaded key should match the original pubkey + sig, err = priv2.Sign(msg) + require.Nil(t, err) + valid = pub.VerifyBytes(msg, sig) + require.True(t, valid) + + // make sure pubkeys serialize properly as well + bs = pub.Bytes() + bpub, err := tcrypto.PubKeyFromBytes(bs) + require.NoError(t, err) + require.Equal(t, pub, bpub) +} + +// TestRealLedgerErrorHandling calls. These tests assume +// the ledger is not plugged in.... +func TestRealLedgerErrorHandling(t *testing.T) { + if os.Getenv("WITH_LEDGER") != "" { + t.Skip("Skipping on WITH_LEDGER as it tests unplugged cases") + } + + // first, try to generate a key, must return an error + // (no panic) + path := DerivationPath{44, 60, 0, 0, 0} + _, err := NewPrivKeyLedgerSecp256k1(path) + require.Error(t, err) +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..fca518db4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,68 @@ +version: '3' + +services: + gaiadnode0: + container_name: gaiadnode0 + image: "tendermint/gaiadnode" + ports: + - "26656-26657:26656-26657" + environment: + - ID=0 + - LOG=$${LOG:-gaiad.log} + volumes: + - ./build:/gaiad:Z + networks: + localnet: + ipv4_address: 192.168.10.2 + + gaiadnode1: + container_name: gaiadnode1 + image: "tendermint/gaiadnode" + ports: + - "26659-26660:26656-26657" + environment: + - ID=1 + - LOG=$${LOG:-gaiad.log} + volumes: + - ./build:/gaiad:Z + networks: + localnet: + ipv4_address: 192.168.10.3 + + gaiadnode2: + container_name: gaiadnode2 + image: "tendermint/gaiadnode" + environment: + - ID=2 + - LOG=$${LOG:-gaiad.log} + ports: + - "26661-26662:26656-26657" + volumes: + - ./build:/gaiad:Z + networks: + localnet: + ipv4_address: 192.168.10.4 + + gaiadnode3: + container_name: gaiadnode3 + image: "tendermint/gaiadnode" + environment: + - ID=3 + - LOG=$${LOG:-gaiad.log} + ports: + - "26663-26664:26656-26657" + volumes: + - ./build:/gaiad:Z + networks: + localnet: + ipv4_address: 192.168.10.5 + +networks: + localnet: + driver: bridge + ipam: + driver: default + config: + - + subnet: 192.168.10.0/16 + diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index f4bccf3bd..000000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = python -msphinx -SPHINXPROJ = Cosmos-SDK -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..77dfd1f3f --- /dev/null +++ b/docs/README.md @@ -0,0 +1,66 @@ +# Cosmos SDK Documentation + +NOTE: This documentation is a work-in-progress! + +- [Overview](overview) + - [Overview](overview/overview.md) - An overview of the Cosmos-SDK + - [The Object-Capability Model](overview/capabilities.md) - Security by + least-privilege + - [Application Architecture](overview/apps.md) - Layers in the application architecture +- [Install](install.md) - Install the library and example applications +- [Core](core) + - [Introduction](core/intro.md) - Intro to the tutorial + - [App1 - The Basics](core/app1.md) + - [Messages](core/app1.md#messages) - Messages contain the content of a transaction + - [Stores](core/app1.md#kvstore) - KVStore is a Merkle Key-Value store. + - [Handlers](core/app1.md#handlers) - Handlers are the workhorse of the app! + - [Tx](core/app1.md#tx) - Transactions are the ultimate input to the + application + - [BaseApp](core/app1.md#baseapp) - BaseApp is the base layer of the application + - [App2 - Transactions](core/app2.md) + - [Amino](core/app2.md#amino) - Amino is the primary serialization library used in the SDK + - [Ante Handler](core/app2.md#antehandler) - The AnteHandler + authenticates transactions + - [App3 - Modules: Auth and Bank](core/app3.md) + - [auth.Account](core/app3.md#accounts) - Accounts are the prototypical object kept in the store + - [auth.AccountMapper](core/app3.md#account-mapper) - AccountMapper gets and sets Account on a KVStore + - [auth.StdTx](core/app3.md#stdtx) - `StdTx` is the default implementation of `Tx` + - [auth.StdSignBytes](core/app3.md#signing) - `StdTx` must be signed with certain + information + - [auth.AnteHandler](core/app3.md#antehandler) - The `AnteHandler` + verifies `StdTx`, manages accounts, and deducts fees + - [bank.CoinKeeper](core/app3.md#coinkeeper) - CoinKeeper allows for coin + transfers on an underlying AccountMapper + - [App4 - ABCI](core/app4.md) + - [ABCI](core/app4.md#abci) - ABCI is the interface between Tendermint + and the Cosmos-SDK + - [InitChain](core/app4.md#initchain) - Initialize the application + store + - [BeginBlock](core/app4.md#beginblock) - BeginBlock runs at the + beginning of every block and updates the app about validator behaviour + - [EndBlock](core/app4.md#endblock) - EndBlock runs at the + end of every block and lets the app change the validator set. + - [Query](core/app4.md#query) - Query the application store + - [CheckTx](core/app4.md#checktx) - CheckTx only runs the AnteHandler + - [App5 - Basecoin](core/app5.md) - + - [Directory Structure](core/app5.md#directory-structure) - Keep your + application code organized + - [Tendermint Node](core/app5.md#tendermint-node) - Run a full + blockchain node with your app + - [Clients](core/app5.md#clients) - Hook up your app to CLI and REST + interfaces for clients to use! + +- [Modules](modules) + - [Bank](modules/README.md#bank) + - [Staking](modules/README.md#stake) + - [Slashing](modules/README.md#slashing) + - [Provisions](modules/README.md#provisions) + - [Governance](modules/README.md#governance) + - [IBC](modules/README.md#ibc) + +- [Clients](clients) + - [Running a Node](clients/node.md) - Run a full node! + - [Key Management](clients/keys.md) - Managing user keys + - [CLI](clients/cli.md) - Queries and transactions via command line + - [REST Light Client Daemon](clients/rest.md) - Queries and transactions via REST + API diff --git a/docs/staking/intro.rst b/docs/_attic/staking/intro.rst similarity index 96% rename from docs/staking/intro.rst rename to docs/_attic/staking/intro.rst index 00a68811a..14735ef33 100644 --- a/docs/staking/intro.rst +++ b/docs/_attic/staking/intro.rst @@ -197,9 +197,9 @@ We'll have ``alice`` send some ``mycoin`` to ``bob``, who has now joined the net :: - gaiacli send --amount=1000mycoin --sequence=0 --name=alice --to=5A35E4CC7B7DC0A5CB49CEA91763213A9AE92AD6 --chain-id=test-chain-Uv1EVU + gaiacli send --amount=1000mycoin --sequence=0 --from=alice --to=5A35E4CC7B7DC0A5CB49CEA91763213A9AE92AD6 --chain-id=test-chain-Uv1EVU -where the ``--sequence`` flag is to be incremented for each transaction, the ``--name`` flag is the sender (alice), and the ``--to`` flag takes ``bob``'s address. You'll see something like: +where the ``--sequence`` flag is to be incremented for each transaction, the ``--from`` flag is the sender (alice), and the ``--to`` flag takes ``bob``'s address. You'll see something like: :: @@ -264,7 +264,7 @@ Now ``bob`` can create a validator with that pubkey. :: - gaiacli stake create-validator --amount=10mycoin --name=bob --address-validator=<address> --pub-key=<pubkey> --moniker=bobby + gaiacli stake create-validator --amount=10mycoin --from=bob --address-validator=<address> --pub-key=<pubkey> --moniker=bobby with an output like: @@ -291,7 +291,7 @@ To confirm for certain the new validator is active, ask the tendermint node: :: - curl localhost:46657/validators + curl localhost:26657/validators If you now kill either node, blocks will stop streaming in, because there aren't enough validators online. Turn it back on and they will @@ -306,13 +306,13 @@ First let's have ``alice`` send some coins to ``charlie``: :: - gaiacli send --amount=1000mycoin --sequence=2 --name=alice --to=48F74F48281C89E5E4BE9092F735EA519768E8EF + gaiacli send --amount=1000mycoin --sequence=2 --from=alice --to=48F74F48281C89E5E4BE9092F735EA519768E8EF Then ``charlie`` will delegate some mycoin to ``bob``: :: - gaiacli stake delegate --amount=10mycoin --address-delegator=<charlie's address> --address-validator=<bob's address> --name=charlie + gaiacli stake delegate --amount=10mycoin --address-delegator=<charlie's address> --address-validator=<bob's address> --from=charlie You'll see output like: @@ -396,7 +396,7 @@ your VotingPower reduce and your account balance increase. :: - gaiacli stake unbond --amount=5mycoin --name=charlie --address-delegator=<address> --address-validator=<address> + gaiacli stake unbond --amount=5mycoin --from=charlie --address-delegator=<address> --address-validator=<address> gaiacli account 48F74F48281C89E5E4BE9092F735EA519768E8EF See the bond decrease with ``gaiacli stake delegation`` like above. diff --git a/docs/_attic/staking/overview.md b/docs/_attic/staking/overview.md new file mode 100644 index 000000000..79033fe1e --- /dev/null +++ b/docs/_attic/staking/overview.md @@ -0,0 +1,216 @@ +//TODO update .rst + +# Staking Module + +## Overview + +The Cosmos Hub is a Tendermint-based Delegated Proof of Stake (DPos) blockchain +system that serves as a backbone of the Cosmos ecosystem. It is operated and +secured by an open and globally decentralized set of validators. Tendermint is +a Byzantine fault-tolerant distributed protocol for consensus among distrusting +parties, in this case the group of validators which produce the blocks for the +Cosmos Hub. To avoid the nothing-at-stake problem, a validator in Tendermint +needs to lock up coins in a bond deposit. Each bond's atoms are illiquid, they +cannot be transferred - in order to become liquid, they must be unbonded, a +process which will take 3 weeks by default at Cosmos Hub launch. Tendermint +protocol messages are signed by the validator's private key and are therefor +attributable. Validators acting outside protocol specifications can be made +accountable through punishing by slashing (burning) their bonded Atoms. On the +other hand, validators are rewarded for their service of securing blockchain +network by the inflationary provisions and transactions fees. This incentivizes +correct behavior of the validators and provides the economic security of the +network. + +The native token of the Cosmos Hub is called the Atom; becoming a validator of the +Cosmos Hub requires holding Atoms. However, not all Atom holders are validators +of the Cosmos Hub. More precisely, there is a selection process that determines +the validator set as a subset of all validators (Atom holders that +want to become a validator). The other option for Atom holders is to delegate +their atoms to validators, i.e., being a delegator. A delegator is an Atom +holder that has put its Atoms at stake by delegating it to a validator. By bonding +Atoms to secure the network (and taking a risk of being slashed in case of +misbehaviour), a user is rewarded with inflationary provisions and transaction +fees proportional to the amount of its bonded Atoms. The Cosmos Hub is +designed to efficiently facilitate a small numbers of validators (hundreds), +and large numbers of delegators (tens of thousands). More precisely, it is the +role of the Staking module of the Cosmos Hub to support various staking +functionality including validator set selection, delegating, bonding and +withdrawing Atoms, and the distribution of inflationary provisions and +transaction fees. + +## Basic Terms and Definitions + +* Cosmsos Hub - a Tendermint-based Delegated Proof of Stake (DPos) + blockchain system +* Atom - native token of the Cosmsos Hub +* Atom holder - an entity that holds some amount of Atoms +* Pool - Global object within the Cosmos Hub which accounts global state + including the total amount of bonded, unbonding, and unbonded atoms +* Validator Share - Share which a validator holds to represent its portion of + bonded, unbonding or unbonded atoms in the pool +* Delegation Share - Shares which a delegation bond holds to represent its + portion of bonded, unbonding or unbonded shares in a validator +* Bond Atoms - a process of locking Atoms in a delegation share which holds them + under protocol control. +* Slash Atoms - the process of burning atoms in the pool and assoiated + validator shares of a misbehaving validator, (not behaving according to the + protocol specification). This process devalues the worth of delegation shares + of the given validator +* Unbond Shares - Process of retrieving atoms from shares. If the shares are + bonded the shares must first remain in an inbetween unbonding state for the + duration of the unbonding period +* Redelegating Shares - Process of redelegating atoms from one validator to + another. This process is instantaneous, but the redelegated atoms are + retrospecively slashable if the old validator is found to misbehave for any + blocks before the redelegation. These atoms are simultaniously slashable + for any new blocks which the new validator misbehavess +* Validator - entity with atoms which is either actively validating the Tendermint + protocol (bonded validator) or vying to validate . +* Bonded Validator - a validator whose atoms are currently bonded and liable to + be slashed. These validators are to be able to sign protocol messages for + Tendermint consensus. At Cosmos Hub genesis there is a maximum of 100 + bonded validator positions. Only Bonded Validators receive atom provisions + and fee rewards. +* Delegator - an Atom holder that has bonded Atoms to a validator +* Unbonding period - time required in the unbonding state when unbonding + shares. Time slashable to old validator after a redelegation. Time for which + validators can be slashed after an infraction. To provide the requisite + cryptoeconomic security guarantees, all of these must be equal. +* Atom provisions - The process of increasing the Atom supply. Atoms are + periodically created on the Cosmos Hub and issued to bonded Atom holders. + The goal of inflation is to incentize most of the Atoms in existence to be + bonded. Atoms are distributed unbonded and using the fee_distribution mechanism +* Transaction fees - transaction fee is a fee that is included in a Cosmsos Hub + transaction. The fees are collected by the current validator set and + distributed among validators and delegators in proportion to their bonded + Atom share +* Commission fee - a fee taken from the transaction fees by a validator for + their service + +## The pool and the share + +At the core of the Staking module is the concept of a pool which denotes a +collection of Atoms contributed by different Atom holders. There are three +pools in the Staking module: the bonded, unbonding, and unbonded pool. Bonded +Atoms are part of the global bonded pool. If a validator or delegator wants to +unbond its shares, these Shares are moved to the the unbonding pool for the +duration of the unbonding period. From here normally Atoms will be moved +directly into the delegators wallet, however under the situation thatn an +entire validator gets unbonded, the Atoms of the delegations will remain with +the validator and moved to the unbonded pool. For each pool, the total amount +of bonded, unbonding, or unbonded Atoms are tracked as well as the current +amount of issued pool-shares, the specific holdings of these shares by +validators are tracked in protocol by the validator object. + +A share is a unit of Atom distribution and the value of the share +(share-to-atom exchange rate) can change during system execution. The +share-to-atom exchange rate can be computed as: + +`share-to-atom-exchange-rate = size of the pool / ammount of issued shares` + +Then for each validator (in a per validator data structure) the protocol keeps +track of the amount of shares the validator owns in a pool. At any point in +time, the exact amount of Atoms a validator has in the pool can be computed as +the number of shares it owns multiplied with the current share-to-atom exchange +rate: + +`validator-coins = validator.Shares * share-to-atom-exchange-rate` + +The benefit of such accounting of the pool resources is the fact that a +modification to the pool from bonding/unbonding/slashing of Atoms affects only +global data (size of the pool and the number of shares) and not the related +validator data structure, i.e., the data structure of other validators do not +need to be modified. This has the advantage that modifying global data is much +cheaper computationally than modifying data of every validator. Let's explain +this further with several small examples: + +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XXX TODO make way less verbose lets use bullet points to describe the example +XXX Also need to update to not include bonded atom provisions all atoms are +XXX redistributed with the fee pool now + +We consider initially 4 validators p1, p2, p3 and p4, and that each validator +has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have +issued initially 40 shares (note that the initial distribution of the shares, +i.e., share-to-atom exchange rate can be set to any meaningful value), i.e., +share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we +have, the size of the pool is 40 Atoms, and the amount of issued shares is +equal to 40. And for each validator we store in their corresponding data +structure that each has 10 shares of the bonded pool. Now lets assume that the +validator p4 starts process of unbonding of 5 shares. Then the total size of +the pool is decreased and now it will be 35 shares and the amount of Atoms is +35 . Note that the only change in other data structures needed is reducing the +number of shares for a validator p4 from 10 to 5. + +Let's consider now the case where a validator p1 wants to bond 15 more atoms to +the pool. Now the size of the pool is 50, and as the exchange rate hasn't +changed (1 share is still worth 1 Atom), we need to create more shares, i.e. we +now have 50 shares in the pool in total. Validators p2, p3 and p4 still have +(correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we +don't need to modify anything in their corresponding data structures. But p1 +now has 25 shares, so we update the amount of shares owned by p1 in its +data structure. Note that apart from the size of the pool that is in Atoms, all +other data structures refer only to shares. + +Finally, let's consider what happens when new Atoms are created and added to +the pool due to inflation. Let's assume that the inflation rate is 10 percent +and that it is applied to the current state of the pool. This means that 5 +Atoms are created and added to the pool and that each validator now +proportionally increase it's Atom count. Let's analyse how this change is +reflected in the data structures. First, the size of the pool is increased and +is now 55 atoms. As a share of each validator in the pool hasn't changed, this +means that the total number of shares stay the same (50) and that the amount of +shares of each validator stays the same (correspondingly 25, 10, 10, 5). But +the exchange rate has changed and each share is now worth 55/50 Atoms per +share, so each validator has effectively increased amount of Atoms it has. So +validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms. + +The concepts of the pool and its shares is at the core of the accounting in the +Staking module. It is used for managing the global pools (such as bonding and +unbonding pool), but also for distribution of Atoms between validator and its +delegators (we will explain this in section X). + +#### Delegator shares + +A validator is, depending on its status, contributing Atoms to either the +unbonding or unbonded pool - the validator in turn holds some amount of pool +shares. Not all of a validator's Atoms (and respective shares) are necessarily +owned by the validator, some may be owned by delegators to that validator. The +mechanism for distribution of Atoms (and shares) between a validator and its +delegators is based on a notion of delegator shares. More precisely, every +validator is issuing (local) delegator shares +(`Validator.IssuedDelegatorShares`) that represents some portion of global +shares managed by the validator (`Validator.GlobalStakeShares`). The principle +behind managing delegator shares is the same as described in [Section](#The +pool and the share). We now illustrate it with an example. + +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XXX TODO make way less verbose lets use bullet points to describe the example +XXX Also need to update to not include bonded atom provisions all atoms are +XXX redistributed with the fee pool now + +Let's consider 4 validators p1, p2, p3 and p4, and assume that each validator +has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have +issued initially 40 global shares, i.e., that +`share-to-atom-exchange-rate = 1 atom per share`. So we will set +`GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the +Validator data structure of each validator `Validator.GlobalStakeShares = 10`. +Furthermore, each validator issued 10 delegator shares which are initially +owned by itself, i.e., `Validator.IssuedDelegatorShares = 10`, where +`delegator-share-to-global-share-ex-rate = 1 global share per delegator share`. +Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and +consider what are the updates we need to make to the data structures. First, +`GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, for +validator p1 we have `Validator.GlobalStakeShares = 15`, but we also need to +issue also additional delegator shares, i.e., +`Validator.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator +shares of validator p1, where each delegator share is worth 1 global shares, +i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due to +inflation. In that case, we only need to update `GlobalState.BondedPool` which +is now equal to 50 Atoms as created Atoms are added to the bonded pool. Note +that the amount of global and delegator shares stay the same but they are now +worth more as share-to-atom-exchange-rate is now worth 50/45 Atoms per share. +Therefore, a delegator d1 now owns: + +`delegatorCoins = 5 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 5.55 Atoms` + diff --git a/docs/spec/staking/old/spec.md b/docs/_attic/staking/stakingSpec1.md similarity index 100% rename from docs/spec/staking/old/spec.md rename to docs/_attic/staking/stakingSpec1.md diff --git a/docs/spec/staking/old/spec2.md b/docs/_attic/staking/stakingSpec2.md similarity index 100% rename from docs/spec/staking/old/spec2.md rename to docs/_attic/staking/stakingSpec2.md diff --git a/docs/_attic/staking/testnet.md b/docs/_attic/staking/testnet.md new file mode 100644 index 000000000..5228070cf --- /dev/null +++ b/docs/_attic/staking/testnet.md @@ -0,0 +1,94 @@ +# Testnet Setup + +**Note:** This document is incomplete and may not be up-to-date with the +state of the code. + +See the [installation guide](../sdk/install.html) for details on +installation. + +Here is a quick example to get you off your feet: + +First, generate a couple of genesis transactions to be incorporated into +the genesis file, this will create two keys with the password +`1234567890`: + +``` +gaiad init gen-tx --name=foo --home=$HOME/.gaiad1 +gaiad init gen-tx --name=bar --home=$HOME/.gaiad2 +gaiacli keys list +``` + +**Note:** If you've already run these tests you may need to overwrite +keys using the `--owk` flag When you list the keys you should see two +addresses, we'll need these later so take note. Now let's actually +create the genesis files for both nodes: + +``` +cp -a ~/.gaiad2/config/gentx/. ~/.gaiad1/config/gentx/ +cp -a ~/.gaiad1/config/gentx/. ~/.gaiad2/config/gentx/ +gaiad init --gen-txs --home=$HOME/.gaiad1 --chain-id=test-chain +gaiad init --gen-txs --home=$HOME/.gaiad2 --chain-id=test-chain +``` + +**Note:** If you've already run these tests you may need to overwrite +genesis using the `-o` flag. What we just did is copy the genesis +transactions between each of the nodes so there is a common genesis +transaction set; then we created both genesis files independently from +each home directory. Importantly both nodes have independently created +their `genesis.json` and `config.toml` files, which should be identical +between nodes. + +Great, now that we've initialized the chains, we can start both nodes in +the background: + +``` +gaiad start --home=$HOME/.gaiad1 &> gaia1.log & +NODE1_PID=$! +gaia start --home=$HOME/.gaiad2 &> gaia2.log & +NODE2_PID=$! +``` + +Note that we save the PID so we can later kill the processes. You can +peak at your logs with `tail gaia1.log`, or follow them for a bit with +`tail -f gaia1.log`. + +Nice. We can also lookup the validator set: + +``` +gaiacli validatorset +``` + +Then, we try to transfer some `steak` to another account: + +``` +gaiacli account <FOO-ADDR> +gaiacli account <BAR-ADDR> +gaiacli send --amount=10steak --to=<BAR-ADDR> --from=foo --chain-id=test-chain +``` + +**Note:** We need to be careful with the `chain-id` and `sequence` + +Check the balance & sequence with: + +``` +gaiacli account <BAR-ADDR> +``` + +To confirm for certain the new validator is active, check tendermint: + +``` +curl localhost:46657/validators +``` + +Finally, to relinquish all your power, unbond some coins. You should see +your VotingPower reduce and your account balance increase. + +``` +gaiacli unbond --chain-id=<chain-id> --from=test +``` + +That's it! + +**Note:** TODO demonstrate edit-candidacy **Note:** TODO demonstrate +delegation **Note:** TODO demonstrate unbond of delegation **Note:** +TODO demonstrate unbond candidate diff --git a/docs/clients/cli.md b/docs/clients/cli.md new file mode 100644 index 000000000..dbd234d37 --- /dev/null +++ b/docs/clients/cli.md @@ -0,0 +1,8 @@ +# CLI + +See `gaiacli --help` for more details. + +Also see the [testnet +tutorial](https://github.com/cosmos/cosmos-sdk/tree/develop/cmd/gaia/testnets). + +TODO: cleanup the UX and document this properly. diff --git a/docs/clients/keys.md b/docs/clients/keys.md new file mode 100644 index 000000000..6d9732868 --- /dev/null +++ b/docs/clients/keys.md @@ -0,0 +1,10 @@ +# Keys + +See the [Tendermint specification](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/encoding.md#public-key-cryptography) for how we work with keys. + +See `gaiacli keys --help`. + +Also see the [testnet +tutorial](https://github.com/cosmos/cosmos-sdk/tree/develop/cmd/gaia/testnets). + +TODO: cleanup the UX and document this properly diff --git a/docs/sdk/lcd-rest-api.yaml b/docs/clients/lcd-rest-api.yaml similarity index 98% rename from docs/sdk/lcd-rest-api.yaml rename to docs/clients/lcd-rest-api.yaml index 3008d7f73..a39b7bf08 100644 --- a/docs/sdk/lcd-rest-api.yaml +++ b/docs/clients/lcd-rest-api.yaml @@ -17,6 +17,13 @@ paths: responses: 200: description: Plaintext version i.e. "v0.5.0" + /node_version: + get: + summary: Version of the connected node + description: Get the version of the SDK running on the connected node to compare against expected + responses: + 200: + description: Plaintext version i.e. "v0.5.0" /node_info: get: description: Only the node info. Block information can be queried via /block/latest @@ -41,7 +48,7 @@ paths: type: string listen_addr: type: string - example: 192.168.56.1:46656 + example: 192.168.56.1:26656 version: description: Tendermint version type: string diff --git a/docs/clients/ledger.md b/docs/clients/ledger.md new file mode 100644 index 000000000..5ea1224ea --- /dev/null +++ b/docs/clients/ledger.md @@ -0,0 +1,29 @@ +# Ledger // Cosmos + +### Ledger Support for account keys + +`gaiacli` now supports derivation of account keys from a Ledger seed. To use this functionality you will need the following: + +- A running `gaiad` instance connected to the network you wish to use. +- A `gaiacli` instance configured to connect to your chosen `gaiad` instance. +- A LedgerNano with the `ledger-cosmos` app installed + * Install the Cosmos app onto your Ledger by following the instructions in the [`ledger-cosmos`](https://github.com/cosmos/ledger-cosmos/blob/master/docs/BUILD.md) repository. + * A production-ready version of this app will soon be included in the [Ledger Apps Store](https://www.ledgerwallet.com/apps) + +> **NOTE:** Cosmos keys are derived acording to the [BIP 44 Hierarchical Deterministic wallet spec](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki). For more information on Cosmos derivation paths [see the hd package](https://github.com/cosmos/cosmos-sdk/blob/develop/crypto/keys/hd/hdpath.go#L30). + +Once you have the Cosmos app installed on your Ledger, and the Ledger is accessible from the machine you are using `gaiacli` from you can create a new account key using the Ledger: + +```bash +$ gaiacli keys add {{ .Key.Name }} --ledger +NAME: TYPE: ADDRESS: PUBKEY: +{{ .Key.Name }} ledger cosmosaccaddr1aw64xxr80lwqqdk8u2xhlrkxqaxamkr3e2g943 cosmosaccpub1addwnpepqvhs678gh9aqrjc2tg2vezw86csnvgzqq530ujkunt5tkuc7lhjkz5mj629 +``` + +This key will only be accessible while the Ledger is plugged in and unlocked. To send some coins with this key, run the following: + +```bash +$ gaiacli send --from {{ .Key.Name }} --to {{ .Destination.AccAddr }} --chain-id=gaia-7000 +``` + +You will be asked to review and confirm the transaction on the Ledger. Once you do this you should see the result in the console! Now you can use your Ledger to manage your Atoms and Stake! diff --git a/docs/clients/node.md b/docs/clients/node.md new file mode 100644 index 000000000..71d4846fd --- /dev/null +++ b/docs/clients/node.md @@ -0,0 +1,10 @@ +# Running a Node + +TODO: document `gaiad` + +Options for running the `gaiad` binary are effectively the same as for `tendermint`. +See `gaiad --help` and the +[guide to using Tendermint](https://github.com/tendermint/tendermint/blob/master/docs/using-tendermint.md) +for more details. + + diff --git a/docs/clients/rest.md b/docs/clients/rest.md new file mode 100644 index 000000000..190eeb1f3 --- /dev/null +++ b/docs/clients/rest.md @@ -0,0 +1,6 @@ +# REST + +See `gaiacli advanced rest-server --help` for more. + +Also see the +[work in progress API specification](https://github.com/cosmos/cosmos-sdk/pull/1314) diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 73a0220fd..000000000 --- a/docs/conf.py +++ /dev/null @@ -1,170 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Cosmos-SDK documentation build configuration file, created by -# sphinx-quickstart on Fri Sep 1 21:37:02 2017. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - -import sphinx_rtd_theme - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Cosmos-SDK' -copyright = u'2018, The Authors' -author = u'The Authors' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = u'' -# The full version, including alpha/beta/rc tags. -release = u'' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'old'] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' -# html_theme = 'alabaster' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# This is required for the alabaster theme -# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars -html_sidebars = { - '**': [ - 'about.html', - 'navigation.html', - 'relations.html', # needs 'show_related': True theme option to display - 'searchbox.html', - 'donate.html', - ] -} - - -# -- Options for HTMLHelp output ------------------------------------------ - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Cosmos-SDKdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'Cosmos-SDK.tex', u'Cosmos-SDK Documentation', - u'The Authors', 'manual'), -] - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'cosmos-sdk', u'Cosmos-SDK Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'Cosmos-SDK', u'Cosmos-SDK Documentation', - author, 'Cosmos-SDK', 'One line description of project.', - 'Miscellaneous'), -] diff --git a/docs/core/app1.md b/docs/core/app1.md new file mode 100644 index 000000000..6977d2ddc --- /dev/null +++ b/docs/core/app1.md @@ -0,0 +1,495 @@ +# The Basics + +Here we introduce the basic components of an SDK by building `App1`, a simple bank. +Users have an account address and an account, and they can send coins around. +It has no authentication, and just uses JSON for serialization. + +The complete code can be found in [app1.go](examples/app1.go). + +## Messages + +Messages are the primary inputs to the application state machine. +They define the content of transactions and can contain arbitrary information. +Developers can create messages by implementing the `Msg` interface: + +```go +type Msg interface { + // Return the message type. + // Must be alphanumeric or empty. + // Must correspond to name of message handler (XXX). + Type() string + + // ValidateBasic does a simple validation check that + // doesn't require access to any other information. + ValidateBasic() error + + // Get the canonical byte representation of the Msg. + // This is what is signed. + GetSignBytes() []byte + + // Signers returns the addrs of signers that must sign. + // CONTRACT: All signatures must be present to be valid. + // CONTRACT: Returns addrs in some deterministic order. + GetSigners() []AccAddress +} +``` + + +The `Msg` interface allows messages to define basic validity checks, as well as +what needs to be signed and who needs to sign it. + +For instance, take the simple token sending message type from app1.go: + +```go +// MsgSend to send coins from Input to Output +type MsgSend struct { + From sdk.AccAddress `json:"from"` + To sdk.AccAddress `json:"to"` + Amount sdk.Coins `json:"amount"` +} + +// Implements Msg. +func (msg MsgSend) Type() string { return "bank" } +``` + +It specifies that the message should be JSON marshaled and signed by the sender: + +```go +// Implements Msg. JSON encode the message. +func (msg MsgSend) GetSignBytes() []byte { + bz, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return bz +} + +// Implements Msg. Return the signer. +func (msg MsgSend) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.From} +} +``` + +Note Addresses in the SDK are arbitrary byte arrays that are +[Bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) encoded +when displayed as a string or rendered in JSON. Typically, addresses are the hash of +a public key, so we can use them to uniquely identify the required signers for a +transaction. + + +The basic validity check ensures the From and To address are specified and the +Amount is positive: + +```go +// Implements Msg. Ensure the addresses are good and the +// amount is positive. +func (msg MsgSend) ValidateBasic() sdk.Error { + if len(msg.From) == 0 { + return sdk.ErrInvalidAddress("From address is empty") + } + if len(msg.To) == 0 { + return sdk.ErrInvalidAddress("To address is empty") + } + if !msg.Amount.IsPositive() { + return sdk.ErrInvalidCoins("Amount is not positive") + } + return nil +} +``` + +Note the `ValidateBasic` method is called automatically by the SDK! + +## KVStore + +The basic persistence layer for an SDK application is the KVStore: + +```go +type KVStore interface { + Store + + // Get returns nil iff key doesn't exist. Panics on nil key. + Get(key []byte) []byte + + // Has checks if a key exists. Panics on nil key. + Has(key []byte) bool + + // Set sets the key. Panics on nil key. + Set(key, value []byte) + + // Delete deletes the key. Panics on nil key. + Delete(key []byte) + + // Iterator over a domain of keys in ascending order. End is exclusive. + // Start must be less than end, or the Iterator is invalid. + // CONTRACT: No writes may happen within a domain while an iterator exists over it. + Iterator(start, end []byte) Iterator + + // Iterator over a domain of keys in descending order. End is exclusive. + // Start must be greater than end, or the Iterator is invalid. + // CONTRACT: No writes may happen within a domain while an iterator exists over it. + ReverseIterator(start, end []byte) Iterator + + } +``` + +Note it is unforgiving - it panics on nil keys! + +The primary implementation of the KVStore is currently the IAVL store. In the future, we plan to support other Merkle KVStores, +like Ethereum's radix trie. + +As we'll soon see, apps have many distinct KVStores, each with a different name and for a different concern. +Access to a store is mediated by *object-capability keys*, which must be granted to a handler during application startup. + +## Handlers + +Now that we have a message type and a store interface, we can define our state transition function using a handler: + +```go +// Handler defines the core of the state transition function of an application. +type Handler func(ctx Context, msg Msg) Result +``` + +Along with the message, the Handler takes environmental information (a `Context`), and returns a `Result`. +All information necessary for processing a message should be available in the context. + +Where is the KVStore in all of this? Access to the KVStore in a message handler is restricted by the Context via object-capability keys. +Only handlers which were given explict access to a store's key will be able to access that store during message processsing. + +### Context + +The SDK uses a `Context` to propogate common information across functions. +Most importantly, the `Context` restricts access to KVStores based on object-capability keys. +Only handlers which have been given explicit access to a key will be able to access the corresponding store. + +For instance, the FooHandler can only load the store it's given the key for: + +```go +// newFooHandler returns a Handler that can access a single store. +func newFooHandler(key sdk.StoreKey) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + store := ctx.KVStore(key) + // ... + } +} +``` + +`Context` is modeled after the Golang +[context.Context](https://golang.org/pkg/context/), which has +become ubiquitous in networking middleware and routing applications as a means +to easily propogate request context through handler functions. +Many methods on SDK objects receive a context as the first argument. + +The Context also contains the +[block header](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/blockchain.md#header), +which includes the latest timestamp from the blockchain and other information about the latest block. + +See the [Context API +docs](https://godoc.org/github.com/cosmos/cosmos-sdk/types#Context) for more details. + +### Result + +Handler takes a Context and Msg and returns a Result. +Result is motivated by the corresponding [ABCI result](https://github.com/tendermint/tendermint/blob/master/abci/types/types.proto#L165). +It contains return values, error information, logs, and meta data about the transaction: + +```go +// Result is the union of ResponseDeliverTx and ResponseCheckTx. +type Result struct { + + // Code is the response code, is stored back on the chain. + Code ABCICodeType + + // Data is any data returned from the app. + Data []byte + + // Log is just debug information. NOTE: nondeterministic. + Log string + + // GasWanted is the maximum units of work we allow this tx to perform. + GasWanted int64 + + // GasUsed is the amount of gas actually consumed. NOTE: unimplemented + GasUsed int64 + + // Tx fee amount and denom. + FeeAmount int64 + FeeDenom string + + // Tags are used for transaction indexing and pubsub. + Tags Tags +} +``` + +We'll talk more about these fields later in the tutorial. For now, note that a +`0` value for the `Code` is considered a success, and everything else is a +failure. The `Tags` can contain meta data about the transaction that will allow +us to easily lookup transactions that pertain to particular accounts or actions. + +### Handler + +Let's define our handler for App1: + +```go +// Handle MsgSend. +// NOTE: msg.From, msg.To, and msg.Amount were already validated +// in ValidateBasic(). +func handleMsgSend(ctx sdk.Context, key *sdk.KVStoreKey, msg MsgSend) sdk.Result { + // Load the store. + store := ctx.KVStore(key) + + // Debit from the sender. + if res := handleFrom(store, msg.From, msg.Amount); !res.IsOK() { + return res + } + + // Credit the receiver. + if res := handleTo(store, msg.To, msg.Amount); !res.IsOK() { + return res + } + + // Return a success (Code 0). + // Add list of key-value pair descriptors ("tags"). + return sdk.Result{ + Tags: msg.Tags(), + } +} +``` + +We have only a single message type, so just one message-specific function to define, `handleMsgSend`. + +Note this handler has unrestricted access to the store specified by the capability key `keyAcc`, +so it must define what to store and how to encode it. Later, we'll introduce +higher-level abstractions so Handlers are restricted in what they can do. +For this first example, we use a simple account that is JSON encoded: + +```go +type appAccount struct { + Coins sdk.Coins `json:"coins"` +} +``` + +Coins is a useful type provided by the SDK for multi-asset accounts. +We could just use an integer here for a single coin type, but +it's worth [getting to know +Coins](https://godoc.org/github.com/cosmos/cosmos-sdk/types#Coins). + + +Now we're ready to handle the two parts of the MsgSend: + +```go +func handleFrom(store sdk.KVStore, from sdk.AccAddress, amt sdk.Coins) sdk.Result { + // Get sender account from the store. + accBytes := store.Get(from) + if accBytes == nil { + // Account was not added to store. Return the result of the error. + return sdk.NewError(2, 101, "Account not added to store").Result() + } + + // Unmarshal the JSON account bytes. + var acc appAccount + err := json.Unmarshal(accBytes, &acc) + if err != nil { + // InternalError + return sdk.ErrInternal("Error when deserializing account").Result() + } + + // Deduct msg amount from sender account. + senderCoins := acc.Coins.Minus(amt) + + // If any coin has negative amount, return insufficient coins error. + if !senderCoins.IsNotNegative() { + return sdk.ErrInsufficientCoins("Insufficient coins in account").Result() + } + + // Set acc coins to new amount. + acc.Coins = senderCoins + + // Encode sender account. + accBytes, err = json.Marshal(acc) + if err != nil { + return sdk.ErrInternal("Account encoding error").Result() + } + + // Update store with updated sender account + store.Set(from, accBytes) + return sdk.Result{} +} + +func handleTo(store sdk.KVStore, to sdk.AccAddress, amt sdk.Coins) sdk.Result { + // Add msg amount to receiver account + accBytes := store.Get(to) + var acc appAccount + if accBytes == nil { + // Receiver account does not already exist, create a new one. + acc = appAccount{} + } else { + // Receiver account already exists. Retrieve and decode it. + err := json.Unmarshal(accBytes, &acc) + if err != nil { + return sdk.ErrInternal("Account decoding error").Result() + } + } + + // Add amount to receiver's old coins + receiverCoins := acc.Coins.Plus(amt) + + // Update receiver account + acc.Coins = receiverCoins + + // Encode receiver account + accBytes, err := json.Marshal(acc) + if err != nil { + return sdk.ErrInternal("Account encoding error").Result() + } + + // Update store with updated receiver account + store.Set(to, accBytes) + return sdk.Result{} +} +``` + +The handler is straight forward. We first load the KVStore from the context using the granted capability key. +Then we make two state transitions: one for the sender, one for the receiver. +Each one involves JSON unmarshalling the account bytes from the store, mutating +the `Coins`, and JSON marshalling back into the store. + +And that's that! + +## Tx + +The final piece before putting it all together is the `Tx`. +While `Msg` contains the content for particular functionality in the application, the actual input +provided by the user is a serialized `Tx`. Applications may have many implementations of the `Msg` interface, +but they should have only a single implementation of `Tx`: + + +```go +// Transactions wrap messages. +type Tx interface { + // Gets the Msgs. + GetMsgs() []Msg +} +``` + +The `Tx` just wraps a `[]Msg`, and may include additional authentication data, like signatures and account nonces. +Applications must specify how their `Tx` is decoded, as this is the ultimate input into the application. +We'll talk more about `Tx` types later, specifically when we introduce the `StdTx`. + +In this first application, we won't have any authentication at all. This might +make sense in a private network where access is controlled by alternative means, +like client-side TLS certificates, but in general, we'll want to bake the authentication +right into our state machine. We'll use `Tx` to do that +in the next app. For now, the `Tx` just embeds `MsgSend` and uses JSON: + + +```go +// Simple tx to wrap the Msg. +type app1Tx struct { + MsgSend +} + +// This tx only has one Msg. +func (tx app1Tx) GetMsgs() []sdk.Msg { + return []sdk.Msg{tx.MsgSend} +} + +// JSON decode MsgSend. +func txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) { + var tx app1Tx + err := json.Unmarshal(txBytes, &tx) + if err != nil { + return nil, sdk.ErrTxDecode(err.Error()) + } + return tx, nil +} +``` + +## BaseApp + +Finally, we stitch it all together using the `BaseApp`. + +The BaseApp is an abstraction over the [Tendermint +ABCI](https://github.com/tendermint/tendermint/tree/master/abci) that +simplifies application development by handling common low-level concerns. +It serves as the mediator between the two key components of an SDK app: the store +and the message handlers. The BaseApp implements the +[`abci.Application`](https://godoc.org/github.com/tendermint/tendermint/abci/types#Application) interface. +See the [BaseApp API +documentation](https://godoc.org/github.com/cosmos/cosmos-sdk/baseapp) for more details. + +Here is the complete setup for App1: + +```go +func NewApp1(logger log.Logger, db dbm.DB) *bapp.BaseApp { + cdc := wire.NewCodec() + + // Create the base application object. + app := bapp.NewBaseApp(app1Name, cdc, logger, db) + + // Create a capability key for accessing the account store. + keyAccount := sdk.NewKVStoreKey("acc") + + // Determine how transactions are decoded. + app.SetTxDecoder(txDecoder) + + // Register message routes. + // Note the handler receives the keyAccount and thus + // gets access to the account store. + app.Router(). + AddRoute("bank", NewApp1Handler(keyAccount)) + + // Mount stores and load the latest state. + app.MountStoresIAVL(keyAccount) + err := app.LoadLatestVersion(keyAccount) + if err != nil { + cmn.Exit(err.Error()) + } + return app +} +``` + +Every app will have such a function that defines the setup of the app. +It will typically be contained in an `app.go` file. +We'll talk about how to connect this app object with the CLI, a REST API, +the logger, and the filesystem later in the tutorial. For now, note that this is where we +register handlers for messages and grant them access to stores. + +Here, we have only a single Msg type, `bank`, a single store for accounts, and a single handler. +The handler is granted access to the store by giving it the capability key. +In future apps, we'll have multiple stores and handlers, and not every handler will get access to every store. + +After setting the transaction decoder and the message handling routes, the final +step is to mount the stores and load the latest version. +Since we only have one store, we only mount one. + +## Execution + +We're now done the core logic of the app! From here, we can write tests in Go +that initialize the store with accounts and execute transactions by calling +the `app.DeliverTx` method. + +In a real setup, the app would run as an ABCI application on top of the +Tendermint consensus engine. It would be initialized by a Genesis file, and it +would be driven by blocks of transactions committed by the underlying Tendermint +consensus. We'll talk more about ABCI and how this all works a bit later, but +feel free to check the +[specification](https://github.com/tendermint/tendermint/blob/master/docs/abci-spec.md). +We'll also see how to connect our app to a complete suite of components +for running and using a live blockchain application. + +For now, we note the follow sequence of events occurs when a transaction is +received (through `app.DeliverTx`): + +- serialized transaction is received by `app.DeliverTx` +- transaction is deserialized using `TxDecoder` +- for each message in the transaction, run `msg.ValidateBasic()` +- for each message in the transaction, load the appropriate handler and execute + it with the message + +## Conclusion + +We now have a complete implementation of a simple app! + +In the next section, we'll add another Msg type and another store. Once we have multiple message types +we'll need a better way of decoding transactions, since we'll need to decode +into the `Msg` interface. This is where we introduce Amino, a superior encoding scheme that lets us decode into interface types! diff --git a/docs/core/app2.md b/docs/core/app2.md new file mode 100644 index 000000000..0158976cd --- /dev/null +++ b/docs/core/app2.md @@ -0,0 +1,301 @@ +# Transactions + +In the previous app we built a simple `bank` with one message type for sending +coins and one store for storing accounts. +Here we build `App2`, which expands on `App1` by introducing + +- a new message type for issuing new coins +- a new store for coin metadata (like who can issue coins) +- a requirement that transactions include valid signatures + +Along the way, we'll be introduced to Amino for encoding and decoding +transactions and to the AnteHandler for processing them. + +The complete code can be found in [app2.go](examples/app2.go). + + +## Message + +Let's introduce a new message type for issuing coins: + +```go +// MsgIssue to allow a registered issuer +// to issue new coins. +type MsgIssue struct { + Issuer sdk.AccAddress + Receiver sdk.AccAddress + Coin sdk.Coin +} + +// Implements Msg. +func (msg MsgIssue) Type() string { return "issue" } +``` + +Note the `Type()` method returns `"issue"`, so this message is of a different +type and will be executed by a different handler than `MsgSend`. The other +methods for `MsgIssue` are similar to `MsgSend`. + +## Handler + +We'll need a new handler to support the new message type. It just checks if the +sender of the `MsgIssue` is the correct issuer for the given coin type, as per the information +in the issuer store: + +```go +// Handle MsgIssue +func handleMsgIssue(keyIssue *sdk.KVStoreKey, keyAcc *sdk.KVStoreKey) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + issueMsg, ok := msg.(MsgIssue) + if !ok { + return sdk.NewError(2, 1, "MsgIssue is malformed").Result() + } + + // Retrieve stores + issueStore := ctx.KVStore(keyIssue) + accStore := ctx.KVStore(keyAcc) + + // Handle updating coin info + if res := handleIssuer(issueStore, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() { + return res + } + + // Issue coins to receiver using previously defined handleTo function + if res := handleTo(accStore, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}); !res.IsOK() { + return res + } + + return sdk.Result{ + // Return result with Issue msg tags + Tags: issueMsg.Tags(), + } + } +} + +func handleIssuer(store sdk.KVStore, issuer sdk.AccAddress, coin sdk.Coin) sdk.Result { + // the issuer address is stored directly under the coin denomination + denom := []byte(coin.Denom) + infoBytes := store.Get(denom) + if infoBytes == nil { + return sdk.ErrInvalidCoins(fmt.Sprintf("Unknown coin type %s", coin.Denom)).Result() + } + + var coinInfo coinInfo + err := json.Unmarshal(infoBytes, &coinInfo) + if err != nil { + return sdk.ErrInternal("Error when deserializing coinInfo").Result() + } + + // Msg Issuer is not authorized to issue these coins + if !bytes.Equal(coinInfo.Issuer, issuer) { + return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result() + } + + return sdk.Result{} +} + +// coinInfo stores meta data about a coin +type coinInfo struct { + Issuer sdk.AccAddress `json:"issuer"` +} +``` + +Note we've introduced the `coinInfo` type to store the issuer address for each coin. +We JSON serialize this type and store it directly under the denomination in the +issuer store. We could of course add more fields and logic around this, +like including the current supply of coins in existence, and enforcing a maximum supply, +but that's left as an excercise for the reader :). + +## Amino + +Now that we have two implementations of `Msg`, we won't know before hand +which type is contained in a serialized `Tx`. Ideally, we would use the +`Msg` interface inside our `Tx` implementation, but the JSON decoder can't +decode into interface types. In fact, there's no standard way to unmarshal +into interfaces in Go. This is one of the primary reasons we built +[Amino](https://github.com/tendermint/go-amino) :). + +While SDK developers can encode transactions and state objects however they +like, Amino is the recommended format. The goal of Amino is to improve over the latest version of Protocol Buffers, +`proto3`. To that end, Amino is compatible with the subset of `proto3` that +excludes the `oneof` keyword. + +While `oneof` provides union types, Amino aims to provide interfaces. +The main difference being that with union types, you have to know all the types +up front. But anyone can implement an interface type whenever and however +they like. + +To implement interface types, Amino allows any concrete implementation of an +interface to register a globally unique name that is carried along whenever the +type is serialized. This allows Amino to seamlessly deserialize into interface +types! + +The primary use for Amino in the SDK is for messages that implement the +`Msg` interface. By registering each message with a distinct name, they are each +given a distinct Amino prefix, allowing them to be easily distinguished in +transactions. + +Amino can also be used for persistent storage of interfaces. + +To use Amino, simply create a codec, and then register types: + +``` +func NewCodec() *wire.Codec { + cdc := wire.NewCodec() + cdc.RegisterInterface((*sdk.Msg)(nil), nil) + cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil) + cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil) + return cdc +} +``` + +Amino supports encoding and decoding in both a binary and JSON format. +See the [codec API docs](https://godoc.org/github.com/tendermint/go-amino#Codec) for more details. + +## Tx + +Now that we're using Amino, we can embed the `Msg` interface directly in our +`Tx`. We can also add a public key and a signature for authentication. + +```go +// Simple tx to wrap the Msg. +type app2Tx struct { + sdk.Msg + + PubKey crypto.PubKey + Signature crypto.Signature +} + +// This tx only has one Msg. +func (tx app2Tx) GetMsgs() []sdk.Msg { + return []sdk.Msg{tx.Msg} +} +``` + +We don't need a custom TxDecoder function anymore, since we're just using the +Amino codec! + +## AnteHandler + +Now that we have an implementation of `Tx` that includes more than just the Msg, +we need to specify how that extra information is validated and processed. This +is the role of the `AnteHandler`. The word `ante` here denotes "before", as the +`AnteHandler` is run before a `Handler`. While an app can have many Handlers, +one for each set of messages, it can have only a single `AnteHandler` that +corresponds to its single implementation of `Tx`. + + +The AnteHandler resembles a Handler: + + +```go +type AnteHandler func(ctx Context, tx Tx) (newCtx Context, result Result, abort bool) +``` + +Like Handler, AnteHandler takes a Context that restricts its access to stores +according to whatever capability keys it was granted. Instead of a `Msg`, +however, it takes a `Tx`. + +Like Handler, AnteHandler returns a `Result` type, but it also returns a new +`Context` and an `abort bool`. + +For `App2`, we simply check if the PubKey matches the Address, and the Signature validates with the PubKey: + +```go +// Simple anteHandler that ensures msg signers have signed. +// Provides no replay protection. +func antehandler(ctx sdk.Context, tx sdk.Tx) (_ sdk.Context, _ sdk.Result, abort bool) { + appTx, ok := tx.(app2Tx) + if !ok { + // set abort boolean to true so that we don't continue to process failed tx + return ctx, sdk.ErrTxDecode("Tx must be of format app2Tx").Result(), true + } + + // expect only one msg in app2Tx + msg := tx.GetMsgs()[0] + + signerAddrs := msg.GetSigners() + + if len(signerAddrs) != len(appTx.GetSignatures()) { + return ctx, sdk.ErrUnauthorized("Number of signatures do not match required amount").Result(), true + } + + signBytes := msg.GetSignBytes() + for i, addr := range signerAddrs { + sig := appTx.GetSignatures()[i] + + // check that submitted pubkey belongs to required address + if !bytes.Equal(sig.PubKey.Address(), addr) { + return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true + } + + // check that signature is over expected signBytes + if !sig.PubKey.VerifyBytes(signBytes, sig.Signature) { + return ctx, sdk.ErrUnauthorized("Signature verification failed").Result(), true + } + } + + // authentication passed, app to continue processing by sending msg to handler + return ctx, sdk.Result{}, false +} +``` + +## App2 + +Let's put it all together now to get App2: + +```go +func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp { + + cdc := NewCodec() + + // Create the base application object. + app := bapp.NewBaseApp(app2Name, cdc, logger, db) + + // Create a key for accessing the account store. + keyAccount := sdk.NewKVStoreKey("acc") + // Create a key for accessing the issue store. + keyIssue := sdk.NewKVStoreKey("issue") + + // set antehandler function + app.SetAnteHandler(antehandler) + + // Register message routes. + // Note the handler gets access to the account store. + app.Router(). + AddRoute("send", handleMsgSend(keyAccount)). + AddRoute("issue", handleMsgIssue(keyAccount, keyIssue)) + + // Mount stores and load the latest state. + app.MountStoresIAVL(keyAccount, keyIssue) + err := app.LoadLatestVersion(keyAccount) + if err != nil { + cmn.Exit(err.Error()) + } + return app +} +``` + +The main difference here, compared to `App1`, is that we use a second capability +key for a second store that is *only* passed to a second handler, the +`handleMsgIssue`. The first `handleMsgSend` has no access to this second store and cannot read or write to +it, ensuring a strong separation of concerns. + +Note also that we do not need to use `SetTxDecoder` here - now that we're using +Amino, we simply create a codec, register our types on the codec, and pass the +codec into `NewBaseApp`. The SDK takes care of the rest for us! + +## Conclusion + +We've expanded on our first app by adding a new message type for issuing coins, +and by checking signatures. We learned how to use Amino for decoding into +interface types, allowing us to support multiple Msg types, and we learned how +to use the AnteHandler to validate transactions. + +Unfortunately, our application is still insecure, because any valid transaction +can be replayed multiple times to drain someones account! Besides, validating +signatures and preventing replays aren't things developers should have to think +about. + +In the next section, we introduce the built-in SDK modules `auth` and `bank`, +which respectively provide secure implementations for all our transaction authentication +and coin transfering needs. diff --git a/docs/core/app3.md b/docs/core/app3.md new file mode 100644 index 000000000..459f48c83 --- /dev/null +++ b/docs/core/app3.md @@ -0,0 +1,370 @@ +# Modules + +In the previous app, we introduced a new `Msg` type and used Amino to encode +transactions. We also introduced additional data to the `Tx`, and used a simple +`AnteHandler` to validate it. + +Here, in `App3`, we introduce two built-in SDK modules to +replace the `Msg`, `Tx`, `Handler`, and `AnteHandler` implementations we've seen +so far: `x/auth` and `x/bank`. + +The `x/auth` module implements `Tx` and `AnteHandler` - it has everything we need to +authenticate transactions. It also includes a new `Account` type that simplifies +working with accounts in the store. + +The `x/bank` module implements `Msg` and `Handler` - it has everything we need +to transfer coins between accounts. + +Here, we'll introduce the important types from `x/auth` and `x/bank`, and use +them to build `App3`, our shortest app yet. The complete code can be found in +[app3.go](examples/app3.go), and at the end of this section. + +For more details, see the +[x/auth](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth) and +[x/bank](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank) API documentation. + +## Accounts + +The `x/auth` module defines a model of accounts much like Ethereum. +In this model, an account contains: + +- Address for identification +- PubKey for authentication +- AccountNumber to prune empty accounts +- Sequence to prevent transaction replays +- Coins to carry a balance + +Note that the `AccountNumber` is a unique number that is assigned when the account is +created, and the `Sequence` is incremented by one every time a transaction is +sent from the account. + +### Account + +The `Account` interface captures this account model with getters and setters: + +```go +// Account is a standard account using a sequence number for replay protection +// and a pubkey for authentication. +type Account interface { + GetAddress() sdk.AccAddress + SetAddress(sdk.AccAddress) error // errors if already set. + + GetPubKey() crypto.PubKey // can return nil. + SetPubKey(crypto.PubKey) error + + GetAccountNumber() int64 + SetAccountNumber(int64) error + + GetSequence() int64 + SetSequence(int64) error + + GetCoins() sdk.Coins + SetCoins(sdk.Coins) error +} +``` + +Note this is a low-level interface - it allows any of the fields to be over +written. As we'll soon see, access can be restricted using the `Keeper` +paradigm. + +### BaseAccount + +The default implementation of `Account` is the `BaseAccount`: + +```go +// BaseAccount - base account structure. +// Extend this by embedding this in your AppAccount. +// See the examples/basecoin/types/account.go for an example. +type BaseAccount struct { + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` + PubKey crypto.PubKey `json:"public_key"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` +} +``` + +It simply contains a field for each of the methods. + +### AccountMapper + +In previous apps using our `appAccount`, we handled +marshaling/unmarshaling the account from the store ourselves, by performing +operations directly on the KVStore. But unrestricted access to a KVStore isn't really the interface we want +to work with in our applications. In the SDK, we use the term `Mapper` to refer +to an abstaction over a KVStore that handles marshalling and unmarshalling a +particular data type to and from the underlying store. + +The `x/auth` module provides an `AccountMapper` that allows us to get and +set `Account` types to the store. Note the benefit of using the `Account` +interface here - developers can implement their own account type that extends +the `BaseAccount` to store additional data without requiring another lookup from +the store. + +Creating an AccountMapper is easy - we just need to specify a codec, a +capability key, and a prototype of the object being encoded + +```go +accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount) +``` + +Then we can get, modify, and set accounts. For instance, we could double the +amount of coins in an account: + +```go +acc := accountMapper.GetAccount(ctx, addr) +acc.SetCoins(acc.Coins.Plus(acc.Coins)) +accountMapper.SetAccount(ctx, addr) +``` + +Note that the `AccountMapper` takes a `Context` as the first argument, and will +load the KVStore from there using the capability key it was granted on creation. + +Also note that you must explicitly call `SetAccount` after mutating an account +for the change to persist! + +See the [AccountMapper API +docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth#AccountMapper) for more information. + +## StdTx + +Now that we have a native model for accounts, it's time to introduce the native +`Tx` type, the `auth.StdTx`: + +```go +// StdTx is a standard way to wrap a Msg with Fee and Signatures. +// NOTE: the first signature is the FeePayer (Signatures must not be nil). +type StdTx struct { + Msgs []sdk.Msg `json:"msg"` + Fee StdFee `json:"fee"` + Signatures []StdSignature `json:"signatures"` + Memo string `json:"memo"` +} +``` + +This is the standard form for a transaction in the SDK. Besides the Msgs, it +includes: + +- a fee to be paid by the first signer +- replay protecting nonces in the signature +- a memo of prunable additional data + +Details on how these components are validated is provided under +[auth.AnteHandler](#antehandler) below. + +The standard form for signatures is `StdSignature`: + +```go +// StdSignature wraps the Signature and includes counters for replay protection. +// It also includes an optional public key, which must be provided at least in +// the first transaction made by the account. +type StdSignature struct { + crypto.PubKey `json:"pub_key"` // optional + crypto.Signature `json:"signature"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` +} +``` + +The signature includes both an `AccountNumber` and a `Sequence`. +The `Sequence` must match the one in the +corresponding account when the transaction is processed, and will increment by +one with every transaction. This prevents the same +transaction from being replayed multiple times, resolving the insecurity that +remains in App2. + +The `AccountNumber` is also for replay protection - it allows accounts to be +deleted from the store when they run out of accounts. If an account receives +coins after it is deleted, the account will be re-created, with the Sequence +reset to 0, but a new AccountNumber. If it weren't for the AccountNumber, the +last sequence of transactions made by the account before it was deleted could be +replayed! + +Finally, the standard form for a transaction fee is `StdFee`: + +```go +// StdFee includes the amount of coins paid in fees and the maximum +// gas to be used by the transaction. The ratio yields an effective "gasprice", +// which must be above some miminum to be accepted into the mempool. +type StdFee struct { + Amount sdk.Coins `json:"amount"` + Gas int64 `json:"gas"` +} +``` + +The fee must be paid by the first signer. This allows us to quickly check if the +transaction fee can be paid, and reject the transaction if not. + +## Signing + +The `StdTx` supports multiple messages and multiple signers. +To sign the transaction, each signer must collect the following information: + +- the ChainID +- the AccountNumber and Sequence for the given signer's account (from the + blockchain) +- the transaction fee +- the list of transaction messages +- an optional memo + +Then they can compute the transaction bytes to sign using the +`auth.StdSignBytes` function: + +```go +bytesToSign := StdSignBytes(chainID, accNum, accSequence, fee, msgs, memo) +``` + +Note these bytes are unique for each signer, as they depend on the particular +signers AccountNumber, Sequence, and optional memo. To facilitate easy +inspection before signing, the bytes are actually just a JSON encoded form of +all the relevant information. + +## AnteHandler + +As we saw in `App2`, we can use an `AnteHandler` to authenticate transactions +before we handle any of their internal messages. While previously we implemented +our own simple `AnteHandler`, the `x/auth` module provides a much more advanced +one that uses `AccountMapper` and works with `StdTx`: + +```go +app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper)) +``` + +The AnteHandler provided by `x/auth` enforces the following rules: + +- the memo must not be too big +- the right number of signatures must be provided (one for each unique signer + returned by `msg.GetSigner` for each `msg`) +- any account signing for the first-time must include a public key in the + StdSignature +- the signatures must be valid when authenticated in the same order as specified + by the messages + +Note that validating +signatures requires checking that the correct account number and sequence was +used by each signer, as this information is required in the `StdSignBytes`. + +If any of the above are not satisfied, the AnteHandelr returns an error. + +If all of the above verifications pass, the AnteHandler makes the following +changes to the state: + +- increment account sequence by one for all signers +- set the pubkey in the account for any first-time signers +- deduct the fee from the first signer's account + +Recall that incrementing the `Sequence` prevents "replay attacks" where +the same message could be executed over and over again. + +The PubKey is required for signature verification, but it is only required in +the StdSignature once. From that point on, it will be stored in the account. + +The fee is paid by the first address returned by `msg.GetSigners()` for the first `Msg`, +as provided by the `FeePayer(tx Tx) sdk.AccAddress` function. + +## CoinKeeper + +Now that we've seen the `auth.AccountMapper` and how its used to build a +complete AnteHandler, it's time to look at how to build higher-level +abstractions for taking action on accounts. + +Earlier, we noted that `Mappers` are abstactions over KVStores that handle +marshalling and unmarshalling data types to and from underlying stores. +We can build another abstraction on top of `Mappers` that we call `Keepers`, +which expose only limitted functionality on the underlying types stored by the `Mapper`. + +For instance, the `x/bank` module defines the canonical versions of `MsgSend` +and `MsgIssue` for the SDK, as well as a `Handler` for processing them. However, +rather than passing a `KVStore` or even an `AccountMapper` directly to the handler, +we introduce a `bank.Keeper`, which can only be used to transfer coins in and out of accounts. +This allows us to determine up front that the only effect the bank module's +`Handler` can have on the store is to change the amount of coins in an account - +it can't increment sequence numbers, change PubKeys, or otherwise. + + +A `bank.Keeper` is easily instantiated from an `AccountMapper`: + +```go +coinKeeper = bank.NewKeeper(accountMapper) +``` + +We can then use it within a handler, instead of working directly with the +`AccountMapper`. For instance, to add coins to an account: + +```go +// Finds account with addr in AccountMapper. +// Adds coins to account's coin array. +// Sets updated account in AccountMapper +app.coinKeeper.AddCoins(ctx, addr, coins) +``` + +See the [bank.Keeper API +docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#Keeper) for the full set of methods. + +Note we can refine the `bank.Keeper` by restricting it's method set. For +instance, the +[bank.ViewKeeper](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#ViewKeeper) +is a read-only version, while the +[bank.SendKeeper](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#SendKeeper) +only executes transfers of coins from input accounts to output +accounts. + +We use this `Keeper` paradigm extensively in the SDK as the way to define what +kind of functionality each module gets access to. In particular, we try to +follow the *principle of least authority*. +Rather than providing full blown access to the `KVStore` or the `AccountMapper`, +we restrict access to a small number of functions that do very specific things. + +## App3 + +With the `auth.AccountMapper` and `bank.Keeper` in hand, +we're now ready to build `App3`. +The `x/auth` and `x/bank` modules do all the heavy lifting: + +```go +func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { + + // Create the codec with registered Msg types + cdc := NewCodec() + + // Create the base application object. + app := bapp.NewBaseApp(app3Name, cdc, logger, db) + + // Create a key for accessing the account store. + keyAccount := sdk.NewKVStoreKey("acc") + keyFees := sdk.NewKVStoreKey("fee") // TODO + + // Set various mappers/keepers to interact easily with underlying stores + accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount) + coinKeeper := bank.NewKeeper(accountMapper) + feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees) + + app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper)) + + // Register message routes. + // Note the handler gets access to + app.Router(). + AddRoute("send", bank.NewHandler(coinKeeper)) + + // Mount stores and load the latest state. + app.MountStoresIAVL(keyAccount, keyFees) + err := app.LoadLatestVersion(keyAccount) + if err != nil { + cmn.Exit(err.Error()) + } + return app +} +``` + +Note we use `bank.NewHandler`, which handles only `bank.MsgSend`, +and receives only the `bank.Keeper`. See the +[x/bank API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank) +for more details. + +## Conclusion + +Armed with native modules for authentication and coin transfer, +emboldened by the paradigm of mappers and keepers, +and ever invigorated by the desire to build secure state-machines, +we find ourselves here with a full-blown, all-checks-in-place, multi-asset +cryptocurrency - the beating heart of the Cosmos-SDK. diff --git a/docs/core/app4.md b/docs/core/app4.md new file mode 100644 index 000000000..0724e5e7a --- /dev/null +++ b/docs/core/app4.md @@ -0,0 +1,73 @@ +# ABCI + +The Application BlockChain Interface, or ABCI, is a powerfully +delineated boundary between the Cosmos-SDK and Tendermint. +It separates the logical state transition machine of your application from +its secure replication across many physical machines. + +By providing a clear, language agnostic boundary between applications and consensus, +ABCI provides tremendous developer flexibility and [support in many +languages](https://tendermint.com/ecosystem). That said, it is still quite a low-level protocol, and +requires frameworks to be built to abstract over that low-level componentry. +The Cosmos-SDK is one such framework. + +While we've already seen `DeliverTx`, the workhorse of any ABCI application, +here we will introduce the other ABCI requests sent by Tendermint, and +how we can use them to build more advanced applications. For a more complete +depiction of the ABCI and how its used, see +[the +specification](https://github.com/tendermint/tendermint/blob/master/docs/abci-spec.md) + +## InitChain + +In our previous apps, we built out all the core logic, but we never specified +how the store should be initialized. For that, we use the `app.InitChain` method, +which is called once by Tendermint the very first time the application boots up. + +The InitChain request contains a variety of Tendermint information, like the consensus +parameters and an initial validator set, but it also contains an opaque blob of +application specific bytes - typically JSON encoded. +Apps can decide what to do with all of this information by calling the +`app.SetInitChainer` method. + +For instance, let's introduce a `GenesisAccount` struct that can be JSON encoded +and part of a genesis file. Then we can populate the store with such accounts +during InitChain: + +```go +TODO +``` + +If we include a correctly formatted `GenesisAccount` in our Tendermint +genesis.json file, the store will be initialized with those accounts and they'll +be able to send transactions! + +## BeginBlock + +BeginBlock is called at the beginning of each block, before processing any +transactions with DeliverTx. +It contains information on what validators have signed. + +## EndBlock + +EndBlock is called at the end of each block, after processing all transactions +with DeliverTx. +It allows the application to return updates to the validator set. + +## Commit + +Commit is called after EndBlock. It persists the application state and returns +the Merkle root hash to be included in the next Tendermint block. The root hash +can be in Query for Merkle proofs of the state. + +## Query + +Query allows queries into the application store according to a path. + +## CheckTx + +CheckTx is used for the mempool. It only runs the AnteHandler. This is so +potentially expensive message handling doesn't begin until the transaction has +actually been committed in a block. The AnteHandler authenticates the sender and +ensures they have enough to pay the fee for the transaction. If the transaction +later fails, the sender still pays the fee. diff --git a/docs/core/app5.md b/docs/core/app5.md new file mode 100644 index 000000000..39cc7a263 --- /dev/null +++ b/docs/core/app5.md @@ -0,0 +1,72 @@ +# App5 - Basecoin + +As we've seen, the SDK provides a flexible yet comprehensive framework for building state +machines and defining their transitions, including authenticating transactions, +executing messages, controlling access to stores, and updating the validator set. + +Until now, we have focused on building only isolated ABCI applications to +demonstrate and explain the various features and flexibilities of the SDK. +Here, we'll connect our ABCI application to Tendermint so we can run a full +blockchain node, and introduce command line and HTTP interfaces for interacting with it. + +But first, let's talk about how source code should be laid out. + +## Directory Structure + +TODO + +## Tendermint Node + +Since the Cosmos-SDK is written in Go, Cosmos-SDK applications can be compiled +with Tendermint into a single binary. Of course, like any ABCI application, they +can also run as separate processes that communicate with Tendermint via socket. + +For more details on what's involved in starting a Tendermint full node, see the +[NewNode](https://godoc.org/github.com/tendermint/tendermint/node#NewNode) +function in `github.com/tendermint/tendermint/node`. + +The `server` package in the Cosmos-SDK simplifies +connecting an application with a Tendermint node. +For instance, the following `main.go` file will give us a complete full node +using the Basecoin application we built: + +```go +//TODO imports + +func main() { + cdc := app.MakeCodec() + ctx := server.NewDefaultContext() + + rootCmd := &cobra.Command{ + Use: "basecoind", + Short: "Basecoin Daemon (server)", + PersistentPreRunE: server.PersistentPreRunEFn(ctx), + } + + server.AddCommands(ctx, cdc, rootCmd, server.DefaultAppInit, + server.ConstructAppCreator(newApp, "basecoin")) + + // prepare and add flags + rootDir := os.ExpandEnv("$HOME/.basecoind") + executor := cli.PrepareBaseCmd(rootCmd, "BC", rootDir) + executor.Execute() +} + +func newApp(logger log.Logger, db dbm.DB) abci.Application { + return app.NewBasecoinApp(logger, db) +} +``` + +Note we utilize the popular [cobra library](https://github.com/spf13/cobra) +for the CLI, in concert with the [viper library](https://github.com/spf13/library) +for managing configuration. See our [cli library](https://github.com/tendermint/blob/master/tmlibs/cli/setup.go) +for more details. + +TODO: compile and run the binary + +Options for running the `basecoind` binary are effectively the same as for `tendermint`. +See [Using Tendermint](TODO) for more details. + +## Clients + +TODO diff --git a/docs/core/examples/app1.go b/docs/core/examples/app1.go new file mode 100644 index 000000000..b208f75cf --- /dev/null +++ b/docs/core/examples/app1.go @@ -0,0 +1,235 @@ +package app + +import ( + "encoding/json" + + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + bapp "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +const ( + app1Name = "App1" +) + +func NewApp1(logger log.Logger, db dbm.DB) *bapp.BaseApp { + + cdc := wire.NewCodec() + + // Create the base application object. + app := bapp.NewBaseApp(app1Name, cdc, logger, db) + + // Create a key for accessing the account store. + keyAccount := sdk.NewKVStoreKey("acc") + + // Determine how transactions are decoded. + app.SetTxDecoder(txDecoder) + + // Register message routes. + // Note the handler gets access to the account store. + app.Router(). + AddRoute("send", handleMsgSend(keyAccount)) + + // Mount stores and load the latest state. + app.MountStoresIAVL(keyAccount) + err := app.LoadLatestVersion(keyAccount) + if err != nil { + cmn.Exit(err.Error()) + } + return app +} + +//------------------------------------------------------------------ +// Msg + +// MsgSend implements sdk.Msg +var _ sdk.Msg = MsgSend{} + +// MsgSend to send coins from Input to Output +type MsgSend struct { + From sdk.AccAddress `json:"from"` + To sdk.AccAddress `json:"to"` + Amount sdk.Coins `json:"amount"` +} + +// NewMsgSend +func NewMsgSend(from, to sdk.AccAddress, amt sdk.Coins) MsgSend { + return MsgSend{from, to, amt} +} + +// Implements Msg. +func (msg MsgSend) Type() string { return "send" } + +// Implements Msg. Ensure the addresses are good and the +// amount is positive. +func (msg MsgSend) ValidateBasic() sdk.Error { + if len(msg.From) == 0 { + return sdk.ErrInvalidAddress("From address is empty") + } + if len(msg.To) == 0 { + return sdk.ErrInvalidAddress("To address is empty") + } + if !msg.Amount.IsPositive() { + return sdk.ErrInvalidCoins("Amount is not positive") + } + return nil +} + +// Implements Msg. JSON encode the message. +func (msg MsgSend) GetSignBytes() []byte { + bz, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(bz) +} + +// Implements Msg. Return the signer. +func (msg MsgSend) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.From} +} + +// Returns the sdk.Tags for the message +func (msg MsgSend) Tags() sdk.Tags { + return sdk.NewTags("sender", []byte(msg.From.String())). + AppendTag("receiver", []byte(msg.To.String())) +} + +//------------------------------------------------------------------ +// Handler for the message + +// Handle MsgSend. +// NOTE: msg.From, msg.To, and msg.Amount were already validated +// in ValidateBasic(). +func handleMsgSend(key *sdk.KVStoreKey) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + sendMsg, ok := msg.(MsgSend) + if !ok { + // Create custom error message and return result + // Note: Using unreserved error codespace + return sdk.NewError(2, 1, "MsgSend is malformed").Result() + } + + // Load the store. + store := ctx.KVStore(key) + + // Debit from the sender. + if res := handleFrom(store, sendMsg.From, sendMsg.Amount); !res.IsOK() { + return res + } + + // Credit the receiver. + if res := handleTo(store, sendMsg.To, sendMsg.Amount); !res.IsOK() { + return res + } + + // Return a success (Code 0). + // Add list of key-value pair descriptors ("tags"). + return sdk.Result{ + Tags: sendMsg.Tags(), + } + } +} + +// Convenience Handlers +func handleFrom(store sdk.KVStore, from sdk.AccAddress, amt sdk.Coins) sdk.Result { + // Get sender account from the store. + accBytes := store.Get(from) + if accBytes == nil { + // Account was not added to store. Return the result of the error. + return sdk.NewError(2, 101, "Account not added to store").Result() + } + + // Unmarshal the JSON account bytes. + var acc appAccount + err := json.Unmarshal(accBytes, &acc) + if err != nil { + // InternalError + return sdk.ErrInternal("Error when deserializing account").Result() + } + + // Deduct msg amount from sender account. + senderCoins := acc.Coins.Minus(amt) + + // If any coin has negative amount, return insufficient coins error. + if !senderCoins.IsNotNegative() { + return sdk.ErrInsufficientCoins("Insufficient coins in account").Result() + } + + // Set acc coins to new amount. + acc.Coins = senderCoins + + // Encode sender account. + accBytes, err = json.Marshal(acc) + if err != nil { + return sdk.ErrInternal("Account encoding error").Result() + } + + // Update store with updated sender account + store.Set(from, accBytes) + return sdk.Result{} +} + +func handleTo(store sdk.KVStore, to sdk.AccAddress, amt sdk.Coins) sdk.Result { + // Add msg amount to receiver account + accBytes := store.Get(to) + var acc appAccount + if accBytes == nil { + // Receiver account does not already exist, create a new one. + acc = appAccount{} + } else { + // Receiver account already exists. Retrieve and decode it. + err := json.Unmarshal(accBytes, &acc) + if err != nil { + return sdk.ErrInternal("Account decoding error").Result() + } + } + + // Add amount to receiver's old coins + receiverCoins := acc.Coins.Plus(amt) + + // Update receiver account + acc.Coins = receiverCoins + + // Encode receiver account + accBytes, err := json.Marshal(acc) + if err != nil { + return sdk.ErrInternal("Account encoding error").Result() + } + + // Update store with updated receiver account + store.Set(to, accBytes) + return sdk.Result{} +} + +// Simple account struct +type appAccount struct { + Coins sdk.Coins `json:"coins"` +} + +//------------------------------------------------------------------ +// Tx + +// Simple tx to wrap the Msg. +type app1Tx struct { + MsgSend +} + +// This tx only has one Msg. +func (tx app1Tx) GetMsgs() []sdk.Msg { + return []sdk.Msg{tx.MsgSend} +} + +// JSON decode MsgSend. +func txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) { + var tx app1Tx + err := json.Unmarshal(txBytes, &tx) + if err != nil { + return nil, sdk.ErrTxDecode(err.Error()) + } + return tx, nil +} diff --git a/docs/core/examples/app2.go b/docs/core/examples/app2.go new file mode 100644 index 000000000..4c20c17c3 --- /dev/null +++ b/docs/core/examples/app2.go @@ -0,0 +1,231 @@ +package app + +import ( + "bytes" + "encoding/json" + "fmt" + + "github.com/tendermint/tendermint/crypto" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + bapp "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +const ( + app2Name = "App2" +) + +var ( + issuer = crypto.GenPrivKeyEd25519().PubKey().Address() +) + +func NewCodec() *wire.Codec { + cdc := wire.NewCodec() + cdc.RegisterInterface((*sdk.Msg)(nil), nil) + cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil) + cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil) + return cdc +} + +func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp { + + cdc := NewCodec() + + // Create the base application object. + app := bapp.NewBaseApp(app2Name, cdc, logger, db) + + // Create a key for accessing the account store. + keyAccount := sdk.NewKVStoreKey("acc") + // Create a key for accessing the issue store. + keyIssue := sdk.NewKVStoreKey("issue") + + // set antehandler function + app.SetAnteHandler(antehandler) + + // Register message routes. + // Note the handler gets access to the account store. + app.Router(). + AddRoute("send", handleMsgSend(keyAccount)). + AddRoute("issue", handleMsgIssue(keyAccount, keyIssue)) + + // Mount stores and load the latest state. + app.MountStoresIAVL(keyAccount, keyIssue) + err := app.LoadLatestVersion(keyAccount) + if err != nil { + cmn.Exit(err.Error()) + } + return app +} + +//------------------------------------------------------------------ +// Msgs + +// MsgIssue to allow a registered issuer +// to issue new coins. +type MsgIssue struct { + Issuer sdk.AccAddress + Receiver sdk.AccAddress + Coin sdk.Coin +} + +// Implements Msg. +func (msg MsgIssue) Type() string { return "issue" } + +// Implements Msg. Ensures addresses are valid and Coin is positive +func (msg MsgIssue) ValidateBasic() sdk.Error { + if len(msg.Issuer) == 0 { + return sdk.ErrInvalidAddress("Issuer address cannot be empty") + } + + if len(msg.Receiver) == 0 { + return sdk.ErrInvalidAddress("Receiver address cannot be empty") + } + + // Cannot issue zero or negative coins + if !msg.Coin.IsPositive() { + return sdk.ErrInvalidCoins("Cannot issue 0 or negative coin amounts") + } + + return nil +} + +// Implements Msg. Get canonical sign bytes for MsgIssue +func (msg MsgIssue) GetSignBytes() []byte { + bz, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(bz) +} + +// Implements Msg. Return the signer. +func (msg MsgIssue) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Issuer} +} + +// Returns the sdk.Tags for the message +func (msg MsgIssue) Tags() sdk.Tags { + return sdk.NewTags("issuer", []byte(msg.Issuer.String())). + AppendTag("receiver", []byte(msg.Receiver.String())) +} + +//------------------------------------------------------------------ +// Handler for the message + +// Handle MsgIssue. +func handleMsgIssue(keyIssue *sdk.KVStoreKey, keyAcc *sdk.KVStoreKey) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + issueMsg, ok := msg.(MsgIssue) + if !ok { + return sdk.NewError(2, 1, "MsgIssue is malformed").Result() + } + + // Retrieve stores + issueStore := ctx.KVStore(keyIssue) + accStore := ctx.KVStore(keyAcc) + + // Handle updating coin info + if res := handleIssuer(issueStore, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() { + return res + } + + // Issue coins to receiver using previously defined handleTo function + if res := handleTo(accStore, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}); !res.IsOK() { + return res + } + + return sdk.Result{ + // Return result with Issue msg tags + Tags: issueMsg.Tags(), + } + } +} + +func handleIssuer(store sdk.KVStore, issuer sdk.AccAddress, coin sdk.Coin) sdk.Result { + // the issuer address is stored directly under the coin denomination + denom := []byte(coin.Denom) + infoBytes := store.Get(denom) + if infoBytes == nil { + return sdk.ErrInvalidCoins(fmt.Sprintf("Unknown coin type %s", coin.Denom)).Result() + } + + var coinInfo coinInfo + err := json.Unmarshal(infoBytes, &coinInfo) + if err != nil { + return sdk.ErrInternal("Error when deserializing coinInfo").Result() + } + + // Msg Issuer is not authorized to issue these coins + if !bytes.Equal(coinInfo.Issuer, issuer) { + return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result() + } + + return sdk.Result{} +} + +// coinInfo stores meta data about a coin +type coinInfo struct { + Issuer sdk.AccAddress `json:"issuer"` +} + +//------------------------------------------------------------------ +// Tx + +// Simple tx to wrap the Msg. +type app2Tx struct { + sdk.Msg + Signatures []auth.StdSignature +} + +// This tx only has one Msg. +func (tx app2Tx) GetMsgs() []sdk.Msg { + return []sdk.Msg{tx.Msg} +} + +func (tx app2Tx) GetSignatures() []auth.StdSignature { + return tx.Signatures +} + +//------------------------------------------------------------------ + +// Simple anteHandler that ensures msg signers have signed. +// Provides no replay protection. +func antehandler(ctx sdk.Context, tx sdk.Tx) (_ sdk.Context, _ sdk.Result, abort bool) { + appTx, ok := tx.(app2Tx) + if !ok { + // set abort boolean to true so that we don't continue to process failed tx + return ctx, sdk.ErrTxDecode("Tx must be of format app2Tx").Result(), true + } + + // expect only one msg in app2Tx + msg := tx.GetMsgs()[0] + + signerAddrs := msg.GetSigners() + + if len(signerAddrs) != len(appTx.GetSignatures()) { + return ctx, sdk.ErrUnauthorized("Number of signatures do not match required amount").Result(), true + } + + signBytes := msg.GetSignBytes() + for i, addr := range signerAddrs { + sig := appTx.GetSignatures()[i] + + // check that submitted pubkey belongs to required address + if !bytes.Equal(sig.PubKey.Address(), addr) { + return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true + } + + // check that signature is over expected signBytes + if !sig.PubKey.VerifyBytes(signBytes, sig.Signature) { + return ctx, sdk.ErrUnauthorized("Signature verification failed").Result(), true + } + } + + // authentication passed, app to continue processing by sending msg to handler + return ctx, sdk.Result{}, false +} diff --git a/docs/core/examples/app3.go b/docs/core/examples/app3.go new file mode 100644 index 000000000..853ad687e --- /dev/null +++ b/docs/core/examples/app3.go @@ -0,0 +1,49 @@ +package app + +import ( + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + bapp "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" +) + +const ( + app3Name = "App3" +) + +func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { + + // Create the codec with registered Msg types + cdc := NewCodec() + + // Create the base application object. + app := bapp.NewBaseApp(app3Name, cdc, logger, db) + + // Create a key for accessing the account store. + keyAccount := sdk.NewKVStoreKey("acc") + keyFees := sdk.NewKVStoreKey("fee") // TODO + + // Set various mappers/keepers to interact easily with underlying stores + accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount) + coinKeeper := bank.NewKeeper(accountMapper) + feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees) + + app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper)) + + // Register message routes. + // Note the handler gets access to + app.Router(). + AddRoute("send", bank.NewHandler(coinKeeper)) + + // Mount stores and load the latest state. + app.MountStoresIAVL(keyAccount, keyFees) + err := app.LoadLatestVersion(keyAccount) + if err != nil { + cmn.Exit(err.Error()) + } + return app +} diff --git a/docs/core/examples/app4.go b/docs/core/examples/app4.go new file mode 100644 index 000000000..a8ef37cee --- /dev/null +++ b/docs/core/examples/app4.go @@ -0,0 +1,100 @@ +package app + +import ( + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + bapp "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" +) + +const ( + app4Name = "App4" +) + +func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp { + + cdc := NewCodec() + + // Create the base application object. + app := bapp.NewBaseApp(app3Name, cdc, logger, db) + + // Create a key for accessing the account store. + keyAccount := sdk.NewKVStoreKey("acc") + + // Set various mappers/keepers to interact easily with underlying stores + accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount) + coinKeeper := bank.NewKeeper(accountMapper) + + // TODO + keyFees := sdk.NewKVStoreKey("fee") + feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees) + + app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper)) + + // Set InitChainer + app.SetInitChainer(NewInitChainer(cdc, accountMapper)) + + // Register message routes. + // Note the handler gets access to the account store. + app.Router(). + AddRoute("send", bank.NewHandler(coinKeeper)) + + // Mount stores and load the latest state. + app.MountStoresIAVL(keyAccount, keyFees) + err := app.LoadLatestVersion(keyAccount) + if err != nil { + cmn.Exit(err.Error()) + } + return app +} + +// Application state at Genesis has accounts with starting balances +type GenesisState struct { + Accounts []*GenesisAccount `json:"accounts"` +} + +// GenesisAccount doesn't need pubkey or sequence +type GenesisAccount struct { + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` +} + +// Converts GenesisAccount to auth.BaseAccount for storage in account store +func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount, err error) { + baseAcc := auth.BaseAccount{ + Address: ga.Address, + Coins: ga.Coins.Sort(), + } + return &baseAcc, nil +} + +// InitChainer will set initial balances for accounts as well as initial coin metadata +// MsgIssue can no longer be used to create new coin +func NewInitChainer(cdc *wire.Codec, accountMapper auth.AccountMapper) sdk.InitChainer { + return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + stateJSON := req.AppStateBytes + + genesisState := new(GenesisState) + err := cdc.UnmarshalJSON(stateJSON, genesisState) + if err != nil { + panic(err) + } + + for _, gacc := range genesisState.Accounts { + acc, err := gacc.ToAccount() + if err != nil { + panic(err) + } + acc.AccountNumber = accountMapper.GetNextAccountNumber(ctx) + accountMapper.SetAccount(ctx, acc) + } + + return abci.ResponseInitChain{} + } +} diff --git a/docs/core/intro.md b/docs/core/intro.md new file mode 100644 index 000000000..c1347260d --- /dev/null +++ b/docs/core/intro.md @@ -0,0 +1,16 @@ +# Introduction + +Welcome to the Cosmos-SDK Core Documentation. + +Here you will learn how to use the Cosmos-SDK to build Basecoin, a +complete proof-of-stake cryptocurrency system + +We proceed through a series of increasingly advanced and complete implementations of +the Basecoin application, with each implementation showcasing a new component of +the SDK: + +- App1 - The Basics - Messages, Stores, Handlers, BaseApp +- App2 - Transactions - Amino and AnteHandler +- App3 - Modules - `x/auth` and `x/bank` +- App4 - Validator Set Changes - Change the Tendermint validator set +- App5 - Basecoin - Bringing it all together diff --git a/docs/core/multistore.md b/docs/core/multistore.md new file mode 100644 index 000000000..cd733ed21 --- /dev/null +++ b/docs/core/multistore.md @@ -0,0 +1,72 @@ +# MultiStore + +TODO: reconcile this with everything ... would be nice to have this explanation +somewhere but where does it belong ? So far we've already showed how to use it +all by creating KVStore keys and calling app.MountStoresIAVL ! + + +The Cosmos-SDK provides a special Merkle database called a `MultiStore` to be used for all application +storage. The MultiStore consists of multiple Stores that must be mounted to the +MultiStore during application setup. Stores are mounted to the MultiStore using a capabilities key, +ensuring that only parts of the program with access to the key can access the store. + +The goals of the MultiStore are as follows: + +- Enforce separation of concerns at the storage level +- Restrict access to storage using capabilities +- Support multiple Store implementations in a single MultiStore, for instance the Tendermint IAVL tree and + the Ethereum Patricia Trie +- Merkle proofs for various queries (existence, absence, range, etc.) on current and retained historical state +- Allow for iteration within Stores +- Provide caching for intermediate state during execution of blocks and transactions (including for iteration) + +- Support historical state pruning and snapshotting + +Currently, all Stores in the MultiStore must satisfy the `KVStore` interface, +which defines a simple key-value store. In the future, +we may support more kinds of stores, such as a HeapStore +or a NDStore for multidimensional storage. + +## Mounting Stores + +Stores are mounted during application setup. To mount some stores, first create +their capability-keys: + +``` +fooKey := sdk.NewKVStoreKey("foo") +barKey := sdk.NewKVStoreKey("bar") +catKey := sdk.NewKVStoreKey("cat") +``` + +Stores are mounted directly on the BaseApp. +They can either specify their own database, or share the primary one already +passed to the BaseApp. + +In this example, `foo` and `bar` will share the primary database, while `cat` will +specify its own: + +``` +catDB := dbm.NewMemDB() +app.MountStore(fooKey, sdk.StoreTypeIAVL) +app.MountStore(barKey, sdk.StoreTypeIAVL) +app.MountStoreWithDB(catKey, sdk.StoreTypeIAVL, catDB) +``` + +## Accessing Stores + +In the Cosmos-SDK, the only way to access a store is with a capability-key. +Only modules given explicit access to the capability-key will +be able to access the corresponding store. Access to the MultiStore is mediated +through the `Context`. + +## Notes + +TODO: move this to the spec + +In the example above, all IAVL nodes (inner and leaf) will be stored +in mainDB with the prefix of "s/k:foo/" and "s/k:bar/" respectively, +thus sharing the mainDB. All IAVL nodes (inner and leaf) for the +cat KVStore are stored separately in catDB with the prefix of +"s/\_/". The "s/k:KEY/" and "s/\_/" prefixes are there to +disambiguate store items from other items of non-storage concern. + diff --git a/docs/guide.md b/docs/guide.md deleted file mode 100644 index db5ba392e..000000000 --- a/docs/guide.md +++ /dev/null @@ -1,320 +0,0 @@ -## Introduction - -If you want to see some examples, take a look at the [examples/basecoin](/examples/basecoin) directory. - -## Design Goals - -The design of the Cosmos SDK is based on the principles of "capabilities systems". - -## Capabilities systems - -### Need for module isolation -### Capability is implied permission -### TODO Link to thesis - -## Tx & Msg - -The SDK distinguishes between transactions (Tx) and messages -(Msg). A Tx is a Msg wrapped with authentication and fee data. - -### Messages - -Users can create messages containing arbitrary information by -implementing the `Msg` interface: - -```go -type Msg interface { - - // Return the message type. - // Must be alphanumeric or empty. - Type() string - - // Get the canonical byte representation of the Msg. - GetSignBytes() []byte - - // ValidateBasic does a simple validation check that - // doesn't require access to any other information. - ValidateBasic() error - - // Signers returns the addrs of signers that must sign. - // CONTRACT: All signatures must be present to be valid. - // CONTRACT: Returns addrs in some deterministic order. - GetSigners() []Address -} - -``` - -Messages must specify their type via the `Type()` method. The type should -correspond to the messages handler, so there can be many messages with the same -type. - -Messages must also specify how they are to be authenticated. The `GetSigners()` -method return a list of addresses that must sign the message, while the -`GetSignBytes()` method returns the bytes that must be signed for a signature -to be valid. - -Addresses in the SDK are arbitrary byte arrays that are hex-encoded when -displayed as a string or rendered in JSON. - -Messages can specify basic self-consistency checks using the `ValidateBasic()` -method to enforce that message contents are well formed before any actual logic -begins. - -For instance, the `Basecoin` message types are defined in `x/bank/tx.go`: - -```go -type MsgSend struct { - Inputs []Input `json:"inputs"` - Outputs []Output `json:"outputs"` -} - -type MsgIssue struct { - Banker sdk.Address `json:"banker"` - Outputs []Output `json:"outputs"` -} -``` - -Each specifies the addresses that must sign the message: - -```go -func (msg MsgSend) GetSigners() []sdk.Address { - addrs := make([]sdk.Address, len(msg.Inputs)) - for i, in := range msg.Inputs { - addrs[i] = in.Address - } - return addrs -} - -func (msg MsgIssue) GetSigners() []sdk.Address { - return []sdk.Address{msg.Banker} -} -``` - -### Transactions - -A transaction is a message with additional information for authentication: - -```go -type Tx interface { - - GetMsg() Msg - - // Signatures returns the signature of signers who signed the Msg. - // CONTRACT: Length returned is same as length of - // pubkeys returned from MsgKeySigners, and the order - // matches. - // CONTRACT: If the signature is missing (ie the Msg is - // invalid), then the corresponding signature is - // .Empty(). - GetSignatures() []StdSignature -} -``` - -The `tx.GetSignatures()` method returns a list of signatures, which must match -the list of addresses returned by `tx.Msg.GetSigners()`. The signatures come in -a standard form: - -```go -type StdSignature struct { - crypto.PubKey // optional - crypto.Signature - Sequence int64 -} -``` - -It contains the signature itself, as well as the corresponding account's -sequence number. The sequence number is expected to increment every time a -message is signed by a given account. This prevents "replay attacks", where -the same message could be executed over and over again. - -The `StdSignature` can also optionally include the public key for verifying the -signature. An application can store the public key for each address it knows -about, making it optional to include the public key in the transaction. In the -case of Basecoin, the public key only needs to be included in the first -transaction send by a given account - after that, the public key is forever -stored by the application and can be left out of transactions. - -The address responsible for paying the transactions fee is the first address -returned by msg.GetSigners(). The convenience function `FeePayer(tx Tx)` is provided -to return this. - -The standard way to create a transaction from a message is to use the `StdTx`: - -```go -type StdTx struct { - Msg - Signatures []StdSignature -} -``` - -### Encoding and Decoding Transactions - -Messages and transactions are designed to be generic enough for developers to -specify their own encoding schemes. This enables the SDK to be used as the -framwork for constructing already specified cryptocurrency state machines, for -instance Ethereum. - -When initializing an application, a developer must specify a `TxDecoder` -function which determines how an arbitrary byte array should be unmarshalled -into a `Tx`: - -```go -type TxDecoder func(txBytes []byte) (Tx, error) -``` - -In `Basecoin`, we use the Tendermint wire format and the `go-amino` library for -encoding and decoding all message types. The `go-amino` library has the nice -property that it can unmarshal into interface types, but it requires the -relevant types to be registered ahead of type. Registration happens on a -`Codec` object, so as not to taint the global name space. - -For instance, in `Basecoin`, we wish to register the `MsgSend` and `MsgIssue` -types: - -```go -cdc.RegisterInterface((*sdk.Msg)(nil), nil) -cdc.RegisterConcrete(bank.MsgSend{}, "cosmos-sdk/MsgSend", nil) -cdc.RegisterConcrete(bank.MsgIssue{}, "cosmos-sdk/MsgIssue", nil) -``` - -Note how each concrete type is given a name - these name determine the type's -unique "prefix bytes" during encoding. A registered type will always use the -same prefix-bytes, regardless of what interface it is satisfying. For more -details, see the [go-amino documentation](https://github.com/tendermint/go-amino/blob/develop). - - -## Storage - -### MultiStore - -MultiStore is like a root filesystem of an operating system, except -all the entries are fully Merkleized. You mount onto a MultiStore -any number of Stores. Currently only KVStores are supported, but in -the future we may support more kinds of stores, such as a HeapStore -or a NDStore for multidimensional storage. - -The MultiStore as well as all mounted stores provide caching (aka -cache-wrapping) for intermediate state (aka software transactional -memory) during the execution of transactions. In the case of the -KVStore, this also works for iterators. For example, after running -the app's AnteHandler, the MultiStore is cache-wrapped (and each -store is also cache-wrapped) so that should processing of the -transaction fail, at least the transaction fees are paid and -sequence incremented. - -The MultiStore as well as all stores support (or will support) -historical state pruning and snapshotting and various kinds of -queries with proofs. - -### KVStore - -Here we'll focus on the IAVLStore, which is a kind of KVStore. - -IAVLStore is a fast balanced dynamic Merkle store that also supports -iteration, and of course cache-wrapping, state pruning, and various -queries with proofs, such as proofs of existence, absence, range, -and so on. - -Here's how you mount them to a MultiStore. - -```go -mainDB, catDB := dbm.NewMemDB(), dbm.NewMemDB() -fooKey := sdk.NewKVStoreKey("foo") -barKey := sdk.NewKVStoreKey("bar") -catKey := sdk.NewKVStoreKey("cat") -ms := NewCommitMultiStore(mainDB) -ms.MountStoreWithDB(fooKey, sdk.StoreTypeIAVL, nil) -ms.MountStoreWithDB(barKey, sdk.StoreTypeIAVL, nil) -ms.MountStoreWithDB(catKey, sdk.StoreTypeIAVL, catDB) -``` - -In the example above, all IAVL nodes (inner and leaf) will be stored -in mainDB with the prefix of "s/k:foo/" and "s/k:bar/" respectively, -thus sharing the mainDB. All IAVL nodes (inner and leaf) for the -cat KVStore are stored separately in catDB with the prefix of -"s/\_/". The "s/k:KEY/" and "s/\_/" prefixes are there to -disambiguate store items from other items of non-storage concern. - - -## Context - -The SDK uses a `Context` to propogate common information across functions. The -`Context` is modeled after the Golang `context.Context` object, which has -become ubiquitous in networking middleware and routing applications as a means -to easily propogate request context through handler functions. - -The main information stored in the `Context` includes the application -MultiStore (see below), the last block header, and the transaction bytes. -Effectively, the context contains all data that may be necessary for processing -a transaction. - -Many methods on SDK objects receive a context as the first argument. - -## Handler - -Transaction processing in the SDK is defined through `Handler` functions: - -```go -type Handler func(ctx Context, tx Tx) Result -``` - -A handler takes a context and a transaction and returns a result. All -information necessary for processing a transaction should be available in the -context. - -While the context holds the entire application state (all referenced from the -root MultiStore), a particular handler only needs a particular kind of access -to a particular store (or two or more). Access to stores is managed using -capabilities keys and mappers. When a handler is initialized, it is passed a -key or mapper that gives it access to the relevant stores. - -```go -// File: cosmos-sdk/examples/basecoin/app/init_stores.go -app.BaseApp.MountStore(app.capKeyMainStore, sdk.StoreTypeIAVL) -app.accountMapper = auth.NewAccountMapper( - app.capKeyMainStore, // target store - &types.AppAccount{}, // prototype -) - -// File: cosmos-sdk/examples/basecoin/app/init_handlers.go -app.router.AddRoute("bank", bank.NewHandler(app.accountMapper)) - -// File: cosmos-sdk/x/bank/handler.go -// NOTE: Technically, NewHandler only needs a CoinMapper -func NewHandler(am sdk.AccountMapper) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - cm := CoinMapper{am} - ... - } -} -``` - -## AnteHandler - -### Handling Fee payment -### Handling Authentication - -## Accounts and x/auth - -### sdk.Account -### auth.BaseAccount -### auth.AccountMapper - -## Wire codec - -### Why another codec? -### vs encoding/json -### vs protobuf - -## KVStore example - -## Basecoin example - -The quintessential SDK application is Basecoin - a simple -multi-asset cryptocurrency. Basecoin consists of a set of -accounts stored in a Merkle tree, where each account may have -many coins. There are two message types: MsgSend and MsgIssue. -MsgSend allows coins to be sent around, while MsgIssue allows a -set of predefined users to issue new coins. - -## Conclusion diff --git a/docs/index.rst b/docs/index.rst index 1bbe94952..31fdbfca6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,4 +9,3 @@ Welcome to the Cosmos SDK! This location for our documentation has been deprecated, please see: - https://cosmos.network/docs/ -- diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 000000000..bafdfc4bd --- /dev/null +++ b/docs/install.md @@ -0,0 +1,59 @@ +# Install + +The fastest and easiest way to install the Cosmos SDK binaries +is to run [this script](https://github.com/cosmos/cosmos-sdk/blob/develop/scripts/install_sdk_ubuntu.sh) on a fresh Ubuntu instance. Similarly, you can run [this script](https://github.com/cosmos/cosmos-sdk/blob/develop/scripts/install_sdk_bsd.sh) on a fresh FreeBSD instance. Read the scripts before running them to ensure no untrusted connection is being made, for example we're making curl requests to download golang. Also read the comments / instructions carefully (i.e., reset your terminal after running the script). + +Cosmos SDK can be installed to +`$GOPATH/src/github.com/cosmos/cosmos-sdk` like a normal Go program: + +``` +go get github.com/cosmos/cosmos-sdk +``` + +If the dependencies have been updated with breaking changes, or if +another branch is required, `dep` is used for dependency management. +Thus, assuming you've already run `go get` or otherwise cloned the repo, +the correct way to install is: + +``` +cd $GOPATH/src/github.com/cosmos/cosmos-sdk +make get_tools +make get_vendor_deps +make install +make install_examples +``` + +This will install `gaiad` and `gaiacli` and four example binaries: +`basecoind`, `basecli`, `democoind`, and `democli`. + +Verify that everything is OK by running: + +``` +gaiad version +``` + +you should see: + +``` +0.17.3-a5a78eb +``` + +then with: + +``` +gaiacli version +``` +you should see the same version (or a later one for both). + +## Update + +Get latest code (you can also `git fetch` only the version desired), +ensure the dependencies are up to date, then recompile. + +``` +cd $GOPATH/src/github.com/cosmos/cosmos-sdk +git fetch -a origin +git checkout VERSION +make get_vendor_deps +make install +``` diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 916e57ee7..000000000 --- a/docs/make.bat +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=python -msphinx -) -set SOURCEDIR=. -set BUILDDIR=_build -set SPHINXPROJ=Cosmos-SDK - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The Sphinx module was not found. Make sure you have Sphinx installed, - echo.then set the SPHINXBUILD environment variable to point to the full - echo.path of the 'sphinx-build' executable. Alternatively you may add the - echo.Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/docs/modules/README.md b/docs/modules/README.md new file mode 100644 index 000000000..53e02687e --- /dev/null +++ b/docs/modules/README.md @@ -0,0 +1,51 @@ +# Bank + +The `x/bank` module is for transferring coins between accounts. + +See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank). + +# Stake + +The `x/stake` module is for Cosmos Delegated-Proof-of-Stake. + +See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/stake). + +See the +[specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/staking) + +# Slashing + +The `x/slashing` module is for Cosmos Delegated-Proof-of-Stake. + +See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/slashing) + +See the +[specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/slashing) + +# Provisions + +The `x/provisions` module is for distributing fees and inflation across bonded +stakeholders. + +See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/provisions) + +See the +[specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/provisions) + +# Governance + +The `x/gov` module is for bonded stakeholders to make proposals and vote on them. + +See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/gov) + +See the +[specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/governance) + +# IBC + +The `x/ibc` module is for InterBlockchain Communication. + +See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/ibc) + +See the +[specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/ibc) diff --git a/docs/old/basecoin/basics.rst b/docs/old/basecoin/basics.rst deleted file mode 100644 index d3627b2b1..000000000 --- a/docs/old/basecoin/basics.rst +++ /dev/null @@ -1,289 +0,0 @@ -Basecoin Basics -=============== - -Here we explain how to get started with a basic Basecoin blockchain, how -to send transactions between accounts using the ``basecoin`` tool, and -what is happening under the hood. - -Install -------- - -With go, it's one command: - -:: - - go get -u github.com/cosmos/cosmos-sdk - -If you have trouble, see the `installation guide <./install.html>`__. - -TODO: update all the below - -Generate some keys -~~~~~~~~~~~~~~~~~~ - -Let's generate two keys, one to receive an initial allocation of coins, -and one to send some coins to later: - -:: - - basecli keys new cool - basecli keys new friend - -You'll need to enter passwords. You can view your key names and -addresses with ``basecli keys list``, or see a particular key's address -with ``basecli keys get <NAME>``. - -Initialize Basecoin -------------------- - -To initialize a new Basecoin blockchain, run: - -:: - - basecoin init <ADDRESS> - -If you prefer not to copy-paste, you can provide the address -programatically: - -:: - - basecoin init $(basecli keys get cool | awk '{print $2}') - -This will create the necessary files for a Basecoin blockchain with one -validator and one account (corresponding to your key) in -``~/.basecoin``. For more options on setup, see the `guide to using the -Basecoin tool </docs/guide/basecoin-tool.md>`__. - -If you like, you can manually add some more accounts to the blockchain -by generating keys and editing the ``~/.basecoin/genesis.json``. - -Start Basecoin -~~~~~~~~~~~~~~ - -Now we can start Basecoin: - -:: - - basecoin start - -You should see blocks start streaming in! - -Initialize Light-Client ------------------------ - -Now that Basecoin is running we can initialize ``basecli``, the -light-client utility. Basecli is used for sending transactions and -querying the state. Leave Basecoin running and open a new terminal -window. Here run: - -:: - - basecli init --node=tcp://localhost:46657 --genesis=$HOME/.basecoin/genesis.json - -If you provide the genesis file to basecli, it can calculate the proper -chainID and validator hash. Basecli needs to get this information from -some trusted source, so all queries done with ``basecli`` can be -cryptographically proven to be correct according to a known validator -set. - -Note: that ``--genesis`` only works if there have been no validator set -changes since genesis. If there are validator set changes, you need to -find the current set through some other method. - -Send transactions -~~~~~~~~~~~~~~~~~ - -Now we are ready to send some transactions. First Let's check the -balance of the two accounts we setup earlier: - -:: - - ME=$(basecli keys get cool | awk '{print $2}') - YOU=$(basecli keys get friend | awk '{print $2}') - basecli query account $ME - basecli query account $YOU - -The first account is flush with cash, while the second account doesn't -exist. Let's send funds from the first account to the second: - -:: - - basecli tx send --name=cool --amount=1000mycoin --to=$YOU --sequence=1 - -Now if we check the second account, it should have ``1000`` 'mycoin' -coins! - -:: - - basecli query account $YOU - -We can send some of these coins back like so: - -:: - - basecli tx send --name=friend --amount=500mycoin --to=$ME --sequence=1 - -Note how we use the ``--name`` flag to select a different account to -send from. - -If we try to send too much, we'll get an error: - -:: - - basecli tx send --name=friend --amount=500000mycoin --to=$ME --sequence=2 - -Let's send another transaction: - -:: - - basecli tx send --name=cool --amount=2345mycoin --to=$YOU --sequence=2 - -Note the ``hash`` value in the response - this is the hash of the -transaction. We can query for the transaction by this hash: - -:: - - basecli query tx <HASH> - -See ``basecli tx send --help`` for additional details. - -Proof ------ - -Even if you don't see it in the UI, the result of every query comes with -a proof. This is a Merkle proof that the result of the query is actually -contained in the state. And the state's Merkle root is contained in a -recent block header. Behind the scenes, ``countercli`` will not only -verify that this state matches the header, but also that the header is -properly signed by the known validator set. It will even update the -validator set as needed, so long as there have not been major changes -and it is secure to do so. So, if you wonder why the query may take a -second... there is a lot of work going on in the background to make sure -even a lying full node can't trick your client. - -Accounts and Transactions -------------------------- - -For a better understanding of how to further use the tools, it helps to -understand the underlying data structures. - -Accounts -~~~~~~~~ - -The Basecoin state consists entirely of a set of accounts. Each account -contains a public key, a balance in many different coin denominations, -and a strictly increasing sequence number for replay protection. This -type of account was directly inspired by accounts in Ethereum, and is -unlike Bitcoin's use of Unspent Transaction Outputs (UTXOs). Note -Basecoin is a multi-asset cryptocurrency, so each account can have many -different kinds of tokens. - -:: - - type Account struct { - PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known. - Sequence int `json:"sequence"` - Balance Coins `json:"coins"` - } - - type Coins []Coin - - type Coin struct { - Denom string `json:"denom"` - Amount int64 `json:"amount"` - } - -If you want to add more coins to a blockchain, you can do so manually in -the ``~/.basecoin/genesis.json`` before you start the blockchain for the -first time. - -Accounts are serialized and stored in a Merkle tree under the key -``base/a/<address>``, where ``<address>`` is the address of the account. -Typically, the address of the account is the 20-byte ``RIPEMD160`` hash -of the public key, but other formats are acceptable as well, as defined -in the `Tendermint crypto -library <https://github.com/tendermint/go-crypto>`__. The Merkle tree -used in Basecoin is a balanced, binary search tree, which we call an -`IAVL tree <https://github.com/tendermint/iavl>`__. - -Transactions -~~~~~~~~~~~~ - -Basecoin defines a transaction type, the ``SendTx``, which allows tokens -to be sent to other accounts. The ``SendTx`` takes a list of inputs and -a list of outputs, and transfers all the tokens listed in the inputs -from their corresponding accounts to the accounts listed in the output. -The ``SendTx`` is structured as follows: - -:: - - type SendTx struct { - Gas int64 `json:"gas"` - Fee Coin `json:"fee"` - Inputs []TxInput `json:"inputs"` - Outputs []TxOutput `json:"outputs"` - } - - type TxInput struct { - Address []byte `json:"address"` // Hash of the PubKey - Coins Coins `json:"coins"` // - Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput - Signature crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx - PubKey crypto.PubKey `json:"pub_key"` // Is present iff Sequence == 0 - } - - type TxOutput struct { - Address []byte `json:"address"` // Hash of the PubKey - Coins Coins `json:"coins"` // - } - -Note the ``SendTx`` includes a field for ``Gas`` and ``Fee``. The -``Gas`` limits the total amount of computation that can be done by the -transaction, while the ``Fee`` refers to the total amount paid in fees. -This is slightly different from Ethereum's concept of ``Gas`` and -``GasPrice``, where ``Fee = Gas x GasPrice``. In Basecoin, the ``Gas`` -and ``Fee`` are independent, and the ``GasPrice`` is implicit. - -In Basecoin, the ``Fee`` is meant to be used by the validators to inform -the ordering of transactions, like in Bitcoin. And the ``Gas`` is meant -to be used by the application plugin to control its execution. There is -currently no means to pass ``Fee`` information to the Tendermint -validators, but it will come soon... - -Note also that the ``PubKey`` only needs to be sent for -``Sequence == 0``. After that, it is stored under the account in the -Merkle tree and subsequent transactions can exclude it, using only the -``Address`` to refer to the sender. Ethereum does not require public -keys to be sent in transactions as it uses a different elliptic curve -scheme which enables the public key to be derived from the signature -itself. - -Finally, note that the use of multiple inputs and multiple outputs -allows us to send many different types of tokens between many different -accounts at once in an atomic transaction. Thus, the ``SendTx`` can -serve as a basic unit of decentralized exchange. When using multiple -inputs and outputs, you must make sure that the sum of coins of the -inputs equals the sum of coins of the outputs (no creating money), and -that all accounts that provide inputs have signed the transaction. - -Clean Up --------- - -**WARNING:** Running these commands will wipe out any existing -information in both the ``~/.basecli`` and ``~/.basecoin`` directories, -including private keys. - -To remove all the files created and refresh your environment (e.g., if -starting this tutorial again or trying something new), the following -commands are run: - -:: - - basecli reset_all - rm -rf ~/.basecoin - -In this guide, we introduced the ``basecoin`` and ``basecli`` tools, -demonstrated how to start a new basecoin blockchain and how to send -tokens between accounts, and discussed the underlying data types for -accounts and transactions, specifically the ``Account`` and the -``SendTx``. diff --git a/docs/old/basecoin/extensions.rst b/docs/old/basecoin/extensions.rst deleted file mode 100644 index c1db864a3..000000000 --- a/docs/old/basecoin/extensions.rst +++ /dev/null @@ -1,215 +0,0 @@ -Basecoin Extensions -=================== - -TODO: re-write for extensions - -In the `previous guide <basecoin-basics.md>`__, we saw how to use the -``basecoin`` tool to start a blockchain and the ``basecli`` tools to -send transactions. We also learned about ``Account`` and ``SendTx``, the -basic data types giving us a multi-asset cryptocurrency. Here, we will -demonstrate how to extend the tools to use another transaction type, the -``AppTx``, so we can send data to a custom plugin. In this example we -explore a simple plugin named ``counter``. - -Example Plugin --------------- - -The design of the ``basecoin`` tool makes it easy to extend for custom -functionality. The Counter plugin is bundled with basecoin, so if you -have already `installed basecoin <install.md>`__ and run -``make install`` then you should be able to run a full node with -``counter`` and the a light-client ``countercli`` from terminal. The -Counter plugin is just like the ``basecoin`` tool. They both use the -same library of commands, including one for signing and broadcasting -``SendTx``. - -Counter transactions take two custom inputs, a boolean argument named -``valid``, and a coin amount named ``countfee``. The transaction is only -accepted if both ``valid`` is set to true and the transaction input -coins is greater than ``countfee`` that the user provides. - -A new blockchain can be initialized and started just like in the -`previous guide <basecoin-basics.md>`__: - -:: - - # WARNING: this wipes out data - but counter is only for demos... - rm -rf ~/.counter - countercli reset_all - - countercli keys new cool - countercli keys new friend - - counter init $(countercli keys get cool | awk '{print $2}') - - counter start - -The default files are stored in ``~/.counter``. In another window we can -initialize the light-client and send a transaction: - -:: - - countercli init --node=tcp://localhost:46657 --genesis=$HOME/.counter/genesis.json - - YOU=$(countercli keys get friend | awk '{print $2}') - countercli tx send --name=cool --amount=1000mycoin --to=$YOU --sequence=1 - -But the Counter has an additional command, ``countercli tx counter``, -which crafts an ``AppTx`` specifically for this plugin: - -:: - - countercli tx counter --name cool - countercli tx counter --name cool --valid - -The first transaction is rejected by the plugin because it was not -marked as valid, while the second transaction passes. We can build -plugins that take many arguments of different types, and easily extend -the tool to accomodate them. Of course, we can also expose queries on -our plugin: - -:: - - countercli query counter - -Tada! We can now see that our custom counter plugin transactions went -through. You should see a Counter value of 1 representing the number of -valid transactions. If we send another transaction, and then query -again, we will see the value increment. Note that we need the sequence -number here to send the coins (it didn't increment when we just pinged -the counter) - -:: - - countercli tx counter --name cool --countfee=2mycoin --sequence=2 --valid - countercli query counter - -The Counter value should be 2, because we sent a second valid -transaction. And this time, since we sent a countfee (which must be less -than or equal to the total amount sent with the tx), it stores the -``TotalFees`` on the counter as well. - -Keep it mind that, just like with ``basecli``, the ``countercli`` -verifies a proof that the query response is correct and up-to-date. - -Now, before we implement our own plugin and tooling, it helps to -understand the ``AppTx`` and the design of the plugin system. - -AppTx ------ - -The ``AppTx`` is similar to the ``SendTx``, but instead of sending coins -from inputs to outputs, it sends coins from one input to a plugin, and -can also send some data. - -:: - - type AppTx struct { - Gas int64 `json:"gas"` - Fee Coin `json:"fee"` - Input TxInput `json:"input"` - Name string `json:"type"` // Name of the plugin - Data []byte `json:"data"` // Data for the plugin to process - } - -The ``AppTx`` enables Basecoin to be extended with arbitrary additional -functionality through the use of plugins. The ``Name`` field in the -``AppTx`` refers to the particular plugin which should process the -transaction, and the ``Data`` field of the ``AppTx`` is the data to be -forwarded to the plugin for processing. - -Note the ``AppTx`` also has a ``Gas`` and ``Fee``, with the same meaning -as for the ``SendTx``. It also includes a single ``TxInput``, which -specifies the sender of the transaction, and some coins that can be -forwarded to the plugin as well. - -Plugins -------- - -A plugin is simply a Go package that implements the ``Plugin`` -interface: - -:: - - type Plugin interface { - - // Name of this plugin, should be short. - Name() string - - // Run a transaction from ABCI DeliverTx - RunTx(store KVStore, ctx CallContext, txBytes []byte) (res abci.Result) - - // Other ABCI message handlers - SetOption(store KVStore, key string, value string) (log string) - InitChain(store KVStore, vals []*abci.Validator) - BeginBlock(store KVStore, hash []byte, header *abci.Header) - EndBlock(store KVStore, height uint64) (res abci.ResponseEndBlock) - } - - type CallContext struct { - CallerAddress []byte // Caller's Address (hash of PubKey) - CallerAccount *Account // Caller's Account, w/ fee & TxInputs deducted - Coins Coins // The coins that the caller wishes to spend, excluding fees - } - -The workhorse of the plugin is ``RunTx``, which is called when an -``AppTx`` is processed. The ``Data`` from the ``AppTx`` is passed in as -the ``txBytes``, while the ``Input`` from the ``AppTx`` is used to -populate the ``CallContext``. - -Note that ``RunTx`` also takes a ``KVStore`` - this is an abstraction -for the underlying Merkle tree which stores the account data. By passing -this to the plugin, we enable plugins to update accounts in the Basecoin -state directly, and also to store arbitrary other information in the -state. In this way, the functionality and state of a Basecoin-derived -cryptocurrency can be greatly extended. One could imagine going so far -as to implement the Ethereum Virtual Machine as a plugin! - -For details on how to initialize the state using ``SetOption``, see the -`guide to using the basecoin tool <basecoin-tool.md#genesis>`__. - -Implement your own ------------------- - -To implement your own plugin and tooling, make a copy of -``docs/guide/counter``, and modify the code accordingly. Here, we will -briefly describe the design and the changes to be made, but see the code -for more details. - -First is the ``cmd/counter/main.go``, which drives the program. It can -be left alone, but you should change any occurrences of ``counter`` to -whatever your plugin tool is going to be called. You must also register -your plugin(s) with the basecoin app with ``RegisterStartPlugin``. - -The light-client is located in ``cmd/countercli/main.go`` and allows for -transaction and query commands. This file can also be left mostly alone -besides replacing the application name and adding references to new -plugin commands. - -Next is the custom commands in ``cmd/countercli/commands/``. These files -are where we extend the tool with any new commands and flags we need to -send transactions or queries to our plugin. You define custom ``tx`` and -``query`` subcommands, which are registered in ``main.go`` (avoiding -``init()`` auto-registration, for less magic and more control in the -main executable). - -Finally is ``plugins/counter/counter.go``, where we provide an -implementation of the ``Plugin`` interface. The most important part of -the implementation is the ``RunTx`` method, which determines the meaning -of the data sent along in the ``AppTx``. In our example, we define a new -transaction type, the ``CounterTx``, which we expect to be encoded in -the ``AppTx.Data``, and thus to be decoded in the ``RunTx`` method, and -used to update the plugin state. - -For more examples and inspiration, see our `repository of example -plugins <https://github.com/tendermint/basecoin-examples>`__. - -Conclusion ----------- - -In this guide, we demonstrated how to create a new plugin and how to -extend the ``basecoin`` tool to start a blockchain with the plugin -enabled and send transactions to it. In the next guide, we introduce a -`plugin for Inter Blockchain Communication <ibc.md>`__, which allows us -to publish proofs of the state of one blockchain to another, and thus to -transfer tokens and data between them. diff --git a/docs/old/glossary.rst b/docs/old/glossary.rst deleted file mode 100644 index faf682da4..000000000 --- a/docs/old/glossary.rst +++ /dev/null @@ -1,230 +0,0 @@ -Glossary -======== - -This glossary defines many terms used throughout documentation of Quark. -If there is every a concept that seems unclear, check here. This is -mainly to provide a background and general understanding of the -different words and concepts that are used. Other documents will explain -in more detail how to combine these concepts to build a particular -application. - -Transaction ------------ - -A transaction is a packet of binary data that contains all information -to validate and perform an action on the blockchain. The only other data -that it interacts with is the current state of the chain (key-value -store), and it must have a deterministic action. The transaction is the -main piece of one request. - -We currently make heavy use of -`go-amino <https://github.com/tendermint/go-amino>`__ to -provide binary and json encodings and decodings for ``struct`` or -interface\ ``objects. Here, encoding and decoding operations are designed to operate with interfaces nested any amount times (like an onion!). There is one public``\ TxMapper\` -in the basecoin root package, and all modules can register their own -transaction types there. This allows us to deserialize the entire -transaction in one location (even with types defined in other repos), to -easily embed an arbitrary transaction inside another without specifying -the type, and provide an automatic json representation allowing for -users (or apps) to inspect the chain. - -Note how we can wrap any other transaction, add a fee level, and not -worry about the encoding in our module any more? - -:: - - type Fee struct { - Fee coin.Coin `json:"fee"` - Payer basecoin.Actor `json:"payer"` // the address who pays the fee - Tx basecoin.Tx `json:"tx"` - } - -Context (ctx) -------------- - -As a request passes through the system, it may pick up information such -as the block height the request runs at. In order to carry this information -between modules it is saved to the context. Further, all information -must be deterministic from the context in which the request runs (based -on the transaction and the block it was included in) and can be used to -validate the transaction. - -Data Store ----------- - -In order to provide proofs to Tendermint, we keep all data in one -key-value (kv) store which is indexed with a merkle tree. This allows -for the easy generation of a root hash and proofs for queries without -requiring complex logic inside each module. Standardization of this -process also allows powerful light-client tooling as any store data may -be verified on the fly. - -The largest limitation of the current implemenation of the kv-store is -that interface that the application must use can only ``Get`` and -``Set`` single data points. That said, there are some data structures -like queues and range queries that are available in ``state`` package. -These provide higher-level functionality in a standard format, but have -not yet been integrated into the kv-store interface. - -Isolation ---------- - -One of the main arguments for blockchain is security. So while we -encourage the use of third-party modules, all developers must be -vigilant against security holes. If you use the -`stack <https://github.com/cosmos/cosmos-sdk/tree/master/stack>`__ -package, it will provide two different types of compartmentalization -security. - -The first is to limit the working kv-store space of each module. When -``DeliverTx`` is called for a module, it is never given the entire data -store, but rather only its own prefixed subset of the store. This is -achieved by prefixing all keys transparently with -``<module name> + 0x0``, using the null byte as a separator. Since the -module name must be a string, no malicious naming scheme can ever lead -to a collision. Inside a module, we can write using any key value we -desire without the possibility that we have modified data belonging to -separate module. - -The second is to add permissions to the transaction context. The -transaction context can specify that the tx has been signed by one or -multiple specific actors. - -A transactions will only be executed if the permission requirements have -been fulfilled. For example the sender of funds must have signed, or 2 -out of 3 multi-signature actors must have signed a joint account. To -prevent the forgery of account signatures from unintended modules each -permission is associated with the module that granted it (in this case -`auth <https://github.com/cosmos/cosmos-sdk/tree/master/x/auth>`__), -and if a module tries to add a permission for another module, it will -panic. There is also protection if a module creates a brand new fake -context to trick the downstream modules. Each context enforces the rules -on how to make child contexts, and the stack builder enforces -that the context passed from one level to the next is a valid child of -the original one. - -These security measures ensure that modules can confidently write to -their local section of the database and trust the permissions associated -with the context, without concern of interference from other modules. -(Okay, if you see a bunch of C-code in the module traversing through all -the memory space of the application, then get worried....) - -Handler -------- - -The ABCI interface is handled by ``app``, which translates these data -structures into an internal format that is more convenient, but unable -to travel over the wire. The basic interface for any code that modifies -state is the ``Handler`` interface, which provides four methods: - -:: - - Name() string - CheckTx(ctx Context, store state.KVStore, tx Tx) (Result, error) - DeliverTx(ctx Context, store state.KVStore, tx Tx) (Result, error) - SetOption(l log.Logger, store state.KVStore, module, key, value string) (string, error) - -Note the ``Context``, ``KVStore``, and ``Tx`` as principal carriers of -information. And that Result is always success, and we have a second -error return for errors (which is much more standard golang that -``res.IsErr()``) - -The ``Handler`` interface is designed to be the basis for all modules -that execute transactions, and this can provide a large degree of code -interoperability, much like ``http.Handler`` does in golang web -development. - -Modules -------- - -TODO: update (s/Modules/handlers+mappers+stores/g) & add Msg + Tx (a signed message) - -A module is a set of functionality which should be typically designed as -self-sufficient. Common elements of a module are: - -- transaction types (either end transactions, or transaction wrappers) -- custom error codes -- data models (to persist in the kv-store) -- handler (to handle any end transactions) - -Dispatcher ----------- - -We usually will want to have multiple modules working together, and need -to make sure the correct transactions get to the correct module. So we -have ``coin`` sending money, ``roles`` to create multi-sig accounts, and -``ibc`` for following other chains all working together without -interference. - -We can then register a ``Dispatcher``, which -also implements the ``Handler`` interface. We then register a list of -modules with the dispatcher. Every module has a unique ``Name()``, which -is used for isolating its state space. We use this same name for routing -transactions. Each transaction implementation must be registed with -go-amino via ``TxMapper``, so we just look at the registered name of this -transaction, which should be of the form ``<module name>/xxx``. The -dispatcher grabs the appropriate module name from the tx name and routes -it if the module is present. - -This all seems like a bit of magic, but really we're just making use of -go-amino magic that we are already using, rather than add another layer. -For all the transactions to be properly routed, the only thing you need -to remember is to use the following pattern: - -:: - - const ( - NameCoin = "coin" - TypeSend = NameCoin + "/send" - ) - -Permissions ------------ - -TODO: replaces perms with object capabilities/object capability keys -- get rid of IPC - -IPC requires a more complex permissioning system to allow the modules to -have limited access to each other and also to allow more types of -permissions than simple public key signatures. Rather than just use an -address to identify who is performing an action, we can use a more -complex structure: - -:: - - type Actor struct { - ChainID string `json:"chain"` // this is empty unless it comes from a different chain - App string `json:"app"` // the app that the actor belongs to - Address data.Bytes `json:"addr"` // arbitrary app-specific unique id - } - -Here, the ``Actor`` abstracts any address that can authorize actions, -hold funds, or initiate any sort of transaction. It doesn't just have to -be a pubkey on this chain, it could stem from another app (such as -multi-sig account), or even another chain (via IBC) - -``ChainID`` is for IBC, discussed below. Let's focus on ``App`` and -``Address``. For a signature, the App is ``auth``, and any modules can -check to see if a specific public key address signed like this -``ctx.HasPermission(auth.SigPerm(addr))``. However, we can also -authorize a tx with ``roles``, which handles multi-sig accounts, it -checks if there were enough signatures by checking as above, then it can -add the role permission like -``ctx= ctx.WithPermissions(NewPerm(assume.Role))`` - -In addition to the permissions schema, the Actors are addresses just -like public key addresses. So one can create a mulit-sig role, then send -coin there, which can only be moved upon meeting the authorization -requirements from that module. ``coin`` doesn't even know the existence -of ``roles`` and one could build any other sort of module to provide -permissions (like bind the outcome of an election to move coins or to -modify the accounts on a role). - -One idea - not yet implemented - is to provide scopes on the -permissions. Currently, if I sign a transaction to one module, it can -pass it on to any other module over IPC with the same permissions. It -could move coins, vote in an election, or anything else. Ideally, when -signing, one could also specify the scope(s) that this signature -authorizes. The `oauth -protocol <https://api.slack.com/docs/oauth-scopes>`__ also has to deal -with a similar problem, and maybe could provide some inspiration. diff --git a/docs/old/ibc.rst b/docs/old/ibc.rst deleted file mode 100644 index 30b9a16fa..000000000 --- a/docs/old/ibc.rst +++ /dev/null @@ -1,424 +0,0 @@ -IBC -=== - -TODO: update in light of latest SDK (this document is currently out of date) - -One of the most exciting elements of the Cosmos Network is the -InterBlockchain Communication (IBC) protocol, which enables -interoperability across different blockchains. We implemented IBC as a -basecoin plugin, and we'll show you how to use it to send tokens across -blockchains! - -Please note: this tutorial assumes familiarity with the Cosmos SDK. - -The IBC plugin defines a new set of transactions as subtypes of the -``AppTx``. The plugin's functionality is accessed by setting the -``AppTx.Name`` field to ``"IBC"``, and setting the ``Data`` field to the -serialized IBC transaction type. - -We'll demonstrate exactly how this works below. - -Inter BlockChain Communication ------------------------------- - -Let's review the IBC protocol. The purpose of IBC is to enable one -blockchain to function as a light-client of another. Since we are using -a classical Byzantine Fault Tolerant consensus algorithm, light-client -verification is cheap and easy: all we have to do is check validator -signatures on the latest block, and verify a Merkle proof of the state. - -In Tendermint, validators agree on a block before processing it. This -means that the signatures and state root for that block aren't included -until the next block. Thus, each block contains a field called -``LastCommit``, which contains the votes responsible for committing the -previous block, and a field in the block header called ``AppHash``, -which refers to the Merkle root hash of the application after processing -the transactions from the previous block. So, if we want to verify the -``AppHash`` from height H, we need the signatures from ``LastCommit`` at -height H+1. (And remember that this ``AppHash`` only contains the -results from all transactions up to and including block H-1) - -Unlike Proof-of-Work, the light-client protocol does not need to -download and check all the headers in the blockchain - the client can -always jump straight to the latest header available, so long as the -validator set has not changed much. If the validator set is changing, -the client needs to track these changes, which requires downloading -headers for each block in which there is a significant change. Here, we -will assume the validator set is constant, and postpone handling -validator set changes for another time. - -Now we can describe exactly how IBC works. Suppose we have two -blockchains, ``chain1`` and ``chain2``, and we want to send some data -from ``chain1`` to ``chain2``. We need to do the following: 1. Register -the details (ie. chain ID and genesis configuration) of ``chain1`` on -``chain2`` 2. Within ``chain1``, broadcast a transaction that creates an -outgoing IBC packet destined for ``chain2`` 3. Broadcast a transaction -to ``chain2`` informing it of the latest state (ie. header and commit -signatures) of ``chain1`` 4. Post the outgoing packet from ``chain1`` to -``chain2``, including the proof that it was indeed committed on -``chain1``. Note ``chain2`` can only verify this proof because it has a -recent header and commit. - -Each of these steps involves a separate IBC transaction type. Let's take -them up in turn. - -IBCRegisterChainTx -~~~~~~~~~~~~~~~~~~ - -The ``IBCRegisterChainTx`` is used to register one chain on another. It -contains the chain ID and genesis configuration of the chain to -register: - -:: - - type IBCRegisterChainTx struct { BlockchainGenesis } - - type BlockchainGenesis struct { ChainID string Genesis string } - -This transaction should only be sent once for a given chain ID, and -successive sends will return an error. - -IBCUpdateChainTx -~~~~~~~~~~~~~~~~ - -The ``IBCUpdateChainTx`` is used to update the state of one chain on -another. It contains the header and commit signatures for some block in -the chain: - -:: - - type IBCUpdateChainTx struct { - Header tm.Header - Commit tm.Commit - } - -In the future, it needs to be updated to include changes to the -validator set as well. Anyone can relay an ``IBCUpdateChainTx``, and -they only need to do so as frequently as packets are being sent or the -validator set is changing. - -IBCPacketCreateTx -~~~~~~~~~~~~~~~~~ - -The ``IBCPacketCreateTx`` is used to create an outgoing packet on one -chain. The packet itself contains the source and destination chain IDs, -a sequence number (i.e. an integer that increments with every message -sent between this pair of chains), a packet type (e.g. coin, data, -etc.), and a payload. - -:: - - type IBCPacketCreateTx struct { - Packet - } - - type Packet struct { - SrcChainID string - DstChainID string - Sequence uint64 - Type string - Payload []byte - } - -We have yet to define the format for the payload, so, for now, it's just -arbitrary bytes. - -One way to think about this is that ``chain2`` has an account on -``chain1``. With a ``IBCPacketCreateTx`` on ``chain1``, we send funds to -that account. Then we can prove to ``chain2`` that there are funds -locked up for it in it's account on ``chain1``. Those funds can only be -unlocked with corresponding IBC messages back from ``chain2`` to -``chain1`` sending the locked funds to another account on ``chain1``. - -IBCPacketPostTx -~~~~~~~~~~~~~~~ - -The ``IBCPacketPostTx`` is used to post an outgoing packet from one -chain to another. It contains the packet and a proof that the packet was -committed into the state of the sending chain: - -:: - - type IBCPacketPostTx struct { - FromChainID string // The immediate source of the packet, not always Packet.SrcChainID - FromChainHeight uint64 // The block height in which Packet was committed, to check Proof Packet - Proof *merkle.IAVLProof - } - -The proof is a Merkle proof in an IAVL tree, our implementation of a -balanced, Merklized binary search tree. It contains a list of nodes in -the tree, which can be hashed together to get the Merkle root hash. This -hash must match the ``AppHash`` contained in the header at -``FromChainHeight + 1`` - -- note the ``+ 1`` is necessary since ``FromChainHeight`` is the height - in which the packet was committed, and the resulting state root is - not included until the next block. - -IBC State -~~~~~~~~~ - -Now that we've seen all the transaction types, let's talk about the -state. Each chain stores some IBC state in its Merkle tree. For each -chain being tracked by our chain, we store: - -- Genesis configuration -- Latest state -- Headers for recent heights - -We also store all incoming (ingress) and outgoing (egress) packets. - -The state of a chain is updated every time an ``IBCUpdateChainTx`` is -committed. New packets are added to the egress state upon -``IBCPacketCreateTx``. New packets are added to the ingress state upon -``IBCPacketPostTx``, assuming the proof checks out. - -Merkle Queries --------------- - -The Basecoin application uses a single Merkle tree that is shared across -all its state, including the built-in accounts state and all plugin -state. For this reason, it's important to use explicit key names and/or -hashes to ensure there are no collisions. - -We can query the Merkle tree using the ABCI Query method. If we pass in -the correct key, it will return the corresponding value, as well as a -proof that the key and value are contained in the Merkle tree. - -The results of a query can thus be used as proof in an -``IBCPacketPostTx``. - -Relay ------ - -While we need all these packet types internally to keep track of all the -proofs on both chains in a secure manner, for the normal work-flow, we -can run a relay node that handles the cross-chain interaction. - -In this case, there are only two steps. First ``basecoin relay init``, -which must be run once to register each chain with the other one, and -make sure they are ready to send and recieve. And then -``basecoin relay start``, which is a long-running process polling the -queue on each side, and relaying all new message to the other block. - -This requires that the relay has access to accounts with some funds on -both chains to pay for all the ibc packets it will be forwarding. - -Try it out ----------- - -Now that we have all the background knowledge, let's actually walk -through the tutorial. - -Make sure you have installed `basecoin and -basecli </docs/guide/install.md>`__. - -Basecoin is a framework for creating new cryptocurrency applications. It -comes with an ``IBC`` plugin enabled by default. - -You will also want to install the -`jq <https://stedolan.github.io/jq/>`__ for handling JSON at the command -line. - -If you have any trouble with this, you can also look at the `test -scripts </tests/cli/ibc.sh>`__ or just run ``make test_cli`` in basecoin -repo. Otherwise, open up 5 (yes 5!) terminal tabs.... - -Preliminaries -~~~~~~~~~~~~~ - -:: - - # first, clean up any old garbage for a fresh slate... - rm -rf ~/.ibcdemo/ - -Let's start by setting up some environment variables and aliases: - -:: - - export BCHOME1_CLIENT=~/.ibcdemo/chain1/client - export BCHOME1_SERVER=~/.ibcdemo/chain1/server - export BCHOME2_CLIENT=~/.ibcdemo/chain2/client - export BCHOME2_SERVER=~/.ibcdemo/chain2/server - alias basecli1="basecli --home $BCHOME1_CLIENT" - alias basecli2="basecli --home $BCHOME2_CLIENT" - alias basecoin1="basecoin --home $BCHOME1_SERVER" - alias basecoin2="basecoin --home $BCHOME2_SERVER" - -This will give us some new commands to use instead of raw ``basecli`` -and ``basecoin`` to ensure we're using the right configuration for the -chain we want to talk to. - -We also want to set some chain IDs: - -:: - - export CHAINID1="test-chain-1" - export CHAINID2="test-chain-2" - -And since we will run two different chains on one machine, we need to -maintain different sets of ports: - -:: - - export PORT_PREFIX1=1234 - export PORT_PREFIX2=2345 - export RPC_PORT1=${PORT_PREFIX1}7 - export RPC_PORT2=${PORT_PREFIX2}7 - -Setup Chain 1 -~~~~~~~~~~~~~ - -Now, let's create some keys that we can use for accounts on -test-chain-1: - -:: - - basecli1 keys new money - basecli1 keys new gotnone - export MONEY=$(basecli1 keys get money | awk '{print $2}') - export GOTNONE=$(basecli1 keys get gotnone | awk '{print $2}') - -and create an initial configuration giving lots of coins to the $MONEY -key: - -:: - - basecoin1 init --chain-id $CHAINID1 $MONEY - -Now start basecoin: - -:: - - sed -ie "s/4665/$PORT_PREFIX1/" $BCHOME1_SERVER/config.toml - - basecoin1 start &> basecoin1.log & - -Note the ``sed`` command to replace the ports in the config file. You -can follow the logs with ``tail -f basecoin1.log`` - -Now we can attach the client to the chain and verify the state. The -first account should have money, the second none: - -:: - - basecli1 init --node=tcp://localhost:${RPC_PORT1} --genesis=${BCHOME1_SERVER}/genesis.json - basecli1 query account $MONEY - basecli1 query account $GOTNONE - -Setup Chain 2 -~~~~~~~~~~~~~ - -This is the same as above, except with ``basecli2``, ``basecoin2``, and -``$CHAINID2``. We will also need to change the ports, since we're -running another chain on the same local machine. - -Let's create new keys for test-chain-2: - -:: - - basecli2 keys new moremoney - basecli2 keys new broke - MOREMONEY=$(basecli2 keys get moremoney | awk '{print $2}') - BROKE=$(basecli2 keys get broke | awk '{print $2}') - -And prepare the genesis block, and start the server: - -:: - - basecoin2 init --chain-id $CHAINID2 $(basecli2 keys get moremoney | awk '{print $2}') - - sed -ie "s/4665/$PORT_PREFIX2/" $BCHOME2_SERVER/config.toml - - basecoin2 start &> basecoin2.log & - -Now attach the client to the chain and verify the state. The first -account should have money, the second none: - -:: - - basecli2 init --node=tcp://localhost:${RPC_PORT2} --genesis=${BCHOME2_SERVER}/genesis.json - basecli2 query account $MOREMONEY - basecli2 query account $BROKE - -Connect these chains -~~~~~~~~~~~~~~~~~~~~ - -OK! So we have two chains running on your local machine, with different -keys on each. Let's hook them up together by starting a relay process to -forward messages from one chain to the other. - -The relay account needs some money in it to pay for the ibc messages, so -for now, we have to transfer some cash from the rich accounts before we -start the actual relay. - -:: - - # note that this key.json file is a hardcoded demo for all chains, this will - # be updated in a future release - RELAY_KEY=$BCHOME1_SERVER/key.json - RELAY_ADDR=$(cat $RELAY_KEY | jq .address | tr -d \") - - basecli1 tx send --amount=100000mycoin --sequence=1 --to=$RELAY_ADDR--name=money - basecli1 query account $RELAY_ADDR - - basecli2 tx send --amount=100000mycoin --sequence=1 --to=$RELAY_ADDR --name=moremoney - basecli2 query account $RELAY_ADDR - -Now we can start the relay process. - -:: - - basecoin relay init --chain1-id=$CHAINID1 --chain2-id=$CHAINID2 \ - --chain1-addr=tcp://localhost:${RPC_PORT1} --chain2-addr=tcp://localhost:${RPC_PORT2} \ - --genesis1=${BCHOME1_SERVER}/genesis.json --genesis2=${BCHOME2_SERVER}/genesis.json \ - --from=$RELAY_KEY - - basecoin relay start --chain1-id=$CHAINID1 --chain2-id=$CHAINID2 \ - --chain1-addr=tcp://localhost:${RPC_PORT1} --chain2-addr=tcp://localhost:${RPC_PORT2} \ - --from=$RELAY_KEY &> relay.log & - -This should start up the relay, and assuming no error messages came out, -the two chains are now fully connected over IBC. Let's use this to send -our first tx accross the chains... - -Sending cross-chain payments -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The hard part is over, we set up two blockchains, a few private keys, -and a secure relay between them. Now we can enjoy the fruits of our -labor... - -:: - - # Here's an empty account on test-chain-2 - basecli2 query account $BROKE - -:: - - # Let's send some funds from test-chain-1 - basecli1 tx send --amount=12345mycoin --sequence=2 --to=test-chain-2/$BROKE --name=money - -:: - - # give it time to arrive... - sleep 2 - # now you should see 12345 coins! - basecli2 query account $BROKE - -You're no longer broke! Cool, huh? Now have fun exploring and sending -coins across the chains. And making more accounts as you want to. - -Conclusion ----------- - -In this tutorial we explained how IBC works, and demonstrated how to use -it to communicate between two chains. We did the simplest communciation -possible: a one way transfer of data from chain1 to chain2. The most -important part was that we updated chain2 with the latest state (i.e. -header and commit) of chain1, and then were able to post a proof to -chain2 that a packet was committed to the outgoing state of chain1. - -In a future tutorial, we will demonstrate how to use IBC to actually -transfer tokens between two blockchains, but we'll do it with real -testnets deployed across multiple nodes on the network. Stay tuned! diff --git a/docs/old/keys.md b/docs/old/keys.md deleted file mode 100644 index 029508ad5..000000000 --- a/docs/old/keys.md +++ /dev/null @@ -1,119 +0,0 @@ -# Keys CLI - -**WARNING: out-of-date and parts are wrong.... please update** - -This is as much an example how to expose cobra/viper, as for a cli itself -(I think this code is overkill for what go-keys needs). But please look at -the commands, and give feedback and changes. - -`RootCmd` calls some initialization functions (`cobra.OnInitialize` and `RootCmd.PersistentPreRunE`) which serve to connect environmental variables and cobra flags, as well as load the config file. It also validates the flags registered on root and creates the cryptomanager, which will be used by all subcommands. - -## Help info - -``` -# keys help - -Keys allows you to manage your local keystore for tendermint. - -These keys may be in any format supported by go-crypto and can be -used by light-clients, full nodes, or any other application that -needs to sign with a private key. - -Usage: - keys [command] - -Available Commands: - get Get details of one key - list List all keys - new Create a new public/private key pair - serve Run the key manager as an http server - update Change the password for a private key - -Flags: - --keydir string Directory to store private keys (subdir of root) (default "keys") - -o, --output string Output format (text|json) (default "text") - -r, --root string root directory for config and data (default "/Users/ethan/.tlc") - -Use "keys [command] --help" for more information about a command. -``` - -## Getting the config file - -The first step is to load in root, by checking the following in order: - -* -r, --root command line flag -* TM_ROOT environmental variable -* default ($HOME/.tlc evaluated at runtime) - -Once the `rootDir` is established, the script looks for a config file named `keys.{json,toml,yaml,hcl}` in that directory and parses it. These values will provide defaults for flags of the same name. - -There is an example config file for testing out locally, which writes keys to `./.mykeys`. You can - -## Getting/Setting variables - -When we want to get the value of a user-defined variable (eg. `output`), we can call `viper.GetString("output")`, which will do the following checks, until it finds a match: - -* Is `--output` command line flag present? -* Is `TM_OUTPUT` environmental variable set? -* Was a config file found and does it have an `output` variable? -* Is there a default set on the command line flag? - -If no variable is set and there was no default, we get back "". - -This setup allows us to have powerful command line flags, but use env variables or config files (local or 12-factor style) to avoid passing these arguments every time. - -## Nesting structures - -Sometimes we don't just need key-value pairs, but actually a multi-level config file, like - -``` -[mail] -from = "no-reply@example.com" -server = "mail.example.com" -port = 567 -password = "XXXXXX" -``` - -This CLI is too simple to warant such a structure, but I think eg. tendermint could benefit from such an approach. Here are some pointers: - -* [Accessing nested keys from config files](https://github.com/spf13/viper#accessing-nested-keys) -* [Overriding nested values with envvars](https://www.netlify.com/blog/2016/09/06/creating-a-microservice-boilerplate-in-go/#nested-config-values) - the mentioned outstanding PR is already merged into master! -* Overriding nested values with cli flags? (use `--log_config.level=info` ??) - -I'd love to see an example of this fully worked out in a more complex CLI. - -## Have your cake and eat it too - -It's easy to render data different ways. Some better for viewing, some better for importing to other programs. You can just add some global (persistent) flags to control the output formatting, and everyone gets what they want. - -``` -# keys list -e hex -All keys: -betty d0789984492b1674e276b590d56b7ae077f81adc -john b77f4720b220d1411a649b6c7f1151eb6b1c226a - -# keys list -e btc -All keys: -betty 3uTF4r29CbtnzsNHZoPSYsE4BDwH -john 3ZGp2Md35iw4XVtRvZDUaAEkCUZP - -# keys list -e b64 -o json -[ - { - "name": "betty", - "address": "0HiZhEkrFnTidrWQ1Wt64Hf4Gtw=", - "pubkey": { - "type": "secp256k1", - "data": "F83WvhT0KwttSoqQqd_0_r2ztUUaQix5EXdO8AZyREoV31Og780NW59HsqTAb2O4hZ-w-j0Z-4b2IjfdqqfhVQ==" - } - }, - { - "name": "john", - "address": "t39HILIg0UEaZJtsfxFR62scImo=", - "pubkey": { - "type": "ed25519", - "data": "t1LFmbg_8UTwj-n1wkqmnTp6NfaOivokEhlYySlGYCY=" - } - } -] -``` diff --git a/docs/old/replay-protection.rst b/docs/old/replay-protection.rst deleted file mode 100644 index d262add97..000000000 --- a/docs/old/replay-protection.rst +++ /dev/null @@ -1,38 +0,0 @@ -Replay Protection ------------------ - -In order to prevent `replay -attacks <https://en.wikipedia.org/wiki/Replay_attack>`__ a multi account -nonce system has been constructed as a module, which can be found in -``modules/nonce``. By adding the nonce module to the stack, each -transaction is verified for authenticity against replay attacks. This is -achieved by requiring that a new signed copy of the sequence number -which must be exactly 1 greater than the sequence number of the previous -transaction. A distinct sequence number is assigned per chain-id, -application, and group of signers. Each sequence number is tracked as a -nonce-store entry where the key is the marshaled list of actors after -having been sorted by chain, app, and address. - -.. code:: golang - - // Tx - Nonce transaction structure, contains list of signers and current sequence number - type Tx struct { - Sequence uint32 `json:"sequence"` - Signers []basecoin.Actor `json:"signers"` - Tx basecoin.Tx `json:"tx"` - } - -By distinguishing sequence numbers across groups of Signers, -multi-signature Actors need not lock up use of their Address while -waiting for all the members of a multi-sig transaction to occur. Instead -only the multi-sig account will be locked, while other accounts -belonging to that signer can be used and signed with other sequence -numbers. - -By abstracting out the nonce module in the stack, entire series of -transactions can occur without needing to verify the nonce for each -member of the series. An common example is a stack which will send coins -and charge a fee. Within the SDK this can be achieved using separate -modules in a stack, one to send the coins and the other to charge the -fee, however both modules do not need to check the nonce. This can occur -as a separate module earlier in the stack. diff --git a/docs/old/staking/key-management.rst b/docs/old/staking/key-management.rst deleted file mode 100644 index ebeca0e44..000000000 --- a/docs/old/staking/key-management.rst +++ /dev/null @@ -1,204 +0,0 @@ -Key Management -============== - -Here we explain a bit how to work with your keys, using the -``gaia client keys`` subcommand. - -**Note:** This keys tooling is not considered production ready and is -for dev only. - -We'll look at what you can do using the six sub-commands of -``gaia client keys``: - -:: - - new - list - get - delete - recover - update - -Create keys ------------ - -``gaia client keys new`` has two inputs (name, password) and two outputs -(address, seed). - -First, we name our key: - -:: - - gaia client keys new alice - -This will prompt (10 character minimum) password entry which must be -re-typed. You'll see: - -:: - - Enter a passphrase: - Repeat the passphrase: - alice A159C96AE911F68913E715ED889D211C02EC7D70 - **Important** write this seed phrase in a safe place. - It is the only way to recover your account if you ever forget your password. - - pelican amateur empower assist awkward claim brave process cliff save album pigeon intact asset - -which shows the address of your key named ``alice``, and its recovery -seed. We'll use these shortly. - -Adding the ``--output json`` flag to the above command would give this -output: - -:: - - Enter a passphrase: - Repeat the passphrase: - { - "key": { - "name": "alice", - "address": "A159C96AE911F68913E715ED889D211C02EC7D70", - "pubkey": { - "type": "ed25519", - "data": "4BF22554B0F0BF2181187E5E5456E3BF3D96DB4C416A91F07F03A9C36F712B77" - } - }, - "seed": "pelican amateur empower assist awkward claim brave process cliff save album pigeon intact asset" - } - -To avoid the prompt, it's possible to pipe the password into the -command, e.g.: - -:: - - echo 1234567890 | gaia client keys new fred --output json - -After trying each of the three ways to create a key, look at them, use: - -:: - - gaia client keys list - -to list all the keys: - -:: - - All keys: - alice 6FEA9C99E2565B44FCC3C539A293A1378CDA7609 - bob A159C96AE911F68913E715ED889D211C02EC7D70 - charlie 784D623E0C15DE79043C126FA6449B68311339E5 - -Again, we can use the ``--output json`` flag: - -:: - - [ - { - "name": "alice", - "address": "6FEA9C99E2565B44FCC3C539A293A1378CDA7609", - "pubkey": { - "type": "ed25519", - "data": "878B297F1E863CC30CAD71E04A8B3C23DB71C18F449F39E35B954EDB2276D32D" - } - }, - { - "name": "bob", - "address": "A159C96AE911F68913E715ED889D211C02EC7D70", - "pubkey": { - "type": "ed25519", - "data": "2127CAAB96C08E3042C5B33C8B5A820079AAE8DD50642DCFCC1E8B74821B2BB9" - } - }, - { - "name": "charlie", - "address": "784D623E0C15DE79043C126FA6449B68311339E5", - "pubkey": { - "type": "ed25519", - "data": "4BF22554B0F0BF2181187E5E5456E3BF3D96DB4C416A91F07F03A9C36F712B77" - } - }, - ] - -to get machine readable output. - -If we want information about one specific key, then: - -:: - - gaia client keys get charlie --output json - -will, for example, return the info for only the "charlie" key returned -from the previous ``gaia client keys list`` command. - -The keys tooling can support different types of keys with a flag: - -:: - - gaia client keys new bit --type secp256k1 - -and you'll see the difference in the ``"type": field from``\ gaia client -keys get\` - -Before moving on, let's set an enviroment variable to make -``--output json`` the default. - -Either run or put in your ``~/.bash_profile`` the following line: - -:: - - export BC_OUTPUT=json - -Recover a key -------------- - -Let's say, for whatever reason, you lose a key or forget the password. -On creation, you were given a seed. We'll use it to recover a lost key. - -First, let's simulate the loss by deleting a key: - -:: - - gaia client keys delete alice - -which prompts for your current password, now rendered obsolete, and -gives a warning message. The only way you can recover your key now is -using the 12 word seed given on initial creation of the key. Let's try -it: - -:: - - gaia client keys recover alice-again - -which prompts for a new password then the seed: - -:: - - Enter the new passphrase: - Enter your recovery seed phrase: - strike alien praise vendor term left market practice junior better deputy divert front calm - alice-again CBF5D9CE6DDCC32806162979495D07B851C53451 - -and voila! You've recovered your key. Note that the seed can be typed -out, pasted in, or piped into the command alongside the password. - -To change the password of a key, we can: - -:: - - gaia client keys update alice-again - -and follow the prompts. - -That covers most features of the keys sub command. - -.. raw:: html - - <!-- use later in a test script, or more advance tutorial? - SEED=$(echo 1234567890 | gaia client keys new fred -o json | jq .seed | tr -d \") - echo $SEED - (echo qwertyuiop; echo $SEED stamp) | gaia client keys recover oops - (echo qwertyuiop; echo $SEED) | gaia client keys recover derf - gaia client keys get fred -o json - gaia client keys get derf -o json - ``` - --> diff --git a/docs/old/staking/local-testnet.rst b/docs/old/staking/local-testnet.rst deleted file mode 100644 index e3f69bded..000000000 --- a/docs/old/staking/local-testnet.rst +++ /dev/null @@ -1,83 +0,0 @@ -Local Testnet -============= - -This tutorial demonstrates the basics of setting up a gaia -testnet locally. - -If you haven't already made a key, make one now: - -:: - - gaia client keys new alice - -otherwise, use an existing key. - -Initialize The Chain --------------------- - -Now initialize a gaia chain, using ``alice``'s address: - -:: - - gaia node init 5D93A6059B6592833CBC8FA3DA90EE0382198985 --home=$HOME/.gaia1 --chain-id=gaia-test - -This will create all the files necessary to run a single node chain in -``$HOME/.gaia1``: a ``priv_validator.json`` file with the validators -private key, and a ``genesis.json`` file with the list of validators and -accounts. - -We'll add a second node on our local machine by initiating a node in a -new directory, with the same address, and copying in the genesis: - -:: - - gaia node init 5D93A6059B6592833CBC8FA3DA90EE0382198985 --home=$HOME/.gaia2 --chain-id=gaia-test - cp $HOME/.gaia1/genesis.json $HOME/.gaia2/genesis.json - -We also need to modify ``$HOME/.gaia2/config.toml`` to set new seeds -and ports. It should look like: - -:: - - proxy_app = "tcp://127.0.0.1:46668" - moniker = "anonymous" - fast_sync = true - db_backend = "leveldb" - log_level = "state:info,*:error" - - [rpc] - laddr = "tcp://0.0.0.0:46667" - - [p2p] - laddr = "tcp://0.0.0.0:46666" - seeds = "0.0.0.0:46656" - -Start Nodes ------------ - -Now that we've initialized the chains, we can start both nodes: - -NOTE: each command below must be started in seperate terminal windows. Alternatively, to run this testnet across multiple machines, you'd replace the ``seeds = "0.0.0.0"`` in ``~/.gaia2.config.toml`` with the IP of the first node, and could skip the modifications we made to the config file above because port conflicts would be avoided. - -:: - - gaia node start --home=$HOME/.gaia1 - gaia node start --home=$HOME/.gaia2 - -Now we can initialize a client for the first node, and look up our -account: - -:: - - gaia client init --chain-id=gaia-test --node=tcp://localhost:46657 - gaia client query account 5D93A6059B6592833CBC8FA3DA90EE0382198985 - -To see what tendermint considers the validator set is, use: - -:: - - curl localhost:46657/validators - -and compare the information in this file: ``~/.gaia1/priv_validator.json``. The ``address`` and ``pub_key`` fields should match. - -To add a second validator on your testnet, you'll need to bond some tokens be declaring candidacy. diff --git a/docs/old/staking/public-testnet.rst b/docs/old/staking/public-testnet.rst deleted file mode 100644 index 640163642..000000000 --- a/docs/old/staking/public-testnet.rst +++ /dev/null @@ -1,64 +0,0 @@ -Public Testnets -=============== - -Here we'll cover the basics of joining a public testnet. These testnets -come and go with various names are we release new versions of tendermint -core. This tutorial covers joining the ``gaia-1`` testnet. To join -other testnets, choose different initialization files, described below. - -Get Tokens ----------- - -If you haven't already `created a key <../key-management.html>`__, -do so now. Copy your key's address and enter it into -`this utility <http://www.cosmosvalidators.com/>`__ which will send you -some ``steak`` testnet tokens. - -Get Files ---------- - -Now, to sync with the testnet, we need the genesis file and seeds. The -easiest way to get them is to clone and navigate to the tendermint -testnet repo: - -:: - - git clone https://github.com/tendermint/testnets ~/testnets - cd ~/testnets/gaia-1/gaia - -NOTE: to join a different testnet, change the ``gaia-1/gaia`` filepath -to another directory with testnet inititalization files *and* an -active testnet. - -Start Node ----------- - -Now we can start a new node:it may take awhile to sync with the -existing testnet. - -:: - - gaia node start --home=$HOME/testnets/gaia-1/gaia - -Once blocks slow down to about one per second, you're all caught up. - -The ``gaia node start`` command will automaticaly generate a validator -private key found in ``~/testnets/gaia-1/gaia/priv_validator.json``. - -Finally, let's initialize the gaia client to interact with the testnet: - -:: - - gaia client init --chain-id=gaia-1 --node=tcp://localhost:46657 - -and check our balance: - -:: - - gaia client query account $MYADDR - -Where ``$MYADDR`` is the address originally generated by ``gaia keys new bob``. - -You are now ready to declare candidacy or delegate some steaks. See the -`staking module overview <./staking-module.html>`__ for more information -on using the ``gaia client``. diff --git a/docs/sdk/apps.md b/docs/overview/apps.md similarity index 100% rename from docs/sdk/apps.md rename to docs/overview/apps.md diff --git a/docs/overview/capabilities.md b/docs/overview/capabilities.md new file mode 100644 index 000000000..ded5928a9 --- /dev/null +++ b/docs/overview/capabilities.md @@ -0,0 +1,118 @@ +Overview +======== + +The SDK design optimizes flexibility and security. The framework is +designed around a modular execution stack which allows applications to +mix and match elements as desired. In addition, all modules are +sandboxed for greater application security. + +Framework Overview +------------------ + +### Object-Capability Model + +When thinking about security, it's good to start with a specific threat +model. Our threat model is the following: + + We assume that a thriving ecosystem of Cosmos-SDK modules that are easy to compose into a blockchain application will contain faulty or malicious modules. + +The Cosmos-SDK is designed to address this threat by being the +foundation of an object capability system. + + The structural properties of object capability systems favor + modularity in code design and ensure reliable encapsulation in + code implementation. + + These structural properties facilitate the analysis of some + security properties of an object-capability program or operating + system. Some of these — in particular, information flow properties + — can be analyzed at the level of object references and + connectivity, independent of any knowledge or analysis of the code + that determines the behavior of the objects. As a consequence, + these security properties can be established and maintained in the + presence of new objects that contain unknown and possibly + malicious code. + + These structural properties stem from the two rules governing + access to existing objects: + + 1) An object A can send a message to B only if object A holds a + reference to B. + + 2) An object A can obtain a reference to C only + if object A receives a message containing a reference to C. As a + consequence of these two rules, an object can obtain a reference + to another object only through a preexisting chain of references. + In short, "Only connectivity begets connectivity." + +See the [wikipedia +article](https://en.wikipedia.org/wiki/Object-capability_model) for more +information. + +Strictly speaking, Golang does not implement object capabilities +completely, because of several issues: + +- pervasive ability to import primitive modules (e.g. "unsafe", "os") +- pervasive ability to override module vars + <https://github.com/golang/go/issues/23161> +- data-race vulnerability where 2+ goroutines can create illegal + interface values + +The first is easy to catch by auditing imports and using a proper +dependency version control system like Dep. The second and third are +unfortunate but it can be audited with some cost. + +Perhaps [Go2 will implement the object capability +model](https://github.com/golang/go/issues/23157). + +#### What does it look like? + +Only reveal what is necessary to get the work done. + +For example, the following code snippet violates the object capabilities +principle: + + type AppAccount struct {...} + var account := &AppAccount{ + Address: pub.Address(), + Coins: sdk.Coins{{"ATM", 100}}, + } + var sumValue := externalModule.ComputeSumValue(account) + +The method "ComputeSumValue" implies a pure function, yet the implied +capability of accepting a pointer value is the capability to modify that +value. The preferred method signature should take a copy instead. + + var sumValue := externalModule.ComputeSumValue(*account) + +In the Cosmos SDK, you can see the application of this principle in the +basecoin examples folder. + + // File: cosmos-sdk/examples/basecoin/app/init_handlers.go + package app + + import ( + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/sketchy" + ) + + func (app *BasecoinApp) initRouterHandlers() { + + // All handlers must be added here. + // The order matters. + app.router.AddRoute("bank", bank.NewHandler(app.accountMapper)) + app.router.AddRoute("sketchy", sketchy.NewHandler()) + } + +In the Basecoin example, the sketchy handler isn't provided an account +mapper, which does provide the bank handler with the capability (in +conjunction with the context of a transaction run). + +### Capabilities systems + +TODO: + +- Need for module isolation +- Capability is implied permission +- Link to thesis + diff --git a/docs/overview/overview.md b/docs/overview/overview.md new file mode 100644 index 000000000..9da8c233b --- /dev/null +++ b/docs/overview/overview.md @@ -0,0 +1,34 @@ +# Overview + +The Cosmos-SDK is a framework for building Tendermint ABCI applications in +Golang. It is designed to allow developers to easily create custom interoperable +blockchain applications within the Cosmos Network. + +We envision the SDK as the `npm`-like framework to build secure blockchain applications on top of Tendermint. + +To achieve its goals of flexibility and security, the SDK makes extensive use of +the [object-capability +model](https://en.wikipedia.org/wiki/Object-capability_model) +and the [principle of least +privelege](https://en.wikipedia.org/wiki/Principle_of_least_privilege). + +For an introduction to object-capabilities, see this [article](http://habitatchronicles.com/2017/05/what-are-capabilities/). + +## Languages + +The Cosmos-SDK is currently writen in [Golang](https://golang.org/), though the +framework could be implemented similarly in other languages. +Contact us for information about funding an implementation in another language. + +## Directory Structure + +The SDK is laid out in the following directories: + +- `baseapp`: Defines the template for a basic [ABCI](https://cosmos.network/whitepaper#abci) application so that your Cosmos-SDK application can communicate with the underlying Tendermint node. +- `client`: CLI and REST server tooling for interacting with SDK application. +- `examples`: Examples of how to build working standalone applications. +- `server`: The full node server for running an SDK application on top of + Tendermint. +- `store`: The database of the SDK - a Merkle multistore supporting multiple types of underling Merkle key-value stores. +- `types`: Common types in SDK applications. +- `x`: Extensions to the core, where all messages and handlers are defined. diff --git a/docs/sdk/install.rst b/docs/sdk/install.rst deleted file mode 100644 index 03b219cb5..000000000 --- a/docs/sdk/install.rst +++ /dev/null @@ -1,48 +0,0 @@ -Install -======= - -Cosmos SDK can be installed to -``$GOPATH/src/github.com/cosmos/cosmos-sdk`` like a normal Go program: - -:: - - go get github.com/cosmos/cosmos-sdk - -If the dependencies have been updated with breaking changes, or if -another branch is required, ``dep`` is used for dependency management. -Thus, assuming you've already run ``go get`` or otherwise cloned the -repo, the correct way to install is: - -:: - - cd $GOPATH/src/github.com/cosmos/cosmos-sdk - make get_vendor_deps - make install - make install_examples - -This will install ``gaiad`` and ``gaiacli`` and four example binaries: -``basecoind``, ``basecli``, ``democoind``, and ``democli``. - -Verify that everything is OK by running: - -:: - - gaiad version - -you should see: - -:: - - 0.15.0-rc1-9d90c6b - -then with: - -:: - - gaiacli version - -you should see: - -:: - - 0.15.0-rc1-9d90c6b diff --git a/docs/sdk/key-management.rst b/docs/sdk/key-management.rst deleted file mode 100644 index d2b657729..000000000 --- a/docs/sdk/key-management.rst +++ /dev/null @@ -1,18 +0,0 @@ -Key Management -============== - -Here we cover many aspects of handling keys within the Cosmos SDK framework. - -Pseudo Code ------------ - -Generating an address for an ed25519 public key (in pseudo code): - -:: - - const TypeDistinguisher = HexToBytes("1624de6220") - - // prepend the TypeDistinguisher as Bytes - SerializedBytes = TypeDistinguisher ++ PubKey.asBytes() - - Address = ripemd160(SerializedBytes) diff --git a/docs/sdk/overview.rst b/docs/sdk/overview.rst deleted file mode 100644 index 0cb7e7304..000000000 --- a/docs/sdk/overview.rst +++ /dev/null @@ -1,420 +0,0 @@ -Overview -======== - -The SDK design optimizes flexibility and security. The -framework is designed around a modular execution stack which allows -applications to mix and match elements as desired. In addition, -all modules are sandboxed for greater application security. - -Framework Overview ------------------- - -Object-Capability Model -~~~~~~~~~~~~~~~~~~~~~~~ - -When thinking about security, it's good to start with a specific threat model. Our threat model is the following: - -:: - - We assume that a thriving ecosystem of Cosmos-SDK modules that are easy to compose into a blockchain application will contain faulty or malicious modules. - -The Cosmos-SDK is designed to address this threat by being the foundation of an object capability system. - -:: - - The structural properties of object capability systems favor - modularity in code design and ensure reliable encapsulation in - code implementation. - - These structural properties facilitate the analysis of some - security properties of an object-capability program or operating - system. Some of these — in particular, information flow properties - — can be analyzed at the level of object references and - connectivity, independent of any knowledge or analysis of the code - that determines the behavior of the objects. As a consequence, - these security properties can be established and maintained in the - presence of new objects that contain unknown and possibly - malicious code. - - These structural properties stem from the two rules governing - access to existing objects: - - 1) An object A can send a message to B only if object A holds a - reference to B. - - 2) An object A can obtain a reference to C only - if object A receives a message containing a reference to C. As a - consequence of these two rules, an object can obtain a reference - to another object only through a preexisting chain of references. - In short, "Only connectivity begets connectivity." - -See the `wikipedia article <https://en.wikipedia.org/wiki/Object-capability_model>`__ for more information. - -Strictly speaking, Golang does not implement object capabilities completely, because of several issues: - -* pervasive ability to import primitive modules (e.g. "unsafe", "os") -* pervasive ability to override module vars https://github.com/golang/go/issues/23161 -* data-race vulnerability where 2+ goroutines can create illegal interface values - -The first is easy to catch by auditing imports and using a proper dependency version control system like Dep. The second and third are unfortunate but it can be audited with some cost. - -Perhaps `Go2 will implement the object capability model <https://github.com/golang/go/issues/23157>`__. - -What does it look like? -^^^^^^^^^^^^^^^^^^^^^^^ - -Only reveal what is necessary to get the work done. - -For example, the following code snippet violates the object capabilities principle: - -:: - - type AppAccount struct {...} - var account := &AppAccount{ - Address: pub.Address(), - Coins: sdk.Coins{{"ATM", 100}}, - } - var sumValue := externalModule.ComputeSumValue(account) - -The method "ComputeSumValue" implies a pure function, yet the implied capability of accepting a pointer value is the capability to modify that value. The preferred method signature should take a copy instead. - -:: - - var sumValue := externalModule.ComputeSumValue(*account) - -In the Cosmos SDK, you can see the application of this principle in the basecoin examples folder. - -:: - - // File: cosmos-sdk/examples/basecoin/app/init_handlers.go - package app - - import ( - "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/cosmos/cosmos-sdk/x/sketchy" - ) - - func (app *BasecoinApp) initRouterHandlers() { - - // All handlers must be added here. - // The order matters. - app.router.AddRoute("bank", bank.NewHandler(app.accountMapper)) - app.router.AddRoute("sketchy", sketchy.NewHandler()) - } - -In the Basecoin example, the sketchy handler isn't provided an account mapper, which does provide the bank handler with the capability (in conjunction with the context of a transaction run). - -Security Overview ------------------ - -For examples, see the `examples <https://github.com/cosmos/cosmos-sdk/tree/develop/examples>`__ directory. - -Design Goals -~~~~~~~~~~~~ - -The design of the Cosmos SDK is based on the principles of "capabilities systems". - -Capabilities systems -~~~~~~~~~~~~~~~~~~~~ - -TODO: - -* Need for module isolation -* Capability is implied permission -* Link to thesis - -Tx & Msg -~~~~~~~~ - -The SDK distinguishes between transactions (Tx) and messages -(Msg). A Tx is a Msg wrapped with authentication and fee data. - -Messages -^^^^^^^^ - -Users can create messages containing arbitrary information by -implementing the ``Msg`` interface: - -:: - - type Msg interface { - - // Return the message type. - // Must be alphanumeric or empty. - Type() string - - // Get the canonical byte representation of the Msg. - GetSignBytes() []byte - - // ValidateBasic does a simple validation check that - // doesn't require access to any other information. - ValidateBasic() error - - // Signers returns the addrs of signers that must sign. - // CONTRACT: All signatures must be present to be valid. - // CONTRACT: Returns addrs in some deterministic order. - GetSigners() []Address - } - -Messages must specify their type via the ``Type()`` method. The type should -correspond to the messages handler, so there can be many messages with the same -type. - -Messages must also specify how they are to be authenticated. The ``GetSigners()`` -method return a list of addresses that must sign the message, while the -``GetSignBytes()`` method returns the bytes that must be signed for a signature -to be valid. - -Addresses in the SDK are arbitrary byte arrays that are hex-encoded when -displayed as a string or rendered in JSON. - -Messages can specify basic self-consistency checks using the ``ValidateBasic()`` -method to enforce that message contents are well formed before any actual logic -begins. - -For instance, the ``Basecoin`` message types are defined in ``x/bank/tx.go``: - -:: - - type SendMsg struct { - Inputs []Input `json:"inputs"` - Outputs []Output `json:"outputs"` - } - - type IssueMsg struct { - Banker sdk.Address `json:"banker"` - Outputs []Output `json:"outputs"` - } - -Each specifies the addresses that must sign the message: - -:: - - func (msg SendMsg) GetSigners() []sdk.Address { - addrs := make([]sdk.Address, len(msg.Inputs)) - for i, in := range msg.Inputs { - addrs[i] = in.Address - } - return addrs - } - - func (msg IssueMsg) GetSigners() []sdk.Address { - return []sdk.Address{msg.Banker} - } - -Transactions -^^^^^^^^^^^^ - -A transaction is a message with additional information for authentication: - -:: - - type Tx interface { - - GetMsg() Msg - - // Signatures returns the signature of signers who signed the Msg. - // CONTRACT: Length returned is same as length of - // pubkeys returned from MsgKeySigners, and the order - // matches. - // CONTRACT: If the signature is missing (ie the Msg is - // invalid), then the corresponding signature is - // .Empty(). - GetSignatures() []StdSignature - } - -The ``tx.GetSignatures()`` method returns a list of signatures, which must match -the list of addresses returned by ``tx.Msg.GetSigners()``. The signatures come in -a standard form: - -:: - - type StdSignature struct { - crypto.PubKey // optional - crypto.Signature - AccountNumber int64 - Sequence int64 - } - -It contains the signature itself, as well as the corresponding account's account and -sequence numbers. The sequence number is expected to increment every time a -message is signed by a given account. The account number stays the same and is assigned -when the account is first generated. These prevent "replay attacks", where -the same message could be executed over and over again. - -The ``StdSignature`` can also optionally include the public key for verifying the -signature. An application can store the public key for each address it knows -about, making it optional to include the public key in the transaction. In the -case of Basecoin, the public key only needs to be included in the first -transaction send by a given account - after that, the public key is forever -stored by the application and can be left out of transactions. - -The standard way to create a transaction from a message is to use the ``StdTx``: - -:: - - type StdTx struct { - Msg - Signatures []StdSignature - } - -Encoding and Decoding Transactions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Messages and transactions are designed to be generic enough for developers to -specify their own encoding schemes. This enables the SDK to be used as the -framwork for constructing already specified cryptocurrency state machines, for -instance Ethereum. - -When initializing an application, a developer must specify a ``TxDecoder`` -function which determines how an arbitrary byte array should be unmarshalled -into a ``Tx``: - -:: - - type TxDecoder func(txBytes []byte) (Tx, error) - -In ``Basecoin``, we use the Tendermint wire format and the ``go-amino`` library for -encoding and decoding all message types. The ``go-amino`` library has the nice -property that it can unmarshal into interface types, but it requires the -relevant types to be registered ahead of type. Registration happens on a -``Codec`` object, so as not to taint the global name space. - -For instance, in ``Basecoin``, we wish to register the ``SendMsg`` and ``IssueMsg`` -types: - -:: - - cdc.RegisterInterface((*sdk.Msg)(nil), nil) - cdc.RegisterConcrete(bank.SendMsg{}, "cosmos-sdk/SendMsg", nil) - cdc.RegisterConcrete(bank.IssueMsg{}, "cosmos-sdk/IssueMsg", nil) - -Note how each concrete type is given a name - these name determine the type's -unique "prefix bytes" during encoding. A registered type will always use the -same prefix-bytes, regardless of what interface it is satisfying. For more -details, see the `go-amino documentation <https://github.com/tendermint/go-amino/tree/develop>`__. - - -MultiStore -~~~~~~~~~~ - -MultiStore is like a filesystem -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Mounting an IAVLStore -^^^^^^^^^^^^^^^^^^^^^ - -TODO: - -* IAVLStore: Fast balanced dynamic Merkle store. - - * supports iteration. - -* MultiStore: multiple Merkle tree backends in a single store - - * allows using Ethereum Patricia Trie and Tendermint IAVL in same app - -* Provide caching for intermediate state during execution of blocks and transactions (including for iteration) -* Historical state pruning and snapshotting. -* Query proofs (existence, absence, range, etc.) on current and retained historical state. - -Context -------- - -The SDK uses a ``Context`` to propogate common information across functions. The -``Context`` is modelled after the Golang ``context.Context`` object, which has -become ubiquitous in networking middleware and routing applications as a means -to easily propogate request context through handler functions. - -The main information stored in the ``Context`` includes the application -MultiStore (see below), the last block header, and the transaction bytes. -Effectively, the context contains all data that may be necessary for processing -a transaction. - -Many methods on SDK objects receive a context as the first argument. - -Handler -------- - -Transaction processing in the SDK is defined through ``Handler`` functions: - -:: - - type Handler func(ctx Context, tx Tx) Result - -A handler takes a context and a transaction and returns a result. All -information necessary for processing a transaction should be available in the -context. - -While the context holds the entire application state (all referenced from the -root MultiStore), a particular handler only needs a particular kind of access -to a particular store (or two or more). Access to stores is managed using -capabilities keys and mappers. When a handler is initialized, it is passed a -key or mapper that gives it access to the relevant stores. - -:: - - // File: cosmos-sdk/examples/basecoin/app/init_stores.go - app.BaseApp.MountStore(app.capKeyMainStore, sdk.StoreTypeIAVL) - app.accountMapper = auth.NewAccountMapper( - app.capKeyMainStore, // target store - &types.AppAccount{}, // prototype - ) - - // File: cosmos-sdk/examples/basecoin/app/init_handlers.go - app.router.AddRoute("bank", bank.NewHandler(app.accountMapper)) - - // File: cosmos-sdk/x/bank/handler.go - // NOTE: Technically, NewHandler only needs a CoinMapper - func NewHandler(am sdk.AccountMapper) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - cm := CoinMapper{am} - ... - } - } - -AnteHandler ------------ - -Handling Fee payment -~~~~~~~~~~~~~~~~~~~~ - -Handling Authentication -~~~~~~~~~~~~~~~~~~~~~~~ - -Accounts and x/auth -------------------- - -sdk.Account -~~~~~~~~~~~ - -auth.BaseAccount -~~~~~~~~~~~~~~~~ - -auth.AccountMapper -~~~~~~~~~~~~~~~~~~ - -Wire codec ----------- - -Why another codec? -~~~~~~~~~~~~~~~~~~ - -vs encoding/json -~~~~~~~~~~~~~~~~ - -vs protobuf -~~~~~~~~~~~ - -KVStore example ---------------- - -Basecoin example ----------------- - -The quintessential SDK application is Basecoin - a simple -multi-asset cryptocurrency. Basecoin consists of a set of -accounts stored in a Merkle tree, where each account may have -many coins. There are two message types: SendMsg and IssueMsg. -SendMsg allows coins to be sent around, while IssueMsg allows a -set of predefined users to issue new coins. diff --git a/docs/spec/README.md b/docs/spec/README.md index e7507bf95..0b708aba2 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -1,19 +1,27 @@ # Cosmos Hub Spec -This directory contains specifications for the application level components of -the Cosmos Hub. +This directory contains specifications for the state transition machine of the +Cosmos Hub. -NOTE: the specifications are not yet complete and very much a work in progress. +The Cosmos Hub holds all of its state in a Merkle store. Updates to +the store may be made during transactions and at the beginning and end of every +block. -- [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for - sending tokens. -- [Staking](staking) - Proof of Stake related specifications including bonding - and delegation transactions, inflation, fees, etc. -- [Governance](governance) - Governance related specifications including - proposals and voting. -- [IBC](ibc) - Specification of the Cosmos inter-blockchain communication (IBC) protocol. +While the first implementation of the Cosmos Hub is built using the Cosmos-SDK, +these specifications aim to be independent of any implementation details. That +said, they provide a detailed resource for understanding the Cosmos-SDK. + +- [Store](store) - The core Merkle store that holds the state. +- [Auth](auth) - The structure and authentication of accounts and transactions. +- [Bank](bank) - Sending tokens. +- [Governance](governance) - Proposals and voting. +- [Staking](staking) - Proof-of-stake bonding, delegation, etc. +- [Slashing](slashing) - Validator punishment mechanisms. +- [Provisioning](provisioning) - Fee distribution, and atom provision distribution +- [IBC](ibc) - Inter-Blockchain Communication (IBC) protocol. - [Other](other) - Other components of the Cosmos Hub, including the reserve pool, All in Bits vesting, etc. -The [specification for Tendermint](https://github.com/tendermint/tendermint/tree/develop/docs/specification/new-spec), -i.e. the underlying blockchain, can be found elsewhere. +For details on the underlying blockchain and p2p protocols, see +the [Tendermint specification](https://github.com/tendermint/tendermint/tree/develop/docs/spec). + diff --git a/tools/bin/.gitkeep b/docs/spec/auth/state.md similarity index 100% rename from tools/bin/.gitkeep rename to docs/spec/auth/state.md diff --git a/docs/spec/auth/transactions.md b/docs/spec/auth/transactions.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/spec/basecoin/basecoin.md b/docs/spec/bank/state.md similarity index 100% rename from docs/spec/basecoin/basecoin.md rename to docs/spec/bank/state.md diff --git a/docs/spec/bank/transactions.md b/docs/spec/bank/transactions.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/spec/governance/state.md b/docs/spec/governance/state.md index 91cfa1f76..d538c765c 100644 --- a/docs/spec/governance/state.md +++ b/docs/spec/governance/state.md @@ -31,8 +31,7 @@ const ( type Procedure struct { VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks MinDeposit int64 // Minimum deposit for a proposal to enter voting period. - VoteTypes []VoteType // Vote types available to voters. - ProposalTypes []ProposalType // Proposal types available to submitters. + ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal} Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months diff --git a/docs/spec/governance/transactions.md b/docs/spec/governance/transactions.md index aacf046c1..9f93b2636 100644 --- a/docs/spec/governance/transactions.md +++ b/docs/spec/governance/transactions.md @@ -183,8 +183,8 @@ vote on the proposal. ```go type TxGovVote struct { ProposalID int64 // proposalID of the proposal - Option string // option from OptionSet chosen by the voter - Address crypto.address // Address of the validator voter wants to tie its vote to + Option string // option chosen by the voter + ValidatorAddress crypto.address // Address of the validator voter wants to tie its vote to } ``` @@ -224,17 +224,15 @@ handled: throw validator = load(CurrentValidators, txGovVote.Address) - - if !proposal.InitProcedure.OptionSet.includes(txGovVote.Option) OR - (validator == nil) then - - // Throws if - // Option is not in Option Set of procedure that was active when vote opened OR if - // Address is not the address of a current validator - - throw - - option = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.Address>) + if (validator == nil) then + + // Throws if + // ValidatorAddress is not the address of a current validator + + throw + + else + option = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>) if (option != nil) // sender has already voted with the Atoms bonded to Address diff --git a/docs/spec/other/bech32.md b/docs/spec/other/bech32.md new file mode 100644 index 000000000..1d337ef6c --- /dev/null +++ b/docs/spec/other/bech32.md @@ -0,0 +1,25 @@ +# Bech32 on Cosmos + +The Cosmos network prefers to use the Bech32 address format whereever users must handle binary data. Bech32 encoding provides robust integrity checks on data and the human readable part(HRP) provides contextual hints that can assist UI developers with providing informative error messages. + +In the Cosmos network, keys and addresses may refer to a number of different roles in the network like accounts, validators etc. + + +## HRP table + +| HRP | Definition | +| ------------- |:-------------:| +| `cosmosaccaddr` | Cosmos Account Address | +| `cosmosaccpub` | Cosmos Account Public Key | +| `cosmosvaladdr` | Cosmos Consensus Address | +| `cosmosvalpub` | Cosmos Consensus Public Key| + +## Encoding + +While all user facing interfaces to Cosmos software should exposed bech32 interfaces, many internal interfaces encode binary value in hex or base64 encoded form. + +To covert between other binary reprsentation of addresses and keys, it is important to first apply the Amino enocoding process before bech32 encoding. + +A complete implementation of the Amino serialization format is unncessary in most cases. Simply prepending bytes from this [table](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/encoding.md#public-key-cryptography) to the bytestring payload before bech32 encoding will sufficient for compatible representation. + +  \ No newline at end of file diff --git a/docs/spec/staking/AbsoluteFeeDistrModel.xlsx b/docs/spec/provisioning/fee_distribution_model.xlsx similarity index 100% rename from docs/spec/staking/AbsoluteFeeDistrModel.xlsx rename to docs/spec/provisioning/fee_distribution_model.xlsx diff --git a/docs/spec/provisioning/overview.md b/docs/spec/provisioning/overview.md new file mode 100644 index 000000000..046223a4b --- /dev/null +++ b/docs/spec/provisioning/overview.md @@ -0,0 +1,229 @@ +# Fee Distribution + +## Overview + +Fees are pooled separately and withdrawn lazily, at any time. They are not +bonded, and can be paid in multiple tokens. An adjustment factor is maintained +for each validator and delegator to determine the true proportion of fees in +the pool they are entitled too. Adjustment factors are updated every time a +validator or delegator's voting power changes. Validators and delegators must +withdraw all fees they are entitled too before they can bond or unbond Atoms. + +## Affect on Staking + +Because fees are optimized to note + +Commission on Atom Provisions and having atoms autobonded are mutually +exclusive (we can’t have both). The reason for this is that if there are atoms +commissions and autobonding, the portion of atoms the fee distribution +calculation would become very large as the atom portion for each delegator +would change each block making a withdrawal of fees for a delegator require a +calculation for every single block since the last withdrawal. Conclusion we can +only have atom commission and unbonded atoms provisions, or bonded atom +provisions and no atom commission + +## Fee Calculations + +Collected fees are pooled globally and divided out passively to validators and +delegators. Each validator has the opportunity to charge commission to the +delegators on the fees collected on behalf of the delegators by the validators. +Fees are paid directly into a global fee pool. Due to the nature of of passive +accounting whenever changes to parameters which affect the rate of fee +distribution occurs, withdrawal of fees must also occur. + + - when withdrawing one must withdrawal the maximum amount they are entitled + too, leaving nothing in the pool, + - when bonding, unbonding, or re-delegating tokens to an existing account a + full withdrawal of the fees must occur (as the rules for lazy accounting + change), + - when a validator chooses to change the commission on fees, all accumulated + commission fees must be simultaneously withdrawn. + +When the validator is the proposer of the round, that validator (and their +delegators) receives between 1% and 5% of fee rewards, the reserve tax is then +charged, then the remainder is distributed socially by voting power to all +validators including the proposer validator. The amount of proposer reward is +calculated from pre-commits Tendermint messages. All provision rewards are +added to a provision reward pool which validator holds individually. Here note +that `BondedShares` represents the sum of all voting power saved in the +`GlobalState` (denoted `gs`). + +``` +proposerReward = feesCollected * (0.01 + 0.04 + * sumOfVotingPowerOfPrecommitValidators / gs.BondedShares) +validator.ProposerRewardPool += proposerReward + +reserveTaxed = feesCollected * params.ReserveTax +gs.ReservePool += reserveTaxed + +distributedReward = feesCollected - proposerReward - reserveTaxed +gs.FeePool += distributedReward +gs.SumFeesReceived += distributedReward +gs.RecentFee = distributedReward +``` + +The entitlement to the fee pool held by the each validator can be accounted for +lazily. First we must account for a validator's `count` and `adjustment`. The +`count` represents a lazy accounting of what that validators entitlement to the +fee pool would be if there `VotingPower` was to never change and they were to +never withdraw fees. + +``` +validator.count = validator.VotingPower * BlockHeight +``` + +Similarly the GlobalState count can be passively calculated whenever needed, +where `BondedShares` is the updated sum of voting powers from all validators. + +``` +gs.count = gs.BondedShares * BlockHeight +``` + +The `adjustment` term accounts for changes in voting power and withdrawals of +fees. The adjustment factor must be persisted with the validator and modified +whenever fees are withdrawn from the validator or the voting power of the +validator changes. When the voting power of the validator changes the +`Adjustment` factor is increased/decreased by the cumulative difference in the +voting power if the voting power has been the new voting power as opposed to +the old voting power for the entire duration of the blockchain up the previous +block. Each time there is an adjustment change the GlobalState (denoted `gs`) +`Adjustment` must also be updated. + +``` +simplePool = validator.count / gs.count * gs.SumFeesReceived +projectedPool = validator.PrevPower * (height-1) + / (gs.PrevPower * (height-1)) * gs.PrevFeesReceived + + validator.Power / gs.Power * gs.RecentFee + +AdjustmentChange = simplePool - projectedPool +validator.AdjustmentRewardPool += AdjustmentChange +gs.Adjustment += AdjustmentChange +``` + +Every instance that the voting power changes, information about the state of +the validator set during the change must be recorded as a `powerChange` for +other validators to run through. Before any validator modifies its voting power +it must first run through the above calculation to determine the change in +their `caandidate.AdjustmentRewardPool` for all historical changes in the set +of `powerChange` which they have not yet synced to. The set of all +`powerChange` may be trimmed from its oldest members once all validators have +synced past the height of the oldest `powerChange`. This trim procedure will +occur on an epoch basis. + +```golang +type powerChange struct { + height int64 // block height at change + power rational.Rat // total power at change + prevpower rational.Rat // total power at previous height-1 + feesin coins.Coin // fees in at block height + prevFeePool coins.Coin // total fees in at previous block height +} +``` + +Note that the adjustment factor may result as negative if the voting power of a +different validator has decreased. + +``` +validator.AdjustmentRewardPool += withdrawn +gs.Adjustment += withdrawn +``` + +Now the entitled fee pool of each validator can be lazily accounted for at +any given block: + +``` +validator.feePool = validator.simplePool - validator.Adjustment +``` + +So far we have covered two sources fees which can be withdrawn from: Fees from +proposer rewards (`validator.ProposerRewardPool`), and fees from the fee pool +(`validator.feePool`). However we should note that all fees from fee pool are +subject to commission rate from the owner of the validator. These next +calculations outline the math behind withdrawing fee rewards as either a +delegator to a validator providing commission, or as the owner of a validator +who is receiving commission. + +### Calculations For Delegators and Validators + +The same mechanism described to calculate the fees which an entire validator is +entitled to is be applied to delegator level to determine the entitled fees for +each delegator and the validators entitled commission from `gs.FeesPool` and +`validator.ProposerRewardPool`. + +The calculations are identical with a few modifications to the parameters: + - Delegator's entitlement to `gs.FeePool`: + - entitled party voting power should be taken as the effective voting power + after commission is retrieved, + `bond.Shares/validator.TotalDelegatorShares * validator.VotingPower * (1 - validator.Commission)` + - Delegator's entitlement to `validator.ProposerFeePool` + - global power in this context is actually shares + `validator.TotalDelegatorShares` + - entitled party voting power should be taken as the effective shares after + commission is retrieved, `bond.Shares * (1 - validator.Commission)` + - Validator's commission entitlement to `gs.FeePool` + - entitled party voting power should be taken as the effective voting power + of commission portion of total voting power, + `validator.VotingPower * validator.Commission` + - Validator's commission entitlement to `validator.ProposerFeePool` + - global power in this context is actually shares + `validator.TotalDelegatorShares` + - entitled party voting power should be taken as the of commission portion + of total delegators shares, + `validator.TotalDelegatorShares * validator.Commission` + +For more implementation ideas see spreadsheet `spec/AbsoluteFeeDistrModel.xlsx` + +As mentioned earlier, every time the voting power of a delegator bond is +changing either by unbonding or further bonding, all fees must be +simultaneously withdrawn. Similarly if the validator changes the commission +rate, all commission on fees must be simultaneously withdrawn. + +### Other general notes on fees accounting + +- When a delegator chooses to re-delegate shares, fees continue to accumulate + until the re-delegation queue reaches maturity. At the block which the queue + reaches maturity and shares are re-delegated all available fees are + simultaneously withdrawn. +- Whenever a totally new validator is added to the validator set, the `accum` + of the entire validator must be 0, meaning that the initial value for + `validator.Adjustment` must be set to the value of `canidate.Count` for the + height which the validator is added on the validator set. +- The feePool of a new delegator bond will be 0 for the height at which the bond + was added. This is achieved by setting `DelegatorBond.FeeWithdrawalHeight` to + the height which the bond was added. + +### Atom provisions + +Validator provisions are minted on an hourly basis (the first block of a new +hour). The annual target of between 7% and 20%. The long-term target ratio of +bonded tokens to unbonded tokens is 67%. + +The target annual inflation rate is recalculated for each provisions cycle. The +inflation is also subject to a rate change (positive or negative) depending on +the distance from the desired ratio (67%). The maximum rate change possible is +defined to be 13% per year, however the annual inflation is capped as between +7% and 20%. + +```go +inflationRateChange(0) = 0 +Inflation(0) = 0.07 + +bondedRatio = Pool.BondedTokens / Pool.TotalSupplyTokens +AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13 + +annualInflation += AnnualInflationRateChange + +if annualInflation > 0.20 then Inflation = 0.20 +if annualInflation < 0.07 then Inflation = 0.07 + +provisionTokensHourly = Pool.TotalSupplyTokens * Inflation / (365.25*24) +``` + +Because the validators hold a relative bonded share (`GlobalStakeShares`), when +more bonded tokens are added proportionally to all validators, the only term +which needs to be updated is the `GlobalState.BondedPool`. So for each +provisions cycle: + +```go +Pool.BondedPool += provisionTokensHourly +``` diff --git a/docs/spec/provisioning/state.md b/docs/spec/provisioning/state.md new file mode 100644 index 000000000..0711b01aa --- /dev/null +++ b/docs/spec/provisioning/state.md @@ -0,0 +1,13 @@ + + +Validator + +* Adjustment factor used to passively calculate each validators entitled fees + from `GlobalState.FeePool` + +Delegation Shares + +* AdjustmentFeePool: Adjustment factor used to passively calculate each bonds + entitled fees from `GlobalState.FeePool` +* AdjustmentRewardPool: Adjustment factor used to passively calculate each + bonds entitled fees from `Validator.ProposerRewardPool` diff --git a/docs/spec/slashing/end_block.md b/docs/spec/slashing/end_block.md new file mode 100644 index 000000000..3eec27372 --- /dev/null +++ b/docs/spec/slashing/end_block.md @@ -0,0 +1,115 @@ +# End-Block + +## Slashing + +Tendermint blocks can include +[Evidence](https://github.com/tendermint/tendermint/blob/develop/docs/spec/blockchain/blockchain.md#evidence), which indicates that a validator +committed malicious behaviour. The relevant information is forwarded to the +application as [ABCI +Evidence](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto#L259), so the validator an be accordingly punished. + +For some `evidence` to be valid, it must satisfy: + +`evidence.Timestamp >= block.Timestamp - MAX_EVIDENCE_AGE` + +where `evidence.Timestamp` is the timestamp in the block at height +`evidence.Height` and `block.Timestamp` is the current block timestamp. + +If valid evidence is included in a block, the validator's stake is reduced by `SLASH_PROPORTION` of +what their stake was when the infraction occurred (rather than when the evidence was discovered). +We want to "follow the stake": the stake which contributed to the infraction should be +slashed, even if it has since been redelegated or started unbonding. + +We first need to loop through the unbondings and redelegations from the slashed validator +and track how much stake has since moved: + +``` +slashAmountUnbondings := 0 +slashAmountRedelegations := 0 + +unbondings := getUnbondings(validator.Address) +for unbond in unbondings { + + if was not bonded before evidence.Height or started unbonding before unbonding period ago { + continue + } + + burn := unbond.InitialTokens * SLASH_PROPORTION + slashAmountUnbondings += burn + + unbond.Tokens = max(0, unbond.Tokens - burn) +} + +// only care if source gets slashed because we're already bonded to destination +// so if destination validator gets slashed our delegation just has same shares +// of smaller pool. +redels := getRedelegationsBySource(validator.Address) +for redel in redels { + + if was not bonded before evidence.Height or started redelegating before unbonding period ago { + continue + } + + burn := redel.InitialTokens * SLASH_PROPORTION + slashAmountRedelegations += burn + + amount := unbondFromValidator(redel.Destination, burn) + destroy(amount) +} +``` + +We then slash the validator: + +``` +curVal := validator +oldVal := loadValidator(evidence.Height, evidence.Address) + +slashAmount := SLASH_PROPORTION * oldVal.Shares +slashAmount -= slashAmountUnbondings +slashAmount -= slashAmountRedelegations + +curVal.Shares = max(0, curVal.Shares - slashAmount) +``` + +This ensures that offending validators are punished the same amount whether they +act as a single validator with X stake or as N validators with collectively X +stake. + +## Automatic Unbonding + +At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded: + +``` +height := block.Height + +for val in block.Validators: + signInfo = SigningInfo.Get(val.Address) + if signInfo == nil{ + signInfo.StartHeight = height + } + + index := signInfo.IndexOffset % SIGNED_BLOCKS_WINDOW + signInfo.IndexOffset++ + previous = SigningBitArray.Get(val.Address, index) + + // update counter if array has changed + if previous and val in block.AbsentValidators: + SigningBitArray.Set(val.Address, index, false) + signInfo.SignedBlocksCounter-- + else if !previous and val not in block.AbsentValidators: + SigningBitArray.Set(val.Address, index, true) + signInfo.SignedBlocksCounter++ + // else previous == val not in block.AbsentValidators, no change + + // validator must be active for at least SIGNED_BLOCKS_WINDOW + // before they can be automatically unbonded for failing to be + // included in 50% of the recent LastCommits + minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW + minSigned = SIGNED_BLOCKS_WINDOW / 2 + if height > minHeight AND signInfo.SignedBlocksCounter < minSigned: + signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION + + slash & unbond the validator + + SigningInfo.Set(val.Address, signInfo) +``` diff --git a/docs/spec/slashing/state.md b/docs/spec/slashing/state.md new file mode 100644 index 000000000..1df9d5022 --- /dev/null +++ b/docs/spec/slashing/state.md @@ -0,0 +1,51 @@ +## State + +### Signing Info + +Every block includes a set of precommits by the validators for the previous block, +known as the LastCommit. A LastCommit is valid so long as it contains precommits from +2/3 of voting power. + +Proposers are incentivized to include precommits from all +validators in the LastCommit by receiving additional fees +proportional to the difference between the voting power included in the +LastCommit and +2/3 (see [TODO](https://github.com/cosmos/cosmos-sdk/issues/967)). + +Validators are penalized for failing to be included in the LastCommit for some +number of blocks by being automatically unbonded. + +Information about validator activity is tracked in a `ValidatorSigningInfo`. +It is indexed in the store as follows: + +- SigningInfo: ` 0x01 | ValTendermintAddr -> amino(valSigningInfo)` +- SigningBitArray: ` 0x02 | ValTendermintAddr | LittleEndianUint64(signArrayIndex) -> VarInt(didSign)` + +The first map allows us to easily lookup the recent signing info for a +validator, according to the Tendermint validator address. The second map acts as +a bit-array of size `SIGNED_BLOCKS_WINDOW` that tells us if the validator signed for a given index in the bit-array. + +The index in the bit-array is given as little endian uint64. + +The result is a `varint` that takes on `0` or `1`, where `0` indicates the +validator did not sign the corresponding block, and `1` indicates they did. + +Note that the SigningBitArray is not explicitly initialized up-front. Keys are +added as we progress through the first `SIGNED_BLOCKS_WINDOW` blocks for a newly +bonded validator. + +The information stored for tracking validator liveness is as follows: + +```go +type ValidatorSigningInfo struct { + StartHeight int64 + IndexOffset int64 + JailedUntil int64 + SignedBlocksCounter int64 +} + +``` + +Where: +* `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power). +* `IndexOffset` is incremented each time the candidate was a bonded validator in a block (and may have signed a precommit or not). +* `JailedUntil` is set whenever the candidate is revoked due to downtime +* `SignedBlocksCounter` is a counter kept to avoid unnecessary array reads. `SignedBlocksBitArray.Sum() == SignedBlocksCounter` always. diff --git a/docs/spec/slashing/transactions.md b/docs/spec/slashing/transactions.md new file mode 100644 index 000000000..cdf495e4d --- /dev/null +++ b/docs/spec/slashing/transactions.md @@ -0,0 +1,19 @@ + +### TxProveLive + +If a validator was automatically unbonded due to liveness issues and wishes to +assert it is still online, it can send `TxProveLive`: + +```golang +type TxProveLive struct { + PubKey crypto.PubKey +} +``` + +All delegators in the temporary unbonding pool which have not +transacted to move will be bonded back to the now-live validator and begin to +once again collect provisions and rewards. + +``` +TODO: pseudo-code +``` diff --git a/docs/spec/staking/README.md b/docs/spec/staking/README.md index 82604e2de..30dbf1dd3 100644 --- a/docs/spec/staking/README.md +++ b/docs/spec/staking/README.md @@ -2,30 +2,38 @@ ## Abstract -This paper specifies the Staking module of the Cosmos-SDK, which was first described in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper) in June 2016. +This paper specifies the Staking module of the Cosmos-SDK, which was first +described in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper) +in June 2016. -The module enables Cosmos-SDK based blockchain to support an advanced Proof-of-Stake system. In this system, holders of the native staking token of the chain can become candidate validators and can delegate tokens to candidate validators, ultimately determining the effective validator set for the system. +The module enables Cosmos-SDK based blockchain to support an advanced +Proof-of-Stake system. In this system, holders of the native staking token of +the chain can become validators and can delegate tokens to validator +validators, ultimately determining the effective validator set for the system. -This module will be used in the Cosmos Hub, the first Hub in the Cosmos network. +This module will be used in the Cosmos Hub, the first Hub in the Cosmos +network. ## Contents -The following specification uses *Atom* as the native staking token. The module can be adapted to any Proof-Of-Stake blockchain by replacing *Atom* with the native staking token of the chain. +The following specification uses *Atom* as the native staking token. The module +can be adapted to any Proof-Of-Stake blockchain by replacing *Atom* with the +native staking token of the chain. 1. **[Design overview](overview.md)** 2. **Implementation** 1. **[State](state.md)** - 1. Global State - 2. Validator Candidates - 3. Delegator Bonds - 4. Unbond and Rebond Queue + 1. Params + 1. Pool + 2. Validators + 3. Delegations 2. **[Transactions](transactions.md)** - 1. Declare Candidacy - 2. Edit Candidacy - 3. Delegate - 4. Unbond - 5. Redelegate - 6. ProveLive + 1. Create-Validator + 2. Edit-Validator + 3. Repeal-Revocation + 4. Delegate + 5. Unbond + 6. Redelegate 3. **[Validator Set Changes](valset-changes.md)** 1. Validator set updates 2. Slashing diff --git a/docs/spec/staking/end_block.md b/docs/spec/staking/end_block.md new file mode 100644 index 000000000..61643f526 --- /dev/null +++ b/docs/spec/staking/end_block.md @@ -0,0 +1,61 @@ +# End-Block + +Two staking activities are intended to be processed in the application end-block. + - inform Tendermint of validator set changes + - process and set atom inflation + +# Validator Set Changes + +The Tendermint validator set may be updated by state transitions that run at +the end of every block. The Tendermint validator set may be changed by +validators either being revoked due to inactivity/unexpected behaviour (covered +in slashing) or changed in validator power. Determining which validator set +changes must be made occurs during staking transactions (and slashing +transactions) - during end-block the already accounted changes are applied and +the changes cleared + +```golang +EndBlock() ValidatorSetChanges + vsc = GetTendermintUpdates() + ClearTendermintUpdates() + return vsc +``` + +# Inflation + +The atom inflation rate is changed once per hour based on the current and +historic bond ratio + +```golang +processProvisions(): + hrsPerYr = 8766 // as defined by a julian year of 365.25 days + + time = BFTTime() + if time > pool.InflationLastTime + ProvisionTimeout + pool.InflationLastTime = time + pool.Inflation = nextInflation(hrsPerYr).Round(1000000000) + + provisions = pool.Inflation * (pool.TotalSupply / hrsPerYr) + + pool.LooseTokens += provisions + feePool += LooseTokens + + setPool(pool) + +nextInflation(hrsPerYr rational.Rat): + if pool.TotalSupply > 0 + bondedRatio = pool.BondedPool / pool.TotalSupply + else + bondedRation = 0 + + inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange + inflationRateChange = inflationRateChangePerYear / hrsPerYr + + inflation = pool.Inflation + inflationRateChange + if inflation > params.InflationMax then inflation = params.InflationMax + + if inflation < params.InflationMin then inflation = params.InflationMin + + return inflation +``` + diff --git a/docs/spec/staking/overview.md b/docs/spec/staking/overview.md deleted file mode 100644 index a202fbc11..000000000 --- a/docs/spec/staking/overview.md +++ /dev/null @@ -1,214 +0,0 @@ -# Staking Module - -## Overview - -The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that -serves as a backbone of the Cosmos ecosystem. It is operated and secured by an -open and globally decentralized set of validators. Tendermint consensus is a -Byzantine fault-tolerant distributed protocol that involves all validators in -the process of exchanging protocol messages in the production of each block. To -avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up -coins in a bond deposit. Tendermint protocol messages are signed by the -validator's private key, and this is a basis for Tendermint strict -accountability that allows punishing misbehaving validators by slashing -(burning) their bonded Atoms. On the other hand, validators are rewarded for -their service of securing blockchain network by the inflationary provisions and -transactions fees. This incentives correct behavior of the validators and -provides the economic security of the network. - -The native token of the Cosmos Hub is called Atom; becoming a validator of the -Cosmos Hub requires holding Atoms. However, not all Atom holders are validators -of the Cosmos Hub. More precisely, there is a selection process that determines -the validator set as a subset of all validator candidates (Atom holders that -wants to become a validator). The other option for Atom holder is to delegate -their atoms to validators, i.e., being a delegator. A delegator is an Atom -holder that has bonded its Atoms by delegating it to a validator (or validator -candidate). By bonding Atoms to secure the network (and taking a risk of being -slashed in case of misbehaviour), a user is rewarded with inflationary -provisions and transaction fees proportional to the amount of its bonded Atoms. -The Cosmos Hub is designed to efficiently facilitate a small numbers of -validators (hundreds), and large numbers of delegators (tens of thousands). -More precisely, it is the role of the Staking module of the Cosmos Hub to -support various staking functionality including validator set selection, -delegating, bonding and withdrawing Atoms, and the distribution of inflationary -provisions and transaction fees. - -## Basic Terms and Definitions - -* Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system -* Atom - native token of the Cosmsos Hub -* Atom holder - an entity that holds some amount of Atoms -* Candidate - an Atom holder that is actively involved in the Tendermint - blockchain protocol (running Tendermint Full Node (TODO: add link to Full - Node definition) and is competing with other candidates to be elected as a - validator (TODO: add link to Validator definition)) -* Validator - a candidate that is currently selected among a set of candidates - to be able to sign protocol messages in the Tendermint consensus protocol -* Delegator - an Atom holder that has bonded some of its Atoms by delegating - them to a validator (or a candidate) -* Bonding Atoms - a process of locking Atoms in a bond deposit (putting Atoms - under protocol control). Atoms are always bonded through a validator (or - candidate) process. Bonded atoms can be slashed (burned) in case a validator - process misbehaves (does not behave according to the protocol specification). - Atom holders can regain access to their bonded Atoms if they have not been - slashed by waiting an Unbonding period. -* Unbonding period - a period of time after which Atom holder gains access to - its bonded Atoms (they can be withdrawn to a user account) or they can be - re-delegated. -* Inflationary provisions - inflation is the process of increasing the Atom supply. - Atoms are periodically created on the Cosmos Hub and issued to bonded Atom holders. - The goal of inflation is to incentize most of the Atoms in existence to be bonded. -* Transaction fees - transaction fee is a fee that is included in a Cosmsos Hub - transaction. The fees are collected by the current validator set and - distributed among validators and delegators in proportion to their bonded - Atom share. -* Commission fee - a fee taken from the transaction fees by a validator for - their service - -## The pool and the share - -At the core of the Staking module is the concept of a pool which denotes a -collection of Atoms contributed by different Atom holders. There are two global -pools in the Staking module: the bonded pool and unbonding pool. Bonded Atoms -are part of the global bonded pool. If a candidate or delegator wants to unbond -its Atoms, those Atoms are moved to the the unbonding pool for the duration of -the unbonding period. In the Staking module, a pool is a logical concept, i.e., -there is no pool data structure that would be responsible for managing pool -resources. Instead, it is managed in a distributed way. More precisely, at the -global level, for each pool, we track only the total amount of bonded or unbonded -Atoms and the current amount of issued shares. A share is a unit of Atom distribution -and the value of the share (share-to-atom exchange rate) changes during -system execution. The share-to-atom exchange rate can be computed as: - -`share-to-atom-exchange-rate = size of the pool / ammount of issued shares` - -Then for each validator candidate (in a per candidate data structure) we keep track of -the amount of shares the candidate owns in a pool. At any point in time, -the exact amount of Atoms a candidate has in the pool can be computed as the -number of shares it owns multiplied with the current share-to-atom exchange rate: - -`candidate-coins = candidate.Shares * share-to-atom-exchange-rate` - -The benefit of such accounting of the pool resources is the fact that a -modification to the pool from bonding/unbonding/slashing/provisioning of -Atoms affects only global data (size of the pool and the number of shares) and -not the related validator/candidate data structure, i.e., the data structure of -other validators do not need to be modified. This has the advantage that -modifying global data is much cheaper computationally than modifying data of -every validator. Let's explain this further with several small examples: - -We consider initially 4 validators p1, p2, p3 and p4, and that each validator -has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have -issued initially 40 shares (note that the initial distribution of the shares, -i.e., share-to-atom exchange rate can be set to any meaningful value), i.e., -share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we -have, the size of the pool is 40 Atoms, and the amount of issued shares is -equal to 40. And for each validator we store in their corresponding data -structure that each has 10 shares of the bonded pool. Now lets assume that the -validator p4 starts process of unbonding of 5 shares. Then the total size of -the pool is decreased and now it will be 35 shares and the amount of Atoms is -35 . Note that the only change in other data structures needed is reducing the -number of shares for a validator p4 from 10 to 5. - -Let's consider now the case where a validator p1 wants to bond 15 more atoms to -the pool. Now the size of the pool is 50, and as the exchange rate hasn't -changed (1 share is still worth 1 Atom), we need to create more shares, i.e. we -now have 50 shares in the pool in total. Validators p2, p3 and p4 still have -(correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we -don't need to modify anything in their corresponding data structures. But p1 -now has 25 shares, so we update the amount of shares owned by p1 in its -data structure. Note that apart from the size of the pool that is in Atoms, all -other data structures refer only to shares. - -Finally, let's consider what happens when new Atoms are created and added to -the pool due to inflation. Let's assume that the inflation rate is 10 percent -and that it is applied to the current state of the pool. This means that 5 -Atoms are created and added to the pool and that each validator now -proportionally increase it's Atom count. Let's analyse how this change is -reflected in the data structures. First, the size of the pool is increased and -is now 55 atoms. As a share of each validator in the pool hasn't changed, this -means that the total number of shares stay the same (50) and that the amount of -shares of each validator stays the same (correspondingly 25, 10, 10, 5). But -the exchange rate has changed and each share is now worth 55/50 Atoms per -share, so each validator has effectively increased amount of Atoms it has. So -validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms. - -The concepts of the pool and its shares is at the core of the accounting in the -Staking module. It is used for managing the global pools (such as bonding and -unbonding pool), but also for distribution of Atoms between validator and its -delegators (we will explain this in section X). - -#### Delegator shares - -A candidate is, depending on it's status, contributing Atoms to either the -bonded or unbonding pool, and in return gets some amount of (global) pool -shares. Note that not all those Atoms (and respective shares) are owned by the -candidate as some Atoms could be delegated to a candidate. The mechanism for -distribution of Atoms (and shares) between a candidate and it's delegators is -based on a notion of delegator shares. More precisely, every candidate is -issuing (local) delegator shares (`Candidate.IssuedDelegatorShares`) that -represents some portion of global shares managed by the candidate -(`Candidate.GlobalStakeShares`). The principle behind managing delegator shares -is the same as described in [Section](#The pool and the share). We now -illustrate it with an example. - -Let's consider 4 validators p1, p2, p3 and p4, and assume that each validator -has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have -issued initially 40 global shares, i.e., that -`share-to-atom-exchange-rate = 1 atom per share`. So we will set -`GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the -Candidate data structure of each validator `Candidate.GlobalStakeShares = 10`. -Furthermore, each validator issued 10 delegator shares which are initially -owned by itself, i.e., `Candidate.IssuedDelegatorShares = 10`, where -`delegator-share-to-global-share-ex-rate = 1 global share per delegator share`. -Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and -consider what are the updates we need to make to the data structures. First, -`GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, for -validator p1 we have `Candidate.GlobalStakeShares = 15`, but we also need to -issue also additional delegator shares, i.e., -`Candidate.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator -shares of validator p1, where each delegator share is worth 1 global shares, -i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due to -inflation. In that case, we only need to update `GlobalState.BondedPool` which -is now equal to 50 Atoms as created Atoms are added to the bonded pool. Note -that the amount of global and delegator shares stay the same but they are now -worth more as share-to-atom-exchange-rate is now worth 50/45 Atoms per share. -Therefore, a delegator d1 now owns: - -`delegatorCoins = 5 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 5.55 Atoms` - -### Inflation provisions - -Validator provisions are minted on an hourly basis (the first block of a new -hour). The annual target of between 7% and 20%. The long-term target ratio of -bonded tokens to unbonded tokens is 67%. - -The target annual inflation rate is recalculated for each provisions cycle. The -inflation is also subject to a rate change (positive or negative) depending on -the distance from the desired ratio (67%). The maximum rate change possible is -defined to be 13% per year, however the annual inflation is capped as between -7% and 20%. - -```go -inflationRateChange(0) = 0 -GlobalState.Inflation(0) = 0.07 - -bondedRatio = GlobalState.BondedPool / GlobalState.TotalSupply -AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13 - -annualInflation += AnnualInflationRateChange - -if annualInflation > 0.20 then GlobalState.Inflation = 0.20 -if annualInflation < 0.07 then GlobalState.Inflation = 0.07 - -provisionTokensHourly = GlobalState.TotalSupply * GlobalState.Inflation / (365.25*24) -``` - -Because the validators hold a relative bonded share (`GlobalStakeShares`), when -more bonded tokens are added proportionally to all validators, the only term -which needs to be updated is the `GlobalState.BondedPool`. So for each -provisions cycle: - -```go -GlobalState.BondedPool += provisionTokensHourly -``` diff --git a/docs/spec/staking/state.md b/docs/spec/staking/state.md index 2bcf13dea..f337f4f71 100644 --- a/docs/spec/staking/state.md +++ b/docs/spec/staking/state.md @@ -1,204 +1,201 @@ - ## State -The staking module persists the following information to the store: -* `GlobalState`, a struct describing the global pools, inflation, and - fees -* `ValidatorCandidates: <pubkey | shares> => <candidate>`, a map of all candidates (including current validators) in the store, -indexed by their public key and shares in the global pool. -* `DelegatorBonds: < delegator-address | candidate-pubkey > => <delegator-bond>`. a map of all delegations by a delegator to a candidate, -indexed by delegator address and candidate pubkey. - public key -* `UnbondQueue`, the queue of unbonding delegations -* `RedelegateQueue`, the queue of re-delegations +### Pool -### Global State + - key: `01` + - value: `amino(pool)` -The GlobalState contains information about the total amount of Atoms, the -global bonded/unbonded position, the Atom inflation rate, and the fees. +The pool is a space for all dynamic global state of the Cosmos Hub. It tracks +information about the total amounts of Atoms in all states, representative +validator shares for stake in the global pools, moving Atom inflation +information, etc. -`Params` is global data structure that stores system parameters and defines overall functioning of the -module. - -``` go -type GlobalState struct { - TotalSupply int64 // total supply of Atoms - BondedPool int64 // reserve of bonded tokens - BondedShares rational.Rat // sum of all shares distributed for the BondedPool - UnbondedPool int64 // reserve of unbonding tokens held with candidates - UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool - InflationLastTime int64 // timestamp of last processing of inflation - Inflation rational.Rat // current annual inflation rate - DateLastCommissionReset int64 // unix timestamp for last commission accounting reset - FeePool coin.Coins // fee pool for all the fee shares which have already been distributed - ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use - Adjustment rational.Rat // Adjustment factor for calculating global fee accum +```golang +type Pool struct { + LooseTokens int64 // tokens not associated with any validator + UnbondedTokens int64 // reserve of unbonded tokens held with validators + UnbondingTokens int64 // tokens moving from bonded to unbonded pool + BondedTokens int64 // reserve of bonded tokens + UnbondedShares sdk.Rat // sum of all shares distributed for the Unbonded Pool + UnbondingShares sdk.Rat // shares moving from Bonded to Unbonded Pool + BondedShares sdk.Rat // sum of all shares distributed for the Bonded Pool + InflationLastTime int64 // block which the last inflation was processed // TODO make time + Inflation sdk.Rat // current annual inflation rate + + DateLastCommissionReset int64 // unix timestamp for last commission accounting reset (daily) } -type Params struct { - HoldBonded Address // account where all bonded coins are held - HoldUnbonding Address // account where all delegated but unbonding coins are held - - InflationRateChange rational.Rational // maximum annual change in inflation rate - InflationMax rational.Rational // maximum inflation rate - InflationMin rational.Rational // minimum inflation rate - GoalBonded rational.Rational // Goal of percent bonded atoms - ReserveTax rational.Rational // Tax collected on all fees - - MaxVals uint16 // maximum number of validators - AllowedBondDenom string // bondable coin denomination - - // gas costs for txs - GasDeclareCandidacy int64 - GasEditCandidacy int64 - GasDelegate int64 - GasRedelegate int64 - GasUnbond int64 +type PoolShares struct { + Status sdk.BondStatus // either: unbonded, unbonding, or bonded + Amount sdk.Rat // total shares of type ShareKind } ``` -### Candidate +### Params + - key: `00` + - value: `amino(params)` -The `Candidate` holds the current state and some historical -actions of validators or candidate-validators. +Params is global data structure that stores system parameters and defines +overall functioning of the stake module. -``` go -type CandidateStatus byte +```golang +type Params struct { + InflationRateChange sdk.Rat // maximum annual change in inflation rate + InflationMax sdk.Rat // maximum inflation rate + InflationMin sdk.Rat // minimum inflation rate + GoalBonded sdk.Rat // Goal of percent bonded atoms -const ( - Bonded CandidateStatus = 0x01 - Unbonded CandidateStatus = 0x02 - Revoked CandidateStatus = 0x03 -) + MaxValidators uint16 // maximum number of validators + BondDenom string // bondable coin denomination +} +``` -type Candidate struct { - Status CandidateStatus - ConsensusPubKey crypto.PubKey - GovernancePubKey crypto.PubKey - Owner crypto.Address - GlobalStakeShares rational.Rat - IssuedDelegatorShares rational.Rat - RedelegatingShares rational.Rat - VotingPower rational.Rat - Commission rational.Rat - CommissionMax rational.Rat - CommissionChangeRate rational.Rat - CommissionChangeToday rational.Rat - ProposerRewardPool coin.Coins - Adjustment rational.Rat - Description Description +### Validator + +Validators are identified according to the `ValOwnerAddr`, +an SDK account address for the owner of the validator. + +Validators also have a `ValTendermintAddr`, the address +of the public key of the validator. + +Validators are indexed in the store using the following maps: + + - Validators: `0x02 | ValOwnerAddr -> amino(validator)` + - ValidatorsByPubKey: `0x03 | ValTendermintAddr -> ValOwnerAddr` + - ValidatorsByPower: `0x05 | power | blockHeight | blockTx -> ValOwnerAddr` + + `Validators` is the primary index - it ensures that each owner can have only one + associated validator, where the public key of that validator can change in the + future. Delegators can refer to the immutable owner of the validator, without + concern for the changing public key. + + `ValidatorsByPubKey` is a secondary index that enables lookups for slashing. + When Tendermint reports evidence, it provides the validator address, so this + map is needed to find the owner. + + `ValidatorsByPower` is a secondary index that provides a sorted list of + potential validators to quickly determine the current active set. For instance, + the first 100 validators in this list can be returned with every EndBlock. + +The `Validator` holds the current state and some historical actions of the +validator. + +```golang +type Validator struct { + ConsensusPubKey crypto.PubKey // Tendermint consensus pubkey of validator + Revoked bool // has the validator been revoked? + + PoolShares PoolShares // total shares for tokens held in the pool + DelegatorShares sdk.Rat // total shares issued to a validator's delegators + SlashRatio sdk.Rat // increases each time the validator is slashed + + Description Description // description terms for the validator + + // Needed for ordering vals in the bypower key + BondHeight int64 // earliest height as a bonded validator + BondIntraTxCounter int16 // block-local tx index of validator change + + CommissionInfo CommissionInfo // info about the validator's commission + + ProposerRewardPool sdk.Coins // reward pool collected from being the proposer + + // TODO: maybe this belongs in distribution module ? + PrevPoolShares PoolShares // total shares of a global hold pools +} + +type CommissionInfo struct { + Rate sdk.Rat // the commission rate of fees charged to any delegators + Max sdk.Rat // maximum commission rate which this validator can ever charge + ChangeRate sdk.Rat // maximum daily increase of the validator commission + ChangeToday sdk.Rat // commission rate change today, reset each day (UTC time) + LastChange int64 // unix timestamp of last commission change } type Description struct { - Name string - DateBonded string - Identity string - Website string - Details string + Moniker string // name + Identity string // optional identity signature (ex. UPort or Keybase) + Website string // optional website link + Details string // optional details } ``` -Candidate parameters are described: -* Status: it can be Bonded (active validator), Unbonding (validator candidate) - or Revoked -* ConsensusPubKey: candidate public key that is used strictly for participating in - consensus -* GovernancePubKey: public key used by the validator for governance voting -* Owner: Address that is allowed to unbond coins. -* GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if - `Candidate.Status` is `Bonded`; or shares of `GlobalState.Unbondingt Pool` - otherwise -* IssuedDelegatorShares: Sum of all shares a candidate issued to delegators - (which includes the candidate's self-bond); a delegator share represents - their stake in the Candidate's `GlobalStakeShares` -* RedelegatingShares: The portion of `IssuedDelegatorShares` which are - currently re-delegating to a new validator -* VotingPower: Proportional to the amount of bonded tokens which the validator - has if `Candidate.Status` is `Bonded`; otherwise it is equal to `0` -* Commission: The commission rate of fees charged to any delegators -* CommissionMax: The maximum commission rate this candidate can charge each - day from the date `GlobalState.DateLastCommissionReset` -* CommissionChangeRate: The maximum daily increase of the candidate commission -* CommissionChangeToday: Counter for the amount of change to commission rate - which has occurred today, reset on the first block of each day (UTC time) -* ProposerRewardPool: reward pool for extra fees collected when this candidate - is the proposer of a block -* Adjustment factor used to passively calculate each validators entitled fees - from `GlobalState.FeePool` -* Description - * Name: moniker - * DateBonded: date determined which the validator was bonded - * Identity: optional field to provide a signature which verifies the - validators identity (ex. UPort or Keybase) - * Website: optional website link - * Details: optional details +### Delegation -### DelegatorBond +Delegations are identified by combining `DelegatorAddr` (the address of the delegator) with the ValOwnerAddr +Delegators are indexed in the store as follows: -Atom holders may delegate coins to candidates; under this circumstance their -funds are held in a `DelegatorBond` data structure. It is owned by one -delegator, and is associated with the shares for one candidate. The sender of + - Delegation: ` 0x0A | DelegatorAddr | ValOwnerAddr -> amino(delegation)` + +Atom holders may delegate coins to validators; under this circumstance their +funds are held in a `Delegation` data structure. It is owned by one +delegator, and is associated with the shares for one validator. The sender of the transaction is the owner of the bond. -``` go -type DelegatorBond struct { - Candidate crypto.PubKey - Shares rational.Rat - AdjustmentFeePool coin.Coins - AdjustmentRewardPool coin.Coins -} -``` - -Description: -* Candidate: the public key of the validator candidate: bonding too -* Shares: the number of delegator shares received from the validator candidate -* AdjustmentFeePool: Adjustment factor used to passively calculate each bonds - entitled fees from `GlobalState.FeePool` -* AdjustmentRewardPool: Adjustment factor used to passively calculate each - bonds entitled fees from `Candidate.ProposerRewardPool` - - -### QueueElem - -The Unbonding and re-delegation process is implemented using the ordered queue -data structure. All queue elements share a common structure: - ```golang -type QueueElem struct { - Candidate crypto.PubKey - InitTime int64 // when the element was added to the queue +type Delegation struct { + Shares sdk.Rat // delegation shares recieved + Height int64 // last height bond updated } ``` -The queue is ordered so the next element to unbond/re-delegate is at the head. -Every tick the head of the queue is checked and if the unbonding period has -passed since `InitTime`, the final settlement of the unbonding is started or -re-delegation is executed, and the element is popped from the queue. Each -`QueueElem` is persisted in the store until it is popped from the queue. +### UnbondingDelegation -### QueueElemUnbondDelegation +Shares in a `Delegation` can be unbonded, but they must for some time exist as an `UnbondingDelegation`, +where shares can be reduced if Byzantine behaviour is detected. -QueueElemUnbondDelegation structure is used in the unbonding queue. +`UnbondingDelegation` are indexed in the store as: + + - UnbondingDelegationByDelegator: ` 0x0B | DelegatorAddr | ValOwnerAddr -> + amino(unbondingDelegation)` + - UnbondingDelegationByValOwner: ` 0x0C | ValOwnerAddr | DelegatorAddr | ValOwnerAddr -> + nil` + + The first map here is used in queries, to lookup all unbonding delegations for + a given delegator, while the second map is used in slashing, to lookup all + unbonding delegations associated with a given validator that need to be + slashed. + +A UnbondingDelegation object is created every time an unbonding is initiated. +The unbond must be completed with a second transaction provided by the +delegation owner after the unbonding period has passed. ```golang -type QueueElemUnbondDelegation struct { - QueueElem - Payout Address // account to pay out to - Tokens coin.Coins // the value in Atoms of the amount of delegator shares which are unbonding - StartSlashRatio rational.Rat // candidate slash ratio +type UnbondingDelegation struct { + Tokens sdk.Coins // the value in Atoms of the amount of shares which are unbonding + CompleteTime int64 // unix time to complete redelegation } ``` -### QueueElemReDelegate +### Redelegation -QueueElemReDelegate structure is used in the re-delegation queue. +Shares in a `Delegation` can be rebonded to a different validator, but they must for some time exist as a `Redelegation`, +where shares can be reduced if Byzantine behaviour is detected. This is tracked +as moving a delegation from a `FromValOwnerAddr` to a `ToValOwnerAddr`. + +`Redelegation` are indexed in the store as: + + - Redelegations: `0x0D | DelegatorAddr | FromValOwnerAddr | ToValOwnerAddr -> + amino(redelegation)` + - RedelegationsBySrc: `0x0E | FromValOwnerAddr | ToValOwnerAddr | + DelegatorAddr -> nil` + - RedelegationsByDst: `0x0F | ToValOwnerAddr | FromValOwnerAddr | DelegatorAddr + -> nil` + + +The first map here is used for queries, to lookup all redelegations for a given +delegator. The second map is used for slashing based on the FromValOwnerAddr, +while the third map is for slashing based on the ToValOwnerAddr. + +A redelegation object is created every time a redelegation occurs. The +redelegation must be completed with a second transaction provided by the +delegation owner after the unbonding period has passed. The destination +delegation of a redelegation may not itself undergo a new redelegation until +the original redelegation has been completed. ```golang -type QueueElemReDelegate struct { - QueueElem - Payout Address // account to pay out to - Shares rational.Rat // amount of shares which are unbonding - NewCandidate crypto.PubKey // validator to bond to after unbond +type Redelegation struct { + SourceShares sdk.Rat // amount of source shares redelegating + DestinationShares sdk.Rat // amount of destination shares created at redelegation + CompleteTime int64 // unix time to complete redelegation } ``` - diff --git a/docs/spec/staking/transactions.md b/docs/spec/staking/transactions.md index 52f324b0f..55b1a8ed1 100644 --- a/docs/spec/staking/transactions.md +++ b/docs/spec/staking/transactions.md @@ -1,67 +1,61 @@ ### Transaction Overview -Available Transactions: -* TxDeclareCandidacy -* TxEditCandidacy -* TxDelegate -* TxUnbond -* TxRedelegate -* TxProveLive +In this section we describe the processing of the transactions and the +corresponding updates to the state. Transactions: + - TxCreateValidator + - TxEditValidator + - TxDelegation + - TxStartUnbonding + - TxCompleteUnbonding + - TxRedelegate + - TxCompleteRedelegation -## Transaction processing +Other important state changes: + - Update Validators -In this section we describe the processing of the transactions and the -corresponding updates to the global state. In the following text we will use -`gs` to refer to the `GlobalState` data structure, `unbondDelegationQueue` is a -reference to the queue of unbond delegations, `reDelegationQueue` is the -reference for the queue of redelegations. We use `tx` to denote a -reference to a transaction that is being processed, and `sender` to denote the -address of the sender of the transaction. We use function -`loadCandidate(store, PubKey)` to obtain a Candidate structure from the store, -and `saveCandidate(store, candidate)` to save it. Similarly, we use -`loadDelegatorBond(store, sender, PubKey)` to load a delegator bond with the -key (sender and PubKey) from the store, and -`saveDelegatorBond(store, sender, bond)` to save it. -`removeDelegatorBond(store, sender, bond)` is used to remove the bond from the -store. +Other notes: + - `tx` denotes a reference to the transaction being processed + - `sender` denotes the address of the sender of the transaction + - `getXxx`, `setXxx`, and `removeXxx` functions are used to retrieve and + modify objects from the store + - `sdk.Rat` refers to a rational numeric type specified by the SDK. -### TxDeclareCandidacy +### TxCreateValidator -A validator candidacy is declared using the `TxDeclareCandidacy` transaction. +A validator is created using the `TxCreateValidator` transaction. ```golang -type TxDeclareCandidacy struct { +type TxCreateValidator struct { + OwnerAddr sdk.Address ConsensusPubKey crypto.PubKey - Amount coin.Coin GovernancePubKey crypto.PubKey - Commission rational.Rat - CommissionMax int64 - CommissionMaxChange int64 + SelfDelegation coin.Coin + Description Description + Commission sdk.Rat + CommissionMax sdk.Rat + CommissionMaxChange sdk.Rat } + -declareCandidacy(tx TxDeclareCandidacy): - candidate = loadCandidate(store, tx.PubKey) - if candidate != nil return // candidate with that public key already exists +createValidator(tx TxCreateValidator): + validator = getValidator(tx.OwnerAddr) + if validator != nil return // only one validator per address - candidate = NewCandidate(tx.PubKey) - candidate.Status = Unbonded - candidate.Owner = sender - init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares, RedelegatingShares and Adjustment to rational.Zero - init commision related fields based on the values from tx - candidate.ProposerRewardPool = Coin(0) - candidate.Description = tx.Description + validator = NewValidator(OwnerAddr, ConsensusPubKey, GovernancePubKey, Description) + init validator poolShares, delegatorShares set to 0 + init validator commision fields from tx + validator.PoolShares = 0 - saveCandidate(store, candidate) + setValidator(validator) - txDelegate = TxDelegate(tx.PubKey, tx.Amount) - return delegateWithCandidate(txDelegate, candidate) - -// see delegateWithCandidate function in [TxDelegate](TxDelegate) + txDelegate = TxDelegate(tx.OwnerAddr, tx.OwnerAddr, tx.SelfDelegation) + delegate(txDelegate, validator) // see delegate function in [TxDelegate](TxDelegate) + return ``` -### TxEditCandidacy +### TxEditValidator If either the `Description` (excluding `DateBonded` which is constant), `Commission`, or the `GovernancePubKey` need to be updated, the @@ -70,214 +64,268 @@ If either the `Description` (excluding `DateBonded` which is constant), ```golang type TxEditCandidacy struct { GovernancePubKey crypto.PubKey - Commission int64 + Commission sdk.Rat Description Description } editCandidacy(tx TxEditCandidacy): - candidate = loadCandidate(store, tx.PubKey) - if candidate == nil or candidate.Status == Revoked return + validator = getValidator(tx.ValidatorAddr) - if tx.GovernancePubKey != nil candidate.GovernancePubKey = tx.GovernancePubKey - if tx.Commission >= 0 candidate.Commission = tx.Commission - if tx.Description != nil candidate.Description = tx.Description + if tx.Commission > CommissionMax || tx.Commission < 0 then fail + if rateChange(tx.Commission) > CommissionMaxChange then fail + validator.Commission = tx.Commission + + if tx.GovernancePubKey != nil validator.GovernancePubKey = tx.GovernancePubKey + if tx.Description != nil validator.Description = tx.Description - saveCandidate(store, candidate) + setValidator(store, validator) return ``` -### TxDelegate +### TxDelegation -Delegator bonds are created using the `TxDelegate` transaction. Within this -transaction the delegator provides an amount of coins, and in return receives -some amount of candidate's delegator shares that are assigned to -`DelegatorBond.Shares`. +Within this transaction the delegator provides coins, and in return receives +some amount of their validator's delegator-shares that are assigned to +`Delegation.Shares`. ```golang type TxDelegate struct { - PubKey crypto.PubKey - Amount coin.Coin + DelegatorAddr sdk.Address + ValidatorAddr sdk.Address + Amount sdk.Coin } delegate(tx TxDelegate): - candidate = loadCandidate(store, tx.PubKey) - if candidate == nil return - return delegateWithCandidate(tx, candidate) + pool = getPool() + if validator.Status == Revoked return -delegateWithCandidate(tx TxDelegate, candidate Candidate): - if candidate.Status == Revoked return - - if candidate.Status == Bonded - poolAccount = params.HoldBonded - else - poolAccount = params.HoldUnbonded + delegation = getDelegatorBond(DelegatorAddr, ValidatorAddr) + if delegation == nil then delegation = NewDelegation(DelegatorAddr, ValidatorAddr) - err = transfer(sender, poolAccount, tx.Amount) - if err != nil return - - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil then bond = DelegatorBond(tx.PubKey, rational.Zero, Coin(0), Coin(0)) - - issuedDelegatorShares = addTokens(tx.Amount, candidate) - bond.Shares += issuedDelegatorShares - - saveCandidate(store, candidate) - saveDelegatorBond(store, sender, bond) - saveGlobalState(store, gs) - return - -addTokens(amount coin.Coin, candidate Candidate): - if candidate.Status == Bonded - gs.BondedPool += amount - issuedShares = amount / exchangeRate(gs.BondedShares, gs.BondedPool) - gs.BondedShares += issuedShares - else - gs.UnbondedPool += amount - issuedShares = amount / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) - gs.UnbondedShares += issuedShares - - candidate.GlobalStakeShares += issuedShares + validator, pool, issuedDelegatorShares = validator.addTokensFromDel(tx.Amount, pool) + delegation.Shares += issuedDelegatorShares - if candidate.IssuedDelegatorShares.IsZero() - exRate = rational.One - else - exRate = candidate.GlobalStakeShares / candidate.IssuedDelegatorShares - - issuedDelegatorShares = issuedShares / exRate - candidate.IssuedDelegatorShares += issuedDelegatorShares - return issuedDelegatorShares - -exchangeRate(shares rational.Rat, tokenAmount int64): - if shares.IsZero() then return rational.One - return tokenAmount / shares - + setDelegation(delegation) + updateValidator(validator) + setPool(pool) + return ``` -### TxUnbond +### TxStartUnbonding Delegator unbonding is defined with the following transaction: ```golang -type TxUnbond struct { - PubKey crypto.PubKey - Shares rational.Rat +type TxStartUnbonding struct { + DelegatorAddr sdk.Address + ValidatorAddr sdk.Address + Shares string } -unbond(tx TxUnbond): - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil return - if bond.Shares < tx.Shares return - - bond.Shares -= tx.Shares - - candidate = loadCandidate(store, tx.PubKey) - - revokeCandidacy = false - if bond.Shares.IsZero() - if sender == candidate.Owner and candidate.Status != Revoked then revokeCandidacy = true then removeDelegatorBond(store, sender, bond) - else - saveDelegatorBond(store, sender, bond) - - if candidate.Status == Bonded - poolAccount = params.HoldBonded - else - poolAccount = params.HoldUnbonded - - returnedCoins = removeShares(candidate, shares) - - unbondDelegationElem = QueueElemUnbondDelegation(tx.PubKey, currentHeight(), sender, returnedCoins, startSlashRatio) - unbondDelegationQueue.add(unbondDelegationElem) - - transfer(poolAccount, unbondingPoolAddress, returnCoins) +startUnbonding(tx TxStartUnbonding): + delegation, found = getDelegatorBond(store, sender, tx.PubKey) + if !found == nil return - if revokeCandidacy - if candidate.Status == Bonded then bondedToUnbondedPool(candidate) - candidate.Status = Revoked + if bond.Shares < tx.Shares + return ErrNotEnoughBondShares - if candidate.IssuedDelegatorShares.IsZero() - removeCandidate(store, tx.PubKey) - else - saveCandidate(store, candidate) + validator, found = GetValidator(tx.ValidatorAddr) + if !found { + return err - saveGlobalState(store, gs) - return + bond.Shares -= tx.Shares -removeShares(candidate Candidate, shares rational.Rat): - globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares + revokeCandidacy = false + if bond.Shares.IsZero() { - if candidate.Status == Bonded - gs.BondedShares -= globalPoolSharesToRemove - removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * globalPoolSharesToRemove - gs.BondedPool -= removedTokens - else - gs.UnbondedShares -= globalPoolSharesToRemove - removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * globalPoolSharesToRemove - gs.UnbondedPool -= removedTokens - - candidate.GlobalStakeShares -= removedTokens - candidate.IssuedDelegatorShares -= shares - return returnedCoins + if bond.DelegatorAddr == validator.Owner && validator.Revoked == false + revokeCandidacy = true -delegatorShareExRate(candidate Candidate): - if candidate.IssuedDelegatorShares.IsZero() then return rational.One - return candidate.GlobalStakeShares / candidate.IssuedDelegatorShares - -bondedToUnbondedPool(candidate Candidate): - removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * candidate.GlobalStakeShares - gs.BondedShares -= candidate.GlobalStakeShares - gs.BondedPool -= removedTokens - - gs.UnbondedPool += removedTokens - issuedShares = removedTokens / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) - gs.UnbondedShares += issuedShares - - candidate.GlobalStakeShares = issuedShares - candidate.Status = Unbonded + removeDelegation( bond) + else + bond.Height = currentBlockHeight + setDelegation(bond) - return transfer(address of the bonded pool, address of the unbonded pool, removedTokens) + pool = GetPool() + validator, pool, returnAmount = validator.removeDelShares(pool, tx.Shares) + setPool( pool) + + unbondingDelegation = NewUnbondingDelegation(sender, returnAmount, currentHeight/Time, startSlashRatio) + setUnbondingDelegation(unbondingDelegation) + + if revokeCandidacy + validator.Revoked = true + + validator = updateValidator(validator) + + if validator.DelegatorShares == 0 { + removeValidator(validator.Owner) + + return ``` -### TxRedelegate +### TxCompleteUnbonding -The re-delegation command allows delegators to switch validators while still -receiving equal reward to as if they had never unbonded. +Complete the unbonding and transfer the coins to the delegate. Perform any +slashing that occurred during the unbonding period. ```golang -type TxRedelegate struct { - PubKeyFrom crypto.PubKey - PubKeyTo crypto.PubKey - Shares rational.Rat +type TxUnbondingComplete struct { + DelegatorAddr sdk.Address + ValidatorAddr sdk.Address } -redelegate(tx TxRedelegate): - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil then return - - if bond.Shares < tx.Shares return - candidate = loadCandidate(store, tx.PubKeyFrom) - if candidate == nil return - - candidate.RedelegatingShares += tx.Shares - reDelegationElem = QueueElemReDelegate(tx.PubKeyFrom, currentHeight(), sender, tx.Shares, tx.PubKeyTo) - redelegationQueue.add(reDelegationElem) +redelegationComplete(tx TxRedelegate): + unbonding = getUnbondingDelegation(tx.DelegatorAddr, tx.Validator) + if unbonding.CompleteTime >= CurrentBlockTime && unbonding.CompleteHeight >= CurrentBlockHeight + validator = GetValidator(tx.ValidatorAddr) + returnTokens = ExpectedTokens * tx.startSlashRatio/validator.SlashRatio + AddCoins(unbonding.DelegatorAddr, returnTokens) + removeUnbondingDelegation(unbonding) return ``` -### TxProveLive +### TxRedelegation -If a validator was automatically unbonded due to liveness issues and wishes to -assert it is still online, it can send `TxProveLive`: +The redelegation command allows delegators to instantly switch validators. Once +the unbonding period has passed, the redelegation must be completed with +txRedelegationComplete. ```golang -type TxProveLive struct { - PubKey crypto.PubKey +type TxRedelegate struct { + DelegatorAddr Address + ValidatorFrom Validator + ValidatorTo Validator + Shares sdk.Rat + CompletedTime int64 } + +redelegate(tx TxRedelegate): + + pool = getPool() + delegation = getDelegatorBond(tx.DelegatorAddr, tx.ValidatorFrom.Owner) + if delegation == nil + return + + if delegation.Shares < tx.Shares + return + delegation.shares -= Tx.Shares + validator, pool, createdCoins = validator.RemoveShares(pool, tx.Shares) + setPool(pool) + + redelegation = newRedelegation(tx.DelegatorAddr, tx.validatorFrom, + tx.validatorTo, tx.Shares, createdCoins, tx.CompletedTime) + setRedelegation(redelegation) + return ``` -All delegators in the temporary unbonding pool which have not -transacted to move will be bonded back to the now-live validator and begin to -once again collect provisions and rewards. +### TxCompleteRedelegation +Note that unlike TxCompleteUnbonding slashing of redelegating shares does not +take place during completion. Slashing on redelegated shares takes place +actively as a slashing occurs. + +```golang +type TxRedelegationComplete struct { + DelegatorAddr Address + ValidatorFrom Validator + ValidatorTo Validator +} + +redelegationComplete(tx TxRedelegate): + redelegation = getRedelegation(tx.DelegatorAddr, tx.validatorFrom, tx.validatorTo) + if redelegation.CompleteTime >= CurrentBlockTime && redelegation.CompleteHeight >= CurrentBlockHeight + removeRedelegation(redelegation) + return ``` -TODO: pseudo-code + +### Update Validators + +Within many transactions the validator set must be updated based on changes in +power to a single validator. This process also updates the Tendermint-Updates +store for use in end-block when validators are either added or kicked from the +Tendermint. + +```golang +updateBondedValidators(newValidator Validator) (updatedVal Validator) + + kickCliffValidator = false + oldCliffValidatorAddr = getCliffValidator(ctx) + + // add the actual validator power sorted store + maxValidators = GetParams(ctx).MaxValidators + iterator = ReverseSubspaceIterator(ValidatorsByPowerKey) // largest to smallest + bondedValidatorsCount = 0 + var validator Validator + for { + if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { + + if bondedValidatorsCount == int(maxValidators) { // is cliff validator + setCliffValidator(ctx, validator, GetPool(ctx)) + iterator.Close() + break + + // either retrieve the original validator from the store, + // or under the situation that this is the "new validator" just + // use the validator provided because it has not yet been updated + // in the main validator store + + ownerAddr = iterator.Value() + if bytes.Equal(ownerAddr, newValidator.Owner) { + validator = newValidator + else + validator = getValidator(ownerAddr) + + // if not previously a validator (and unrevoked), + // kick the cliff validator / bond this new validator + if validator.Status() != Bonded && !validator.Revoked { + kickCliffValidator = true + + validator = bondValidator(ctx, store, validator) + if bytes.Equal(ownerAddr, newValidator.Owner) { + updatedVal = validator + + bondedValidatorsCount++ + iterator.Next() + + // perform the actual kicks + if oldCliffValidatorAddr != nil && kickCliffValidator { + validator = getValidator(store, oldCliffValidatorAddr) + unbondValidator(ctx, store, validator) + return + +// perform all the store operations for when a validator status becomes unbonded +unbondValidator(ctx Context, store KVStore, validator Validator) + pool = GetPool(ctx) + + // set the status + validator, pool = validator.UpdateStatus(pool, Unbonded) + setPool(ctx, pool) + + // save the now unbonded validator record + setValidator(validator) + + // add to accumulated changes for tendermint + setTendermintUpdates(validator.abciValidatorZero) + + // also remove from the bonded validators index + removeValidatorsBonded(validator) +} + +// perform all the store operations for when a validator status becomes bonded +bondValidator(ctx Context, store KVStore, validator Validator) Validator + pool = GetPool(ctx) + + // set the status + validator, pool = validator.UpdateStatus(pool, Bonded) + setPool(ctx, pool) + + // save the now bonded validator record to the three referenced stores + setValidator(validator) + setValidatorsBonded(validator) + + // add to accumulated changes for tendermint + setTendermintUpdates(validator.abciValidator) + + return validator ``` diff --git a/docs/spec/staking/valset-changes.md b/docs/spec/staking/valset-changes.md deleted file mode 100644 index bc52b8998..000000000 --- a/docs/spec/staking/valset-changes.md +++ /dev/null @@ -1,190 +0,0 @@ -# Validator Set Changes - -The validator set may be updated by state transitions that run at the beginning and -end of every block. This can happen one of three ways: - -- voting power of a validator changes due to bonding and unbonding -- voting power of validator is "slashed" due to conflicting signed messages -- validator is automatically unbonded due to inactivity - -## Voting Power Changes - -At the end of every block, we run the following: - -(TODO remove inflation from here) - -```golang -tick(ctx Context): - hrsPerYr = 8766 // as defined by a julian year of 365.25 days - - time = ctx.Time() - if time > gs.InflationLastTime + ProvisionTimeout - gs.InflationLastTime = time - gs.Inflation = nextInflation(hrsPerYr).Round(1000000000) - - provisions = gs.Inflation * (gs.TotalSupply / hrsPerYr) - - gs.BondedPool += provisions - gs.TotalSupply += provisions - - saveGlobalState(store, gs) - - if time > unbondDelegationQueue.head().InitTime + UnbondingPeriod - for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do - transfer(unbondingQueueAddress, elem.Payout, elem.Tokens) - unbondDelegationQueue.remove(elem) - - if time > reDelegationQueue.head().InitTime + UnbondingPeriod - for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do - candidate = getCandidate(store, elem.PubKey) - returnedCoins = removeShares(candidate, elem.Shares) - candidate.RedelegatingShares -= elem.Shares - delegateWithCandidate(TxDelegate(elem.NewCandidate, returnedCoins), candidate) - reDelegationQueue.remove(elem) - - return UpdateValidatorSet() - -nextInflation(hrsPerYr rational.Rat): - if gs.TotalSupply > 0 - bondedRatio = gs.BondedPool / gs.TotalSupply - else - bondedRation = 0 - - inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange - inflationRateChange = inflationRateChangePerYear / hrsPerYr - - inflation = gs.Inflation + inflationRateChange - if inflation > params.InflationMax then inflation = params.InflationMax - - if inflation < params.InflationMin then inflation = params.InflationMin - - return inflation - -UpdateValidatorSet(): - candidates = loadCandidates(store) - - v1 = candidates.Validators() - v2 = updateVotingPower(candidates).Validators() - - change = v1.validatorsUpdated(v2) // determine all updated validators between two validator sets - return change - -updateVotingPower(candidates Candidates): - foreach candidate in candidates do - candidate.VotingPower = (candidate.IssuedDelegatorShares - candidate.RedelegatingShares) * delegatorShareExRate(candidate) - - candidates.Sort() - - foreach candidate in candidates do - if candidate is not in the first params.MaxVals - candidate.VotingPower = rational.Zero - if candidate.Status == Bonded then bondedToUnbondedPool(candidate Candidate) - - else if candidate.Status == UnBonded then unbondedToBondedPool(candidate) - - saveCandidate(store, c) - - return candidates - -unbondedToBondedPool(candidate Candidate): - removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * candidate.GlobalStakeShares - gs.UnbondedShares -= candidate.GlobalStakeShares - gs.UnbondedPool -= removedTokens - - gs.BondedPool += removedTokens - issuedShares = removedTokens / exchangeRate(gs.BondedShares, gs.BondedPool) - gs.BondedShares += issuedShares - - candidate.GlobalStakeShares = issuedShares - candidate.Status = Bonded - - return transfer(address of the unbonded pool, address of the bonded pool, removedTokens) -``` - - -## Slashing - -Messges which may compromise the safety of the underlying consensus protocol ("equivocations") -result in some amount of the offending validator's shares being removed ("slashed"). - -Currently, such messages include only the following: - -- prevotes by the same validator for more than one BlockID at the same - Height and Round -- precommits by the same validator for more than one BlockID at the same - Height and Round - -We call any such pair of conflicting votes `Evidence`. Full nodes in the network prioritize the -detection and gossipping of `Evidence` so that it may be rapidly included in blocks and the offending -validators punished. - -For some `evidence` to be valid, it must satisfy: - -`evidence.Timestamp >= block.Timestamp - MAX_EVIDENCE_AGE` - -where `evidence.Timestamp` is the timestamp in the block at height -`evidence.Height` and `block.Timestamp` is the current block timestamp. - -If valid evidence is included in a block, the offending validator loses -a constant `SLASH_PROPORTION` of their current stake at the beginning of the block: - -``` -oldShares = validator.shares -validator.shares = oldShares * (1 - SLASH_PROPORTION) -``` - -This ensures that offending validators are punished the same amount whether they -act as a single validator with X stake or as N validators with collectively X -stake. - - -## Automatic Unbonding - -Every block includes a set of precommits by the validators for the previous block, -known as the LastCommit. A LastCommit is valid so long as it contains precommits from +2/3 of voting power. - -Proposers are incentivized to include precommits from all -validators in the LastCommit by receiving additional fees -proportional to the difference between the voting power included in the -LastCommit and +2/3 (see [TODO](https://github.com/cosmos/cosmos-sdk/issues/967)). - -Validators are penalized for failing to be included in the LastCommit for some -number of blocks by being automatically unbonded. - -The following information is stored with each validator candidate, and is only non-zero if the candidate becomes an active validator: - -```go -type ValidatorSigningInfo struct { - StartHeight int64 - SignedBlocksBitArray BitArray -} -``` - -Where: -* `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power). -* `SignedBlocksBitArray` is a bit-array of size `SIGNED_BLOCKS_WINDOW` that records, for each of the last `SIGNED_BLOCKS_WINDOW` blocks, -whether or not this validator was included in the LastCommit. It uses a `0` if the validator was included, and a `1` if it was not. -Note it is initialized with all 0s. - -At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded: - -``` -h = block.Height -index = h % SIGNED_BLOCKS_WINDOW - -for val in block.Validators: - signInfo = val.SignInfo - if val in block.LastCommit: - signInfo.SignedBlocksBitArray.Set(index, 0) - else - signInfo.SignedBlocksBitArray.Set(index, 1) - - // validator must be active for at least SIGNED_BLOCKS_WINDOW - // before they can be automatically unbonded for failing to be - // included in 50% of the recent LastCommits - minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW - minSigned = SIGNED_BLOCKS_WINDOW / 2 - blocksSigned = signInfo.SignedBlocksBitArray.Sum() - if h > minHeight AND blocksSigned < minSigned: - unbond the validator -``` diff --git a/docs/spec/store/README.md b/docs/spec/store/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/staking/testnet.rst b/docs/staking/testnet.rst deleted file mode 100644 index 4fca09c4a..000000000 --- a/docs/staking/testnet.rst +++ /dev/null @@ -1,82 +0,0 @@ -Testnet Setup -============= - -**Note:** This document is incomplete and may not be up-to-date with the state of the code. - -See the `installation guide <../sdk/install.html>`__ for details on installation. - -Here is a quick example to get you off your feet: - -First, generate a couple of genesis transactions to be incorparated into the genesis file, this will create two keys with the password ``1234567890`` - -:: - - gaiad init gen-tx --name=foo --home=$HOME/.gaiad1 - gaiad init gen-tx --name=bar --home=$HOME/.gaiad2 - gaiacli keys list - -**Note:** If you've already run these tests you may need to overwrite keys using the ``--OWK`` flag -When you list the keys you should see two addresses, we'll need these later so take note. -Now let's actually create the genesis files for both nodes: - -:: - - cp -a ~/.gaiad2/config/gentx/. ~/.gaiad1/config/gentx/ - cp -a ~/.gaiad1/config/gentx/. ~/.gaiad2/config/gentx/ - gaiad init --gen-txs --home=$HOME/.gaiad1 --chain-id=test-chain - gaiad init --gen-txs --home=$HOME/.gaiad2 --chain-id=test-chain - -**Note:** If you've already run these tests you may need to overwrite genesis using the ``-o`` flag -What we just did is copy the genesis transactions between each of the nodes so there is a common genesis transaction set; then we created both genesis files independently from each home directory. Importantly both nodes have independently created their ``genesis.json`` and ``config.toml`` files, which should be identical between nodes. - -Great, now that we've initialized the chains, we can start both nodes in the background: - -:: - - gaiad start --home=$HOME/.gaiad1 &> gaia1.log & - NODE1_PID=$! - gaia start --home=$HOME/.gaiad2 &> gaia2.log & - NODE2_PID=$! - -Note that we save the PID so we can later kill the processes. You can peak at your logs with ``tail gaia1.log``, or follow them for a bit with ``tail -f gaia1.log``. - -Nice. We can also lookup the validator set: - -:: - - gaiacli advanced tendermint validator-set - -Then, we try to transfer some ``steak`` to another account: - -:: - - gaiacli account <FOO-ADDR> - gaiacli account <BAR-ADDR> - gaiacli send --amount=10steak --to=<BAR-ADDR> --name=foo --chain-id=test-chain - -**Note:** We need to be careful with the ``chain-id`` and ``sequence`` - -Check the balance & sequence with: - -:: - - gaiacli account <BAR-ADDR> - -To confirm for certain the new validator is active, check tendermint: - -:: - - curl localhost:46657/validators - -Finally, to relinquish all your power, unbond some coins. You should see your VotingPower reduce and your account balance increase. - -:: - - gaiacli stake unbond --chain-id=<chain-id> --name=test - -That's it! - -**Note:** TODO demonstrate edit-candidacy -**Note:** TODO demonstrate delegation -**Note:** TODO demonstrate unbond of delegation -**Note:** TODO demonstrate unbond candidate diff --git a/examples/examples.md b/examples/README.md similarity index 93% rename from examples/examples.md rename to examples/README.md index 55db606c4..e3016fb70 100644 --- a/examples/examples.md +++ b/examples/README.md @@ -6,7 +6,7 @@ what is happening under the hood. ## Setup and Install -You will need to have go installed on your computer. Please refer to the [cosmos testnet tutorial](https://cosmos.network/validators/tutorial), which will always have the most updated instructions on how to get setup with go and the cosmos repository. +You will need to have go installed on your computer. Please refer to the [cosmos testnet tutorial](https://cosmos.network/validators/tutorial), which will always have the most updated instructions on how to get setup with go and the cosmos repository. Once you have go installed, run the command: @@ -27,7 +27,7 @@ make get_tools // run make update_tools if you already had it installed make get_vendor_deps make install_examples ``` -Then run `make install_examples`, which creates binaries for `basecli` and `basecoind`. You can look at the Makefile if you want to see the details on what these make commands are doing. +Then run `make install_examples`, which creates binaries for `basecli` and `basecoind`. You can look at the Makefile if you want to see the details on what these make commands are doing. ## Using basecli and basecoind @@ -40,7 +40,7 @@ basecoind version They should read something like `0.17.1-5d18d5f`, but the versions will be constantly updating so don't worry if your version is higher that 0.17.1. That's a good thing. -Note that you can always check help in the terminal by running `basecli -h` or `basecoind -h`. It is good to check these out if you are stuck, because updates to the code base might slightly change the commands, and you might find the correct command in there. +Note that you can always check help in the terminal by running `basecli -h` or `basecoind -h`. It is good to check these out if you are stuck, because updates to the code base might slightly change the commands, and you might find the correct command in there. Let's start by initializing the basecoind daemon. Run the command @@ -60,7 +60,7 @@ And you should see something like this: } ``` -This creates the `~/.basecoind folder`, which has config.toml, genesis.json, node_key.json, priv_validator.json. Take some time to review what is contained in these files if you want to understand what is going on at a deeper level. +This creates the `~/.basecoind folder`, which has config.toml, genesis.json, node_key.json, priv_validator.json. Take some time to review what is contained in these files if you want to understand what is going on at a deeper level. ## Generating keys @@ -79,7 +79,7 @@ Repeat the passphrase: Enter your recovery seed phrase: ``` -You just created your first locally stored key, under the name alice, and this account is linked to the private key that is running the basecoind validator node. Once you do this, the ~/.basecli folder is created, which will hold the alice key and any other keys you make. Now that you have the key for alice, you can start up the blockchain by running +You just created your first locally stored key, under the name alice, and this account is linked to the private key that is running the basecoind validator node. Once you do this, the ~/.basecli folder is created, which will hold the alice key and any other keys you make. Now that you have the key for alice, you can start up the blockchain by running ``` basecoind start @@ -100,7 +100,7 @@ You can see your keys with the command: basecli keys list ``` -You should now see alice, bob and charlie's account all show up. +You should now see alice, bob and charlie's account all show up. ``` NAME: ADDRESS: PUBKEY: @@ -123,12 +123,12 @@ Where `90B0B9BE0914ECEE0B6DB74E67B07A00056B9BBD` is alice's address we got from The following command will send coins from alice, to bob: ``` -basecli send --name=alice --amount=10000mycoin --to=29D721F054537C91F618A0FDBF770DA51EF8C48D +basecli send --from=alice --amount=10000mycoin --to=29D721F054537C91F618A0FDBF770DA51EF8C48D --sequence=0 --chain-id=test-chain-AE4XQo ``` -Flag Descriptions: -- `name` is the name you gave your key +Flag Descriptions: +- `from` is the name you gave your key - `mycoin` is the name of the token for this basecoin demo, initialized in the genesis.json file - `sequence` is a tally of how many transactions have been made by this account. Since this is the first tx on this account, it is 0 - `chain-id` is the unique ID that helps tendermint identify which network to connect to. You can find it in the terminal output from the gaiad daemon in the header block , or in the genesis.json file at `~/.basecoind/config/genesis.json` @@ -142,16 +142,16 @@ basecli account 29D721F054537C91F618A0FDBF770DA51EF8C48D Now lets send some from bob to charlie. Make sure you send less than bob has, otherwise the transaction will fail: ``` -basecli send --name=bob --amount=5000mycoin --to=2E8E13EEB8E3F0411ACCBC9BE0384732C24FBD5E +basecli send --from=bob --amount=5000mycoin --to=2E8E13EEB8E3F0411ACCBC9BE0384732C24FBD5E --sequence=0 --chain-id=test-chain-AE4XQo ``` -Note how we use the ``--name`` flag to select a different account to send from. +Note how we use the ``--from`` flag to select a different account to send from. Lets now try to send from bob back to alice: ``` -basecli send --name=bob --amount=3000mycoin --to=90B0B9BE0914ECEE0B6DB74E67B07A00056B9BBD +basecli send --from=bob --amount=3000mycoin --to=90B0B9BE0914ECEE0B6DB74E67B07A00056B9BBD --sequence=1 --chain-id=test-chain-AE4XQo ``` @@ -171,8 +171,8 @@ That is the basic implementation of basecoin! **WARNING:** Running these commands will wipe out any existing information in both the ``~/.basecli`` and ``~/.basecoind`` directories, including private keys. This should be no problem considering that basecoin -is just an example, but it is always good to pay extra attention when -you are removing private keys, in any scenario involving a blockchain. +is just an example, but it is always good to pay extra attention when +you are removing private keys, in any scenario involving a blockchain. To remove all the files created and refresh your environment (e.g., if starting this tutorial again or trying something new), the following @@ -212,7 +212,7 @@ The Basecoin state consists entirely of a set of accounts. Each account contains an address, a public key, a balance in many different coin denominations, and a strictly increasing sequence number for replay protection. This type of account was directly inspired by accounts in Ethereum, and is -unlike Bitcoin's use of Unspent Transaction Outputs (UTXOs). +unlike Bitcoin's use of Unspent Transaction Outputs (UTXOs). ``` type BaseAccount struct { @@ -225,7 +225,7 @@ type BaseAccount struct { You can also add more fields to accounts, and basecoin actually does so. Basecoin adds a Name field in order to show how easily the base account structure can be -modified to suit any applications needs. It takes the `auth.BaseAccount` we see above, +modified to suit any applications needs. It takes the `auth.BaseAccount` we see above, and extends it with `Name`. ``` @@ -256,7 +256,7 @@ Accounts are serialized and stored in a Merkle tree under the key Typically, the address of the account is the 20-byte ``RIPEMD160`` hash of the public key, but other formats are acceptable as well, as defined in the `Tendermint crypto -library <https://github.com/tendermint/go-crypto>`__. The Merkle tree +library <https://github.com/tendermint/tendermint/tree/master/crypto>`__. The Merkle tree used in Basecoin is a balanced, binary search tree, which we call an `IAVL tree <https://github.com/tendermint/iavl>`__. @@ -293,15 +293,15 @@ Note the `SendTx` includes a field for `Gas` and `Fee`. The transaction, while the `Fee` refers to the total amount paid in fees. This is slightly different from Ethereum's concept of `Gas` and `GasPrice`, where `Fee = Gas x GasPrice`. In Basecoin, the `Gas` -and `Fee` are independent, and the `GasPrice` is implicit. +and `Fee` are independent, and the `GasPrice` is implicit. In Basecoin, the `Fee` is meant to be used by the validators to inform the ordering of transactions, like in Bitcoin. And the `Gas` is meant to be used by the application plugin to control its execution. There is currently no means to pass `Fee` information to the Tendermint -validators, but it will come soon... so this version of Basecoin does -not actually fully implement fees and gas, but it still allows us -to send transactions between accounts. +validators, but it will come soon... so this version of Basecoin does +not actually fully implement fees and gas, but it still allows us +to send transactions between accounts. Note also that the `PubKey` only needs to be sent for `Sequence == 0`. After that, it is stored under the account in the @@ -318,4 +318,3 @@ serve as a basic unit of decentralized exchange. When using multiple inputs and outputs, you must make sure that the sum of coins of the inputs equals the sum of coins of the outputs (no creating money), and that all accounts that provide inputs have signed the transaction. - diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index f654ba05e..7b2c7af3a 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -3,182 +3,176 @@ package app import ( "encoding/json" - abci "github.com/tendermint/abci/types" - tmtypes "github.com/tendermint/tendermint/types" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" - bam "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/examples/basecoin/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/ibc" - "github.com/cosmos/cosmos-sdk/x/slashing" - "github.com/cosmos/cosmos-sdk/x/stake" - - "github.com/cosmos/cosmos-sdk/examples/basecoin/types" + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/types" ) const ( appName = "BasecoinApp" ) -// Extended ABCI application +// BasecoinApp implements an extended ABCI application. It contains a BaseApp, +// a codec for serialization, KVStore keys for multistore state management, and +// various mappers and keepers to manage getting, setting, and serializing the +// integral app types. type BasecoinApp struct { *bam.BaseApp cdc *wire.Codec - // keys to access the substores - keyMain *sdk.KVStoreKey - keyAccount *sdk.KVStoreKey - keyIBC *sdk.KVStoreKey - keyStake *sdk.KVStoreKey - keySlashing *sdk.KVStoreKey + // keys to access the multistore + keyMain *sdk.KVStoreKey + keyAccount *sdk.KVStoreKey + keyIBC *sdk.KVStoreKey - // Manage getting and setting accounts + // manage getting and setting accounts accountMapper auth.AccountMapper feeCollectionKeeper auth.FeeCollectionKeeper coinKeeper bank.Keeper ibcMapper ibc.Mapper - stakeKeeper stake.Keeper - slashingKeeper slashing.Keeper } +// NewBasecoinApp returns a reference to a new BasecoinApp given a logger and +// database. Internally, a codec is created along with all the necessary keys. +// In addition, all necessary mappers and keepers are created, routes +// registered, and finally the stores being mounted along with any necessary +// chain initialization. func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp { + // create and register app-level codec for TXs and accounts + cdc := MakeCodec() - // Create app-level codec for txs and accounts. - var cdc = MakeCodec() - - // Create your application object. + // create your application type var app = &BasecoinApp{ - BaseApp: bam.NewBaseApp(appName, cdc, logger, db), - cdc: cdc, - keyMain: sdk.NewKVStoreKey("main"), - keyAccount: sdk.NewKVStoreKey("acc"), - keyIBC: sdk.NewKVStoreKey("ibc"), - keyStake: sdk.NewKVStoreKey("stake"), - keySlashing: sdk.NewKVStoreKey("slashing"), + cdc: cdc, + BaseApp: bam.NewBaseApp(appName, cdc, logger, db), + keyMain: sdk.NewKVStoreKey("main"), + keyAccount: sdk.NewKVStoreKey("acc"), + keyIBC: sdk.NewKVStoreKey("ibc"), } - // Define the accountMapper. + // define and attach the mappers and keepers app.accountMapper = auth.NewAccountMapper( cdc, - app.keyAccount, // target store - &types.AppAccount{}, // prototype + app.keyAccount, // target store + auth.ProtoBaseAccount, // prototype ) - - // add accountMapper/handlers app.coinKeeper = bank.NewKeeper(app.accountMapper) app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace)) - app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace)) - app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.RegisterCodespace(slashing.DefaultCodespace)) // register message routes app.Router(). - AddRoute("auth", auth.NewHandler(app.accountMapper)). AddRoute("bank", bank.NewHandler(app.coinKeeper)). - AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.coinKeeper)). - AddRoute("stake", stake.NewHandler(app.stakeKeeper)) + AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.coinKeeper)) - // Initialize BaseApp. + // perform initialization logic app.SetInitChainer(app.initChainer) app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) - app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing) + + // mount the multistore and load the latest state + app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC) err := app.LoadLatestVersion(app.keyMain) if err != nil { cmn.Exit(err.Error()) } + return app } -// Custom tx codec +// MakeCodec creates a new wire codec and registers all the necessary types +// with the codec. func MakeCodec() *wire.Codec { - var cdc = wire.NewCodec() - wire.RegisterCrypto(cdc) // Register crypto. - sdk.RegisterWire(cdc) // Register Msgs + cdc := wire.NewCodec() + + wire.RegisterCrypto(cdc) + sdk.RegisterWire(cdc) bank.RegisterWire(cdc) - stake.RegisterWire(cdc) - slashing.RegisterWire(cdc) ibc.RegisterWire(cdc) - // register custom AppAccount + // register custom types cdc.RegisterInterface((*auth.Account)(nil), nil) cdc.RegisterConcrete(&types.AppAccount{}, "basecoin/Account", nil) + + cdc.Seal() + return cdc } -// application updates every end block -func (app *BasecoinApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { - tags := slashing.BeginBlocker(ctx, req, app.slashingKeeper) - - return abci.ResponseBeginBlock{ - Tags: tags.ToKVPairs(), - } +// BeginBlocker reflects logic to run before any TXs application are processed +// by the application. +func (app *BasecoinApp) BeginBlocker(_ sdk.Context, _ abci.RequestBeginBlock) abci.ResponseBeginBlock { + return abci.ResponseBeginBlock{} } -// application updates every end block -func (app *BasecoinApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper) - - return abci.ResponseEndBlock{ - ValidatorUpdates: validatorUpdates, - } +// EndBlocker reflects logic to run after all TXs are processed by the +// application. +func (app *BasecoinApp) EndBlocker(_ sdk.Context, _ abci.RequestEndBlock) abci.ResponseEndBlock { + return abci.ResponseEndBlock{} } -// Custom logic for basecoin initialization +// initChainer implements the custom application logic that the BaseApp will +// invoke upon initialization. In this case, it will take the application's +// state provided by 'req' and attempt to deserialize said state. The state +// should contain all the genesis accounts. These accounts will be added to the +// application's account mapper. func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { stateJSON := req.AppStateBytes genesisState := new(types.GenesisState) err := app.cdc.UnmarshalJSON(stateJSON, genesisState) if err != nil { - panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 - // return sdk.ErrGenesisParse("").TraceCause(err, "") + // TODO: https://github.com/cosmos/cosmos-sdk/issues/468 + panic(err) } for _, gacc := range genesisState.Accounts { acc, err := gacc.ToAppAccount() if err != nil { - panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 - // return sdk.ErrGenesisParse("").TraceCause(err, "") + // TODO: https://github.com/cosmos/cosmos-sdk/issues/468 + panic(err) } + acc.AccountNumber = app.accountMapper.GetNextAccountNumber(ctx) app.accountMapper.SetAccount(ctx, acc) } - // load the initial stake information - stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData) - return abci.ResponseInitChain{} } -// Custom logic for state export +// ExportAppStateAndValidators implements custom application logic that exposes +// various parts of the application's state and set of validators. An error is +// returned if any step getting the state or set of validators fails. func (app *BasecoinApp) ExportAppStateAndValidators() (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { ctx := app.NewContext(true, abci.Header{}) - - // iterate to get the accounts accounts := []*types.GenesisAccount{} - appendAccount := func(acc auth.Account) (stop bool) { + + appendAccountsFn := func(acc auth.Account) bool { account := &types.GenesisAccount{ Address: acc.GetAddress(), Coins: acc.GetCoins(), } + accounts = append(accounts, account) return false } - app.accountMapper.IterateAccounts(ctx, appendAccount) - genState := types.GenesisState{ - Accounts: accounts, - } + app.accountMapper.IterateAccounts(ctx, appendAccountsFn) + + genState := types.GenesisState{Accounts: accounts} appState, err = wire.MarshalJSONIndent(app.cdc, genState) if err != nil { return nil, nil, err } - validators = stake.WriteValidators(ctx, app.stakeKeeper) + return appState, validators, err } diff --git a/examples/basecoin/app/app_test.go b/examples/basecoin/app/app_test.go index 23bc531c0..78f9e35a8 100644 --- a/examples/basecoin/app/app_test.go +++ b/examples/basecoin/app/app_test.go @@ -4,74 +4,78 @@ import ( "os" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/cosmos/cosmos-sdk/examples/basecoin/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/stake" - - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" ) -func setGenesis(bapp *BasecoinApp, accs ...auth.BaseAccount) error { - genaccs := make([]*types.GenesisAccount, len(accs)) - for i, acc := range accs { - genaccs[i] = types.NewGenesisAccount(&types.AppAccount{acc, "foobart"}) +func setGenesis(baseApp *BasecoinApp, accounts ...*types.AppAccount) (types.GenesisState, error) { + genAccts := make([]*types.GenesisAccount, len(accounts)) + for i, appAct := range accounts { + genAccts[i] = types.NewGenesisAccount(appAct) } - genesisState := types.GenesisState{ - Accounts: genaccs, - StakeData: stake.DefaultGenesisState(), - } - - stateBytes, err := wire.MarshalJSONIndent(bapp.cdc, genesisState) + genesisState := types.GenesisState{Accounts: genAccts} + stateBytes, err := wire.MarshalJSONIndent(baseApp.cdc, genesisState) if err != nil { - return err + return types.GenesisState{}, err } - // Initialize the chain - vals := []abci.Validator{} - bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) - bapp.Commit() + // initialize and commit the chain + baseApp.InitChain(abci.RequestInitChain{ + Validators: []abci.Validator{}, AppStateBytes: stateBytes, + }) + baseApp.Commit() - return nil + return genesisState, nil } -//_______________________________________________________________________ - func TestGenesis(t *testing.T) { logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") db := dbm.NewMemDB() - bapp := NewBasecoinApp(logger, db) + baseApp := NewBasecoinApp(logger, db) - // Construct some genesis bytes to reflect basecoin/types/AppAccount - pk := crypto.GenPrivKeyEd25519().PubKey() - addr := pk.Address() + // construct a pubkey and an address for the test account + pubkey := crypto.GenPrivKeyEd25519().PubKey() + addr := sdk.AccAddress(pubkey.Address()) + + // construct some test coins coins, err := sdk.ParseCoins("77foocoin,99barcoin") require.Nil(t, err) - baseAcc := auth.BaseAccount{ - Address: addr, - Coins: coins, - } - acc := &types.AppAccount{baseAcc, "foobart"} - err = setGenesis(bapp, baseAcc) + // create an auth.BaseAccount for the given test account and set it's coins + baseAcct := auth.NewBaseAccountWithAddress(addr) + err = baseAcct.SetCoins(coins) require.Nil(t, err) - // A checkTx context - ctx := bapp.BaseApp.NewContext(true, abci.Header{}) - res1 := bapp.accountMapper.GetAccount(ctx, baseAcc.Address) - assert.Equal(t, acc, res1) + // create a new test AppAccount with the given auth.BaseAccount + appAcct := types.NewAppAccount("foobar", baseAcct) + genState, err := setGenesis(baseApp, appAcct) + require.Nil(t, err) + + // create a context for the BaseApp + ctx := baseApp.BaseApp.NewContext(true, abci.Header{}) + res := baseApp.accountMapper.GetAccount(ctx, baseAcct.Address) + require.Equal(t, appAcct, res) // reload app and ensure the account is still there - bapp = NewBasecoinApp(logger, db) - ctx = bapp.BaseApp.NewContext(true, abci.Header{}) - res1 = bapp.accountMapper.GetAccount(ctx, baseAcc.Address) - assert.Equal(t, acc, res1) + baseApp = NewBasecoinApp(logger, db) + + stateBytes, err := wire.MarshalJSONIndent(baseApp.cdc, genState) + require.Nil(t, err) + + // initialize the chain with the expected genesis state + baseApp.InitChain(abci.RequestInitChain{ + Validators: []abci.Validator{}, AppStateBytes: stateBytes, + }) + + ctx = baseApp.BaseApp.NewContext(true, abci.Header{}) + res = baseApp.accountMapper.GetAccount(ctx, baseAcct.Address) + require.Equal(t, appAcct, res) } diff --git a/examples/basecoin/cmd/basecli/main.go b/examples/basecoin/cmd/basecli/main.go index 6540af38a..82a283515 100644 --- a/examples/basecoin/cmd/basecli/main.go +++ b/examples/basecoin/cmd/basecli/main.go @@ -3,24 +3,20 @@ package main import ( "os" - "github.com/spf13/cobra" - - "github.com/tendermint/tmlibs/cli" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/lcd" "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/client/tx" - + "github.com/cosmos/cosmos-sdk/examples/basecoin/app" + "github.com/cosmos/cosmos-sdk/examples/basecoin/types" "github.com/cosmos/cosmos-sdk/version" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/client/cli" stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli" - - "github.com/cosmos/cosmos-sdk/examples/basecoin/app" - "github.com/cosmos/cosmos-sdk/examples/basecoin/types" + "github.com/spf13/cobra" + "github.com/tendermint/tendermint/libs/cli" ) // rootCmd is the entry point for this binary @@ -38,9 +34,9 @@ func main() { // get the codec cdc := app.MakeCodec() - // TODO: setup keybase, viper object, etc. to be passed into + // TODO: Setup keybase, viper object, etc. to be passed into // the below functions and eliminate global vars, like we do - // with the cdc + // with the cdc. // add standard rpc, and tx commands rpc.AddCommands(rootCmd) @@ -51,6 +47,10 @@ func main() { // add query/post commands (custom to binary) rootCmd.AddCommand( client.GetCommands( + stakecmd.GetCmdQueryValidator("stake", cdc), + stakecmd.GetCmdQueryValidators("stake", cdc), + stakecmd.GetCmdQueryDelegation("stake", cdc), + stakecmd.GetCmdQueryDelegations("stake", cdc), authcmd.GetAccountCmd("acc", cdc, types.GetAccountDecoder(cdc)), )...) @@ -62,7 +62,7 @@ func main() { stakecmd.GetCmdCreateValidator(cdc), stakecmd.GetCmdEditValidator(cdc), stakecmd.GetCmdDelegate(cdc), - stakecmd.GetCmdUnbond(cdc), + stakecmd.GetCmdUnbond("stake", cdc), )...) // add proxy, version and key info @@ -76,5 +76,9 @@ func main() { // prepare and add flags executor := cli.PrepareMainCmd(rootCmd, "BC", os.ExpandEnv("$HOME/.basecli")) - executor.Execute() + err := executor.Execute() + if err != nil { + // Note: Handle with #870 + panic(err) + } } diff --git a/examples/basecoin/cmd/basecoind/main.go b/examples/basecoin/cmd/basecoind/main.go index 4a6498e1b..a4f233f0a 100644 --- a/examples/basecoin/cmd/basecoind/main.go +++ b/examples/basecoin/cmd/basecoind/main.go @@ -4,16 +4,14 @@ import ( "encoding/json" "os" - "github.com/spf13/cobra" - - abci "github.com/tendermint/abci/types" - tmtypes "github.com/tendermint/tendermint/types" - "github.com/tendermint/tmlibs/cli" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" - "github.com/cosmos/cosmos-sdk/examples/basecoin/app" "github.com/cosmos/cosmos-sdk/server" + "github.com/spf13/cobra" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/cli" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/types" ) func main() { @@ -33,7 +31,12 @@ func main() { // prepare and add flags rootDir := os.ExpandEnv("$HOME/.basecoind") executor := cli.PrepareBaseCmd(rootCmd, "BC", rootDir) - executor.Execute() + + err := executor.Execute() + if err != nil { + // Note: Handle with #870 + panic(err) + } } func newApp(logger log.Logger, db dbm.DB) abci.Application { diff --git a/examples/basecoin/types/account.go b/examples/basecoin/types/account.go index 43a8e2e38..671494311 100644 --- a/examples/basecoin/types/account.go +++ b/examples/basecoin/types/account.go @@ -4,18 +4,17 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/stake" ) var _ auth.Account = (*AppAccount)(nil) -// Custom extensions for this application. This is just an example of -// extending auth.BaseAccount with custom fields. -// -// This is compatible with the stock auth.AccountStore, since -// auth.AccountStore uses the flexible go-amino library. +// AppAccount is a custom extension for this application. It is an example of +// extending auth.BaseAccount with custom fields. It is compatible with the +// stock auth.AccountStore, since auth.AccountStore uses the flexible go-amino +// library. type AppAccount struct { auth.BaseAccount + Name string `json:"name"` } @@ -23,36 +22,45 @@ type AppAccount struct { func (acc AppAccount) GetName() string { return acc.Name } func (acc *AppAccount) SetName(name string) { acc.Name = name } -// Get the AccountDecoder function for the custom AppAccount +// NewAppAccount returns a reference to a new AppAccount given a name and an +// auth.BaseAccount. +func NewAppAccount(name string, baseAcct auth.BaseAccount) *AppAccount { + return &AppAccount{BaseAccount: baseAcct, Name: name} +} + +// GetAccountDecoder returns the AccountDecoder function for the custom +// AppAccount. func GetAccountDecoder(cdc *wire.Codec) auth.AccountDecoder { - return func(accBytes []byte) (res auth.Account, err error) { + return func(accBytes []byte) (auth.Account, error) { if len(accBytes) == 0 { return nil, sdk.ErrTxDecode("accBytes are empty") } + acct := new(AppAccount) - err = cdc.UnmarshalBinaryBare(accBytes, &acct) + err := cdc.UnmarshalBinaryBare(accBytes, &acct) if err != nil { panic(err) } + return acct, err } } -//___________________________________________________________________________________ - -// State to Unmarshal +// GenesisState reflects the genesis state of the application. type GenesisState struct { - Accounts []*GenesisAccount `json:"accounts"` - StakeData stake.GenesisState `json:"stake"` + Accounts []*GenesisAccount `json:"accounts"` } -// GenesisAccount doesn't need pubkey or sequence +// GenesisAccount reflects a genesis account the application expects in it's +// genesis state. type GenesisAccount struct { - Name string `json:"name"` - Address sdk.Address `json:"address"` - Coins sdk.Coins `json:"coins"` + Name string `json:"name"` + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` } +// NewGenesisAccount returns a reference to a new GenesisAccount given an +// AppAccount. func NewGenesisAccount(aa *AppAccount) *GenesisAccount { return &GenesisAccount{ Name: aa.Name, @@ -61,14 +69,13 @@ func NewGenesisAccount(aa *AppAccount) *GenesisAccount { } } -// convert GenesisAccount to AppAccount +// ToAppAccount converts a GenesisAccount to an AppAccount. func (ga *GenesisAccount) ToAppAccount() (acc *AppAccount, err error) { - baseAcc := auth.BaseAccount{ - Address: ga.Address, - Coins: ga.Coins.Sort(), - } return &AppAccount{ - BaseAccount: baseAcc, - Name: ga.Name, + Name: ga.Name, + BaseAccount: auth.BaseAccount{ + Address: ga.Address, + Coins: ga.Coins.Sort(), + }, }, nil } diff --git a/examples/democoin/app/app.go b/examples/democoin/app/app.go index dc7c3c9d8..51d10002a 100644 --- a/examples/democoin/app/app.go +++ b/examples/democoin/app/app.go @@ -3,11 +3,11 @@ package app import ( "encoding/json" - abci "github.com/tendermint/abci/types" + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" tmtypes "github.com/tendermint/tendermint/types" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" bam "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" @@ -71,7 +71,7 @@ func NewDemocoinApp(logger log.Logger, db dbm.DB) *DemocoinApp { app.accountMapper = auth.NewAccountMapper( cdc, app.capKeyAccountStore, // target store - &types.AppAccount{}, // prototype + types.ProtoAppAccount, // prototype ) // Add handlers. @@ -113,10 +113,14 @@ func MakeCodec() *wire.Codec { // Register AppAccount cdc.RegisterInterface((*auth.Account)(nil), nil) cdc.RegisterConcrete(&types.AppAccount{}, "democoin/Account", nil) + + cdc.Seal() + return cdc } // custom logic for democoin initialization +// nolint: unparam func (app *DemocoinApp) initChainerFn(coolKeeper cool.Keeper, powKeeper pow.Keeper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { stateJSON := req.AppStateBytes diff --git a/examples/democoin/app/app_test.go b/examples/democoin/app/app_test.go index 01264399a..b786af816 100644 --- a/examples/democoin/app/app_test.go +++ b/examples/democoin/app/app_test.go @@ -1,23 +1,45 @@ package app import ( - "encoding/json" "os" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/cosmos/cosmos-sdk/examples/democoin/types" + "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" - - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" ) +func setGenesis(bapp *DemocoinApp, trend string, accs ...auth.BaseAccount) error { + genaccs := make([]*types.GenesisAccount, len(accs)) + for i, acc := range accs { + genaccs[i] = types.NewGenesisAccount(&types.AppAccount{acc, "foobart"}) + } + + genesisState := types.GenesisState{ + Accounts: genaccs, + CoolGenesis: cool.Genesis{trend}, + } + + stateBytes, err := wire.MarshalJSONIndent(bapp.cdc, genesisState) + if err != nil { + return err + } + + // Initialize the chain + vals := []abci.Validator{} + bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) + bapp.Commit() + + return nil +} + func TestGenesis(t *testing.T) { logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") db := dbm.NewMemDB() @@ -25,7 +47,7 @@ func TestGenesis(t *testing.T) { // Construct some genesis bytes to reflect democoin/types/AppAccount pk := crypto.GenPrivKeyEd25519().PubKey() - addr := pk.Address() + addr := sdk.AccAddress(pk.Address()) coins, err := sdk.ParseCoins("77foocoin,99barcoin") require.Nil(t, err) baseAcc := auth.BaseAccount{ @@ -34,28 +56,17 @@ func TestGenesis(t *testing.T) { } acc := &types.AppAccount{baseAcc, "foobart"} - genesisState := map[string]interface{}{ - "accounts": []*types.GenesisAccount{ - types.NewGenesisAccount(acc), - }, - "cool": map[string]string{ - "trend": "ice-cold", - }, - } - stateBytes, err := json.MarshalIndent(genesisState, "", "\t") - - vals := []abci.Validator{} - bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) - bapp.Commit() - + err = setGenesis(bapp, "ice-cold", baseAcc) + require.Nil(t, err) // A checkTx context ctx := bapp.BaseApp.NewContext(true, abci.Header{}) res1 := bapp.accountMapper.GetAccount(ctx, baseAcc.Address) - assert.Equal(t, acc, res1) + require.Equal(t, acc, res1) // reload app and ensure the account is still there bapp = NewDemocoinApp(logger, db) + bapp.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}")}) ctx = bapp.BaseApp.NewContext(true, abci.Header{}) res1 = bapp.accountMapper.GetAccount(ctx, baseAcc.Address) - assert.Equal(t, acc, res1) + require.Equal(t, acc, res1) } diff --git a/examples/democoin/cmd/democli/main.go b/examples/democoin/cmd/democli/main.go index cdf9396d6..43f86504e 100644 --- a/examples/democoin/cmd/democli/main.go +++ b/examples/democoin/cmd/democli/main.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/cobra" - "github.com/tendermint/tmlibs/cli" + "github.com/tendermint/tendermint/libs/cli" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/keys" @@ -92,5 +92,9 @@ func main() { // prepare and add flags executor := cli.PrepareMainCmd(rootCmd, "BC", os.ExpandEnv("$HOME/.democli")) - executor.Execute() + err := executor.Execute() + if err != nil { + // handle with #870 + panic(err) + } } diff --git a/examples/democoin/cmd/democoind/main.go b/examples/democoin/cmd/democoind/main.go index 76d29075e..e74b3b700 100644 --- a/examples/democoin/cmd/democoind/main.go +++ b/examples/democoin/cmd/democoind/main.go @@ -6,11 +6,11 @@ import ( "github.com/spf13/cobra" - abci "github.com/tendermint/abci/types" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/cli" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" tmtypes "github.com/tendermint/tendermint/types" - "github.com/tendermint/tmlibs/cli" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" "github.com/cosmos/cosmos-sdk/examples/democoin/app" "github.com/cosmos/cosmos-sdk/server" @@ -29,17 +29,24 @@ func CoolAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState jso if err != nil { return } + key := "cool" value := json.RawMessage(`{ "trend": "ice-cold" }`) - appState, err = server.AppendJSON(cdc, appState, key, value) + + appState, err = server.InsertKeyJSON(cdc, appState, key, value) + if err != nil { + return + } + key = "pow" value = json.RawMessage(`{ "difficulty": 1, "count": 0 }`) - appState, err = server.AppendJSON(cdc, appState, key, value) + + appState, err = server.InsertKeyJSON(cdc, appState, key, value) return } @@ -69,5 +76,9 @@ func main() { // prepare and add flags rootDir := os.ExpandEnv("$HOME/.democoind") executor := cli.PrepareBaseCmd(rootCmd, "BC", rootDir) - executor.Execute() + err := executor.Execute() + if err != nil { + // handle with #870 + panic(err) + } } diff --git a/examples/democoin/mock/validator.go b/examples/democoin/mock/validator.go new file mode 100644 index 000000000..84d41d488 --- /dev/null +++ b/examples/democoin/mock/validator.go @@ -0,0 +1,127 @@ +package mock + +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tendermint/crypto" +) + +// Validator implements sdk.Validator +type Validator struct { + Address sdk.AccAddress + Power sdk.Rat +} + +// Implements sdk.Validator +func (v Validator) GetStatus() sdk.BondStatus { + return sdk.Bonded +} + +// Implements sdk.Validator +func (v Validator) GetOwner() sdk.AccAddress { + return v.Address +} + +// Implements sdk.Validator +func (v Validator) GetPubKey() crypto.PubKey { + return nil +} + +// Implements sdk.Validator +func (v Validator) GetPower() sdk.Rat { + return v.Power +} + +// Implements sdk.Validator +func (v Validator) GetDelegatorShares() sdk.Rat { + return sdk.ZeroRat() +} + +// Implements sdk.Validator +func (v Validator) GetRevoked() bool { + return false +} + +// Implements sdk.Validator +func (v Validator) GetBondHeight() int64 { + return 0 +} + +// Implements sdk.Validator +func (v Validator) GetMoniker() string { + return "" +} + +// Implements sdk.Validator +type ValidatorSet struct { + Validators []Validator +} + +// IterateValidators implements sdk.ValidatorSet +func (vs *ValidatorSet) IterateValidators(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) { + for i, val := range vs.Validators { + if fn(int64(i), val) { + break + } + } +} + +// IterateValidatorsBonded implements sdk.ValidatorSet +func (vs *ValidatorSet) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) { + vs.IterateValidators(ctx, fn) +} + +// Validator implements sdk.ValidatorSet +func (vs *ValidatorSet) Validator(ctx sdk.Context, addr sdk.AccAddress) sdk.Validator { + for _, val := range vs.Validators { + if bytes.Equal(val.Address, addr) { + return val + } + } + return nil +} + +// TotalPower implements sdk.ValidatorSet +func (vs *ValidatorSet) TotalPower(ctx sdk.Context) sdk.Rat { + res := sdk.ZeroRat() + for _, val := range vs.Validators { + res = res.Add(val.Power) + } + return res +} + +// Helper function for adding new validator +func (vs *ValidatorSet) AddValidator(val Validator) { + vs.Validators = append(vs.Validators, val) +} + +// Helper function for removing exsting validator +func (vs *ValidatorSet) RemoveValidator(addr sdk.AccAddress) { + pos := -1 + for i, val := range vs.Validators { + if bytes.Equal(val.Address, addr) { + pos = i + break + } + } + if pos == -1 { + return + } + vs.Validators = append(vs.Validators[:pos], vs.Validators[pos+1:]...) +} + +// Implements sdk.ValidatorSet +func (vs *ValidatorSet) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, power int64, amt sdk.Rat) { + panic("not implemented") +} + +// Implements sdk.ValidatorSet +func (vs *ValidatorSet) Revoke(ctx sdk.Context, pubkey crypto.PubKey) { + panic("not implemented") +} + +// Implements sdk.ValidatorSet +func (vs *ValidatorSet) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) { + panic("not implemented") +} diff --git a/examples/democoin/types/account.go b/examples/democoin/types/account.go index 211cf3c41..8eb9b0ae4 100644 --- a/examples/democoin/types/account.go +++ b/examples/democoin/types/account.go @@ -21,6 +21,11 @@ type AppAccount struct { Name string `json:"name"` } +// Constructor for AppAccount +func ProtoAppAccount() auth.Account { + return &AppAccount{} +} + // nolint func (acc AppAccount) GetName() string { return acc.Name } func (acc *AppAccount) SetName(name string) { acc.Name = name } @@ -32,7 +37,7 @@ func GetAccountDecoder(cdc *wire.Codec) auth.AccountDecoder { return nil, sdk.ErrTxDecode("accBytes are empty") } acct := new(AppAccount) - err = cdc.UnmarshalBinary(accBytes, &acct) + err = cdc.UnmarshalBinaryBare(accBytes, &acct) if err != nil { panic(err) } @@ -51,9 +56,9 @@ type GenesisState struct { // GenesisAccount doesn't need pubkey or sequence type GenesisAccount struct { - Name string `json:"name"` - Address sdk.Address `json:"address"` - Coins sdk.Coins `json:"coins"` + Name string `json:"name"` + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` } func NewGenesisAccount(aa *AppAccount) *GenesisAccount { diff --git a/examples/democoin/x/assoc/validator_set.go b/examples/democoin/x/assoc/validator_set.go new file mode 100644 index 000000000..94a0ef630 --- /dev/null +++ b/examples/democoin/x/assoc/validator_set.go @@ -0,0 +1,107 @@ +package assoc + +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +// ValidatorSet defines +type ValidatorSet struct { + sdk.ValidatorSet + + key sdk.KVStoreGetter + cdc *wire.Codec + + maxAssoc int + addrLen int +} + +var _ sdk.ValidatorSet = ValidatorSet{} + +// NewValidatorSet returns new ValidatorSet with underlying ValidatorSet +func NewValidatorSet(cdc *wire.Codec, key sdk.KVStoreGetter, valset sdk.ValidatorSet, maxAssoc int, addrLen int) ValidatorSet { + if maxAssoc < 0 || addrLen < 0 { + panic("Cannot use negative integer for NewValidatorSet") + } + return ValidatorSet{ + ValidatorSet: valset, + + key: key, + cdc: cdc, + + maxAssoc: maxAssoc, + addrLen: addrLen, + } +} + +// Implements sdk.ValidatorSet +func (valset ValidatorSet) Validator(ctx sdk.Context, addr sdk.AccAddress) (res sdk.Validator) { + store := valset.key.KVStore(ctx) + base := store.Get(GetBaseKey(addr)) + res = valset.ValidatorSet.Validator(ctx, base) + if res == nil { + res = valset.ValidatorSet.Validator(ctx, addr) + } + return +} + +// GetBaseKey :: sdk.AccAddress -> sdk.AccAddress +func GetBaseKey(addr sdk.AccAddress) []byte { + return append([]byte{0x00}, addr...) +} + +// GetAssocPrefix :: sdk.AccAddress -> (sdk.AccAddress -> byte) +func GetAssocPrefix(base sdk.AccAddress) []byte { + return append([]byte{0x01}, base...) +} + +// GetAssocKey :: (sdk.AccAddress, sdk.AccAddress) -> byte +func GetAssocKey(base sdk.AccAddress, assoc sdk.AccAddress) []byte { + return append(append([]byte{0x01}, base...), assoc...) +} + +// Associate associates new address with validator address +func (valset ValidatorSet) Associate(ctx sdk.Context, base sdk.AccAddress, assoc sdk.AccAddress) bool { + if len(base) != valset.addrLen || len(assoc) != valset.addrLen { + return false + } + store := valset.key.KVStore(ctx) + // If someone already owns the associated address + if store.Get(GetBaseKey(assoc)) != nil { + return false + } + store.Set(GetBaseKey(assoc), base) + store.Set(GetAssocKey(base, assoc), []byte{0x00}) + return true +} + +// Dissociate removes association between addresses +func (valset ValidatorSet) Dissociate(ctx sdk.Context, base sdk.AccAddress, assoc sdk.AccAddress) bool { + if len(base) != valset.addrLen || len(assoc) != valset.addrLen { + return false + } + store := valset.key.KVStore(ctx) + // No associated address found for given validator + if !bytes.Equal(store.Get(GetBaseKey(assoc)), base) { + return false + } + store.Delete(GetBaseKey(assoc)) + store.Delete(GetAssocKey(base, assoc)) + return true +} + +// Associations returns all associated addresses with a validator +func (valset ValidatorSet) Associations(ctx sdk.Context, base sdk.AccAddress) (res []sdk.AccAddress) { + store := valset.key.KVStore(ctx) + res = make([]sdk.AccAddress, valset.maxAssoc) + iter := sdk.KVStorePrefixIterator(store, GetAssocPrefix(base)) + i := 0 + for ; iter.Valid(); iter.Next() { + key := iter.Key() + res[i] = key[len(key)-valset.addrLen:] + i++ + } + return res[:i] +} diff --git a/examples/democoin/x/assoc/validator_set_test.go b/examples/democoin/x/assoc/validator_set_test.go new file mode 100644 index 000000000..e5932c14b --- /dev/null +++ b/examples/democoin/x/assoc/validator_set_test.go @@ -0,0 +1,71 @@ +package assoc + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + + "github.com/cosmos/cosmos-sdk/examples/democoin/mock" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +func defaultContext(key sdk.StoreKey) sdk.Context { + db := dbm.NewMemDB() + cms := store.NewCommitMultiStore(db) + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + cms.LoadLatestVersion() + ctx := sdk.NewContext(cms, abci.Header{}, false, nil) + return ctx +} + +func TestValidatorSet(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx := defaultContext(key) + + addr1 := []byte("addr1") + addr2 := []byte("addr2") + + base := &mock.ValidatorSet{[]mock.Validator{ + {addr1, sdk.NewRat(1)}, + {addr2, sdk.NewRat(2)}, + }} + + valset := NewValidatorSet(wire.NewCodec(), sdk.NewPrefixStoreGetter(key, []byte("assoc")), base, 1, 5) + + require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1)) + require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2)) + + assoc1 := []byte("asso1") + assoc2 := []byte("asso2") + + require.True(t, valset.Associate(ctx, addr1, assoc1)) + require.True(t, valset.Associate(ctx, addr2, assoc2)) + + require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, assoc1)) + require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, assoc2)) + + require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1)) + require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2)) + + assocs := valset.Associations(ctx, addr1) + require.Equal(t, 1, len(assocs)) + require.True(t, bytes.Equal(assoc1, assocs[0])) + + require.False(t, valset.Associate(ctx, addr1, assoc2)) + require.False(t, valset.Associate(ctx, addr2, assoc1)) + + valset.Dissociate(ctx, addr1, assoc1) + valset.Dissociate(ctx, addr2, assoc2) + + require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1)) + require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2)) + + require.Nil(t, valset.Validator(ctx, assoc1)) + require.Nil(t, valset.Validator(ctx, assoc2)) +} diff --git a/examples/democoin/x/cool/app_test.go b/examples/democoin/x/cool/app_test.go index 8d3f347b3..269f0ae49 100644 --- a/examples/democoin/x/cool/app_test.go +++ b/examples/democoin/x/cool/app_test.go @@ -3,20 +3,19 @@ package cool import ( "testing" - "github.com/stretchr/testify/assert" - - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" bank "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/mock" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" ) var ( - priv1 = crypto.GenPrivKeyEd25519() - addr1 = priv1.PubKey().Address() + priv1 = crypto.GenPrivKeyEd25519() + pubKey = priv1.PubKey() + addr1 = sdk.AccAddress(pubKey.Address()) quizMsg1 = MsgQuiz{ Sender: addr1, @@ -56,7 +55,7 @@ func getMockApp(t *testing.T) *mock.App { mapp.SetInitChainer(getInitChainer(mapp, keeper, "ice-cold")) - mapp.CompleteSetup(t, []*sdk.KVStoreKey{keyCool}) + require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyCool})) return mapp } @@ -86,20 +85,20 @@ func TestMsgQuiz(t *testing.T) { // A checkTx context (true) ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) - assert.Equal(t, acc1, res1) + require.Equal(t, acc1, res1) // Set the trend, submit a really cool quiz and check for reward - mock.SignCheckDeliver(t, mapp.BaseApp, setTrendMsg1, []int64{0}, []int64{0}, true, priv1) - mock.SignCheckDeliver(t, mapp.BaseApp, quizMsg1, []int64{0}, []int64{1}, true, priv1) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", 69}}) - mock.SignCheckDeliver(t, mapp.BaseApp, quizMsg2, []int64{0}, []int64{2}, false, priv1) // result without reward - mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", 69}}) - mock.SignCheckDeliver(t, mapp.BaseApp, quizMsg1, []int64{0}, []int64{3}, true, priv1) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", 138}}) - mock.SignCheckDeliver(t, mapp.BaseApp, setTrendMsg2, []int64{0}, []int64{4}, true, priv1) // reset the trend - mock.SignCheckDeliver(t, mapp.BaseApp, quizMsg1, []int64{0}, []int64{5}, false, priv1) // the same answer will nolonger do! - mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", 138}}) - mock.SignCheckDeliver(t, mapp.BaseApp, quizMsg2, []int64{0}, []int64{6}, true, priv1) // earlier answer now relavent again - mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"badvibesonly", 69}, {"icecold", 138}}) - mock.SignCheckDeliver(t, mapp.BaseApp, setTrendMsg3, []int64{0}, []int64{7}, false, priv1) // expect to fail to set the trend to something which is not cool + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg1}, []int64{0}, []int64{0}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{1}, true, priv1) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(69)}}) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{2}, false, priv1) // result without reward + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(69)}}) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{3}, true, priv1) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(138)}}) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg2}, []int64{0}, []int64{4}, true, priv1) // reset the trend + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{5}, false, priv1) // the same answer will nolonger do! + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(138)}}) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{6}, true, priv1) // earlier answer now relevant again + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"badvibesonly", sdk.NewInt(69)}, {"icecold", sdk.NewInt(138)}}) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg3}, []int64{0}, []int64{7}, false, priv1) // expect to fail to set the trend to something which is not cool } diff --git a/examples/democoin/x/cool/client/cli/tx.go b/examples/democoin/x/cool/client/cli/tx.go index f502f09e1..3e034600b 100644 --- a/examples/democoin/x/cool/client/cli/tx.go +++ b/examples/democoin/x/cool/client/cli/tx.go @@ -1,13 +1,12 @@ package cli import ( - "fmt" - "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" @@ -36,12 +35,11 @@ func QuizTxCmd(cdc *wire.Codec) *cobra.Command { name := viper.GetString(client.FlagName) // build and sign the transaction, then broadcast to Tendermint - res, err := ctx.EnsureSignBuildBroadcast(name, msg, cdc) + err = ctx.EnsureSignBuildBroadcast(name, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } @@ -69,12 +67,11 @@ func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command { msg := cool.NewMsgSetTrend(from, args[0]) // build and sign the transaction, then broadcast to Tendermint - res, err := ctx.EnsureSignBuildBroadcast(name, msg, cdc) + err = ctx.EnsureSignBuildBroadcast(name, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } diff --git a/examples/democoin/x/cool/errors.go b/examples/democoin/x/cool/errors.go index 7a5e62c5d..73df2386e 100644 --- a/examples/democoin/x/cool/errors.go +++ b/examples/democoin/x/cool/errors.go @@ -16,5 +16,5 @@ const ( // ErrIncorrectCoolAnswer - Error returned upon an incorrect guess func ErrIncorrectCoolAnswer(codespace sdk.CodespaceType, answer string) sdk.Error { - return sdk.NewError(codespace, CodeIncorrectCoolAnswer, fmt.Sprintf("Incorrect cool answer: %v", answer)) + return sdk.NewError(codespace, CodeIncorrectCoolAnswer, fmt.Sprintf("incorrect cool answer: %v", answer)) } diff --git a/examples/democoin/x/cool/handler.go b/examples/democoin/x/cool/handler.go index b4375c5dc..82247677c 100644 --- a/examples/democoin/x/cool/handler.go +++ b/examples/democoin/x/cool/handler.go @@ -51,7 +51,7 @@ func handleMsgQuiz(ctx sdk.Context, k Keeper, msg MsgQuiz) sdk.Result { return sdk.Result{} // TODO } - bonusCoins := sdk.Coins{{msg.CoolAnswer, 69}} + bonusCoins := sdk.Coins{sdk.NewCoin(msg.CoolAnswer, 69)} _, _, err := k.ck.AddCoins(ctx, msg.Sender, bonusCoins) if err != nil { diff --git a/examples/democoin/x/cool/keeper_test.go b/examples/democoin/x/cool/keeper_test.go index d497dee69..ab59ea610 100644 --- a/examples/democoin/x/cool/keeper_test.go +++ b/examples/democoin/x/cool/keeper_test.go @@ -3,10 +3,10 @@ package cool import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - dbm "github.com/tendermint/tmlibs/db" + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" @@ -29,22 +29,22 @@ func TestCoolKeeper(t *testing.T) { cdc := wire.NewCodec() auth.RegisterBaseAccount(cdc) - am := auth.NewAccountMapper(cdc, capKey, &auth.BaseAccount{}) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, nil) + am := auth.NewAccountMapper(cdc, capKey, auth.ProtoBaseAccount) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil) ck := bank.NewKeeper(am) keeper := NewKeeper(capKey, ck, DefaultCodespace) err := InitGenesis(ctx, keeper, Genesis{"icy"}) - assert.Nil(t, err) + require.Nil(t, err) genesis := WriteGenesis(ctx, keeper) - assert.Nil(t, err) - assert.Equal(t, genesis, Genesis{"icy"}) + require.Nil(t, err) + require.Equal(t, genesis, Genesis{"icy"}) res := keeper.GetTrend(ctx) - assert.Equal(t, res, "icy") + require.Equal(t, res, "icy") keeper.setTrend(ctx, "fiery") res = keeper.GetTrend(ctx) - assert.Equal(t, res, "fiery") + require.Equal(t, res, "fiery") } diff --git a/examples/democoin/x/cool/types.go b/examples/democoin/x/cool/types.go index 77f49a0ba..d335f9c91 100644 --- a/examples/democoin/x/cool/types.go +++ b/examples/democoin/x/cool/types.go @@ -11,7 +11,7 @@ import ( // a really cool msg type, these fields are can be entirely arbitrary and // custom to your message type MsgSetTrend struct { - Sender sdk.Address + Sender sdk.AccAddress Cool string } @@ -21,7 +21,7 @@ type Genesis struct { } // new cool message -func NewMsgSetTrend(sender sdk.Address, cool string) MsgSetTrend { +func NewMsgSetTrend(sender sdk.AccAddress, cool string) MsgSetTrend { return MsgSetTrend{ Sender: sender, Cool: cool, @@ -32,8 +32,8 @@ func NewMsgSetTrend(sender sdk.Address, cool string) MsgSetTrend { var _ sdk.Msg = MsgSetTrend{} // nolint -func (msg MsgSetTrend) Type() string { return "cool" } -func (msg MsgSetTrend) GetSigners() []sdk.Address { return []sdk.Address{msg.Sender} } +func (msg MsgSetTrend) Type() string { return "cool" } +func (msg MsgSetTrend) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Sender} } func (msg MsgSetTrend) String() string { return fmt.Sprintf("MsgSetTrend{Sender: %v, Cool: %v}", msg.Sender, msg.Cool) } @@ -58,7 +58,7 @@ func (msg MsgSetTrend) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } //_______________________________________________________________________ @@ -66,12 +66,12 @@ func (msg MsgSetTrend) GetSignBytes() []byte { // A message type to quiz how cool you are. these fields are can be entirely // arbitrary and custom to your message type MsgQuiz struct { - Sender sdk.Address + Sender sdk.AccAddress CoolAnswer string } // New cool message -func NewMsgQuiz(sender sdk.Address, coolerthancool string) MsgQuiz { +func NewMsgQuiz(sender sdk.AccAddress, coolerthancool string) MsgQuiz { return MsgQuiz{ Sender: sender, CoolAnswer: coolerthancool, @@ -82,8 +82,8 @@ func NewMsgQuiz(sender sdk.Address, coolerthancool string) MsgQuiz { var _ sdk.Msg = MsgQuiz{} // nolint -func (msg MsgQuiz) Type() string { return "cool" } -func (msg MsgQuiz) GetSigners() []sdk.Address { return []sdk.Address{msg.Sender} } +func (msg MsgQuiz) Type() string { return "cool" } +func (msg MsgQuiz) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Sender} } func (msg MsgQuiz) String() string { return fmt.Sprintf("MsgQuiz{Sender: %v, CoolAnswer: %v}", msg.Sender, msg.CoolAnswer) } @@ -102,5 +102,5 @@ func (msg MsgQuiz) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } diff --git a/examples/democoin/x/oracle/README.md b/examples/democoin/x/oracle/README.md new file mode 100644 index 000000000..eec02d724 --- /dev/null +++ b/examples/democoin/x/oracle/README.md @@ -0,0 +1,58 @@ +# Oracle Module + +`x/oracle` provides a way to receive external information(real world price, events from other chains, etc.) with validators' vote. Each validator make transaction which contains those informations, and Oracle aggregates them until the supermajority signed on it. After then, Oracle sends the information to the actual module that processes the information, and prune the votes from the state. + +## Integration + +See `x/oracle/oracle_test.go` for the code that using Oracle + +To use Oracle in your module, first define a `payload`. It should implement `oracle.Payload` and contain nessesary information for your module. Including nonce is recommended. + +```go +type MyPayload struct { + Data int + Nonce int +} +``` + +When you write a payload, its `.Type()` should return same name with your module is registered on the router. It is because `oracle.Msg` inherits `.Type()` from its embedded payload and it should be handled on the user modules. + +Then route every incoming `oracle.Msg` to `oracle.Keeper.Handler()` with the function that implements `oracle.Handler`. + +```go +func NewHandler(keeper Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + switch msg := msg.(type) { + case oracle.Msg: + return keeper.oracle.Handle(ctx sdk.Context, p oracle.Payload) sdk.Error { + switch p := p.(type) { + case MyPayload: + return handleMyPayload(ctx, keeper, p) + } + } + } + } +} +``` + +In the previous example, the keeper has an `oracle.Keeper`. `oracle.Keeper`s are generated by `NewKeeper`. + +```go +func NewKeeper(key sdk.StoreKey, cdc *wire.Codec, valset sdk.ValidatorSet, supermaj sdk.Rat, timeout int64) Keeper { + return Keeper { + cdc: cdc, + key: key, + + // ValidatorSet to get validators infor + valset: valset, + + // The keeper will pass payload + // when more than 2/3 signed on it + supermaj: supermaj, + // The keeper will prune votes after 100 blocks from last sign + timeout: timeout, + } +} +``` + +Now the validators can send `oracle.Msg`s with `MyPayload` when they want to witness external events. diff --git a/examples/democoin/x/oracle/errors.go b/examples/democoin/x/oracle/errors.go new file mode 100644 index 000000000..1378fa76f --- /dev/null +++ b/examples/democoin/x/oracle/errors.go @@ -0,0 +1,31 @@ +package oracle + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Oracle errors reserve 1101-1199 +const ( + CodeNotValidator sdk.CodeType = 1101 + CodeAlreadyProcessed sdk.CodeType = 1102 + CodeAlreadySigned sdk.CodeType = 1103 + CodeUnknownRequest sdk.CodeType = sdk.CodeUnknownRequest +) + +// ---------------------------------------- +// Error constructors + +// ErrNotValidator called when the signer of a Msg is not a validator +func ErrNotValidator(codespace sdk.CodespaceType, address sdk.AccAddress) sdk.Error { + return sdk.NewError(codespace, CodeNotValidator, address.String()) +} + +// ErrAlreadyProcessed called when a payload is already processed +func ErrAlreadyProcessed(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeAlreadyProcessed, "") +} + +// ErrAlreadySigned called when the signer is trying to double signing +func ErrAlreadySigned(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeAlreadySigned, "") +} diff --git a/examples/democoin/x/oracle/handler.go b/examples/democoin/x/oracle/handler.go new file mode 100644 index 000000000..8b94a1894 --- /dev/null +++ b/examples/democoin/x/oracle/handler.go @@ -0,0 +1,105 @@ +package oracle + +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Handler handles payload after it passes voting process +type Handler func(ctx sdk.Context, p Payload) sdk.Error + +func (keeper Keeper) update(ctx sdk.Context, val sdk.Validator, valset sdk.ValidatorSet, p Payload, info Info) Info { + info.Power = info.Power.Add(val.GetPower()) + + // Return if the voted power is not bigger than required power + totalPower := valset.TotalPower(ctx) + requiredPower := totalPower.Mul(keeper.supermaj) + if !info.Power.GT(requiredPower) { + return info + } + + // Check if the validators hash has been changed during the vote process + // and recalculate voted power + hash := ctx.BlockHeader().ValidatorsHash + if !bytes.Equal(hash, info.Hash) { + info.Power = sdk.ZeroRat() + info.Hash = hash + prefix := GetSignPrefix(p, keeper.cdc) + store := keeper.key.KVStore(ctx) + iter := sdk.KVStorePrefixIterator(store, prefix) + for ; iter.Valid(); iter.Next() { + if valset.Validator(ctx, iter.Value()) != nil { + store.Delete(iter.Key()) + continue + } + info.Power = info.Power.Add(val.GetPower()) + } + if !info.Power.GT(totalPower.Mul(keeper.supermaj)) { + return info + } + } + + info.Status = Processed + return info +} + +// Handle is used by other modules to handle Msg +func (keeper Keeper) Handle(h Handler, ctx sdk.Context, o Msg, codespace sdk.CodespaceType) sdk.Result { + valset := keeper.valset + + signer := o.Signer + payload := o.Payload + + // Check the oracle is not in process + info := keeper.Info(ctx, payload) + if info.Status != Pending { + return ErrAlreadyProcessed(codespace).Result() + } + + // Check if it is reporting timeout + now := ctx.BlockHeight() + if now > info.LastSigned+keeper.timeout { + info = Info{Status: Timeout} + keeper.setInfo(ctx, payload, info) + keeper.clearSigns(ctx, payload) + return sdk.Result{} + } + info.LastSigned = ctx.BlockHeight() + + // Check the signer is a validater + val := valset.Validator(ctx, signer) + if val == nil { + return ErrNotValidator(codespace, signer).Result() + } + + // Check double signing + if keeper.signed(ctx, payload, signer) { + return ErrAlreadySigned(codespace).Result() + } + + keeper.sign(ctx, payload, signer) + + info = keeper.update(ctx, val, valset, payload, info) + if info.Status == Processed { + info = Info{Status: Processed} + } + + keeper.setInfo(ctx, payload, info) + + if info.Status == Processed { + keeper.clearSigns(ctx, payload) + cctx, write := ctx.CacheContext() + err := h(cctx, payload) + if err != nil { + return sdk.Result{ + Code: sdk.ABCICodeOK, + Log: err.ABCILog(), + } + } + write() + + } + + return sdk.Result{} +} diff --git a/examples/democoin/x/oracle/keeper.go b/examples/democoin/x/oracle/keeper.go new file mode 100644 index 000000000..97c68251b --- /dev/null +++ b/examples/democoin/x/oracle/keeper.go @@ -0,0 +1,111 @@ +package oracle + +import ( + "github.com/cosmos/cosmos-sdk/wire" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Keeper of the oracle store +type Keeper struct { + key sdk.KVStoreGetter + cdc *wire.Codec + + valset sdk.ValidatorSet + + supermaj sdk.Rat + timeout int64 +} + +// NewKeeper constructs a new keeper +func NewKeeper(key sdk.KVStoreGetter, cdc *wire.Codec, valset sdk.ValidatorSet, supermaj sdk.Rat, timeout int64) Keeper { + if timeout < 0 { + panic("Timeout should not be negative") + } + + return Keeper{ + key: key, + cdc: cdc, + + valset: valset, + + supermaj: supermaj, + timeout: timeout, + } +} + +// InfoStatus - current status of an Info +type InfoStatus int8 + +// Define InfoStatus +const ( + Pending = InfoStatus(iota) + Processed + Timeout +) + +// Info for each payload +type Info struct { + Power sdk.Rat + Hash []byte + LastSigned int64 + Status InfoStatus +} + +// EmptyInfo construct an empty Info +func EmptyInfo(ctx sdk.Context) Info { + return Info{ + Power: sdk.ZeroRat(), + Hash: ctx.BlockHeader().ValidatorsHash, + LastSigned: ctx.BlockHeight(), + Status: Pending, + } +} + +// Info returns the information about a payload +func (keeper Keeper) Info(ctx sdk.Context, p Payload) (res Info) { + store := keeper.key.KVStore(ctx) + + key := GetInfoKey(p, keeper.cdc) + bz := store.Get(key) + if bz == nil { + return EmptyInfo(ctx) + } + keeper.cdc.MustUnmarshalBinary(bz, &res) + + return +} + +func (keeper Keeper) setInfo(ctx sdk.Context, p Payload, info Info) { + store := keeper.key.KVStore(ctx) + + key := GetInfoKey(p, keeper.cdc) + bz := keeper.cdc.MustMarshalBinary(info) + store.Set(key, bz) +} + +func (keeper Keeper) sign(ctx sdk.Context, p Payload, signer sdk.AccAddress) { + store := keeper.key.KVStore(ctx) + + key := GetSignKey(p, signer, keeper.cdc) + store.Set(key, signer) +} + +func (keeper Keeper) signed(ctx sdk.Context, p Payload, signer sdk.AccAddress) bool { + store := keeper.key.KVStore(ctx) + + key := GetSignKey(p, signer, keeper.cdc) + return store.Has(key) +} + +func (keeper Keeper) clearSigns(ctx sdk.Context, p Payload) { + store := keeper.key.KVStore(ctx) + + prefix := GetSignPrefix(p, keeper.cdc) + + iter := sdk.KVStorePrefixIterator(store, prefix) + for ; iter.Valid(); iter.Next() { + store.Delete(iter.Key()) + } + iter.Close() +} diff --git a/examples/democoin/x/oracle/keeper_keys.go b/examples/democoin/x/oracle/keeper_keys.go new file mode 100644 index 000000000..f657e8027 --- /dev/null +++ b/examples/democoin/x/oracle/keeper_keys.go @@ -0,0 +1,23 @@ +package oracle + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +// GetInfoKey returns the key for OracleInfo +func GetInfoKey(p Payload, cdc *wire.Codec) []byte { + bz := cdc.MustMarshalBinary(p) + return append([]byte{0x00}, bz...) +} + +// GetSignPrefix returns the prefix for signs +func GetSignPrefix(p Payload, cdc *wire.Codec) []byte { + bz := cdc.MustMarshalBinary(p) + return append([]byte{0x01}, bz...) +} + +// GetSignKey returns the key for sign +func GetSignKey(p Payload, signer sdk.AccAddress, cdc *wire.Codec) []byte { + return append(GetSignPrefix(p, cdc), signer...) +} diff --git a/examples/democoin/x/oracle/oracle_test.go b/examples/democoin/x/oracle/oracle_test.go new file mode 100644 index 000000000..467035897 --- /dev/null +++ b/examples/democoin/x/oracle/oracle_test.go @@ -0,0 +1,222 @@ +package oracle + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + + "github.com/cosmos/cosmos-sdk/examples/democoin/mock" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +func defaultContext(keys ...sdk.StoreKey) sdk.Context { + db := dbm.NewMemDB() + cms := store.NewCommitMultiStore(db) + for _, key := range keys { + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + } + cms.LoadLatestVersion() + ctx := sdk.NewContext(cms, abci.Header{}, false, nil) + return ctx +} + +type seqOracle struct { + Seq int + Nonce int +} + +func (o seqOracle) Type() string { + return "seq" +} + +func (o seqOracle) ValidateBasic() sdk.Error { + return nil +} + +func makeCodec() *wire.Codec { + var cdc = wire.NewCodec() + + cdc.RegisterInterface((*sdk.Msg)(nil), nil) + cdc.RegisterConcrete(Msg{}, "test/Oracle", nil) + + cdc.RegisterInterface((*Payload)(nil), nil) + cdc.RegisterConcrete(seqOracle{}, "test/oracle/seqOracle", nil) + + cdc.Seal() + + return cdc +} + +func seqHandler(ork Keeper, key sdk.StoreKey, codespace sdk.CodespaceType) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + switch msg := msg.(type) { + case Msg: + return ork.Handle(func(ctx sdk.Context, p Payload) sdk.Error { + switch p := p.(type) { + case seqOracle: + return handleSeqOracle(ctx, key, p) + default: + return sdk.ErrUnknownRequest("") + } + }, ctx, msg, codespace) + default: + return sdk.ErrUnknownRequest("").Result() + } + } +} + +func getSequence(ctx sdk.Context, key sdk.StoreKey) int { + store := ctx.KVStore(key) + seqbz := store.Get([]byte("seq")) + + var seq int + if seqbz == nil { + seq = 0 + } else { + wire.NewCodec().MustUnmarshalBinary(seqbz, &seq) + } + + return seq +} + +func handleSeqOracle(ctx sdk.Context, key sdk.StoreKey, o seqOracle) sdk.Error { + store := ctx.KVStore(key) + + seq := getSequence(ctx, key) + if seq != o.Seq { + return sdk.NewError(sdk.CodespaceRoot, 1, "") + } + + bz := wire.NewCodec().MustMarshalBinary(seq + 1) + store.Set([]byte("seq"), bz) + + return nil +} + +func TestOracle(t *testing.T) { + cdc := makeCodec() + + addr1 := []byte("addr1") + addr2 := []byte("addr2") + addr3 := []byte("addr3") + addr4 := []byte("addr4") + valset := &mock.ValidatorSet{[]mock.Validator{ + {addr1, sdk.NewRat(7)}, + {addr2, sdk.NewRat(7)}, + {addr3, sdk.NewRat(1)}, + }} + + key := sdk.NewKVStoreKey("testkey") + ctx := defaultContext(key) + + bz, err := json.Marshal(valset) + require.Nil(t, err) + ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz}) + + ork := NewKeeper(sdk.NewPrefixStoreGetter(key, []byte("oracle")), cdc, valset, sdk.NewRat(2, 3), 100) + h := seqHandler(ork, key, sdk.CodespaceRoot) + + // Nonmock.Validator signed, transaction failed + msg := Msg{seqOracle{0, 0}, []byte("randomguy")} + res := h(ctx, msg) + require.False(t, res.IsOK()) + require.Equal(t, 0, getSequence(ctx, key)) + + // Less than 2/3 signed, msg not processed + msg.Signer = addr1 + res = h(ctx, msg) + require.True(t, res.IsOK()) + require.Equal(t, 0, getSequence(ctx, key)) + + // Double signed, transaction failed + res = h(ctx, msg) + require.False(t, res.IsOK()) + require.Equal(t, 0, getSequence(ctx, key)) + + // More than 2/3 signed, msg processed + msg.Signer = addr2 + res = h(ctx, msg) + require.True(t, res.IsOK()) + require.Equal(t, 1, getSequence(ctx, key)) + + // Already processed, transaction failed + msg.Signer = addr3 + res = h(ctx, msg) + require.False(t, res.IsOK()) + require.Equal(t, 1, getSequence(ctx, key)) + + // Less than 2/3 signed, msg not processed + msg = Msg{seqOracle{100, 1}, addr1} + res = h(ctx, msg) + require.True(t, res.IsOK()) + require.Equal(t, 1, getSequence(ctx, key)) + + // More than 2/3 signed but payload is invalid + msg.Signer = addr2 + res = h(ctx, msg) + require.True(t, res.IsOK()) + require.NotEqual(t, "", res.Log) + require.Equal(t, 1, getSequence(ctx, key)) + + // Already processed, transaction failed + msg.Signer = addr3 + res = h(ctx, msg) + require.False(t, res.IsOK()) + require.Equal(t, 1, getSequence(ctx, key)) + + // Should handle mock.Validator set change + valset.AddValidator(mock.Validator{addr4, sdk.NewRat(12)}) + bz, err = json.Marshal(valset) + require.Nil(t, err) + ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz}) + + // Less than 2/3 signed, msg not processed + msg = Msg{seqOracle{1, 2}, addr1} + res = h(ctx, msg) + require.True(t, res.IsOK()) + require.Equal(t, 1, getSequence(ctx, key)) + + // Less than 2/3 signed, msg not processed + msg.Signer = addr2 + res = h(ctx, msg) + require.True(t, res.IsOK()) + require.Equal(t, 1, getSequence(ctx, key)) + + // More than 2/3 signed, msg processed + msg.Signer = addr4 + res = h(ctx, msg) + require.True(t, res.IsOK()) + require.Equal(t, 2, getSequence(ctx, key)) + + // Should handle mock.Validator set change while oracle process is happening + msg = Msg{seqOracle{2, 3}, addr4} + + // Less than 2/3 signed, msg not processed + res = h(ctx, msg) + require.True(t, res.IsOK()) + require.Equal(t, 2, getSequence(ctx, key)) + + // Signed mock.Validator is kicked out + valset.RemoveValidator(addr4) + bz, err = json.Marshal(valset) + require.Nil(t, err) + ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz}) + + // Less than 2/3 signed, msg not processed + msg.Signer = addr1 + res = h(ctx, msg) + require.True(t, res.IsOK()) + require.Equal(t, 2, getSequence(ctx, key)) + + // More than 2/3 signed, msg processed + msg.Signer = addr2 + res = h(ctx, msg) + require.True(t, res.IsOK()) + require.Equal(t, 3, getSequence(ctx, key)) +} diff --git a/examples/democoin/x/oracle/types.go b/examples/democoin/x/oracle/types.go new file mode 100644 index 000000000..ab4b04a42 --- /dev/null +++ b/examples/democoin/x/oracle/types.go @@ -0,0 +1,33 @@ +package oracle + +import ( + "encoding/json" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Msg - struct for voting on payloads +type Msg struct { + Payload + Signer sdk.AccAddress +} + +// GetSignBytes implements sdk.Msg +func (msg Msg) GetSignBytes() []byte { + bz, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(bz) +} + +// GetSigners implements sdk.Msg +func (msg Msg) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} + +// Payload defines inner data for actual execution +type Payload interface { + Type() string + ValidateBasic() sdk.Error +} diff --git a/examples/democoin/x/pow/app_test.go b/examples/democoin/x/pow/app_test.go index aa71fb080..32f57b8cb 100644 --- a/examples/democoin/x/pow/app_test.go +++ b/examples/democoin/x/pow/app_test.go @@ -3,20 +3,20 @@ package pow import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/mock" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" ) var ( priv1 = crypto.GenPrivKeyEd25519() - addr1 = priv1.PubKey().Address() + addr1 = sdk.AccAddress(priv1.PubKey().Address()) ) // initialize the mock application for this module @@ -32,7 +32,7 @@ func getMockApp(t *testing.T) *mock.App { mapp.SetInitChainer(getInitChainer(mapp, keeper)) - mapp.CompleteSetup(t, []*sdk.KVStoreKey{keyPOW}) + require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyPOW})) return mapp } @@ -67,17 +67,17 @@ func TestMsgMine(t *testing.T) { // A checkTx context (true) ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) - assert.Equal(t, acc1, res1) + require.Equal(t, acc1, res1) // Mine and check for reward mineMsg1 := GenerateMsgMine(addr1, 1, 2) - mock.SignCheckDeliver(t, mapp.BaseApp, mineMsg1, []int64{0}, []int64{0}, true, priv1) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", 1}}) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg1}, []int64{0}, []int64{0}, true, priv1) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(1)}}) // Mine again and check for reward mineMsg2 := GenerateMsgMine(addr1, 2, 3) - mock.SignCheckDeliver(t, mapp.BaseApp, mineMsg2, []int64{0}, []int64{1}, true, priv1) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", 2}}) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, true, priv1) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(2)}}) // Mine again - should be invalid - mock.SignCheckDeliver(t, mapp.BaseApp, mineMsg2, []int64{0}, []int64{1}, false, priv1) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", 2}}) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, false, priv1) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(2)}}) } diff --git a/examples/democoin/x/pow/client/cli/tx.go b/examples/democoin/x/pow/client/cli/tx.go index 92f97d4e0..bc958ffae 100644 --- a/examples/democoin/x/pow/client/cli/tx.go +++ b/examples/democoin/x/pow/client/cli/tx.go @@ -1,7 +1,6 @@ package cli import ( - "fmt" "strconv" "github.com/spf13/cobra" @@ -9,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/examples/democoin/x/pow" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" ) @@ -48,12 +48,11 @@ func MineCmd(cdc *wire.Codec) *cobra.Command { name := ctx.FromAddressName // build and sign the transaction, then broadcast to Tendermint - res, err := ctx.EnsureSignBuildBroadcast(name, msg, cdc) + err = ctx.EnsureSignBuildBroadcast(name, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } diff --git a/examples/democoin/x/pow/errors.go b/examples/democoin/x/pow/errors.go index a499e0d9f..e25964da7 100644 --- a/examples/democoin/x/pow/errors.go +++ b/examples/democoin/x/pow/errors.go @@ -23,21 +23,21 @@ const ( func codeToDefaultMsg(code CodeType) string { switch code { case CodeInvalidDifficulty: - return "Insuffient difficulty" + return "insuffient difficulty" case CodeNonexistentDifficulty: - return "Nonexistent difficulty" + return "nonexistent difficulty" case CodeNonexistentReward: - return "Nonexistent reward" + return "nonexistent reward" case CodeNonexistentCount: - return "Nonexistent count" + return "nonexistent count" case CodeInvalidProof: - return "Invalid proof" + return "invalid proof" case CodeNotBelowTarget: - return "Not below target" + return "not below target" case CodeInvalidCount: - return "Invalid count" + return "invalid count" case CodeUnknownRequest: - return "Unknown request" + return "unknown request" default: return sdk.CodeToDefaultMsg(code) } diff --git a/examples/democoin/x/pow/handler_test.go b/examples/democoin/x/pow/handler_test.go index 30aeafa2a..a203d2776 100644 --- a/examples/democoin/x/pow/handler_test.go +++ b/examples/democoin/x/pow/handler_test.go @@ -3,10 +3,10 @@ package pow import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" sdk "github.com/cosmos/cosmos-sdk/types" wire "github.com/cosmos/cosmos-sdk/wire" @@ -19,34 +19,34 @@ func TestPowHandler(t *testing.T) { cdc := wire.NewCodec() auth.RegisterBaseAccount(cdc) - am := auth.NewAccountMapper(cdc, capKey, &auth.BaseAccount{}) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + am := auth.NewAccountMapper(cdc, capKey, auth.ProtoBaseAccount) + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) config := NewConfig("pow", int64(1)) ck := bank.NewKeeper(am) keeper := NewKeeper(capKey, config, ck, DefaultCodespace) handler := keeper.Handler - addr := sdk.Address([]byte("sender")) + addr := sdk.AccAddress([]byte("sender")) count := uint64(1) difficulty := uint64(2) err := InitGenesis(ctx, keeper, Genesis{uint64(1), uint64(0)}) - assert.Nil(t, err) + require.Nil(t, err) nonce, proof := mine(addr, count, difficulty) msg := NewMsgMine(addr, difficulty, count, nonce, proof) result := handler(ctx, msg) - assert.Equal(t, result, sdk.Result{}) + require.Equal(t, result, sdk.Result{}) newDiff, err := keeper.GetLastDifficulty(ctx) - assert.Nil(t, err) - assert.Equal(t, newDiff, uint64(2)) + require.Nil(t, err) + require.Equal(t, newDiff, uint64(2)) newCount, err := keeper.GetLastCount(ctx) - assert.Nil(t, err) - assert.Equal(t, newCount, uint64(1)) + require.Nil(t, err) + require.Equal(t, newCount, uint64(1)) // todo assert correct coin change, awaiting https://github.com/cosmos/cosmos-sdk/pull/691 @@ -55,5 +55,5 @@ func TestPowHandler(t *testing.T) { msg = NewMsgMine(addr, difficulty, count, nonce, proof) result = handler(ctx, msg) - assert.NotEqual(t, result, sdk.Result{}) + require.NotEqual(t, result, sdk.Result{}) } diff --git a/examples/democoin/x/pow/keeper.go b/examples/democoin/x/pow/keeper.go index 35fccf742..e276b8721 100644 --- a/examples/democoin/x/pow/keeper.go +++ b/examples/democoin/x/pow/keeper.go @@ -124,8 +124,8 @@ func (k Keeper) CheckValid(ctx sdk.Context, difficulty uint64, count uint64) (ui } // Add some coins for a POW well done -func (k Keeper) ApplyValid(ctx sdk.Context, sender sdk.Address, newDifficulty uint64, newCount uint64) sdk.Error { - _, _, ckErr := k.ck.AddCoins(ctx, sender, []sdk.Coin{sdk.Coin{k.config.Denomination, k.config.Reward}}) +func (k Keeper) ApplyValid(ctx sdk.Context, sender sdk.AccAddress, newDifficulty uint64, newCount uint64) sdk.Error { + _, _, ckErr := k.ck.AddCoins(ctx, sender, []sdk.Coin{sdk.NewCoin(k.config.Denomination, k.config.Reward)}) if ckErr != nil { return ckErr } diff --git a/examples/democoin/x/pow/keeper_test.go b/examples/democoin/x/pow/keeper_test.go index a4afb876a..a6802cb21 100644 --- a/examples/democoin/x/pow/keeper_test.go +++ b/examples/democoin/x/pow/keeper_test.go @@ -3,11 +3,11 @@ package pow import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" @@ -32,26 +32,26 @@ func TestPowKeeperGetSet(t *testing.T) { cdc := wire.NewCodec() auth.RegisterBaseAccount(cdc) - am := auth.NewAccountMapper(cdc, capKey, &auth.BaseAccount{}) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + am := auth.NewAccountMapper(cdc, capKey, auth.ProtoBaseAccount) + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) config := NewConfig("pow", int64(1)) ck := bank.NewKeeper(am) keeper := NewKeeper(capKey, config, ck, DefaultCodespace) err := InitGenesis(ctx, keeper, Genesis{uint64(1), uint64(0)}) - assert.Nil(t, err) + require.Nil(t, err) genesis := WriteGenesis(ctx, keeper) - assert.Nil(t, err) - assert.Equal(t, genesis, Genesis{uint64(1), uint64(0)}) + require.Nil(t, err) + require.Equal(t, genesis, Genesis{uint64(1), uint64(0)}) res, err := keeper.GetLastDifficulty(ctx) - assert.Nil(t, err) - assert.Equal(t, res, uint64(1)) + require.Nil(t, err) + require.Equal(t, res, uint64(1)) keeper.SetLastDifficulty(ctx, 2) res, err = keeper.GetLastDifficulty(ctx) - assert.Nil(t, err) - assert.Equal(t, res, uint64(2)) + require.Nil(t, err) + require.Equal(t, res, uint64(2)) } diff --git a/examples/democoin/x/pow/mine.go b/examples/democoin/x/pow/mine.go index 21d389a1d..bf1c64cd4 100644 --- a/examples/democoin/x/pow/mine.go +++ b/examples/democoin/x/pow/mine.go @@ -6,16 +6,16 @@ import ( "strconv" sdk "github.com/cosmos/cosmos-sdk/types" - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/crypto" ) // generate the mine message -func GenerateMsgMine(sender sdk.Address, count uint64, difficulty uint64) MsgMine { +func GenerateMsgMine(sender sdk.AccAddress, count uint64, difficulty uint64) MsgMine { nonce, hash := mine(sender, count, difficulty) return NewMsgMine(sender, difficulty, count, nonce, hash) } -func hash(sender sdk.Address, count uint64, nonce uint64) []byte { +func hash(sender sdk.AccAddress, count uint64, nonce uint64) []byte { var bytes []byte bytes = append(bytes, []byte(sender)...) countBytes := strconv.FormatUint(count, 16) @@ -30,7 +30,7 @@ func hash(sender sdk.Address, count uint64, nonce uint64) []byte { return ret[:16] } -func mine(sender sdk.Address, count uint64, difficulty uint64) (uint64, []byte) { +func mine(sender sdk.AccAddress, count uint64, difficulty uint64) (uint64, []byte) { target := math.MaxUint64 / difficulty for nonce := uint64(0); ; nonce++ { hash := hash(sender, count, nonce) diff --git a/examples/democoin/x/pow/types.go b/examples/democoin/x/pow/types.go index 039f57d44..2247a1e88 100644 --- a/examples/democoin/x/pow/types.go +++ b/examples/democoin/x/pow/types.go @@ -8,33 +8,33 @@ import ( "math" "strconv" - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" ) // MsgMine - mine some coins with PoW type MsgMine struct { - Sender sdk.Address `json:"sender"` - Difficulty uint64 `json:"difficulty"` - Count uint64 `json:"count"` - Nonce uint64 `json:"nonce"` - Proof []byte `json:"proof"` + Sender sdk.AccAddress `json:"sender"` + Difficulty uint64 `json:"difficulty"` + Count uint64 `json:"count"` + Nonce uint64 `json:"nonce"` + Proof []byte `json:"proof"` } // enforce the msg type at compile time var _ sdk.Msg = MsgMine{} // NewMsgMine - construct mine message -func NewMsgMine(sender sdk.Address, difficulty uint64, count uint64, nonce uint64, proof []byte) MsgMine { +func NewMsgMine(sender sdk.AccAddress, difficulty uint64, count uint64, nonce uint64, proof []byte) MsgMine { return MsgMine{sender, difficulty, count, nonce, proof} } // nolint -func (msg MsgMine) Type() string { return "pow" } -func (msg MsgMine) GetSigners() []sdk.Address { return []sdk.Address{msg.Sender} } +func (msg MsgMine) Type() string { return "pow" } +func (msg MsgMine) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Sender} } func (msg MsgMine) String() string { - return fmt.Sprintf("MsgMine{Sender: %v, Difficulty: %d, Count: %d, Nonce: %d, Proof: %s}", msg.Sender, msg.Difficulty, msg.Count, msg.Nonce, msg.Proof) + return fmt.Sprintf("MsgMine{Sender: %s, Difficulty: %d, Count: %d, Nonce: %d, Proof: %s}", msg.Sender, msg.Difficulty, msg.Count, msg.Nonce, msg.Proof) } // validate the mine message @@ -76,5 +76,5 @@ func (msg MsgMine) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } diff --git a/examples/democoin/x/pow/types_test.go b/examples/democoin/x/pow/types_test.go index e69e59559..be848d940 100644 --- a/examples/democoin/x/pow/types_test.go +++ b/examples/democoin/x/pow/types_test.go @@ -4,27 +4,27 @@ import ( "fmt" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" ) func TestNewMsgMine(t *testing.T) { - addr := sdk.Address([]byte("sender")) + addr := sdk.AccAddress([]byte("sender")) msg := MsgMine{addr, 0, 0, 0, []byte("")} equiv := NewMsgMine(addr, 0, 0, 0, []byte("")) - assert.Equal(t, msg, equiv, "%s != %s", msg, equiv) + require.Equal(t, msg, equiv, "%s != %s", msg, equiv) } func TestMsgMineType(t *testing.T) { - addr := sdk.Address([]byte("sender")) + addr := sdk.AccAddress([]byte("sender")) msg := MsgMine{addr, 0, 0, 0, []byte("")} - assert.Equal(t, msg.Type(), "pow") + require.Equal(t, msg.Type(), "pow") } func TestMsgMineValidation(t *testing.T) { - addr := sdk.Address([]byte("sender")) - otherAddr := sdk.Address([]byte("another")) + addr := sdk.AccAddress([]byte("sender")) + otherAddr := sdk.AccAddress([]byte("another")) count := uint64(0) for difficulty := uint64(1); difficulty < 1000; difficulty += 100 { @@ -33,41 +33,41 @@ func TestMsgMineValidation(t *testing.T) { nonce, proof := mine(addr, count, difficulty) msg := MsgMine{addr, difficulty, count, nonce, proof} err := msg.ValidateBasic() - assert.Nil(t, err, "error with difficulty %d - %+v", difficulty, err) + require.Nil(t, err, "error with difficulty %d - %+v", difficulty, err) msg.Count++ err = msg.ValidateBasic() - assert.NotNil(t, err, "count was wrong, should have thrown error with msg %s", msg) + require.NotNil(t, err, "count was wrong, should have thrown error with msg %s", msg) msg.Count-- msg.Nonce++ err = msg.ValidateBasic() - assert.NotNil(t, err, "nonce was wrong, should have thrown error with msg %s", msg) + require.NotNil(t, err, "nonce was wrong, should have thrown error with msg %s", msg) msg.Nonce-- msg.Sender = otherAddr err = msg.ValidateBasic() - assert.NotNil(t, err, "sender was wrong, should have thrown error with msg %s", msg) + require.NotNil(t, err, "sender was wrong, should have thrown error with msg %s", msg) } } func TestMsgMineString(t *testing.T) { - addr := sdk.Address([]byte("sender")) + addr := sdk.AccAddress([]byte("sender")) msg := MsgMine{addr, 0, 0, 0, []byte("abc")} res := msg.String() - assert.Equal(t, res, "MsgMine{Sender: 73656E646572, Difficulty: 0, Count: 0, Nonce: 0, Proof: abc}") + require.Equal(t, res, "MsgMine{Sender: cosmosaccaddr1wdjkuer9wg4wml9c, Difficulty: 0, Count: 0, Nonce: 0, Proof: abc}") } func TestMsgMineGetSignBytes(t *testing.T) { - addr := sdk.Address([]byte("sender")) + addr := sdk.AccAddress([]byte("sender")) msg := MsgMine{addr, 1, 1, 1, []byte("abc")} res := msg.GetSignBytes() - assert.Equal(t, string(res), `{"sender":"73656E646572","difficulty":1,"count":1,"nonce":1,"proof":"YWJj"}`) + require.Equal(t, string(res), `{"count":1,"difficulty":1,"nonce":1,"proof":"YWJj","sender":"cosmosaccaddr1wdjkuer9wg4wml9c"}`) } func TestMsgMineGetSigners(t *testing.T) { - addr := sdk.Address([]byte("sender")) + addr := sdk.AccAddress([]byte("sender")) msg := MsgMine{addr, 1, 1, 1, []byte("abc")} res := msg.GetSigners() - assert.Equal(t, fmt.Sprintf("%v", res), "[73656E646572]") + require.Equal(t, fmt.Sprintf("%v", res), "[73656E646572]") } diff --git a/examples/democoin/x/simplestake/client/cli/commands.go b/examples/democoin/x/simplestake/client/cli/commands.go index ede1efc75..20b5d9522 100644 --- a/examples/democoin/x/simplestake/client/cli/commands.go +++ b/examples/democoin/x/simplestake/client/cli/commands.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/crypto" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" @@ -87,11 +87,10 @@ func UnbondTxCmd(cdc *wire.Codec) *cobra.Command { func sendMsg(cdc *wire.Codec, msg sdk.Msg) error { ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) + err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil } diff --git a/examples/democoin/x/simplestake/errors.go b/examples/democoin/x/simplestake/errors.go index 0effba9c0..8125e57aa 100644 --- a/examples/democoin/x/simplestake/errors.go +++ b/examples/democoin/x/simplestake/errors.go @@ -32,6 +32,7 @@ func ErrEmptyStake(codespace sdk.CodespaceType) sdk.Error { // ----------------------------- // Helpers +// nolint: unparam func newError(codespace sdk.CodespaceType, code sdk.CodeType, msg string) sdk.Error { return sdk.NewError(codespace, code, msg) } diff --git a/examples/democoin/x/simplestake/handler.go b/examples/democoin/x/simplestake/handler.go index a94da3d5e..114f06643 100644 --- a/examples/democoin/x/simplestake/handler.go +++ b/examples/democoin/x/simplestake/handler.go @@ -1,56 +1,33 @@ package simplestake import ( - abci "github.com/tendermint/abci/types" - tmtypes "github.com/tendermint/tendermint/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // NewHandler returns a handler for "simplestake" type messages. func NewHandler(k Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - switch msg := msg.(type) { + switch msg.(type) { case MsgBond: - return handleMsgBond(ctx, k, msg) + return handleMsgBond() case MsgUnbond: - return handleMsgUnbond(ctx, k, msg) + return handleMsgUnbond() default: return sdk.ErrUnknownRequest("No match for message type.").Result() } } } -func handleMsgBond(ctx sdk.Context, k Keeper, msg MsgBond) sdk.Result { - power, err := k.Bond(ctx, msg.Address, msg.PubKey, msg.Stake) - if err != nil { - return err.Result() - } - - valSet := abci.Validator{ - PubKey: tmtypes.TM2PB.PubKey(msg.PubKey), - Power: power, - } - +func handleMsgBond() sdk.Result { + // Removed ValidatorSet from result because it does not get used. + // TODO: Implement correct bond/unbond handling return sdk.Result{ - Code: sdk.ABCICodeOK, - ValidatorUpdates: abci.Validators{valSet}, + Code: sdk.ABCICodeOK, } } -func handleMsgUnbond(ctx sdk.Context, k Keeper, msg MsgUnbond) sdk.Result { - pubKey, _, err := k.Unbond(ctx, msg.Address) - if err != nil { - return err.Result() - } - - valSet := abci.Validator{ - PubKey: tmtypes.TM2PB.PubKey(pubKey), - Power: int64(0), - } - +func handleMsgUnbond() sdk.Result { return sdk.Result{ - Code: sdk.ABCICodeOK, - ValidatorUpdates: abci.Validators{valSet}, + Code: sdk.ABCICodeOK, } } diff --git a/examples/democoin/x/simplestake/keeper.go b/examples/democoin/x/simplestake/keeper.go index 5bd263961..a6e3704db 100644 --- a/examples/democoin/x/simplestake/keeper.go +++ b/examples/democoin/x/simplestake/keeper.go @@ -1,7 +1,7 @@ package simplestake import ( - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" @@ -32,7 +32,7 @@ func NewKeeper(key sdk.StoreKey, coinKeeper bank.Keeper, codespace sdk.Codespace } } -func (k Keeper) getBondInfo(ctx sdk.Context, addr sdk.Address) bondInfo { +func (k Keeper) getBondInfo(ctx sdk.Context, addr sdk.AccAddress) bondInfo { store := ctx.KVStore(k.key) bz := store.Get(addr) if bz == nil { @@ -46,7 +46,7 @@ func (k Keeper) getBondInfo(ctx sdk.Context, addr sdk.Address) bondInfo { return bi } -func (k Keeper) setBondInfo(ctx sdk.Context, addr sdk.Address, bi bondInfo) { +func (k Keeper) setBondInfo(ctx sdk.Context, addr sdk.AccAddress, bi bondInfo) { store := ctx.KVStore(k.key) bz, err := k.cdc.MarshalBinary(bi) if err != nil { @@ -55,13 +55,13 @@ func (k Keeper) setBondInfo(ctx sdk.Context, addr sdk.Address, bi bondInfo) { store.Set(addr, bz) } -func (k Keeper) deleteBondInfo(ctx sdk.Context, addr sdk.Address) { +func (k Keeper) deleteBondInfo(ctx sdk.Context, addr sdk.AccAddress) { store := ctx.KVStore(k.key) store.Delete(addr) } // register a bond with the keeper -func (k Keeper) Bond(ctx sdk.Context, addr sdk.Address, pubKey crypto.PubKey, stake sdk.Coin) (int64, sdk.Error) { +func (k Keeper) Bond(ctx sdk.Context, addr sdk.AccAddress, pubKey crypto.PubKey, stake sdk.Coin) (int64, sdk.Error) { if stake.Denom != stakingToken { return 0, ErrIncorrectStakingToken(k.codespace) } @@ -79,21 +79,21 @@ func (k Keeper) Bond(ctx sdk.Context, addr sdk.Address, pubKey crypto.PubKey, st } } - bi.Power = bi.Power + stake.Amount + bi.Power = bi.Power + stake.Amount.Int64() k.setBondInfo(ctx, addr, bi) return bi.Power, nil } // register an unbond with the keeper -func (k Keeper) Unbond(ctx sdk.Context, addr sdk.Address) (crypto.PubKey, int64, sdk.Error) { +func (k Keeper) Unbond(ctx sdk.Context, addr sdk.AccAddress) (crypto.PubKey, int64, sdk.Error) { bi := k.getBondInfo(ctx, addr) if bi.isEmpty() { return nil, 0, ErrInvalidUnbond(k.codespace) } k.deleteBondInfo(ctx, addr) - returnedBond := sdk.Coin{stakingToken, bi.Power} + returnedBond := sdk.NewCoin(stakingToken, bi.Power) _, _, err := k.ck.AddCoins(ctx, addr, []sdk.Coin{returnedBond}) if err != nil { @@ -105,7 +105,7 @@ func (k Keeper) Unbond(ctx sdk.Context, addr sdk.Address) (crypto.PubKey, int64, // FOR TESTING PURPOSES ------------------------------------------------- -func (k Keeper) bondWithoutCoins(ctx sdk.Context, addr sdk.Address, pubKey crypto.PubKey, stake sdk.Coin) (int64, sdk.Error) { +func (k Keeper) bondWithoutCoins(ctx sdk.Context, addr sdk.AccAddress, pubKey crypto.PubKey, stake sdk.Coin) (int64, sdk.Error) { if stake.Denom != stakingToken { return 0, ErrIncorrectStakingToken(k.codespace) } @@ -118,13 +118,13 @@ func (k Keeper) bondWithoutCoins(ctx sdk.Context, addr sdk.Address, pubKey crypt } } - bi.Power = bi.Power + stake.Amount + bi.Power = bi.Power + stake.Amount.Int64() k.setBondInfo(ctx, addr, bi) return bi.Power, nil } -func (k Keeper) unbondWithoutCoins(ctx sdk.Context, addr sdk.Address) (crypto.PubKey, int64, sdk.Error) { +func (k Keeper) unbondWithoutCoins(ctx sdk.Context, addr sdk.AccAddress) (crypto.PubKey, int64, sdk.Error) { bi := k.getBondInfo(ctx, addr) if bi.isEmpty() { return nil, 0, ErrInvalidUnbond(k.codespace) diff --git a/examples/democoin/x/simplestake/keeper_test.go b/examples/democoin/x/simplestake/keeper_test.go index 15bd14c79..026f34346 100644 --- a/examples/democoin/x/simplestake/keeper_test.go +++ b/examples/democoin/x/simplestake/keeper_test.go @@ -5,12 +5,12 @@ import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" @@ -35,13 +35,13 @@ func TestKeeperGetSet(t *testing.T) { cdc := wire.NewCodec() auth.RegisterBaseAccount(cdc) - accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{}) + accountMapper := auth.NewAccountMapper(cdc, authKey, auth.ProtoBaseAccount) stakeKeeper := NewKeeper(capKey, bank.NewKeeper(accountMapper), DefaultCodespace) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) - addr := sdk.Address([]byte("some-address")) + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + addr := sdk.AccAddress([]byte("some-address")) bi := stakeKeeper.getBondInfo(ctx, addr) - assert.Equal(t, bi, bondInfo{}) + require.Equal(t, bi, bondInfo{}) privKey := crypto.GenPrivKeyEd25519() @@ -53,9 +53,9 @@ func TestKeeperGetSet(t *testing.T) { stakeKeeper.setBondInfo(ctx, addr, bi) savedBi := stakeKeeper.getBondInfo(ctx, addr) - assert.NotNil(t, savedBi) + require.NotNil(t, savedBi) fmt.Printf("Bond Info: %v\n", savedBi) - assert.Equal(t, int64(10), savedBi.Power) + require.Equal(t, int64(10), savedBi.Power) } func TestBonding(t *testing.T) { @@ -63,28 +63,29 @@ func TestBonding(t *testing.T) { cdc := wire.NewCodec() auth.RegisterBaseAccount(cdc) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{}) + accountMapper := auth.NewAccountMapper(cdc, authKey, auth.ProtoBaseAccount) coinKeeper := bank.NewKeeper(accountMapper) stakeKeeper := NewKeeper(capKey, coinKeeper, DefaultCodespace) - addr := sdk.Address([]byte("some-address")) + addr := sdk.AccAddress([]byte("some-address")) privKey := crypto.GenPrivKeyEd25519() pubKey := privKey.PubKey() _, _, err := stakeKeeper.unbondWithoutCoins(ctx, addr) - assert.Equal(t, err, ErrInvalidUnbond(DefaultCodespace)) + require.Equal(t, err, ErrInvalidUnbond(DefaultCodespace)) - _, err = stakeKeeper.bondWithoutCoins(ctx, addr, pubKey, sdk.Coin{"steak", 10}) - assert.Nil(t, err) + _, err = stakeKeeper.bondWithoutCoins(ctx, addr, pubKey, sdk.NewCoin("steak", 10)) + require.Nil(t, err) - power, err := stakeKeeper.bondWithoutCoins(ctx, addr, pubKey, sdk.Coin{"steak", 10}) - assert.Equal(t, int64(20), power) + power, err := stakeKeeper.bondWithoutCoins(ctx, addr, pubKey, sdk.NewCoin("steak", 10)) + require.Nil(t, err) + require.Equal(t, int64(20), power) pk, _, err := stakeKeeper.unbondWithoutCoins(ctx, addr) - assert.Nil(t, err) - assert.Equal(t, pubKey, pk) + require.Nil(t, err) + require.Equal(t, pubKey, pk) _, _, err = stakeKeeper.unbondWithoutCoins(ctx, addr) - assert.Equal(t, err, ErrInvalidUnbond(DefaultCodespace)) + require.Equal(t, err, ErrInvalidUnbond(DefaultCodespace)) } diff --git a/examples/democoin/x/simplestake/msgs.go b/examples/democoin/x/simplestake/msgs.go index 605f6f4e2..9f4c4f5f6 100644 --- a/examples/democoin/x/simplestake/msgs.go +++ b/examples/democoin/x/simplestake/msgs.go @@ -3,7 +3,7 @@ package simplestake import ( "encoding/json" - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -12,12 +12,12 @@ import ( // simple bond message type MsgBond struct { - Address sdk.Address `json:"address"` - Stake sdk.Coin `json:"coins"` - PubKey crypto.PubKey `json:"pub_key"` + Address sdk.AccAddress `json:"address"` + Stake sdk.Coin `json:"coins"` + PubKey crypto.PubKey `json:"pub_key"` } -func NewMsgBond(addr sdk.Address, stake sdk.Coin, pubKey crypto.PubKey) MsgBond { +func NewMsgBond(addr sdk.AccAddress, stake sdk.Coin, pubKey crypto.PubKey) MsgBond { return MsgBond{ Address: addr, Stake: stake, @@ -26,8 +26,8 @@ func NewMsgBond(addr sdk.Address, stake sdk.Coin, pubKey crypto.PubKey) MsgBond } //nolint -func (msg MsgBond) Type() string { return moduleName } //TODO update "stake/createvalidator" -func (msg MsgBond) GetSigners() []sdk.Address { return []sdk.Address{msg.Address} } +func (msg MsgBond) Type() string { return moduleName } //TODO update "stake/createvalidator" +func (msg MsgBond) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Address} } // basic validation of the bond message func (msg MsgBond) ValidateBasic() sdk.Error { @@ -48,26 +48,26 @@ func (msg MsgBond) GetSignBytes() []byte { if err != nil { panic(err) } - return bz + return sdk.MustSortJSON(bz) } //_______________________________________________________________ // simple unbond message type MsgUnbond struct { - Address sdk.Address `json:"address"` + Address sdk.AccAddress `json:"address"` } -func NewMsgUnbond(addr sdk.Address) MsgUnbond { +func NewMsgUnbond(addr sdk.AccAddress) MsgUnbond { return MsgUnbond{ Address: addr, } } //nolint -func (msg MsgUnbond) Type() string { return moduleName } //TODO update "stake/createvalidator" -func (msg MsgUnbond) GetSigners() []sdk.Address { return []sdk.Address{msg.Address} } -func (msg MsgUnbond) ValidateBasic() sdk.Error { return nil } +func (msg MsgUnbond) Type() string { return moduleName } //TODO update "stake/createvalidator" +func (msg MsgUnbond) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Address} } +func (msg MsgUnbond) ValidateBasic() sdk.Error { return nil } // get unbond message sign bytes func (msg MsgUnbond) GetSignBytes() []byte { diff --git a/examples/democoin/x/simplestake/msgs_test.go b/examples/democoin/x/simplestake/msgs_test.go index c71ebe7fc..fd6f3612a 100644 --- a/examples/democoin/x/simplestake/msgs_test.go +++ b/examples/democoin/x/simplestake/msgs_test.go @@ -3,9 +3,9 @@ package simplestake import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -16,16 +16,16 @@ func TestBondMsgValidation(t *testing.T) { valid bool msgBond MsgBond }{ - {true, NewMsgBond(sdk.Address{}, sdk.Coin{"mycoin", 5}, privKey.PubKey())}, - {false, NewMsgBond(sdk.Address{}, sdk.Coin{"mycoin", 0}, privKey.PubKey())}, + {true, NewMsgBond(sdk.AccAddress{}, sdk.NewCoin("mycoin", 5), privKey.PubKey())}, + {false, NewMsgBond(sdk.AccAddress{}, sdk.NewCoin("mycoin", 0), privKey.PubKey())}, } for i, tc := range cases { err := tc.msgBond.ValidateBasic() if tc.valid { - assert.Nil(t, err, "%d: %+v", i, err) + require.Nil(t, err, "%d: %+v", i, err) } else { - assert.NotNil(t, err, "%d", i) + require.NotNil(t, err, "%d", i) } } } diff --git a/examples/democoin/x/simplestake/types.go b/examples/democoin/x/simplestake/types.go index 3371fc977..937d36d65 100644 --- a/examples/democoin/x/simplestake/types.go +++ b/examples/democoin/x/simplestake/types.go @@ -1,6 +1,6 @@ package simplestake -import crypto "github.com/tendermint/go-crypto" +import "github.com/tendermint/tendermint/crypto" type bondInfo struct { PubKey crypto.PubKey diff --git a/examples/kvstore/kvstore b/examples/kvstore/kvstore new file mode 100755 index 000000000..43c7dc61c Binary files /dev/null and b/examples/kvstore/kvstore differ diff --git a/examples/kvstore/main.go b/examples/kvstore/main.go index 856538f63..47416da05 100644 --- a/examples/kvstore/main.go +++ b/examples/kvstore/main.go @@ -7,11 +7,11 @@ import ( "github.com/spf13/viper" - "github.com/tendermint/abci/server" - "github.com/tendermint/tmlibs/cli" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" + "github.com/tendermint/tendermint/abci/server" + "github.com/tendermint/tendermint/libs/cli" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" bam "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" @@ -50,17 +50,23 @@ func main() { } // Start the ABCI server - srv, err := server.NewServer("0.0.0.0:46658", "socket", baseApp) + srv, err := server.NewServer("0.0.0.0:26658", "socket", baseApp) if err != nil { fmt.Println(err) os.Exit(1) } - srv.Start() + err = srv.Start() + if err != nil { + cmn.Exit(err.Error()) + } // Wait forever cmn.TrapSignal(func() { // Cleanup - srv.Stop() + err = srv.Stop() + if err != nil { + cmn.Exit(err.Error()) + } }) return } diff --git a/examples/kvstore/tx.go b/examples/kvstore/tx.go index fa32d93bf..0d8312fab 100644 --- a/examples/kvstore/tx.go +++ b/examples/kvstore/tx.go @@ -18,12 +18,16 @@ func (tx kvstoreTx) Type() string { return "kvstore" } -func (tx kvstoreTx) GetMsg() sdk.Msg { - return tx +func (tx kvstoreTx) GetMsgs() []sdk.Msg { + return []sdk.Msg{tx} +} + +func (tx kvstoreTx) GetMemo() string { + return "" } func (tx kvstoreTx) GetSignBytes() []byte { - return tx.bytes + return sdk.MustSortJSON(tx.bytes) } // Should the app be calling this? Or only handlers? @@ -31,7 +35,7 @@ func (tx kvstoreTx) ValidateBasic() sdk.Error { return nil } -func (tx kvstoreTx) GetSigners() []sdk.Address { +func (tx kvstoreTx) GetSigners() []sdk.AccAddress { return nil } diff --git a/networks/local/Makefile b/networks/local/Makefile new file mode 100644 index 000000000..c707a168e --- /dev/null +++ b/networks/local/Makefile @@ -0,0 +1,7 @@ +# Makefile for the "gaiadnode" docker image. + +all: + docker build --tag tendermint/gaiadnode gaiadnode + +.PHONY: all + diff --git a/networks/local/README.md b/networks/local/README.md new file mode 100644 index 000000000..e16d947ab --- /dev/null +++ b/networks/local/README.md @@ -0,0 +1,79 @@ +# Local Cluster with Docker Compose + +## Requirements + +- [Install gaia](/docs/index.rst) +- [Install docker](https://docs.docker.com/engine/installation/) +- [Install docker-compose](https://docs.docker.com/compose/install/) + +## Build + +Build the `gaiad` binary and the `tendermint/gaiadnode` docker image. + +Note the binary will be mounted into the container so it can be updated without +rebuilding the image. + +``` +cd $GOPATH/src/github.com/cosmos/cosmos-sdk + +# Build the linux binary in ./build +make build-linux + +# Build tendermint/gaiadnode image +make build-docker-gaiadnode +``` + + +## Run a testnet + +To start a 4 node testnet run: + +``` +make localnet-start +``` + +The nodes bind their RPC servers to ports 46657, 46660, 46662, and 46664 on the host. +This file creates a 4-node network using the gaiadnode image. +The nodes of the network expose their P2P and RPC endpoints to the host machine on ports 46656-46657, 46659-46660, 46661-46662, and 46663-46664 respectively. + +To update the binary, just rebuild it and restart the nodes: + +``` +make build-linux +make localnet-stop +make localnet-start +``` + +## Configuration + +The `make localnet-start` creates files for a 4-node testnet in `./build` by calling the `gaiad testnet` command. + +The `./build` directory is mounted to the `/gaiad` mount point to attach the binary and config files to the container. + +For instance, to create a single node testnet: + +``` +cd $GOPATH/src/github.com/cosmos/cosmos-sdk + +# Clear the build folder +rm -rf ./build + +# Build binary +make build-linux + +# Create configuration +docker run -v `pwd`/build:/gaiad tendermint/gaiadnode testnet --o . --v 1 + +#Run the node +docker run -v `pwd`/build:/gaiad tendermint/gaiadnode + +``` + +## Logging + +Log is saved under the attached volume, in the `gaiad.log` file and written on the screen. + +## Special binaries + +If you have multiple binaries with different names, you can specify which one to run with the BINARY environment variable. The path of the binary is relative to the attached volume. + diff --git a/networks/local/gaiadnode/Dockerfile b/networks/local/gaiadnode/Dockerfile new file mode 100644 index 000000000..fc2c0d4a0 --- /dev/null +++ b/networks/local/gaiadnode/Dockerfile @@ -0,0 +1,16 @@ +FROM alpine:3.7 +MAINTAINER Greg Szabo <greg@tendermint.com> + +RUN apk update && \ + apk upgrade && \ + apk --no-cache add curl jq file + +VOLUME [ /gaiad ] +WORKDIR /gaiad +EXPOSE 46656 46657 +ENTRYPOINT ["/usr/bin/wrapper.sh"] +CMD ["start"] +STOPSIGNAL SIGTERM + +COPY wrapper.sh /usr/bin/wrapper.sh + diff --git a/networks/local/gaiadnode/wrapper.sh b/networks/local/gaiadnode/wrapper.sh new file mode 100755 index 000000000..b3e90a2a0 --- /dev/null +++ b/networks/local/gaiadnode/wrapper.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env sh + +## +## Input parameters +## +BINARY=/gaiad/${BINARY:-gaiad} +ID=${ID:-0} +LOG=${LOG:-gaiad.log} + +## +## Assert linux binary +## +if ! [ -f "${BINARY}" ]; then + echo "The binary $(basename "${BINARY}") cannot be found. Please add the binary to the shared folder. Please use the BINARY environment variable if the name of the binary is not 'gaiad' E.g.: -e BINARY=gaiad_my_test_version" + exit 1 +fi +BINARY_CHECK="$(file "$BINARY" | grep 'ELF 64-bit LSB executable, x86-64')" +if [ -z "${BINARY_CHECK}" ]; then + echo "Binary needs to be OS linux, ARCH amd64" + exit 1 +fi + +## +## Run binary with all parameters +## +export GAIADHOME="/gaiad/node${ID}/gaiad" + +if [ -d "`dirname ${GAIADHOME}/${LOG}`" ]; then + "$BINARY" --home "$GAIADHOME" "$@" | tee "${GAIADHOME}/${LOG}" +else + "$BINARY" --home "$GAIADHOME" "$@" +fi + +chmod 777 -R /gaiad + diff --git a/networks/remote/ansible/status.yml b/networks/remote/ansible/status.yml index fffba41fc..d0b89d13f 100644 --- a/networks/remote/ansible/status.yml +++ b/networks/remote/ansible/status.yml @@ -9,7 +9,7 @@ - name: Gather status uri: body_format: json - url: "http://{{inventory_hostname}}:46657/status" + url: "http://{{inventory_hostname}}:26657/status" register: status - name: Print status diff --git a/scripts/install_sdk_bsd.sh b/scripts/install_sdk_bsd.sh new file mode 100644 index 000000000..5bff970e6 --- /dev/null +++ b/scripts/install_sdk_bsd.sh @@ -0,0 +1,51 @@ +#!/usr/bin/tcsh + +# Just run tcsh install_sdk_bsd.sh +# XXX: this script is intended to be run from +# a fresh Digital Ocean droplet with FreeBSD + +# upon its completion, you must either reset +# your terminal or run `source ~/.tcshrc` + +# This assumes your installing it through tcsh as root. +# Change the relevant lines from tcsh to csh if your +# installing as a different user, along with changing the +# gopath. + +# change this to a specific release or branch +set BRANCH=master + +sudo pkg update + +sudo pkg upgrade -y +sudo pkg install -y gmake +sudo pkg install -y git + +# get and unpack golang +curl -O https://storage.googleapis.com/golang/go1.10.freebsd-amd64.tar.gz +tar -xvf go1.10.freebsd-amd64.tar.gz + +# move go binary and add to path +mv go /usr/local +set path=($path /usr/local/go/bin) + + +# create the go directory, set GOPATH, and put it on PATH +mkdir go +echo "setenv GOPATH /root/go" >> ~/.tcshrc +setenv GOPATH /root/go +echo "set path=($path $GOPATH/bin)" >> ~/.tcshrc + +source ~/.tcshrc + +# get the code and move into repo +set REPO=github.com/cosmos/cosmos-sdk +go get $REPO +cd $GOPATH/src/$REPO + +# build & install master +git checkout $BRANCH +gmake get_tools +gmake get_vendor_deps +gmake install +gmake install_examples diff --git a/scripts/install_sdk_ubuntu.sh b/scripts/install_sdk_ubuntu.sh new file mode 100644 index 000000000..e8a6a75e3 --- /dev/null +++ b/scripts/install_sdk_ubuntu.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# XXX: this script is intended to be run from +# a fresh Digital Ocean droplet with Ubuntu + +# upon its completion, you must either reset +# your terminal or run `source ~/.profile` + +# change this to a specific release or branch +BRANCH=master + +sudo apt-get update -y +sudo apt-get upgrade -y +sudo apt-get install -y make + +# get and unpack golang +curl -O https://storage.googleapis.com/golang/go1.10.linux-amd64.tar.gz +tar -xvf go1.10.linux-amd64.tar.gz + +# move go binary and add to path +mv go /usr/local +echo "export PATH=\$PATH:/usr/local/go/bin" >> ~/.profile + +# create the goApps directory, set GOPATH, and put it on PATH +mkdir goApps +echo "export GOPATH=/root/goApps" >> ~/.profile +echo "export PATH=\$PATH:\$GOPATH/bin" >> ~/.profile + +source ~/.profile + +# get the code and move into repo +REPO=github.com/cosmos/cosmos-sdk +go get $REPO +cd $GOPATH/src/$REPO + +# build & install master +git checkout $BRANCH +make get_tools +make get_vendor_deps +make install +make install_examples diff --git a/server/config/config.go b/server/config/config.go new file mode 100644 index 000000000..e6fc6a4de --- /dev/null +++ b/server/config/config.go @@ -0,0 +1,14 @@ +package config + +//_____________________________________________________________________ + +// Configuration structure for command functions that share configuration. +// For example: init, init gen-tx and testnet commands need similar input and run the same code + +// Storage for init gen-tx command input parameters +type GenTx struct { + Name string + CliRoot string + Overwrite bool + IP string +} diff --git a/server/constructors.go b/server/constructors.go index c91c67a18..ab7a949e6 100644 --- a/server/constructors.go +++ b/server/constructors.go @@ -4,10 +4,10 @@ import ( "encoding/json" "path/filepath" - abci "github.com/tendermint/abci/types" + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" tmtypes "github.com/tendermint/tendermint/types" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" ) // AppCreator lets us lazily initialize app, using home dir diff --git a/server/export.go b/server/export.go index 794235f62..0ff7456ad 100644 --- a/server/export.go +++ b/server/export.go @@ -20,7 +20,7 @@ func ExportCmd(ctx *Context, cdc *wire.Codec, appExporter AppExporter) *cobra.Co home := viper.GetString("home") appState, validators, err := appExporter(home, ctx.Logger) if err != nil { - return errors.Errorf("Error exporting state: %v\n", err) + return errors.Errorf("error exporting state: %v\n", err) } doc, err := tmtypes.GenesisDocFromFile(ctx.Config.GenesisFile()) if err != nil { diff --git a/server/init.go b/server/init.go index 512751bed..ef7808186 100644 --- a/server/init.go +++ b/server/init.go @@ -14,22 +14,38 @@ import ( "github.com/spf13/pflag" "github.com/spf13/viper" - crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/go-crypto/keys" - "github.com/tendermint/go-crypto/keys/words" + "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/tendermint/tendermint/crypto" + cfg "github.com/tendermint/tendermint/config" + tmcli "github.com/tendermint/tendermint/libs/cli" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/p2p" - tmtypes "github.com/tendermint/tendermint/types" pvm "github.com/tendermint/tendermint/privval" - tmcli "github.com/tendermint/tmlibs/cli" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" + tmtypes "github.com/tendermint/tendermint/types" clkeys "github.com/cosmos/cosmos-sdk/client/keys" + serverconfig "github.com/cosmos/cosmos-sdk/server/config" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" ) +//Parameter names, for init gen-tx command +var ( + FlagName = "name" + FlagClientHome = "home-client" + FlagOWK = "owk" +) + +//parameter names, init command +var ( + FlagOverwrite = "overwrite" + FlagGenTxs = "gen-txs" + FlagIP = "ip" + FlagChainID = "chain-id" +) + // genesis piece structure for creating combined genesis type GenesisTx struct { NodeID string `json:"node_id"` @@ -38,12 +54,13 @@ type GenesisTx struct { AppGenTx json.RawMessage `json:"app_gen_tx"` } -var ( - flagOverwrite = "overwrite" - flagGenTxs = "gen-txs" - flagIP = "ip" - flagChainID = "chain-id" -) +// Storage for init command input parameters +type InitConfig struct { + ChainID string + GenTxs bool + GenTxsDir string + Overwrite bool +} // get cmd to initialize all files for tendermint and application func GenTxCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command { @@ -54,50 +71,27 @@ func GenTxCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command { RunE: func(_ *cobra.Command, args []string) error { config := ctx.Config - nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) - if err != nil { - return err - } - nodeID := string(nodeKey.ID()) - pubKey := readOrCreatePrivValidator(config) + config.SetRoot(viper.GetString(tmcli.HomeFlag)) - appGenTx, cliPrint, validator, err := appInit.AppGenTx(cdc, pubKey) - if err != nil { - return err - } - - ip := viper.GetString(flagIP) + ip := viper.GetString(FlagIP) if len(ip) == 0 { - ip, err = externalIP() + eip, err := externalIP() if err != nil { return err } + ip = eip } - tx := GenesisTx{ - NodeID: nodeID, - IP: ip, - Validator: validator, - AppGenTx: appGenTx, + genTxConfig := serverconfig.GenTx{ + viper.GetString(FlagName), + viper.GetString(FlagClientHome), + viper.GetBool(FlagOWK), + ip, } - bz, err := wire.MarshalJSONIndent(cdc, tx) + cliPrint, genTxFile, err := gentxWithConfig(cdc, appInit, config, genTxConfig) if err != nil { return err } - genTxFile := json.RawMessage(bz) - name := fmt.Sprintf("gentx-%v.json", nodeID) - writePath := filepath.Join(viper.GetString(tmcli.HomeFlag), "config", "gentx") - file := filepath.Join(writePath, name) - err = cmn.EnsureDir(writePath, 0700) - if err != nil { - return err - } - err = cmn.WriteFile(file, bz, 0644) - if err != nil { - return err - } - - // print out some key information toPrint := struct { AppMessage json.RawMessage `json:"app_message"` GenTxFile json.RawMessage `json:"gen_tx_file"` @@ -113,11 +107,51 @@ func GenTxCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command { return nil }, } - cmd.Flags().String(flagIP, "", "external facing IP to use if left blank IP will be retrieved from this machine") + cmd.Flags().String(FlagIP, "", "external facing IP to use if left blank IP will be retrieved from this machine") cmd.Flags().AddFlagSet(appInit.FlagsAppGenTx) return cmd } +func gentxWithConfig(cdc *wire.Codec, appInit AppInit, config *cfg.Config, genTxConfig serverconfig.GenTx) ( + cliPrint json.RawMessage, genTxFile json.RawMessage, err error) { + nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + if err != nil { + return + } + nodeID := string(nodeKey.ID()) + pubKey := readOrCreatePrivValidator(config) + + appGenTx, cliPrint, validator, err := appInit.AppGenTx(cdc, pubKey, genTxConfig) + if err != nil { + return + } + + tx := GenesisTx{ + NodeID: nodeID, + IP: genTxConfig.IP, + Validator: validator, + AppGenTx: appGenTx, + } + bz, err := wire.MarshalJSONIndent(cdc, tx) + if err != nil { + return + } + genTxFile = json.RawMessage(bz) + name := fmt.Sprintf("gentx-%v.json", nodeID) + writePath := filepath.Join(config.RootDir, "config", "gentx") + file := filepath.Join(writePath, name) + err = cmn.EnsureDir(writePath, 0700) + if err != nil { + return + } + err = cmn.WriteFile(file, bz, 0644) + if err != nil { + return + } + + return +} + // get cmd to initialize all files for tendermint and application func InitCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command { cmd := &cobra.Command{ @@ -127,58 +161,18 @@ func InitCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command { RunE: func(_ *cobra.Command, _ []string) error { config := ctx.Config - nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + config.SetRoot(viper.GetString(tmcli.HomeFlag)) + initConfig := InitConfig{ + viper.GetString(FlagChainID), + viper.GetBool(FlagGenTxs), + filepath.Join(config.RootDir, "config", "gentx"), + viper.GetBool(FlagOverwrite), + } + + chainID, nodeID, appMessage, err := initWithConfig(cdc, appInit, config, initConfig) if err != nil { return err } - nodeID := string(nodeKey.ID()) - pubKey := readOrCreatePrivValidator(config) - - chainID := viper.GetString(flagChainID) - if chainID == "" { - chainID = cmn.Fmt("test-chain-%v", cmn.RandStr(6)) - } - - genFile := config.GenesisFile() - if !viper.GetBool(flagOverwrite) && cmn.FileExists(genFile) { - return fmt.Errorf("genesis.json file already exists: %v", genFile) - } - - // process genesis transactions, or otherwise create one for defaults - var appMessage json.RawMessage - var appGenTxs []json.RawMessage - var validators []tmtypes.GenesisValidator - var persistentPeers string - - if viper.GetBool(flagGenTxs) { - genTxsDir := filepath.Join(viper.GetString(tmcli.HomeFlag), "config", "gentx") - validators, appGenTxs, persistentPeers, err = processGenTxs(genTxsDir, cdc, appInit) - if err != nil { - return err - } - config.P2P.PersistentPeers = persistentPeers - configFilePath := filepath.Join(viper.GetString(tmcli.HomeFlag), "config", "config.toml") - cfg.WriteConfigFile(configFilePath, config) - } else { - appGenTx, am, validator, err := appInit.AppGenTx(cdc, pubKey) - appMessage = am - if err != nil { - return err - } - validators = []tmtypes.GenesisValidator{validator} - appGenTxs = []json.RawMessage{appGenTx} - } - - appState, err := appInit.AppGenState(cdc, appGenTxs) - if err != nil { - return err - } - - err = writeGenesisFile(cdc, genFile, chainID, validators, appState) - if err != nil { - return err - } - // print out some key information toPrint := struct { ChainID string `json:"chain_id"` @@ -194,21 +188,82 @@ func InitCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command { return err } fmt.Println(string(out)) - return nil }, } - cmd.Flags().BoolP(flagOverwrite, "o", false, "overwrite the genesis.json file") - cmd.Flags().String(flagChainID, "", "genesis file chain-id, if left blank will be randomly created") - cmd.Flags().Bool(flagGenTxs, false, "apply genesis transactions from [--home]/config/gentx/") + cmd.Flags().BoolP(FlagOverwrite, "o", false, "overwrite the genesis.json file") + cmd.Flags().String(FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") + cmd.Flags().Bool(FlagGenTxs, false, "apply genesis transactions from [--home]/config/gentx/") cmd.Flags().AddFlagSet(appInit.FlagsAppGenState) cmd.Flags().AddFlagSet(appInit.FlagsAppGenTx) // need to add this flagset for when no GenTx's provided cmd.AddCommand(GenTxCmd(ctx, cdc, appInit)) return cmd } +func initWithConfig(cdc *wire.Codec, appInit AppInit, config *cfg.Config, initConfig InitConfig) ( + chainID string, nodeID string, appMessage json.RawMessage, err error) { + nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + if err != nil { + return + } + nodeID = string(nodeKey.ID()) + pubKey := readOrCreatePrivValidator(config) + + if initConfig.ChainID == "" { + initConfig.ChainID = fmt.Sprintf("test-chain-%v", cmn.RandStr(6)) + } + chainID = initConfig.ChainID + + genFile := config.GenesisFile() + if !initConfig.Overwrite && cmn.FileExists(genFile) { + err = fmt.Errorf("genesis.json file already exists: %v", genFile) + return + } + + // process genesis transactions, or otherwise create one for defaults + var appGenTxs []json.RawMessage + var validators []tmtypes.GenesisValidator + var persistentPeers string + + if initConfig.GenTxs { + validators, appGenTxs, persistentPeers, err = processGenTxs(initConfig.GenTxsDir, cdc) + if err != nil { + return + } + config.P2P.PersistentPeers = persistentPeers + configFilePath := filepath.Join(config.RootDir, "config", "config.toml") + cfg.WriteConfigFile(configFilePath, config) + } else { + genTxConfig := serverconfig.GenTx{ + viper.GetString(FlagName), + viper.GetString(FlagClientHome), + viper.GetBool(FlagOWK), + "127.0.0.1", + } + appGenTx, am, validator, err := appInit.AppGenTx(cdc, pubKey, genTxConfig) + appMessage = am + if err != nil { + return "", "", nil, err + } + validators = []tmtypes.GenesisValidator{validator} + appGenTxs = []json.RawMessage{appGenTx} + } + + appState, err := appInit.AppGenState(cdc, appGenTxs) + if err != nil { + return + } + + err = writeGenesisFile(cdc, genFile, initConfig.ChainID, validators, appState) + if err != nil { + return + } + + return +} + // append a genesis-piece -func processGenTxs(genTxsDir string, cdc *wire.Codec, appInit AppInit) ( +func processGenTxs(genTxsDir string, cdc *wire.Codec) ( validators []tmtypes.GenesisValidator, appGenTxs []json.RawMessage, persistentPeers string, err error) { var fos []os.FileInfo @@ -255,7 +310,7 @@ func processGenTxs(genTxsDir string, cdc *wire.Codec, appInit AppInit) ( if len(persistentPeers) == 0 { comma = "" } - persistentPeers += fmt.Sprintf("%s%s@%s:46656", comma, genTx.NodeID, genTx.IP) + persistentPeers += fmt.Sprintf("%s%s@%s:26656", comma, genTx.NodeID, genTx.IP) } return @@ -277,32 +332,20 @@ func readOrCreatePrivValidator(tmConfig *cfg.Config) crypto.PubKey { return privValidator.GetPubKey() } -// create the genesis file +// writeGenesisFile creates and writes the genesis configuration to disk. An +// error is returned if building or writing the configuration to file fails. func writeGenesisFile(cdc *wire.Codec, genesisFile, chainID string, validators []tmtypes.GenesisValidator, appState json.RawMessage) error { genDoc := tmtypes.GenesisDoc{ - ChainID: chainID, - Validators: validators, + ChainID: chainID, + Validators: validators, + AppStateJSON: appState, } + if err := genDoc.ValidateAndComplete(); err != nil { return err } - if err := genDoc.SaveAs(genesisFile); err != nil { - return err - } - return addAppStateToGenesis(cdc, genesisFile, appState) -} -// Add one line to the genesis file -func addAppStateToGenesis(cdc *wire.Codec, genesisConfigPath string, appState json.RawMessage) error { - bz, err := ioutil.ReadFile(genesisConfigPath) - if err != nil { - return err - } - out, err := AppendJSON(cdc, bz, "app_state", appState) - if err != nil { - return err - } - return ioutil.WriteFile(genesisConfigPath, out, 0600) + return genDoc.SaveAs(genesisFile) } //_____________________________________________________________________ @@ -315,7 +358,7 @@ type AppInit struct { FlagsAppGenTx *pflag.FlagSet // create the application genesis tx - AppGenTx func(cdc *wire.Codec, pk crypto.PubKey) ( + AppGenTx func(cdc *wire.Codec, pk crypto.PubKey, genTxConfig serverconfig.GenTx) ( appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) // AppGenState creates the core parameters initialization. It takes in a @@ -333,14 +376,14 @@ var DefaultAppInit = AppInit{ // simple genesis tx type SimpleGenTx struct { - Addr sdk.Address `json:"addr"` + Addr sdk.AccAddress `json:"addr"` } // Generate a genesis transaction -func SimpleAppGenTx(cdc *wire.Codec, pk crypto.PubKey) ( +func SimpleAppGenTx(cdc *wire.Codec, pk crypto.PubKey, genTxConfig serverconfig.GenTx) ( appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { - var addr sdk.Address + var addr sdk.AccAddress var secret string addr, secret, err = GenerateCoinKey() if err != nil { @@ -389,11 +432,11 @@ func SimpleAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState j "coins": [ { "denom": "mycoin", - "amount": 9007199254740992 + "amount": "9007199254740992" } ] }] -}`, genTx.Addr.String())) +}`, genTx.Addr)) return } @@ -401,50 +444,45 @@ func SimpleAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState j // GenerateCoinKey returns the address of a public key, along with the secret // phrase to recover the private key. -func GenerateCoinKey() (sdk.Address, string, error) { +func GenerateCoinKey() (sdk.AccAddress, string, error) { // construct an in-memory key store - codec, err := words.LoadCodec("english") - if err != nil { - return nil, "", err - } keybase := keys.New( dbm.NewMemDB(), - codec, ) // generate a private key, with recovery phrase - info, secret, err := keybase.Create("name", "pass", keys.AlgoEd25519) + info, secret, err := keybase.CreateMnemonic("name", keys.English, "pass", keys.Secp256k1) if err != nil { - return nil, "", err + return sdk.AccAddress([]byte{}), "", err } - addr := info.PubKey.Address() - return addr, secret, nil + addr := info.GetPubKey().Address() + return sdk.AccAddress(addr), secret, nil } // GenerateSaveCoinKey returns the address of a public key, along with the secret // phrase to recover the private key. -func GenerateSaveCoinKey(clientRoot, keyName, keyPass string, overwrite bool) (sdk.Address, string, error) { +func GenerateSaveCoinKey(clientRoot, keyName, keyPass string, overwrite bool) (sdk.AccAddress, string, error) { // get the keystore from the client keybase, err := clkeys.GetKeyBaseFromDir(clientRoot) if err != nil { - return nil, "", err + return sdk.AccAddress([]byte{}), "", err } // ensure no overwrite if !overwrite { _, err := keybase.Get(keyName) if err == nil { - return nil, "", errors.New("key already exists, overwrite is disabled") + return sdk.AccAddress([]byte{}), "", errors.New("key already exists, overwrite is disabled") } } // generate a private key, with recovery phrase - info, secret, err := keybase.Create(keyName, keyPass, keys.AlgoEd25519) + info, secret, err := keybase.CreateMnemonic(keyName, keys.English, keyPass, keys.Secp256k1) if err != nil { - return nil, "", err + return sdk.AccAddress([]byte{}), "", err } - addr := info.PubKey.Address() - return addr, secret, nil + addr := info.GetPubKey().Address() + return sdk.AccAddress(addr), secret, nil } diff --git a/server/init_test.go b/server/init_test.go index 300decf33..fb448bf5f 100644 --- a/server/init_test.go +++ b/server/init_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/tendermint/tmlibs/log" + "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/server/mock" "github.com/cosmos/cosmos-sdk/wire" @@ -34,6 +34,10 @@ func TestGenTxCmd(t *testing.T) { // TODO } +func TestTestnetFilesCmd(t *testing.T) { + // TODO +} + func TestSimpleAppGenTx(t *testing.T) { // TODO } diff --git a/server/mock/app.go b/server/mock/app.go index ab1a8447a..5229da41e 100644 --- a/server/mock/app.go +++ b/server/mock/app.go @@ -5,13 +5,14 @@ import ( "fmt" "path/filepath" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" tmtypes "github.com/tendermint/tendermint/types" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" bam "github.com/cosmos/cosmos-sdk/baseapp" + gc "github.com/cosmos/cosmos-sdk/server/config" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" ) @@ -124,7 +125,7 @@ func AppGenState(_ *wire.Codec, _ []json.RawMessage) (appState json.RawMessage, } // Return a validator, not much else -func AppGenTx(_ *wire.Codec, pk crypto.PubKey) ( +func AppGenTx(_ *wire.Codec, pk crypto.PubKey, genTxConfig gc.GenTx) ( appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { validator = tmtypes.GenesisValidator{ diff --git a/server/mock/app_test.go b/server/mock/app_test.go index be1d77829..05ec86521 100644 --- a/server/mock/app_test.go +++ b/server/mock/app_test.go @@ -3,10 +3,9 @@ package mock import ( "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" + abci "github.com/tendermint/tendermint/abci/types" ) // TestInitApp makes sure we can initialize this thing without an error @@ -38,7 +37,7 @@ func TestInitApp(t *testing.T) { } qres := app.Query(query) require.Equal(t, uint32(0), qres.Code, qres.Log) - assert.Equal(t, []byte("bar"), qres.Value) + require.Equal(t, []byte("bar"), qres.Value) } // TextDeliverTx ensures we can write a tx @@ -74,5 +73,5 @@ func TestDeliverTx(t *testing.T) { } qres := app.Query(query) require.Equal(t, uint32(0), qres.Code, qres.Log) - assert.Equal(t, []byte(value), qres.Value) + require.Equal(t, []byte(value), qres.Value) } diff --git a/server/mock/helpers.go b/server/mock/helpers.go index 601fee897..88aacb4d8 100644 --- a/server/mock/helpers.go +++ b/server/mock/helpers.go @@ -1,11 +1,12 @@ package mock import ( + "fmt" "io/ioutil" "os" - abci "github.com/tendermint/abci/types" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" ) // SetupApp returns an application as well as a clean-up function @@ -19,7 +20,10 @@ func SetupApp() (abci.Application, func(), error) { } cleanup := func() { - os.RemoveAll(rootDir) + err := os.RemoveAll(rootDir) + if err != nil { + fmt.Printf("could not delete %s, had error %s\n", rootDir, err.Error()) + } } app, err := NewApp(rootDir, logger) diff --git a/server/mock/store.go b/server/mock/store.go index 7f62234ea..d62b9ad23 100644 --- a/server/mock/store.go +++ b/server/mock/store.go @@ -1,7 +1,7 @@ package mock import ( - dbm "github.com/tendermint/tmlibs/db" + dbm "github.com/tendermint/tendermint/libs/db" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -95,6 +95,10 @@ func (kv kvStore) Delete(key []byte) { delete(kv.store, string(key)) } +func (kv kvStore) Prefix(prefix []byte) sdk.KVStore { + panic("not implemented") +} + func (kv kvStore) Iterator(start, end []byte) sdk.Iterator { panic("not implemented") } @@ -111,6 +115,6 @@ func (kv kvStore) ReverseSubspaceIterator(prefix []byte) sdk.Iterator { panic("not implemented") } -func NewCommitMultiStore(db dbm.DB) sdk.CommitMultiStore { +func NewCommitMultiStore() sdk.CommitMultiStore { return multiStore{kv: make(map[sdk.StoreKey]kvStore)} } diff --git a/server/mock/store_test.go b/server/mock/store_test.go index e92060947..0ea5ffd2c 100644 --- a/server/mock/store_test.go +++ b/server/mock/store_test.go @@ -3,31 +3,31 @@ package mock import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - dbm "github.com/tendermint/tmlibs/db" + dbm "github.com/tendermint/tendermint/libs/db" sdk "github.com/cosmos/cosmos-sdk/types" ) func TestStore(t *testing.T) { db := dbm.NewMemDB() - cms := NewCommitMultiStore(db) + cms := NewCommitMultiStore() key := sdk.NewKVStoreKey("test") cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) err := cms.LoadLatestVersion() - assert.Nil(t, err) + require.Nil(t, err) store := cms.GetKVStore(key) - assert.NotNil(t, store) + require.NotNil(t, store) k := []byte("hello") v := []byte("world") - assert.False(t, store.Has(k)) + require.False(t, store.Has(k)) store.Set(k, v) - assert.True(t, store.Has(k)) - assert.Equal(t, v, store.Get(k)) + require.True(t, store.Has(k)) + require.Equal(t, v, store.Get(k)) store.Delete(k) - assert.False(t, store.Has(k)) + require.False(t, store.Has(k)) } diff --git a/server/mock/tx.go b/server/mock/tx.go index bd437f2d0..c15e0ab2c 100644 --- a/server/mock/tx.go +++ b/server/mock/tx.go @@ -31,8 +31,12 @@ func (tx kvstoreTx) Type() string { return "kvstore" } -func (tx kvstoreTx) GetMsg() sdk.Msg { - return tx +func (tx kvstoreTx) GetMsgs() []sdk.Msg { + return []sdk.Msg{tx} +} + +func (tx kvstoreTx) GetMemo() string { + return "" } func (tx kvstoreTx) GetSignBytes() []byte { @@ -44,7 +48,7 @@ func (tx kvstoreTx) ValidateBasic() sdk.Error { return nil } -func (tx kvstoreTx) GetSigners() []sdk.Address { +func (tx kvstoreTx) GetSigners() []sdk.AccAddress { return nil } diff --git a/server/start.go b/server/start.go index 9bf2d30cd..37e40c999 100644 --- a/server/start.go +++ b/server/start.go @@ -5,13 +5,13 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/abci/server" + "github.com/tendermint/tendermint/abci/server" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" + cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/node" - "github.com/tendermint/tendermint/proxy" pvm "github.com/tendermint/tendermint/privval" - cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tendermint/proxy" ) const ( @@ -31,13 +31,14 @@ func StartCmd(ctx *Context, appCreator AppCreator) *cobra.Command { return startStandAlone(ctx, appCreator) } ctx.Logger.Info("Starting ABCI with Tendermint") - return startInProcess(ctx, appCreator) + _, err := startInProcess(ctx, appCreator) + return err }, } // basic flags for abci app cmd.Flags().Bool(flagWithTendermint, true, "run abci app embedded in-process with tendermint") - cmd.Flags().String(flagAddress, "tcp://0.0.0.0:46658", "Listen address") + cmd.Flags().String(flagAddress, "tcp://0.0.0.0:26658", "Listen address") // AddNodeFlags adds support for all tendermint-specific command line options tcmd.AddNodeFlags(cmd) @@ -55,25 +56,31 @@ func startStandAlone(ctx *Context, appCreator AppCreator) error { svr, err := server.NewServer(addr, "socket", app) if err != nil { - return errors.Errorf("Error creating listener: %v\n", err) + return errors.Errorf("error creating listener: %v\n", err) } svr.SetLogger(ctx.Logger.With("module", "abci-server")) - svr.Start() + err = svr.Start() + if err != nil { + cmn.Exit(err.Error()) + } // Wait forever cmn.TrapSignal(func() { // Cleanup - svr.Stop() + err = svr.Stop() + if err != nil { + cmn.Exit(err.Error()) + } }) return nil } -func startInProcess(ctx *Context, appCreator AppCreator) error { +func startInProcess(ctx *Context, appCreator AppCreator) (*node.Node, error) { cfg := ctx.Config home := cfg.RootDir app, err := appCreator(home, ctx.Logger) if err != nil { - return err + return nil, err } // Create & start tendermint node @@ -82,17 +89,18 @@ func startInProcess(ctx *Context, appCreator AppCreator) error { proxy.NewLocalClientCreator(app), node.DefaultGenesisDocProviderFunc(cfg), node.DefaultDBProvider, + node.DefaultMetricsProvider, ctx.Logger.With("module", "node")) if err != nil { - return err + return nil, err } err = n.Start() if err != nil { - return err + return nil, err } // Trap signal, run forever. n.RunForever() - return nil + return n, nil } diff --git a/server/start_test.go b/server/start_test.go index 1c1ad671e..570071e7b 100644 --- a/server/start_test.go +++ b/server/start_test.go @@ -6,18 +6,18 @@ import ( "testing" "time" - "github.com/spf13/viper" "github.com/stretchr/testify/require" "github.com/cosmos/cosmos-sdk/server/mock" "github.com/cosmos/cosmos-sdk/wire" - "github.com/tendermint/abci/server" + "github.com/tendermint/tendermint/abci/server" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" - "github.com/tendermint/tmlibs/log" + "github.com/tendermint/tendermint/libs/log" ) func TestStartStandAlone(t *testing.T) { home, err := ioutil.TempDir("", "mock-sdk-cmd") + require.Nil(t, err) defer func() { os.RemoveAll(home) }() @@ -40,41 +40,13 @@ func TestStartStandAlone(t *testing.T) { svrAddr, _, err := FreeTCPAddr() require.Nil(t, err) svr, err := server.NewServer(svrAddr, "socket", app) - require.Nil(t, err, "Error creating listener") + require.Nil(t, err, "error creating listener") svr.SetLogger(logger.With("module", "abci-server")) svr.Start() - timer := time.NewTimer(time.Duration(5) * time.Second) + timer := time.NewTimer(time.Duration(2) * time.Second) select { case <-timer.C: svr.Stop() } } - -func TestStartWithTendermint(t *testing.T) { - defer setupViper(t)() - - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). - With("module", "mock-cmd") - cfg, err := tcmd.ParseConfig() - require.Nil(t, err) - ctx := NewContext(cfg, logger) - cdc := wire.NewCodec() - appInit := AppInit{ - AppGenState: mock.AppGenState, - AppGenTx: mock.AppGenTx, - } - initCmd := InitCmd(ctx, cdc, appInit) - err = initCmd.RunE(nil, nil) - require.NoError(t, err) - - // set up app and start up - viper.Set(flagWithTendermint, true) - startCmd := StartCmd(ctx, mock.NewApp) - svrAddr, _, err := FreeTCPAddr() - require.NoError(t, err) - startCmd.Flags().Set(flagAddress, svrAddr) // set to a new free address - timeout := time.Duration(5) * time.Second - - close(RunOrTimeout(startCmd, timeout, t)) -} diff --git a/server/test_helpers.go b/server/test_helpers.go index b1050bc6f..d1230898d 100644 --- a/server/test_helpers.go +++ b/server/test_helpers.go @@ -6,23 +6,30 @@ import ( "net" "os" "testing" - "time" - "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/stretchr/testify/require" - "github.com/tendermint/tmlibs/cli" + "github.com/tendermint/tendermint/libs/cli" ) // Get a free address for a test tendermint server // protocol is either tcp, http, etc func FreeTCPAddr() (addr, port string, err error) { l, err := net.Listen("tcp", "0.0.0.0:0") - defer l.Close() if err != nil { return "", "", err } + closer := func() { + err := l.Close() + if err != nil { + // TODO: Handle with #870 + panic(err) + } + } + + defer closer() + portI := l.Addr().(*net.TCPAddr).Port port = fmt.Sprintf("%d", portI) addr = fmt.Sprintf("tcp://0.0.0.0:%s", port) @@ -36,28 +43,10 @@ func setupViper(t *testing.T) func() { require.Nil(t, err) viper.Set(cli.HomeFlag, rootDir) return func() { - os.RemoveAll(rootDir) - } -} - -// Run or Timout RunE of command passed in -func RunOrTimeout(cmd *cobra.Command, timeout time.Duration, t *testing.T) chan error { - done := make(chan error) - go func(out chan<- error) { - // this should NOT exit - err := cmd.RunE(nil, nil) + err := os.RemoveAll(rootDir) if err != nil { - out <- err + // TODO: Handle with #870 + panic(err) } - out <- fmt.Errorf("start died for unknown reasons") - }(done) - timer := time.NewTimer(timeout) - - select { - case err := <-done: - require.NoError(t, err) - case <-timer.C: - return done } - return done } diff --git a/server/testnet.go b/server/testnet.go new file mode 100644 index 000000000..d7e4ec9ac --- /dev/null +++ b/server/testnet.go @@ -0,0 +1,187 @@ +package server + +import ( + "fmt" + "net" + "path/filepath" + + "github.com/spf13/cobra" + + gc "github.com/cosmos/cosmos-sdk/server/config" + + "os" + + "github.com/cosmos/cosmos-sdk/wire" + "github.com/spf13/viper" + cfg "github.com/tendermint/tendermint/config" + cmn "github.com/tendermint/tendermint/libs/common" +) + +var ( + nodeDirPrefix = "node-dir-prefix" + nValidators = "v" + outputDir = "o" + + startingIPAddress = "starting-ip-address" +) + +const nodeDirPerm = 0755 + +// get cmd to initialize all files for tendermint testnet and application +func TestnetFilesCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command { + cmd := &cobra.Command{ + Use: "testnet", + Short: "Initialize files for a Gaiad testnet", + Long: `testnet will create "v" number of directories and populate each with +necessary files (private validator, genesis, config, etc.). + +Note, strict routability for addresses is turned off in the config file. + +Example: + + gaiad testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2 + `, + RunE: func(_ *cobra.Command, _ []string) error { + config := ctx.Config + err := testnetWithConfig(config, cdc, appInit) + return err + }, + } + cmd.Flags().Int(nValidators, 4, + "Number of validators to initialize the testnet with") + cmd.Flags().String(outputDir, "./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(startingIPAddress, "192.168.0.1", + "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)") + return cmd +} + +func testnetWithConfig(config *cfg.Config, cdc *wire.Codec, appInit AppInit) error { + outDir := viper.GetString(outputDir) + numValidators := viper.GetInt(nValidators) + + // Generate private key, node ID, initial transaction + for i := 0; i < numValidators; i++ { + nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i) + nodeDir := filepath.Join(outDir, nodeDirName, "gaiad") + clientDir := filepath.Join(outDir, nodeDirName, "gaiacli") + gentxsDir := filepath.Join(outDir, "gentxs") + config.SetRoot(nodeDir) + + err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm) + if err != nil { + _ = os.RemoveAll(outDir) + return err + } + + err = os.MkdirAll(clientDir, nodeDirPerm) + if err != nil { + _ = os.RemoveAll(outDir) + return err + } + + config.Moniker = nodeDirName + ip, err := getIP(i) + if err != nil { + return err + } + + genTxConfig := gc.GenTx{ + nodeDirName, + clientDir, + true, + ip, + } + + // Run `init gen-tx` and generate initial transactions + cliPrint, genTxFile, err := gentxWithConfig(cdc, appInit, config, genTxConfig) + if err != nil { + return err + } + + // Save private key seed words + name := fmt.Sprintf("%v.json", "key_seed") + err = writeFile(name, clientDir, cliPrint) + if err != nil { + return err + } + + // Gather gentxs folder + name = fmt.Sprintf("%v.json", nodeDirName) + err = writeFile(name, gentxsDir, genTxFile) + if err != nil { + return err + } + } + + // Generate genesis.json and config.toml + chainID := "chain-" + cmn.RandStr(6) + for i := 0; i < numValidators; i++ { + + nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i) + nodeDir := filepath.Join(outDir, nodeDirName, "gaiad") + gentxsDir := filepath.Join(outDir, "gentxs") + initConfig := InitConfig{ + chainID, + true, + gentxsDir, + true, + } + config.Moniker = nodeDirName + config.SetRoot(nodeDir) + + // Run `init` and generate genesis.json and config.toml + _, _, _, err := initWithConfig(cdc, appInit, config, initConfig) + if err != nil { + return err + } + } + + fmt.Printf("Successfully initialized %v node directories\n", viper.GetInt(nValidators)) + return nil +} + +func getIP(i int) (ip string, err error) { + ip = viper.GetString(startingIPAddress) + if len(ip) == 0 { + ip, err = externalIP() + if err != nil { + return "", err + } + } else { + ip, err = calculateIP(ip, 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 +} + +func calculateIP(ip string, i int) (string, error) { + ipv4 := net.ParseIP(ip).To4() + if ipv4 == nil { + return "", fmt.Errorf("%v: non ipv4 address", ip) + } + + for j := 0; j < i; j++ { + ipv4[3]++ + } + return ipv4.String(), nil +} diff --git a/server/util.go b/server/util.go index 9e705f879..1e6ed06c9 100644 --- a/server/util.go +++ b/server/util.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net" "os" + "path/filepath" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -14,9 +15,9 @@ import ( "github.com/cosmos/cosmos-sdk/wire" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" cfg "github.com/tendermint/tendermint/config" - "github.com/tendermint/tmlibs/cli" - tmflags "github.com/tendermint/tmlibs/cli/flags" - "github.com/tendermint/tmlibs/log" + "github.com/tendermint/tendermint/libs/cli" + tmflags "github.com/tendermint/tendermint/libs/cli/flags" + "github.com/tendermint/tendermint/libs/log" ) // server context @@ -46,7 +47,7 @@ func PersistentPreRunEFn(context *Context) func(*cobra.Command, []string) error if cmd.Name() == version.VersionCmd.Name() { return nil } - config, err := tcmd.ParseConfig() + config, err := interceptLoadConfig() if err != nil { return err } @@ -65,6 +66,30 @@ func PersistentPreRunEFn(context *Context) func(*cobra.Command, []string) error } } +// If a new config is created, change some of the default tendermint settings +func interceptLoadConfig() (conf *cfg.Config, err error) { + tmpConf := cfg.DefaultConfig() + err = viper.Unmarshal(tmpConf) + if err != nil { + // TODO: Handle with #870 + panic(err) + } + rootDir := tmpConf.RootDir + configFilePath := filepath.Join(rootDir, "config/config.toml") + // Intercept only if the file doesn't already exist + if _, err := os.Stat(configFilePath); os.IsNotExist(err) { + // the following parse config is needed to create directories + sdkDefaultConfig, _ := tcmd.ParseConfig() + sdkDefaultConfig.ProfListenAddress = "prof_laddr=localhost:6060" + sdkDefaultConfig.P2P.RecvRate = 5120000 + sdkDefaultConfig.P2P.SendRate = 5120000 + cfg.WriteConfigFile(configFilePath, sdkDefaultConfig) + // Fall through, just so that its parsed into memory. + } + conf, err = tcmd.ParseConfig() + return +} + // add server commands func AddCommands( ctx *Context, cdc *wire.Codec, @@ -85,6 +110,7 @@ func AddCommands( rootCmd.AddCommand( InitCmd(ctx, cdc, appInit), + TestnetFilesCmd(ctx, cdc, appInit), StartCmd(ctx, appCreator), UnsafeResetAllCmd(ctx), client.LineBreak, @@ -97,15 +123,21 @@ func AddCommands( //___________________________________________________________________________________ -// append a new json field to existing json message -func AppendJSON(cdc *wire.Codec, baseJSON []byte, key string, value json.RawMessage) (appended []byte, err error) { +// InsertKeyJSON inserts a new JSON field/key with a given value to an existing +// JSON message. An error is returned if any serialization operation fails. +// +// NOTE: The ordering of the keys returned as the resulting JSON message is +// non-deterministic, so the client should not rely on key ordering. +func InsertKeyJSON(cdc *wire.Codec, baseJSON []byte, key string, value json.RawMessage) ([]byte, error) { var jsonMap map[string]json.RawMessage - err = cdc.UnmarshalJSON(baseJSON, &jsonMap) - if err != nil { + + if err := cdc.UnmarshalJSON(baseJSON, &jsonMap); err != nil { return nil, err } + jsonMap[key] = value bz, err := wire.MarshalJSONIndent(cdc, jsonMap) + return json.RawMessage(bz), err } @@ -117,24 +149,15 @@ func externalIP() (string, error) { return "", err } for _, iface := range ifaces { - if iface.Flags&net.FlagUp == 0 { - continue // interface down - } - if iface.Flags&net.FlagLoopback != 0 { - continue // loopback interface + if skipInterface(iface) { + continue } addrs, err := iface.Addrs() if err != nil { return "", err } for _, addr := range addrs { - var ip net.IP - switch v := addr.(type) { - case *net.IPNet: - ip = v.IP - case *net.IPAddr: - ip = v.IP - } + ip := addrToIP(addr) if ip == nil || ip.IsLoopback() { continue } @@ -147,3 +170,24 @@ func externalIP() (string, error) { } return "", errors.New("are you connected to the network?") } + +func skipInterface(iface net.Interface) bool { + if iface.Flags&net.FlagUp == 0 { + return true // interface down + } + if iface.Flags&net.FlagLoopback != 0 { + return true // loopback interface + } + return false +} + +func addrToIP(addr net.Addr) net.IP { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + return ip +} diff --git a/server/util_test.go b/server/util_test.go index 13f8ad5db..6082caa2c 100644 --- a/server/util_test.go +++ b/server/util_test.go @@ -5,11 +5,10 @@ import ( "testing" "github.com/cosmos/cosmos-sdk/wire" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestAppendJSON(t *testing.T) { +func TestInsertKeyJSON(t *testing.T) { cdc := wire.NewCodec() foo := map[string]string{"foo": "foofoo"} @@ -25,7 +24,7 @@ func TestAppendJSON(t *testing.T) { barRaw := json.RawMessage(bz) // make the append - appBz, err := AppendJSON(cdc, fooRaw, "barOuter", barRaw) + appBz, err := InsertKeyJSON(cdc, fooRaw, "barOuter", barRaw) require.NoError(t, err) // test the append @@ -37,5 +36,5 @@ func TestAppendJSON(t *testing.T) { err = cdc.UnmarshalJSON(appended["barOuter"], &resBar) require.NoError(t, err) - assert.Equal(t, bar, resBar, "appended: %v", appended) + require.Equal(t, bar, resBar, "appended: %v", appended) } diff --git a/store/cachekvstore.go b/store/cachekvstore.go index 109bbfc75..1263cef5f 100644 --- a/store/cachekvstore.go +++ b/store/cachekvstore.go @@ -5,7 +5,7 @@ import ( "sort" "sync" - cmn "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tendermint/libs/common" ) // If value is nil but deleted is false, it means the parent doesn't have the @@ -80,6 +80,11 @@ func (ci *cacheKVStore) Delete(key []byte) { ci.setCacheValue(key, nil, true, true) } +// Implements KVStore +func (ci *cacheKVStore) Prefix(prefix []byte) KVStore { + return prefixStore{ci, prefix} +} + // Implements CacheKVStore. func (ci *cacheKVStore) Write() { ci.mtx.Lock() diff --git a/store/cachekvstore_test.go b/store/cachekvstore_test.go index 0c88ca27d..e7958dfcd 100644 --- a/store/cachekvstore_test.go +++ b/store/cachekvstore_test.go @@ -3,10 +3,9 @@ package store import ( "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" ) func newCacheKVStore() CacheKVStore { @@ -103,11 +102,11 @@ func TestCacheKVIteratorBounds(t *testing.T) { var i = 0 for ; itr.Valid(); itr.Next() { k, v := itr.Key(), itr.Value() - assert.Equal(t, keyFmt(i), k) - assert.Equal(t, valFmt(i), v) + require.Equal(t, keyFmt(i), k) + require.Equal(t, valFmt(i), v) i++ } - assert.Equal(t, nItems, i) + require.Equal(t, nItems, i) // iterate over none itr = st.Iterator(bz("money"), nil) @@ -115,29 +114,29 @@ func TestCacheKVIteratorBounds(t *testing.T) { for ; itr.Valid(); itr.Next() { i++ } - assert.Equal(t, 0, i) + require.Equal(t, 0, i) // iterate over lower itr = st.Iterator(keyFmt(0), keyFmt(3)) i = 0 for ; itr.Valid(); itr.Next() { k, v := itr.Key(), itr.Value() - assert.Equal(t, keyFmt(i), k) - assert.Equal(t, valFmt(i), v) + require.Equal(t, keyFmt(i), k) + require.Equal(t, valFmt(i), v) i++ } - assert.Equal(t, 3, i) + require.Equal(t, 3, i) // iterate over upper itr = st.Iterator(keyFmt(2), keyFmt(4)) i = 2 for ; itr.Valid(); itr.Next() { k, v := itr.Key(), itr.Value() - assert.Equal(t, keyFmt(i), k) - assert.Equal(t, valFmt(i), v) + require.Equal(t, keyFmt(i), k) + require.Equal(t, valFmt(i), v) i++ } - assert.Equal(t, 4, i) + require.Equal(t, 4, i) } func TestCacheKVMergeIteratorBasics(t *testing.T) { @@ -367,11 +366,11 @@ func assertIterateDomain(t *testing.T, st KVStore, expectedN int) { var i = 0 for ; itr.Valid(); itr.Next() { k, v := itr.Key(), itr.Value() - assert.Equal(t, keyFmt(i), k) - assert.Equal(t, valFmt(i), v) + require.Equal(t, keyFmt(i), k) + require.Equal(t, valFmt(i), v) i++ } - assert.Equal(t, expectedN, i) + require.Equal(t, expectedN, i) } func assertIterateDomainCheck(t *testing.T, st KVStore, mem dbm.DB, r []keyRange) { @@ -383,25 +382,25 @@ func assertIterateDomainCheck(t *testing.T, st KVStore, mem dbm.DB, r []keyRange i := 0 for ; krc.valid(); krc.next() { - assert.True(t, itr.Valid()) - assert.True(t, itr2.Valid()) + require.True(t, itr.Valid()) + require.True(t, itr2.Valid()) // check the key/val matches the ground truth k, v := itr.Key(), itr.Value() k2, v2 := itr2.Key(), itr2.Value() - assert.Equal(t, k, k2) - assert.Equal(t, v, v2) + require.Equal(t, k, k2) + require.Equal(t, v, v2) // check they match the counter - assert.Equal(t, k, keyFmt(krc.key())) + require.Equal(t, k, keyFmt(krc.key())) itr.Next() itr2.Next() i++ } - assert.False(t, itr.Valid()) - assert.False(t, itr2.Valid()) + require.False(t, itr.Valid()) + require.False(t, itr2.Valid()) } func assertIterateDomainCompare(t *testing.T, st KVStore, mem dbm.DB) { @@ -414,15 +413,15 @@ func assertIterateDomainCompare(t *testing.T, st KVStore, mem dbm.DB) { func checkIterators(t *testing.T, itr, itr2 Iterator) { for ; itr.Valid(); itr.Next() { - assert.True(t, itr2.Valid()) + require.True(t, itr2.Valid()) k, v := itr.Key(), itr.Value() k2, v2 := itr2.Key(), itr2.Value() - assert.Equal(t, k, k2) - assert.Equal(t, v, v2) + require.Equal(t, k, k2) + require.Equal(t, v, v2) itr2.Next() } - assert.False(t, itr.Valid()) - assert.False(t, itr2.Valid()) + require.False(t, itr.Valid()) + require.False(t, itr2.Valid()) } //-------------------------------------------------------- diff --git a/store/dbstoreadapter.go b/store/dbstoreadapter.go index 58c9e1b29..09d48cf9d 100644 --- a/store/dbstoreadapter.go +++ b/store/dbstoreadapter.go @@ -2,7 +2,7 @@ package store import ( sdk "github.com/cosmos/cosmos-sdk/types" - dbm "github.com/tendermint/tmlibs/db" + dbm "github.com/tendermint/tendermint/libs/db" ) type dbStoreAdapter struct { @@ -19,5 +19,10 @@ func (dsa dbStoreAdapter) CacheWrap() CacheWrap { return NewCacheKVStore(dsa) } +// Implements KVStore +func (dsa dbStoreAdapter) Prefix(prefix []byte) KVStore { + return prefixStore{dsa, prefix} +} + // dbm.DB implements KVStore so we can CacheKVStore it. var _ KVStore = dbStoreAdapter{dbm.DB(nil)} diff --git a/store/firstlast.go b/store/firstlast.go index e6cb08432..70f6659a8 100644 --- a/store/firstlast.go +++ b/store/firstlast.go @@ -3,7 +3,7 @@ package store import ( "bytes" - cmn "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tendermint/libs/common" ) // Gets the first item. diff --git a/store/gaskvstore.go b/store/gaskvstore.go index db65921da..6dc699dfb 100644 --- a/store/gaskvstore.go +++ b/store/gaskvstore.go @@ -65,6 +65,11 @@ func (gi *gasKVStore) Delete(key []byte) { gi.parent.Delete(key) } +// Implements KVStore +func (gi *gasKVStore) Prefix(prefix []byte) KVStore { + return prefixStore{gi, prefix} +} + // Implements KVStore. func (gi *gasKVStore) Iterator(start, end []byte) sdk.Iterator { return gi.iterator(start, end, true) diff --git a/store/gaskvstore_test.go b/store/gaskvstore_test.go index 524dc5323..fe84affa2 100644 --- a/store/gaskvstore_test.go +++ b/store/gaskvstore_test.go @@ -5,7 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" - dbm "github.com/tendermint/tmlibs/db" + dbm "github.com/tendermint/tendermint/libs/db" ) func newGasKVStore() KVStore { diff --git a/store/iavlstore.go b/store/iavlstore.go index 2b7914c4e..d05c867c2 100644 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -4,17 +4,19 @@ import ( "fmt" "sync" - abci "github.com/tendermint/abci/types" + "github.com/tendermint/go-amino" "github.com/tendermint/iavl" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" sdk "github.com/cosmos/cosmos-sdk/types" ) const ( defaultIAVLCacheSize = 10000 - defaultIAVLNumHistory = 1<<53 - 1 // DEPRECATED + defaultIAVLNumRecent = 100 + defaultIAVLStoreEvery = 1 ) // load the iavl store @@ -24,7 +26,7 @@ func LoadIAVLStore(db dbm.DB, id CommitID) (CommitStore, error) { if err != nil { return nil, err } - store := newIAVLStore(tree, defaultIAVLNumHistory) + store := newIAVLStore(tree, defaultIAVLNumRecent, defaultIAVLStoreEvery) return store, nil } @@ -41,15 +43,25 @@ type iavlStore struct { tree *iavl.VersionedTree // How many old versions we hold onto. - // A value of 0 means keep all history. - numHistory int64 + // A value of 0 means keep no recent states + numRecent int64 + + // Distance between state-sync waypoint states to be stored + // See https://github.com/tendermint/tendermint/issues/828 + // A value of 1 means store every state + // A value of 0 means store no waypoints (node cannot assist in state-sync) + // By default this value should be set the same across all nodes, + // so that nodes can know the waypoints their peers store + // TODO if set to non-default, signal to peers that the node is not suitable as a state sync source + storeEvery int64 } // CONTRACT: tree should be fully loaded. -func newIAVLStore(tree *iavl.VersionedTree, numHistory int64) *iavlStore { +func newIAVLStore(tree *iavl.VersionedTree, numRecent int64, storeEvery int64) *iavlStore { st := &iavlStore{ tree: tree, - numHistory: numHistory, + numRecent: numRecent, + storeEvery: storeEvery, } return st } @@ -64,10 +76,16 @@ func (st *iavlStore) Commit() CommitID { panic(err) } - // Release an old version of history - if st.numHistory > 0 && (st.numHistory < st.tree.Version64()) { - toRelease := version - st.numHistory - st.tree.DeleteVersion(toRelease) + // Release an old version of history, if not a sync waypoint + previous := version - 1 + if st.numRecent < previous { + toRelease := previous - st.numRecent + if st.storeEvery == 0 || toRelease%st.storeEvery != 0 { + err := st.tree.DeleteVersion(toRelease) + if err != nil { + panic(err) + } + } } return CommitID{ @@ -84,6 +102,11 @@ func (st *iavlStore) LastCommitID() CommitID { } } +// VersionExists returns whether or not a given version is stored +func (st *iavlStore) VersionExists(version int64) bool { + return st.tree.VersionExists(version) +} + // Implements Store. func (st *iavlStore) GetStoreType() StoreType { return sdk.StoreTypeIAVL @@ -115,6 +138,11 @@ func (st *iavlStore) Delete(key []byte) { st.tree.Remove(key) } +// Implements KVStore +func (st *iavlStore) Prefix(prefix []byte) KVStore { + return prefixStore{st, prefix} +} + // Implements KVStore. func (st *iavlStore) Iterator(start, end []byte) Iterator { return newIAVLIterator(st.tree.Tree(), start, end, true) @@ -125,6 +153,20 @@ func (st *iavlStore) ReverseIterator(start, end []byte) Iterator { return newIAVLIterator(st.tree.Tree(), start, end, false) } +// Handle gatest the latest height, if height is 0 +func getHeight(tree *iavl.VersionedTree, req abci.RequestQuery) int64 { + height := req.Height + if height == 0 { + latest := tree.Version64() + if tree.VersionExists(latest - 1) { + height = latest - 1 + } else { + height = latest + } + } + return height +} + // Query implements ABCI interface, allows queries // // by default we will return from (latest height -1), @@ -139,32 +181,31 @@ func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) { } tree := st.tree - height := req.Height - if height == 0 { - latest := tree.Version64() - if tree.VersionExists(latest - 1) { - height = latest - 1 - } else { - height = latest - } - } - // store the height we chose in the response - res.Height = height + + // store the height we chose in the response, with 0 being changed to the + // latest height + res.Height = getHeight(tree, req) switch req.Path { case "/store", "/key": // Get by key key := req.Data // Data holds the key bytes res.Key = key if req.Prove { - value, proof, err := tree.GetVersionedWithProof(key, height) + value, proof, err := tree.GetVersionedWithProof(key, res.Height) if err != nil { res.Log = err.Error() break } res.Value = value - res.Proof = proof.Bytes() + cdc := amino.NewCodec() + p, err := cdc.MarshalBinary(proof) + if err != nil { + res.Log = err.Error() + break + } + res.Proof = p } else { - _, res.Value = tree.GetVersioned(key, height) + _, res.Value = tree.GetVersioned(key, res.Height) } case "/subspace": subspace := req.Data diff --git a/store/iavlstore_test.go b/store/iavlstore_test.go index e324758c5..f6f236dd8 100644 --- a/store/iavlstore_test.go +++ b/store/iavlstore_test.go @@ -1,21 +1,23 @@ package store import ( + "fmt" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" "github.com/tendermint/iavl" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" sdk "github.com/cosmos/cosmos-sdk/types" ) var ( cacheSize = 100 - numHistory int64 = 5 + numRecent int64 = 5 + storeEvery int64 = 3 ) var ( @@ -38,39 +40,39 @@ func newTree(t *testing.T, db dbm.DB) (*iavl.VersionedTree, CommitID) { tree.Set(key, value) } hash, ver, err := tree.SaveVersion() - assert.Nil(t, err) + require.Nil(t, err) return tree, CommitID{ver, hash} } func TestIAVLStoreGetSetHasDelete(t *testing.T) { db := dbm.NewMemDB() tree, _ := newTree(t, db) - iavlStore := newIAVLStore(tree, numHistory) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) key := "hello" exists := iavlStore.Has([]byte(key)) - assert.True(t, exists) + require.True(t, exists) value := iavlStore.Get([]byte(key)) - assert.EqualValues(t, value, treeData[key]) + require.EqualValues(t, value, treeData[key]) value2 := "notgoodbye" iavlStore.Set([]byte(key), []byte(value2)) value = iavlStore.Get([]byte(key)) - assert.EqualValues(t, value, value2) + require.EqualValues(t, value, value2) iavlStore.Delete([]byte(key)) exists = iavlStore.Has([]byte(key)) - assert.False(t, exists) + require.False(t, exists) } func TestIAVLIterator(t *testing.T) { db := dbm.NewMemDB() tree, _ := newTree(t, db) - iavlStore := newIAVLStore(tree, numHistory) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) iter := iavlStore.Iterator([]byte("aloha"), []byte("hellz")) expected := []string{"aloha", "hello"} var i int @@ -78,72 +80,72 @@ func TestIAVLIterator(t *testing.T) { for i = 0; iter.Valid(); iter.Next() { expectedKey := expected[i] key, value := iter.Key(), iter.Value() - assert.EqualValues(t, key, expectedKey) - assert.EqualValues(t, value, treeData[expectedKey]) + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, treeData[expectedKey]) i++ } - assert.Equal(t, len(expected), i) + require.Equal(t, len(expected), i) iter = iavlStore.Iterator([]byte("golang"), []byte("rocks")) expected = []string{"hello"} for i = 0; iter.Valid(); iter.Next() { expectedKey := expected[i] key, value := iter.Key(), iter.Value() - assert.EqualValues(t, key, expectedKey) - assert.EqualValues(t, value, treeData[expectedKey]) + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, treeData[expectedKey]) i++ } - assert.Equal(t, len(expected), i) + require.Equal(t, len(expected), i) iter = iavlStore.Iterator(nil, []byte("golang")) expected = []string{"aloha"} for i = 0; iter.Valid(); iter.Next() { expectedKey := expected[i] key, value := iter.Key(), iter.Value() - assert.EqualValues(t, key, expectedKey) - assert.EqualValues(t, value, treeData[expectedKey]) + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, treeData[expectedKey]) i++ } - assert.Equal(t, len(expected), i) + require.Equal(t, len(expected), i) iter = iavlStore.Iterator(nil, []byte("shalom")) expected = []string{"aloha", "hello"} for i = 0; iter.Valid(); iter.Next() { expectedKey := expected[i] key, value := iter.Key(), iter.Value() - assert.EqualValues(t, key, expectedKey) - assert.EqualValues(t, value, treeData[expectedKey]) + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, treeData[expectedKey]) i++ } - assert.Equal(t, len(expected), i) + require.Equal(t, len(expected), i) iter = iavlStore.Iterator(nil, nil) expected = []string{"aloha", "hello"} for i = 0; iter.Valid(); iter.Next() { expectedKey := expected[i] key, value := iter.Key(), iter.Value() - assert.EqualValues(t, key, expectedKey) - assert.EqualValues(t, value, treeData[expectedKey]) + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, treeData[expectedKey]) i++ } - assert.Equal(t, len(expected), i) + require.Equal(t, len(expected), i) iter = iavlStore.Iterator([]byte("golang"), nil) expected = []string{"hello"} for i = 0; iter.Valid(); iter.Next() { expectedKey := expected[i] key, value := iter.Key(), iter.Value() - assert.EqualValues(t, key, expectedKey) - assert.EqualValues(t, value, treeData[expectedKey]) + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, treeData[expectedKey]) i++ } - assert.Equal(t, len(expected), i) + require.Equal(t, len(expected), i) } func TestIAVLSubspaceIterator(t *testing.T) { db := dbm.NewMemDB() tree, _ := newTree(t, db) - iavlStore := newIAVLStore(tree, numHistory) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) iavlStore.Set([]byte("test1"), []byte("test1")) iavlStore.Set([]byte("test2"), []byte("test2")) @@ -155,54 +157,54 @@ func TestIAVLSubspaceIterator(t *testing.T) { iavlStore.Set([]byte{byte(255), byte(255), byte(1)}, []byte("test4")) iavlStore.Set([]byte{byte(255), byte(255), byte(255)}, []byte("test4")) - i := 0 + var i int iter := sdk.KVStorePrefixIterator(iavlStore, []byte("test")) expected := []string{"test1", "test2", "test3"} for i = 0; iter.Valid(); iter.Next() { expectedKey := expected[i] key, value := iter.Key(), iter.Value() - assert.EqualValues(t, key, expectedKey) - assert.EqualValues(t, value, expectedKey) + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, expectedKey) i++ } - assert.Equal(t, len(expected), i) + require.Equal(t, len(expected), i) iter = sdk.KVStorePrefixIterator(iavlStore, []byte{byte(55), byte(255), byte(255)}) expected2 := [][]byte{ - []byte{byte(55), byte(255), byte(255), byte(0)}, - []byte{byte(55), byte(255), byte(255), byte(1)}, - []byte{byte(55), byte(255), byte(255), byte(255)}, + {byte(55), byte(255), byte(255), byte(0)}, + {byte(55), byte(255), byte(255), byte(1)}, + {byte(55), byte(255), byte(255), byte(255)}, } for i = 0; iter.Valid(); iter.Next() { expectedKey := expected2[i] key, value := iter.Key(), iter.Value() - assert.EqualValues(t, key, expectedKey) - assert.EqualValues(t, value, []byte("test4")) + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, []byte("test4")) i++ } - assert.Equal(t, len(expected), i) + require.Equal(t, len(expected), i) iter = sdk.KVStorePrefixIterator(iavlStore, []byte{byte(255), byte(255)}) expected2 = [][]byte{ - []byte{byte(255), byte(255), byte(0)}, - []byte{byte(255), byte(255), byte(1)}, - []byte{byte(255), byte(255), byte(255)}, + {byte(255), byte(255), byte(0)}, + {byte(255), byte(255), byte(1)}, + {byte(255), byte(255), byte(255)}, } for i = 0; iter.Valid(); iter.Next() { expectedKey := expected2[i] key, value := iter.Key(), iter.Value() - assert.EqualValues(t, key, expectedKey) - assert.EqualValues(t, value, []byte("test4")) + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, []byte("test4")) i++ } - assert.Equal(t, len(expected), i) + require.Equal(t, len(expected), i) } func TestIAVLReverseSubspaceIterator(t *testing.T) { db := dbm.NewMemDB() tree, _ := newTree(t, db) - iavlStore := newIAVLStore(tree, numHistory) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) iavlStore.Set([]byte("test1"), []byte("test1")) iavlStore.Set([]byte("test2"), []byte("test2")) @@ -214,54 +216,133 @@ func TestIAVLReverseSubspaceIterator(t *testing.T) { iavlStore.Set([]byte{byte(255), byte(255), byte(1)}, []byte("test4")) iavlStore.Set([]byte{byte(255), byte(255), byte(255)}, []byte("test4")) - i := 0 + var i int iter := sdk.KVStoreReversePrefixIterator(iavlStore, []byte("test")) expected := []string{"test3", "test2", "test1"} for i = 0; iter.Valid(); iter.Next() { expectedKey := expected[i] key, value := iter.Key(), iter.Value() - assert.EqualValues(t, key, expectedKey) - assert.EqualValues(t, value, expectedKey) + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, expectedKey) i++ } - assert.Equal(t, len(expected), i) + require.Equal(t, len(expected), i) iter = sdk.KVStoreReversePrefixIterator(iavlStore, []byte{byte(55), byte(255), byte(255)}) expected2 := [][]byte{ - []byte{byte(55), byte(255), byte(255), byte(255)}, - []byte{byte(55), byte(255), byte(255), byte(1)}, - []byte{byte(55), byte(255), byte(255), byte(0)}, + {byte(55), byte(255), byte(255), byte(255)}, + {byte(55), byte(255), byte(255), byte(1)}, + {byte(55), byte(255), byte(255), byte(0)}, } for i = 0; iter.Valid(); iter.Next() { expectedKey := expected2[i] key, value := iter.Key(), iter.Value() - assert.EqualValues(t, key, expectedKey) - assert.EqualValues(t, value, []byte("test4")) + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, []byte("test4")) i++ } - assert.Equal(t, len(expected), i) + require.Equal(t, len(expected), i) iter = sdk.KVStoreReversePrefixIterator(iavlStore, []byte{byte(255), byte(255)}) expected2 = [][]byte{ - []byte{byte(255), byte(255), byte(255)}, - []byte{byte(255), byte(255), byte(1)}, - []byte{byte(255), byte(255), byte(0)}, + {byte(255), byte(255), byte(255)}, + {byte(255), byte(255), byte(1)}, + {byte(255), byte(255), byte(0)}, } for i = 0; iter.Valid(); iter.Next() { expectedKey := expected2[i] key, value := iter.Key(), iter.Value() - assert.EqualValues(t, key, expectedKey) - assert.EqualValues(t, value, []byte("test4")) + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, []byte("test4")) i++ } - assert.Equal(t, len(expected), i) + require.Equal(t, len(expected), i) +} + +func nextVersion(iavl *iavlStore) { + key := []byte(fmt.Sprintf("Key for tree: %d", iavl.LastCommitID().Version)) + value := []byte(fmt.Sprintf("Value for tree: %d", iavl.LastCommitID().Version)) + iavl.Set(key, value) + iavl.Commit() +} +func TestIAVLDefaultPruning(t *testing.T) { + //Expected stored / deleted version numbers for: + //numRecent = 5, storeEvery = 3 + var states = []struct { + stored []int64 + deleted []int64 + }{ + {[]int64{}, []int64{}}, + {[]int64{1}, []int64{}}, + {[]int64{1, 2}, []int64{}}, + {[]int64{1, 2, 3}, []int64{}}, + {[]int64{1, 2, 3, 4}, []int64{}}, + {[]int64{1, 2, 3, 4, 5}, []int64{}}, + {[]int64{1, 2, 3, 4, 5, 6}, []int64{}}, + {[]int64{2, 3, 4, 5, 6, 7}, []int64{1}}, + {[]int64{3, 4, 5, 6, 7, 8}, []int64{1, 2}}, + {[]int64{3, 4, 5, 6, 7, 8, 9}, []int64{1, 2}}, + {[]int64{3, 5, 6, 7, 8, 9, 10}, []int64{1, 2, 4}}, + {[]int64{3, 6, 7, 8, 9, 10, 11}, []int64{1, 2, 4, 5}}, + {[]int64{3, 6, 7, 8, 9, 10, 11, 12}, []int64{1, 2, 4, 5}}, + {[]int64{3, 6, 8, 9, 10, 11, 12, 13}, []int64{1, 2, 4, 5, 7}}, + {[]int64{3, 6, 9, 10, 11, 12, 13, 14}, []int64{1, 2, 4, 5, 7, 8}}, + {[]int64{3, 6, 9, 10, 11, 12, 13, 14, 15}, []int64{1, 2, 4, 5, 7, 8}}, + } + db := dbm.NewMemDB() + tree := iavl.NewVersionedTree(db, cacheSize) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) + for step, state := range states { + for _, ver := range state.stored { + require.True(t, iavlStore.VersionExists(ver), + "Missing version %d with latest version %d. Should save last %d and every %d", + ver, step, numRecent, storeEvery) + } + for _, ver := range state.deleted { + require.False(t, iavlStore.VersionExists(ver), + "Unpruned version %d with latest version %d. Should prune all but last %d and every %d", + ver, step, numRecent, storeEvery) + } + nextVersion(iavlStore) + } +} +func TestIAVLNoPrune(t *testing.T) { + db := dbm.NewMemDB() + tree := iavl.NewVersionedTree(db, cacheSize) + iavlStore := newIAVLStore(tree, numRecent, int64(1)) + nextVersion(iavlStore) + for i := 1; i < 100; i++ { + for j := 1; j <= i; j++ { + require.True(t, iavlStore.VersionExists(int64(j)), + "Missing version %d with latest version %d. Should be storing all versions", + j, i) + } + nextVersion(iavlStore) + } +} +func TestIAVLPruneEverything(t *testing.T) { + db := dbm.NewMemDB() + tree := iavl.NewVersionedTree(db, cacheSize) + iavlStore := newIAVLStore(tree, int64(0), int64(0)) + nextVersion(iavlStore) + for i := 1; i < 100; i++ { + for j := 1; j < i; j++ { + require.False(t, iavlStore.VersionExists(int64(j)), + "Unpruned version %d with latest version %d. Should prune all old versions", + j, i) + } + require.True(t, iavlStore.VersionExists(int64(i)), + "Missing current version on step %d, should not prune current state tree", + i) + nextVersion(iavlStore) + } } func TestIAVLStoreQuery(t *testing.T) { db := dbm.NewMemDB() tree := iavl.NewVersionedTree(db, cacheSize) - iavlStore := newIAVLStore(tree, numHistory) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) k1, v1 := []byte("key1"), []byte("val1") k2, v2 := []byte("key2"), []byte("val2") @@ -288,8 +369,8 @@ func TestIAVLStoreQuery(t *testing.T) { // query subspace before anything set qres := iavlStore.Query(querySub) - assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Equal(t, valExpSubEmpty, qres.Value) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, valExpSubEmpty, qres.Value) // set data iavlStore.Set(k1, v1) @@ -297,25 +378,25 @@ func TestIAVLStoreQuery(t *testing.T) { // set data without commit, doesn't show up qres = iavlStore.Query(query) - assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Nil(t, qres.Value) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Nil(t, qres.Value) // commit it, but still don't see on old version cid = iavlStore.Commit() qres = iavlStore.Query(query) - assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Nil(t, qres.Value) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Nil(t, qres.Value) // but yes on the new version query.Height = cid.Version qres = iavlStore.Query(query) - assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Equal(t, v1, qres.Value) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, v1, qres.Value) // and for the subspace qres = iavlStore.Query(querySub) - assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Equal(t, valExpSub1, qres.Value) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, valExpSub1, qres.Value) // modify iavlStore.Set(k1, v3) @@ -323,26 +404,26 @@ func TestIAVLStoreQuery(t *testing.T) { // query will return old values, as height is fixed qres = iavlStore.Query(query) - assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Equal(t, v1, qres.Value) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, v1, qres.Value) // update to latest in the query and we are happy query.Height = cid.Version qres = iavlStore.Query(query) - assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Equal(t, v3, qres.Value) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, v3, qres.Value) query2 := abci.RequestQuery{Path: "/key", Data: k2, Height: cid.Version} qres = iavlStore.Query(query2) - assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Equal(t, v2, qres.Value) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, v2, qres.Value) // and for the subspace qres = iavlStore.Query(querySub) - assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Equal(t, valExpSub2, qres.Value) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, valExpSub2, qres.Value) // default (height 0) will show latest -1 query0 := abci.RequestQuery{Path: "/store", Data: k1} qres = iavlStore.Query(query0) - assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Equal(t, v1, qres.Value) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, v1, qres.Value) } diff --git a/store/memiterator.go b/store/memiterator.go index a05f3443e..a72418db6 100644 --- a/store/memiterator.go +++ b/store/memiterator.go @@ -3,8 +3,8 @@ package store import ( "bytes" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" ) // Iterates over iterKVCache items. diff --git a/store/prefixstore.go b/store/prefixstore.go new file mode 100644 index 000000000..c9f124a88 --- /dev/null +++ b/store/prefixstore.go @@ -0,0 +1,112 @@ +package store + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type prefixStore struct { + store KVStore + prefix []byte +} + +// Implements Store +func (s prefixStore) GetStoreType() StoreType { + return sdk.StoreTypePrefix +} + +// Implements CacheWrap +func (s prefixStore) CacheWrap() CacheWrap { + return NewCacheKVStore(s) +} + +// Implements KVStore +func (s prefixStore) Get(key []byte) []byte { + return s.store.Get(append(s.prefix, key...)) +} + +// Implements KVStore +func (s prefixStore) Has(key []byte) bool { + return s.store.Has(append(s.prefix, key...)) +} + +// Implements KVStore +func (s prefixStore) Set(key, value []byte) { + s.store.Set(append(s.prefix, key...), value) +} + +// Implements KVStore +func (s prefixStore) Delete(key []byte) { + s.store.Delete(append(s.prefix, key...)) +} + +// Implements KVStore +func (s prefixStore) Prefix(prefix []byte) KVStore { + return prefixStore{s, prefix} +} + +// Implements KVStore +func (s prefixStore) Iterator(start, end []byte) Iterator { + if end == nil { + end = sdk.PrefixEndBytes(s.prefix) + } else { + end = append(s.prefix, end...) + } + return prefixIterator{ + prefix: s.prefix, + iter: s.store.Iterator(append(s.prefix, start...), end), + } +} + +// Implements KVStore +func (s prefixStore) ReverseIterator(start, end []byte) Iterator { + if end == nil { + end = sdk.PrefixEndBytes(s.prefix) + } else { + end = append(s.prefix, end...) + } + return prefixIterator{ + prefix: s.prefix, + iter: s.store.ReverseIterator(start, end), + } +} + +type prefixIterator struct { + prefix []byte + + iter Iterator +} + +// Implements Iterator +func (iter prefixIterator) Domain() (start []byte, end []byte) { + start, end = iter.iter.Domain() + start = start[len(iter.prefix):] + end = end[len(iter.prefix):] + return +} + +// Implements Iterator +func (iter prefixIterator) Valid() bool { + return iter.iter.Valid() +} + +// Implements Iterator +func (iter prefixIterator) Next() { + iter.iter.Next() +} + +// Implements Iterator +func (iter prefixIterator) Key() (key []byte) { + key = iter.iter.Key() + key = key[len(iter.prefix):] + return +} + +// Implements Iterator +func (iter prefixIterator) Value() []byte { + return iter.iter.Value() +} + +// Implements Iterator +func (iter prefixIterator) Close() { + iter.iter.Close() +} diff --git a/store/prefixstore_test.go b/store/prefixstore_test.go new file mode 100644 index 000000000..1961bb4bb --- /dev/null +++ b/store/prefixstore_test.go @@ -0,0 +1,110 @@ +package store + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/tendermint/iavl" + dbm "github.com/tendermint/tendermint/libs/db" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type kvpair struct { + key []byte + value []byte +} + +func setRandomKVPairs(t *testing.T, store KVStore) []kvpair { + kvps := make([]kvpair, 20) + + for i := 0; i < 20; i++ { + kvps[i].key = make([]byte, 32) + rand.Read(kvps[i].key) + kvps[i].value = make([]byte, 32) + rand.Read(kvps[i].value) + + store.Set(kvps[i].key, kvps[i].value) + } + + return kvps +} + +func testPrefixStore(t *testing.T, baseStore KVStore, prefix []byte) { + prefixStore := baseStore.Prefix(prefix) + + kvps := setRandomKVPairs(t, prefixStore) + + buf := make([]byte, 32) + for i := 0; i < 20; i++ { + rand.Read(buf) + assert.False(t, prefixStore.Has(buf)) + assert.Nil(t, prefixStore.Get(buf)) + assert.False(t, baseStore.Has(append(prefix, buf...))) + assert.Nil(t, baseStore.Get(append(prefix, buf...))) + } + + for i := 0; i < 20; i++ { + key := kvps[i].key + assert.True(t, prefixStore.Has(key)) + assert.Equal(t, kvps[i].value, prefixStore.Get(key)) + assert.True(t, baseStore.Has(append(prefix, key...))) + assert.Equal(t, kvps[i].value, baseStore.Get(append(prefix, key...))) + + prefixStore.Delete(key) + assert.False(t, prefixStore.Has(key)) + assert.Nil(t, prefixStore.Get(key)) + assert.False(t, baseStore.Has(append(prefix, buf...))) + assert.Nil(t, baseStore.Get(append(prefix, buf...))) + } + +} + +func TestIAVLStorePrefix(t *testing.T) { + db := dbm.NewMemDB() + tree := iavl.NewVersionedTree(db, cacheSize) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) + + testPrefixStore(t, iavlStore, []byte("test")) +} + +func TestCacheKVStorePrefix(t *testing.T) { + cacheStore := newCacheKVStore() + + testPrefixStore(t, cacheStore, []byte("test")) +} + +func TestGasKVStorePrefix(t *testing.T) { + meter := sdk.NewGasMeter(100000000) + mem := dbStoreAdapter{dbm.NewMemDB()} + gasStore := NewGasKVStore(meter, mem) + + testPrefixStore(t, gasStore, []byte("test")) +} + +func TestPrefixStoreIterate(t *testing.T) { + db := dbm.NewMemDB() + baseStore := dbStoreAdapter{db} + prefix := []byte("test") + prefixStore := baseStore.Prefix(prefix) + + setRandomKVPairs(t, prefixStore) + + bIter := sdk.KVStorePrefixIterator(baseStore, prefix) + pIter := sdk.KVStorePrefixIterator(prefixStore, nil) + + for bIter.Valid() && pIter.Valid() { + require.Equal(t, bIter.Key(), append(prefix, pIter.Key()...)) + require.Equal(t, bIter.Value(), pIter.Value()) + + bIter.Next() + pIter.Next() + } + + require.Equal(t, bIter.Valid(), pIter.Valid()) + bIter.Close() + pIter.Close() +} diff --git a/store/rootmultistore.go b/store/rootmultistore.go index 11cebc22e..10926e4bc 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -6,9 +6,9 @@ import ( "golang.org/x/crypto/ripemd160" - abci "github.com/tendermint/abci/types" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/merkle" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/merkle" + dbm "github.com/tendermint/tendermint/libs/db" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -89,7 +89,7 @@ func (rs *rootMultiStore) LoadVersion(ver int64) error { id := CommitID{} store, err := rs.loadCommitStoreFromParams(id, storeParams) if err != nil { - return fmt.Errorf("Failed to load rootMultiStore: %v", err) + return fmt.Errorf("failed to load rootMultiStore: %v", err) } rs.stores[key] = store } @@ -112,7 +112,7 @@ func (rs *rootMultiStore) LoadVersion(ver int64) error { storeParams := rs.storesParams[key] store, err := rs.loadCommitStoreFromParams(commitID, storeParams) if err != nil { - return fmt.Errorf("Failed to load rootMultiStore: %v", err) + return fmt.Errorf("failed to load rootMultiStore: %v", err) } newStores[key] = store } @@ -120,7 +120,7 @@ func (rs *rootMultiStore) LoadVersion(ver int64) error { // If any CommitStoreLoaders were not used, return error. for key := range rs.storesParams { if _, ok := newStores[key]; !ok { - return fmt.Errorf("Unused CommitStoreLoader: %v", key) + return fmt.Errorf("unused CommitStoreLoader: %v", key) } } @@ -343,7 +343,11 @@ func (si storeInfo) Hash() []byte { // include them via the keys. bz, _ := cdc.MarshalBinary(si.Core) // Does not error hasher := ripemd160.New() - hasher.Write(bz) + _, err := hasher.Write(bz) + if err != nil { + // TODO: Handle with #870 + panic(err) + } return hasher.Sum(nil) } @@ -399,14 +403,14 @@ func getCommitInfo(db dbm.DB, ver int64) (commitInfo, error) { cInfoKey := fmt.Sprintf(commitInfoKeyFmt, ver) cInfoBytes := db.Get([]byte(cInfoKey)) if cInfoBytes == nil { - return commitInfo{}, fmt.Errorf("Failed to get rootMultiStore: no data") + return commitInfo{}, fmt.Errorf("failed to get rootMultiStore: no data") } // Parse bytes. var cInfo commitInfo err := cdc.UnmarshalBinary(cInfoBytes, &cInfo) if err != nil { - return commitInfo{}, fmt.Errorf("Failed to get rootMultiStore: %v", err) + return commitInfo{}, fmt.Errorf("failed to get rootMultiStore: %v", err) } return cInfo, nil } diff --git a/store/rootmultistore_test.go b/store/rootmultistore_test.go index f4164f0b5..f56411462 100644 --- a/store/rootmultistore_test.go +++ b/store/rootmultistore_test.go @@ -3,10 +3,10 @@ package store import ( "testing" - "github.com/stretchr/testify/assert" - abci "github.com/tendermint/abci/types" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/merkle" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/merkle" + dbm "github.com/tendermint/tendermint/libs/db" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -20,7 +20,7 @@ func TestMultistoreCommitLoad(t *testing.T) { } store := newMultiStoreWithMounts(db) err := store.LoadLatestVersion() - assert.Nil(t, err) + require.Nil(t, err) // New store has empty last commit. commitID := CommitID{} @@ -28,11 +28,11 @@ func TestMultistoreCommitLoad(t *testing.T) { // Make sure we can get stores by name. s1 := store.getStoreByName("store1") - assert.NotNil(t, s1) + require.NotNil(t, s1) s3 := store.getStoreByName("store3") - assert.NotNil(t, s3) + require.NotNil(t, s3) s77 := store.getStoreByName("store77") - assert.Nil(t, s77) + require.Nil(t, s77) // Make a few commits and check them. nCommits := int64(3) @@ -45,7 +45,7 @@ func TestMultistoreCommitLoad(t *testing.T) { // Load the latest multistore again and check version. store = newMultiStoreWithMounts(db) err = store.LoadLatestVersion() - assert.Nil(t, err) + require.Nil(t, err) commitID = getExpectedCommitID(store, nCommits) checkStore(t, store, commitID, commitID) @@ -58,7 +58,7 @@ func TestMultistoreCommitLoad(t *testing.T) { ver := nCommits - 1 store = newMultiStoreWithMounts(db) err = store.LoadVersion(ver) - assert.Nil(t, err) + require.Nil(t, err) commitID = getExpectedCommitID(store, ver) checkStore(t, store, commitID, commitID) @@ -71,29 +71,29 @@ func TestMultistoreCommitLoad(t *testing.T) { // LatestVersion store = newMultiStoreWithMounts(db) err = store.LoadLatestVersion() - assert.Nil(t, err) + require.Nil(t, err) commitID = getExpectedCommitID(store, ver+1) checkStore(t, store, commitID, commitID) } func TestParsePath(t *testing.T) { _, _, err := parsePath("foo") - assert.Error(t, err) + require.Error(t, err) store, subpath, err := parsePath("/foo") - assert.NoError(t, err) - assert.Equal(t, store, "foo") - assert.Equal(t, subpath, "") + require.NoError(t, err) + require.Equal(t, store, "foo") + require.Equal(t, subpath, "") store, subpath, err = parsePath("/fizz/bang/baz") - assert.NoError(t, err) - assert.Equal(t, store, "fizz") - assert.Equal(t, subpath, "/bang/baz") + require.NoError(t, err) + require.Equal(t, store, "fizz") + require.Equal(t, subpath, "/bang/baz") substore, subsubpath, err := parsePath(subpath) - assert.NoError(t, err) - assert.Equal(t, substore, "bang") - assert.Equal(t, subsubpath, "/baz") + require.NoError(t, err) + require.Equal(t, substore, "bang") + require.Equal(t, subsubpath, "/baz") } @@ -101,7 +101,7 @@ func TestMultiStoreQuery(t *testing.T) { db := dbm.NewMemDB() multi := newMultiStoreWithMounts(db) err := multi.LoadLatestVersion() - assert.Nil(t, err) + require.Nil(t, err) k, v := []byte("wind"), []byte("blows") k2, v2 := []byte("water"), []byte("flows") @@ -111,7 +111,7 @@ func TestMultiStoreQuery(t *testing.T) { // Make sure we can get by name. garbage := multi.getStoreByName("bad-name") - assert.Nil(t, garbage) + require.Nil(t, garbage) // Set and commit data in one store. store1 := multi.getStoreByName("store1").(KVStore) @@ -128,35 +128,35 @@ func TestMultiStoreQuery(t *testing.T) { // Test bad path. query := abci.RequestQuery{Path: "/key", Data: k, Height: ver} qres := multi.Query(query) - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), sdk.ABCICodeType(qres.Code)) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), sdk.ABCICodeType(qres.Code)) query.Path = "h897fy32890rf63296r92" qres = multi.Query(query) - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), sdk.ABCICodeType(qres.Code)) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), sdk.ABCICodeType(qres.Code)) // Test invalid store name. query.Path = "/garbage/key" qres = multi.Query(query) - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), sdk.ABCICodeType(qres.Code)) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), sdk.ABCICodeType(qres.Code)) // Test valid query with data. query.Path = "/store1/key" qres = multi.Query(query) - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), sdk.ABCICodeType(qres.Code)) - assert.Equal(t, v, qres.Value) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), sdk.ABCICodeType(qres.Code)) + require.Equal(t, v, qres.Value) // Test valid but empty query. query.Path = "/store2/key" query.Prove = true qres = multi.Query(query) - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), sdk.ABCICodeType(qres.Code)) - assert.Nil(t, qres.Value) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), sdk.ABCICodeType(qres.Code)) + require.Nil(t, qres.Value) // Test store2 data. query.Data = k2 qres = multi.Query(query) - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), sdk.ABCICodeType(qres.Code)) - assert.Equal(t, v2, qres.Value) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), sdk.ABCICodeType(qres.Code)) + require.Equal(t, v2, qres.Value) } //----------------------------------------------------------------------- @@ -174,8 +174,8 @@ func newMultiStoreWithMounts(db dbm.DB) *rootMultiStore { } func checkStore(t *testing.T, store *rootMultiStore, expect, got CommitID) { - assert.Equal(t, expect, got) - assert.Equal(t, expect, store.LastCommitID()) + require.Equal(t, expect, got) + require.Equal(t, expect, store.LastCommitID()) } diff --git a/tests/check_basecli.sh b/tests/check_basecli.sh index ec2c458cb..ffcf39a51 100755 --- a/tests/check_basecli.sh +++ b/tests/check_basecli.sh @@ -36,7 +36,7 @@ echo; echo "Empty account:" $TO ./build/basecli account $TO # send some money -TX=`echo $PASS | ./build/basecli send --to=$TO --amount=1000mycoin --name=demo --seq=0` +TX=`echo $PASS | ./build/basecli send --to=$TO --amount=1000mycoin --from=demo --seq=0` echo; echo "SendTx"; echo $TX HASH=`echo $TX | cut -d' ' -f6` echo "tx hash:" $HASH diff --git a/tests/gobash.go b/tests/gobash.go index f46bad3c1..71db2d2dd 100644 --- a/tests/gobash.go +++ b/tests/gobash.go @@ -1,11 +1,13 @@ package tests import ( + "fmt" + "io/ioutil" "strings" "testing" "github.com/stretchr/testify/require" - cmn "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tendermint/libs/common" ) // Execute the command, return stdout, logging stdout/err to t. @@ -21,13 +23,15 @@ func ExecuteT(t *testing.T, cmd string) (out string) { } // Start process and wait. - proc, err := StartProcess("", name, args, nil, nil) + proc, err := StartProcess("", name, args) require.NoError(t, err) - proc.Wait() // Get the output. - outbz := proc.StdoutBuffer.Bytes() - errbz := proc.StderrBuffer.Bytes() + outbz, errbz, err := proc.ReadAll() + if err != nil { + fmt.Println("Err on proc.ReadAll()", err, args) + } + proc.Wait() // Log output. if len(outbz) > 0 { @@ -56,36 +60,38 @@ func GoExecuteT(t *testing.T, cmd string) (proc *Process) { } // Start process. - proc, err := StartProcess("", name, args, nil, nil) + proc, err := StartProcess("", name, args) require.NoError(t, err) - - // Run goroutines to log stdout. - go func() { - buf := make([]byte, 10240) // TODO Document the effects. - for { - n, err := proc.StdoutBuffer.Read(buf) - if err != nil { - return - } - if n > 0 { - t.Log("Stdout:", cmn.Green(string(buf[:n]))) - } - } - }() - - // Run goroutines to log stderr. - go func() { - buf := make([]byte, 10240) // TODO Document the effects. - for { - n, err := proc.StderrBuffer.Read(buf) - if err != nil { - return - } - if n > 0 { - t.Log("Stderr:", cmn.Red(string(buf[:n]))) - } - } - }() - + return proc +} + +// Same as GoExecuteT but spawns a go routine to ReadAll off stdout. +func GoExecuteTWithStdout(t *testing.T, cmd string) (proc *Process) { + t.Log("Running", cmn.Cyan(cmd)) + + // Split cmd to name and args. + split := strings.Split(cmd, " ") + require.True(t, len(split) > 0, "no command provided") + name, args := split[0], []string(nil) + if len(split) > 1 { + args = split[1:] + } + + // Start process. + proc, err := CreateProcess("", name, args) + require.NoError(t, err) + + // Without this, the test halts ?! + go func() { + _, err := ioutil.ReadAll(proc.StdoutPipe) + if err != nil { + fmt.Println("-------------ERR-----------------------", err) + return + } + }() + + err = proc.Cmd.Start() + require.NoError(t, err) + proc.Pid = proc.Cmd.Process.Pid return proc } diff --git a/tests/process.go b/tests/process.go index d2459a0b5..08f975cf8 100644 --- a/tests/process.go +++ b/tests/process.go @@ -1,8 +1,8 @@ package tests import ( - "bytes" "io" + "io/ioutil" "os" "os/exec" "time" @@ -10,26 +10,37 @@ import ( // execution process type Process struct { - ExecPath string - Args []string - Pid int - StartTime time.Time - EndTime time.Time - Cmd *exec.Cmd `json:"-"` - ExitState *os.ProcessState `json:"-"` - WaitCh chan struct{} `json:"-"` - StdinPipe io.WriteCloser `json:"-"` - StdoutBuffer *bytes.Buffer `json:"-"` - StderrBuffer *bytes.Buffer `json:"-"` + ExecPath string + Args []string + Pid int + StartTime time.Time + EndTime time.Time + Cmd *exec.Cmd `json:"-"` + ExitState *os.ProcessState `json:"-"` + StdinPipe io.WriteCloser `json:"-"` + StdoutPipe io.ReadCloser `json:"-"` + StderrPipe io.ReadCloser `json:"-"` } // dir: The working directory. If "", os.Getwd() is used. // name: Command name // args: Args to command. (should not include name) -// outFile, errFile: If not nil, will use, otherwise new Buffers will be -// allocated. Either way, Process.Cmd.StdoutPipe and Process.Cmd.StderrPipe will be nil -// respectively. -func StartProcess(dir string, name string, args []string, outFile, errFile io.WriteCloser) (*Process, error) { +func StartProcess(dir string, name string, args []string) (*Process, error) { + proc, err := CreateProcess(dir, name, args) + if err != nil { + return nil, err + } + // cmd start + if err := proc.Cmd.Start(); err != nil { + return nil, err + } + proc.Pid = proc.Cmd.Process.Pid + + return proc, nil +} + +// Same as StartProcess but doesn't start the process +func CreateProcess(dir string, name string, args []string) (*Process, error) { var cmd = exec.Command(name, args...) // is not yet started. // cmd dir if dir == "" { @@ -46,52 +57,27 @@ func StartProcess(dir string, name string, args []string, outFile, errFile io.Wr if err != nil { return nil, err } - // cmd stdout, stderr - var outBuffer, errBuffer *bytes.Buffer - if outFile != nil { - cmd.Stdout = outFile - } else { - outBuffer = bytes.NewBuffer(nil) - cmd.Stdout = outBuffer - } - if errFile != nil { - cmd.Stderr = errFile - } else { - errBuffer = bytes.NewBuffer(nil) - cmd.Stderr = errBuffer - } - // cmd start - if err := cmd.Start(); err != nil { + + stdout, err := cmd.StdoutPipe() + if err != nil { return nil, err } + + stderr, err := cmd.StderrPipe() + if err != nil { + return nil, err + } + proc := &Process{ - ExecPath: name, - Args: args, - Pid: cmd.Process.Pid, - StartTime: time.Now(), - Cmd: cmd, - ExitState: nil, - WaitCh: make(chan struct{}), - StdinPipe: stdin, + ExecPath: name, + Args: args, + StartTime: time.Now(), + Cmd: cmd, + ExitState: nil, + StdinPipe: stdin, + StdoutPipe: stdout, + StderrPipe: stderr, } - if outBuffer != nil { - proc.StdoutBuffer = outBuffer - } - if errBuffer != nil { - proc.StderrBuffer = errBuffer - } - go func() { - err := proc.Cmd.Wait() - if err != nil { - // fmt.Printf("Process exit: %v\n", err) - if exitError, ok := err.(*exec.ExitError); ok { - proc.ExitState = exitError.ProcessState - } - } - proc.ExitState = proc.Cmd.ProcessState - proc.EndTime = time.Now() // TODO make this goroutine-safe - close(proc.WaitCh) - }() return proc, nil } @@ -106,5 +92,26 @@ func (proc *Process) Stop(kill bool) error { // wait for the process func (proc *Process) Wait() { - <-proc.WaitCh + err := proc.Cmd.Wait() + if err != nil { + // fmt.Printf("Process exit: %v\n", err) + if exitError, ok := err.(*exec.ExitError); ok { + proc.ExitState = exitError.ProcessState + } + } + proc.ExitState = proc.Cmd.ProcessState + proc.EndTime = time.Now() // TODO make this goroutine-safe +} + +// ReadAll calls ioutil.ReadAll on the StdoutPipe and StderrPipe. +func (proc *Process) ReadAll() (stdout []byte, stderr []byte, err error) { + outbz, err := ioutil.ReadAll(proc.StdoutPipe) + if err != nil { + return nil, nil, err + } + errbz, err := ioutil.ReadAll(proc.StderrPipe) + if err != nil { + return nil, nil, err + } + return outbz, errbz, nil } diff --git a/tests/tests.go b/tests/tests.go deleted file mode 100644 index b4435659b..000000000 --- a/tests/tests.go +++ /dev/null @@ -1,321 +0,0 @@ -package tests - -import ( - "bufio" - "bytes" - "fmt" - "os" - "os/exec" - "path" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/cosmos/cosmos-sdk/server" - "github.com/stretchr/testify/require" -) - -// Tests assume the `basecoind` and `basecli` binaries -// have been built and are located in `./build` - -// TODO remove test dirs if tests are successful - -//nolint -var ( - basecoind = "build/basecoind" - basecli = "build/basecli" - - basecoindDir = "./tmp-basecoind-tests" - basecliDir = "./tmp-basecli-tests" - - ACCOUNTS = []string{"alice", "bob", "charlie", "igor"} - alice = ACCOUNTS[0] - bob = ACCOUNTS[1] - charlie = ACCOUNTS[2] - igor = ACCOUNTS[3] -) - -func gopath() string { - return filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "cosmos", "cosmos-sdk") -} - -func whereIsBasecoind() string { - return filepath.Join(gopath(), basecoind) -} - -func whereIsBasecli() string { - return filepath.Join(gopath(), basecli) -} - -// Init Basecoin Test -func TestInitBasecoin(t *testing.T, home string) string { - var err error - - password := "some-random-password" - - initBasecoind := exec.Command(whereIsBasecoind(), "init", "--home", home) - cmdWriter, err := initBasecoind.StdinPipe() - require.Nil(t, err) - - buf := new(bytes.Buffer) - initBasecoind.Stdout = buf - - if err = initBasecoind.Start(); err != nil { - t.Error(err) - } - - _, err = cmdWriter.Write([]byte(password)) - require.Nil(t, err) - cmdWriter.Close() - - if err = initBasecoind.Wait(); err != nil { - t.Error(err) - } - - // get seed from initialization - theOutput := strings.Split(buf.String(), "\n") - var seedLine int - for _seedLine, o := range theOutput { - if strings.HasPrefix(string(o), "Secret phrase") { - seedLine = _seedLine + 1 - break - } - } - - seed := string(theOutput[seedLine]) - - // enable indexing - err = appendToFile(path.Join(home, "config", "config.toml"), "\n\n[tx_indexing]\nindex_all_tags = true\n") - require.Nil(t, err) - - return seed -} - -func appendToFile(path string, text string) error { - f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0600) - if err != nil { - return err - } - - defer f.Close() - - if _, err = f.WriteString(text); err != nil { - return err - } - - return nil -} - -func makeKeys() error { - for _, acc := range ACCOUNTS { - makeKeys := exec.Command(whereIsBasecli(), "keys", "add", acc, "--home", basecliDir) - cmdWriter, err := makeKeys.StdinPipe() - if err != nil { - return err - } - - makeKeys.Stdout = os.Stdout - if err := makeKeys.Start(); err != nil { - return err - } - cmdWriter.Write([]byte("1234567890")) - if err != nil { - return err - } - cmdWriter.Close() - - if err := makeKeys.Wait(); err != nil { - return err - } - } - - return nil -} - -func _TestSendCoins(t *testing.T) { - if err := StartServer(); err != nil { - t.Error(err) - } - - // send some coins - // [zr] where dafuq do I get a FROM (oh, use --name) - - sendTo := fmt.Sprintf("--to=%s", bob) - sendFrom := fmt.Sprintf("--from=%s", alice) - - cmdOut, err := exec.Command(whereIsBasecli(), "send", sendTo, "--amount=1000mycoin", sendFrom, "--seq=0").Output() - if err != nil { - t.Error(err) - } - - fmt.Printf("sent: %s", string(cmdOut)) - -} - -// expects TestInitBaseCoin to have been run -func StartServer() error { - // straight outta https://nathanleclaire.com/blog/2014/12/29/shelled-out-commands-in-golang/ - cmdName := whereIsBasecoind() - cmdArgs := []string{"start", "--home", basecoindDir} - - cmd := exec.Command(cmdName, cmdArgs...) - cmdReader, err := cmd.StdoutPipe() - if err != nil { - return err - } - - scanner := bufio.NewScanner(cmdReader) - go func() { - for scanner.Scan() { - fmt.Printf("running [basecoind start] %s\n", scanner.Text()) - } - }() - - err = cmd.Start() - if err != nil { - return err - } - - err = cmd.Wait() - if err != nil { - return err - } - - time.Sleep(5 * time.Second) - - return nil - - // TODO return cmd.Process so that we can later do something like: - // cmd.Process.Kill() - // see: https://stackoverflow.com/questions/11886531/terminating-a-process-started-with-os-exec-in-golang -} - -// Init Basecoin Test -func InitServerForTest(t *testing.T) { - Clean() - - var err error - - password := "some-random-password" - usePassword := exec.Command("echo", password) - - initBasecoind := exec.Command(whereIsBasecoind(), "init", "--home", basecoindDir) - - initBasecoind.Stdin, err = usePassword.StdoutPipe() - require.Nil(t, err) - - initBasecoind.Stdout = os.Stdout - - err = initBasecoind.Start() - require.Nil(t, err) - err = usePassword.Run() - require.Nil(t, err) - err = initBasecoind.Wait() - require.Nil(t, err) - - err = makeKeys() - require.Nil(t, err) -} - -// expects TestInitBaseCoin to have been run -func StartNodeServerForTest(t *testing.T, home string) *exec.Cmd { - cmdName := whereIsBasecoind() - cmdArgs := []string{"start", "--home", home} - cmd := exec.Command(cmdName, cmdArgs...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Start() - require.Nil(t, err) - - // FIXME: if there is a nondeterministic node start failure, - // we should probably make this read the logs to wait for RPC - time.Sleep(time.Second * 2) - - return cmd -} - -// expects TestInitBaseCoin to have been run -func StartLCDServerForTest(t *testing.T, home, chainID string) (cmd *exec.Cmd, port string) { - cmdName := whereIsBasecli() - var err error - _, port, err = server.FreeTCPAddr() - require.NoError(t, err) - cmdArgs := []string{ - "rest-server", - "--home", - home, - "--bind", - fmt.Sprintf("localhost:%s", port), - "--chain-id", - chainID, - } - cmd = exec.Command(cmdName, cmdArgs...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err = cmd.Start() - require.Nil(t, err) - time.Sleep(time.Second * 2) // TODO: LOL - return cmd, port -} - -// clean the directories -func Clean() { - // ignore errors b/c the dirs may not yet exist - err := os.Remove(basecoindDir) - panic(err) - err = os.Remove(basecliDir) - panic(err) -} - -/* - - chainID = "staking_test" - testDir = "./tmp_tests" -) - -func runTests() { - - if err := os.Mkdir(testDir, 0666); err != nil { - panic(err) - } - defer os.Remove(testDir) - - // make some keys - - //if err := makeKeys(); err != nil { - // panic(err) - //} - - if err := initServer(); err != nil { - fmt.Printf("Err: %v", err) - panic(err) - } - -} - -func initServer() error { - serveDir := filepath.Join(testDir, "server") - //serverLog := filepath.Join(testDir, "gaia-node.log") - - // get RICH - keyOut, err := exec.Command(GAIA, CLIENT_EXE, "keys", "get", "alice").Output() - if err != nil { - fmt.Println("one") - return err - } - key := strings.Split(string(keyOut), "\t") - fmt.Printf("wit:%s", key[2]) - - outByte, err := exec.Command(GAIA, SERVER_EXE, "init", "--static", fmt.Sprintf("--chain-id=%s", chainID), fmt.Sprintf("--home=%s", serveDir), key[2]).Output() - if err != nil { - fmt.Println("teo") - fmt.Printf("Error: %v", err) - - return err - } - fmt.Sprintf("OUT: %s", string(outByte)) - return nil -} - -*/ diff --git a/tests/util.go b/tests/util.go index 387ccb769..1138bc95e 100644 --- a/tests/util.go +++ b/tests/util.go @@ -15,13 +15,26 @@ import ( // Wait for the next tendermint block from the Tendermint RPC // on localhost func WaitForNextHeightTM(port string) { + WaitForNextNBlocksTM(1, port) +} + +// Wait for N tendermint blocks to pass using the Tendermint RPC +// on localhost +func WaitForNextNBlocksTM(n int64, port string) { + + // get the latest block and wait for n more url := fmt.Sprintf("http://localhost:%v", port) cl := tmclient.NewHTTP(url, "/websocket") resBlock, err := cl.Block(nil) - if err != nil { - panic(err) + var height int64 + if err != nil || resBlock.Block == nil { + // wait for the first block to exist + WaitForHeightTM(1, port) + height = 1 + n + } else { + height = resBlock.Block.Height + n } - waitForHeightTM(resBlock.Block.Height+1, url) + waitForHeightTM(height, url) } // Wait for the given height from the Tendermint RPC @@ -51,7 +64,6 @@ func waitForHeightTM(height int64, url string) { if resBlock.Block != nil && resBlock.Block.Height >= height { - fmt.Println("HEIGHT", resBlock.Block.Height) return } time.Sleep(time.Millisecond * 100) @@ -64,18 +76,22 @@ func WaitForHeight(height int64, port string) { waitForHeight(height, url) } +// Whether or not an HTTP status code was "successful" +func StatusOK(statusCode int) bool { + switch statusCode { + case http.StatusOK: + case http.StatusCreated: + case http.StatusNoContent: + return true + } + return false +} + func waitForHeight(height int64, url string) { + var res *http.Response + var err error for { - // get url, try a few times - var res *http.Response - var err error - for i := 0; i < 5; i++ { - res, err = http.Get(url) - if err == nil { - break - } - time.Sleep(time.Millisecond * 200) - } + res, err = http.Get(url) if err != nil { panic(err) } @@ -84,10 +100,13 @@ func waitForHeight(height int64, url string) { if err != nil { panic(err) } - res.Body.Close() + err = res.Body.Close() + if err != nil { + panic(err) + } var resultBlock ctypes.ResultBlock - err = cdc.UnmarshalJSON([]byte(body), &resultBlock) + err = cdc.UnmarshalJSON(body, &resultBlock) if err != nil { fmt.Println("RES", res) fmt.Println("BODY", string(body)) @@ -102,30 +121,49 @@ func waitForHeight(height int64, url string) { } } -// wait for tendermint to start -func WaitForStart(port string) { +// wait for tendermint to start by querying the LCD +func WaitForLCDStart(port string) { + url := fmt.Sprintf("http://localhost:%v/blocks/latest", port) + WaitForStart(url) +} + +// wait for tendermint to start by querying tendermint +func WaitForTMStart(port string) { + url := fmt.Sprintf("http://localhost:%v/block", port) + WaitForStart(url) +} + +// WaitForStart waits for the node to start by pinging the url +// every 100ms for 5s until it returns 200. If it takes longer than 5s, +// it panics. +func WaitForStart(url string) { var err error - for i := 0; i < 5; i++ { - time.Sleep(time.Second) - url := fmt.Sprintf("http://localhost:%v/blocks/latest", port) + // ping the status endpoint a few times a second + // for a few seconds until we get a good response. + // otherwise something probably went wrong + for i := 0; i < 50; i++ { + time.Sleep(time.Millisecond * 100) - // get url, try a few times var res *http.Response res, err = http.Get(url) - if err == nil || res == nil { + if err != nil || res == nil { continue } + // body, _ := ioutil.ReadAll(res.Body) + // fmt.Println("BODY", string(body)) + err = res.Body.Close() + if err != nil { + panic(err) + } - // waiting for server to start ... - if res.StatusCode != http.StatusOK { - res.Body.Close() + if res.StatusCode == http.StatusOK { + // good! return } } - if err != nil { - panic(err) - } + // still haven't started up?! panic! + panic(err) } // TODO: these functions just print to Stdout. diff --git a/tools/Gopkg.lock b/tools/Gopkg.lock deleted file mode 100644 index 92fe1e260..000000000 --- a/tools/Gopkg.lock +++ /dev/null @@ -1,56 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - name = "github.com/alecthomas/gometalinter" - packages = ["."] - revision = "46cc1ea3778b247666c2949669a3333c532fa9c6" - version = "v2.0.5" - -[[projects]] - branch = "master" - name = "github.com/alecthomas/units" - packages = ["."] - revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a" - -[[projects]] - branch = "master" - name = "github.com/google/shlex" - packages = ["."] - revision = "6f45313302b9c56850fc17f99e40caebce98c716" - -[[projects]] - name = "github.com/nicksnyder/go-i18n" - packages = [ - "i18n", - "i18n/bundle", - "i18n/language", - "i18n/translation" - ] - revision = "0dc1626d56435e9d605a29875701721c54bc9bbd" - version = "v1.10.0" - -[[projects]] - name = "github.com/pelletier/go-toml" - packages = ["."] - revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8" - version = "v1.1.0" - -[[projects]] - branch = "v3-unstable" - name = "gopkg.in/alecthomas/kingpin.v3-unstable" - packages = ["."] - revision = "b8d601de6db1f3b56a99ffe9051eb708574bc1cd" - -[[projects]] - name = "gopkg.in/yaml.v2" - packages = ["."] - revision = "7f97868eec74b32b0982dd158a51a446d1da7eb5" - version = "v2.1.1" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - inputs-digest = "bb8cda576a5c4dda202435f43a46ae50a254181a4bf22c6af6f4d3d03079d509" - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/tools/Gopkg.toml b/tools/Gopkg.toml deleted file mode 100644 index 97fb62975..000000000 --- a/tools/Gopkg.toml +++ /dev/null @@ -1,34 +0,0 @@ -# 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" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - -[[constraint]] - name = "github.com/alecthomas/gometalinter" - version = "2.0.5" - -[prune] - go-tests = true - unused-packages = true - diff --git a/tools/Makefile b/tools/Makefile index ee1c14a84..d58f52d1b 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -1,11 +1,28 @@ -all: install +all: get_tools ######################################## ### DEP DEP = github.com/golang/dep/cmd/dep +GOLINT = github.com/tendermint/lint/golint +GOMETALINTER = gopkg.in/alecthomas/gometalinter.v2 +UNCONVERT = github.com/mdempsky/unconvert +INEFFASSIGN = github.com/gordonklaus/ineffassign +MISSPELL = github.com/client9/misspell/cmd/misspell +ERRCHECK = github.com/kisielk/errcheck +UNPARAM = mvdan.cc/unparam +GOCYCLO = github.com/alecthomas/gocyclo + DEP_CHECK := $(shell command -v dep 2> /dev/null) +GOLINT_CHECK := $(shell command -v golint 2> /dev/null) +GOMETALINTER_CHECK := $(shell command -v gometalinter.v2 2> /dev/null) +UNCONVERT_CHECK := $(shell command -v unconvert 2> /dev/null) +INEFFASSIGN_CHECK := $(shell command -v ineffassign 2> /dev/null) +MISSPELL_CHECK := $(shell command -v misspell 2> /dev/null) +ERRCHECK_CHECK := $(shell command -v errcheck 2> /dev/null) +UNPARAM_CHECK := $(shell command -v unparam 2> /dev/null) +GOCYCLO_CHECK := $(shell command -v gocyclo 2> /dev/null) check_tools: ifndef DEP_CHECK @@ -13,50 +30,124 @@ ifndef DEP_CHECK else @echo "Found dep in path." endif +ifndef GOLINT_CHECK + @echo "No golint in path. Install with 'make get_tools'." +else + @echo "Found golint in path." +endif +ifndef GOMETALINTER_CHECK + @echo "No gometalinter in path. Install with 'make get_tools'." +else + @echo "Found gometalinter in path." +endif +ifndef UNCONVERT_CHECK + @echo "No unconvert in path. Install with 'make get_tools'." +else + @echo "Found unconvert in path." +endif +ifndef INEFFASSIGN_CHECK + @echo "No ineffassign in path. Install with 'make get_tools'." +else + @echo "Found ineffassign in path." +endif +ifndef MISSPELL_CHECK + @echo "No misspell in path. Install with 'make get_tools'." +else + @echo "Found misspell in path." +endif +ifndef ERRCHECK_CHECK + @echo "No errcheck in path. Install with 'make get_tools'." +else + @echo "Found errcheck in path." +endif +ifndef UNPARAM_CHECK + @echo "No unparam in path. Install with 'make get_tools'." +else + @echo "Found unparam in path." +endif +ifndef GOCYCLO_CHECK + @echo "No gocyclo in path. Install with 'make get_tools'." +else + @echo "Found gocyclo in path." +endif get_tools: ifdef DEP_CHECK @echo "Dep is already installed. Run 'make update_tools' to update." else - @echo "$(ansi_grn)Installing dep$(ansi_end)" + @echo "Installing dep" go get -v $(DEP) endif +ifdef GOLINT_CHECK + @echo "Golint is already installed. Run 'make update_tools' to update." +else + @echo "Installing golint" + go get -v $(GOLINT) +endif +ifdef GOMETALINTER_CHECK + @echo "Gometalinter.v2 is already installed. Run 'make update_tools' to update." +else + @echo "Installing gometalinter.v2" + go get -v $(GOMETALINTER) +endif +ifdef UNCONVERT_CHECK + @echo "Unconvert is already installed. Run 'make update_tools' to update." +else + @echo "Installing unconvert" + go get -v $(UNCONVERT) +endif +ifdef INEFFASSIGN_CHECK + @echo "Ineffassign is already installed. Run 'make update_tools' to update." +else + @echo "Installing ineffassign" + go get -v $(INEFFASSIGN) +endif +ifdef MISSPELL_CHECK + @echo "misspell is already installed. Run 'make update_tools' to update." +else + @echo "Installing misspell" + go get -v $(MISSPELL) +endif +ifdef ERRCHECK_CHECK + @echo "errcheck is already installed. Run 'make update_tools' to update." +else + @echo "Installing errcheck" + go get -v $(ERRCHECK) +endif +ifdef UNPARAM_CHECK + @echo "unparam is already installed. Run 'make update_tools' to update." +else + @echo "Installing unparam" + go get -v $(UNPARAM) +endif +ifdef GOYCLO_CHECK + @echo "goyclo is already installed. Run 'make update_tools' to update." +else + @echo "Installing goyclo" + go get -v $(GOCYCLO) +endif update_tools: - @echo "$(ansi_grn)Updating dep$(ansi_end)" + @echo "Updating dep" go get -u -v $(DEP) - - -######################################## -### Install tools - - -get_vendor_deps: check_tools - @rm -rf vendor/ - @echo "--> Running dep ensure" - @dep ensure -v - -install: get_vendor_deps - @echo "$(ansi_grn)Installing tools$(ansi_end)" - @echo "$(ansi_yel)Install go-vendorinstall$(ansi_end)" - go build -o bin/go-vendorinstall go-vendorinstall/*.go - - @echo "$(ansi_yel)Install gometalinter.v2$(ansi_end)" - GOBIN="$(CURDIR)/bin" ./bin/go-vendorinstall github.com/alecthomas/gometalinter - - @echo "$(ansi_grn)Done installing tools$(ansi_end)" - - -######################################## -# ANSI colors - -ansi_red=\033[0;31m -ansi_grn=\033[0;32m -ansi_yel=\033[0;33m -ansi_end=\033[0m - + @echo "Updating tendermint/golint" + go get -u -v $(GOLINT) + @echo "Updating gometalinter.v2" + go get -u -v $(GOMETALINTER) + @echo "Updating unconvert" + go get -u -v $(UNCONVERT) + @echo "Updating ineffassign" + go get -u -v $(INEFFASSIGN) + @echo "Updating misspell" + go get -u -v $(MISSPELL) + @echo "Updating errcheck" + go get -u -v $(ERRCHECK) + @echo "Updating unparam" + go get -u -v $(UNPARAM) + @echo "Updating goyclo" + go get -u -v $(GOCYCLO) # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: check_tools install_tools update_tools get_vendor_deps install +.PHONY: check_tools get_tools update_tools diff --git a/tools/go-vendorinstall/main.go b/tools/go-vendorinstall/main.go deleted file mode 100644 index c42e678f2..000000000 --- a/tools/go-vendorinstall/main.go +++ /dev/null @@ -1,129 +0,0 @@ -// https://raw.githubusercontent.com/roboll/go-vendorinstall/a3e9f0a5d5861b3bb16b93200b2c359c9846b3c5/main.go - -package main - -import ( - "errors" - "flag" - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strings" -) - -var ( - source = flag.String("source", "vendor", "source directory") - target = flag.String("target", "", "target directory (defaults to $GOBIN, if not set $GOPATH/bin)") - commands = flag.String("commands", "", "comma separated list of commands to execute after go install in temporary environment") - quiet = flag.Bool("quiet", false, "disable output") -) - -func main() { - flag.Parse() - - packages := flag.Args() - if len(packages) < 1 { - fail(errors.New("no packages: specify a package")) - } - - gopath, err := ioutil.TempDir("", "go-vendorinstall-gopath") - if err != nil { - fail(err) - } - print(fmt.Sprintf("gopath: %s", gopath)) - defer func() { - if err := os.RemoveAll(gopath); err != nil { - fail(err) - } - }() - - if len(*target) == 0 { - if gobin := os.Getenv("GOBIN"); len(gobin) > 0 { - target = &gobin - } else { - bin := fmt.Sprintf("%s/bin", os.Getenv("GOPATH")) - target = &bin - } - } - - gobin, err := filepath.Abs(*target) - if err != nil { - fail(err) - } - print(fmt.Sprintf("gobin: %s", gobin)) - - if err := link(gopath, *source); err != nil { - fail(err) - } - - oldpath := os.Getenv("PATH") - path := fmt.Sprintf("%s%s%s", gobin, string(os.PathListSeparator), os.Getenv("PATH")) - os.Setenv("PATH", fmt.Sprintf("%s%s%s", gobin, string(os.PathListSeparator), os.Getenv("PATH"))) - defer os.Setenv("PATH", oldpath) - - env := []string{fmt.Sprintf("PATH=%s", path), fmt.Sprintf("GOPATH=%s", gopath), fmt.Sprintf("GOBIN=%s", gobin)} - args := append([]string{"install"}, packages...) - if out, err := doexec("go", gopath, args, env); err != nil { - print(string(out)) - fail(err) - } - - if len(*commands) > 0 { - for _, cmd := range strings.Split(*commands, ",") { - split := strings.Split(cmd, " ") - if out, err := doexec(split[0], gopath, split[1:], env); err != nil { - print(string(out)) - fail(err) - } - } - } -} - -func print(msg string) { - if !*quiet { - fmt.Println(msg) - } -} - -func fail(err error) { - fmt.Printf("error: %s", err.Error()) - os.Exit(1) -} - -func link(gopath, source string) error { - srcdir, err := filepath.Abs(source) - if err != nil { - return err - } - - linkto := filepath.Join(gopath, "src") - if err := os.MkdirAll(linkto, 0777); err != nil { - return err - } - - files, err := ioutil.ReadDir(srcdir) - if err != nil { - return err - } - - for _, file := range files { - real := filepath.Join(srcdir, file.Name()) - link := filepath.Join(linkto, file.Name()) - if err := os.Symlink(real, link); err != nil { - return err - } - } - - return nil -} - -func doexec(bin, dir string, args []string, env []string) ([]byte, error) { - print(fmt.Sprintf("%s %s", bin, strings.Join(args, " "))) - cmd := exec.Command(bin, args...) - cmd.Env = env - cmd.Dir = dir - - return cmd.CombinedOutput() -} diff --git a/tools/gometalinter.json b/tools/gometalinter.json new file mode 100644 index 000000000..124e28c14 --- /dev/null +++ b/tools/gometalinter.json @@ -0,0 +1,9 @@ +{ + "Linters": { + "vet": "go tool vet -composites=false :PATH:LINE:MESSAGE" + }, + "Enable": ["golint", "vet", "ineffassign", "unparam", "unconvert", "misspell", "gocyclo"], + "Deadline": "500s", + "Vendor": true, + "Cyclo": 11 +} \ No newline at end of file diff --git a/tools/main.go b/tools/main.go deleted file mode 100644 index 104268010..000000000 --- a/tools/main.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -// Include dependencies here so dep picks them up -// and installs sub-dependencies. - -// TODO: Ideally this gets auto-imported on dep update. -// Any way to make that happen? -// NOTE: problems with this import because its a main not a lib -// _ "github.com/alecthomas/gometalinter" - -func main() {} diff --git a/types/abci.go b/types/abci.go index a46e797eb..0646d21e3 100644 --- a/types/abci.go +++ b/types/abci.go @@ -1,6 +1,6 @@ package types -import abci "github.com/tendermint/abci/types" +import abci "github.com/tendermint/tendermint/abci/types" // initialize application state at genesis type InitChainer func(ctx Context, req abci.RequestInitChain) abci.ResponseInitChain diff --git a/types/account.go b/types/account.go index a7dd50ead..daa3b614e 100644 --- a/types/account.go +++ b/types/account.go @@ -2,37 +2,192 @@ package types import ( "encoding/hex" + "encoding/json" "errors" "fmt" - crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/tmlibs/bech32" - cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/libs/bech32" ) -//Address is a go crypto-style Address -type Address = cmn.HexBytes - // Bech32 prefixes const ( + // expected address length + AddrLen = 20 + + // Bech32 prefixes Bech32PrefixAccAddr = "cosmosaccaddr" Bech32PrefixAccPub = "cosmosaccpub" Bech32PrefixValAddr = "cosmosvaladdr" Bech32PrefixValPub = "cosmosvalpub" ) -// Bech32ifyAcc takes Address and returns the bech32 encoded string -func Bech32ifyAcc(addr Address) (string, error) { - return bech32.ConvertAndEncode(Bech32PrefixAccAddr, addr.Bytes()) +//__________________________________________________________ + +// AccAddress a wrapper around bytes meant to represent an account address +// When marshaled to a string or json, it uses bech32 +type AccAddress []byte + +// create an AccAddress from a hex string +func AccAddressFromHex(address string) (addr AccAddress, err error) { + if len(address) == 0 { + return addr, errors.New("decoding bech32 address failed: must provide an address") + } + bz, err := hex.DecodeString(address) + if err != nil { + return nil, err + } + return AccAddress(bz), nil } -// MustBech32ifyAcc panics on bech32-encoding failure -func MustBech32ifyAcc(addr Address) string { - enc, err := Bech32ifyAcc(addr) +// create an AccAddress from a bech32 string +func AccAddressFromBech32(address string) (addr AccAddress, err error) { + bz, err := GetFromBech32(address, Bech32PrefixAccAddr) + if err != nil { + return nil, err + } + return AccAddress(bz), nil +} + +// Marshal needed for protobuf compatibility +func (bz AccAddress) Marshal() ([]byte, error) { + return bz, nil +} + +// Unmarshal needed for protobuf compatibility +func (bz *AccAddress) Unmarshal(data []byte) error { + *bz = data + return nil +} + +// Marshals to JSON using Bech32 +func (bz AccAddress) MarshalJSON() ([]byte, error) { + return json.Marshal(bz.String()) +} + +// Unmarshals from JSON assuming Bech32 encoding +func (bz *AccAddress) UnmarshalJSON(data []byte) error { + var s string + err := json.Unmarshal(data, &s) + if err != nil { + return nil + } + + bz2, err := AccAddressFromBech32(s) + if err != nil { + return err + } + *bz = bz2 + return nil +} + +// Allow it to fulfill various interfaces in light-client, etc... +func (bz AccAddress) Bytes() []byte { + return bz +} + +func (bz AccAddress) String() string { + bech32Addr, err := bech32.ConvertAndEncode(Bech32PrefixAccAddr, bz.Bytes()) if err != nil { panic(err) } - return enc + return bech32Addr +} + +// For Printf / Sprintf, returns bech32 when using %s +func (bz AccAddress) Format(s fmt.State, verb rune) { + switch verb { + case 's': + s.Write([]byte(fmt.Sprintf("%s", bz.String()))) + case 'p': + s.Write([]byte(fmt.Sprintf("%p", bz))) + default: + s.Write([]byte(fmt.Sprintf("%X", []byte(bz)))) + } +} + +//__________________________________________________________ + +// AccAddress a wrapper around bytes meant to represent a validator address +// (from over ABCI). When marshaled to a string or json, it uses bech32 +type ValAddress []byte + +// create a ValAddress from a hex string +func ValAddressFromHex(address string) (addr ValAddress, err error) { + if len(address) == 0 { + return addr, errors.New("decoding bech32 address failed: must provide an address") + } + bz, err := hex.DecodeString(address) + if err != nil { + return nil, err + } + return ValAddress(bz), nil +} + +// create a ValAddress from a bech32 string +func ValAddressFromBech32(address string) (addr ValAddress, err error) { + bz, err := GetFromBech32(address, Bech32PrefixValAddr) + if err != nil { + return nil, err + } + return ValAddress(bz), nil +} + +// Marshal needed for protobuf compatibility +func (bz ValAddress) Marshal() ([]byte, error) { + return bz, nil +} + +// Unmarshal needed for protobuf compatibility +func (bz *ValAddress) Unmarshal(data []byte) error { + *bz = data + return nil +} + +// Marshals to JSON using Bech32 +func (bz ValAddress) MarshalJSON() ([]byte, error) { + return json.Marshal(bz.String()) +} + +// Unmarshals from JSON assuming Bech32 encoding +func (bz *ValAddress) UnmarshalJSON(data []byte) error { + var s string + err := json.Unmarshal(data, &s) + if err != nil { + return nil + } + + bz2, err := ValAddressFromBech32(s) + if err != nil { + return err + } + *bz = bz2 + return nil +} + +// Allow it to fulfill various interfaces in light-client, etc... +func (bz ValAddress) Bytes() []byte { + return bz +} + +func (bz ValAddress) String() string { + bech32Addr, err := bech32.ConvertAndEncode(Bech32PrefixValAddr, bz.Bytes()) + if err != nil { + panic(err) + } + return bech32Addr +} + +// For Printf / Sprintf, returns bech32 when using %s +func (bz ValAddress) Format(s fmt.State, verb rune) { + switch verb { + case 's': + s.Write([]byte(fmt.Sprintf("%s", bz.String()))) + case 'p': + s.Write([]byte(fmt.Sprintf("%p", bz))) + default: + s.Write([]byte(fmt.Sprintf("%X", []byte(bz)))) + } } // Bech32ifyAccPub takes AccountPubKey and returns the bech32 encoded string @@ -49,26 +204,12 @@ func MustBech32ifyAccPub(pub crypto.PubKey) string { return enc } -// Bech32ifyVal returns the bech32 encoded string for a validator address -func Bech32ifyVal(addr Address) (string, error) { - return bech32.ConvertAndEncode(Bech32PrefixValAddr, addr.Bytes()) -} - -// MustBech32ifyVal panics on bech32-encoding failure -func MustBech32ifyVal(addr Address) string { - enc, err := Bech32ifyVal(addr) - if err != nil { - panic(err) - } - return enc -} - // Bech32ifyValPub returns the bech32 encoded string for a validator pubkey func Bech32ifyValPub(pub crypto.PubKey) (string, error) { return bech32.ConvertAndEncode(Bech32PrefixValPub, pub.Bytes()) } -// MustBech32ifyValPub pancis on bech32-encoding failure +// MustBech32ifyValPub panics on bech32-encoding failure func MustBech32ifyValPub(pub crypto.PubKey) string { enc, err := Bech32ifyValPub(pub) if err != nil { @@ -77,27 +218,6 @@ func MustBech32ifyValPub(pub crypto.PubKey) string { return enc } -// create an Address from a string -func GetAccAddressHex(address string) (addr Address, err error) { - if len(address) == 0 { - return addr, errors.New("must use provide address") - } - bz, err := hex.DecodeString(address) - if err != nil { - return nil, err - } - return Address(bz), nil -} - -// create an Address from a string -func GetAccAddressBech32(address string) (addr Address, err error) { - bz, err := GetFromBech32(address, Bech32PrefixAccAddr) - if err != nil { - return nil, err - } - return Address(bz), nil -} - // create a Pubkey from a string func GetAccPubKeyBech32(address string) (pk crypto.PubKey, err error) { bz, err := GetFromBech32(address, Bech32PrefixAccPub) @@ -113,25 +233,13 @@ func GetAccPubKeyBech32(address string) (pk crypto.PubKey, err error) { return pk, nil } -// create an Address from a hex string -func GetValAddressHex(address string) (addr Address, err error) { - if len(address) == 0 { - return addr, errors.New("must use provide address") - } - bz, err := hex.DecodeString(address) +// create an Pubkey from a string, panics on error +func MustGetAccPubKeyBech32(address string) (pk crypto.PubKey) { + pk, err := GetAccPubKeyBech32(address) if err != nil { - return nil, err + panic(err) } - return Address(bz), nil -} - -// create an Address from a bech32 string -func GetValAddressBech32(address string) (addr Address, err error) { - bz, err := GetFromBech32(address, Bech32PrefixValAddr) - if err != nil { - return nil, err - } - return Address(bz), nil + return pk } // decode a validator public key into a PubKey @@ -149,10 +257,19 @@ func GetValPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) { return pk, nil } +// create an Pubkey from a string, panics on error +func MustGetValPubKeyBech32(address string) (pk crypto.PubKey) { + pk, err := GetValPubKeyBech32(address) + if err != nil { + panic(err) + } + return pk +} + // decode a bytestring from a bech32-encoded string func GetFromBech32(bech32str, prefix string) ([]byte, error) { if len(bech32str) == 0 { - return nil, errors.New("must provide non-empty string") + return nil, errors.New("decoding bech32 address failed: must provide an address") } hrp, bz, err := bech32.DecodeAndConvert(bech32str) if err != nil { @@ -160,7 +277,7 @@ func GetFromBech32(bech32str, prefix string) ([]byte, error) { } if hrp != prefix { - return nil, fmt.Errorf("Invalid bech32 prefix. Expected %s, Got %s", prefix, hrp) + return nil, fmt.Errorf("invalid bech32 prefix. Expected %s, Got %s", prefix, hrp) } return bz, nil diff --git a/types/coin.go b/types/coin.go index 8a80bee22..eba645932 100644 --- a/types/coin.go +++ b/types/coin.go @@ -11,7 +11,14 @@ import ( // Coin hold some amount of one currency type Coin struct { Denom string `json:"denom"` - Amount int64 `json:"amount"` + Amount Int `json:"amount"` +} + +func NewCoin(denom string, amount int64) Coin { + return Coin{ + Denom: denom, + Amount: NewInt(amount), + } } // String provides a human-readable representation of a coin @@ -26,28 +33,28 @@ func (coin Coin) SameDenomAs(other Coin) bool { // IsZero returns if this represents no money func (coin Coin) IsZero() bool { - return coin.Amount == 0 + return coin.Amount.IsZero() } // IsGTE returns true if they are the same type and the receiver is // an equal or greater value func (coin Coin) IsGTE(other Coin) bool { - return coin.SameDenomAs(other) && (coin.Amount >= other.Amount) + return coin.SameDenomAs(other) && (!coin.Amount.LT(other.Amount)) } // IsEqual returns true if the two sets of Coins have the same value func (coin Coin) IsEqual(other Coin) bool { - return coin.SameDenomAs(other) && (coin.Amount == other.Amount) + return coin.SameDenomAs(other) && (coin.Amount.Equal(other.Amount)) } // IsPositive returns true if coin amount is positive func (coin Coin) IsPositive() bool { - return (coin.Amount > 0) + return (coin.Amount.Sign() == 1) } // IsNotNegative returns true if coin amount is not negative func (coin Coin) IsNotNegative() bool { - return (coin.Amount >= 0) + return (coin.Amount.Sign() != -1) } // Adds amounts of two coins with same denom @@ -55,7 +62,7 @@ func (coin Coin) Plus(coinB Coin) Coin { if !coin.SameDenomAs(coinB) { return coin } - return Coin{coin.Denom, coin.Amount + coinB.Amount} + return Coin{coin.Denom, coin.Amount.Add(coinB.Amount)} } // Subtracts amounts of two coins with same denom @@ -63,7 +70,7 @@ func (coin Coin) Minus(coinB Coin) Coin { if !coin.SameDenomAs(coinB) { return coin } - return Coin{coin.Denom, coin.Amount - coinB.Amount} + return Coin{coin.Denom, coin.Amount.Sub(coinB.Amount)} } //---------------------------------------- @@ -128,7 +135,7 @@ func (coins Coins) Plus(coinsB Coins) Coins { sum = append(sum, coinA) indexA++ case 0: - if coinA.Amount+coinB.Amount == 0 { + if coinA.Amount.Add(coinB.Amount).IsZero() { // ignore 0 sum coin type } else { sum = append(sum, coinA.Plus(coinB)) @@ -148,7 +155,7 @@ func (coins Coins) Negative() Coins { for _, coin := range coins { res = append(res, Coin{ Denom: coin.Denom, - Amount: -coin.Amount, + Amount: coin.Amount.Neg(), }) } return res @@ -187,7 +194,7 @@ func (coins Coins) IsEqual(coinsB Coins) bool { return false } for i := 0; i < len(coins); i++ { - if coins[i] != coinsB[i] { + if coins[i].Denom != coinsB[i].Denom || !coins[i].Amount.Equal(coinsB[i].Amount) { return false } } @@ -223,25 +230,25 @@ func (coins Coins) IsNotNegative() bool { } // Returns the amount of a denom from coins -func (coins Coins) AmountOf(denom string) int64 { +func (coins Coins) AmountOf(denom string) Int { switch len(coins) { case 0: - return 0 + return ZeroInt() case 1: coin := coins[0] if coin.Denom == denom { return coin.Amount } - return 0 + return ZeroInt() default: midIdx := len(coins) / 2 // 2:1, 3:1, 4:2 coin := coins[midIdx] if denom < coin.Denom { - return Coins(coins[:midIdx]).AmountOf(denom) + return coins[:midIdx].AmountOf(denom) } else if denom == coin.Denom { return coin.Amount } else { - return Coins(coins[midIdx+1:]).AmountOf(denom) + return coins[midIdx+1:].AmountOf(denom) } } } @@ -280,7 +287,7 @@ func ParseCoin(coinStr string) (coin Coin, err error) { matches := reCoin.FindStringSubmatch(coinStr) if matches == nil { - err = fmt.Errorf("Invalid coin expression: %s", coinStr) + err = fmt.Errorf("invalid coin expression: %s", coinStr) return } denomStr, amountStr := matches[2], matches[1] @@ -290,7 +297,7 @@ func ParseCoin(coinStr string) (coin Coin, err error) { return } - return Coin{denomStr, int64(amount)}, nil + return Coin{denomStr, NewInt(int64(amount))}, nil } // ParseCoins will parse out a list of coins separated by commas. @@ -316,7 +323,7 @@ func ParseCoins(coinsStr string) (coins Coins, err error) { // Validate coins before returning. if !coins.IsValid() { - return nil, fmt.Errorf("ParseCoins invalid: %#v", coins) + return nil, fmt.Errorf("parseCoins invalid: %#v", coins) } return coins, nil diff --git a/types/coin_test.go b/types/coin_test.go index eb9f5e087..c7ccc5746 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -4,179 +4,182 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestIsPositiveCoin(t *testing.T) { - assert := assert.New(t) - cases := []struct { inputOne Coin expected bool }{ - {Coin{"A", 1}, true}, - {Coin{"A", 0}, false}, - {Coin{"a", -1}, false}, + {NewCoin("A", 1), true}, + {NewCoin("A", 0), false}, + {NewCoin("a", -1), false}, } for _, tc := range cases { res := tc.inputOne.IsPositive() - assert.Equal(tc.expected, res) + require.Equal(t, tc.expected, res) } } func TestIsNotNegativeCoin(t *testing.T) { - assert := assert.New(t) - cases := []struct { inputOne Coin expected bool }{ - {Coin{"A", 1}, true}, - {Coin{"A", 0}, true}, - {Coin{"a", -1}, false}, + {NewCoin("A", 1), true}, + {NewCoin("A", 0), true}, + {NewCoin("a", -1), false}, } for _, tc := range cases { res := tc.inputOne.IsNotNegative() - assert.Equal(tc.expected, res) + require.Equal(t, tc.expected, res) } } func TestSameDenomAsCoin(t *testing.T) { - assert := assert.New(t) - cases := []struct { inputOne Coin inputTwo Coin expected bool }{ - {Coin{"A", 1}, Coin{"A", 1}, true}, - {Coin{"A", 1}, Coin{"a", 1}, false}, - {Coin{"a", 1}, Coin{"b", 1}, false}, - {Coin{"steak", 1}, Coin{"steak", 10}, true}, - {Coin{"steak", -11}, Coin{"steak", 10}, true}, + {NewCoin("A", 1), NewCoin("A", 1), true}, + {NewCoin("A", 1), NewCoin("a", 1), false}, + {NewCoin("a", 1), NewCoin("b", 1), false}, + {NewCoin("steak", 1), NewCoin("steak", 10), true}, + {NewCoin("steak", -11), NewCoin("steak", 10), true}, } for _, tc := range cases { res := tc.inputOne.SameDenomAs(tc.inputTwo) - assert.Equal(tc.expected, res) + require.Equal(t, tc.expected, res) } } func TestIsGTECoin(t *testing.T) { - assert := assert.New(t) - cases := []struct { inputOne Coin inputTwo Coin expected bool }{ - {Coin{"A", 1}, Coin{"A", 1}, true}, - {Coin{"A", 2}, Coin{"A", 1}, true}, - {Coin{"A", -1}, Coin{"A", 5}, false}, - {Coin{"a", 1}, Coin{"b", 1}, false}, + {NewCoin("A", 1), NewCoin("A", 1), true}, + {NewCoin("A", 2), NewCoin("A", 1), true}, + {NewCoin("A", -1), NewCoin("A", 5), false}, + {NewCoin("a", 1), NewCoin("b", 1), false}, } for _, tc := range cases { res := tc.inputOne.IsGTE(tc.inputTwo) - assert.Equal(tc.expected, res) + require.Equal(t, tc.expected, res) } } func TestIsEqualCoin(t *testing.T) { - assert := assert.New(t) - cases := []struct { inputOne Coin inputTwo Coin expected bool }{ - {Coin{"A", 1}, Coin{"A", 1}, true}, - {Coin{"A", 1}, Coin{"a", 1}, false}, - {Coin{"a", 1}, Coin{"b", 1}, false}, - {Coin{"steak", 1}, Coin{"steak", 10}, false}, - {Coin{"steak", -11}, Coin{"steak", 10}, false}, + {NewCoin("A", 1), NewCoin("A", 1), true}, + {NewCoin("A", 1), NewCoin("a", 1), false}, + {NewCoin("a", 1), NewCoin("b", 1), false}, + {NewCoin("steak", 1), NewCoin("steak", 10), false}, + {NewCoin("steak", -11), NewCoin("steak", 10), false}, } for _, tc := range cases { res := tc.inputOne.IsEqual(tc.inputTwo) - assert.Equal(tc.expected, res) + require.Equal(t, tc.expected, res) } } func TestPlusCoin(t *testing.T) { - assert := assert.New(t) - cases := []struct { inputOne Coin inputTwo Coin expected Coin }{ - {Coin{"A", 1}, Coin{"A", 1}, Coin{"A", 2}}, - {Coin{"A", 1}, Coin{"B", 1}, Coin{"A", 1}}, - {Coin{"asdf", -4}, Coin{"asdf", 5}, Coin{"asdf", 1}}, - {Coin{"asdf", -1}, Coin{"asdf", 1}, Coin{"asdf", 0}}, + {NewCoin("A", 1), NewCoin("A", 1), NewCoin("A", 2)}, + {NewCoin("A", 1), NewCoin("B", 1), NewCoin("A", 1)}, + {NewCoin("asdf", -4), NewCoin("asdf", 5), NewCoin("asdf", 1)}, } for _, tc := range cases { res := tc.inputOne.Plus(tc.inputTwo) - assert.Equal(tc.expected, res) + require.Equal(t, tc.expected, res) } + + tc := struct { + inputOne Coin + inputTwo Coin + expected int64 + }{NewCoin("asdf", -1), NewCoin("asdf", 1), 0} + res := tc.inputOne.Plus(tc.inputTwo) + require.Equal(t, tc.expected, res.Amount.Int64()) } func TestMinusCoin(t *testing.T) { - assert := assert.New(t) - cases := []struct { inputOne Coin inputTwo Coin expected Coin }{ - {Coin{"A", 1}, Coin{"A", 1}, Coin{"A", 0}}, - {Coin{"A", 1}, Coin{"B", 1}, Coin{"A", 1}}, - {Coin{"asdf", -4}, Coin{"asdf", 5}, Coin{"asdf", -9}}, - {Coin{"asdf", 10}, Coin{"asdf", 1}, Coin{"asdf", 9}}, + + {NewCoin("A", 1), NewCoin("B", 1), NewCoin("A", 1)}, + {NewCoin("asdf", -4), NewCoin("asdf", 5), NewCoin("asdf", -9)}, + {NewCoin("asdf", 10), NewCoin("asdf", 1), NewCoin("asdf", 9)}, } for _, tc := range cases { res := tc.inputOne.Minus(tc.inputTwo) - assert.Equal(tc.expected, res) + require.Equal(t, tc.expected, res) } + + tc := struct { + inputOne Coin + inputTwo Coin + expected int64 + }{NewCoin("A", 1), NewCoin("A", 1), 0} + res := tc.inputOne.Minus(tc.inputTwo) + require.Equal(t, tc.expected, res.Amount.Int64()) + } func TestCoins(t *testing.T) { //Define the coins to be used in tests good := Coins{ - {"GAS", 1}, - {"MINERAL", 1}, - {"TREE", 1}, + {"GAS", NewInt(1)}, + {"MINERAL", NewInt(1)}, + {"TREE", NewInt(1)}, } neg := good.Negative() sum := good.Plus(neg) empty := Coins{ - {"GOLD", 0}, + {"GOLD", NewInt(0)}, } badSort1 := Coins{ - {"TREE", 1}, - {"GAS", 1}, - {"MINERAL", 1}, + {"TREE", NewInt(1)}, + {"GAS", NewInt(1)}, + {"MINERAL", NewInt(1)}, } // both are after the first one, but the second and third are in the wrong order badSort2 := Coins{ - {"GAS", 1}, - {"TREE", 1}, - {"MINERAL", 1}, + {"GAS", NewInt(1)}, + {"TREE", NewInt(1)}, + {"MINERAL", NewInt(1)}, } badAmt := Coins{ - {"GAS", 1}, - {"TREE", 0}, - {"MINERAL", 1}, + {"GAS", NewInt(1)}, + {"TREE", NewInt(0)}, + {"MINERAL", NewInt(1)}, } dup := Coins{ - {"GAS", 1}, - {"GAS", 1}, - {"MINERAL", 1}, + {"GAS", NewInt(1)}, + {"GAS", NewInt(1)}, + {"MINERAL", NewInt(1)}, } assert.True(t, good.IsValid(), "Coins are valid") @@ -192,29 +195,33 @@ func TestCoins(t *testing.T) { } func TestPlusCoins(t *testing.T) { - assert := assert.New(t) + one := NewInt(1) + zero := NewInt(0) + negone := NewInt(-1) + two := NewInt(2) cases := []struct { inputOne Coins inputTwo Coins expected Coins }{ - {Coins{{"A", 1}, {"B", 1}}, Coins{{"A", 1}, {"B", 1}}, Coins{{"A", 2}, {"B", 2}}}, - {Coins{{"A", 0}, {"B", 1}}, Coins{{"A", 0}, {"B", 0}}, Coins{{"B", 1}}}, - {Coins{{"A", 0}, {"B", 0}}, Coins{{"A", 0}, {"B", 0}}, Coins(nil)}, - {Coins{{"A", 1}, {"B", 0}}, Coins{{"A", -1}, {"B", 0}}, Coins(nil)}, - {Coins{{"A", -1}, {"B", 0}}, Coins{{"A", 0}, {"B", 0}}, Coins{{"A", -1}}}, + {Coins{{"A", one}, {"B", one}}, Coins{{"A", one}, {"B", one}}, Coins{{"A", two}, {"B", two}}}, + {Coins{{"A", zero}, {"B", one}}, Coins{{"A", zero}, {"B", zero}}, Coins{{"B", one}}}, + {Coins{{"A", zero}, {"B", zero}}, Coins{{"A", zero}, {"B", zero}}, Coins(nil)}, + {Coins{{"A", one}, {"B", zero}}, Coins{{"A", negone}, {"B", zero}}, Coins(nil)}, + {Coins{{"A", negone}, {"B", zero}}, Coins{{"A", zero}, {"B", zero}}, Coins{{"A", negone}}}, } for _, tc := range cases { res := tc.inputOne.Plus(tc.inputTwo) - assert.True(res.IsValid()) - assert.Equal(tc.expected, res) + assert.True(t, res.IsValid()) + require.Equal(t, tc.expected, res) } } //Test the parsing of Coin and Coins func TestParse(t *testing.T) { + one := NewInt(1) cases := []struct { input string @@ -222,12 +229,12 @@ func TestParse(t *testing.T) { expected Coins // if valid is true, make sure this is returned }{ {"", true, nil}, - {"1foo", true, Coins{{"foo", 1}}}, - {"10bar", true, Coins{{"bar", 10}}}, - {"99bar,1foo", true, Coins{{"bar", 99}, {"foo", 1}}}, - {"98 bar , 1 foo ", true, Coins{{"bar", 98}, {"foo", 1}}}, - {" 55\t \t bling\n", true, Coins{{"bling", 55}}}, - {"2foo, 97 bar", true, Coins{{"bar", 97}, {"foo", 2}}}, + {"1foo", true, Coins{{"foo", one}}}, + {"10bar", true, Coins{{"bar", NewInt(10)}}}, + {"99bar,1foo", true, Coins{{"bar", NewInt(99)}, {"foo", one}}}, + {"98 bar , 1 foo ", true, Coins{{"bar", NewInt(98)}, {"foo", one}}}, + {" 55\t \t bling\n", true, Coins{{"bling", NewInt(55)}}}, + {"2foo, 97 bar", true, Coins{{"bar", NewInt(97)}, {"foo", NewInt(2)}}}, {"5 mycoin,", false, nil}, // no empty coins in a list {"2 3foo, 97 bar", false, nil}, // 3foo is invalid coin name {"11me coin, 12you coin", false, nil}, // no spaces in coin names @@ -238,9 +245,9 @@ func TestParse(t *testing.T) { for _, tc := range cases { res, err := ParseCoins(tc.input) if !tc.valid { - assert.NotNil(t, err, "%s: %#v", tc.input, res) + require.NotNil(t, err, "%s: %#v", tc.input, res) } else if assert.Nil(t, err, "%s: %+v", tc.input, err) { - assert.Equal(t, tc.expected, res) + require.Equal(t, tc.expected, res) } } @@ -249,32 +256,32 @@ func TestParse(t *testing.T) { func TestSortCoins(t *testing.T) { good := Coins{ - {"GAS", 1}, - {"MINERAL", 1}, - {"TREE", 1}, + NewCoin("GAS", 1), + NewCoin("MINERAL", 1), + NewCoin("TREE", 1), } empty := Coins{ - {"GOLD", 0}, + NewCoin("GOLD", 0), } badSort1 := Coins{ - {"TREE", 1}, - {"GAS", 1}, - {"MINERAL", 1}, + NewCoin("TREE", 1), + NewCoin("GAS", 1), + NewCoin("MINERAL", 1), } badSort2 := Coins{ // both are after the first one, but the second and third are in the wrong order - {"GAS", 1}, - {"TREE", 1}, - {"MINERAL", 1}, + NewCoin("GAS", 1), + NewCoin("TREE", 1), + NewCoin("MINERAL", 1), } badAmt := Coins{ - {"GAS", 1}, - {"TREE", 0}, - {"MINERAL", 1}, + NewCoin("GAS", 1), + NewCoin("TREE", 0), + NewCoin("MINERAL", 1), } dup := Coins{ - {"GAS", 1}, - {"GAS", 1}, - {"MINERAL", 1}, + NewCoin("GAS", 1), + NewCoin("GAS", 1), + NewCoin("MINERAL", 1), } cases := []struct { @@ -290,9 +297,9 @@ func TestSortCoins(t *testing.T) { } for _, tc := range cases { - assert.Equal(t, tc.before, tc.coins.IsValid()) + require.Equal(t, tc.before, tc.coins.IsValid()) tc.coins.Sort() - assert.Equal(t, tc.after, tc.coins.IsValid()) + require.Equal(t, tc.after, tc.coins.IsValid()) } } @@ -300,31 +307,31 @@ func TestAmountOf(t *testing.T) { case0 := Coins{} case1 := Coins{ - {"", 0}, + NewCoin("", 0), } case2 := Coins{ - {" ", 0}, + NewCoin(" ", 0), } case3 := Coins{ - {"GOLD", 0}, + NewCoin("GOLD", 0), } case4 := Coins{ - {"GAS", 1}, - {"MINERAL", 1}, - {"TREE", 1}, + NewCoin("GAS", 1), + NewCoin("MINERAL", 1), + NewCoin("TREE", 1), } case5 := Coins{ - {"MINERAL", 1}, - {"TREE", 1}, + NewCoin("MINERAL", 1), + NewCoin("TREE", 1), } case6 := Coins{ - {"", 6}, + NewCoin("", 6), } case7 := Coins{ - {" ", 7}, + NewCoin(" ", 7), } case8 := Coins{ - {"GAS", 8}, + NewCoin("GAS", 8), } cases := []struct { @@ -347,10 +354,10 @@ func TestAmountOf(t *testing.T) { } for _, tc := range cases { - assert.Equal(t, tc.amountOf, tc.coins.AmountOf("")) - assert.Equal(t, tc.amountOfSpace, tc.coins.AmountOf(" ")) - assert.Equal(t, tc.amountOfGAS, tc.coins.AmountOf("GAS")) - assert.Equal(t, tc.amountOfMINERAL, tc.coins.AmountOf("MINERAL")) - assert.Equal(t, tc.amountOfTREE, tc.coins.AmountOf("TREE")) + assert.Equal(t, NewInt(tc.amountOf), tc.coins.AmountOf("")) + assert.Equal(t, NewInt(tc.amountOfSpace), tc.coins.AmountOf(" ")) + assert.Equal(t, NewInt(tc.amountOfGAS), tc.coins.AmountOf("GAS")) + assert.Equal(t, NewInt(tc.amountOfMINERAL), tc.coins.AmountOf("MINERAL")) + assert.Equal(t, NewInt(tc.amountOfTREE), tc.coins.AmountOf("TREE")) } } diff --git a/types/context.go b/types/context.go index c4fc385c7..e55eff1ab 100644 --- a/types/context.go +++ b/types/context.go @@ -6,8 +6,8 @@ import ( "github.com/golang/protobuf/proto" - abci "github.com/tendermint/abci/types" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" ) /* @@ -30,7 +30,7 @@ type Context struct { } // create a new context -func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byte, logger log.Logger) Context { +func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, logger log.Logger) Context { c := Context{ Context: context.Background(), @@ -42,7 +42,7 @@ func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byt c = c.WithBlockHeight(header.Height) c = c.WithChainID(header.ChainID) c = c.WithIsCheckTx(isCheckTx) - c = c.WithTxBytes(txBytes) + c = c.WithTxBytes(nil) c = c.WithLogger(logger) c = c.WithSigningValidators(nil) c = c.WithGasMeter(NewInfiniteGasMeter()) diff --git a/types/context_test.go b/types/context_test.go index ec5faab44..fb2786cff 100644 --- a/types/context_test.go +++ b/types/context_test.go @@ -3,15 +3,14 @@ package types_test import ( "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/store" "github.com/cosmos/cosmos-sdk/types" - abci "github.com/tendermint/abci/types" + abci "github.com/tendermint/tendermint/abci/types" ) type MockLogger struct { @@ -43,7 +42,7 @@ func (l MockLogger) With(kvs ...interface{}) log.Logger { func TestContextGetOpShouldNeverPanic(t *testing.T) { var ms types.MultiStore - ctx := types.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := types.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) indices := []int64{ -10, 1, 0, 10, 20, } @@ -58,7 +57,7 @@ func defaultContext(key types.StoreKey) types.Context { cms := store.NewCommitMultiStore(db) cms.MountStoreWithDB(key, types.StoreTypeIAVL, db) cms.LoadLatestVersion() - ctx := types.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := types.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) return ctx } @@ -72,21 +71,21 @@ func TestCacheContext(t *testing.T) { ctx := defaultContext(key) store := ctx.KVStore(key) store.Set(k1, v1) - assert.Equal(t, v1, store.Get(k1)) - assert.Nil(t, store.Get(k2)) + require.Equal(t, v1, store.Get(k1)) + require.Nil(t, store.Get(k2)) cctx, write := ctx.CacheContext() cstore := cctx.KVStore(key) - assert.Equal(t, v1, cstore.Get(k1)) - assert.Nil(t, cstore.Get(k2)) + require.Equal(t, v1, cstore.Get(k1)) + require.Nil(t, cstore.Get(k2)) cstore.Set(k2, v2) - assert.Equal(t, v2, cstore.Get(k2)) - assert.Nil(t, store.Get(k2)) + require.Equal(t, v2, cstore.Get(k2)) + require.Nil(t, store.Get(k2)) write() - assert.Equal(t, v2, store.Get(k2)) + require.Equal(t, v2, store.Get(k2)) } func TestLogContext(t *testing.T) { diff --git a/types/errors.go b/types/errors.go index 65c258b0c..0f37bbd6b 100644 --- a/types/errors.go +++ b/types/errors.go @@ -3,9 +3,9 @@ package types import ( "fmt" - cmn "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tendermint/libs/common" - abci "github.com/tendermint/abci/types" + abci "github.com/tendermint/tendermint/abci/types" ) // ABCICodeType - combined codetype / codespace @@ -53,6 +53,7 @@ const ( CodeInsufficientCoins CodeType = 10 CodeInvalidCoins CodeType = 11 CodeOutOfGas CodeType = 12 + CodeMemoTooLarge CodeType = 13 // CodespaceRoot is a codespace for error codes in this file only. CodespaceRoot CodespaceType = 1 @@ -62,34 +63,37 @@ const ( ) // NOTE: Don't stringer this, we'll put better messages in later. +// nolint: gocyclo func CodeToDefaultMsg(code CodeType) string { switch code { case CodeInternal: - return "Internal error" + return "internal error" case CodeTxDecode: - return "Tx parse error" + return "tx parse error" case CodeInvalidSequence: - return "Invalid sequence" + return "invalid sequence" case CodeUnauthorized: - return "Unauthorized" + return "unauthorized" case CodeInsufficientFunds: - return "Insufficent funds" + return "insufficient funds" case CodeUnknownRequest: - return "Unknown request" + return "unknown request" case CodeInvalidAddress: - return "Invalid address" + return "invalid address" case CodeInvalidPubKey: - return "Invalid pubkey" + return "invalid pubkey" case CodeUnknownAddress: - return "Unknown address" + return "unknown address" case CodeInsufficientCoins: - return "Insufficient coins" + return "insufficient coins" case CodeInvalidCoins: - return "Invalid coins" + return "invalid coins" case CodeOutOfGas: - return "Out of gas" + return "out of gas" + case CodeMemoTooLarge: + return "memo too large" default: - return fmt.Sprintf("Unknown code %d", code) + return fmt.Sprintf("unknown code %d", code) } } @@ -134,6 +138,9 @@ func ErrInvalidCoins(msg string) Error { func ErrOutOfGas(msg string) Error { return newErrorWithRootCodespace(CodeOutOfGas, msg) } +func ErrMemoTooLarge(msg string) Error { + return newErrorWithRootCodespace(CodeMemoTooLarge, msg) +} //---------------------------------------- // Error & sdkError @@ -152,6 +159,9 @@ type Error interface { // convenience TraceSDK(format string, args ...interface{}) Error + // set codespace + WithDefaultCodespace(CodespaceType) Error + Code() CodeType Codespace() CodespaceType ABCILog() string @@ -186,6 +196,19 @@ type sdkError struct { cmnError } +// Implements Error. +func (err *sdkError) WithDefaultCodespace(cs CodespaceType) Error { + codespace := err.codespace + if codespace == CodespaceUndefined { + codespace = cs + } + return &sdkError{ + codespace: cs, + code: err.code, + cmnError: err.cmnError, + } +} + // Implements ABCIError. func (err *sdkError) TraceSDK(format string, args ...interface{}) Error { err.Trace(1, format, args...) diff --git a/types/errors_test.go b/types/errors_test.go index 798eafefc..959f059d0 100644 --- a/types/errors_test.go +++ b/types/errors_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var codeTypes = []CodeType{ @@ -32,11 +32,11 @@ var errFns = []errFn{ } func TestCodeType(t *testing.T) { - assert.True(t, ABCICodeOK.IsOK()) + require.True(t, ABCICodeOK.IsOK()) for _, c := range codeTypes { msg := CodeToDefaultMsg(c) - assert.False(t, strings.HasPrefix(msg, "Unknown code")) + require.False(t, strings.HasPrefix(msg, "Unknown code")) } } @@ -44,7 +44,7 @@ func TestErrFn(t *testing.T) { for i, errFn := range errFns { err := errFn("") codeType := codeTypes[i] - assert.Equal(t, err.Code(), codeType) - assert.Equal(t, err.Result().Code, ToABCICode(CodespaceRoot, codeType)) + require.Equal(t, err.Code(), codeType) + require.Equal(t, err.Result().Code, ToABCICode(CodespaceRoot, codeType)) } } diff --git a/types/gas.go b/types/gas.go index 49bfa27ec..18d3aa570 100644 --- a/types/gas.go +++ b/types/gas.go @@ -1,7 +1,5 @@ package types -import () - // Gas measured by the SDK type Gas = int64 diff --git a/types/handler.go b/types/handler.go index 129f42647..3a50e0ce0 100644 --- a/types/handler.go +++ b/types/handler.go @@ -1,7 +1,8 @@ package types -// core function variable which application runs for transactions +// Handler defines the core of the state transition function of an application. type Handler func(ctx Context, msg Msg) Result +// AnteHandler authenticates transactions, before their internal messages are handled. // If newCtx.IsZero(), ctx is used instead. type AnteHandler func(ctx Context, tx Tx) (newCtx Context, result Result, abort bool) diff --git a/types/int.go b/types/int.go new file mode 100644 index 000000000..0227203cd --- /dev/null +++ b/types/int.go @@ -0,0 +1,469 @@ +package types + +import ( + "encoding/json" + + "math/big" +) + +func newIntegerFromString(s string) (*big.Int, bool) { + return new(big.Int).SetString(s, 0) +} + +func newIntegerWithDecimal(n int64, dec int) (res *big.Int) { + if dec < 0 { + return + } + exp := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(dec)), nil) + i := new(big.Int) + return i.Mul(big.NewInt(n), exp) +} + +func equal(i *big.Int, i2 *big.Int) bool { return i.Cmp(i2) == 0 } + +func gt(i *big.Int, i2 *big.Int) bool { return i.Cmp(i2) == 1 } + +func lt(i *big.Int, i2 *big.Int) bool { return i.Cmp(i2) == -1 } + +func add(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Add(i, i2) } + +func sub(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Sub(i, i2) } + +func mul(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Mul(i, i2) } + +func div(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Div(i, i2) } + +func mod(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Mod(i, i2) } + +func neg(i *big.Int) *big.Int { return new(big.Int).Neg(i) } + +func min(i *big.Int, i2 *big.Int) *big.Int { + if i.Cmp(i2) == 1 { + return new(big.Int).Set(i2) + } + return new(big.Int).Set(i) +} + +// MarshalAmino for custom encoding scheme +func marshalAmino(i *big.Int) (string, error) { + bz, err := i.MarshalText() + return string(bz), err +} + +// UnmarshalAmino for custom decoding scheme +func unmarshalAmino(i *big.Int, text string) (err error) { + return i.UnmarshalText([]byte(text)) +} + +// MarshalJSON for custom encoding scheme +// Must be encoded as a string for JSON precision +func marshalJSON(i *big.Int) ([]byte, error) { + text, err := i.MarshalText() + if err != nil { + return nil, err + } + return json.Marshal(string(text)) +} + +// UnmarshalJSON for custom decoding scheme +// Must be encoded as a string for JSON precision +func unmarshalJSON(i *big.Int, bz []byte) error { + var text string + err := json.Unmarshal(bz, &text) + if err != nil { + return err + } + return i.UnmarshalText([]byte(text)) +} + +// Int wraps integer with 256 bit range bound +// Checks overflow, underflow and division by zero +// Exists in range from -(2^255-1) to 2^255-1 +type Int struct { + i *big.Int +} + +// BigInt converts Int to big.Int +func (i Int) BigInt() *big.Int { + return new(big.Int).Set(i.i) +} + +// NewInt constructs Int from int64 +func NewInt(n int64) Int { + return Int{big.NewInt(n)} +} + +// NewIntFromBigInt constructs Int from big.Int +func NewIntFromBigInt(i *big.Int) Int { + if i.BitLen() > 255 { + panic("NewIntFromBigInt() out of bound") + } + return Int{i} +} + +// NewIntFromString constructs Int from string +func NewIntFromString(s string) (res Int, ok bool) { + i, ok := newIntegerFromString(s) + if !ok { + return + } + // Check overflow + if i.BitLen() > 255 { + ok = false + return + } + return Int{i}, true +} + +// NewIntWithDecimal constructs Int with decimal +// Result value is n*10^dec +func NewIntWithDecimal(n int64, dec int) Int { + i := newIntegerWithDecimal(n, dec) + // Check overflow + if i.BitLen() > 255 { + panic("NewIntWithDecimal() out of bound") + } + return Int{i} +} + +// ZeroInt returns Int value with zero +func ZeroInt() Int { return Int{big.NewInt(0)} } + +// OneInt returns Int value with one +func OneInt() Int { return Int{big.NewInt(1)} } + +// Int64 converts Int to int64 +// Panics if the value is out of range +func (i Int) Int64() int64 { + if !i.i.IsInt64() { + panic("Int64() out of bound") + } + return i.i.Int64() +} + +// IsZero returns true if Int is zero +func (i Int) IsZero() bool { + return i.i.Sign() == 0 +} + +// Sign returns sign of Int +func (i Int) Sign() int { + return i.i.Sign() +} + +// Equal compares two Ints +func (i Int) Equal(i2 Int) bool { + return equal(i.i, i2.i) +} + +// GT returns true if first Int is greater than second +func (i Int) GT(i2 Int) bool { + return gt(i.i, i2.i) +} + +// LT returns true if first Int is lesser than second +func (i Int) LT(i2 Int) bool { + return lt(i.i, i2.i) +} + +// Add adds Int from another +func (i Int) Add(i2 Int) (res Int) { + res = Int{add(i.i, i2.i)} + // Check overflow + if res.i.BitLen() > 255 { + panic("Int overflow") + } + return +} + +// AddRaw adds int64 to Int +func (i Int) AddRaw(i2 int64) Int { + return i.Add(NewInt(i2)) +} + +// Sub subtracts Int from another +func (i Int) Sub(i2 Int) (res Int) { + res = Int{sub(i.i, i2.i)} + // Check overflow + if res.i.BitLen() > 255 { + panic("Int overflow") + } + return +} + +// SubRaw subtracts int64 from Int +func (i Int) SubRaw(i2 int64) Int { + return i.Sub(NewInt(i2)) +} + +// Mul multiples two Ints +func (i Int) Mul(i2 Int) (res Int) { + // Check overflow + if i.i.BitLen()+i2.i.BitLen()-1 > 255 { + panic("Int overflow") + } + res = Int{mul(i.i, i2.i)} + // Check overflow if sign of both are same + if res.i.BitLen() > 255 { + panic("Int overflow") + } + return +} + +// MulRaw multipies Int and int64 +func (i Int) MulRaw(i2 int64) Int { + return i.Mul(NewInt(i2)) +} + +// Div divides Int with Int +func (i Int) Div(i2 Int) (res Int) { + // Check division-by-zero + if i2.i.Sign() == 0 { + panic("Division by zero") + } + return Int{div(i.i, i2.i)} +} + +// DivRaw divides Int with int64 +func (i Int) DivRaw(i2 int64) Int { + return i.Div(NewInt(i2)) +} + +// Neg negates Int +func (i Int) Neg() (res Int) { + return Int{neg(i.i)} +} + +// Return the minimum of the ints +func MinInt(i1, i2 Int) Int { + return Int{min(i1.BigInt(), i2.BigInt())} +} + +func (i Int) String() string { + return i.i.String() +} + +// MarshalAmino defines custom encoding scheme +func (i Int) MarshalAmino() (string, error) { + if i.i == nil { // Necessary since default Uint initialization has i.i as nil + i.i = new(big.Int) + } + return marshalAmino(i.i) +} + +// UnmarshalAmino defines custom decoding scheme +func (i *Int) UnmarshalAmino(text string) error { + if i.i == nil { // Necessary since default Int initialization has i.i as nil + i.i = new(big.Int) + } + return unmarshalAmino(i.i, text) +} + +// MarshalJSON defines custom encoding scheme +func (i Int) MarshalJSON() ([]byte, error) { + if i.i == nil { // Necessary since default Uint initialization has i.i as nil + i.i = new(big.Int) + } + return marshalJSON(i.i) +} + +// UnmarshalJSON defines custom decoding scheme +func (i *Int) UnmarshalJSON(bz []byte) error { + if i.i == nil { // Necessary since default Int initialization has i.i as nil + i.i = new(big.Int) + } + return unmarshalJSON(i.i, bz) +} + +// Int wraps integer with 256 bit range bound +// Checks overflow, underflow and division by zero +// Exists in range from 0 to 2^256-1 +type Uint struct { + i *big.Int +} + +// BigInt converts Uint to big.Unt +func (i Uint) BigInt() *big.Int { + return new(big.Int).Set(i.i) +} + +// NewUint constructs Uint from int64 +func NewUint(n uint64) Uint { + i := new(big.Int) + i.SetUint64(n) + return Uint{i} +} + +// NewUintFromBigUint constructs Uint from big.Uint +func NewUintFromBigInt(i *big.Int) Uint { + // Check overflow + if i.Sign() == -1 || i.Sign() == 1 && i.BitLen() > 256 { + panic("Uint overflow") + } + return Uint{i} +} + +// NewUintFromString constructs Uint from string +func NewUintFromString(s string) (res Uint, ok bool) { + i, ok := newIntegerFromString(s) + if !ok { + return + } + // Check overflow + if i.Sign() == -1 || i.Sign() == 1 && i.BitLen() > 256 { + ok = false + return + } + return Uint{i}, true +} + +// NewUintWithDecimal constructs Uint with decimal +// Result value is n*10^dec +func NewUintWithDecimal(n int64, dec int) Uint { + i := newIntegerWithDecimal(n, dec) + // Check overflow + if i.Sign() == -1 || i.Sign() == 1 && i.BitLen() > 256 { + panic("NewUintWithDecimal() out of bound") + } + return Uint{i} +} + +// ZeroUint returns Uint value with zero +func ZeroUint() Uint { return Uint{big.NewInt(0)} } + +// OneUint returns Uint value with one +func OneUint() Uint { return Uint{big.NewInt(1)} } + +// Uint64 converts Uint to uint64 +// Panics if the value is out of range +func (i Uint) Uint64() uint64 { + if !i.i.IsUint64() { + panic("Uint64() out of bound") + } + return i.i.Uint64() +} + +// IsZero returns true if Uint is zero +func (i Uint) IsZero() bool { + return i.i.Sign() == 0 +} + +// Sign returns sign of Uint +func (i Uint) Sign() int { + return i.i.Sign() +} + +// Equal compares two Uints +func (i Uint) Equal(i2 Uint) bool { + return equal(i.i, i2.i) +} + +// GT returns true if first Uint is greater than second +func (i Uint) GT(i2 Uint) bool { + return gt(i.i, i2.i) +} + +// LT returns true if first Uint is lesser than second +func (i Uint) LT(i2 Uint) bool { + return lt(i.i, i2.i) +} + +// Add adds Uint from another +func (i Uint) Add(i2 Uint) (res Uint) { + res = Uint{add(i.i, i2.i)} + // Check overflow + if res.Sign() == -1 || res.Sign() == 1 && res.i.BitLen() > 256 { + panic("Uint overflow") + } + return +} + +// AddRaw adds int64 to Uint +func (i Uint) AddRaw(i2 uint64) Uint { + return i.Add(NewUint(i2)) +} + +// Sub subtracts Uint from another +func (i Uint) Sub(i2 Uint) (res Uint) { + res = Uint{sub(i.i, i2.i)} + // Check overflow + if res.Sign() == -1 || res.Sign() == 1 && res.i.BitLen() > 256 { + panic("Uint overflow") + } + return +} + +// SubRaw subtracts int64 from Uint +func (i Uint) SubRaw(i2 uint64) Uint { + return i.Sub(NewUint(i2)) +} + +// Mul multiples two Uints +func (i Uint) Mul(i2 Uint) (res Uint) { + // Check overflow + if i.i.BitLen()+i2.i.BitLen()-1 > 256 { + panic("Uint overflow") + } + res = Uint{mul(i.i, i2.i)} + // Check overflow + if res.Sign() == -1 || res.Sign() == 1 && res.i.BitLen() > 256 { + panic("Uint overflow") + } + return +} + +// MulRaw multipies Uint and int64 +func (i Uint) MulRaw(i2 uint64) Uint { + return i.Mul(NewUint(i2)) +} + +// Div divides Uint with Uint +func (i Uint) Div(i2 Uint) (res Uint) { + // Check division-by-zero + if i2.Sign() == 0 { + panic("division-by-zero") + } + return Uint{div(i.i, i2.i)} +} + +// Div divides Uint with int64 +func (i Uint) DivRaw(i2 uint64) Uint { + return i.Div(NewUint(i2)) +} + +// Return the minimum of the Uints +func MinUint(i1, i2 Uint) Uint { + return Uint{min(i1.BigInt(), i2.BigInt())} +} + +// MarshalAmino defines custom encoding scheme +func (i Uint) MarshalAmino() (string, error) { + if i.i == nil { // Necessary since default Uint initialization has i.i as nil + i.i = new(big.Int) + } + return marshalAmino(i.i) +} + +// UnmarshalAmino defines custom decoding scheme +func (i *Uint) UnmarshalAmino(text string) error { + if i.i == nil { // Necessary since default Uint initialization has i.i as nil + i.i = new(big.Int) + } + return unmarshalAmino(i.i, text) +} + +// MarshalJSON defines custom encoding scheme +func (i Uint) MarshalJSON() ([]byte, error) { + if i.i == nil { // Necessary since default Uint initialization has i.i as nil + i.i = new(big.Int) + } + return marshalJSON(i.i) +} + +// UnmarshalJSON defines custom decoding scheme +func (i *Uint) UnmarshalJSON(bz []byte) error { + if i.i == nil { // Necessary since default Uint initialization has i.i as nil + i.i = new(big.Int) + } + return unmarshalJSON(i.i, bz) +} diff --git a/types/int_test.go b/types/int_test.go new file mode 100644 index 000000000..e81bd6d7e --- /dev/null +++ b/types/int_test.go @@ -0,0 +1,111 @@ +package types + +import ( + "math/big" + "math/rand" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFromInt64(t *testing.T) { + for n := 0; n < 20; n++ { + r := rand.Int63() + require.Equal(t, r, NewInt(r).Int64()) + } +} + +func TestInt(t *testing.T) { + // Max Int = 2^255-1 = 5.789e+76 + // Min Int = -(2^255-1) = -5.789e+76 + require.NotPanics(t, func() { NewIntWithDecimal(1, 76) }) + i1 := NewIntWithDecimal(1, 76) + require.NotPanics(t, func() { NewIntWithDecimal(2, 76) }) + i2 := NewIntWithDecimal(2, 76) + require.NotPanics(t, func() { NewIntWithDecimal(3, 76) }) + i3 := NewIntWithDecimal(3, 76) + + require.Panics(t, func() { NewIntWithDecimal(6, 76) }) + require.Panics(t, func() { NewIntWithDecimal(9, 80) }) + + // Overflow check + require.NotPanics(t, func() { i1.Add(i1) }) + require.NotPanics(t, func() { i2.Add(i2) }) + require.Panics(t, func() { i3.Add(i3) }) + + require.NotPanics(t, func() { i1.Sub(i1.Neg()) }) + require.NotPanics(t, func() { i2.Sub(i2.Neg()) }) + require.Panics(t, func() { i3.Sub(i3.Neg()) }) + + require.Panics(t, func() { i1.Mul(i1) }) + require.Panics(t, func() { i2.Mul(i2) }) + require.Panics(t, func() { i3.Mul(i3) }) + + require.Panics(t, func() { i1.Neg().Mul(i1.Neg()) }) + require.Panics(t, func() { i2.Neg().Mul(i2.Neg()) }) + require.Panics(t, func() { i3.Neg().Mul(i3.Neg()) }) + + // Underflow check + i3n := i3.Neg() + require.NotPanics(t, func() { i3n.Sub(i1) }) + require.NotPanics(t, func() { i3n.Sub(i2) }) + require.Panics(t, func() { i3n.Sub(i3) }) + + require.NotPanics(t, func() { i3n.Add(i1.Neg()) }) + require.NotPanics(t, func() { i3n.Add(i2.Neg()) }) + require.Panics(t, func() { i3n.Add(i3.Neg()) }) + + require.Panics(t, func() { i1.Mul(i1.Neg()) }) + require.Panics(t, func() { i2.Mul(i2.Neg()) }) + require.Panics(t, func() { i3.Mul(i3.Neg()) }) + + // Bound check + intmax := NewIntFromBigInt(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(255), nil), big.NewInt(1))) + intmin := intmax.Neg() + require.NotPanics(t, func() { intmax.Add(ZeroInt()) }) + require.NotPanics(t, func() { intmin.Sub(ZeroInt()) }) + require.Panics(t, func() { intmax.Add(OneInt()) }) + require.Panics(t, func() { intmin.Sub(OneInt()) }) + + // Division-by-zero check + require.Panics(t, func() { i1.Div(NewInt(0)) }) +} + +func TestUint(t *testing.T) { + // Max Uint = 1.15e+77 + // Min Uint = 0 + require.NotPanics(t, func() { NewUintWithDecimal(5, 76) }) + i1 := NewUintWithDecimal(5, 76) + require.NotPanics(t, func() { NewUintWithDecimal(10, 76) }) + i2 := NewUintWithDecimal(10, 76) + require.NotPanics(t, func() { NewUintWithDecimal(11, 76) }) + i3 := NewUintWithDecimal(11, 76) + + require.Panics(t, func() { NewUintWithDecimal(12, 76) }) + require.Panics(t, func() { NewUintWithDecimal(1, 80) }) + + // Overflow check + require.NotPanics(t, func() { i1.Add(i1) }) + require.Panics(t, func() { i2.Add(i2) }) + require.Panics(t, func() { i3.Add(i3) }) + + require.Panics(t, func() { i1.Mul(i1) }) + require.Panics(t, func() { i2.Mul(i2) }) + require.Panics(t, func() { i3.Mul(i3) }) + + // Underflow check + require.NotPanics(t, func() { i2.Sub(i1) }) + require.NotPanics(t, func() { i2.Sub(i2) }) + require.Panics(t, func() { i2.Sub(i3) }) + + // Bound check + uintmax := NewUintFromBigInt(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil), big.NewInt(1))) + uintmin := NewUint(0) + require.NotPanics(t, func() { uintmax.Add(ZeroUint()) }) + require.NotPanics(t, func() { uintmin.Sub(ZeroUint()) }) + require.Panics(t, func() { uintmax.Add(OneUint()) }) + require.Panics(t, func() { uintmin.Sub(OneUint()) }) + + // Division-by-zero check + require.Panics(t, func() { i1.Div(uintmin) }) +} diff --git a/types/lib/linear.go b/types/lib/linear.go new file mode 100644 index 000000000..5a311a01f --- /dev/null +++ b/types/lib/linear.go @@ -0,0 +1,254 @@ +package lib + +import ( + "fmt" + "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + wire "github.com/cosmos/cosmos-sdk/wire" +) + +// Linear defines a primitive mapper type +type Linear struct { + cdc *wire.Codec + store sdk.KVStore + keys *LinearKeys +} + +// LinearKeys defines keysions for the key bytes +type LinearKeys struct { + LengthKey []byte + ElemKey []byte + TopKey []byte +} + +// Should never be modified +var cachedDefaultLinearKeys = DefaultLinearKeys() + +// DefaultLinearKeys returns the default setting of LinearOption +func DefaultLinearKeys() *LinearKeys { + keys := LinearKeys{ + LengthKey: []byte{0x00}, + ElemKey: []byte{0x01}, + TopKey: []byte{0x02}, + } + return &keys +} + +// NewLinear constructs new Linear +func NewLinear(cdc *wire.Codec, store sdk.KVStore, keys *LinearKeys) Linear { + if keys == nil { + keys = cachedDefaultLinearKeys + } + if keys.LengthKey == nil || keys.ElemKey == nil || keys.TopKey == nil { + panic("Invalid LinearKeys") + } + return Linear{ + cdc: cdc, + store: store, + keys: keys, + } +} + +// List is a Linear interface that provides list-like functions +// It panics when the element type cannot be (un/)marshalled by the codec +type List interface { + + // Len() returns the length of the list + // The length is only increased by Push() and not decreased + // List dosen't check if an index is in bounds + // The user should check Len() before doing any actions + Len() uint64 + + // Get() returns the element by its index + Get(uint64, interface{}) error + + // Set() stores the element to the given position + // Setting element out of range will break length counting + // Use Push() instead of Set() to append a new element + Set(uint64, interface{}) + + // Delete() deletes the element in the given position + // Other elements' indices are preserved after deletion + // Panics when the index is out of range + Delete(uint64) + + // Push() inserts the element to the end of the list + // It will increase the length when it is called + Push(interface{}) + + // Iterate*() is used to iterate over all existing elements in the list + // Return true in the continuation to break + // The second element of the continuation will indicate the position of the element + // Using it with Get() will return the same one with the provided element + + // CONTRACT: No writes may happen within a domain while iterating over it. + Iterate(interface{}, func(uint64) bool) +} + +// NewList constructs new List +func NewList(cdc *wire.Codec, store sdk.KVStore, keys *LinearKeys) List { + return NewLinear(cdc, store, keys) +} + +// Key for the length of the list +func (m Linear) LengthKey() []byte { + return m.keys.LengthKey +} + +// Key for the elements of the list +func (m Linear) ElemKey(index uint64) []byte { + return append(m.keys.ElemKey, []byte(fmt.Sprintf("%020d", index))...) +} + +// Len implements List +func (m Linear) Len() (res uint64) { + bz := m.store.Get(m.LengthKey()) + if bz == nil { + return 0 + } + m.cdc.MustUnmarshalBinary(bz, &res) + return +} + +// Get implements List +func (m Linear) Get(index uint64, ptr interface{}) error { + bz := m.store.Get(m.ElemKey(index)) + return m.cdc.UnmarshalBinary(bz, ptr) +} + +// Set implements List +func (m Linear) Set(index uint64, value interface{}) { + bz := m.cdc.MustMarshalBinary(value) + m.store.Set(m.ElemKey(index), bz) +} + +// Delete implements List +func (m Linear) Delete(index uint64) { + m.store.Delete(m.ElemKey(index)) +} + +// Push implements List +func (m Linear) Push(value interface{}) { + length := m.Len() + m.Set(length, value) + m.store.Set(m.LengthKey(), m.cdc.MustMarshalBinary(length+1)) +} + +// IterateRead implements List +func (m Linear) Iterate(ptr interface{}, fn func(uint64) bool) { + iter := sdk.KVStorePrefixIterator(m.store, []byte{0x01}) + for ; iter.Valid(); iter.Next() { + v := iter.Value() + m.cdc.MustUnmarshalBinary(v, ptr) + k := iter.Key() + s := string(k[len(k)-20:]) + index, err := strconv.ParseUint(s, 10, 64) + if err != nil { + panic(err) + } + if fn(index) { + break + } + } + + iter.Close() +} + +// Queue is a Linear interface that provides queue-like functions +// It panics when the element type cannot be (un/)marshalled by the codec +type Queue interface { + // Push() inserts the elements to the rear of the queue + Push(interface{}) + + // Popping/Peeking on an empty queue will cause panic + // The user should check IsEmpty() before doing any actions + + // Peek() returns the element at the front of the queue without removing it + Peek(interface{}) error + + // Pop() returns the element at the front of the queue and removes it + Pop() + + // IsEmpty() checks if the queue is empty + IsEmpty() bool + + // Flush() removes elements it processed + // Return true in the continuation to break + // The interface{} is unmarshalled before the continuation is called + // Starts from the top(head) of the queue + // CONTRACT: Pop() or Push() should not be performed while flushing + Flush(interface{}, func() bool) +} + +// NewQueue constructs new Queue +func NewQueue(cdc *wire.Codec, store sdk.KVStore, keys *LinearKeys) Queue { + return NewLinear(cdc, store, keys) +} + +// Key for the top element position in the queue +func (m Linear) TopKey() []byte { + return m.keys.TopKey +} + +func (m Linear) getTop() (res uint64) { + bz := m.store.Get(m.TopKey()) + if bz == nil { + return 0 + } + + m.cdc.MustUnmarshalBinary(bz, &res) + return +} + +func (m Linear) setTop(top uint64) { + bz := m.cdc.MustMarshalBinary(top) + m.store.Set(m.TopKey(), bz) +} + +// Peek implements Queue +func (m Linear) Peek(ptr interface{}) error { + top := m.getTop() + return m.Get(top, ptr) +} + +// Pop implements Queue +func (m Linear) Pop() { + top := m.getTop() + m.Delete(top) + m.setTop(top + 1) +} + +// IsEmpty implements Queue +func (m Linear) IsEmpty() bool { + top := m.getTop() + length := m.Len() + return top >= length +} + +// Flush implements Queue +func (m Linear) Flush(ptr interface{}, fn func() bool) { + top := m.getTop() + length := m.Len() + + var i uint64 + for i = top; i < length; i++ { + err := m.Get(i, ptr) + if err != nil { + // TODO: Handle with #870 + panic(err) + } + m.Delete(i) + if fn() { + break + } + } + m.setTop(i) +} + +func subspace(prefix []byte) (start, end []byte) { + end = make([]byte, len(prefix)) + copy(end, prefix) + end[len(end)-1]++ + return prefix, end +} diff --git a/types/lib/linear_test.go b/types/lib/linear_test.go new file mode 100644 index 000000000..b14300a98 --- /dev/null +++ b/types/lib/linear_test.go @@ -0,0 +1,157 @@ +package lib + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + wire "github.com/cosmos/cosmos-sdk/wire" +) + +type S struct { + I uint64 + B bool +} + +func defaultComponents(key sdk.StoreKey) (sdk.Context, *wire.Codec) { + db := dbm.NewMemDB() + cms := store.NewCommitMultiStore(db) + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + cms.LoadLatestVersion() + ctx := sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) + cdc := wire.NewCodec() + return ctx, cdc +} + +func TestList(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + lm := NewList(cdc, store, nil) + + val := S{1, true} + var res S + + lm.Push(val) + require.Equal(t, uint64(1), lm.Len()) + lm.Get(uint64(0), &res) + require.Equal(t, val, res) + + val = S{2, false} + lm.Set(uint64(0), val) + lm.Get(uint64(0), &res) + require.Equal(t, val, res) + + val = S{100, false} + lm.Push(val) + require.Equal(t, uint64(2), lm.Len()) + lm.Get(uint64(1), &res) + require.Equal(t, val, res) + + lm.Delete(uint64(1)) + require.Equal(t, uint64(2), lm.Len()) + + lm.Iterate(&res, func(index uint64) (brk bool) { + var temp S + lm.Get(index, &temp) + require.Equal(t, temp, res) + + require.True(t, index != 1) + return + }) + + lm.Iterate(&res, func(index uint64) (brk bool) { + lm.Set(index, S{res.I + 1, !res.B}) + return + }) + + lm.Get(uint64(0), &res) + require.Equal(t, S{3, true}, res) +} + +func TestQueue(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + + qm := NewQueue(cdc, store, nil) + + val := S{1, true} + var res S + + qm.Push(val) + qm.Peek(&res) + require.Equal(t, val, res) + + qm.Pop() + empty := qm.IsEmpty() + + require.True(t, empty) + require.NotNil(t, qm.Peek(&res)) + + qm.Push(S{1, true}) + qm.Push(S{2, true}) + qm.Push(S{3, true}) + qm.Flush(&res, func() (brk bool) { + if res.I == 3 { + brk = true + } + return + }) + + require.False(t, qm.IsEmpty()) + + qm.Pop() + require.True(t, qm.IsEmpty()) +} + +func TestOptions(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + + keys := &LinearKeys{ + LengthKey: []byte{0xDE, 0xAD}, + ElemKey: []byte{0xBE, 0xEF}, + TopKey: []byte{0x12, 0x34}, + } + linear := NewLinear(cdc, store, keys) + + for i := 0; i < 10; i++ { + linear.Push(i) + } + + var len uint64 + var top uint64 + var expected int + var actual int + + // Checking keys.LengthKey + err := cdc.UnmarshalBinary(store.Get(keys.LengthKey), &len) + require.Nil(t, err) + require.Equal(t, len, linear.Len()) + + // Checking keys.ElemKey + for i := 0; i < 10; i++ { + linear.Get(uint64(i), &expected) + bz := store.Get(append(keys.ElemKey, []byte(fmt.Sprintf("%020d", i))...)) + err = cdc.UnmarshalBinary(bz, &actual) + require.Nil(t, err) + require.Equal(t, expected, actual) + } + + linear.Pop() + + err = cdc.UnmarshalBinary(store.Get(keys.TopKey), &top) + require.Nil(t, err) + require.Equal(t, top, linear.getTop()) + +} diff --git a/types/lib/mapper.go b/types/lib/mapper.go deleted file mode 100644 index ca9020077..000000000 --- a/types/lib/mapper.go +++ /dev/null @@ -1,286 +0,0 @@ -package lib - -import ( - "fmt" - "strconv" - - sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" -) - -// Mapper defines a primitive mapper type -type Mapper struct { - key sdk.StoreKey - cdc *wire.Codec - prefix string -} - -// ListMapper is a Mapper interface that provides list-like functions -// It panics when the element type cannot be (un/)marshalled by the codec -type ListMapper interface { - - // Len() returns the length of the list - // The length is only increased by Push() and not decreased - // ListMapper dosen't check if an index is in bounds - // The user should check Len() before doing any actions - Len(sdk.Context) uint64 - - // Get() returns the element by its index - Get(sdk.Context, uint64, interface{}) error - - // Set() stores the element to the given position - // Setting element out of range will break length counting - // Use Push() instead of Set() to append a new element - Set(sdk.Context, uint64, interface{}) - - // Delete() deletes the element in the given position - // Other elements' indices are preserved after deletion - // Panics when the index is out of range - Delete(sdk.Context, uint64) - - // Push() inserts the element to the end of the list - // It will increase the length when it is called - Push(sdk.Context, interface{}) - - // Iterate*() is used to iterate over all existing elements in the list - // Return true in the continuation to break - // The second element of the continuation will indicate the position of the element - // Using it with Get() will return the same one with the provided element - - // CONTRACT: No writes may happen within a domain while iterating over it. - IterateRead(sdk.Context, interface{}, func(sdk.Context, uint64) bool) - - // IterateWrite() is safe to write over the domain - IterateWrite(sdk.Context, interface{}, func(sdk.Context, uint64) bool) - - // Key for the length of the list - LengthKey() []byte - - // Key for getting elements - ElemKey(uint64) []byte -} - -// NewListMapper constructs new ListMapper -func NewListMapper(cdc *wire.Codec, key sdk.StoreKey, prefix string) ListMapper { - return Mapper{ - key: key, - cdc: cdc, - prefix: prefix, - } -} - -// Len implements ListMapper -func (m Mapper) Len(ctx sdk.Context) uint64 { - store := ctx.KVStore(m.key) - bz := store.Get(m.LengthKey()) - if bz == nil { - zero, err := m.cdc.MarshalBinary(0) - if err != nil { - panic(err) - } - store.Set(m.LengthKey(), zero) - return 0 - } - var res uint64 - if err := m.cdc.UnmarshalBinary(bz, &res); err != nil { - panic(err) - } - return res -} - -// Get implements ListMapper -func (m Mapper) Get(ctx sdk.Context, index uint64, ptr interface{}) error { - store := ctx.KVStore(m.key) - bz := store.Get(m.ElemKey(index)) - return m.cdc.UnmarshalBinary(bz, ptr) -} - -// Set implements ListMapper -func (m Mapper) Set(ctx sdk.Context, index uint64, value interface{}) { - store := ctx.KVStore(m.key) - bz, err := m.cdc.MarshalBinary(value) - if err != nil { - panic(err) - } - store.Set(m.ElemKey(index), bz) -} - -// Delete implements ListMapper -func (m Mapper) Delete(ctx sdk.Context, index uint64) { - store := ctx.KVStore(m.key) - store.Delete(m.ElemKey(index)) -} - -// Push implements ListMapper -func (m Mapper) Push(ctx sdk.Context, value interface{}) { - length := m.Len(ctx) - m.Set(ctx, length, value) - - store := ctx.KVStore(m.key) - store.Set(m.LengthKey(), marshalUint64(m.cdc, length+1)) -} - -// IterateRead implements ListMapper -func (m Mapper) IterateRead(ctx sdk.Context, ptr interface{}, fn func(sdk.Context, uint64) bool) { - store := ctx.KVStore(m.key) - start, end := subspace([]byte(fmt.Sprintf("%s/elem/", m.prefix))) - iter := store.Iterator(start, end) - for ; iter.Valid(); iter.Next() { - v := iter.Value() - if err := m.cdc.UnmarshalBinary(v, ptr); err != nil { - panic(err) - } - s := string(iter.Key()[len(m.prefix)+6:]) - index, err := strconv.ParseUint(s, 10, 64) - if err != nil { - panic(err) - } - if fn(ctx, index) { - break - } - } - - iter.Close() -} - -// IterateWrite implements ListMapper -func (m Mapper) IterateWrite(ctx sdk.Context, ptr interface{}, fn func(sdk.Context, uint64) bool) { - length := m.Len(ctx) - - for i := uint64(0); i < length; i++ { - if err := m.Get(ctx, i, ptr); err != nil { - continue - } - if fn(ctx, i) { - break - } - } -} - -// LengthKey implements ListMapper -func (m Mapper) LengthKey() []byte { - return []byte(fmt.Sprintf("%s/length", m.prefix)) -} - -// ElemKey implements ListMapper -func (m Mapper) ElemKey(i uint64) []byte { - return []byte(fmt.Sprintf("%s/elem/%020d", m.prefix, i)) -} - -// QueueMapper is a Mapper interface that provides queue-like functions -// It panics when the element type cannot be (un/)marshalled by the codec -type QueueMapper interface { - // Push() inserts the elements to the rear of the queue - Push(sdk.Context, interface{}) - - // Popping/Peeking on an empty queue will cause panic - // The user should check IsEmpty() before doing any actions - - // Peek() returns the element at the front of the queue without removing it - Peek(sdk.Context, interface{}) error - - // Pop() returns the element at the front of the queue and removes it - Pop(sdk.Context) - - // IsEmpty() checks if the queue is empty - IsEmpty(sdk.Context) bool - - // Flush() removes elements it processed - // Return true in the continuation to break - // The interface{} is unmarshalled before the continuation is called - // Starts from the top(head) of the queue - // CONTRACT: Pop() or Push() should not be performed while flushing - Flush(sdk.Context, interface{}, func(sdk.Context) bool) - - // Key for the index of top element - TopKey() []byte -} - -// NewQueueMapper constructs new QueueMapper -func NewQueueMapper(cdc *wire.Codec, key sdk.StoreKey, prefix string) QueueMapper { - return Mapper{ - key: key, - cdc: cdc, - prefix: prefix, - } -} - -func (m Mapper) getTop(store sdk.KVStore) (res uint64) { - bz := store.Get(m.TopKey()) - if bz == nil { - store.Set(m.TopKey(), marshalUint64(m.cdc, 0)) - return 0 - } - - if err := m.cdc.UnmarshalBinary(bz, &res); err != nil { - panic(err) - } - - return -} - -func (m Mapper) setTop(store sdk.KVStore, top uint64) { - bz := marshalUint64(m.cdc, top) - store.Set(m.TopKey(), bz) -} - -// Peek implements QueueMapper -func (m Mapper) Peek(ctx sdk.Context, ptr interface{}) error { - store := ctx.KVStore(m.key) - top := m.getTop(store) - return m.Get(ctx, top, ptr) -} - -// Pop implements QueueMapper -func (m Mapper) Pop(ctx sdk.Context) { - store := ctx.KVStore(m.key) - top := m.getTop(store) - m.Delete(ctx, top) - m.setTop(store, top+1) -} - -// IsEmpty implements QueueMapper -func (m Mapper) IsEmpty(ctx sdk.Context) bool { - store := ctx.KVStore(m.key) - top := m.getTop(store) - length := m.Len(ctx) - return top >= length -} - -// Flush implements QueueMapper -func (m Mapper) Flush(ctx sdk.Context, ptr interface{}, fn func(sdk.Context) bool) { - store := ctx.KVStore(m.key) - top := m.getTop(store) - length := m.Len(ctx) - - var i uint64 - for i = top; i < length; i++ { - m.Get(ctx, i, ptr) - m.Delete(ctx, i) - if fn(ctx) { - break - } - } - - m.setTop(store, i) -} - -// TopKey implements QueueMapper -func (m Mapper) TopKey() []byte { - return []byte(fmt.Sprintf("%s/top", m.prefix)) -} - -func marshalUint64(cdc *wire.Codec, i uint64) []byte { - bz, err := cdc.MarshalBinary(i) - if err != nil { - panic(err) - } - return bz -} - -func subspace(prefix []byte) (start, end []byte) { - end = make([]byte, len(prefix)) - copy(end, prefix) - end[len(end)-1]++ - return prefix, end -} diff --git a/types/lib/mapper_test.go b/types/lib/mapper_test.go deleted file mode 100644 index e1759b06a..000000000 --- a/types/lib/mapper_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package lib - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" - - abci "github.com/tendermint/abci/types" - - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" -) - -type S struct { - I uint64 - B bool -} - -func defaultComponents(key sdk.StoreKey) (sdk.Context, *wire.Codec) { - db := dbm.NewMemDB() - cms := store.NewCommitMultiStore(db) - cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) - cms.LoadLatestVersion() - ctx := sdk.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger()) - cdc := wire.NewCodec() - return ctx, cdc -} - -func TestListMapper(t *testing.T) { - key := sdk.NewKVStoreKey("list") - ctx, cdc := defaultComponents(key) - lm := NewListMapper(cdc, key, "data") - - val := S{1, true} - var res S - - lm.Push(ctx, val) - assert.Equal(t, uint64(1), lm.Len(ctx)) - lm.Get(ctx, uint64(0), &res) - assert.Equal(t, val, res) - - val = S{2, false} - lm.Set(ctx, uint64(0), val) - lm.Get(ctx, uint64(0), &res) - assert.Equal(t, val, res) - - val = S{100, false} - lm.Push(ctx, val) - assert.Equal(t, uint64(2), lm.Len(ctx)) - lm.Get(ctx, uint64(1), &res) - assert.Equal(t, val, res) - - lm.Delete(ctx, uint64(1)) - assert.Equal(t, uint64(2), lm.Len(ctx)) - - lm.IterateRead(ctx, &res, func(ctx sdk.Context, index uint64) (brk bool) { - var temp S - lm.Get(ctx, index, &temp) - assert.Equal(t, temp, res) - - assert.True(t, index != 1) - return - }) - - lm.IterateWrite(ctx, &res, func(ctx sdk.Context, index uint64) (brk bool) { - lm.Set(ctx, index, S{res.I + 1, !res.B}) - return - }) - - lm.Get(ctx, uint64(0), &res) - assert.Equal(t, S{3, true}, res) -} - -func TestQueueMapper(t *testing.T) { - key := sdk.NewKVStoreKey("queue") - ctx, cdc := defaultComponents(key) - qm := NewQueueMapper(cdc, key, "data") - - val := S{1, true} - var res S - - qm.Push(ctx, val) - qm.Peek(ctx, &res) - assert.Equal(t, val, res) - - qm.Pop(ctx) - empty := qm.IsEmpty(ctx) - - assert.True(t, empty) - assert.NotNil(t, qm.Peek(ctx, &res)) - - qm.Push(ctx, S{1, true}) - qm.Push(ctx, S{2, true}) - qm.Push(ctx, S{3, true}) - qm.Flush(ctx, &res, func(ctx sdk.Context) (brk bool) { - if res.I == 3 { - brk = true - } - return - }) - - assert.False(t, qm.IsEmpty(ctx)) - - qm.Pop(ctx) - assert.True(t, qm.IsEmpty(ctx)) -} diff --git a/types/rational.go b/types/rational.go index 0709a350f..f81ee0836 100644 --- a/types/rational.go +++ b/types/rational.go @@ -18,29 +18,57 @@ import ( // we will panic unmarshalling into the // nil embedded big.Rat type Rat struct { - big.Rat `json:"rat"` + *big.Rat `json:"rat"` } // nolint - common values -func ZeroRat() Rat { return Rat{*big.NewRat(0, 1)} } -func OneRat() Rat { return Rat{*big.NewRat(1, 1)} } +func ZeroRat() Rat { return Rat{big.NewRat(0, 1)} } +func OneRat() Rat { return Rat{big.NewRat(1, 1)} } // New - create a new Rat from integers func NewRat(Numerator int64, Denominator ...int64) Rat { switch len(Denominator) { case 0: - return Rat{*big.NewRat(Numerator, 1)} + return Rat{big.NewRat(Numerator, 1)} case 1: - return Rat{*big.NewRat(Numerator, Denominator[0])} + return Rat{big.NewRat(Numerator, Denominator[0])} default: panic("improper use of New, can only have one denominator") } } -// create a rational from decimal string or integer string -func NewRatFromDecimal(decimalStr string) (f Rat, err Error) { +func getNumeratorDenominator(str []string, prec int) (numerator string, denom int64, err Error) { + switch len(str) { + case 1: + if len(str[0]) == 0 { + return "", 0, ErrUnknownRequest("not a decimal string") + } + numerator = str[0] + return numerator, 1, nil + case 2: + if len(str[0]) == 0 || len(str[1]) == 0 { + return "", 0, ErrUnknownRequest("not a decimal string") + } + if len(str[1]) > prec { + return "", 0, ErrUnknownRequest("string has too many decimals") + } + numerator = str[0] + str[1] + len := int64(len(str[1])) + denom = new(big.Int).Exp(big.NewInt(10), big.NewInt(len), nil).Int64() + return numerator, denom, nil + default: + return "", 0, ErrUnknownRequest("not a decimal string") + } +} +// create a rational from decimal string or integer string +// precision is the number of values after the decimal point which should be read +func NewRatFromDecimal(decimalStr string, prec int) (f Rat, err Error) { // first extract any negative symbol + if len(decimalStr) == 0 { + return f, ErrUnknownRequest("decimal string is empty") + } + neg := false if string(decimalStr[0]) == "-" { neg = true @@ -49,28 +77,26 @@ func NewRatFromDecimal(decimalStr string) (f Rat, err Error) { str := strings.Split(decimalStr, ".") - var numStr string - var denom int64 = 1 - switch len(str) { - case 1: - if len(str[0]) == 0 { - return f, ErrUnknownRequest("not a decimal string") - } - numStr = str[0] - case 2: - if len(str[0]) == 0 || len(str[1]) == 0 { - return f, ErrUnknownRequest("not a decimal string") - } - numStr = str[0] + str[1] - len := int64(len(str[1])) - denom = new(big.Int).Exp(big.NewInt(10), big.NewInt(len), nil).Int64() - default: - return f, ErrUnknownRequest("not a decimal string") + numStr, denom, err := getNumeratorDenominator(str, prec) + if err != nil { + return f, err } num, errConv := strconv.Atoi(numStr) - if errConv != nil { - return f, ErrUnknownRequest(errConv.Error()) + if errConv != nil && strings.HasSuffix(errConv.Error(), "value out of range") { + // resort to big int, don't make this default option for efficiency + numBig, success := new(big.Int).SetString(numStr, 10) + if success != true { + return f, ErrUnknownRequest("not a decimal string") + } + + if neg { + numBig.Neg(numBig) + } + + return NewRatFromBigInt(numBig, big.NewInt(denom)), nil + } else if errConv != nil { + return f, ErrUnknownRequest("not a decimal string") } if neg { @@ -80,18 +106,45 @@ func NewRatFromDecimal(decimalStr string) (f Rat, err Error) { return NewRat(int64(num), denom), nil } +// NewRatFromBigInt constructs Rat from big.Int +func NewRatFromBigInt(num *big.Int, denom ...*big.Int) Rat { + switch len(denom) { + case 0: + return Rat{new(big.Rat).SetInt(num)} + case 1: + return Rat{new(big.Rat).SetFrac(num, denom[0])} + default: + panic("improper use of NewRatFromBigInt, can only have one denominator") + } +} + +// NewRatFromInt constructs Rat from Int +func NewRatFromInt(num Int, denom ...Int) Rat { + switch len(denom) { + case 0: + return Rat{new(big.Rat).SetInt(num.BigInt())} + case 1: + return Rat{new(big.Rat).SetFrac(num.BigInt(), denom[0].BigInt())} + default: + panic("improper use of NewRatFromBigInt, can only have one denominator") + } +} + //nolint -func (r Rat) Num() int64 { return r.Rat.Num().Int64() } // Num - return the numerator -func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - return the denominator -func (r Rat) IsZero() bool { return r.Num() == 0 } // IsZero - Is the Rat equal to zero -func (r Rat) Equal(r2 Rat) bool { return (&(r.Rat)).Cmp(&(r2.Rat)) == 0 } -func (r Rat) GT(r2 Rat) bool { return (&(r.Rat)).Cmp(&(r2.Rat)) == 1 } // greater than -func (r Rat) LT(r2 Rat) bool { return (&(r.Rat)).Cmp(&(r2.Rat)) == -1 } // less than -func (r Rat) Mul(r2 Rat) Rat { return Rat{*new(big.Rat).Mul(&(r.Rat), &(r2.Rat))} } // Mul - multiplication -func (r Rat) Quo(r2 Rat) Rat { return Rat{*new(big.Rat).Quo(&(r.Rat), &(r2.Rat))} } // Quo - quotient -func (r Rat) Add(r2 Rat) Rat { return Rat{*new(big.Rat).Add(&(r.Rat), &(r2.Rat))} } // Add - addition -func (r Rat) Sub(r2 Rat) Rat { return Rat{*new(big.Rat).Sub(&(r.Rat), &(r2.Rat))} } // Sub - subtraction -func (r Rat) String() string { return fmt.Sprintf("%v/%v", r.Num(), r.Denom()) } +func (r Rat) Num() Int { return Int{r.Rat.Num()} } // Num - return the numerator +func (r Rat) Denom() Int { return Int{r.Rat.Denom()} } // Denom - return the denominator +func (r Rat) IsZero() bool { return r.Num().IsZero() } // IsZero - Is the Rat equal to zero +func (r Rat) Equal(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == 0 } +func (r Rat) GT(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == 1 } // greater than +func (r Rat) GTE(r2 Rat) bool { return !r.LT(r2) } // greater than or equal +func (r Rat) LT(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == -1 } // less than +func (r Rat) LTE(r2 Rat) bool { return !r.GT(r2) } // less than or equal +func (r Rat) Mul(r2 Rat) Rat { return Rat{new(big.Rat).Mul(r.Rat, r2.Rat)} } // Mul - multiplication +func (r Rat) Quo(r2 Rat) Rat { return Rat{new(big.Rat).Quo(r.Rat, r2.Rat)} } // Quo - quotient +func (r Rat) Add(r2 Rat) Rat { return Rat{new(big.Rat).Add(r.Rat, r2.Rat)} } // Add - addition +func (r Rat) Sub(r2 Rat) Rat { return Rat{new(big.Rat).Sub(r.Rat, r2.Rat)} } // Sub - subtraction +func (r Rat) String() string { return r.Rat.String() } +func (r Rat) FloatString() string { return r.Rat.FloatString(10) } // a human-friendly string format. The last digit is rounded to nearest, with halves rounded away from zero. var ( zero = big.NewInt(0) @@ -132,15 +185,20 @@ func (r Rat) EvaluateBig() *big.Int { return d } -// evaluate the rational using bankers rounding -func (r Rat) Evaluate() int64 { +// RoundInt64 rounds the rational using bankers rounding +func (r Rat) RoundInt64() int64 { return r.EvaluateBig().Int64() } +// RoundInt round the rational using bankers rounding +func (r Rat) RoundInt() Int { + return NewIntFromBigInt(r.EvaluateBig()) +} + // round Rat with the provided precisionFactor func (r Rat) Round(precisionFactor int64) Rat { - rTen := Rat{*new(big.Rat).Mul(&(r.Rat), big.NewRat(precisionFactor, 1))} - return Rat{*big.NewRat(rTen.Evaluate(), precisionFactor)} + rTen := Rat{new(big.Rat).Mul(r.Rat, big.NewRat(precisionFactor, 1))} + return Rat{big.NewRat(rTen.RoundInt64(), precisionFactor)} } // TODO panic if negative or if totalDigits < len(initStr)??? @@ -155,7 +213,10 @@ func (r Rat) ToLeftPadded(totalDigits int8) string { //Wraps r.MarshalText(). func (r Rat) MarshalAmino() (string, error) { - bz, err := (&(r.Rat)).MarshalText() + if r.Rat == nil { + r.Rat = new(big.Rat) + } + bz, err := r.Rat.MarshalText() return string(bz), err } @@ -166,7 +227,7 @@ func (r *Rat) UnmarshalAmino(text string) (err error) { if err != nil { return err } - r.Rat = *tempRat + r.Rat = tempRat return nil } diff --git a/types/rational_test.go b/types/rational_test.go index 30abb1a51..ecbc09e88 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -5,33 +5,41 @@ import ( "testing" wire "github.com/cosmos/cosmos-sdk/wire" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNew(t *testing.T) { - assert.Equal(t, NewRat(1), NewRat(1, 1)) - assert.Equal(t, NewRat(100), NewRat(100, 1)) - assert.Equal(t, NewRat(-1), NewRat(-1, 1)) - assert.Equal(t, NewRat(-100), NewRat(-100, 1)) - assert.Equal(t, NewRat(0), NewRat(0, 1)) + require.Equal(t, NewRat(1), NewRat(1, 1)) + require.Equal(t, NewRat(100), NewRat(100, 1)) + require.Equal(t, NewRat(-1), NewRat(-1, 1)) + require.Equal(t, NewRat(-100), NewRat(-100, 1)) + require.Equal(t, NewRat(0), NewRat(0, 1)) // do not allow for more than 2 variables - assert.Panics(t, func() { NewRat(1, 1, 1) }) + require.Panics(t, func() { NewRat(1, 1, 1) }) } func TestNewFromDecimal(t *testing.T) { + largeBigInt, success := new(big.Int).SetString("3109736052979742687701388262607869", 10) + require.True(t, success) tests := []struct { decimalStr string expErr bool exp Rat }{ + {"", true, Rat{}}, {"0", false, NewRat(0)}, {"1", false, NewRat(1)}, {"1.1", false, NewRat(11, 10)}, {"0.75", false, NewRat(3, 4)}, {"0.8", false, NewRat(4, 5)}, - {"0.11111", false, NewRat(11111, 100000)}, + {"0.11111", true, NewRat(1111, 10000)}, + {"628240629832763.5738930323617075341", true, NewRat(3141203149163817869, 5000)}, + {"621947210595948537540277652521.5738930323617075341", + true, NewRatFromBigInt(largeBigInt, big.NewInt(5000))}, + {"628240629832763.5738", false, NewRat(3141203149163817869, 5000)}, + {"621947210595948537540277652521.5738", + false, NewRatFromBigInt(largeBigInt, big.NewInt(5000))}, {".", true, Rat{}}, {".0", true, Rat{}}, {"1.", true, Rat{}}, @@ -41,22 +49,21 @@ func TestNewFromDecimal(t *testing.T) { } for _, tc := range tests { - - res, err := NewRatFromDecimal(tc.decimalStr) + res, err := NewRatFromDecimal(tc.decimalStr, 4) if tc.expErr { - assert.NotNil(t, err, tc.decimalStr) + require.NotNil(t, err, tc.decimalStr) } else { - assert.Nil(t, err) - assert.True(t, res.Equal(tc.exp)) + require.Nil(t, err, tc.decimalStr) + require.True(t, res.Equal(tc.exp), tc.decimalStr) } // negative tc - res, err = NewRatFromDecimal("-" + tc.decimalStr) + res, err = NewRatFromDecimal("-"+tc.decimalStr, 4) if tc.expErr { - assert.NotNil(t, err, tc.decimalStr) + require.NotNil(t, err, tc.decimalStr) } else { - assert.Nil(t, err) - assert.True(t, res.Equal(tc.exp.Mul(NewRat(-1)))) + require.Nil(t, err, tc.decimalStr) + require.True(t, res.Equal(tc.exp.Mul(NewRat(-1))), tc.decimalStr) } } } @@ -93,14 +100,14 @@ func TestEqualities(t *testing.T) { } for _, tc := range tests { - assert.Equal(t, tc.gt, tc.r1.GT(tc.r2)) - assert.Equal(t, tc.lt, tc.r1.LT(tc.r2)) - assert.Equal(t, tc.eq, tc.r1.Equal(tc.r2)) + require.Equal(t, tc.gt, tc.r1.GT(tc.r2)) + require.Equal(t, tc.lt, tc.r1.LT(tc.r2)) + require.Equal(t, tc.eq, tc.r1.Equal(tc.r2)) } } -func TestArithmatic(t *testing.T) { +func TestArithmetic(t *testing.T) { tests := []struct { r1, r2 Rat resMul, resDiv, resAdd, resSub Rat @@ -129,14 +136,14 @@ func TestArithmatic(t *testing.T) { } for _, tc := range tests { - assert.True(t, tc.resMul.Equal(tc.r1.Mul(tc.r2)), "r1 %v, r2 %v", tc.r1.Rat, tc.r2.Rat) - assert.True(t, tc.resAdd.Equal(tc.r1.Add(tc.r2)), "r1 %v, r2 %v", tc.r1.Rat, tc.r2.Rat) - assert.True(t, tc.resSub.Equal(tc.r1.Sub(tc.r2)), "r1 %v, r2 %v", tc.r1.Rat, tc.r2.Rat) + require.True(t, tc.resMul.Equal(tc.r1.Mul(tc.r2)), "r1 %v, r2 %v", tc.r1.Rat, tc.r2.Rat) + require.True(t, tc.resAdd.Equal(tc.r1.Add(tc.r2)), "r1 %v, r2 %v", tc.r1.Rat, tc.r2.Rat) + require.True(t, tc.resSub.Equal(tc.r1.Sub(tc.r2)), "r1 %v, r2 %v", tc.r1.Rat, tc.r2.Rat) - if tc.r2.Num() == 0 { // panic for divide by zero - assert.Panics(t, func() { tc.r1.Quo(tc.r2) }) + if tc.r2.Num().IsZero() { // panic for divide by zero + require.Panics(t, func() { tc.r1.Quo(tc.r2) }) } else { - assert.True(t, tc.resDiv.Equal(tc.r1.Quo(tc.r2)), "r1 %v, r2 %v", tc.r1.Rat, tc.r2.Rat) + require.True(t, tc.resDiv.Equal(tc.r1.Quo(tc.r2)), "r1 %v, r2 %v", tc.r1.Rat, tc.r2.Rat) } } } @@ -162,8 +169,8 @@ func TestEvaluate(t *testing.T) { } for _, tc := range tests { - assert.Equal(t, tc.res, tc.r1.Evaluate(), "%v", tc.r1) - assert.Equal(t, tc.res*-1, tc.r1.Mul(NewRat(-1)).Evaluate(), "%v", tc.r1.Mul(NewRat(-1))) + require.Equal(t, tc.res, tc.r1.RoundInt64(), "%v", tc.r1) + require.Equal(t, tc.res*-1, tc.r1.Mul(NewRat(-1)).RoundInt64(), "%v", tc.r1.Mul(NewRat(-1))) } } @@ -180,15 +187,15 @@ func TestRound(t *testing.T) { precFactor int64 }{ {NewRat(333, 777), NewRat(429, 1000), 1000}, - {Rat{*new(big.Rat).SetFrac(big3, big7)}, NewRat(429, 1000), 1000}, - {Rat{*new(big.Rat).SetFrac(big3, big7)}, Rat{*big.NewRat(4285714286, 10000000000)}, 10000000000}, + {Rat{new(big.Rat).SetFrac(big3, big7)}, NewRat(429, 1000), 1000}, + {Rat{new(big.Rat).SetFrac(big3, big7)}, Rat{big.NewRat(4285714286, 10000000000)}, 10000000000}, {NewRat(1, 2), NewRat(1, 2), 1000}, } for _, tc := range tests { - assert.Equal(t, tc.res, tc.r.Round(tc.precFactor), "%v", tc.r) + require.Equal(t, tc.res, tc.r.Round(tc.precFactor), "%v", tc.r) negR1, negRes := tc.r.Mul(NewRat(-1)), tc.res.Mul(NewRat(-1)) - assert.Equal(t, negRes, negR1.Round(tc.precFactor), "%v", negR1) + require.Equal(t, negRes, negR1.Round(tc.precFactor), "%v", negR1) } } @@ -205,7 +212,7 @@ func TestToLeftPadded(t *testing.T) { {NewRat(1000, 3), 12, "000000000333"}, } for _, tc := range tests { - assert.Equal(t, tc.res, tc.rat.ToLeftPadded(tc.digits)) + require.Equal(t, tc.res, tc.rat.ToLeftPadded(tc.digits)) } } @@ -214,13 +221,13 @@ var cdc = wire.NewCodec() //var jsonCdc JSONCodec // TODO wire.Codec func TestZeroSerializationJSON(t *testing.T) { r := NewRat(0, 1) err := cdc.UnmarshalJSON([]byte(`"0/1"`), &r) - assert.Nil(t, err) + require.Nil(t, err) err = cdc.UnmarshalJSON([]byte(`"0/0"`), &r) - assert.NotNil(t, err) + require.NotNil(t, err) err = cdc.UnmarshalJSON([]byte(`"1/0"`), &r) - assert.NotNil(t, err) + require.NotNil(t, err) err = cdc.UnmarshalJSON([]byte(`"{}"`), &r) - assert.NotNil(t, err) + require.NotNil(t, err) } func TestSerializationText(t *testing.T) { @@ -229,10 +236,10 @@ func TestSerializationText(t *testing.T) { bz, err := r.MarshalText() require.NoError(t, err) - var r2 Rat + var r2 = Rat{new(big.Rat)} err = r2.UnmarshalText(bz) require.NoError(t, err) - assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) + require.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) } func TestSerializationGoWireJSON(t *testing.T) { @@ -243,7 +250,7 @@ func TestSerializationGoWireJSON(t *testing.T) { var r2 Rat err = cdc.UnmarshalJSON(bz, &r2) require.NoError(t, err) - assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) + require.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) } func TestSerializationGoWireBinary(t *testing.T) { @@ -254,7 +261,7 @@ func TestSerializationGoWireBinary(t *testing.T) { var r2 Rat err = cdc.UnmarshalBinary(bz, &r2) require.NoError(t, err) - assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) + require.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) } type testEmbedStruct struct { @@ -272,9 +279,9 @@ func TestEmbeddedStructSerializationGoWire(t *testing.T) { err = cdc.UnmarshalJSON(bz, &obj2) require.Nil(t, err) - assert.Equal(t, obj.Field1, obj2.Field1) - assert.Equal(t, obj.Field2, obj2.Field2) - assert.True(t, obj.Field3.Equal(obj2.Field3), "original: %v, unmarshalled: %v", obj, obj2) + require.Equal(t, obj.Field1, obj2.Field1) + require.Equal(t, obj.Field2, obj2.Field2) + require.True(t, obj.Field3.Equal(obj2.Field3), "original: %v, unmarshalled: %v", obj, obj2) } func TestRatsEqual(t *testing.T) { @@ -292,8 +299,19 @@ func TestRatsEqual(t *testing.T) { } for _, tc := range tests { - assert.Equal(t, tc.eq, RatsEqual(tc.r1s, tc.r2s)) - assert.Equal(t, tc.eq, RatsEqual(tc.r2s, tc.r1s)) + require.Equal(t, tc.eq, RatsEqual(tc.r1s, tc.r2s)) + require.Equal(t, tc.eq, RatsEqual(tc.r2s, tc.r1s)) } } + +func TestStringOverflow(t *testing.T) { + // two random 64 bit primes + rat1 := NewRat(5164315003622678713, 4389711697696177267) + rat2 := NewRat(-3179849666053572961, 8459429845579852627) + rat3 := rat1.Add(rat2) + require.Equal(t, + "29728537197630860939575850336935951464/37134458148982045574552091851127630409", + rat3.String(), + ) +} diff --git a/types/result.go b/types/result.go index 65f87400d..63b8a6e18 100644 --- a/types/result.go +++ b/types/result.go @@ -1,9 +1,5 @@ package types -import ( - abci "github.com/tendermint/abci/types" -) - // Result is the union of ResponseDeliverTx and ResponseCheckTx. type Result struct { @@ -26,9 +22,6 @@ type Result struct { FeeAmount int64 FeeDenom string - // Changes to the validator set. - ValidatorUpdates []abci.Validator - // Tags are used for transaction indexing and pubsub. Tags Tags } diff --git a/types/stake.go b/types/stake.go index 7484295cc..35eeaba1f 100644 --- a/types/stake.go +++ b/types/stake.go @@ -1,8 +1,8 @@ package types import ( - abci "github.com/tendermint/abci/types" - "github.com/tendermint/go-crypto" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" tmtypes "github.com/tendermint/tendermint/types" ) @@ -32,11 +32,13 @@ func BondStatusToString(b BondStatus) string { // validator for a delegated proof of stake system type Validator interface { + GetRevoked() bool // whether the validator is revoked GetMoniker() string // moniker of the validator GetStatus() BondStatus // status of the validator - GetOwner() Address // owner address to receive/return validators coins + GetOwner() AccAddress // owner AccAddress to receive/return validators coins GetPubKey() crypto.PubKey // validation pubkey GetPower() Rat // validation power + GetDelegatorShares() Rat // Total out standing delegator shares GetBondHeight() int64 // height in which the validator became active } @@ -44,41 +46,44 @@ type Validator interface { func ABCIValidator(v Validator) abci.Validator { return abci.Validator{ PubKey: tmtypes.TM2PB.PubKey(v.GetPubKey()), - Power: v.GetPower().Evaluate(), + Power: v.GetPower().RoundInt64(), } } // properties for the set of all validators type ValidatorSet interface { - // iterate through validator by owner-address, execute func for each validator + // iterate through validator by owner-AccAddress, execute func for each validator IterateValidators(Context, func(index int64, validator Validator) (stop bool)) - // iterate through bonded validator by pubkey-address, execute func for each validator + // iterate through bonded validator by pubkey-AccAddress, execute func for each validator IterateValidatorsBonded(Context, func(index int64, validator Validator) (stop bool)) - Validator(Context, Address) Validator // get a particular validator by owner address - TotalPower(Context) Rat // total power of the validator set - Slash(Context, crypto.PubKey, int64, Rat) // slash the validator and delegators of the validator, specifying offence height & slash fraction - Revoke(Context, crypto.PubKey) // revoke a validator - Unrevoke(Context, crypto.PubKey) // unrevoke a validator + Validator(Context, AccAddress) Validator // get a particular validator by owner AccAddress + TotalPower(Context) Rat // total power of the validator set + + // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction + Slash(Context, crypto.PubKey, int64, int64, Rat) + Revoke(Context, crypto.PubKey) // revoke a validator + Unrevoke(Context, crypto.PubKey) // unrevoke a validator } //_______________________________________________________________________________ // delegation bond for a delegated proof of stake system type Delegation interface { - GetDelegator() Address // delegator address for the bond - GetValidator() Address // validator owner address for the bond - GetBondShares() Rat // amount of validator's shares + GetDelegator() AccAddress // delegator AccAddress for the bond + GetValidator() AccAddress // validator owner AccAddress for the bond + GetBondShares() Rat // amount of validator's shares } // properties for the set of all delegations for a particular type DelegationSet interface { + GetValidatorSet() ValidatorSet // validator set for which delegation set is based upon - // iterate through all delegations from one delegator by validator-address, + // iterate through all delegations from one delegator by validator-AccAddress, // execute func for each validator - IterateDelegators(Context, delegator Address, + IterateDelegations(ctx Context, delegator AccAddress, fn func(index int64, delegation Delegation) (stop bool)) } diff --git a/types/store.go b/types/store.go index 2bd34bebd..04de2c7c1 100644 --- a/types/store.go +++ b/types/store.go @@ -3,9 +3,9 @@ package types import ( "fmt" - abci "github.com/tendermint/abci/types" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" ) // NOTE: These are implemented in cosmos-sdk/store. @@ -103,13 +103,19 @@ type KVStore interface { // Delete deletes the key. Panics on nil key. Delete(key []byte) + // Prefix applied keys with the argument + Prefix(prefix []byte) KVStore + // Iterator over a domain of keys in ascending order. End is exclusive. // Start must be less than end, or the Iterator is invalid. + // Iterator must be closed by caller. + // To iterate over entire domain, use store.Iterator(nil, nil) // CONTRACT: No writes may happen within a domain while an iterator exists over it. Iterator(start, end []byte) Iterator // Iterator over a domain of keys in descending order. End is exclusive. // Start must be greater than end, or the Iterator is invalid. + // Iterator must be closed by caller. // CONTRACT: No writes may happen within a domain while an iterator exists over it. ReverseIterator(start, end []byte) Iterator @@ -149,6 +155,11 @@ type CommitKVStore interface { KVStore } +// Wrapper for StoreKeys to get KVStores +type KVStoreGetter interface { + KVStore(Context) KVStore +} + //---------------------------------------- // CacheWrap @@ -201,6 +212,7 @@ const ( StoreTypeMulti StoreType = iota StoreTypeDB StoreTypeIAVL + StoreTypePrefix ) //---------------------------------------- @@ -234,6 +246,11 @@ func (key *KVStoreKey) String() string { return fmt.Sprintf("KVStoreKey{%p, %s}", key, key.name) } +// Implements KVStoreGetter +func (key *KVStoreKey) KVStore(ctx Context) KVStore { + return ctx.KVStore(key) +} + // PrefixEndBytes returns the []byte that would end a // range query for all []byte with a certain prefix // Deals with last byte of prefix being FF without overflowing @@ -260,7 +277,24 @@ func PrefixEndBytes(prefix []byte) []byte { return end } +// Getter struct for prefixed stores +type PrefixStoreGetter struct { + key StoreKey + prefix []byte +} + +func NewPrefixStoreGetter(key StoreKey, prefix []byte) PrefixStoreGetter { + return PrefixStoreGetter{key, prefix} +} + +// Implements sdk.KVStoreGetter +func (getter PrefixStoreGetter) KVStore(ctx Context) KVStore { + return ctx.KVStore(getter.key).Prefix(getter.prefix) +} + //---------------------------------------- // key-value result for iterator queries type KVPair cmn.KVPair + +//---------------------------------------- diff --git a/types/store_test.go b/types/store_test.go index 43dd1f5d3..f376f3be1 100644 --- a/types/store_test.go +++ b/types/store_test.go @@ -3,12 +3,10 @@ package types import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestPrefixEndBytes(t *testing.T) { - assert := assert.New(t) - var testCases = []struct { prefix []byte expected []byte @@ -24,6 +22,6 @@ func TestPrefixEndBytes(t *testing.T) { for _, test := range testCases { end := PrefixEndBytes(test.prefix) - assert.Equal(test.expected, end) + require.Equal(t, test.expected, end) } } diff --git a/types/tags.go b/types/tags.go index 5a8eb1f47..add0c0ad5 100644 --- a/types/tags.go +++ b/types/tags.go @@ -1,7 +1,7 @@ package types import ( - cmn "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tendermint/libs/common" ) // Type synonym for convenience @@ -21,8 +21,8 @@ func (t Tags) AppendTag(k string, v []byte) Tags { } // Append two lists of tags -func (t Tags) AppendTags(a Tags) Tags { - return append(t, a...) +func (t Tags) AppendTags(tags Tags) Tags { + return append(t, tags...) } // Turn tags into KVPair list @@ -51,3 +51,13 @@ func NewTags(tags ...interface{}) Tags { func MakeTag(k string, v []byte) Tag { return Tag{Key: []byte(k), Value: v} } + +//__________________________________________________ + +// common tags +var ( + TagAction = "action" + TagSrcValidator = "source-validator" + TagDstValidator = "destination-validator" + TagDelegator = "delegator" +) diff --git a/types/tx_msg.go b/types/tx_msg.go index 240db3106..b0f5c78f4 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -11,17 +11,17 @@ type Msg interface { // Must be alphanumeric or empty. Type() string - // Get the canonical byte representation of the Msg. - GetSignBytes() []byte - // ValidateBasic does a simple validation check that // doesn't require access to any other information. ValidateBasic() Error + // Get the canonical byte representation of the Msg. + GetSignBytes() []byte + // Signers returns the addrs of signers that must sign. // CONTRACT: All signatures must be present to be valid. // CONTRACT: Returns addrs in some deterministic order. - GetSigners() []Address + GetSigners() []AccAddress } //__________________________________________________________ @@ -30,7 +30,7 @@ type Msg interface { type Tx interface { // Gets the Msg. - GetMsg() Msg + GetMsgs() []Msg } //__________________________________________________________ @@ -44,10 +44,10 @@ var _ Msg = (*TestMsg)(nil) // msg type for testing type TestMsg struct { - signers []Address + signers []AccAddress } -func NewTestMsg(addrs ...Address) *TestMsg { +func NewTestMsg(addrs ...AccAddress) *TestMsg { return &TestMsg{ signers: addrs, } @@ -60,9 +60,9 @@ func (msg *TestMsg) GetSignBytes() []byte { if err != nil { panic(err) } - return bz + return MustSortJSON(bz) } func (msg *TestMsg) ValidateBasic() Error { return nil } -func (msg *TestMsg) GetSigners() []Address { +func (msg *TestMsg) GetSigners() []AccAddress { return msg.signers } diff --git a/types/utils.go b/types/utils.go new file mode 100644 index 000000000..2e027676a --- /dev/null +++ b/types/utils.go @@ -0,0 +1,31 @@ +package types + +import "encoding/json" + +// SortedJSON takes any JSON and returns it sorted by keys. Also, all white-spaces +// are removed. +// This method can be used to canonicalize JSON to be returned by GetSignBytes, +// e.g. for the ledger integration. +// If the passed JSON isn't valid it will return an error. +func SortJSON(toSortJSON []byte) ([]byte, error) { + var c interface{} + err := json.Unmarshal(toSortJSON, &c) + if err != nil { + return nil, err + } + js, err := json.Marshal(c) + if err != nil { + return nil, err + } + return js, nil +} + +// MustSortJSON is like SortJSON but panic if an error occurs, e.g., if +// the passed JSON isn't valid. +func MustSortJSON(toSortJSON []byte) []byte { + js, err := SortJSON(toSortJSON) + if err != nil { + panic(err) + } + return js +} diff --git a/types/utils_test.go b/types/utils_test.go new file mode 100644 index 000000000..8c84e2ace --- /dev/null +++ b/types/utils_test.go @@ -0,0 +1,39 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSortJSON(t *testing.T) { + cases := []struct { + unsortedJSON string + want string + wantErr bool + }{ + // simple case + {unsortedJSON: `{"cosmos":"foo", "atom":"bar", "tendermint":"foobar"}`, + want: `{"atom":"bar","cosmos":"foo","tendermint":"foobar"}`, wantErr: false}, + // failing case (invalid JSON): + {unsortedJSON: `"cosmos":"foo",,,, "atom":"bar", "tendermint":"foobar"}`, + want: "", + wantErr: true}, + // genesis.json + {unsortedJSON: `{"consensus_params":{"block_size_params":{"max_bytes":22020096,"max_txs":100000,"max_gas":-1},"tx_size_params":{"max_bytes":10240,"max_gas":-1},"block_gossip_params":{"block_part_size_bytes":65536},"evidence_params":{"max_age":100000}},"validators":[{"pub_key":{"type":"AC26791624DE60","value":"c7UMMAbjFuc5GhGPy0E5q5tefy12p9Tq0imXqdrKXwo="},"power":100,"name":""}],"app_hash":"","genesis_time":"2018-05-11T15:52:25.424795506Z","chain_id":"test-chain-Q6VeoW","app_state":{"accounts":[{"address":"718C9C23F98C9642569742ADDD9F9AB9743FBD5D","coins":[{"denom":"Token","amount":1000},{"denom":"steak","amount":50}]}],"stake":{"pool":{"total_supply":50,"bonded_shares":"0","unbonded_shares":"0","bonded_pool":0,"unbonded_pool":0,"inflation_last_time":0,"inflation":"7/100"},"params":{"inflation_rate_change":"13/100","inflation_max":"1/5","inflation_min":"7/100","goal_bonded":"67/100","max_validators":100,"bond_denom":"steak"},"candidates":null,"bonds":null}}}`, + want: `{"app_hash":"","app_state":{"accounts":[{"address":"718C9C23F98C9642569742ADDD9F9AB9743FBD5D","coins":[{"amount":1000,"denom":"Token"},{"amount":50,"denom":"steak"}]}],"stake":{"bonds":null,"candidates":null,"params":{"bond_denom":"steak","goal_bonded":"67/100","inflation_max":"1/5","inflation_min":"7/100","inflation_rate_change":"13/100","max_validators":100},"pool":{"bonded_pool":0,"bonded_shares":"0","inflation":"7/100","inflation_last_time":0,"total_supply":50,"unbonded_pool":0,"unbonded_shares":"0"}}},"chain_id":"test-chain-Q6VeoW","consensus_params":{"block_gossip_params":{"block_part_size_bytes":65536},"block_size_params":{"max_bytes":22020096,"max_gas":-1,"max_txs":100000},"evidence_params":{"max_age":100000},"tx_size_params":{"max_bytes":10240,"max_gas":-1}},"genesis_time":"2018-05-11T15:52:25.424795506Z","validators":[{"name":"","power":100,"pub_key":{"type":"AC26791624DE60","value":"c7UMMAbjFuc5GhGPy0E5q5tefy12p9Tq0imXqdrKXwo="}}]}`, + wantErr: false}, + // from the TXSpec: + {unsortedJSON: `{"chain_id":"test-chain-1","sequence":1,"fee_bytes":{"amount":[{"amount":5,"denom":"photon"}],"gas":10000},"msg_bytes":{"inputs":[{"address":"696E707574","coins":[{"amount":10,"denom":"atom"}]}],"outputs":[{"address":"6F7574707574","coins":[{"amount":10,"denom":"atom"}]}]},"alt_bytes":null}`, + want: `{"alt_bytes":null,"chain_id":"test-chain-1","fee_bytes":{"amount":[{"amount":5,"denom":"photon"}],"gas":10000},"msg_bytes":{"inputs":[{"address":"696E707574","coins":[{"amount":10,"denom":"atom"}]}],"outputs":[{"address":"6F7574707574","coins":[{"amount":10,"denom":"atom"}]}]},"sequence":1}`, + wantErr: false}, + } + + for _, tc := range cases { + got, err := SortJSON([]byte(tc.unsortedJSON)) + if tc.wantErr != (err != nil) { + t.Fatalf("got %t, want: %t, err=%s", err != nil, tc.wantErr, err) + } + require.Equal(t, string(got), tc.want) + } +} diff --git a/version/command.go b/version/command.go index b505414b1..2cff1bbe9 100644 --- a/version/command.go +++ b/version/command.go @@ -2,7 +2,6 @@ package version import ( "fmt" - "net/http" "github.com/spf13/cobra" ) @@ -16,7 +15,8 @@ var ( } ) -func getVersion() string { +// return version of CLI/node and commit hash +func GetVersion() string { v := Version if GitCommit != "" { v = v + "-" + GitCommit @@ -26,12 +26,6 @@ func getVersion() string { // CMD func printVersion(cmd *cobra.Command, args []string) { - v := getVersion() + v := GetVersion() fmt.Println(v) } - -// version REST handler endpoint -func RequestHandler(w http.ResponseWriter, r *http.Request) { - v := getVersion() - w.Write([]byte(v)) -} diff --git a/version/version.go b/version/version.go index cb866a001..b9de4f991 100644 --- a/version/version.go +++ b/version/version.go @@ -1,15 +1,11 @@ //nolint package version -// when updating these, -// remember to also update examples/basecoin/tests/cli/rpc.sh -// TODO improve - const Maj = "0" -const Min = "19" -const Fix = "1" +const Min = "20" +const Fix = "0" -const Version = "0.19.1" +const Version = "0.20.0" // GitCommit set by build flags var GitCommit = "" diff --git a/wire/wire.go b/wire/wire.go index 0ee01939d..679bf7c28 100644 --- a/wire/wire.go +++ b/wire/wire.go @@ -4,8 +4,8 @@ import ( "bytes" "encoding/json" - "github.com/tendermint/go-amino" - "github.com/tendermint/go-crypto" + amino "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/crypto" ) // amino codec to marshal/unmarshal @@ -35,3 +35,14 @@ func MarshalJSONIndent(cdc *Codec, obj interface{}) ([]byte, error) { } return out.Bytes(), nil } + +//__________________________________________________________________ + +// generic sealed codec to be used throughout sdk +var Cdc *Codec + +func init() { + cdc := NewCodec() + RegisterCrypto(cdc) + Cdc = cdc.Seal() +} diff --git a/x/auth/account.go b/x/auth/account.go index 77966b8e7..3340c8bd5 100644 --- a/x/auth/account.go +++ b/x/auth/account.go @@ -5,14 +5,14 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/crypto" ) // Account is a standard account using a sequence number for replay protection // and a pubkey for authentication. type Account interface { - GetAddress() sdk.Address - SetAddress(sdk.Address) error // errors if already set. + GetAddress() sdk.AccAddress + SetAddress(sdk.AccAddress) error // errors if already set. GetPubKey() crypto.PubKey // can return nil. SetPubKey(crypto.PubKey) error @@ -39,26 +39,31 @@ var _ Account = (*BaseAccount)(nil) // Extend this by embedding this in your AppAccount. // See the examples/basecoin/types/account.go for an example. type BaseAccount struct { - Address sdk.Address `json:"address"` - Coins sdk.Coins `json:"coins"` - PubKey crypto.PubKey `json:"public_key"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` + PubKey crypto.PubKey `json:"public_key"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` } -func NewBaseAccountWithAddress(addr sdk.Address) BaseAccount { +// Prototype function for BaseAccount +func ProtoBaseAccount() Account { + return &BaseAccount{} +} + +func NewBaseAccountWithAddress(addr sdk.AccAddress) BaseAccount { return BaseAccount{ Address: addr, } } // Implements sdk.Account. -func (acc BaseAccount) GetAddress() sdk.Address { +func (acc BaseAccount) GetAddress() sdk.AccAddress { return acc.Address } // Implements sdk.Account. -func (acc *BaseAccount) SetAddress(addr sdk.Address) error { +func (acc *BaseAccount) SetAddress(addr sdk.AccAddress) error { if len(acc.Address) != 0 { return errors.New("cannot override BaseAccount address") } diff --git a/x/auth/account_test.go b/x/auth/account_test.go index a06545d3b..871f7f1c2 100644 --- a/x/auth/account_test.go +++ b/x/auth/account_test.go @@ -3,18 +3,18 @@ package auth import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" wire "github.com/cosmos/cosmos-sdk/wire" ) -func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.Address) { +func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) { key := crypto.GenPrivKeyEd25519() pub := key.PubKey() - addr := pub.Address() + addr := sdk.AccAddress(pub.Address()) return key, pub, addr } @@ -24,42 +24,42 @@ func TestBaseAddressPubKey(t *testing.T) { acc := NewBaseAccountWithAddress(addr1) // check the address (set) and pubkey (not set) - assert.EqualValues(t, addr1, acc.GetAddress()) - assert.EqualValues(t, nil, acc.GetPubKey()) + require.EqualValues(t, addr1, acc.GetAddress()) + require.EqualValues(t, nil, acc.GetPubKey()) // can't override address err := acc.SetAddress(addr2) - assert.NotNil(t, err) - assert.EqualValues(t, addr1, acc.GetAddress()) + require.NotNil(t, err) + require.EqualValues(t, addr1, acc.GetAddress()) // set the pubkey err = acc.SetPubKey(pub1) - assert.Nil(t, err) - assert.Equal(t, pub1, acc.GetPubKey()) + require.Nil(t, err) + require.Equal(t, pub1, acc.GetPubKey()) // can override pubkey err = acc.SetPubKey(pub2) - assert.Nil(t, err) - assert.Equal(t, pub2, acc.GetPubKey()) + require.Nil(t, err) + require.Equal(t, pub2, acc.GetPubKey()) //------------------------------------ // can set address on empty account acc2 := BaseAccount{} err = acc2.SetAddress(addr2) - assert.Nil(t, err) - assert.EqualValues(t, addr2, acc2.GetAddress()) + require.Nil(t, err) + require.EqualValues(t, addr2, acc2.GetAddress()) } func TestBaseAccountCoins(t *testing.T) { _, _, addr := keyPubAddr() acc := NewBaseAccountWithAddress(addr) - someCoins := sdk.Coins{{"atom", 123}, {"eth", 246}} + someCoins := sdk.Coins{sdk.NewCoin("atom", 123), sdk.NewCoin("eth", 246)} err := acc.SetCoins(someCoins) - assert.Nil(t, err) - assert.Equal(t, someCoins, acc.GetCoins()) + require.Nil(t, err) + require.Equal(t, someCoins, acc.GetCoins()) } func TestBaseAccountSequence(t *testing.T) { @@ -69,40 +69,40 @@ func TestBaseAccountSequence(t *testing.T) { seq := int64(7) err := acc.SetSequence(seq) - assert.Nil(t, err) - assert.Equal(t, seq, acc.GetSequence()) + require.Nil(t, err) + require.Equal(t, seq, acc.GetSequence()) } func TestBaseAccountMarshal(t *testing.T) { _, pub, addr := keyPubAddr() acc := NewBaseAccountWithAddress(addr) - someCoins := sdk.Coins{{"atom", 123}, {"eth", 246}} + someCoins := sdk.Coins{sdk.NewCoin("atom", 123), sdk.NewCoin("eth", 246)} seq := int64(7) // set everything on the account err := acc.SetPubKey(pub) - assert.Nil(t, err) + require.Nil(t, err) err = acc.SetSequence(seq) - assert.Nil(t, err) + require.Nil(t, err) err = acc.SetCoins(someCoins) - assert.Nil(t, err) + require.Nil(t, err) // need a codec for marshaling codec := wire.NewCodec() wire.RegisterCrypto(codec) b, err := codec.MarshalBinary(acc) - assert.Nil(t, err) + require.Nil(t, err) acc2 := BaseAccount{} err = codec.UnmarshalBinary(b, &acc2) - assert.Nil(t, err) - assert.Equal(t, acc, acc2) + require.Nil(t, err) + require.Equal(t, acc, acc2) // error on bad bytes acc2 = BaseAccount{} err = codec.UnmarshalBinary(b[:len(b)/2], &acc2) - assert.NotNil(t, err) + require.NotNil(t, err) } diff --git a/x/auth/ante.go b/x/auth/ante.go index c50da0c32..9652b37de 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -5,12 +5,13 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/spf13/viper" ) const ( - deductFeesCost sdk.Gas = 10 - verifyCost = 100 + deductFeesCost sdk.Gas = 10 + memoCostPerByte sdk.Gas = 1 + verifyCost = 100 + maxMemoCharacters = 100 ) // NewAnteHandler returns an AnteHandler that checks @@ -28,41 +29,29 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { return ctx, sdk.ErrInternal("tx must be StdTx").Result(), true } - // Assert that there are signatures. - var sigs = stdTx.GetSignatures() - if len(sigs) == 0 { - return ctx, - sdk.ErrUnauthorized("no signers").Result(), - true + err := validateBasic(stdTx) + if err != nil { + return ctx, err.Result(), true } - msg := tx.GetMsg() + sigs := stdTx.GetSignatures() + signerAddrs := stdTx.GetSigners() + msgs := tx.GetMsgs() - // Assert that number of signatures is correct. - var signerAddrs = msg.GetSigners() - if len(sigs) != len(signerAddrs) { - return ctx, - sdk.ErrUnauthorized("wrong number of signers").Result(), - true - } + // set the gas meter + ctx = ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas)) + + // charge gas for the memo + ctx.GasMeter().ConsumeGas(memoCostPerByte*sdk.Gas(len(stdTx.GetMemo())), "memo") // Get the sign bytes (requires all account & sequence numbers and the fee) - sequences := make([]int64, len(signerAddrs)) - for i := 0; i < len(signerAddrs); i++ { + sequences := make([]int64, len(sigs)) + accNums := make([]int64, len(sigs)) + for i := 0; i < len(sigs); i++ { sequences[i] = sigs[i].Sequence - } - accNums := make([]int64, len(signerAddrs)) - for i := 0; i < len(signerAddrs); i++ { accNums[i] = sigs[i].AccountNumber } fee := stdTx.Fee - chainID := ctx.ChainID() - // XXX: major hack; need to get ChainID - // into the app right away (#565) - if chainID == "" { - chainID = viper.GetString("chain-id") - } - signBytes := StdSignBytes(ctx.ChainID(), accNums, sequences, fee, msg) // Check sig and nonce and collect signer accounts. var signerAccs = make([]Account, len(signerAddrs)) @@ -70,6 +59,7 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { signerAddr, sig := signerAddrs[i], sigs[i] // check signature, return account with incremented nonce + signBytes := StdSignBytes(ctx.ChainID(), accNums[i], sequences[i], fee, msgs, stdTx.GetMemo()) signerAcc, res := processSig( ctx, am, signerAddr, sig, signBytes, @@ -79,16 +69,15 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { } // first sig pays the fees - if i == 0 { - // TODO: min fee - if !fee.Amount.IsZero() { - ctx.GasMeter().ConsumeGas(deductFeesCost, "deductFees") - signerAcc, res = deductFees(signerAcc, fee) - if !res.IsOK() { - return ctx, res, true - } - fck.addCollectedFees(ctx, fee.Amount) + // TODO: Add min fees + // Can this function be moved outside of the loop? + if i == 0 && !fee.Amount.IsZero() { + ctx.GasMeter().ConsumeGas(deductFeesCost, "deductFees") + signerAcc, res = deductFees(signerAcc, fee) + if !res.IsOK() { + return ctx, res, true } + fck.addCollectedFees(ctx, fee.Amount) } // Save the account. @@ -99,20 +88,40 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { // cache the signer accounts in the context ctx = WithSigners(ctx, signerAccs) - // set the gas meter - ctx = ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas)) - // TODO: tx tags (?) return ctx, sdk.Result{}, false // continue... } } +// Validate the transaction based on things that don't depend on the context +func validateBasic(tx StdTx) (err sdk.Error) { + // Assert that there are signatures. + sigs := tx.GetSignatures() + if len(sigs) == 0 { + return sdk.ErrUnauthorized("no signers") + } + + // Assert that number of signatures is correct. + var signerAddrs = tx.GetSigners() + if len(sigs) != len(signerAddrs) { + return sdk.ErrUnauthorized("wrong number of signers") + } + + memo := tx.GetMemo() + if len(memo) > maxMemoCharacters { + return sdk.ErrMemoTooLarge( + fmt.Sprintf("maximum number of characters is %d but received %d characters", + maxMemoCharacters, len(memo))) + } + return nil +} + // verify the signature and increment the sequence. // if the account doesn't have a pubkey, set it. func processSig( ctx sdk.Context, am AccountMapper, - addr sdk.Address, sig StdSignature, signBytes []byte) ( + addr sdk.AccAddress, sig StdSignature, signBytes []byte) ( acc Account, res sdk.Result) { // Get the account. @@ -134,8 +143,11 @@ func processSig( return nil, sdk.ErrInvalidSequence( fmt.Sprintf("Invalid sequence. Got %d, expected %d", sig.Sequence, seq)).Result() } - acc.SetSequence(seq + 1) - + err := acc.SetSequence(seq + 1) + if err != nil { + // Handle w/ #870 + panic(err) + } // If pubkey is not known for account, // set it from the StdSignature. pubKey := acc.GetPubKey() @@ -148,7 +160,7 @@ func processSig( return nil, sdk.ErrInvalidPubKey( fmt.Sprintf("PubKey does not match Signer address %v", addr)).Result() } - err := acc.SetPubKey(pubKey) + err = acc.SetPubKey(pubKey) if err != nil { return nil, sdk.ErrInternal("setting PubKey on signer's account").Result() } @@ -175,7 +187,11 @@ func deductFees(acc Account, fee StdFee) (Account, sdk.Result) { errMsg := fmt.Sprintf("%s < %s", coins, feeAmount) return nil, sdk.ErrInsufficientFunds(errMsg).Result() } - acc.SetCoins(newCoins) + err := acc.SetCoins(newCoins) + if err != nil { + // Handle w/ #870 + panic(err) + } return acc, sdk.Result{} } diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index aff80cb59..c30013d32 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -1,68 +1,108 @@ package auth import ( + "fmt" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/libs/log" sdk "github.com/cosmos/cosmos-sdk/types" wire "github.com/cosmos/cosmos-sdk/wire" ) -func newTestMsg(addrs ...sdk.Address) *sdk.TestMsg { +func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg { return sdk.NewTestMsg(addrs...) } func newStdFee() StdFee { - return NewStdFee(100, - sdk.Coin{"atom", 150}, + return NewStdFee(5000, + sdk.NewCoin("atom", 150), ) } // coins to more than cover the fee func newCoins() sdk.Coins { return sdk.Coins{ - {"atom", 10000000}, + sdk.NewCoin("atom", 10000000), } } // generate a priv key and return it with its address -func privAndAddr() (crypto.PrivKey, sdk.Address) { +func privAndAddr() (crypto.PrivKey, sdk.AccAddress) { priv := crypto.GenPrivKeyEd25519() - addr := priv.PubKey().Address() + addr := sdk.AccAddress(priv.PubKey().Address()) return priv, addr } // run the tx through the anteHandler and ensure its valid func checkValidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx) { _, result, abort := anteHandler(ctx, tx) - assert.False(t, abort) - assert.Equal(t, sdk.ABCICodeOK, result.Code) - assert.True(t, result.IsOK()) + require.False(t, abort) + require.Equal(t, sdk.ABCICodeOK, result.Code) + require.True(t, result.IsOK()) } // run the tx through the anteHandler and ensure it fails with the given code func checkInvalidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, code sdk.CodeType) { + defer func() { + if r := recover(); r != nil { + switch r.(type) { + case sdk.ErrorOutOfGas: + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, code), sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOutOfGas), + fmt.Sprintf("Expected ErrorOutOfGas, got %v", r)) + default: + panic(r) + } + } + }() _, result, abort := anteHandler(ctx, tx) - assert.True(t, abort) - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, code), result.Code) + require.True(t, abort) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, code), result.Code, + fmt.Sprintf("Expected %v, got %v", sdk.ToABCICode(sdk.CodespaceRoot, code), result)) } -func newTestTx(ctx sdk.Context, msg sdk.Msg, privs []crypto.PrivKey, accNums []int64, seqs []int64, fee StdFee) sdk.Tx { - signBytes := StdSignBytes(ctx.ChainID(), accNums, seqs, fee, msg) - return newTestTxWithSignBytes(msg, privs, accNums, seqs, fee, signBytes) -} - -func newTestTxWithSignBytes(msg sdk.Msg, privs []crypto.PrivKey, accNums []int64, seqs []int64, fee StdFee, signBytes []byte) sdk.Tx { +func newTestTx(ctx sdk.Context, msgs []sdk.Msg, privs []crypto.PrivKey, accNums []int64, seqs []int64, fee StdFee) sdk.Tx { sigs := make([]StdSignature, len(privs)) for i, priv := range privs { - sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: priv.Sign(signBytes), AccountNumber: accNums[i], Sequence: seqs[i]} + signBytes := StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "") + sig, err := priv.Sign(signBytes) + if err != nil { + panic(err) + } + sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} } - tx := NewStdTx(msg, fee, sigs) + tx := NewStdTx(msgs, fee, sigs, "") + return tx +} + +func newTestTxWithMemo(ctx sdk.Context, msgs []sdk.Msg, privs []crypto.PrivKey, accNums []int64, seqs []int64, fee StdFee, memo string) sdk.Tx { + sigs := make([]StdSignature, len(privs)) + for i, priv := range privs { + signBytes := StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, memo) + sig, err := priv.Sign(signBytes) + if err != nil { + panic(err) + } + sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} + } + tx := NewStdTx(msgs, fee, sigs, memo) + return tx +} + +// All signers sign over the same StdSignDoc. Should always create invalid signatures +func newTestTxWithSignBytes(msgs []sdk.Msg, privs []crypto.PrivKey, accNums []int64, seqs []int64, fee StdFee, signBytes []byte, memo string) sdk.Tx { + sigs := make([]StdSignature, len(privs)) + for i, priv := range privs { + sig, err := priv.Sign(signBytes) + if err != nil { + panic(err) + } + sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} + } + tx := NewStdTx(msgs, fee, sigs, memo) return tx } @@ -72,33 +112,44 @@ func TestAnteHandlerSigErrors(t *testing.T) { ms, capKey, capKey2 := setupMultiStore() cdc := wire.NewCodec() RegisterBaseAccount(cdc) - mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) + mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) // keys and addresses priv1, addr1 := privAndAddr() priv2, addr2 := privAndAddr() + priv3, addr3 := privAndAddr() // msg and signatures var tx sdk.Tx - msg := newTestMsg(addr1, addr2) + msg1 := newTestMsg(addr1, addr2) + msg2 := newTestMsg(addr1, addr3) fee := newStdFee() + msgs := []sdk.Msg{msg1, msg2} + // test no signatures privs, accNums, seqs := []crypto.PrivKey{}, []int64{}, []int64{} - tx = newTestTx(ctx, msg, privs, accNums, seqs, fee) + tx = newTestTx(ctx, msgs, privs, accNums, seqs, fee) + + // tx.GetSigners returns addresses in correct order: addr1, addr2, addr3 + expectedSigners := []sdk.AccAddress{addr1, addr2, addr3} + stdTx := tx.(StdTx) + require.Equal(t, expectedSigners, stdTx.GetSigners()) + + // Check no signatures fails checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) // test num sigs dont match GetSigners privs, accNums, seqs = []crypto.PrivKey{priv1}, []int64{0}, []int64{0} - tx = newTestTx(ctx, msg, privs, accNums, seqs, fee) + tx = newTestTx(ctx, msgs, privs, accNums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) // test an unrecognized account - privs, accNums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 1}, []int64{0, 0} - tx = newTestTx(ctx, msg, privs, accNums, seqs, fee) + privs, accNums, seqs = []crypto.PrivKey{priv1, priv2, priv3}, []int64{0, 1, 2}, []int64{0, 0, 0} + tx = newTestTx(ctx, msgs, privs, accNums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnknownAddress) // save the first account, but second is still unrecognized @@ -114,10 +165,10 @@ func TestAnteHandlerAccountNumbers(t *testing.T) { ms, capKey, capKey2 := setupMultiStore() cdc := wire.NewCodec() RegisterBaseAccount(cdc) - mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) + mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) // keys and addresses priv1, addr1 := privAndAddr() @@ -136,30 +187,34 @@ func TestAnteHandlerAccountNumbers(t *testing.T) { msg := newTestMsg(addr1) fee := newStdFee() + msgs := []sdk.Msg{msg} + // test good tx from one signer privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} - tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) // new tx from wrong account number seqs = []int64{1} - tx = newTestTx(ctx, msg, privs, []int64{1}, seqs, fee) + tx = newTestTx(ctx, msgs, privs, []int64{1}, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidSequence) // from correct account number seqs = []int64{1} - tx = newTestTx(ctx, msg, privs, []int64{0}, seqs, fee) + tx = newTestTx(ctx, msgs, privs, []int64{0}, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) // new tx with another signer and incorrect account numbers - msg = newTestMsg(addr1, addr2) + msg1 := newTestMsg(addr1, addr2) + msg2 := newTestMsg(addr2, addr1) + msgs = []sdk.Msg{msg1, msg2} privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{1, 0}, []int64{2, 0} - tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidSequence) // correct account numbers privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 1}, []int64{2, 0} - tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) } @@ -169,14 +224,15 @@ func TestAnteHandlerSequences(t *testing.T) { ms, capKey, capKey2 := setupMultiStore() cdc := wire.NewCodec() RegisterBaseAccount(cdc) - mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) + mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) // keys and addresses priv1, addr1 := privAndAddr() priv2, addr2 := privAndAddr() + priv3, addr3 := privAndAddr() // set the accounts acc1 := mapper.NewAccountWithAddress(ctx, addr1) @@ -185,15 +241,20 @@ func TestAnteHandlerSequences(t *testing.T) { acc2 := mapper.NewAccountWithAddress(ctx, addr2) acc2.SetCoins(newCoins()) mapper.SetAccount(ctx, acc2) + acc3 := mapper.NewAccountWithAddress(ctx, addr3) + acc3.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc3) // msg and signatures var tx sdk.Tx msg := newTestMsg(addr1) fee := newStdFee() + msgs := []sdk.Msg{msg} + // test good tx from one signer privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} - tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) // test sending it again fails (replay protection) @@ -201,13 +262,16 @@ func TestAnteHandlerSequences(t *testing.T) { // fix sequence, should pass seqs = []int64{1} - tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) // new tx with another signer and correct sequences - msg = newTestMsg(addr1, addr2) - privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 1}, []int64{2, 0} - tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + msg1 := newTestMsg(addr1, addr2) + msg2 := newTestMsg(addr3, addr1) + msgs = []sdk.Msg{msg1, msg2} + + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2, priv3}, []int64{0, 1, 2}, []int64{2, 0, 0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) // replay fails @@ -215,18 +279,20 @@ func TestAnteHandlerSequences(t *testing.T) { // tx from just second signer with incorrect sequence fails msg = newTestMsg(addr2) + msgs = []sdk.Msg{msg} privs, accnums, seqs = []crypto.PrivKey{priv2}, []int64{1}, []int64{0} - tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidSequence) // fix the sequence and it passes - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv2}, []int64{1}, []int64{1}, fee) + tx = newTestTx(ctx, msgs, []crypto.PrivKey{priv2}, []int64{1}, []int64{1}, fee) checkValidTx(t, anteHandler, ctx, tx) // another tx from both of them that passes msg = newTestMsg(addr1, addr2) + msgs = []sdk.Msg{msg} privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 1}, []int64{3, 2} - tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) } @@ -236,10 +302,10 @@ func TestAnteHandlerFees(t *testing.T) { ms, capKey, capKey2 := setupMultiStore() cdc := wire.NewCodec() RegisterBaseAccount(cdc) - mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) + mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) // keys and addresses priv1, addr1 := privAndAddr() @@ -252,25 +318,119 @@ func TestAnteHandlerFees(t *testing.T) { var tx sdk.Tx msg := newTestMsg(addr1) privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} - fee := NewStdFee(100, - sdk.Coin{"atom", 150}, - ) + fee := newStdFee() + msgs := []sdk.Msg{msg} // signer does not have enough funds to pay the fee - tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInsufficientFunds) - acc1.SetCoins(sdk.Coins{{"atom", 149}}) + acc1.SetCoins(sdk.Coins{sdk.NewCoin("atom", 149)}) mapper.SetAccount(ctx, acc1) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInsufficientFunds) - assert.True(t, feeCollector.GetCollectedFees(ctx).IsEqual(emptyCoins)) + require.True(t, feeCollector.GetCollectedFees(ctx).IsEqual(emptyCoins)) - acc1.SetCoins(sdk.Coins{{"atom", 150}}) + acc1.SetCoins(sdk.Coins{sdk.NewCoin("atom", 150)}) mapper.SetAccount(ctx, acc1) checkValidTx(t, anteHandler, ctx, tx) - assert.True(t, feeCollector.GetCollectedFees(ctx).IsEqual(sdk.Coins{{"atom", 150}})) + require.True(t, feeCollector.GetCollectedFees(ctx).IsEqual(sdk.Coins{sdk.NewCoin("atom", 150)})) +} + +// Test logic around memo gas consumption. +func TestAnteHandlerMemoGas(t *testing.T) { + // setup + ms, capKey, capKey2 := setupMultiStore() + cdc := wire.NewCodec() + RegisterBaseAccount(cdc) + mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + + // keys and addresses + priv1, addr1 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + mapper.SetAccount(ctx, acc1) + + // msg and signatures + var tx sdk.Tx + msg := newTestMsg(addr1) + privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} + fee := NewStdFee(0, sdk.NewCoin("atom", 0)) + + // tx does not have enough gas + tx = newTestTx(ctx, []sdk.Msg{msg}, privs, accnums, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeOutOfGas) + + // tx with memo doesn't have enough gas + fee = NewStdFee(801, sdk.NewCoin("atom", 0)) + tx = newTestTxWithMemo(ctx, []sdk.Msg{msg}, privs, accnums, seqs, fee, "abcininasidniandsinasindiansdiansdinaisndiasndiadninsd") + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeOutOfGas) + + // memo too large + fee = NewStdFee(2001, sdk.NewCoin("atom", 0)) + tx = newTestTxWithMemo(ctx, []sdk.Msg{msg}, privs, accnums, seqs, fee, "abcininasidniandsinasindiansdiansdinaisndiasndiadninsdabcininasidniandsinasindiansdiansdinaisndiasndiadninsdabcininasidniandsinasindiansdiansdinaisndiasndiadninsd") + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeMemoTooLarge) + + // tx with memo has enough gas + fee = NewStdFee(1100, sdk.NewCoin("atom", 0)) + tx = newTestTxWithMemo(ctx, []sdk.Msg{msg}, privs, accnums, seqs, fee, "abcininasidniandsinasindiansdiansdinaisndiasndiadninsd") + checkValidTx(t, anteHandler, ctx, tx) +} + +func TestAnteHandlerMultiSigner(t *testing.T) { + // setup + ms, capKey, capKey2 := setupMultiStore() + cdc := wire.NewCodec() + RegisterBaseAccount(cdc) + mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + + // keys and addresses + priv1, addr1 := privAndAddr() + priv2, addr2 := privAndAddr() + priv3, addr3 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc1) + acc2 := mapper.NewAccountWithAddress(ctx, addr2) + acc2.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc2) + acc3 := mapper.NewAccountWithAddress(ctx, addr3) + acc3.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc3) + + // set up msgs and fee + var tx sdk.Tx + msg1 := newTestMsg(addr1, addr2) + msg2 := newTestMsg(addr3, addr1) + msg3 := newTestMsg(addr2, addr3) + msgs := []sdk.Msg{msg1, msg2, msg3} + fee := newStdFee() + + // signers in order + privs, accnums, seqs := []crypto.PrivKey{priv1, priv2, priv3}, []int64{0, 1, 2}, []int64{0, 0, 0} + tx = newTestTxWithMemo(ctx, msgs, privs, accnums, seqs, fee, "Check signers are in expected order and different account numbers works") + + checkValidTx(t, anteHandler, ctx, tx) + + // change sequence numbers + tx = newTestTx(ctx, []sdk.Msg{msg1}, []crypto.PrivKey{priv1, priv2}, []int64{0, 1}, []int64{1, 1}, fee) + checkValidTx(t, anteHandler, ctx, tx) + tx = newTestTx(ctx, []sdk.Msg{msg2}, []crypto.PrivKey{priv3, priv1}, []int64{2, 0}, []int64{1, 2}, fee) + checkValidTx(t, anteHandler, ctx, tx) + + // expected seqs = [3, 2, 2] + tx = newTestTxWithMemo(ctx, msgs, privs, accnums, []int64{3, 2, 2}, fee, "Check signers are in expected order and different account numbers and sequence numbers works") + checkValidTx(t, anteHandler, ctx, tx) } func TestAnteHandlerBadSignBytes(t *testing.T) { @@ -278,10 +438,10 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { ms, capKey, capKey2 := setupMultiStore() cdc := wire.NewCodec() RegisterBaseAccount(cdc) - mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) + mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) // keys and addresses priv1, addr1 := privAndAddr() @@ -297,15 +457,16 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { var tx sdk.Tx msg := newTestMsg(addr1) + msgs := []sdk.Msg{msg} fee := newStdFee() fee2 := newStdFee() fee2.Gas += 100 fee3 := newStdFee() - fee3.Amount[0].Amount += 100 + fee3.Amount[0].Amount = fee3.Amount[0].Amount.AddRaw(100) // test good tx and signBytes privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} - tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) chainID := ctx.ChainID() @@ -314,39 +475,41 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { cases := []struct { chainID string - accnums []int64 - seqs []int64 + accnum int64 + seq int64 fee StdFee - msg sdk.Msg + msgs []sdk.Msg code sdk.CodeType }{ - {chainID2, []int64{0}, []int64{1}, fee, msg, codeUnauth}, // test wrong chain_id - {chainID, []int64{0}, []int64{2}, fee, msg, codeUnauth}, // test wrong seqs - {chainID, []int64{0}, []int64{1, 2}, fee, msg, codeUnauth}, // test wrong seqs - {chainID, []int64{1}, []int64{1}, fee, msg, codeUnauth}, // test wrong accnum - {chainID, []int64{0}, []int64{1}, fee, newTestMsg(addr2), codeUnauth}, // test wrong msg - {chainID, []int64{0}, []int64{1}, fee2, msg, codeUnauth}, // test wrong fee - {chainID, []int64{0}, []int64{1}, fee3, msg, codeUnauth}, // test wrong fee + {chainID2, 0, 1, fee, msgs, codeUnauth}, // test wrong chain_id + {chainID, 0, 2, fee, msgs, codeUnauth}, // test wrong seqs + {chainID, 1, 1, fee, msgs, codeUnauth}, // test wrong accnum + {chainID, 0, 1, fee, []sdk.Msg{newTestMsg(addr2)}, codeUnauth}, // test wrong msg + {chainID, 0, 1, fee2, msgs, codeUnauth}, // test wrong fee + {chainID, 0, 1, fee3, msgs, codeUnauth}, // test wrong fee } privs, seqs = []crypto.PrivKey{priv1}, []int64{1} for _, cs := range cases { tx := newTestTxWithSignBytes( - msg, privs, accnums, seqs, fee, - StdSignBytes(cs.chainID, cs.accnums, cs.seqs, cs.fee, cs.msg), + + msgs, privs, accnums, seqs, fee, + StdSignBytes(cs.chainID, cs.accnum, cs.seq, cs.fee, cs.msgs, ""), + "", ) checkInvalidTx(t, anteHandler, ctx, tx, cs.code) } // test wrong signer if public key exist privs, accnums, seqs = []crypto.PrivKey{priv2}, []int64{0}, []int64{1} - tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) // test wrong signer if public doesn't exist msg = newTestMsg(addr2) + msgs = []sdk.Msg{msg} privs, accnums, seqs = []crypto.PrivKey{priv1}, []int64{1}, []int64{0} - tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidPubKey) } @@ -356,10 +519,10 @@ func TestAnteHandlerSetPubKey(t *testing.T) { ms, capKey, capKey2 := setupMultiStore() cdc := wire.NewCodec() RegisterBaseAccount(cdc) - mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) + mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) // keys and addresses priv1, addr1 := privAndAddr() @@ -377,9 +540,10 @@ func TestAnteHandlerSetPubKey(t *testing.T) { // test good tx and set public key msg := newTestMsg(addr1) + msgs := []sdk.Msg{msg} privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} fee := newStdFee() - tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) acc1 = mapper.GetAccount(ctx, addr1) @@ -387,18 +551,19 @@ func TestAnteHandlerSetPubKey(t *testing.T) { // test public key not found msg = newTestMsg(addr2) - tx = newTestTx(ctx, msg, privs, []int64{1}, seqs, fee) + msgs = []sdk.Msg{msg} + tx = newTestTx(ctx, msgs, privs, []int64{1}, seqs, fee) sigs := tx.(StdTx).GetSignatures() sigs[0].PubKey = nil checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidPubKey) acc2 = mapper.GetAccount(ctx, addr2) - assert.Nil(t, acc2.GetPubKey()) + require.Nil(t, acc2.GetPubKey()) // test invalid signature and public key - tx = newTestTx(ctx, msg, privs, []int64{1}, seqs, fee) + tx = newTestTx(ctx, msgs, privs, []int64{1}, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidPubKey) acc2 = mapper.GetAccount(ctx, addr2) - assert.Nil(t, acc2.GetPubKey()) + require.Nil(t, acc2.GetPubKey()) } diff --git a/x/auth/client/cli/account.go b/x/auth/client/cli/account.go index a3265a78c..5fc8545b9 100644 --- a/x/auth/client/cli/account.go +++ b/x/auth/client/cli/account.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "github.com/spf13/cobra" @@ -40,21 +41,21 @@ func GetAccountCmd(storeName string, cdc *wire.Codec, decoder auth.AccountDecode // find the key to look up the account addr := args[0] - key, err := sdk.GetAccAddressBech32(addr) + key, err := sdk.AccAddressFromBech32(addr) if err != nil { return err } // perform query ctx := context.NewCoreContextFromViper() - res, err := ctx.Query(auth.AddressStoreKey(key), storeName) + res, err := ctx.QueryStore(auth.AddressStoreKey(key), storeName) if err != nil { return err } // Check if account was found if res == nil { - return sdk.ErrUnknownAddress("No account with address " + addr + + return errors.New("No account with address " + addr + " was found in the state.\nAre you sure there has been a transaction involving it?") } diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index 9ccbe8e14..5cdc2ee25 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -27,17 +27,17 @@ func QueryAccountRequestHandlerFn(storeName string, cdc *wire.Codec, decoder aut vars := mux.Vars(r) bech32addr := vars["address"] - addr, err := sdk.GetAccAddressBech32(bech32addr) + addr, err := sdk.AccAddressFromBech32(bech32addr) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - res, err := ctx.Query(auth.AddressStoreKey(addr), storeName) + res, err := ctx.QueryStore(auth.AddressStoreKey(addr), storeName) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("Could't query account. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("couldn't query account. Error: %s", err.Error()))) return } @@ -51,7 +51,7 @@ func QueryAccountRequestHandlerFn(storeName string, cdc *wire.Codec, decoder aut account, err := decoder(res) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("Could't parse query result. Result: %s. Error: %s", res, err.Error()))) + w.Write([]byte(fmt.Sprintf("couldn't parse query result. Result: %s. Error: %s", res, err.Error()))) return } @@ -59,7 +59,7 @@ func QueryAccountRequestHandlerFn(storeName string, cdc *wire.Codec, decoder aut output, err := cdc.MarshalJSON(account) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("Could't marshall query result. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("couldn't marshall query result. Error: %s", err.Error()))) return } diff --git a/x/auth/context_test.go b/x/auth/context_test.go index a93de44d0..b58547328 100644 --- a/x/auth/context_test.go +++ b/x/auth/context_test.go @@ -3,17 +3,17 @@ package auth import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" sdk "github.com/cosmos/cosmos-sdk/types" ) func TestContextWithSigners(t *testing.T) { ms, _, _ := setupMultiStore() - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) _, _, addr1 := keyPubAddr() _, _, addr2 := keyPubAddr() @@ -24,17 +24,17 @@ func TestContextWithSigners(t *testing.T) { // new ctx has no signers signers := GetSigners(ctx) - assert.Equal(t, 0, len(signers)) + require.Equal(t, 0, len(signers)) ctx2 := WithSigners(ctx, []Account{&acc1, &acc2}) // original context is unchanged signers = GetSigners(ctx) - assert.Equal(t, 0, len(signers)) + require.Equal(t, 0, len(signers)) // new context has signers signers = GetSigners(ctx2) - assert.Equal(t, 2, len(signers)) - assert.Equal(t, acc1, *(signers[0].(*BaseAccount))) - assert.Equal(t, acc2, *(signers[1].(*BaseAccount))) + require.Equal(t, 2, len(signers)) + require.Equal(t, acc1, *(signers[0].(*BaseAccount))) + require.Equal(t, acc2, *(signers[1].(*BaseAccount))) } diff --git a/x/auth/feekeeper_test.go b/x/auth/feekeeper_test.go index 2f1ffc59b..a53839a8b 100644 --- a/x/auth/feekeeper_test.go +++ b/x/auth/feekeeper_test.go @@ -3,10 +3,10 @@ package auth import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" sdk "github.com/cosmos/cosmos-sdk/types" wire "github.com/cosmos/cosmos-sdk/wire" @@ -14,8 +14,8 @@ import ( var ( emptyCoins = sdk.Coins{} - oneCoin = sdk.Coins{{"foocoin", 1}} - twoCoins = sdk.Coins{{"foocoin", 2}} + oneCoin = sdk.Coins{sdk.NewCoin("foocoin", 1)} + twoCoins = sdk.Coins{sdk.NewCoin("foocoin", 2)} ) func TestFeeCollectionKeeperGetSet(t *testing.T) { @@ -23,18 +23,18 @@ func TestFeeCollectionKeeperGetSet(t *testing.T) { cdc := wire.NewCodec() // make context and keeper - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) fck := NewFeeCollectionKeeper(cdc, capKey2) // no coins initially currFees := fck.GetCollectedFees(ctx) - assert.True(t, currFees.IsEqual(emptyCoins)) + require.True(t, currFees.IsEqual(emptyCoins)) // set feeCollection to oneCoin fck.setCollectedFees(ctx, oneCoin) // check that it is equal to oneCoin - assert.True(t, fck.GetCollectedFees(ctx).IsEqual(oneCoin)) + require.True(t, fck.GetCollectedFees(ctx).IsEqual(oneCoin)) } func TestFeeCollectionKeeperAdd(t *testing.T) { @@ -42,19 +42,19 @@ func TestFeeCollectionKeeperAdd(t *testing.T) { cdc := wire.NewCodec() // make context and keeper - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) fck := NewFeeCollectionKeeper(cdc, capKey2) // no coins initially - assert.True(t, fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) + require.True(t, fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) // add oneCoin and check that pool is now oneCoin fck.addCollectedFees(ctx, oneCoin) - assert.True(t, fck.GetCollectedFees(ctx).IsEqual(oneCoin)) + require.True(t, fck.GetCollectedFees(ctx).IsEqual(oneCoin)) // add oneCoin again and check that pool is now twoCoins fck.addCollectedFees(ctx, oneCoin) - assert.True(t, fck.GetCollectedFees(ctx).IsEqual(twoCoins)) + require.True(t, fck.GetCollectedFees(ctx).IsEqual(twoCoins)) } func TestFeeCollectionKeeperClear(t *testing.T) { @@ -62,14 +62,14 @@ func TestFeeCollectionKeeperClear(t *testing.T) { cdc := wire.NewCodec() // make context and keeper - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) fck := NewFeeCollectionKeeper(cdc, capKey2) // set coins initially fck.setCollectedFees(ctx, twoCoins) - assert.True(t, fck.GetCollectedFees(ctx).IsEqual(twoCoins)) + require.True(t, fck.GetCollectedFees(ctx).IsEqual(twoCoins)) // clear fees and see that pool is now empty fck.ClearCollectedFees(ctx) - assert.True(t, fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) + require.True(t, fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) } diff --git a/x/auth/handler.go b/x/auth/handler.go deleted file mode 100644 index 764c6f7a2..000000000 --- a/x/auth/handler.go +++ /dev/null @@ -1,34 +0,0 @@ -package auth - -import ( - "reflect" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// NewHandler returns a handler for "baseaccount" type messages. -func NewHandler(am AccountMapper) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - switch msg := msg.(type) { - case MsgChangeKey: - return handleMsgChangeKey(ctx, am, msg) - default: - errMsg := "Unrecognized baseaccount Msg type: " + reflect.TypeOf(msg).Name() - return sdk.ErrUnknownRequest(errMsg).Result() - } - } -} - -// Handle MsgChangeKey -// Should be very expensive, because once this happens, an account is un-prunable -func handleMsgChangeKey(ctx sdk.Context, am AccountMapper, msg MsgChangeKey) sdk.Result { - - err := am.SetPubKey(ctx, msg.Address, msg.NewPubKey) - if err != nil { - return err.Result() - } - - return sdk.Result{ - Tags: sdk.NewTags("action", []byte("changePubkey"), "address", msg.Address.Bytes(), "pubkey", msg.NewPubKey.Bytes()), - } -} diff --git a/x/auth/mapper.go b/x/auth/mapper.go index b4364f768..244527af3 100644 --- a/x/auth/mapper.go +++ b/x/auth/mapper.go @@ -1,12 +1,9 @@ package auth import ( - "fmt" - "reflect" - sdk "github.com/cosmos/cosmos-sdk/types" wire "github.com/cosmos/cosmos-sdk/wire" - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/crypto" ) var globalAccountNumberKey = []byte("globalAccountNumber") @@ -18,8 +15,8 @@ type AccountMapper struct { // The (unexposed) key used to access the store from the Context. key sdk.StoreKey - // The prototypical Account concrete type. - proto Account + // The prototypical Account constructor. + proto func() Account // The wire codec for binary encoding/decoding of accounts. cdc *wire.Codec @@ -28,7 +25,7 @@ type AccountMapper struct { // NewAccountMapper returns a new sdk.AccountMapper that // uses go-amino to (binary) encode and decode concrete sdk.Accounts. // nolint -func NewAccountMapper(cdc *wire.Codec, key sdk.StoreKey, proto Account) AccountMapper { +func NewAccountMapper(cdc *wire.Codec, key sdk.StoreKey, proto func() Account) AccountMapper { return AccountMapper{ key: key, proto: proto, @@ -37,26 +34,38 @@ func NewAccountMapper(cdc *wire.Codec, key sdk.StoreKey, proto Account) AccountM } // Implaements sdk.AccountMapper. -func (am AccountMapper) NewAccountWithAddress(ctx sdk.Context, addr sdk.Address) Account { - acc := am.clonePrototype() - acc.SetAddress(addr) - acc.SetAccountNumber(am.GetNextAccountNumber(ctx)) +func (am AccountMapper) NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) Account { + acc := am.proto() + err := acc.SetAddress(addr) + if err != nil { + // Handle w/ #870 + panic(err) + } + err = acc.SetAccountNumber(am.GetNextAccountNumber(ctx)) + if err != nil { + // Handle w/ #870 + panic(err) + } return acc } // New Account func (am AccountMapper) NewAccount(ctx sdk.Context, acc Account) Account { - acc.SetAccountNumber(am.GetNextAccountNumber(ctx)) + err := acc.SetAccountNumber(am.GetNextAccountNumber(ctx)) + if err != nil { + // TODO: Handle with #870 + panic(err) + } return acc } // Turn an address to key used to get it from the account store -func AddressStoreKey(addr sdk.Address) []byte { +func AddressStoreKey(addr sdk.AccAddress) []byte { return append([]byte("account:"), addr.Bytes()...) } // Implements sdk.AccountMapper. -func (am AccountMapper) GetAccount(ctx sdk.Context, addr sdk.Address) Account { +func (am AccountMapper) GetAccount(ctx sdk.Context, addr sdk.AccAddress) Account { store := ctx.KVStore(am.key) bz := store.Get(AddressStoreKey(addr)) if bz == nil { @@ -92,7 +101,7 @@ func (am AccountMapper) IterateAccounts(ctx sdk.Context, process func(Account) ( } // Returns the PubKey of the account at address -func (am AccountMapper) GetPubKey(ctx sdk.Context, addr sdk.Address) (crypto.PubKey, sdk.Error) { +func (am AccountMapper) GetPubKey(ctx sdk.Context, addr sdk.AccAddress) (crypto.PubKey, sdk.Error) { acc := am.GetAccount(ctx, addr) if acc == nil { return nil, sdk.ErrUnknownAddress(addr.String()) @@ -100,19 +109,8 @@ func (am AccountMapper) GetPubKey(ctx sdk.Context, addr sdk.Address) (crypto.Pub return acc.GetPubKey(), nil } -// Sets the PubKey of the account at address -func (am AccountMapper) SetPubKey(ctx sdk.Context, addr sdk.Address, newPubKey crypto.PubKey) sdk.Error { - acc := am.GetAccount(ctx, addr) - if acc == nil { - return sdk.ErrUnknownAddress(addr.String()) - } - acc.SetPubKey(newPubKey) - am.SetAccount(ctx, acc) - return nil -} - // Returns the Sequence of the account at address -func (am AccountMapper) GetSequence(ctx sdk.Context, addr sdk.Address) (int64, sdk.Error) { +func (am AccountMapper) GetSequence(ctx sdk.Context, addr sdk.AccAddress) (int64, sdk.Error) { acc := am.GetAccount(ctx, addr) if acc == nil { return 0, sdk.ErrUnknownAddress(addr.String()) @@ -120,12 +118,16 @@ func (am AccountMapper) GetSequence(ctx sdk.Context, addr sdk.Address) (int64, s return acc.GetSequence(), nil } -func (am AccountMapper) setSequence(ctx sdk.Context, addr sdk.Address, newSequence int64) sdk.Error { +func (am AccountMapper) setSequence(ctx sdk.Context, addr sdk.AccAddress, newSequence int64) sdk.Error { acc := am.GetAccount(ctx, addr) if acc == nil { return sdk.ErrUnknownAddress(addr.String()) } - acc.SetSequence(newSequence) + err := acc.SetSequence(newSequence) + if err != nil { + // Handle w/ #870 + panic(err) + } am.SetAccount(ctx, acc) return nil } @@ -153,30 +155,6 @@ func (am AccountMapper) GetNextAccountNumber(ctx sdk.Context) int64 { //---------------------------------------- // misc. -// Creates a new struct (or pointer to struct) from am.proto. -func (am AccountMapper) clonePrototype() Account { - protoRt := reflect.TypeOf(am.proto) - if protoRt.Kind() == reflect.Ptr { - protoCrt := protoRt.Elem() - if protoCrt.Kind() != reflect.Struct { - panic("accountMapper requires a struct proto sdk.Account, or a pointer to one") - } - protoRv := reflect.New(protoCrt) - clone, ok := protoRv.Interface().(Account) - if !ok { - panic(fmt.Sprintf("accountMapper requires a proto sdk.Account, but %v doesn't implement sdk.Account", protoRt)) - } - return clone - } - - protoRv := reflect.New(protoRt).Elem() - clone, ok := protoRv.Interface().(Account) - if !ok { - panic(fmt.Sprintf("accountMapper requires a proto sdk.Account, but %v doesn't implement sdk.Account", protoRt)) - } - return clone -} - func (am AccountMapper) encodeAccount(acc Account) []byte { bz, err := am.cdc.MarshalBinaryBare(acc) if err != nil { diff --git a/x/auth/mapper_test.go b/x/auth/mapper_test.go index 7f6397069..679ee12cd 100644 --- a/x/auth/mapper_test.go +++ b/x/auth/mapper_test.go @@ -3,11 +3,11 @@ package auth import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" @@ -31,24 +31,24 @@ func TestAccountMapperGetSet(t *testing.T) { RegisterBaseAccount(cdc) // make context and mapper - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) - mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) - addr := sdk.Address([]byte("some-address")) + addr := sdk.AccAddress([]byte("some-address")) // no account before its created acc := mapper.GetAccount(ctx, addr) - assert.Nil(t, acc) + require.Nil(t, acc) // create account and check default values acc = mapper.NewAccountWithAddress(ctx, addr) - assert.NotNil(t, acc) - assert.Equal(t, addr, acc.GetAddress()) - assert.EqualValues(t, nil, acc.GetPubKey()) - assert.EqualValues(t, 0, acc.GetSequence()) + require.NotNil(t, acc) + require.Equal(t, addr, acc.GetAddress()) + require.EqualValues(t, nil, acc.GetPubKey()) + require.EqualValues(t, 0, acc.GetSequence()) // NewAccount doesn't call Set, so it's still nil - assert.Nil(t, mapper.GetAccount(ctx, addr)) + require.Nil(t, mapper.GetAccount(ctx, addr)) // set some values on the account and save it newSequence := int64(20) @@ -57,6 +57,6 @@ func TestAccountMapperGetSet(t *testing.T) { // check the new values acc = mapper.GetAccount(ctx, addr) - assert.NotNil(t, acc) - assert.Equal(t, newSequence, acc.GetSequence()) + require.NotNil(t, acc) + require.Equal(t, newSequence, acc.GetSequence()) } diff --git a/x/auth/mock/app.go b/x/auth/mock/app.go deleted file mode 100644 index 953008807..000000000 --- a/x/auth/mock/app.go +++ /dev/null @@ -1,88 +0,0 @@ -package mock - -import ( - "testing" - - "os" - - "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" - - bam "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" - "github.com/cosmos/cosmos-sdk/x/auth" -) - -// Extended ABCI application -type App struct { - *bam.BaseApp - Cdc *wire.Codec // public since the codec is passed into the module anyways. - KeyMain *sdk.KVStoreKey - KeyAccount *sdk.KVStoreKey - - // TODO: Abstract this out from not needing to be auth specifically - AccountMapper auth.AccountMapper - FeeCollectionKeeper auth.FeeCollectionKeeper - - GenesisAccounts []auth.Account -} - -// partially construct a new app on the memstore for module and genesis testing -func NewApp() *App { - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") - db := dbm.NewMemDB() - - // create the cdc with some standard codecs - cdc := wire.NewCodec() - sdk.RegisterWire(cdc) - wire.RegisterCrypto(cdc) - auth.RegisterWire(cdc) - - // create your application object - app := &App{ - BaseApp: bam.NewBaseApp("mock", cdc, logger, db), - Cdc: cdc, - KeyMain: sdk.NewKVStoreKey("main"), - KeyAccount: sdk.NewKVStoreKey("acc"), - } - - // define the accountMapper - app.AccountMapper = auth.NewAccountMapper( - app.Cdc, - app.KeyAccount, // target store - &auth.BaseAccount{}, // prototype - ) - - // initialize the app, the chainers and blockers can be overwritten before calling complete setup - app.SetInitChainer(app.InitChainer) - - app.SetAnteHandler(auth.NewAnteHandler(app.AccountMapper, app.FeeCollectionKeeper)) - - return app -} - -// complete the application setup after the routes have been registered -func (app *App) CompleteSetup(t *testing.T, newKeys []*sdk.KVStoreKey) { - - newKeys = append(newKeys, app.KeyMain) - newKeys = append(newKeys, app.KeyAccount) - app.MountStoresIAVL(newKeys...) - err := app.LoadLatestVersion(app.KeyMain) - require.NoError(t, err) -} - -// custom logic for initialization -func (app *App) InitChainer(ctx sdk.Context, _ abci.RequestInitChain) abci.ResponseInitChain { - - // load the accounts - for _, genacc := range app.GenesisAccounts { - acc := app.AccountMapper.NewAccountWithAddress(ctx, genacc.GetAddress()) - acc.SetCoins(genacc.GetCoins()) - app.AccountMapper.SetAccount(ctx, acc) - } - - return abci.ResponseInitChain{} -} diff --git a/x/auth/mock/auth_app_test.go b/x/auth/mock/auth_app_test.go deleted file mode 100644 index bb4d7007b..000000000 --- a/x/auth/mock/auth_app_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package mock - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/bank" - - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" -) - -// test auth module messages - -var ( - priv1 = crypto.GenPrivKeyEd25519() - addr1 = priv1.PubKey().Address() - priv2 = crypto.GenPrivKeyEd25519() - addr2 = priv2.PubKey().Address() - - coins = sdk.Coins{{"foocoin", 10}} - sendMsg1 = bank.MsgSend{ - Inputs: []bank.Input{bank.NewInput(addr1, coins)}, - Outputs: []bank.Output{bank.NewOutput(addr2, coins)}, - } -) - -// initialize the mock application for this module -func getMockApp(t *testing.T) *App { - mapp := NewApp() - - coinKeeper := bank.NewKeeper(mapp.AccountMapper) - mapp.Router().AddRoute("bank", bank.NewHandler(coinKeeper)) - mapp.Router().AddRoute("auth", auth.NewHandler(mapp.AccountMapper)) - - mapp.CompleteSetup(t, []*sdk.KVStoreKey{}) - return mapp -} - -func TestMsgChangePubKey(t *testing.T) { - mapp := getMockApp(t) - - // Construct some genesis bytes to reflect basecoin/types/AppAccount - // Give 77 foocoin to the first key - coins := sdk.Coins{{"foocoin", 77}} - acc1 := &auth.BaseAccount{ - Address: addr1, - Coins: coins, - } - accs := []auth.Account{acc1} - - // Construct genesis state - SetGenesis(mapp, accs) - - // A checkTx context (true) - ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) - res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) - assert.Equal(t, acc1, res1.(*auth.BaseAccount)) - - // Run a CheckDeliver - SignCheckDeliver(t, mapp.BaseApp, sendMsg1, []int64{0}, []int64{0}, true, priv1) - - // Check balances - CheckBalance(t, mapp, addr1, sdk.Coins{{"foocoin", 67}}) - CheckBalance(t, mapp, addr2, sdk.Coins{{"foocoin", 10}}) - - changePubKeyMsg := auth.MsgChangeKey{ - Address: addr1, - NewPubKey: priv2.PubKey(), - } - - mapp.BeginBlock(abci.RequestBeginBlock{}) - ctxDeliver := mapp.BaseApp.NewContext(false, abci.Header{}) - acc2 := mapp.AccountMapper.GetAccount(ctxDeliver, addr1) - - // send a MsgChangePubKey - SignCheckDeliver(t, mapp.BaseApp, changePubKeyMsg, []int64{0}, []int64{1}, true, priv1) - acc2 = mapp.AccountMapper.GetAccount(ctxDeliver, addr1) - - assert.True(t, priv2.PubKey().Equals(acc2.GetPubKey())) - - // signing a SendMsg with the old privKey should be an auth error - mapp.BeginBlock(abci.RequestBeginBlock{}) - tx := GenTx(sendMsg1, []int64{0}, []int64{2}, priv1) - res := mapp.Deliver(tx) - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) - - // resigning the tx with the new correct priv key should work - SignCheckDeliver(t, mapp.BaseApp, sendMsg1, []int64{0}, []int64{2}, true, priv2) - - // Check balances - CheckBalance(t, mapp, addr1, sdk.Coins{{"foocoin", 57}}) - CheckBalance(t, mapp, addr2, sdk.Coins{{"foocoin", 20}}) -} diff --git a/x/auth/mock/simulate_block.go b/x/auth/mock/simulate_block.go deleted file mode 100644 index 7a77e0f09..000000000 --- a/x/auth/mock/simulate_block.go +++ /dev/null @@ -1,88 +0,0 @@ -package mock - -import ( - "testing" - - "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - crypto "github.com/tendermint/go-crypto" - - abci "github.com/tendermint/abci/types" -) - -var chainID = "" // TODO - -// set the mock app genesis -func SetGenesis(app *App, accs []auth.Account) { - - // pass the accounts in via the application (lazy) instead of through RequestInitChain - app.GenesisAccounts = accs - - app.InitChain(abci.RequestInitChain{}) - app.Commit() -} - -// check an account balance -func CheckBalance(t *testing.T, app *App, addr sdk.Address, exp sdk.Coins) { - ctxCheck := app.BaseApp.NewContext(true, abci.Header{}) - res := app.AccountMapper.GetAccount(ctxCheck, addr) - assert.Equal(t, exp, res.GetCoins()) -} - -// generate a signed transaction -func GenTx(msg sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKeyEd25519) auth.StdTx { - - // make the transaction free - fee := auth.StdFee{ - sdk.Coins{{"foocoin", 0}}, - 100000, - } - - sigs := make([]auth.StdSignature, len(priv)) - for i, p := range priv { - sigs[i] = auth.StdSignature{ - PubKey: p.PubKey(), - Signature: p.Sign(auth.StdSignBytes(chainID, accnums, seq, fee, msg)), - AccountNumber: accnums[i], - Sequence: seq[i], - } - } - return auth.NewStdTx(msg, fee, sigs) -} - -// check a transaction result -func SignCheck(t *testing.T, app *baseapp.BaseApp, msg sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKeyEd25519) sdk.Result { - tx := GenTx(msg, accnums, seq, priv...) - res := app.Check(tx) - return res -} - -// simulate a block -func SignCheckDeliver(t *testing.T, app *baseapp.BaseApp, msg sdk.Msg, accnums []int64, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) { - - // Sign the tx - tx := GenTx(msg, accnums, seq, priv...) - - // Run a Check - res := app.Check(tx) - if expPass { - require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) - } else { - require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) - } - - // Simulate a Block - app.BeginBlock(abci.RequestBeginBlock{}) - res = app.Deliver(tx) - if expPass { - require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) - } else { - require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) - } - app.EndBlock(abci.RequestEndBlock{}) - - app.Commit() -} diff --git a/x/auth/msgs.go b/x/auth/msgs.go deleted file mode 100644 index 3eb5cc8ba..000000000 --- a/x/auth/msgs.go +++ /dev/null @@ -1,41 +0,0 @@ -package auth - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - crypto "github.com/tendermint/go-crypto" -) - -// MsgChangeKey - high level transaction of the auth module -type MsgChangeKey struct { - Address sdk.Address `json:"address"` - NewPubKey crypto.PubKey `json:"public_key"` -} - -var _ sdk.Msg = MsgChangeKey{} - -// NewMsgChangeKey - msg to claim an account and set the PubKey -func NewMsgChangeKey(addr sdk.Address, pubkey crypto.PubKey) MsgChangeKey { - return MsgChangeKey{Address: addr, NewPubKey: pubkey} -} - -// Implements Msg. -func (msg MsgChangeKey) Type() string { return "auth" } - -// Implements Msg. -func (msg MsgChangeKey) ValidateBasic() sdk.Error { - return nil -} - -// Implements Msg. -func (msg MsgChangeKey) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(msg) // XXX: ensure some canonical form - if err != nil { - panic(err) - } - return b -} - -// Implements Msg. -func (msg MsgChangeKey) GetSigners() []sdk.Address { - return []sdk.Address{msg.Address} -} diff --git a/x/auth/msgs_test.go b/x/auth/msgs_test.go deleted file mode 100644 index 30c98b073..000000000 --- a/x/auth/msgs_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package auth - -import ( - "testing" - - "github.com/stretchr/testify/assert" - crypto "github.com/tendermint/go-crypto" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestNewMsgChangeKey(t *testing.T) {} - -func TestMsgChangeKeyType(t *testing.T) { - addr1 := sdk.Address([]byte("input")) - newPubKey := crypto.GenPrivKeyEd25519().PubKey() - - var msg = MsgChangeKey{ - Address: addr1, - NewPubKey: newPubKey, - } - - assert.Equal(t, msg.Type(), "auth") -} - -func TestMsgChangeKeyValidation(t *testing.T) { - - addr1 := sdk.Address([]byte("input")) - - // emptyPubKey := crypto.PubKeyEd25519{} - // var msg = MsgChangeKey{ - // Address: addr1, - // NewPubKey: emptyPubKey, - // } - - // // fmt.Println(msg.NewPubKey.Empty()) - // fmt.Println(msg.NewPubKey.Bytes()) - - // assert.NotNil(t, msg.ValidateBasic()) - - newPubKey := crypto.GenPrivKeyEd25519().PubKey() - msg := MsgChangeKey{ - Address: addr1, - NewPubKey: newPubKey, - } - assert.Nil(t, msg.ValidateBasic()) -} diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index 5c43a3717..316ff5e95 100644 --- a/x/auth/stdtx.go +++ b/x/auth/stdtx.go @@ -4,7 +4,7 @@ import ( "encoding/json" sdk "github.com/cosmos/cosmos-sdk/types" - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/crypto" ) var _ sdk.Tx = (*StdTx)(nil) @@ -12,23 +12,48 @@ var _ sdk.Tx = (*StdTx)(nil) // StdTx is a standard way to wrap a Msg with Fee and Signatures. // NOTE: the first signature is the FeePayer (Signatures must not be nil). type StdTx struct { - Msg sdk.Msg `json:"msg"` + Msgs []sdk.Msg `json:"msg"` Fee StdFee `json:"fee"` Signatures []StdSignature `json:"signatures"` + Memo string `json:"memo"` } -func NewStdTx(msg sdk.Msg, fee StdFee, sigs []StdSignature) StdTx { +func NewStdTx(msgs []sdk.Msg, fee StdFee, sigs []StdSignature, memo string) StdTx { return StdTx{ - Msg: msg, + Msgs: msgs, Fee: fee, Signatures: sigs, + Memo: memo, } } //nolint -func (tx StdTx) GetMsg() sdk.Msg { return tx.Msg } +func (tx StdTx) GetMsgs() []sdk.Msg { return tx.Msgs } + +// GetSigners returns the addresses that must sign the transaction. +// Addresses are returned in a determistic order. +// They are accumulated from the GetSigners method for each Msg +// in the order they appear in tx.GetMsgs(). +// Duplicate addresses will be omitted. +func (tx StdTx) GetSigners() []sdk.AccAddress { + seen := map[string]bool{} + var signers []sdk.AccAddress + for _, msg := range tx.GetMsgs() { + for _, addr := range msg.GetSigners() { + if !seen[addr.String()] { + signers = append(signers, addr) + seen[addr.String()] = true + } + } + } + return signers +} + +//nolint +func (tx StdTx) GetMemo() string { return tx.Memo } // Signatures returns the signature of signers who signed the Msg. +// GetSignatures returns the signature of signers who signed the Msg. // CONTRACT: Length returned is same as length of // pubkeys returned from MsgKeySigners, and the order // matches. @@ -40,8 +65,8 @@ func (tx StdTx) GetSignatures() []StdSignature { return tx.Signatures } // FeePayer returns the address responsible for paying the fees // for the transactions. It's the first address returned by msg.GetSigners(). // If GetSigners() is empty, this panics. -func FeePayer(tx sdk.Tx) sdk.Address { - return tx.GetMsg().GetSigners()[0] +func FeePayer(tx sdk.Tx) sdk.AccAddress { + return tx.GetMsgs()[0].GetSigners()[0] } //__________________________________________________________ @@ -85,45 +110,49 @@ func (fee StdFee) Bytes() []byte { // and the Sequence numbers for each signature (prevent // inchain replay and enforce tx ordering per account). type StdSignDoc struct { - ChainID string `json:"chain_id"` - AccountNumbers []int64 `json:"account_numbers"` - Sequences []int64 `json:"sequences"` - FeeBytes []byte `json:"fee_bytes"` - MsgBytes []byte `json:"msg_bytes"` - AltBytes []byte `json:"alt_bytes"` + AccountNumber int64 `json:"account_number"` + ChainID string `json:"chain_id"` + Fee json.RawMessage `json:"fee"` + Memo string `json:"memo"` + Msgs []json.RawMessage `json:"msgs"` + Sequence int64 `json:"sequence"` } // StdSignBytes returns the bytes to sign for a transaction. -// TODO: change the API to just take a chainID and StdTx ? -func StdSignBytes(chainID string, accnums []int64, sequences []int64, fee StdFee, msg sdk.Msg) []byte { - bz, err := json.Marshal(StdSignDoc{ - ChainID: chainID, - AccountNumbers: accnums, - Sequences: sequences, - FeeBytes: fee.Bytes(), - MsgBytes: msg.GetSignBytes(), +func StdSignBytes(chainID string, accnum int64, sequence int64, fee StdFee, msgs []sdk.Msg, memo string) []byte { + var msgsBytes []json.RawMessage + for _, msg := range msgs { + msgsBytes = append(msgsBytes, json.RawMessage(msg.GetSignBytes())) + } + bz, err := msgCdc.MarshalJSON(StdSignDoc{ + AccountNumber: accnum, + ChainID: chainID, + Fee: json.RawMessage(fee.Bytes()), + Memo: memo, + Msgs: msgsBytes, + Sequence: sequence, }) if err != nil { panic(err) } - return bz + return sdk.MustSortJSON(bz) } // StdSignMsg is a convenience structure for passing along // a Msg with the other requirements for a StdSignDoc before // it is signed. For use in the CLI. type StdSignMsg struct { - ChainID string - AccountNumbers []int64 - Sequences []int64 - Fee StdFee - Msg sdk.Msg - // XXX: Alt + ChainID string + AccountNumber int64 + Sequence int64 + Fee StdFee + Msgs []sdk.Msg + Memo string } // get message bytes func (msg StdSignMsg) Bytes() []byte { - return StdSignBytes(msg.ChainID, msg.AccountNumbers, msg.Sequences, msg.Fee, msg.Msg) + return StdSignBytes(msg.ChainID, msg.AccountNumber, msg.Sequence, msg.Fee, msg.Msgs, msg.Memo) } // Standard Signature diff --git a/x/auth/stdtx_test.go b/x/auth/stdtx_test.go index 412b37c20..bc9439663 100644 --- a/x/auth/stdtx_test.go +++ b/x/auth/stdtx_test.go @@ -1,32 +1,43 @@ package auth import ( + "fmt" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" ) -// func newStdFee() StdFee { -// return NewStdFee(100, -// Coin{"atom", 150}, -// ) -// } - func TestStdTx(t *testing.T) { priv := crypto.GenPrivKeyEd25519() - addr := priv.PubKey().Address() - msg := sdk.NewTestMsg(addr) + addr := sdk.AccAddress(priv.PubKey().Address()) + msgs := []sdk.Msg{sdk.NewTestMsg(addr)} fee := newStdFee() sigs := []StdSignature{} - tx := NewStdTx(msg, fee, sigs) - assert.Equal(t, msg, tx.GetMsg()) - assert.Equal(t, sigs, tx.GetSignatures()) + tx := NewStdTx(msgs, fee, sigs, "") + require.Equal(t, msgs, tx.GetMsgs()) + require.Equal(t, sigs, tx.GetSignatures()) feePayer := FeePayer(tx) - assert.Equal(t, addr, feePayer) + require.Equal(t, addr, feePayer) +} + +func TestStdSignBytes(t *testing.T) { + priv := crypto.GenPrivKeyEd25519() + addr := sdk.AccAddress(priv.PubKey().Address()) + msgs := []sdk.Msg{sdk.NewTestMsg(addr)} + fee := newStdFee() + signMsg := StdSignMsg{ + "1234", + 3, + 6, + fee, + msgs, + "memo", + } + require.Equal(t, fmt.Sprintf("{\"account_number\":\"3\",\"chain_id\":\"1234\",\"fee\":{\"amount\":[{\"amount\":\"150\",\"denom\":\"atom\"}],\"gas\":\"5000\"},\"memo\":\"memo\",\"msgs\":[[\"%s\"]],\"sequence\":\"6\"}", addr), string(signMsg.Bytes())) } diff --git a/x/auth/wire.go b/x/auth/wire.go index 6e430be4c..e22151101 100644 --- a/x/auth/wire.go +++ b/x/auth/wire.go @@ -8,7 +8,6 @@ import ( func RegisterWire(cdc *wire.Codec) { cdc.RegisterInterface((*Account)(nil), nil) cdc.RegisterConcrete(&BaseAccount{}, "auth/Account", nil) - cdc.RegisterConcrete(MsgChangeKey{}, "auth/ChangeKey", nil) cdc.RegisterConcrete(StdTx{}, "auth/StdTx", nil) } diff --git a/x/bank/app_test.go b/x/bank/app_test.go index d0c112b3d..74a421bd7 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -3,32 +3,33 @@ package bank import ( "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "math/rand" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" + "github.com/cosmos/cosmos-sdk/x/mock" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" ) // test bank module in a mock application var ( priv1 = crypto.GenPrivKeyEd25519() - addr1 = priv1.PubKey().Address() + addr1 = sdk.AccAddress(priv1.PubKey().Address()) priv2 = crypto.GenPrivKeyEd25519() - addr2 = priv2.PubKey().Address() - addr3 = crypto.GenPrivKeyEd25519().PubKey().Address() + addr2 = sdk.AccAddress(priv2.PubKey().Address()) + addr3 = sdk.AccAddress(crypto.GenPrivKeyEd25519().PubKey().Address()) priv4 = crypto.GenPrivKeyEd25519() - addr4 = priv4.PubKey().Address() - coins = sdk.Coins{{"foocoin", 10}} - halfCoins = sdk.Coins{{"foocoin", 5}} - manyCoins = sdk.Coins{{"foocoin", 1}, {"barcoin", 1}} + addr4 = sdk.AccAddress(priv4.PubKey().Address()) + coins = sdk.Coins{sdk.NewCoin("foocoin", 10)} + halfCoins = sdk.Coins{sdk.NewCoin("foocoin", 5)} + manyCoins = sdk.Coins{sdk.NewCoin("foocoin", 1), sdk.NewCoin("barcoin", 1)} freeFee = auth.StdFee{ // no fees for a buncha gas - sdk.Coins{{"foocoin", 0}}, + sdk.Coins{sdk.NewCoin("foocoin", 0)}, 100000, } @@ -77,23 +78,33 @@ var ( // initialize the mock application for this module func getMockApp(t *testing.T) *mock.App { - mapp := mock.NewApp() - - RegisterWire(mapp.Cdc) - coinKeeper := NewKeeper(mapp.AccountMapper) - mapp.Router().AddRoute("bank", NewHandler(coinKeeper)) - - mapp.CompleteSetup(t, []*sdk.KVStoreKey{}) + mapp, err := getBenchmarkMockApp() + require.NoError(t, err) return mapp } +func TestBankWithRandomMessages(t *testing.T) { + mapp := getMockApp(t) + setup := func(r *rand.Rand, keys []crypto.PrivKey) { + return + } + + mapp.RandomizedTesting( + t, + []mock.TestAndRunTx{TestAndRunSingleInputMsgSend}, + []mock.RandSetup{setup}, + []mock.Invariant{ModuleInvariants}, + 100, 30, 30, + ) +} + func TestMsgSendWithAccounts(t *testing.T) { mapp := getMockApp(t) // Add an account at genesis acc := &auth.BaseAccount{ Address: addr1, - Coins: sdk.Coins{{"foocoin", 67}}, + Coins: sdk.Coins{sdk.NewCoin("foocoin", 67)}, } accs := []auth.Account{acc} @@ -104,28 +115,28 @@ func TestMsgSendWithAccounts(t *testing.T) { ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) require.NotNil(t, res1) - assert.Equal(t, acc, res1.(*auth.BaseAccount)) + require.Equal(t, acc, res1.(*auth.BaseAccount)) // Run a CheckDeliver - mock.SignCheckDeliver(t, mapp.BaseApp, sendMsg1, []int64{0}, []int64{0}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg1}, []int64{0}, []int64{0}, true, priv1) // Check balances - mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"foocoin", 57}}) - mock.CheckBalance(t, mapp, addr2, sdk.Coins{{"foocoin", 10}}) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewCoin("foocoin", 57)}) + mock.CheckBalance(t, mapp, addr2, sdk.Coins{sdk.NewCoin("foocoin", 10)}) // Delivering again should cause replay error - mock.SignCheckDeliver(t, mapp.BaseApp, sendMsg1, []int64{0}, []int64{0}, false, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg1, sendMsg2}, []int64{0}, []int64{0}, false, priv1) // bumping the txnonce number without resigning should be an auth error mapp.BeginBlock(abci.RequestBeginBlock{}) - tx := mock.GenTx(sendMsg1, []int64{0}, []int64{0}, priv1) + tx := mock.GenTx([]sdk.Msg{sendMsg1}, []int64{0}, []int64{0}, priv1) tx.Signatures[0].Sequence = 1 res := mapp.Deliver(tx) - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) // resigning the tx with the bumped sequence should work - mock.SignCheckDeliver(t, mapp.BaseApp, sendMsg1, []int64{0}, []int64{1}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg1, sendMsg2}, []int64{0}, []int64{1}, true, priv1) } func TestMsgSendMultipleOut(t *testing.T) { @@ -133,24 +144,24 @@ func TestMsgSendMultipleOut(t *testing.T) { acc1 := &auth.BaseAccount{ Address: addr1, - Coins: sdk.Coins{{"foocoin", 42}}, + Coins: sdk.Coins{sdk.NewCoin("foocoin", 42)}, } acc2 := &auth.BaseAccount{ Address: addr2, - Coins: sdk.Coins{{"foocoin", 42}}, + Coins: sdk.Coins{sdk.NewCoin("foocoin", 42)}, } accs := []auth.Account{acc1, acc2} mock.SetGenesis(mapp, accs) // Simulate a Block - mock.SignCheckDeliver(t, mapp.BaseApp, sendMsg2, []int64{0}, []int64{0}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg2}, []int64{0}, []int64{0}, true, priv1) // Check balances - mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"foocoin", 32}}) - mock.CheckBalance(t, mapp, addr2, sdk.Coins{{"foocoin", 47}}) - mock.CheckBalance(t, mapp, addr3, sdk.Coins{{"foocoin", 5}}) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewCoin("foocoin", 32)}) + mock.CheckBalance(t, mapp, addr2, sdk.Coins{sdk.NewCoin("foocoin", 47)}) + mock.CheckBalance(t, mapp, addr3, sdk.Coins{sdk.NewCoin("foocoin", 5)}) } func TestSengMsgMultipleInOut(t *testing.T) { @@ -158,28 +169,28 @@ func TestSengMsgMultipleInOut(t *testing.T) { acc1 := &auth.BaseAccount{ Address: addr1, - Coins: sdk.Coins{{"foocoin", 42}}, + Coins: sdk.Coins{sdk.NewCoin("foocoin", 42)}, } acc2 := &auth.BaseAccount{ Address: addr2, - Coins: sdk.Coins{{"foocoin", 42}}, + Coins: sdk.Coins{sdk.NewCoin("foocoin", 42)}, } acc4 := &auth.BaseAccount{ Address: addr4, - Coins: sdk.Coins{{"foocoin", 42}}, + Coins: sdk.Coins{sdk.NewCoin("foocoin", 42)}, } accs := []auth.Account{acc1, acc2, acc4} mock.SetGenesis(mapp, accs) // CheckDeliver - mock.SignCheckDeliver(t, mapp.BaseApp, sendMsg3, []int64{0, 2}, []int64{0, 0}, true, priv1, priv4) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg3}, []int64{0, 2}, []int64{0, 0}, true, priv1, priv4) // Check balances - mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"foocoin", 32}}) - mock.CheckBalance(t, mapp, addr4, sdk.Coins{{"foocoin", 32}}) - mock.CheckBalance(t, mapp, addr2, sdk.Coins{{"foocoin", 52}}) - mock.CheckBalance(t, mapp, addr3, sdk.Coins{{"foocoin", 10}}) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewCoin("foocoin", 32)}) + mock.CheckBalance(t, mapp, addr4, sdk.Coins{sdk.NewCoin("foocoin", 32)}) + mock.CheckBalance(t, mapp, addr2, sdk.Coins{sdk.NewCoin("foocoin", 52)}) + mock.CheckBalance(t, mapp, addr3, sdk.Coins{sdk.NewCoin("foocoin", 10)}) } func TestMsgSendDependent(t *testing.T) { @@ -187,22 +198,22 @@ func TestMsgSendDependent(t *testing.T) { acc1 := &auth.BaseAccount{ Address: addr1, - Coins: sdk.Coins{{"foocoin", 42}}, + Coins: sdk.Coins{sdk.NewCoin("foocoin", 42)}, } accs := []auth.Account{acc1} mock.SetGenesis(mapp, accs) // CheckDeliver - mock.SignCheckDeliver(t, mapp.BaseApp, sendMsg1, []int64{0}, []int64{0}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg1}, []int64{0}, []int64{0}, true, priv1) // Check balances - mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"foocoin", 32}}) - mock.CheckBalance(t, mapp, addr2, sdk.Coins{{"foocoin", 10}}) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewCoin("foocoin", 32)}) + mock.CheckBalance(t, mapp, addr2, sdk.Coins{sdk.NewCoin("foocoin", 10)}) // Simulate a Block - mock.SignCheckDeliver(t, mapp.BaseApp, sendMsg4, []int64{1}, []int64{0}, true, priv2) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg4}, []int64{1}, []int64{0}, true, priv2) // Check balances - mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"foocoin", 42}}) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewCoin("foocoin", 42)}) } diff --git a/x/bank/bench_test.go b/x/bank/bench_test.go new file mode 100644 index 000000000..b90d56955 --- /dev/null +++ b/x/bank/bench_test.go @@ -0,0 +1,54 @@ +package bank + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/mock" + + abci "github.com/tendermint/tendermint/abci/types" +) + +// getBenchmarkMockApp initializes a mock application for this module, for purposes of benchmarking +// Any long term API support commitments do not apply to this function. +func getBenchmarkMockApp() (*mock.App, error) { + mapp := mock.NewApp() + + RegisterWire(mapp.Cdc) + coinKeeper := NewKeeper(mapp.AccountMapper) + mapp.Router().AddRoute("bank", NewHandler(coinKeeper)) + + err := mapp.CompleteSetup([]*sdk.KVStoreKey{}) + return mapp, err +} + +func BenchmarkOneBankSendTxPerBlock(b *testing.B) { + benchmarkApp, _ := getBenchmarkMockApp() + + // Add an account at genesis + acc := &auth.BaseAccount{ + Address: addr1, + // Some value conceivably higher than the benchmarks would ever go + Coins: sdk.Coins{sdk.NewCoin("foocoin", 100000000000)}, + } + accs := []auth.Account{acc} + + // Construct genesis state + mock.SetGenesis(benchmarkApp, accs) + // Precompute all txs + txs := mock.GenSequenceOfTxs([]sdk.Msg{sendMsg1}, []int64{0}, []int64{int64(0)}, b.N, priv1) + b.ResetTimer() + // Run this with a profiler, so its easy to distinguish what time comes from + // Committing, and what time comes from Check/Deliver Tx. + for i := 0; i < b.N; i++ { + benchmarkApp.BeginBlock(abci.RequestBeginBlock{}) + x := benchmarkApp.Check(txs[i]) + if !x.IsOK() { + panic("something is broken in checking transaction") + } + benchmarkApp.Deliver(txs[i]) + benchmarkApp.EndBlock(abci.RequestEndBlock{}) + benchmarkApp.Commit() + } +} diff --git a/x/bank/client/cli/sendtx.go b/x/bank/client/cli/sendtx.go index 817c9a174..b294e8cc7 100644 --- a/x/bank/client/cli/sendtx.go +++ b/x/bank/client/cli/sendtx.go @@ -1,16 +1,16 @@ package cli import ( - "fmt" - - "github.com/spf13/cobra" - "github.com/spf13/viper" + "github.com/pkg/errors" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" "github.com/cosmos/cosmos-sdk/x/bank/client" + "github.com/spf13/cobra" + "github.com/spf13/viper" ) const ( @@ -18,7 +18,7 @@ const ( flagAmount = "amount" ) -// SendTxCommand will create a send tx and sign it with the given key +// SendTxCmd will create a send tx and sign it with the given key func SendTxCmd(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "send", @@ -32,31 +32,52 @@ func SendTxCmd(cdc *wire.Codec) *cobra.Command { return err } - toStr := viper.GetString(flagTo) - - to, err := sdk.GetAccAddressBech32(toStr) + fromAcc, err := ctx.QueryStore(auth.AddressStoreKey(from), ctx.AccountStore) if err != nil { return err } - // parse coins + + // Check if account was found + if fromAcc == nil { + return errors.Errorf("No account with address %s was found in the state.\nAre you sure there has been a transaction involving it?", from) + } + + toStr := viper.GetString(flagTo) + + to, err := sdk.AccAddressFromBech32(toStr) + if err != nil { + return err + } + // parse coins trying to be sent amount := viper.GetString(flagAmount) coins, err := sdk.ParseCoins(amount) if err != nil { return err } - // build and sign the transaction, then broadcast to Tendermint - msg := client.BuildMsg(from, to, coins) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) + // ensure account has enough coins + account, err := ctx.Decoder(fromAcc) + if err != nil { + return err + } + if !account.GetCoins().IsGTE(coins) { + return errors.Errorf("Address %s doesn't have enough coins to pay for this transaction.", from) + } + + // build and sign the transaction, then broadcast to Tendermint + msg := client.BuildMsg(from, to, coins) + + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil + }, } cmd.Flags().String(flagTo, "", "Address to send coins") cmd.Flags().String(flagAmount, "", "Amount of coins to send") + return cmd } diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 2639d2788..872b3cfe3 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -1,12 +1,11 @@ package rest import ( - "encoding/json" "io/ioutil" "net/http" + "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/gorilla/mux" - "github.com/tendermint/go-crypto/keys" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" @@ -45,7 +44,7 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreCont vars := mux.Vars(r) bech32addr := vars["address"] - address, err := sdk.GetAccAddressBech32(bech32addr) + to, err := sdk.AccAddressFromBech32(bech32addr) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) @@ -73,15 +72,8 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreCont return } - to, err := sdk.GetAccAddressHex(address.String()) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - // build message - msg := client.BuildMsg(info.PubKey.Address(), to, m.Amount) + msg := client.BuildMsg(sdk.AccAddress(info.GetPubKey().Address()), to, m.Amount) if err != nil { // XXX rechecking same error ? w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) @@ -90,11 +82,13 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreCont // add gas to context ctx = ctx.WithGas(m.Gas) + // add chain-id to context + ctx = ctx.WithChainID(m.ChainID) // sign ctx = ctx.WithAccountNumber(m.AccountNumber) ctx = ctx.WithSequence(m.Sequence) - txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, msg, cdc) + txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, []sdk.Msg{msg}, cdc) if err != nil { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(err.Error())) @@ -109,7 +103,7 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreCont return } - output, err := json.MarshalIndent(res, "", " ") + output, err := wire.MarshalJSONIndent(cdc, res) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) diff --git a/x/bank/client/util.go b/x/bank/client/util.go index 4d461b34f..c733517a8 100644 --- a/x/bank/client/util.go +++ b/x/bank/client/util.go @@ -6,7 +6,7 @@ import ( ) // build the sendTx msg -func BuildMsg(from sdk.Address, to sdk.Address, coins sdk.Coins) sdk.Msg { +func BuildMsg(from sdk.AccAddress, to sdk.AccAddress, coins sdk.Coins) sdk.Msg { input := bank.NewInput(from, coins) output := bank.NewOutput(to, coins) msg := bank.NewMsgSend([]bank.Input{input}, []bank.Output{output}) diff --git a/x/bank/errors.go b/x/bank/errors.go index bf2a3ef26..cf11ddc22 100644 --- a/x/bank/errors.go +++ b/x/bank/errors.go @@ -17,9 +17,9 @@ const ( func codeToDefaultMsg(code sdk.CodeType) string { switch code { case CodeInvalidInput: - return "Invalid input coins" + return "invalid input coins" case CodeInvalidOutput: - return "Invalid output coins" + return "invalid output coins" default: return sdk.CodeToDefaultMsg(code) } diff --git a/x/bank/keeper.go b/x/bank/keeper.go index 71c884ffe..1494c8caf 100644 --- a/x/bank/keeper.go +++ b/x/bank/keeper.go @@ -26,32 +26,32 @@ func NewKeeper(am auth.AccountMapper) Keeper { } // GetCoins returns the coins at the addr. -func (keeper Keeper) GetCoins(ctx sdk.Context, addr sdk.Address) sdk.Coins { +func (keeper Keeper) GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { return getCoins(ctx, keeper.am, addr) } // SetCoins sets the coins at the addr. -func (keeper Keeper) SetCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) sdk.Error { +func (keeper Keeper) SetCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) sdk.Error { return setCoins(ctx, keeper.am, addr, amt) } // HasCoins returns whether or not an account has at least amt coins. -func (keeper Keeper) HasCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) bool { +func (keeper Keeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool { return hasCoins(ctx, keeper.am, addr, amt) } // SubtractCoins subtracts amt from the coins at the addr. -func (keeper Keeper) SubtractCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { +func (keeper Keeper) SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { return subtractCoins(ctx, keeper.am, addr, amt) } // AddCoins adds amt to the coins at the addr. -func (keeper Keeper) AddCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { +func (keeper Keeper) AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { return addCoins(ctx, keeper.am, addr, amt) } // SendCoins moves coins from one account to another -func (keeper Keeper) SendCoins(ctx sdk.Context, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) (sdk.Tags, sdk.Error) { +func (keeper Keeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) { return sendCoins(ctx, keeper.am, fromAddr, toAddr, amt) } @@ -73,17 +73,17 @@ func NewSendKeeper(am auth.AccountMapper) SendKeeper { } // GetCoins returns the coins at the addr. -func (keeper SendKeeper) GetCoins(ctx sdk.Context, addr sdk.Address) sdk.Coins { +func (keeper SendKeeper) GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { return getCoins(ctx, keeper.am, addr) } // HasCoins returns whether or not an account has at least amt coins. -func (keeper SendKeeper) HasCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) bool { +func (keeper SendKeeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool { return hasCoins(ctx, keeper.am, addr, amt) } // SendCoins moves coins from one account to another -func (keeper SendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) (sdk.Tags, sdk.Error) { +func (keeper SendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) { return sendCoins(ctx, keeper.am, fromAddr, toAddr, amt) } @@ -105,18 +105,18 @@ func NewViewKeeper(am auth.AccountMapper) ViewKeeper { } // GetCoins returns the coins at the addr. -func (keeper ViewKeeper) GetCoins(ctx sdk.Context, addr sdk.Address) sdk.Coins { +func (keeper ViewKeeper) GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { return getCoins(ctx, keeper.am, addr) } // HasCoins returns whether or not an account has at least amt coins. -func (keeper ViewKeeper) HasCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) bool { +func (keeper ViewKeeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool { return hasCoins(ctx, keeper.am, addr, amt) } //______________________________________________________________________________________________ -func getCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.Address) sdk.Coins { +func getCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.AccAddress) sdk.Coins { ctx.GasMeter().ConsumeGas(costGetCoins, "getCoins") acc := am.GetAccount(ctx, addr) if acc == nil { @@ -125,25 +125,29 @@ func getCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.Address) sdk.Coin return acc.GetCoins() } -func setCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.Address, amt sdk.Coins) sdk.Error { +func setCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.AccAddress, amt sdk.Coins) sdk.Error { ctx.GasMeter().ConsumeGas(costSetCoins, "setCoins") acc := am.GetAccount(ctx, addr) if acc == nil { acc = am.NewAccountWithAddress(ctx, addr) } - acc.SetCoins(amt) + err := acc.SetCoins(amt) + if err != nil { + // Handle w/ #870 + panic(err) + } am.SetAccount(ctx, acc) return nil } // HasCoins returns whether or not an account has at least amt coins. -func hasCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.Address, amt sdk.Coins) bool { +func hasCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.AccAddress, amt sdk.Coins) bool { ctx.GasMeter().ConsumeGas(costHasCoins, "hasCoins") return getCoins(ctx, am, addr).IsGTE(amt) } // SubtractCoins subtracts amt from the coins at the addr. -func subtractCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { +func subtractCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { ctx.GasMeter().ConsumeGas(costSubtractCoins, "subtractCoins") oldCoins := getCoins(ctx, am, addr) newCoins := oldCoins.Minus(amt) @@ -156,7 +160,7 @@ func subtractCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.Address, amt } // AddCoins adds amt to the coins at the addr. -func addCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { +func addCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { ctx.GasMeter().ConsumeGas(costAddCoins, "addCoins") oldCoins := getCoins(ctx, am, addr) newCoins := oldCoins.Plus(amt) @@ -170,7 +174,7 @@ func addCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.Address, amt sdk. // SendCoins moves coins from one account to another // NOTE: Make sure to revert state changes from tx on error -func sendCoins(ctx sdk.Context, am auth.AccountMapper, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) (sdk.Tags, sdk.Error) { +func sendCoins(ctx sdk.Context, am auth.AccountMapper, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) { _, subTags, err := subtractCoins(ctx, am, fromAddr, amt) if err != nil { return nil, err diff --git a/x/bank/keeper_test.go b/x/bank/keeper_test.go index 117c69e7a..c9d7c4119 100644 --- a/x/bank/keeper_test.go +++ b/x/bank/keeper_test.go @@ -4,10 +4,11 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" @@ -31,82 +32,82 @@ func TestKeeper(t *testing.T) { cdc := wire.NewCodec() auth.RegisterBaseAccount(cdc) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) - accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{}) + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + accountMapper := auth.NewAccountMapper(cdc, authKey, auth.ProtoBaseAccount) coinKeeper := NewKeeper(accountMapper) - addr := sdk.Address([]byte("addr1")) - addr2 := sdk.Address([]byte("addr2")) - addr3 := sdk.Address([]byte("addr3")) + addr := sdk.AccAddress([]byte("addr1")) + addr2 := sdk.AccAddress([]byte("addr2")) + addr3 := sdk.AccAddress([]byte("addr3")) acc := accountMapper.NewAccountWithAddress(ctx, addr) // Test GetCoins/SetCoins accountMapper.SetAccount(ctx, acc) - assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) + require.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) - coinKeeper.SetCoins(ctx, addr, sdk.Coins{{"foocoin", 10}}) - assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}})) + coinKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewCoin("foocoin", 10)}) + require.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("foocoin", 10)})) // Test HasCoins - assert.True(t, coinKeeper.HasCoins(ctx, addr, sdk.Coins{{"foocoin", 10}})) - assert.True(t, coinKeeper.HasCoins(ctx, addr, sdk.Coins{{"foocoin", 5}})) - assert.False(t, coinKeeper.HasCoins(ctx, addr, sdk.Coins{{"foocoin", 15}})) - assert.False(t, coinKeeper.HasCoins(ctx, addr, sdk.Coins{{"barcoin", 5}})) + require.True(t, coinKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewCoin("foocoin", 10)})) + require.True(t, coinKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewCoin("foocoin", 5)})) + require.False(t, coinKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewCoin("foocoin", 15)})) + require.False(t, coinKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewCoin("barcoin", 5)})) // Test AddCoins - coinKeeper.AddCoins(ctx, addr, sdk.Coins{{"foocoin", 15}}) - assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 25}})) + coinKeeper.AddCoins(ctx, addr, sdk.Coins{sdk.NewCoin("foocoin", 15)}) + require.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("foocoin", 25)})) - coinKeeper.AddCoins(ctx, addr, sdk.Coins{{"barcoin", 15}}) - assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 15}, {"foocoin", 25}})) + coinKeeper.AddCoins(ctx, addr, sdk.Coins{sdk.NewCoin("barcoin", 15)}) + require.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 15), sdk.NewCoin("foocoin", 25)})) // Test SubtractCoins - coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"foocoin", 10}}) - coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 5}}) - assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}})) + coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{sdk.NewCoin("foocoin", 10)}) + coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{sdk.NewCoin("barcoin", 5)}) + require.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 10), sdk.NewCoin("foocoin", 15)})) - coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 11}}) - assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}})) + coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{sdk.NewCoin("barcoin", 11)}) + require.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 10), sdk.NewCoin("foocoin", 15)})) - coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 10}}) - assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 15}})) - assert.False(t, coinKeeper.HasCoins(ctx, addr, sdk.Coins{{"barcoin", 1}})) + coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{sdk.NewCoin("barcoin", 10)}) + require.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("foocoin", 15)})) + require.False(t, coinKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewCoin("barcoin", 1)})) // Test SendCoins - coinKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 5}}) - assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}})) - assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}})) + coinKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.NewCoin("foocoin", 5)}) + require.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("foocoin", 10)})) + require.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewCoin("foocoin", 5)})) - _, err2 := coinKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 50}}) + _, err2 := coinKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.NewCoin("foocoin", 50)}) assert.Implements(t, (*sdk.Error)(nil), err2) - assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}})) - assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}})) + require.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("foocoin", 10)})) + require.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewCoin("foocoin", 5)})) - coinKeeper.AddCoins(ctx, addr, sdk.Coins{{"barcoin", 30}}) - coinKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"barcoin", 10}, {"foocoin", 5}}) - assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 20}, {"foocoin", 5}})) - assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 10}})) + coinKeeper.AddCoins(ctx, addr, sdk.Coins{sdk.NewCoin("barcoin", 30)}) + coinKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.NewCoin("barcoin", 10), sdk.NewCoin("foocoin", 5)}) + require.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 20), sdk.NewCoin("foocoin", 5)})) + require.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 10), sdk.NewCoin("foocoin", 10)})) // Test InputOutputCoins - input1 := NewInput(addr2, sdk.Coins{{"foocoin", 2}}) - output1 := NewOutput(addr, sdk.Coins{{"foocoin", 2}}) + input1 := NewInput(addr2, sdk.Coins{sdk.NewCoin("foocoin", 2)}) + output1 := NewOutput(addr, sdk.Coins{sdk.NewCoin("foocoin", 2)}) coinKeeper.InputOutputCoins(ctx, []Input{input1}, []Output{output1}) - assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 20}, {"foocoin", 7}})) - assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 8}})) + require.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 20), sdk.NewCoin("foocoin", 7)})) + require.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 10), sdk.NewCoin("foocoin", 8)})) inputs := []Input{ - NewInput(addr, sdk.Coins{{"foocoin", 3}}), - NewInput(addr2, sdk.Coins{{"barcoin", 3}, {"foocoin", 2}}), + NewInput(addr, sdk.Coins{sdk.NewCoin("foocoin", 3)}), + NewInput(addr2, sdk.Coins{sdk.NewCoin("barcoin", 3), sdk.NewCoin("foocoin", 2)}), } outputs := []Output{ - NewOutput(addr, sdk.Coins{{"barcoin", 1}}), - NewOutput(addr3, sdk.Coins{{"barcoin", 2}, {"foocoin", 5}}), + NewOutput(addr, sdk.Coins{sdk.NewCoin("barcoin", 1)}), + NewOutput(addr3, sdk.Coins{sdk.NewCoin("barcoin", 2), sdk.NewCoin("foocoin", 5)}), } coinKeeper.InputOutputCoins(ctx, inputs, outputs) - assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 21}, {"foocoin", 4}})) - assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"barcoin", 7}, {"foocoin", 6}})) - assert.True(t, coinKeeper.GetCoins(ctx, addr3).IsEqual(sdk.Coins{{"barcoin", 2}, {"foocoin", 5}})) + require.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 21), sdk.NewCoin("foocoin", 4)})) + require.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 7), sdk.NewCoin("foocoin", 6)})) + require.True(t, coinKeeper.GetCoins(ctx, addr3).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 2), sdk.NewCoin("foocoin", 5)})) } @@ -116,66 +117,66 @@ func TestSendKeeper(t *testing.T) { cdc := wire.NewCodec() auth.RegisterBaseAccount(cdc) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) - accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{}) + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + accountMapper := auth.NewAccountMapper(cdc, authKey, auth.ProtoBaseAccount) coinKeeper := NewKeeper(accountMapper) sendKeeper := NewSendKeeper(accountMapper) - addr := sdk.Address([]byte("addr1")) - addr2 := sdk.Address([]byte("addr2")) - addr3 := sdk.Address([]byte("addr3")) + addr := sdk.AccAddress([]byte("addr1")) + addr2 := sdk.AccAddress([]byte("addr2")) + addr3 := sdk.AccAddress([]byte("addr3")) acc := accountMapper.NewAccountWithAddress(ctx, addr) // Test GetCoins/SetCoins accountMapper.SetAccount(ctx, acc) - assert.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) + require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) - coinKeeper.SetCoins(ctx, addr, sdk.Coins{{"foocoin", 10}}) - assert.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}})) + coinKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewCoin("foocoin", 10)}) + require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("foocoin", 10)})) // Test HasCoins - assert.True(t, sendKeeper.HasCoins(ctx, addr, sdk.Coins{{"foocoin", 10}})) - assert.True(t, sendKeeper.HasCoins(ctx, addr, sdk.Coins{{"foocoin", 5}})) - assert.False(t, sendKeeper.HasCoins(ctx, addr, sdk.Coins{{"foocoin", 15}})) - assert.False(t, sendKeeper.HasCoins(ctx, addr, sdk.Coins{{"barcoin", 5}})) + require.True(t, sendKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewCoin("foocoin", 10)})) + require.True(t, sendKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewCoin("foocoin", 5)})) + require.False(t, sendKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewCoin("foocoin", 15)})) + require.False(t, sendKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewCoin("barcoin", 5)})) - coinKeeper.SetCoins(ctx, addr, sdk.Coins{{"foocoin", 15}}) + coinKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewCoin("foocoin", 15)}) // Test SendCoins - sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 5}}) - assert.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}})) - assert.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}})) + sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.NewCoin("foocoin", 5)}) + require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("foocoin", 10)})) + require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewCoin("foocoin", 5)})) - _, err2 := sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 50}}) + _, err2 := sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.NewCoin("foocoin", 50)}) assert.Implements(t, (*sdk.Error)(nil), err2) - assert.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}})) - assert.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}})) + require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("foocoin", 10)})) + require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewCoin("foocoin", 5)})) - coinKeeper.AddCoins(ctx, addr, sdk.Coins{{"barcoin", 30}}) - sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"barcoin", 10}, {"foocoin", 5}}) - assert.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 20}, {"foocoin", 5}})) - assert.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 10}})) + coinKeeper.AddCoins(ctx, addr, sdk.Coins{sdk.NewCoin("barcoin", 30)}) + sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.NewCoin("barcoin", 10), sdk.NewCoin("foocoin", 5)}) + require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 20), sdk.NewCoin("foocoin", 5)})) + require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 10), sdk.NewCoin("foocoin", 10)})) // Test InputOutputCoins - input1 := NewInput(addr2, sdk.Coins{{"foocoin", 2}}) - output1 := NewOutput(addr, sdk.Coins{{"foocoin", 2}}) + input1 := NewInput(addr2, sdk.Coins{sdk.NewCoin("foocoin", 2)}) + output1 := NewOutput(addr, sdk.Coins{sdk.NewCoin("foocoin", 2)}) sendKeeper.InputOutputCoins(ctx, []Input{input1}, []Output{output1}) - assert.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 20}, {"foocoin", 7}})) - assert.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 8}})) + require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 20), sdk.NewCoin("foocoin", 7)})) + require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 10), sdk.NewCoin("foocoin", 8)})) inputs := []Input{ - NewInput(addr, sdk.Coins{{"foocoin", 3}}), - NewInput(addr2, sdk.Coins{{"barcoin", 3}, {"foocoin", 2}}), + NewInput(addr, sdk.Coins{sdk.NewCoin("foocoin", 3)}), + NewInput(addr2, sdk.Coins{sdk.NewCoin("barcoin", 3), sdk.NewCoin("foocoin", 2)}), } outputs := []Output{ - NewOutput(addr, sdk.Coins{{"barcoin", 1}}), - NewOutput(addr3, sdk.Coins{{"barcoin", 2}, {"foocoin", 5}}), + NewOutput(addr, sdk.Coins{sdk.NewCoin("barcoin", 1)}), + NewOutput(addr3, sdk.Coins{sdk.NewCoin("barcoin", 2), sdk.NewCoin("foocoin", 5)}), } sendKeeper.InputOutputCoins(ctx, inputs, outputs) - assert.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 21}, {"foocoin", 4}})) - assert.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"barcoin", 7}, {"foocoin", 6}})) - assert.True(t, sendKeeper.GetCoins(ctx, addr3).IsEqual(sdk.Coins{{"barcoin", 2}, {"foocoin", 5}})) + require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 21), sdk.NewCoin("foocoin", 4)})) + require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 7), sdk.NewCoin("foocoin", 6)})) + require.True(t, sendKeeper.GetCoins(ctx, addr3).IsEqual(sdk.Coins{sdk.NewCoin("barcoin", 2), sdk.NewCoin("foocoin", 5)})) } @@ -185,24 +186,24 @@ func TestViewKeeper(t *testing.T) { cdc := wire.NewCodec() auth.RegisterBaseAccount(cdc) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) - accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{}) + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + accountMapper := auth.NewAccountMapper(cdc, authKey, auth.ProtoBaseAccount) coinKeeper := NewKeeper(accountMapper) viewKeeper := NewViewKeeper(accountMapper) - addr := sdk.Address([]byte("addr1")) + addr := sdk.AccAddress([]byte("addr1")) acc := accountMapper.NewAccountWithAddress(ctx, addr) // Test GetCoins/SetCoins accountMapper.SetAccount(ctx, acc) - assert.True(t, viewKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) + require.True(t, viewKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) - coinKeeper.SetCoins(ctx, addr, sdk.Coins{{"foocoin", 10}}) - assert.True(t, viewKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}})) + coinKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewCoin("foocoin", 10)}) + require.True(t, viewKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("foocoin", 10)})) // Test HasCoins - assert.True(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{{"foocoin", 10}})) - assert.True(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{{"foocoin", 5}})) - assert.False(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{{"foocoin", 15}})) - assert.False(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{{"barcoin", 5}})) + require.True(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewCoin("foocoin", 10)})) + require.True(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewCoin("foocoin", 5)})) + require.False(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewCoin("foocoin", 15)})) + require.False(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewCoin("barcoin", 5)})) } diff --git a/x/bank/msgs.go b/x/bank/msgs.go index 1a871979e..316d66ba1 100644 --- a/x/bank/msgs.go +++ b/x/bank/msgs.go @@ -72,12 +72,12 @@ func (msg MsgSend) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // Implements Msg. -func (msg MsgSend) GetSigners() []sdk.Address { - addrs := make([]sdk.Address, len(msg.Inputs)) +func (msg MsgSend) GetSigners() []sdk.AccAddress { + addrs := make([]sdk.AccAddress, len(msg.Inputs)) for i, in := range msg.Inputs { addrs[i] = in.Address } @@ -89,14 +89,14 @@ func (msg MsgSend) GetSigners() []sdk.Address { // MsgIssue - high level transaction of the coin module type MsgIssue struct { - Banker sdk.Address `json:"banker"` - Outputs []Output `json:"outputs"` + Banker sdk.AccAddress `json:"banker"` + Outputs []Output `json:"outputs"` } var _ sdk.Msg = MsgIssue{} // NewMsgIssue - construct arbitrary multi-in, multi-out send msg. -func NewMsgIssue(banker sdk.Address, out []Output) MsgIssue { +func NewMsgIssue(banker sdk.AccAddress, out []Output) MsgIssue { return MsgIssue{Banker: banker, Outputs: out} } @@ -124,21 +124,21 @@ func (msg MsgIssue) GetSignBytes() []byte { outputs = append(outputs, output.GetSignBytes()) } b, err := msgCdc.MarshalJSON(struct { - Banker string `json:"banker"` + Banker sdk.AccAddress `json:"banker"` Outputs []json.RawMessage `json:"outputs"` }{ - Banker: sdk.MustBech32ifyAcc(msg.Banker), + Banker: msg.Banker, Outputs: outputs, }) if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // Implements Msg. -func (msg MsgIssue) GetSigners() []sdk.Address { - return []sdk.Address{msg.Banker} +func (msg MsgIssue) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Banker} } //---------------------------------------- @@ -146,23 +146,17 @@ func (msg MsgIssue) GetSigners() []sdk.Address { // Transaction Input type Input struct { - Address sdk.Address `json:"address"` - Coins sdk.Coins `json:"coins"` + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` } // Return bytes to sign for Input func (in Input) GetSignBytes() []byte { - bin, err := msgCdc.MarshalJSON(struct { - Address string `json:"address"` - Coins sdk.Coins `json:"coins"` - }{ - Address: sdk.MustBech32ifyAcc(in.Address), - Coins: in.Coins, - }) + bin, err := msgCdc.MarshalJSON(in) if err != nil { panic(err) } - return bin + return sdk.MustSortJSON(bin) } // ValidateBasic - validate transaction input @@ -180,7 +174,7 @@ func (in Input) ValidateBasic() sdk.Error { } // NewInput - create a transaction input, used with MsgSend -func NewInput(addr sdk.Address, coins sdk.Coins) Input { +func NewInput(addr sdk.AccAddress, coins sdk.Coins) Input { input := Input{ Address: addr, Coins: coins, @@ -193,23 +187,17 @@ func NewInput(addr sdk.Address, coins sdk.Coins) Input { // Transaction Output type Output struct { - Address sdk.Address `json:"address"` - Coins sdk.Coins `json:"coins"` + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` } // Return bytes to sign for Output func (out Output) GetSignBytes() []byte { - bin, err := msgCdc.MarshalJSON(struct { - Address string `json:"address"` - Coins sdk.Coins `json:"coins"` - }{ - Address: sdk.MustBech32ifyAcc(out.Address), - Coins: out.Coins, - }) + bin, err := msgCdc.MarshalJSON(out) if err != nil { panic(err) } - return bin + return sdk.MustSortJSON(bin) } // ValidateBasic - validate transaction output @@ -227,7 +215,7 @@ func (out Output) ValidateBasic() sdk.Error { } // NewOutput - create a transaction output, used with MsgSend -func NewOutput(addr sdk.Address, coins sdk.Coins) Output { +func NewOutput(addr sdk.AccAddress, coins sdk.Coins) Output { output := Output{ Address: addr, Coins: coins, diff --git a/x/bank/msgs_test.go b/x/bank/msgs_test.go index 90b4869db..b60cc65ad 100644 --- a/x/bank/msgs_test.go +++ b/x/bank/msgs_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -13,31 +13,31 @@ func TestNewMsgSend(t *testing.T) {} func TestMsgSendType(t *testing.T) { // Construct a MsgSend - addr1 := sdk.Address([]byte("input")) - addr2 := sdk.Address([]byte("output")) - coins := sdk.Coins{{"atom", 10}} + addr1 := sdk.AccAddress([]byte("input")) + addr2 := sdk.AccAddress([]byte("output")) + coins := sdk.Coins{sdk.NewCoin("atom", 10)} var msg = MsgSend{ Inputs: []Input{NewInput(addr1, coins)}, Outputs: []Output{NewOutput(addr2, coins)}, } // TODO some failures for bad result - assert.Equal(t, msg.Type(), "bank") + require.Equal(t, msg.Type(), "bank") } func TestInputValidation(t *testing.T) { - addr1 := sdk.Address([]byte{1, 2}) - addr2 := sdk.Address([]byte{7, 8}) - someCoins := sdk.Coins{{"atom", 123}} - multiCoins := sdk.Coins{{"atom", 123}, {"eth", 20}} + addr1 := sdk.AccAddress([]byte{1, 2}) + addr2 := sdk.AccAddress([]byte{7, 8}) + someCoins := sdk.Coins{sdk.NewCoin("atom", 123)} + multiCoins := sdk.Coins{sdk.NewCoin("atom", 123), sdk.NewCoin("eth", 20)} - var emptyAddr sdk.Address + var emptyAddr sdk.AccAddress emptyCoins := sdk.Coins{} - emptyCoins2 := sdk.Coins{{"eth", 0}} - someEmptyCoins := sdk.Coins{{"eth", 10}, {"atom", 0}} - minusCoins := sdk.Coins{{"eth", -34}} - someMinusCoins := sdk.Coins{{"atom", 20}, {"eth", -34}} - unsortedCoins := sdk.Coins{{"eth", 1}, {"atom", 1}} + emptyCoins2 := sdk.Coins{sdk.NewCoin("eth", 0)} + someEmptyCoins := sdk.Coins{sdk.NewCoin("eth", 10), sdk.NewCoin("atom", 0)} + minusCoins := sdk.Coins{sdk.NewCoin("eth", -34)} + someMinusCoins := sdk.Coins{sdk.NewCoin("atom", 20), sdk.NewCoin("eth", -34)} + unsortedCoins := sdk.Coins{sdk.NewCoin("eth", 1), sdk.NewCoin("atom", 1)} cases := []struct { valid bool @@ -60,26 +60,26 @@ func TestInputValidation(t *testing.T) { for i, tc := range cases { err := tc.txIn.ValidateBasic() if tc.valid { - assert.Nil(t, err, "%d: %+v", i, err) + require.Nil(t, err, "%d: %+v", i, err) } else { - assert.NotNil(t, err, "%d", i) + require.NotNil(t, err, "%d", i) } } } func TestOutputValidation(t *testing.T) { - addr1 := sdk.Address([]byte{1, 2}) - addr2 := sdk.Address([]byte{7, 8}) - someCoins := sdk.Coins{{"atom", 123}} - multiCoins := sdk.Coins{{"atom", 123}, {"eth", 20}} + addr1 := sdk.AccAddress([]byte{1, 2}) + addr2 := sdk.AccAddress([]byte{7, 8}) + someCoins := sdk.Coins{sdk.NewCoin("atom", 123)} + multiCoins := sdk.Coins{sdk.NewCoin("atom", 123), sdk.NewCoin("eth", 20)} - var emptyAddr sdk.Address + var emptyAddr sdk.AccAddress emptyCoins := sdk.Coins{} - emptyCoins2 := sdk.Coins{{"eth", 0}} - someEmptyCoins := sdk.Coins{{"eth", 10}, {"atom", 0}} - minusCoins := sdk.Coins{{"eth", -34}} - someMinusCoins := sdk.Coins{{"atom", 20}, {"eth", -34}} - unsortedCoins := sdk.Coins{{"eth", 1}, {"atom", 1}} + emptyCoins2 := sdk.Coins{sdk.NewCoin("eth", 0)} + someEmptyCoins := sdk.Coins{sdk.NewCoin("eth", 10), sdk.NewCoin("atom", 0)} + minusCoins := sdk.Coins{sdk.NewCoin("eth", -34)} + someMinusCoins := sdk.Coins{sdk.NewCoin("atom", 20), sdk.NewCoin("eth", -34)} + unsortedCoins := sdk.Coins{sdk.NewCoin("eth", 1), sdk.NewCoin("atom", 1)} cases := []struct { valid bool @@ -102,20 +102,20 @@ func TestOutputValidation(t *testing.T) { for i, tc := range cases { err := tc.txOut.ValidateBasic() if tc.valid { - assert.Nil(t, err, "%d: %+v", i, err) + require.Nil(t, err, "%d: %+v", i, err) } else { - assert.NotNil(t, err, "%d", i) + require.NotNil(t, err, "%d", i) } } } func TestMsgSendValidation(t *testing.T) { - addr1 := sdk.Address([]byte{1, 2}) - addr2 := sdk.Address([]byte{7, 8}) - atom123 := sdk.Coins{{"atom", 123}} - atom124 := sdk.Coins{{"atom", 124}} - eth123 := sdk.Coins{{"eth", 123}} - atom123eth123 := sdk.Coins{{"atom", 123}, {"eth", 123}} + addr1 := sdk.AccAddress([]byte{1, 2}) + addr2 := sdk.AccAddress([]byte{7, 8}) + atom123 := sdk.Coins{sdk.NewCoin("atom", 123)} + atom124 := sdk.Coins{sdk.NewCoin("atom", 124)} + eth123 := sdk.Coins{sdk.NewCoin("eth", 123)} + atom123eth123 := sdk.Coins{sdk.NewCoin("atom", 123), sdk.NewCoin("eth", 123)} input1 := NewInput(addr1, atom123) input2 := NewInput(addr1, eth123) @@ -124,7 +124,7 @@ func TestMsgSendValidation(t *testing.T) { output3 := NewOutput(addr2, eth123) outputMulti := NewOutput(addr2, atom123eth123) - var emptyAddr sdk.Address + var emptyAddr sdk.AccAddress cases := []struct { valid bool @@ -132,13 +132,13 @@ func TestMsgSendValidation(t *testing.T) { }{ {false, MsgSend{}}, // no input or output {false, MsgSend{Inputs: []Input{input1}}}, // just input - {false, MsgSend{Outputs: []Output{output1}}}, // just ouput + {false, MsgSend{Outputs: []Output{output1}}}, // just output {false, MsgSend{ Inputs: []Input{NewInput(emptyAddr, atom123)}, // invalid input Outputs: []Output{output1}}}, {false, MsgSend{ Inputs: []Input{input1}, - Outputs: []Output{{emptyAddr, atom123}}}, // invalid ouput + Outputs: []Output{{emptyAddr, atom123}}}, // invalid output }, {false, MsgSend{ Inputs: []Input{input1}, @@ -170,57 +170,57 @@ func TestMsgSendValidation(t *testing.T) { for i, tc := range cases { err := tc.tx.ValidateBasic() if tc.valid { - assert.Nil(t, err, "%d: %+v", i, err) + require.Nil(t, err, "%d: %+v", i, err) } else { - assert.NotNil(t, err, "%d", i) + require.NotNil(t, err, "%d", i) } } } func TestMsgSendGetSignBytes(t *testing.T) { - addr1 := sdk.Address([]byte("input")) - addr2 := sdk.Address([]byte("output")) - coins := sdk.Coins{{"atom", 10}} + addr1 := sdk.AccAddress([]byte("input")) + addr2 := sdk.AccAddress([]byte("output")) + coins := sdk.Coins{sdk.NewCoin("atom", 10)} var msg = MsgSend{ Inputs: []Input{NewInput(addr1, coins)}, Outputs: []Output{NewOutput(addr2, coins)}, } res := msg.GetSignBytes() - expected := `{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"denom":"atom","amount":10}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"denom":"atom","amount":10}]}]}` - assert.Equal(t, expected, string(res)) + expected := `{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}` + require.Equal(t, expected, string(res)) } func TestMsgSendGetSigners(t *testing.T) { var msg = MsgSend{ Inputs: []Input{ - NewInput(sdk.Address([]byte("input1")), nil), - NewInput(sdk.Address([]byte("input2")), nil), - NewInput(sdk.Address([]byte("input3")), nil), + NewInput(sdk.AccAddress([]byte("input1")), nil), + NewInput(sdk.AccAddress([]byte("input2")), nil), + NewInput(sdk.AccAddress([]byte("input3")), nil), }, } res := msg.GetSigners() // TODO: fix this ! - assert.Equal(t, fmt.Sprintf("%v", res), "[696E70757431 696E70757432 696E70757433]") + require.Equal(t, fmt.Sprintf("%v", res), "[696E70757431 696E70757432 696E70757433]") } /* // what to do w/ this test? func TestMsgSendSigners(t *testing.T) { - signers := []sdk.Address{ + signers := []sdk.AccAddress{ {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, } - someCoins := sdk.Coins{{"atom", 123}} + someCoins := sdk.Coins{sdk.NewCoin("atom", 123)} inputs := make([]Input, len(signers)) for i, signer := range signers { inputs[i] = NewInput(signer, someCoins) } tx := NewMsgSend(inputs, nil) - assert.Equal(t, signers, tx.Signers()) + require.Equal(t, signers, tx.Signers()) } */ @@ -233,15 +233,15 @@ func TestNewMsgIssue(t *testing.T) { func TestMsgIssueType(t *testing.T) { // Construct an MsgIssue - addr := sdk.Address([]byte("loan-from-bank")) - coins := sdk.Coins{{"atom", 10}} + addr := sdk.AccAddress([]byte("loan-from-bank")) + coins := sdk.Coins{sdk.NewCoin("atom", 10)} var msg = MsgIssue{ - Banker: sdk.Address([]byte("input")), + Banker: sdk.AccAddress([]byte("input")), Outputs: []Output{NewOutput(addr, coins)}, } // TODO some failures for bad result - assert.Equal(t, msg.Type(), "bank") + require.Equal(t, msg.Type(), "bank") } func TestMsgIssueValidation(t *testing.T) { @@ -249,22 +249,22 @@ func TestMsgIssueValidation(t *testing.T) { } func TestMsgIssueGetSignBytes(t *testing.T) { - addr := sdk.Address([]byte("loan-from-bank")) - coins := sdk.Coins{{"atom", 10}} + addr := sdk.AccAddress([]byte("loan-from-bank")) + coins := sdk.Coins{sdk.NewCoin("atom", 10)} var msg = MsgIssue{ - Banker: sdk.Address([]byte("input")), + Banker: sdk.AccAddress([]byte("input")), Outputs: []Output{NewOutput(addr, coins)}, } res := msg.GetSignBytes() - expected := `{"banker":"cosmosaccaddr1d9h8qat5e4ehc5","outputs":[{"address":"cosmosaccaddr1d3hkzm3dveex7mfdvfsku6cwsauqd","coins":[{"denom":"atom","amount":10}]}]}` - assert.Equal(t, expected, string(res)) + expected := `{"banker":"cosmosaccaddr1d9h8qat5e4ehc5","outputs":[{"address":"cosmosaccaddr1d3hkzm3dveex7mfdvfsku6cwsauqd","coins":[{"amount":"10","denom":"atom"}]}]}` + require.Equal(t, expected, string(res)) } func TestMsgIssueGetSigners(t *testing.T) { var msg = MsgIssue{ - Banker: sdk.Address([]byte("onlyone")), + Banker: sdk.AccAddress([]byte("onlyone")), } res := msg.GetSigners() - assert.Equal(t, fmt.Sprintf("%v", res), "[6F6E6C796F6E65]") + require.Equal(t, fmt.Sprintf("%v", res), "[6F6E6C796F6E65]") } diff --git a/x/bank/test_helpers.go b/x/bank/test_helpers.go new file mode 100644 index 000000000..1dad0ba26 --- /dev/null +++ b/x/bank/test_helpers.go @@ -0,0 +1,150 @@ +package bank + +import ( + "errors" + "fmt" + "math/big" + "math/rand" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/mock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" +) + +// ModuleInvariants runs all invariants of the bank module. +// Currently runs non-negative balance invariant and TotalCoinsInvariant +func ModuleInvariants(t *testing.T, app *mock.App, log string) { + NonnegativeBalanceInvariant(t, app, log) + TotalCoinsInvariant(t, app, log) +} + +// NonnegativeBalanceInvariant checks that all accounts in the application have non-negative balances +func NonnegativeBalanceInvariant(t *testing.T, app *mock.App, log string) { + ctx := app.NewContext(false, abci.Header{}) + accts := mock.GetAllAccounts(app.AccountMapper, ctx) + for _, acc := range accts { + coins := acc.GetCoins() + assert.True(t, coins.IsNotNegative(), + fmt.Sprintf("%s has a negative denomination of %s\n%s", + acc.GetAddress().String(), + coins.String(), + log), + ) + } +} + +// TotalCoinsInvariant checks that the sum of the coins across all accounts +// is what is expected +func TotalCoinsInvariant(t *testing.T, app *mock.App, log string) { + ctx := app.BaseApp.NewContext(false, abci.Header{}) + totalCoins := sdk.Coins{} + + chkAccount := func(acc auth.Account) bool { + coins := acc.GetCoins() + totalCoins = totalCoins.Plus(coins) + return false + } + + app.AccountMapper.IterateAccounts(ctx, chkAccount) + require.Equal(t, app.TotalCoinsSupply, totalCoins, log) +} + +// TestAndRunSingleInputMsgSend tests and runs a single msg send, with one input and one output, where both +// accounts already exist. +func TestAndRunSingleInputMsgSend(t *testing.T, r *rand.Rand, app *mock.App, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { + fromKey := keys[r.Intn(len(keys))] + fromAddr := sdk.AccAddress(fromKey.PubKey().Address()) + toKey := keys[r.Intn(len(keys))] + // Disallow sending money to yourself + for { + if !fromKey.Equals(toKey) { + break + } + toKey = keys[r.Intn(len(keys))] + } + toAddr := sdk.AccAddress(toKey.PubKey().Address()) + initFromCoins := app.AccountMapper.GetAccount(ctx, fromAddr).GetCoins() + + denomIndex := r.Intn(len(initFromCoins)) + amt, goErr := randPositiveInt(r, initFromCoins[denomIndex].Amount) + if goErr != nil { + return "skipping bank send due to account having no coins of denomination " + initFromCoins[denomIndex].Denom, nil + } + + action = fmt.Sprintf("%s is sending %s %s to %s", + fromAddr.String(), + amt.String(), + initFromCoins[denomIndex].Denom, + toAddr.String(), + ) + log = fmt.Sprintf("%s\n%s", log, action) + + coins := sdk.Coins{{initFromCoins[denomIndex].Denom, amt}} + var msg = MsgSend{ + Inputs: []Input{NewInput(fromAddr, coins)}, + Outputs: []Output{NewOutput(toAddr, coins)}, + } + sendAndVerifyMsgSend(t, app, msg, ctx, log, []crypto.PrivKey{fromKey}) + + return action, nil +} + +// Sends and verifies the transition of a msg send. This fails if there are repeated inputs or outputs +func sendAndVerifyMsgSend(t *testing.T, app *mock.App, msg MsgSend, ctx sdk.Context, log string, privkeys []crypto.PrivKey) { + initialInputAddrCoins := make([]sdk.Coins, len(msg.Inputs)) + initialOutputAddrCoins := make([]sdk.Coins, len(msg.Outputs)) + AccountNumbers := make([]int64, len(msg.Inputs)) + SequenceNumbers := make([]int64, len(msg.Inputs)) + + for i := 0; i < len(msg.Inputs); i++ { + acc := app.AccountMapper.GetAccount(ctx, msg.Inputs[i].Address) + AccountNumbers[i] = acc.GetAccountNumber() + SequenceNumbers[i] = acc.GetSequence() + initialInputAddrCoins[i] = acc.GetCoins() + } + for i := 0; i < len(msg.Outputs); i++ { + acc := app.AccountMapper.GetAccount(ctx, msg.Outputs[i].Address) + initialOutputAddrCoins[i] = acc.GetCoins() + } + tx := mock.GenTx([]sdk.Msg{msg}, + AccountNumbers, + SequenceNumbers, + privkeys...) + res := app.Deliver(tx) + if !res.IsOK() { + // TODO: Do this in a more 'canonical' way + fmt.Println(res) + fmt.Println(log) + t.FailNow() + } + + for i := 0; i < len(msg.Inputs); i++ { + terminalInputCoins := app.AccountMapper.GetAccount(ctx, msg.Inputs[i].Address).GetCoins() + require.Equal(t, + initialInputAddrCoins[i].Minus(msg.Inputs[i].Coins), + terminalInputCoins, + fmt.Sprintf("Input #%d had an incorrect amount of coins\n%s", i, log), + ) + } + for i := 0; i < len(msg.Outputs); i++ { + terminalOutputCoins := app.AccountMapper.GetAccount(ctx, msg.Outputs[i].Address).GetCoins() + require.Equal(t, + initialOutputAddrCoins[i].Plus(msg.Outputs[i].Coins), + terminalOutputCoins, + fmt.Sprintf("Output #%d had an incorrect amount of coins\n%s", i, log), + ) + } +} + +func randPositiveInt(r *rand.Rand, max sdk.Int) (sdk.Int, error) { + if !max.GT(sdk.OneInt()) { + return sdk.Int{}, errors.New("max too small") + } + max = max.Sub(sdk.OneInt()) + return sdk.NewIntFromBigInt(new(big.Int).Rand(r, max.BigInt())).Add(sdk.OneInt()), nil +} diff --git a/x/fee_distribution/keeper_test.go b/x/fee_distribution/keeper_test.go index 4ad8180e1..0d732f8a1 100644 --- a/x/fee_distribution/keeper_test.go +++ b/x/fee_distribution/keeper_test.go @@ -15,11 +15,11 @@ package stake //// test that an empty gotValidator set doesn't have any gotValidators //gotValidators := keeper.GetValidators(ctx) -//assert.Equal(t, 5, len(gotValidators)) +//require.Equal(t, 5, len(gotValidators)) //totPow := keeper.GetTotalPrecommitVotingPower(ctx) //exp := sdk.NewRat(11111) -//assert.True(t, exp.Equal(totPow), "exp %v, got %v", exp, totPow) +//require.True(t, exp.Equal(totPow), "exp %v, got %v", exp, totPow) //// set absent gotValidators to be the 1st and 3rd record sorted by pubKey address //ctx = ctx.WithAbsentValidators([]int32{1, 3}) @@ -27,5 +27,5 @@ package stake //// XXX verify that this order should infact exclude these two records //exp = sdk.NewRat(11100) -//assert.True(t, exp.Equal(totPow), "exp %v, got %v", exp, totPow) +//require.True(t, exp.Equal(totPow), "exp %v, got %v", exp, totPow) //} diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go new file mode 100644 index 000000000..1c5c11b71 --- /dev/null +++ b/x/gov/client/cli/tx.go @@ -0,0 +1,249 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/pkg/errors" +) + +const ( + flagProposalID = "proposalID" + flagTitle = "title" + flagDescription = "description" + flagProposalType = "type" + flagDeposit = "deposit" + flagProposer = "proposer" + flagDepositer = "depositer" + flagVoter = "voter" + flagOption = "option" +) + +// submit a proposal tx +func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "submit-proposal", + Short: "Submit a proposal along with an initial deposit", + RunE: func(cmd *cobra.Command, args []string) error { + title := viper.GetString(flagTitle) + description := viper.GetString(flagDescription) + strProposalType := viper.GetString(flagProposalType) + initialDeposit := viper.GetString(flagDeposit) + + // get the from address from the name flag + from, err := sdk.AccAddressFromBech32(viper.GetString(flagProposer)) + if err != nil { + return err + } + + amount, err := sdk.ParseCoins(initialDeposit) + if err != nil { + return err + } + + proposalType, err := gov.StringToProposalType(strProposalType) + if err != nil { + return err + } + + // create the message + msg := gov.NewMsgSubmitProposal(title, description, proposalType, from, amount) + + err = msg.ValidateBasic() + if err != nil { + return err + } + + // build and sign the transaction, then broadcast to Tendermint + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + // proposalID must be returned, and it is a part of response + ctx.PrintResponse = true + + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + if err != nil { + return err + } + return nil + }, + } + + cmd.Flags().String(flagTitle, "", "title of proposal") + cmd.Flags().String(flagDescription, "", "description of proposal") + cmd.Flags().String(flagProposalType, "", "proposalType of proposal") + cmd.Flags().String(flagDeposit, "", "deposit of proposal") + cmd.Flags().String(flagProposer, "", "proposer of proposal") + + return cmd +} + +// set a new Deposit transaction +func GetCmdDeposit(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "deposit", + Short: "deposit tokens for activing proposal", + RunE: func(cmd *cobra.Command, args []string) error { + // get the from address from the name flag + depositer, err := sdk.AccAddressFromBech32(viper.GetString(flagDepositer)) + if err != nil { + return err + } + + proposalID := viper.GetInt64(flagProposalID) + + amount, err := sdk.ParseCoins(viper.GetString(flagDeposit)) + if err != nil { + return err + } + + // create the message + msg := gov.NewMsgDeposit(depositer, proposalID, amount) + + err = msg.ValidateBasic() + if err != nil { + return err + } + + // build and sign the transaction, then broadcast to Tendermint + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + if err != nil { + return err + } + return nil + }, + } + + cmd.Flags().String(flagProposalID, "", "proposalID of proposal depositing on") + cmd.Flags().String(flagDepositer, "", "depositer of deposit") + cmd.Flags().String(flagDeposit, "", "amount of deposit") + + return cmd +} + +// set a new Vote transaction +func GetCmdVote(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "vote", + Short: "vote for an active proposal, options: Yes/No/NoWithVeto/Abstain", + RunE: func(cmd *cobra.Command, args []string) error { + + bechVoter := viper.GetString(flagVoter) + voter, err := sdk.AccAddressFromBech32(bechVoter) + if err != nil { + return err + } + + proposalID := viper.GetInt64(flagProposalID) + + option := viper.GetString(flagOption) + + byteVoteOption, err := gov.StringToVoteOption(option) + if err != nil { + return err + } + + // create the message + msg := gov.NewMsgVote(voter, proposalID, byteVoteOption) + + err = msg.ValidateBasic() + if err != nil { + return err + } + + fmt.Printf("Vote[Voter:%s,ProposalID:%d,Option:%s]", bechVoter, msg.ProposalID, gov.VoteOptionToString(msg.Option)) + + // build and sign the transaction, then broadcast to Tendermint + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + if err != nil { + return err + } + return nil + }, + } + + cmd.Flags().String(flagProposalID, "", "proposalID of proposal voting on") + cmd.Flags().String(flagVoter, "", "bech32 voter address") + cmd.Flags().String(flagOption, "", "vote option {Yes, No, NoWithVeto, Abstain}") + + return cmd +} + +// Command to Get a Proposal Information +func GetCmdQueryProposal(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "query-proposal", + Short: "query proposal details", + RunE: func(cmd *cobra.Command, args []string) error { + proposalID := viper.GetInt64(flagProposalID) + + ctx := context.NewCoreContextFromViper() + + res, err := ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + if len(res) == 0 || err != nil { + return errors.Errorf("proposalID [%d] is not existed", proposalID) + } + + var proposal gov.Proposal + cdc.MustUnmarshalBinary(res, &proposal) + proposalRest := gov.ProposalToRest(proposal) + output, err := wire.MarshalJSONIndent(cdc, proposalRest) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + }, + } + + cmd.Flags().String(flagProposalID, "", "proposalID of proposal being queried") + + return cmd +} + +// Command to Get a Proposal Information +func GetCmdQueryVote(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "query-vote", + Short: "query vote", + RunE: func(cmd *cobra.Command, args []string) error { + proposalID := viper.GetInt64(flagProposalID) + + voterAddr, err := sdk.AccAddressFromBech32(viper.GetString(flagVoter)) + if err != nil { + return err + } + + ctx := context.NewCoreContextFromViper() + + res, err := ctx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName) + if len(res) == 0 || err != nil { + return errors.Errorf("proposalID [%d] does not exist", proposalID) + } + + var vote gov.Vote + cdc.MustUnmarshalBinary(res, &vote) + voteRest := gov.VoteToRest(vote) + output, err := wire.MarshalJSONIndent(cdc, voteRest) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + }, + } + + cmd.Flags().String(flagProposalID, "", "proposalID of proposal voting on") + cmd.Flags().String(flagVoter, "", "bech32 voter address") + + return cmd +} diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go new file mode 100644 index 000000000..3e121d4d4 --- /dev/null +++ b/x/gov/client/rest/rest.go @@ -0,0 +1,449 @@ +package rest + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/gorilla/mux" + "github.com/pkg/errors" +) + +// REST Variable names +// nolint +const ( + RestProposalID = "proposalID" + RestDepositer = "depositer" + RestVoter = "voter" + storeName = "gov" +) + +// RegisterRoutes - Central function to define routes that get registered by the main application +func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) { + r.HandleFunc("/gov/proposals", postProposalHandlerFn(cdc, ctx)).Methods("POST") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), depositHandlerFn(cdc, ctx)).Methods("POST") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), voteHandlerFn(cdc, ctx)).Methods("POST") + + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(cdc)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositer), queryDepositHandlerFn(cdc)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(cdc)).Methods("GET") + + r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(cdc)).Methods("GET") +} + +type postProposalReq struct { + BaseReq baseReq `json:"base_req"` + Title string `json:"title"` // Title of the proposal + Description string `json:"description"` // Description of the proposal + ProposalType string `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Proposer string `json:"proposer"` // Address of the proposer + InitialDeposit sdk.Coins `json:"initial_deposit"` // Coins to add to the proposal's deposit +} + +type depositReq struct { + BaseReq baseReq `json:"base_req"` + Depositer string `json:"depositer"` // Address of the depositer + Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit +} + +type voteReq struct { + BaseReq baseReq `json:"base_req"` + Voter string `json:"voter"` // address of the voter + Option string `json:"option"` // option from OptionSet chosen by the voter +} + +func postProposalHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req postProposalReq + err := buildReq(w, r, cdc, &req) + if err != nil { + return + } + + if !req.BaseReq.baseReqValidate(w) { + return + } + + proposer, err := sdk.AccAddressFromBech32(req.Proposer) + if err != nil { + writeErr(&w, http.StatusBadRequest, err.Error()) + return + } + + proposalTypeByte, err := gov.StringToProposalType(req.ProposalType) + if err != nil { + writeErr(&w, http.StatusBadRequest, err.Error()) + return + } + + // create the message + msg := gov.NewMsgSubmitProposal(req.Title, req.Description, proposalTypeByte, proposer, req.InitialDeposit) + err = msg.ValidateBasic() + if err != nil { + writeErr(&w, http.StatusBadRequest, err.Error()) + return + } + + // sign + signAndBuild(w, ctx, req.BaseReq, msg, cdc) + } +} + +func depositHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + + if len(strProposalID) == 0 { + w.WriteHeader(http.StatusBadRequest) + err := errors.New("proposalId required but not specified") + w.Write([]byte(err.Error())) + return + } + + proposalID, err := strconv.ParseInt(strProposalID, 10, 64) + if err != nil { + err := errors.Errorf("proposalID [%d] is not positive", proposalID) + w.Write([]byte(err.Error())) + return + } + + var req depositReq + err = buildReq(w, r, cdc, &req) + if err != nil { + return + } + + if !req.BaseReq.baseReqValidate(w) { + return + } + + depositer, err := sdk.AccAddressFromBech32(req.Depositer) + if err != nil { + writeErr(&w, http.StatusBadRequest, err.Error()) + return + } + + // create the message + msg := gov.NewMsgDeposit(depositer, proposalID, req.Amount) + err = msg.ValidateBasic() + if err != nil { + writeErr(&w, http.StatusBadRequest, err.Error()) + return + } + + // sign + signAndBuild(w, ctx, req.BaseReq, msg, cdc) + } +} + +func voteHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + + if len(strProposalID) == 0 { + w.WriteHeader(http.StatusBadRequest) + err := errors.New("proposalId required but not specified") + w.Write([]byte(err.Error())) + return + } + + proposalID, err := strconv.ParseInt(strProposalID, 10, 64) + if err != nil { + err := errors.Errorf("proposalID [%d] is not positive", proposalID) + w.Write([]byte(err.Error())) + return + } + + var req voteReq + err = buildReq(w, r, cdc, &req) + if err != nil { + return + } + + if !req.BaseReq.baseReqValidate(w) { + return + } + + voter, err := sdk.AccAddressFromBech32(req.Voter) + if err != nil { + writeErr(&w, http.StatusBadRequest, err.Error()) + return + } + + voteOptionByte, err := gov.StringToVoteOption(req.Option) + if err != nil { + writeErr(&w, http.StatusBadRequest, err.Error()) + return + } + + // create the message + msg := gov.NewMsgVote(voter, proposalID, voteOptionByte) + err = msg.ValidateBasic() + if err != nil { + writeErr(&w, http.StatusBadRequest, err.Error()) + return + } + + // sign + signAndBuild(w, ctx, req.BaseReq, msg, cdc) + } +} + +func queryProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + + if len(strProposalID) == 0 { + w.WriteHeader(http.StatusBadRequest) + err := errors.New("proposalId required but not specified") + w.Write([]byte(err.Error())) + return + } + + proposalID, err := strconv.ParseInt(strProposalID, 10, 64) + if err != nil { + err := errors.Errorf("proposalID [%d] is not positive", proposalID) + w.Write([]byte(err.Error())) + return + } + + ctx := context.NewCoreContextFromViper() + + res, err := ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + if err != nil || len(res) == 0 { + err := errors.Errorf("proposalID [%d] does not exist", proposalID) + w.Write([]byte(err.Error())) + return + } + + var proposal gov.Proposal + cdc.MustUnmarshalBinary(res, &proposal) + proposalRest := gov.ProposalToRest(proposal) + output, err := wire.MarshalJSONIndent(cdc, proposalRest) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + w.Write(output) + } +} + +func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + bechDepositerAddr := vars[RestDepositer] + + if len(strProposalID) == 0 { + w.WriteHeader(http.StatusBadRequest) + err := errors.New("proposalId required but not specified") + w.Write([]byte(err.Error())) + return + } + + proposalID, err := strconv.ParseInt(strProposalID, 10, 64) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + err := errors.Errorf("proposalID [%d] is not positive", proposalID) + w.Write([]byte(err.Error())) + return + } + + if len(bechDepositerAddr) == 0 { + w.WriteHeader(http.StatusBadRequest) + err := errors.New("depositer address required but not specified") + w.Write([]byte(err.Error())) + return + } + + depositerAddr, err := sdk.AccAddressFromBech32(bechDepositerAddr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + err := errors.Errorf("'%s' needs to be bech32 encoded", RestDepositer) + w.Write([]byte(err.Error())) + return + } + + ctx := context.NewCoreContextFromViper() + + res, err := ctx.QueryStore(gov.KeyDeposit(proposalID, depositerAddr), storeName) + if err != nil || len(res) == 0 { + res, err := ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + if err != nil || len(res) == 0 { + w.WriteHeader(http.StatusNotFound) + err := errors.Errorf("proposalID [%d] does not exist", proposalID) + w.Write([]byte(err.Error())) + return + } + w.WriteHeader(http.StatusNotFound) + err = errors.Errorf("depositer [%s] did not deposit on proposalID [%d]", bechDepositerAddr, proposalID) + w.Write([]byte(err.Error())) + return + } + + var deposit gov.Deposit + cdc.MustUnmarshalBinary(res, &deposit) + depositRest := gov.DepositToRest(deposit) + output, err := wire.MarshalJSONIndent(cdc, depositRest) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + w.Write(output) + } +} + +func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + bechVoterAddr := vars[RestVoter] + + if len(strProposalID) == 0 { + w.WriteHeader(http.StatusBadRequest) + err := errors.New("proposalId required but not specified") + w.Write([]byte(err.Error())) + return + } + + proposalID, err := strconv.ParseInt(strProposalID, 10, 64) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + err := errors.Errorf("proposalID [%s] is not positive", proposalID) + w.Write([]byte(err.Error())) + return + } + + if len(bechVoterAddr) == 0 { + w.WriteHeader(http.StatusBadRequest) + err := errors.New("voter address required but not specified") + w.Write([]byte(err.Error())) + return + } + + voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + err := errors.Errorf("'%s' needs to be bech32 encoded", RestVoter) + w.Write([]byte(err.Error())) + return + } + + ctx := context.NewCoreContextFromViper() + + res, err := ctx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName) + if err != nil || len(res) == 0 { + + res, err := ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + if err != nil || len(res) == 0 { + w.WriteHeader(http.StatusNotFound) + err := errors.Errorf("proposalID [%d] does not exist", proposalID) + w.Write([]byte(err.Error())) + return + } + w.WriteHeader(http.StatusNotFound) + err = errors.Errorf("voter [%s] did not vote on proposalID [%d]", bechVoterAddr, proposalID) + w.Write([]byte(err.Error())) + return + } + + var vote gov.Vote + cdc.MustUnmarshalBinary(res, &vote) + voteRest := gov.VoteToRest(vote) + output, err := wire.MarshalJSONIndent(cdc, voteRest) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + w.Write(output) + } +} + +// nolint: gocyclo +// todo: Split this functionality into helper functions to remove the above +func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + bechVoterAddr := r.URL.Query().Get(RestVoter) + bechDepositerAddr := r.URL.Query().Get(RestDepositer) + + var err error + var voterAddr sdk.AccAddress + var depositerAddr sdk.AccAddress + + if len(bechVoterAddr) != 0 { + voterAddr, err = sdk.AccAddressFromBech32(bechVoterAddr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + err := errors.Errorf("'%s' needs to be bech32 encoded", RestVoter) + w.Write([]byte(err.Error())) + return + } + } + + if len(bechDepositerAddr) != 0 { + depositerAddr, err = sdk.AccAddressFromBech32(bechDepositerAddr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + err := errors.Errorf("'%s' needs to be bech32 encoded", RestDepositer) + w.Write([]byte(err.Error())) + return + } + } + + ctx := context.NewCoreContextFromViper() + + res, err := ctx.QueryStore(gov.KeyNextProposalID, storeName) + if err != nil { + err = errors.New("no proposals exist yet and proposalID has not been set") + w.Write([]byte(err.Error())) + return + } + var maxProposalID int64 + cdc.MustUnmarshalBinary(res, &maxProposalID) + + matchingProposals := []gov.ProposalRest{} + + for proposalID := int64(0); proposalID < maxProposalID; proposalID++ { + if voterAddr != nil { + res, err = ctx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName) + if err != nil || len(res) == 0 { + continue + } + } + + if depositerAddr != nil { + res, err = ctx.QueryStore(gov.KeyDeposit(proposalID, depositerAddr), storeName) + if err != nil || len(res) == 0 { + continue + } + } + + res, err = ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + if err != nil || len(res) == 0 { + continue + } + var proposal gov.Proposal + cdc.MustUnmarshalBinary(res, &proposal) + + matchingProposals = append(matchingProposals, gov.ProposalToRest(proposal)) + } + + output, err := wire.MarshalJSONIndent(cdc, matchingProposals) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + w.Write(output) + } +} diff --git a/x/gov/client/rest/util.go b/x/gov/client/rest/util.go new file mode 100644 index 000000000..341f0b057 --- /dev/null +++ b/x/gov/client/rest/util.go @@ -0,0 +1,99 @@ +package rest + +import ( + "io/ioutil" + "net/http" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/pkg/errors" +) + +type baseReq struct { + Name string `json:"name"` + Password string `json:"password"` + ChainID string `json:"chain_id"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` + Gas int64 `json:"gas"` +} + +func buildReq(w http.ResponseWriter, r *http.Request, cdc *wire.Codec, req interface{}) error { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + writeErr(&w, http.StatusBadRequest, err.Error()) + return err + } + err = cdc.UnmarshalJSON(body, req) + if err != nil { + writeErr(&w, http.StatusBadRequest, err.Error()) + return err + } + return nil +} + +func (req baseReq) baseReqValidate(w http.ResponseWriter) bool { + if len(req.Name) == 0 { + writeErr(&w, http.StatusUnauthorized, "Name required but not specified") + return false + } + + if len(req.Password) == 0 { + writeErr(&w, http.StatusUnauthorized, "Password required but not specified") + return false + } + + if len(req.ChainID) == 0 { + writeErr(&w, http.StatusUnauthorized, "ChainID required but not specified") + return false + } + + if req.AccountNumber < 0 { + writeErr(&w, http.StatusUnauthorized, "Account Number required but not specified") + return false + } + + if req.Sequence < 0 { + writeErr(&w, http.StatusUnauthorized, "Sequence required but not specified") + return false + } + return true +} + +func writeErr(w *http.ResponseWriter, status int, msg string) { + (*w).WriteHeader(status) + err := errors.New(msg) + (*w).Write([]byte(err.Error())) +} + +// TODO: Build this function out into a more generic base-request (probably should live in client/lcd) +func signAndBuild(w http.ResponseWriter, ctx context.CoreContext, baseReq baseReq, msg sdk.Msg, cdc *wire.Codec) { + ctx = ctx.WithAccountNumber(baseReq.AccountNumber) + ctx = ctx.WithSequence(baseReq.Sequence) + ctx = ctx.WithChainID(baseReq.ChainID) + + // add gas to context + ctx = ctx.WithGas(baseReq.Gas) + + txBytes, err := ctx.SignAndBuild(baseReq.Name, baseReq.Password, []sdk.Msg{msg}, cdc) + if err != nil { + writeErr(&w, http.StatusUnauthorized, err.Error()) + return + } + + // send + res, err := ctx.BroadcastTx(txBytes) + if err != nil { + writeErr(&w, http.StatusInternalServerError, err.Error()) + return + } + + output, err := wire.MarshalJSONIndent(cdc, res) + if err != nil { + writeErr(&w, http.StatusInternalServerError, err.Error()) + return + } + + w.Write(output) +} diff --git a/x/gov/depositsvotes.go b/x/gov/depositsvotes.go new file mode 100644 index 000000000..5ec565189 --- /dev/null +++ b/x/gov/depositsvotes.go @@ -0,0 +1,108 @@ +package gov + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Type that represents VoteOption as a byte +type VoteOption = byte + +//nolint +const ( + OptionEmpty VoteOption = 0x00 + OptionYes VoteOption = 0x01 + OptionAbstain VoteOption = 0x02 + OptionNo VoteOption = 0x03 + OptionNoWithVeto VoteOption = 0x04 +) + +// Vote +type Vote struct { + Voter sdk.AccAddress `json:"voter"` // address of the voter + ProposalID int64 `json:"proposal_id"` // proposalID of the proposal + Option VoteOption `json:"option"` // option from OptionSet chosen by the voter +} + +// Deposit +type Deposit struct { + Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer + ProposalID int64 `json:"proposal_id"` // proposalID of the proposal + Amount sdk.Coins `json:"amount"` // Deposit amount +} + +// ProposalTypeToString for pretty prints of ProposalType +func VoteOptionToString(option VoteOption) string { + switch option { + case OptionYes: + return "Yes" + case OptionAbstain: + return "Abstain" + case OptionNo: + return "No" + case OptionNoWithVeto: + return "NoWithVeto" + default: + return "" + } +} + +func validVoteOption(option VoteOption) bool { + if option == OptionYes || + option == OptionAbstain || + option == OptionNo || + option == OptionNoWithVeto { + return true + } + return false +} + +// String to proposalType byte. Returns ff if invalid. +func StringToVoteOption(str string) (VoteOption, sdk.Error) { + switch str { + case "Yes": + return OptionYes, nil + case "Abstain": + return OptionAbstain, nil + case "No": + return OptionNo, nil + case "NoWithVeto": + return OptionNoWithVeto, nil + default: + return VoteOption(0xff), ErrInvalidVote(DefaultCodespace, str) + } +} + +//----------------------------------------------------------- +// REST + +// Rest Deposits +type DepositRest struct { + Depositer sdk.AccAddress `json:"depositer"` // address of the depositer + ProposalID int64 `json:"proposal_id"` // proposalID of the proposal + Amount sdk.Coins `json:"option"` +} + +// Turn any Deposit to a DepositRest +func DepositToRest(deposit Deposit) DepositRest { + return DepositRest{ + Depositer: deposit.Depositer, + ProposalID: deposit.ProposalID, + Amount: deposit.Amount, + } +} + +// Rest Votes +type VoteRest struct { + Voter sdk.AccAddress `json:"voter"` // address of the voter + ProposalID int64 `json:"proposal_id"` // proposalID of the proposal + Option string `json:"option"` +} + +// Turn any Vote to a VoteRest +func VoteToRest(vote Vote) VoteRest { + return VoteRest{ + Voter: vote.Voter, + ProposalID: vote.ProposalID, + Option: VoteOptionToString(vote.Option), + } +} diff --git a/x/gov/endblocker_test.go b/x/gov/endblocker_test.go new file mode 100644 index 000000000..0070bb577 --- /dev/null +++ b/x/gov/endblocker_test.go @@ -0,0 +1,168 @@ +package gov + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +func TestTickExpiredDepositPeriod(t *testing.T) { + mapp, keeper, _, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + govHandler := NewHandler(keeper) + + require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) + require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + + newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin("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)) + + ctx = ctx.WithBlockHeight(10) + EndBlocker(ctx, keeper) + require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) + require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + + ctx = ctx.WithBlockHeight(250) + require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) + require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) + EndBlocker(ctx, keeper) + require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) + require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) +} + +func TestTickMultipleExpiredDepositPeriod(t *testing.T) { + mapp, keeper, _, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + govHandler := NewHandler(keeper) + + require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) + require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + + newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin("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)) + + ctx = ctx.WithBlockHeight(10) + EndBlocker(ctx, keeper) + require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) + require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + + newProposalMsg2 := NewMsgSubmitProposal("Test2", "test2", ProposalTypeText, addrs[1], sdk.Coins{sdk.NewCoin("steak", 5)}) + res = govHandler(ctx, newProposalMsg2) + require.True(t, res.IsOK()) + + ctx = ctx.WithBlockHeight(205) + require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) + require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) + EndBlocker(ctx, keeper) + require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) + require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + + ctx = ctx.WithBlockHeight(215) + require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) + require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) + EndBlocker(ctx, keeper) + require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) + require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) +} + +func TestTickPassedDepositPeriod(t *testing.T) { + mapp, keeper, _, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + 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)) + + newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin("steak", 5)}) + + res := govHandler(ctx, newProposalMsg) + require.True(t, res.IsOK()) + var proposalID int64 + keeper.cdc.UnmarshalBinaryBare(res.Data, &proposalID) + + EndBlocker(ctx, keeper) + require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) + require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + + ctx = ctx.WithBlockHeight(10) + EndBlocker(ctx, keeper) + require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) + require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + + newDepositMsg := NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewCoin("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)) +} + +func TestTickPassedVotingPeriod(t *testing.T) { + mapp, keeper, _, addrs, _, _ := getMockApp(t, 10) + SortAddresses(addrs) + mapp.BeginBlock(abci.RequestBeginBlock{}) + 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)) + + newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin("steak", 5)}) + + res := govHandler(ctx, newProposalMsg) + require.True(t, res.IsOK()) + var proposalID int64 + keeper.cdc.UnmarshalBinaryBare(res.Data, &proposalID) + + ctx = ctx.WithBlockHeight(10) + newDepositMsg := NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewCoin("steak", 5)}) + res = govHandler(ctx, newDepositMsg) + require.True(t, res.IsOK()) + + EndBlocker(ctx, keeper) + + ctx = ctx.WithBlockHeight(215) + require.True(t, shouldPopActiveProposalQueue(ctx, keeper)) + depositsIterator := keeper.GetDeposits(ctx, proposalID) + require.True(t, depositsIterator.Valid()) + depositsIterator.Close() + require.Equal(t, StatusVotingPeriod, keeper.GetProposal(ctx, proposalID).GetStatus()) + + 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()) +} diff --git a/x/gov/errors.go b/x/gov/errors.go new file mode 100644 index 000000000..e1f2f2793 --- /dev/null +++ b/x/gov/errors.go @@ -0,0 +1,66 @@ +//nolint +package gov + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + DefaultCodespace sdk.CodespaceType = 5 + + CodeUnknownProposal sdk.CodeType = 1 + CodeInactiveProposal sdk.CodeType = 2 + CodeAlreadyActiveProposal sdk.CodeType = 3 + CodeAlreadyFinishedProposal sdk.CodeType = 4 + CodeAddressNotStaked sdk.CodeType = 5 + CodeInvalidTitle sdk.CodeType = 6 + CodeInvalidDescription sdk.CodeType = 7 + CodeInvalidProposalType sdk.CodeType = 8 + CodeInvalidVote sdk.CodeType = 9 + CodeInvalidGenesis sdk.CodeType = 10 +) + +//---------------------------------------- +// Error constructors + +func ErrUnknownProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error { + return sdk.NewError(codespace, CodeUnknownProposal, fmt.Sprintf("Unknown proposal - %d", proposalID)) +} + +func ErrInactiveProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error { + return sdk.NewError(codespace, CodeInactiveProposal, fmt.Sprintf("Inactive proposal - %d", proposalID)) +} + +func ErrAlreadyActiveProposal(codespace sdk.CodespaceType, proposalID int64) 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 { + return sdk.NewError(codespace, CodeAlreadyFinishedProposal, fmt.Sprintf("Proposal %d has already passed its voting period", proposalID)) +} + +func ErrAddressNotStaked(codespace sdk.CodespaceType, address sdk.AccAddress) sdk.Error { + return sdk.NewError(codespace, CodeAddressNotStaked, fmt.Sprintf("Address %s is not staked and is thus ineligible to vote", address)) +} + +func ErrInvalidTitle(codespace sdk.CodespaceType, title string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidTitle, fmt.Sprintf("Proposal Title '%s' is not valid", title)) +} + +func ErrInvalidDescription(codespace sdk.CodespaceType, description string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDescription, fmt.Sprintf("Proposal Desciption '%s' is not valid", description)) +} + +func ErrInvalidProposalType(codespace sdk.CodespaceType, strProposalType string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidProposalType, fmt.Sprintf("Proposal Type '%s' is not valid", strProposalType)) +} + +func ErrInvalidVote(codespace sdk.CodespaceType, strOption string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidVote, fmt.Sprintf("'%s' is not a valid voting option", strOption)) +} + +func ErrInvalidGenesis(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidVote, msg) +} diff --git a/x/gov/genesis.go b/x/gov/genesis.go new file mode 100644 index 000000000..40218ca86 --- /dev/null +++ b/x/gov/genesis.go @@ -0,0 +1,41 @@ +package gov + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GenesisState - all staking state that must be provided at genesis +type GenesisState struct { + StartingProposalID int64 `json:"starting_proposalID"` +} + +func NewGenesisState(startingProposalID int64) GenesisState { + return GenesisState{ + StartingProposalID: startingProposalID, + } +} + +// get raw genesis raw message for testing +func DefaultGenesisState() GenesisState { + return GenesisState{ + StartingProposalID: 1, + } +} + +// InitGenesis - store genesis parameters +func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { + err := k.setInitialProposalID(ctx, data.StartingProposalID) + if err != nil { + // TODO: Handle this with #870 + panic(err) + } +} + +// WriteGenesis - output genesis parameters +func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState { + initalProposalID, _ := k.getNewProposalID(ctx) + + return GenesisState{ + initalProposalID, + } +} diff --git a/x/gov/handler.go b/x/gov/handler.go new file mode 100644 index 000000000..636454571 --- /dev/null +++ b/x/gov/handler.go @@ -0,0 +1,162 @@ +package gov + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Handle all "gov" type messages. +func NewHandler(keeper Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + switch msg := msg.(type) { + case MsgDeposit: + return handleMsgDeposit(ctx, keeper, msg) + case MsgSubmitProposal: + return handleMsgSubmitProposal(ctx, keeper, msg) + case MsgVote: + return handleMsgVote(ctx, keeper, msg) + default: + errMsg := "Unrecognized gov msg type" + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} + +func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitProposal) sdk.Result { + + proposal := keeper.NewTextProposal(ctx, msg.Title, msg.Description, msg.ProposalType) + + err, votingStarted := keeper.AddDeposit(ctx, proposal.GetProposalID(), msg.Proposer, msg.InitialDeposit) + if err != nil { + return err.Result() + } + + proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(proposal.GetProposalID()) + + tags := sdk.NewTags( + "action", []byte("submitProposal"), + "proposer", []byte(msg.Proposer.String()), + "proposalId", proposalIDBytes, + ) + + if votingStarted { + tags.AppendTag("votingPeriodStart", proposalIDBytes) + } + + return sdk.Result{ + Data: proposalIDBytes, + Tags: tags, + } +} + +func handleMsgDeposit(ctx sdk.Context, keeper Keeper, msg MsgDeposit) sdk.Result { + + err, votingStarted := keeper.AddDeposit(ctx, msg.ProposalID, msg.Depositer, msg.Amount) + if err != nil { + return err.Result() + } + + proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(msg.ProposalID) + + // TODO: Add tag for if voting period started + tags := sdk.NewTags( + "action", []byte("deposit"), + "depositer", []byte(msg.Depositer.String()), + "proposalId", proposalIDBytes, + ) + + if votingStarted { + tags.AppendTag("votingPeriodStart", proposalIDBytes) + } + + return sdk.Result{ + Tags: tags, + } +} + +func handleMsgVote(ctx sdk.Context, keeper Keeper, msg MsgVote) sdk.Result { + + err := keeper.AddVote(ctx, msg.ProposalID, msg.Voter, msg.Option) + if err != nil { + return err.Result() + } + + proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(msg.ProposalID) + + tags := sdk.NewTags( + "action", []byte("vote"), + "voter", []byte(msg.Voter.String()), + "proposalId", proposalIDBytes, + ) + return sdk.Result{ + Tags: tags, + } +} + +// Called every block, process inflation, update validator set +func EndBlocker(ctx sdk.Context, keeper Keeper) (tags sdk.Tags, nonVotingVals []sdk.AccAddress) { + + tags = sdk.NewTags() + + // Delete proposals that haven't met minDeposit + for shouldPopInactiveProposalQueue(ctx, keeper) { + inactiveProposal := keeper.InactiveProposalQueuePop(ctx) + if inactiveProposal.GetStatus() == StatusDepositPeriod { + proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(inactiveProposal.GetProposalID()) + keeper.DeleteProposal(ctx, inactiveProposal) + tags.AppendTag("action", []byte("proposalDropped")) + tags.AppendTag("proposalId", proposalIDBytes) + } + } + + var passes bool + + // Check if earliest Active Proposal ended voting period yet + for shouldPopActiveProposalQueue(ctx, keeper) { + activeProposal := keeper.ActiveProposalQueuePop(ctx) + + if ctx.BlockHeight() >= activeProposal.GetVotingStartBlock()+keeper.GetVotingProcedure().VotingPeriod { + passes, nonVotingVals = tally(ctx, keeper, activeProposal) + proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(activeProposal.GetProposalID()) + if passes { + keeper.RefundDeposits(ctx, activeProposal.GetProposalID()) + activeProposal.SetStatus(StatusPassed) + tags.AppendTag("action", []byte("proposalPassed")) + tags.AppendTag("proposalId", proposalIDBytes) + } else { + keeper.DeleteDeposits(ctx, activeProposal.GetProposalID()) + activeProposal.SetStatus(StatusRejected) + tags.AppendTag("action", []byte("proposalRejected")) + tags.AppendTag("proposalId", proposalIDBytes) + } + + keeper.SetProposal(ctx, activeProposal) + } + } + + return tags, nonVotingVals +} +func shouldPopInactiveProposalQueue(ctx sdk.Context, keeper Keeper) bool { + depositProcedure := keeper.GetDepositProcedure() + peekProposal := keeper.InactiveProposalQueuePeek(ctx) + + if peekProposal == nil { + return false + } else if peekProposal.GetStatus() != StatusDepositPeriod { + return true + } else if ctx.BlockHeight() >= peekProposal.GetSubmitBlock()+depositProcedure.MaxDepositPeriod { + return true + } + return false +} + +func shouldPopActiveProposalQueue(ctx sdk.Context, keeper Keeper) bool { + votingProcedure := keeper.GetVotingProcedure() + peekProposal := keeper.ActiveProposalQueuePeek(ctx) + + if peekProposal == nil { + return false + } else if ctx.BlockHeight() >= peekProposal.GetVotingStartBlock()+votingProcedure.VotingPeriod { + return true + } + return false +} diff --git a/x/gov/keeper.go b/x/gov/keeper.go new file mode 100644 index 000000000..d966a78a2 --- /dev/null +++ b/x/gov/keeper.go @@ -0,0 +1,407 @@ +package gov + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + wire "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/bank" +) + +// Governance Keeper +type Keeper struct { + // The reference to the CoinKeeper to modify balances + ck bank.Keeper + + // The ValidatorSet to get information about validators + vs sdk.ValidatorSet + + // The reference to the DelegationSet to get information about delegators + ds sdk.DelegationSet + + // The (unexposed) keys used to access the stores from the Context. + storeKey sdk.StoreKey + + // The wire codec for binary encoding/decoding. + cdc *wire.Codec + + // Reserved codespace + codespace sdk.CodespaceType +} + +// NewGovernanceMapper returns a mapper that uses go-wire to (binary) encode and decode gov types. +func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, ds sdk.DelegationSet, codespace sdk.CodespaceType) Keeper { + return Keeper{ + storeKey: key, + ck: ck, + ds: ds, + vs: ds.GetValidatorSet(), + cdc: cdc, + codespace: codespace, + } +} + +// Returns the go-wire codec. +func (keeper Keeper) WireCodec() *wire.Codec { + return keeper.cdc +} + +// ===================================================== +// Proposals + +// Creates a NewProposal +func (keeper Keeper) NewTextProposal(ctx sdk.Context, title string, description string, proposalType byte) Proposal { + proposalID, err := keeper.getNewProposalID(ctx) + if err != nil { + return nil + } + var proposal Proposal = &TextProposal{ + ProposalID: proposalID, + Title: title, + Description: description, + ProposalType: proposalType, + Status: StatusDepositPeriod, + TotalDeposit: sdk.Coins{}, + SubmitBlock: ctx.BlockHeight(), + VotingStartBlock: -1, // TODO: Make Time + } + keeper.SetProposal(ctx, proposal) + keeper.InactiveProposalQueuePush(ctx, proposal) + return proposal +} + +// Get Proposal from store by ProposalID +func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID int64) Proposal { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeyProposal(proposalID)) + if bz == nil { + return nil + } + + var proposal Proposal + keeper.cdc.MustUnmarshalBinary(bz, &proposal) + + return proposal +} + +// Implements sdk.AccountMapper. +func (keeper Keeper) SetProposal(ctx sdk.Context, proposal Proposal) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinary(proposal) + store.Set(KeyProposal(proposal.GetProposalID()), bz) +} + +// Implements sdk.AccountMapper. +func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposal Proposal) { + store := ctx.KVStore(keeper.storeKey) + store.Delete(KeyProposal(proposal.GetProposalID())) +} + +func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID int64) 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) + store.Set(KeyNextProposalID, bz) + return nil +} + +func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID int64, err sdk.Error) { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeyNextProposalID) + if bz == nil { + return -1, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") + } + keeper.cdc.MustUnmarshalBinary(bz, &proposalID) + bz = keeper.cdc.MustMarshalBinary(proposalID + 1) + store.Set(KeyNextProposalID, bz) + return proposalID, nil +} + +func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) { + proposal.SetVotingStartBlock(ctx.BlockHeight()) + proposal.SetStatus(StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + keeper.ActiveProposalQueuePush(ctx, proposal) +} + +// ===================================================== +// Procedures + +// Gets procedure from store. TODO: move to global param store and allow for updating of this +func (keeper Keeper) GetDepositProcedure() DepositProcedure { + return DepositProcedure{ + MinDeposit: sdk.Coins{sdk.NewCoin("steak", 10)}, + MaxDepositPeriod: 200, + } +} + +// Gets procedure from store. TODO: move to global param store and allow for updating of this +func (keeper Keeper) GetVotingProcedure() VotingProcedure { + return VotingProcedure{ + VotingPeriod: 200, + } +} + +// Gets procedure from store. TODO: move to global param store and allow for updating of this +func (keeper Keeper) GetTallyingProcedure() TallyingProcedure { + return TallyingProcedure{ + Threshold: sdk.NewRat(1, 2), + Veto: sdk.NewRat(1, 3), + GovernancePenalty: sdk.NewRat(1, 100), + } +} + +// ===================================================== +// Votes + +// Adds a vote on a specific proposal +func (keeper Keeper) AddVote(ctx sdk.Context, proposalID int64, voterAddr sdk.AccAddress, option VoteOption) sdk.Error { + proposal := keeper.GetProposal(ctx, proposalID) + if proposal == nil { + return ErrUnknownProposal(keeper.codespace, proposalID) + } + if proposal.GetStatus() != StatusVotingPeriod { + return ErrInactiveProposal(keeper.codespace, proposalID) + } + + if option != OptionYes && option != OptionAbstain && option != OptionNo && option != OptionNoWithVeto { + return ErrInvalidVote(keeper.codespace, VoteOptionToString(option)) + } + + vote := Vote{ + ProposalID: proposalID, + Voter: voterAddr, + Option: option, + } + keeper.setVote(ctx, proposalID, voterAddr, vote) + + return nil +} + +// 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) { + 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) + return vote, true +} + +func (keeper Keeper) setVote(ctx sdk.Context, proposalID int64, voterAddr sdk.AccAddress, vote Vote) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinary(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 { + store := ctx.KVStore(keeper.storeKey) + return sdk.KVStorePrefixIterator(store, KeyVotesSubspace(proposalID)) +} + +func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID int64, voterAddr sdk.AccAddress) { + store := ctx.KVStore(keeper.storeKey) + store.Delete(KeyVote(proposalID, voterAddr)) +} + +// ===================================================== +// 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) { + 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) + return deposit, true +} + +func (keeper Keeper) setDeposit(ctx sdk.Context, proposalID int64, depositerAddr sdk.AccAddress, deposit Deposit) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinary(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) { + // Checks to see if proposal exists + proposal := keeper.GetProposal(ctx, proposalID) + if proposal == nil { + return ErrUnknownProposal(keeper.codespace, proposalID), false + } + + // Check if proposal is still depositable + if (proposal.GetStatus() != StatusDepositPeriod) && (proposal.GetStatus() != StatusVotingPeriod) { + return ErrAlreadyFinishedProposal(keeper.codespace, proposalID), false + } + + // Subtract coins from depositer's account + _, _, err := keeper.ck.SubtractCoins(ctx, depositerAddr, depositAmount) + if err != nil { + return err, false + } + + // Update Proposal + proposal.SetTotalDeposit(proposal.GetTotalDeposit().Plus(depositAmount)) + keeper.SetProposal(ctx, proposal) + + // Check if deposit tipped proposal into voting period + // Active voting period if so + activatedVotingPeriod := false + if proposal.GetStatus() == StatusDepositPeriod && proposal.GetTotalDeposit().IsGTE(keeper.GetDepositProcedure().MinDeposit) { + keeper.activateVotingPeriod(ctx, proposal) + activatedVotingPeriod = true + } + + // Add or update deposit object + currDeposit, found := keeper.GetDeposit(ctx, proposalID, depositerAddr) + if !found { + newDeposit := Deposit{depositerAddr, proposalID, depositAmount} + keeper.setDeposit(ctx, proposalID, depositerAddr, newDeposit) + } else { + currDeposit.Amount = currDeposit.Amount.Plus(depositAmount) + keeper.setDeposit(ctx, proposalID, depositerAddr, currDeposit) + } + + return nil, activatedVotingPeriod +} + +// Gets all the deposits on a specific proposal +func (keeper Keeper) GetDeposits(ctx sdk.Context, proposalID int64) 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) { + store := ctx.KVStore(keeper.storeKey) + depositsIterator := keeper.GetDeposits(ctx, proposalID) + + for ; depositsIterator.Valid(); depositsIterator.Next() { + deposit := &Deposit{} + keeper.cdc.MustUnmarshalBinary(depositsIterator.Value(), deposit) + + _, _, err := keeper.ck.AddCoins(ctx, deposit.Depositer, deposit.Amount) + if err != nil { + panic("should not happen") + } + + store.Delete(depositsIterator.Key()) + } + + depositsIterator.Close() +} + +// Deletes all the deposits on a specific proposal without refunding them +func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID int64) { + store := ctx.KVStore(keeper.storeKey) + depositsIterator := keeper.GetDeposits(ctx, proposalID) + + for ; depositsIterator.Valid(); depositsIterator.Next() { + store.Delete(depositsIterator.Key()) + } + + depositsIterator.Close() +} + +// ===================================================== +// ProposalQueues + +func (keeper Keeper) getActiveProposalQueue(ctx sdk.Context) ProposalQueue { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeyActiveProposalQueue) + if bz == nil { + return nil + } + + var proposalQueue ProposalQueue + keeper.cdc.MustUnmarshalBinary(bz, &proposalQueue) + + return proposalQueue +} + +func (keeper Keeper) setActiveProposalQueue(ctx sdk.Context, proposalQueue ProposalQueue) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinary(proposalQueue) + store.Set(KeyActiveProposalQueue, bz) +} + +// Return the Proposal at the front of the ProposalQueue +func (keeper Keeper) ActiveProposalQueuePeek(ctx sdk.Context) Proposal { + proposalQueue := keeper.getActiveProposalQueue(ctx) + if len(proposalQueue) == 0 { + return nil + } + return keeper.GetProposal(ctx, proposalQueue[0]) +} + +// Remove and return a Proposal from the front of the ProposalQueue +func (keeper Keeper) ActiveProposalQueuePop(ctx sdk.Context) Proposal { + proposalQueue := keeper.getActiveProposalQueue(ctx) + if len(proposalQueue) == 0 { + return nil + } + frontElement, proposalQueue := proposalQueue[0], proposalQueue[1:] + keeper.setActiveProposalQueue(ctx, proposalQueue) + return keeper.GetProposal(ctx, frontElement) +} + +// 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 { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeyInactiveProposalQueue) + if bz == nil { + return nil + } + + var proposalQueue ProposalQueue + + keeper.cdc.MustUnmarshalBinary(bz, &proposalQueue) + + return proposalQueue +} + +func (keeper Keeper) setInactiveProposalQueue(ctx sdk.Context, proposalQueue ProposalQueue) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinary(proposalQueue) + store.Set(KeyInactiveProposalQueue, bz) +} + +// Return the Proposal at the front of the ProposalQueue +func (keeper Keeper) InactiveProposalQueuePeek(ctx sdk.Context) Proposal { + proposalQueue := keeper.getInactiveProposalQueue(ctx) + if len(proposalQueue) == 0 { + return nil + } + return keeper.GetProposal(ctx, proposalQueue[0]) +} + +// 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) +} diff --git a/x/gov/keeper_keys.go b/x/gov/keeper_keys.go new file mode 100644 index 000000000..7b1bf43f2 --- /dev/null +++ b/x/gov/keeper_keys.go @@ -0,0 +1,41 @@ +package gov + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// TODO remove some of these prefixes once have working multistore + +// Key for getting a the next available proposalID from the store +var ( + KeyNextProposalID = []byte("newProposalID") + KeyActiveProposalQueue = []byte("activeProposalQueue") + KeyInactiveProposalQueue = []byte("inactiveProposalQueue") +) + +// Key for getting a specific proposal from the store +func KeyProposal(proposalID int64) []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 { + 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 { + 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 { + return []byte(fmt.Sprintf("deposits:%d:", proposalID)) +} + +// Key for getting all votes on a proposal from the store +func KeyVotesSubspace(proposalID int64) []byte { + return []byte(fmt.Sprintf("votes:%d:", proposalID)) +} diff --git a/x/gov/keeper_test.go b/x/gov/keeper_test.go new file mode 100644 index 000000000..786953fd3 --- /dev/null +++ b/x/gov/keeper_test.go @@ -0,0 +1,247 @@ +package gov + +import ( + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestGetSetProposal(t *testing.T) { + mapp, keeper, _, _, _, _ := getMockApp(t, 0) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID := proposal.GetProposalID() + keeper.SetProposal(ctx, proposal) + + gotProposal := keeper.GetProposal(ctx, proposalID) + require.True(t, ProposalEqual(proposal, gotProposal)) +} + +func TestIncrementProposalNumber(t *testing.T) { + mapp, keeper, _, _, _, _ := getMockApp(t, 0) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + + keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposal6 := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + + require.Equal(t, int64(6), proposal6.GetProposalID()) +} + +func TestActivateVotingPeriod(t *testing.T) { + mapp, keeper, _, _, _, _ := getMockApp(t, 0) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + + require.Equal(t, int64(-1), proposal.GetVotingStartBlock()) + require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) + + keeper.activateVotingPeriod(ctx, proposal) + + require.Equal(t, proposal.GetVotingStartBlock(), ctx.BlockHeight()) + require.Equal(t, proposal.GetProposalID(), keeper.ActiveProposalQueuePeek(ctx).GetProposalID()) +} + +func TestDeposits(t *testing.T) { + mapp, keeper, _, addrs, _, _ := getMockApp(t, 2) + SortAddresses(addrs) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID := proposal.GetProposalID() + + fourSteak := sdk.Coins{sdk.NewCoin("steak", 4)} + fiveSteak := sdk.Coins{sdk.NewCoin("steak", 5)} + + addr0Initial := keeper.ck.GetCoins(ctx, addrs[0]) + addr1Initial := keeper.ck.GetCoins(ctx, addrs[1]) + + // require.True(t, addr0Initial.IsEqual(sdk.Coins{sdk.NewCoin("steak", 42)})) + require.Equal(t, sdk.Coins{sdk.NewCoin("steak", 42)}, addr0Initial) + + require.True(t, proposal.GetTotalDeposit().IsEqual(sdk.Coins{})) + + // Check no deposits at beginning + deposit, found := keeper.GetDeposit(ctx, proposalID, addrs[1]) + require.False(t, found) + require.Equal(t, keeper.GetProposal(ctx, proposalID).GetVotingStartBlock(), int64(-1)) + require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) + + // Check first deposit + err, votingStarted := keeper.AddDeposit(ctx, proposalID, addrs[0], fourSteak) + require.Nil(t, err) + require.False(t, votingStarted) + deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[0]) + require.True(t, found) + require.Equal(t, fourSteak, deposit.Amount) + require.Equal(t, addrs[0], deposit.Depositer) + require.Equal(t, fourSteak, keeper.GetProposal(ctx, proposalID).GetTotalDeposit()) + require.Equal(t, addr0Initial.Minus(fourSteak), keeper.ck.GetCoins(ctx, addrs[0])) + + // Check a second deposit from same address + err, votingStarted = keeper.AddDeposit(ctx, proposalID, addrs[0], fiveSteak) + require.Nil(t, err) + require.False(t, votingStarted) + deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[0]) + require.True(t, found) + require.Equal(t, fourSteak.Plus(fiveSteak), deposit.Amount) + require.Equal(t, addrs[0], deposit.Depositer) + require.Equal(t, fourSteak.Plus(fiveSteak), keeper.GetProposal(ctx, proposalID).GetTotalDeposit()) + require.Equal(t, addr0Initial.Minus(fourSteak).Minus(fiveSteak), keeper.ck.GetCoins(ctx, addrs[0])) + + // Check third deposit from a new address + err, votingStarted = keeper.AddDeposit(ctx, proposalID, addrs[1], fourSteak) + require.Nil(t, err) + require.True(t, votingStarted) + deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[1]) + require.True(t, found) + require.Equal(t, addrs[1], deposit.Depositer) + require.Equal(t, fourSteak, deposit.Amount) + require.Equal(t, fourSteak.Plus(fiveSteak).Plus(fourSteak), keeper.GetProposal(ctx, proposalID).GetTotalDeposit()) + require.Equal(t, addr1Initial.Minus(fourSteak), keeper.ck.GetCoins(ctx, addrs[1])) + + // Check that proposal moved to voting period + require.Equal(t, ctx.BlockHeight(), keeper.GetProposal(ctx, proposalID).GetVotingStartBlock()) + 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) + require.Equal(t, addrs[0], deposit.Depositer) + require.Equal(t, fourSteak.Plus(fiveSteak), deposit.Amount) + depositsIterator.Next() + keeper.cdc.MustUnmarshalBinary(depositsIterator.Value(), &deposit) + require.Equal(t, addrs[1], deposit.Depositer) + require.Equal(t, fourSteak, deposit.Amount) + depositsIterator.Next() + require.False(t, depositsIterator.Valid()) + + // Test Refund Deposits + deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[1]) + require.True(t, found) + require.Equal(t, fourSteak, deposit.Amount) + keeper.RefundDeposits(ctx, proposalID) + deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[1]) + require.False(t, found) + require.Equal(t, addr0Initial, keeper.ck.GetCoins(ctx, addrs[0])) + require.Equal(t, addr1Initial, keeper.ck.GetCoins(ctx, addrs[1])) + +} + +func TestVotes(t *testing.T) { + mapp, keeper, _, addrs, _, _ := getMockApp(t, 2) + SortAddresses(addrs) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID := proposal.GetProposalID() + + proposal.SetStatus(StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + // Test first vote + keeper.AddVote(ctx, proposalID, addrs[0], OptionAbstain) + vote, found := keeper.GetVote(ctx, proposalID, addrs[0]) + require.True(t, found) + require.Equal(t, addrs[0], vote.Voter) + require.Equal(t, proposalID, vote.ProposalID) + require.Equal(t, OptionAbstain, vote.Option) + + // Test change of vote + keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + vote, found = keeper.GetVote(ctx, proposalID, addrs[0]) + require.True(t, found) + require.Equal(t, addrs[0], vote.Voter) + require.Equal(t, proposalID, vote.ProposalID) + require.Equal(t, OptionYes, vote.Option) + + // Test second vote + keeper.AddVote(ctx, proposalID, addrs[1], OptionNoWithVeto) + vote, found = keeper.GetVote(ctx, proposalID, addrs[1]) + require.True(t, found) + require.Equal(t, addrs[1], vote.Voter) + require.Equal(t, proposalID, vote.ProposalID) + require.Equal(t, OptionNoWithVeto, vote.Option) + + // Test vote iterator + votesIterator := keeper.GetVotes(ctx, proposalID) + require.True(t, votesIterator.Valid()) + keeper.cdc.MustUnmarshalBinary(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) + require.True(t, votesIterator.Valid()) + require.Equal(t, addrs[1], vote.Voter) + require.Equal(t, proposalID, vote.ProposalID) + require.Equal(t, OptionNoWithVeto, vote.Option) + votesIterator.Next() + require.False(t, votesIterator.Valid()) +} + +func TestProposalQueues(t *testing.T) { + mapp, keeper, _, _, _, _ := getMockApp(t, 0) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + mapp.InitChainer(ctx, abci.RequestInitChain{}) + + require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) + require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) + + // create test proposals + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposal2 := keeper.NewTextProposal(ctx, "Test2", "description", ProposalTypeText) + proposal3 := keeper.NewTextProposal(ctx, "Test3", "description", ProposalTypeText) + proposal4 := keeper.NewTextProposal(ctx, "Test4", "description", ProposalTypeText) + + // test pushing to inactive proposal queue + keeper.InactiveProposalQueuePush(ctx, proposal) + keeper.InactiveProposalQueuePush(ctx, proposal2) + keeper.InactiveProposalQueuePush(ctx, proposal3) + keeper.InactiveProposalQueuePush(ctx, proposal4) + + // test peeking and popping from inactive proposal queue + require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal.GetProposalID()) + require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal.GetProposalID()) + require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal2.GetProposalID()) + require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal2.GetProposalID()) + require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal3.GetProposalID()) + require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal3.GetProposalID()) + require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal4.GetProposalID()) + require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal4.GetProposalID()) + + // test pushing to active proposal queue + keeper.ActiveProposalQueuePush(ctx, proposal) + keeper.ActiveProposalQueuePush(ctx, proposal2) + keeper.ActiveProposalQueuePush(ctx, proposal3) + keeper.ActiveProposalQueuePush(ctx, proposal4) + + // test peeking and popping from active proposal queue + require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal.GetProposalID()) + require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal.GetProposalID()) + require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal2.GetProposalID()) + require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal2.GetProposalID()) + require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal3.GetProposalID()) + require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal3.GetProposalID()) + require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal4.GetProposalID()) + require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal4.GetProposalID()) +} diff --git a/x/gov/msgs.go b/x/gov/msgs.go new file mode 100644 index 000000000..5b38861ee --- /dev/null +++ b/x/gov/msgs.go @@ -0,0 +1,222 @@ +package gov + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// name to idetify transaction types +const MsgType = "gov" + +//----------------------------------------------------------- +// MsgSubmitProposal +type MsgSubmitProposal struct { + Title string // Title of the proposal + Description string // Description of the proposal + ProposalType ProposalKind // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Proposer sdk.AccAddress // Address of the proposer + InitialDeposit sdk.Coins // Initial deposit paid by sender. Must be strictly positive. +} + +func NewMsgSubmitProposal(title string, description string, proposalType ProposalKind, proposer sdk.AccAddress, initialDeposit sdk.Coins) MsgSubmitProposal { + return MsgSubmitProposal{ + Title: title, + Description: description, + ProposalType: proposalType, + Proposer: proposer, + InitialDeposit: initialDeposit, + } +} + +// Implements Msg. +func (msg MsgSubmitProposal) Type() string { return MsgType } + +// Implements Msg. +func (msg MsgSubmitProposal) ValidateBasic() sdk.Error { + if len(msg.Title) == 0 { + return ErrInvalidTitle(DefaultCodespace, msg.Title) // TODO: Proper Error + } + if len(msg.Description) == 0 { + return ErrInvalidDescription(DefaultCodespace, msg.Description) // TODO: Proper Error + } + if !validProposalType(msg.ProposalType) { + return ErrInvalidProposalType(DefaultCodespace, ProposalTypeToString(msg.ProposalType)) + } + if len(msg.Proposer) == 0 { + return sdk.ErrInvalidAddress(msg.Proposer.String()) + } + if !msg.InitialDeposit.IsValid() { + return sdk.ErrInvalidCoins(msg.InitialDeposit.String()) + } + if !msg.InitialDeposit.IsNotNegative() { + return sdk.ErrInvalidCoins(msg.InitialDeposit.String()) + } + return nil +} + +func (msg MsgSubmitProposal) String() string { + return fmt.Sprintf("MsgSubmitProposal{%v, %v, %v, %v}", msg.Title, msg.Description, ProposalTypeToString(msg.ProposalType), msg.InitialDeposit) +} + +// Implements Msg. +func (msg MsgSubmitProposal) Get(key interface{}) (value interface{}) { + return nil +} + +// Implements Msg. +func (msg MsgSubmitProposal) GetSignBytes() []byte { + b, err := msgCdc.MarshalJSON(struct { + Title string `json:"title"` + Description string `json:"description"` + ProposalType string `json:"proposal_type"` + Proposer sdk.AccAddress `json:"proposer"` + InitialDeposit sdk.Coins `json:"deposit"` + }{ + Title: msg.Title, + Description: msg.Description, + ProposalType: ProposalTypeToString(msg.ProposalType), + Proposer: msg.Proposer, + InitialDeposit: msg.InitialDeposit, + }) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// Implements Msg. +func (msg MsgSubmitProposal) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Proposer} +} + +//----------------------------------------------------------- +// MsgDeposit +type MsgDeposit struct { + ProposalID int64 `json:"proposalID"` // ID of the proposal + Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer + Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit +} + +func NewMsgDeposit(depositer sdk.AccAddress, proposalID int64, amount sdk.Coins) MsgDeposit { + return MsgDeposit{ + ProposalID: proposalID, + Depositer: depositer, + Amount: amount, + } +} + +// Implements Msg. +func (msg MsgDeposit) Type() string { return MsgType } + +// Implements Msg. +func (msg MsgDeposit) ValidateBasic() sdk.Error { + if len(msg.Depositer) == 0 { + return sdk.ErrInvalidAddress(msg.Depositer.String()) + } + if !msg.Amount.IsValid() { + return sdk.ErrInvalidCoins(msg.Amount.String()) + } + if !msg.Amount.IsNotNegative() { + return sdk.ErrInvalidCoins(msg.Amount.String()) + } + if msg.ProposalID < 0 { + return ErrUnknownProposal(DefaultCodespace, msg.ProposalID) + } + return nil +} + +func (msg MsgDeposit) String() string { + return fmt.Sprintf("MsgDeposit{%v=>%v: %v}", msg.Depositer, msg.ProposalID, msg.Amount) +} + +// Implements Msg. +func (msg MsgDeposit) Get(key interface{}) (value interface{}) { + return nil +} + +// Implements Msg. +func (msg MsgDeposit) GetSignBytes() []byte { + b, err := msgCdc.MarshalJSON(struct { + ProposalID int64 `json:"proposalID"` + Depositer sdk.AccAddress `json:"proposer"` + Amount sdk.Coins `json:"deposit"` + }{ + ProposalID: msg.ProposalID, + Depositer: msg.Depositer, + Amount: msg.Amount, + }) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// Implements Msg. +func (msg MsgDeposit) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Depositer} +} + +//----------------------------------------------------------- +// MsgVote +type MsgVote struct { + ProposalID int64 // proposalID of the proposal + Voter sdk.AccAddress // address of the voter + Option VoteOption // option from OptionSet chosen by the voter +} + +func NewMsgVote(voter sdk.AccAddress, proposalID int64, option VoteOption) MsgVote { + return MsgVote{ + ProposalID: proposalID, + Voter: voter, + Option: option, + } +} + +// Implements Msg. +func (msg MsgVote) Type() string { return MsgType } + +// Implements Msg. +func (msg MsgVote) ValidateBasic() sdk.Error { + if len(msg.Voter.Bytes()) == 0 { + return sdk.ErrInvalidAddress(msg.Voter.String()) + } + if msg.ProposalID < 0 { + return ErrUnknownProposal(DefaultCodespace, msg.ProposalID) + } + if !validVoteOption(msg.Option) { + return ErrInvalidVote(DefaultCodespace, VoteOptionToString(msg.Option)) + } + return nil +} + +func (msg MsgVote) String() string { + return fmt.Sprintf("MsgVote{%v - %v}", msg.ProposalID, msg.Option) +} + +// Implements Msg. +func (msg MsgVote) Get(key interface{}) (value interface{}) { + return nil +} + +// Implements Msg. +func (msg MsgVote) GetSignBytes() []byte { + b, err := msgCdc.MarshalJSON(struct { + ProposalID int64 `json:"proposalID"` + Voter sdk.AccAddress `json:"voter"` + Option string `json:"option"` + }{ + ProposalID: msg.ProposalID, + Voter: msg.Voter, + Option: VoteOptionToString(msg.Option), + }) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// Implements Msg. +func (msg MsgVote) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Voter} +} diff --git a/x/gov/msgs_test.go b/x/gov/msgs_test.go new file mode 100644 index 000000000..fd526c7fa --- /dev/null +++ b/x/gov/msgs_test.go @@ -0,0 +1,105 @@ +package gov + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/mock" +) + +var ( + coinsPos = sdk.Coins{sdk.NewCoin("steak", 1000)} + coinsZero = sdk.Coins{} + coinsNeg = sdk.Coins{sdk.NewCoin("steak", -10000)} + coinsPosNotAtoms = sdk.Coins{sdk.NewCoin("foo", 10000)} + coinsMulti = sdk.Coins{sdk.NewCoin("foo", 10000), sdk.NewCoin("steak", 1000)} +) + +// test ValidateBasic for MsgCreateValidator +func TestMsgSubmitProposal(t *testing.T) { + _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) + tests := []struct { + title, description string + proposalType byte + proposerAddr sdk.AccAddress + initialDeposit sdk.Coins + expectPass bool + }{ + {"Test Proposal", "the purpose of this proposal is to test", ProposalTypeText, addrs[0], coinsPos, true}, + {"", "the purpose of this proposal is to test", ProposalTypeText, addrs[0], coinsPos, false}, + {"Test Proposal", "", ProposalTypeText, addrs[0], coinsPos, false}, + {"Test Proposal", "the purpose of this proposal is to test", ProposalTypeParameterChange, addrs[0], coinsPos, true}, + {"Test Proposal", "the purpose of this proposal is to test", ProposalTypeSoftwareUpgrade, addrs[0], coinsPos, true}, + {"Test Proposal", "the purpose of this proposal is to test", 0x05, addrs[0], coinsPos, false}, + {"Test Proposal", "the purpose of this proposal is to test", ProposalTypeText, sdk.AccAddress{}, coinsPos, false}, + {"Test Proposal", "the purpose of this proposal is to test", ProposalTypeText, addrs[0], coinsZero, true}, + {"Test Proposal", "the purpose of this proposal is to test", ProposalTypeText, addrs[0], coinsNeg, false}, + {"Test Proposal", "the purpose of this proposal is to test", ProposalTypeText, addrs[0], coinsMulti, true}, + } + + for i, tc := range tests { + msg := NewMsgSubmitProposal(tc.title, tc.description, tc.proposalType, tc.proposerAddr, tc.initialDeposit) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", i) + } + } +} + +// test ValidateBasic for MsgDeposit +func TestMsgDeposit(t *testing.T) { + _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) + tests := []struct { + proposalID int64 + 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}, + {1, addrs[0], coinsMulti, true}, + } + + for i, tc := range tests { + msg := NewMsgDeposit(tc.depositerAddr, tc.proposalID, tc.depositAmount) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", i) + } + } +} + +// test ValidateBasic for MsgDeposit +func TestMsgVote(t *testing.T) { + _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) + tests := []struct { + proposalID int64 + 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}, + {0, addrs[0], OptionAbstain, true}, + {0, addrs[0], VoteOption(0x13), false}, + } + + for i, tc := range tests { + msg := NewMsgVote(tc.voterAddr, tc.proposalID, tc.option) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", i) + } + } +} diff --git a/x/gov/procedures.go b/x/gov/procedures.go new file mode 100644 index 000000000..f46c2149f --- /dev/null +++ b/x/gov/procedures.go @@ -0,0 +1,23 @@ +package gov + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Procedure around Deposits for governance +type DepositProcedure struct { + MinDeposit sdk.Coins `json:"min_deposit"` // Minimum deposit for a proposal to enter voting period. + MaxDepositPeriod int64 `json:"max_deposit_period"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months +} + +// Procedure around Tallying votes in governance +type TallyingProcedure struct { + Threshold sdk.Rat `json:"threshold"` // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 + Veto sdk.Rat `json:"veto"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 + GovernancePenalty sdk.Rat `json:"governance_penalty"` // Penalty if validator does not vote +} + +// Procedure around Voting in governance +type VotingProcedure struct { + VotingPeriod int64 `json:"voting_period"` // Length of the voting period. +} diff --git a/x/gov/proposals.go b/x/gov/proposals.go new file mode 100644 index 000000000..c81d61e21 --- /dev/null +++ b/x/gov/proposals.go @@ -0,0 +1,204 @@ +package gov + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Type that represents Status as a byte +type VoteStatus = byte + +// Type that represents Proposal Type as a byte +type ProposalKind = byte + +//nolint +const ( + StatusDepositPeriod VoteStatus = 0x01 + StatusVotingPeriod VoteStatus = 0x02 + StatusPassed VoteStatus = 0x03 + StatusRejected VoteStatus = 0x04 + + ProposalTypeText ProposalKind = 0x01 + ProposalTypeParameterChange ProposalKind = 0x02 + ProposalTypeSoftwareUpgrade ProposalKind = 0x03 +) + +//----------------------------------------------------------- +// Proposal interface +type Proposal interface { + GetProposalID() int64 + SetProposalID(int64) + + GetTitle() string + SetTitle(string) + + GetDescription() string + SetDescription(string) + + GetProposalType() ProposalKind + SetProposalType(ProposalKind) + + GetStatus() VoteStatus + SetStatus(VoteStatus) + + GetSubmitBlock() int64 + SetSubmitBlock(int64) + + GetTotalDeposit() sdk.Coins + SetTotalDeposit(sdk.Coins) + + GetVotingStartBlock() int64 + SetVotingStartBlock(int64) +} + +// checks if two proposals are equal +func ProposalEqual(proposalA Proposal, proposalB Proposal) bool { + if proposalA.GetProposalID() != proposalB.GetProposalID() || + proposalA.GetTitle() != proposalB.GetTitle() || + proposalA.GetDescription() != proposalB.GetDescription() || + proposalA.GetProposalType() != proposalB.GetProposalType() || + proposalA.GetStatus() != proposalB.GetStatus() || + proposalA.GetSubmitBlock() != proposalB.GetSubmitBlock() || + !(proposalA.GetTotalDeposit().IsEqual(proposalB.GetTotalDeposit())) || + proposalA.GetVotingStartBlock() != proposalB.GetVotingStartBlock() { + return false + } + return true +} + +//----------------------------------------------------------- +// Text Proposals +type TextProposal struct { + ProposalID int64 `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} + + Status VoteStatus `json:"string"` // Status of the Proposal {Pending, Active, Passed, Rejected} + + SubmitBlock int64 `json:"submit_block"` // Height of the block where TxGovSubmitProposal was included + TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit + + VotingStartBlock int64 `json:"voting_start_block"` // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached +} + +// 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) GetTitle() string { return tp.Title } +func (tp *TextProposal) SetTitle(title string) { tp.Title = title } +func (tp TextProposal) GetDescription() string { return tp.Description } +func (tp *TextProposal) SetDescription(description string) { tp.Description = description } +func (tp TextProposal) GetProposalType() ProposalKind { return tp.ProposalType } +func (tp *TextProposal) SetProposalType(proposalType ProposalKind) { tp.ProposalType = proposalType } +func (tp TextProposal) GetStatus() VoteStatus { return tp.Status } +func (tp *TextProposal) SetStatus(status VoteStatus) { tp.Status = status } +func (tp TextProposal) GetSubmitBlock() int64 { return tp.SubmitBlock } +func (tp *TextProposal) SetSubmitBlock(submitBlock int64) { tp.SubmitBlock = submitBlock } +func (tp TextProposal) GetTotalDeposit() sdk.Coins { return tp.TotalDeposit } +func (tp *TextProposal) SetTotalDeposit(totalDeposit sdk.Coins) { tp.TotalDeposit = totalDeposit } +func (tp TextProposal) GetVotingStartBlock() int64 { return tp.VotingStartBlock } +func (tp *TextProposal) SetVotingStartBlock(votingStartBlock int64) { + tp.VotingStartBlock = votingStartBlock +} + +// Current Active Proposals +type ProposalQueue []int64 + +// ProposalTypeToString for pretty prints of ProposalType +func ProposalTypeToString(proposalType ProposalKind) string { + switch proposalType { + case 0x00: + return "Text" + case 0x01: + return "ParameterChange" + case 0x02: + return "SoftwareUpgrade" + default: + return "" + } +} + +func validProposalType(proposalType ProposalKind) bool { + if proposalType == ProposalTypeText || + proposalType == ProposalTypeParameterChange || + proposalType == ProposalTypeSoftwareUpgrade { + return true + } + return false +} + +// String to proposalType byte. Returns ff if invalid. +func StringToProposalType(str string) (ProposalKind, sdk.Error) { + switch str { + case "Text": + return ProposalTypeText, nil + case "ParameterChange": + return ProposalTypeParameterChange, nil + case "SoftwareUpgrade": + return ProposalTypeSoftwareUpgrade, nil + default: + return ProposalKind(0xff), ErrInvalidProposalType(DefaultCodespace, str) + } +} + +// StatusToString for pretty prints of Status +func StatusToString(status VoteStatus) string { + switch status { + case StatusDepositPeriod: + return "DepositPeriod" + case StatusVotingPeriod: + return "VotingPeriod" + case StatusPassed: + return "Passed" + case StatusRejected: + return "Rejected" + default: + return "" + } +} + +// StatusToString for pretty prints of Status +func StringToStatus(status string) VoteStatus { + switch status { + case "DepositPeriod": + return StatusDepositPeriod + case "VotingPeriod": + return StatusVotingPeriod + case "Passed": + return StatusPassed + case "Rejected": + return StatusRejected + default: + return VoteStatus(0xff) + } +} + +//----------------------------------------------------------- +// Rest Proposals +type ProposalRest struct { + ProposalID int64 `json:"proposal_id"` // ID of the proposal + Title string `json:"title"` // Title of the proposal + Description string `json:"description"` // Description of the proposal + ProposalType string `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Status string `json:"string"` // Status of the Proposal {Pending, Active, Passed, Rejected} + SubmitBlock int64 `json:"submit_block"` // Height of the block where TxGovSubmitProposal was included + TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit + VotingStartBlock int64 `json:"voting_start_block"` // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached +} + +// Turn any Proposal to a ProposalRest +func ProposalToRest(proposal Proposal) ProposalRest { + return ProposalRest{ + ProposalID: proposal.GetProposalID(), + Title: proposal.GetTitle(), + Description: proposal.GetDescription(), + ProposalType: ProposalTypeToString(proposal.GetProposalType()), + Status: StatusToString(proposal.GetStatus()), + SubmitBlock: proposal.GetSubmitBlock(), + TotalDeposit: proposal.GetTotalDeposit(), + VotingStartBlock: proposal.GetVotingStartBlock(), + } +} diff --git a/x/gov/tally.go b/x/gov/tally.go new file mode 100644 index 000000000..38462e72d --- /dev/null +++ b/x/gov/tally.go @@ -0,0 +1,100 @@ +package gov + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// validatorGovInfo used for tallying +type validatorGovInfo struct { + Address sdk.AccAddress // sdk.AccAddress of the validator owner + Power sdk.Rat // Power of a Validator + DelegatorShares sdk.Rat // Total outstanding delegator shares + Minus sdk.Rat // Minus of validator, used to compute validator's voting power + Vote VoteOption // Vote of the validator +} + +func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, nonVoting []sdk.AccAddress) { + results := make(map[VoteOption]sdk.Rat) + results[OptionYes] = sdk.ZeroRat() + results[OptionAbstain] = sdk.ZeroRat() + results[OptionNo] = sdk.ZeroRat() + results[OptionNoWithVeto] = sdk.ZeroRat() + + totalVotingPower := sdk.ZeroRat() + currValidators := make(map[string]validatorGovInfo) + + keeper.vs.IterateValidatorsBonded(ctx, func(index int64, validator sdk.Validator) (stop bool) { + currValidators[validator.GetOwner().String()] = validatorGovInfo{ + Address: validator.GetOwner(), + Power: validator.GetPower(), + DelegatorShares: validator.GetDelegatorShares(), + Minus: sdk.ZeroRat(), + Vote: OptionEmpty, + } + return false + }) + + // iterate over all the votes + votesIterator := keeper.GetVotes(ctx, proposal.GetProposalID()) + for ; votesIterator.Valid(); votesIterator.Next() { + vote := &Vote{} + keeper.cdc.MustUnmarshalBinary(votesIterator.Value(), vote) + + // if validator, just record it in the map + // if delegator tally voting power + if val, ok := currValidators[vote.Voter.String()]; ok { + val.Vote = vote.Option + currValidators[vote.Voter.String()] = val + } else { + + keeper.ds.IterateDelegations(ctx, vote.Voter, func(index int64, delegation sdk.Delegation) (stop bool) { + val := currValidators[delegation.GetValidator().String()] + val.Minus = val.Minus.Add(delegation.GetBondShares()) + currValidators[delegation.GetValidator().String()] = val + + delegatorShare := delegation.GetBondShares().Quo(val.DelegatorShares) + votingPower := val.Power.Mul(delegatorShare) + + results[vote.Option] = results[vote.Option].Add(votingPower) + totalVotingPower = totalVotingPower.Add(votingPower) + + return false + }) + } + + keeper.deleteVote(ctx, vote.ProposalID, vote.Voter) + } + votesIterator.Close() + + // Iterate over the validators again to tally their voting power and see who didn't vote + nonVoting = []sdk.AccAddress{} + for _, val := range currValidators { + if val.Vote == OptionEmpty { + nonVoting = append(nonVoting, val.Address) + continue + } + sharesAfterMinus := val.DelegatorShares.Sub(val.Minus) + percentAfterMinus := sharesAfterMinus.Quo(val.DelegatorShares) + votingPower := val.Power.Mul(percentAfterMinus) + + results[val.Vote] = results[val.Vote].Add(votingPower) + totalVotingPower = totalVotingPower.Add(votingPower) + } + + tallyingProcedure := keeper.GetTallyingProcedure() + + // If no one votes, proposal fails + if totalVotingPower.Sub(results[OptionAbstain]).Equal(sdk.ZeroRat()) { + return false, nonVoting + } + // If more than 1/3 of voters veto, proposal fails + if results[OptionNoWithVeto].Quo(totalVotingPower).GT(tallyingProcedure.Veto) { + return false, nonVoting + } + // If more than 1/2 of non-abstaining voters vote Yes, proposal passes + if results[OptionYes].Quo(totalVotingPower.Sub(results[OptionAbstain])).GT(tallyingProcedure.Threshold) { + return true, nonVoting + } + // If more than 1/2 of non-abstaining voters vote No, proposal fails + return false, nonVoting +} diff --git a/x/gov/tally_test.go b/x/gov/tally_test.go new file mode 100644 index 000000000..bcc3f2eb0 --- /dev/null +++ b/x/gov/tally_test.go @@ -0,0 +1,391 @@ +package gov + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + + "github.com/cosmos/cosmos-sdk/x/stake" +) + +func TestTallyNoOneVotes(t *testing.T) { + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + stakeHandler := stake.NewHandler(sk) + + dummyDescription := stake.NewDescription("T", "E", "S", "T") + val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription) + stakeHandler(ctx, val1CreateMsg) + val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription) + stakeHandler(ctx, val2CreateMsg) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID := proposal.GetProposalID() + proposal.SetStatus(StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + + require.False(t, passes) +} + +func TestTallyOnlyValidatorsAllYes(t *testing.T) { + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + stakeHandler := stake.NewHandler(sk) + + dummyDescription := stake.NewDescription("T", "E", "S", "T") + val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription) + res := stakeHandler(ctx, val1CreateMsg) + require.True(t, res.IsOK()) + val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription) + res = stakeHandler(ctx, val2CreateMsg) + require.True(t, res.IsOK()) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID := proposal.GetProposalID() + proposal.SetStatus(StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) + require.Nil(t, err) + + passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + + require.True(t, passes) +} + +func TestTallyOnlyValidators51No(t *testing.T) { + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + stakeHandler := stake.NewHandler(sk) + + dummyDescription := stake.NewDescription("T", "E", "S", "T") + val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription) + stakeHandler(ctx, val1CreateMsg) + val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription) + stakeHandler(ctx, val2CreateMsg) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID := proposal.GetProposalID() + proposal.SetStatus(StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo) + require.Nil(t, err) + + passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + + require.False(t, passes) +} + +func TestTallyOnlyValidators51Yes(t *testing.T) { + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + stakeHandler := stake.NewHandler(sk) + + dummyDescription := stake.NewDescription("T", "E", "S", "T") + val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription) + stakeHandler(ctx, val1CreateMsg) + val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription) + stakeHandler(ctx, val2CreateMsg) + val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription) + stakeHandler(ctx, val3CreateMsg) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID := proposal.GetProposalID() + proposal.SetStatus(StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) + require.Nil(t, err) + + passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + + require.True(t, passes) +} + +func TestTallyOnlyValidatorsVetoed(t *testing.T) { + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + stakeHandler := stake.NewHandler(sk) + + dummyDescription := stake.NewDescription("T", "E", "S", "T") + val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription) + stakeHandler(ctx, val1CreateMsg) + val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription) + stakeHandler(ctx, val2CreateMsg) + val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription) + stakeHandler(ctx, val3CreateMsg) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID := proposal.GetProposalID() + proposal.SetStatus(StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNoWithVeto) + require.Nil(t, err) + + passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + + require.False(t, passes) +} + +func TestTallyOnlyValidatorsAbstainPasses(t *testing.T) { + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + stakeHandler := stake.NewHandler(sk) + + dummyDescription := stake.NewDescription("T", "E", "S", "T") + val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription) + stakeHandler(ctx, val1CreateMsg) + val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription) + stakeHandler(ctx, val2CreateMsg) + val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription) + stakeHandler(ctx, val3CreateMsg) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID := proposal.GetProposalID() + proposal.SetStatus(StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + err := keeper.AddVote(ctx, proposalID, addrs[0], OptionAbstain) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes) + require.Nil(t, err) + + passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + + require.True(t, passes) +} + +func TestTallyOnlyValidatorsAbstainFails(t *testing.T) { + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + stakeHandler := stake.NewHandler(sk) + + dummyDescription := stake.NewDescription("T", "E", "S", "T") + val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription) + stakeHandler(ctx, val1CreateMsg) + val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription) + stakeHandler(ctx, val2CreateMsg) + val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription) + stakeHandler(ctx, val3CreateMsg) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID := proposal.GetProposalID() + proposal.SetStatus(StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + err := keeper.AddVote(ctx, proposalID, addrs[0], OptionAbstain) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) + require.Nil(t, err) + + passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + + require.False(t, passes) +} + +func TestTallyOnlyValidatorsNonVoter(t *testing.T) { + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + stakeHandler := stake.NewHandler(sk) + + dummyDescription := stake.NewDescription("T", "E", "S", "T") + val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription) + stakeHandler(ctx, val1CreateMsg) + val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription) + stakeHandler(ctx, val2CreateMsg) + val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription) + stakeHandler(ctx, val3CreateMsg) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID := proposal.GetProposalID() + proposal.SetStatus(StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + err := keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) + require.Nil(t, err) + + passes, nonVoting := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + + require.False(t, passes) + require.Equal(t, 1, len(nonVoting)) + require.Equal(t, addrs[0], nonVoting[0]) +} + +func TestTallyDelgatorOverride(t *testing.T) { + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + stakeHandler := stake.NewHandler(sk) + + dummyDescription := stake.NewDescription("T", "E", "S", "T") + val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription) + stakeHandler(ctx, val1CreateMsg) + val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription) + stakeHandler(ctx, val2CreateMsg) + val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription) + stakeHandler(ctx, val3CreateMsg) + + delegator1Msg := stake.NewMsgDelegate(addrs[3], addrs[2], sdk.NewCoin("steak", 30)) + stakeHandler(ctx, delegator1Msg) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID := proposal.GetProposalID() + proposal.SetStatus(StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[3], OptionNo) + require.Nil(t, err) + + passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + + require.False(t, passes) +} + +func TestTallyDelgatorInherit(t *testing.T) { + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + stakeHandler := stake.NewHandler(sk) + + dummyDescription := stake.NewDescription("T", "E", "S", "T") + val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription) + stakeHandler(ctx, val1CreateMsg) + val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription) + stakeHandler(ctx, val2CreateMsg) + val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription) + stakeHandler(ctx, val3CreateMsg) + + delegator1Msg := stake.NewMsgDelegate(addrs[3], addrs[2], sdk.NewCoin("steak", 30)) + stakeHandler(ctx, delegator1Msg) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID := proposal.GetProposalID() + proposal.SetStatus(StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + err := keeper.AddVote(ctx, proposalID, addrs[0], OptionNo) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes) + require.Nil(t, err) + + passes, nonVoting := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + + require.True(t, passes) + require.Equal(t, 0, len(nonVoting)) +} + +func TestTallyDelgatorMultipleOverride(t *testing.T) { + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + stakeHandler := stake.NewHandler(sk) + + dummyDescription := stake.NewDescription("T", "E", "S", "T") + val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription) + stakeHandler(ctx, val1CreateMsg) + val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription) + stakeHandler(ctx, val2CreateMsg) + val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription) + stakeHandler(ctx, val3CreateMsg) + + delegator1Msg := stake.NewMsgDelegate(addrs[3], addrs[2], sdk.NewCoin("steak", 10)) + stakeHandler(ctx, delegator1Msg) + delegator1Msg2 := stake.NewMsgDelegate(addrs[3], addrs[1], sdk.NewCoin("steak", 10)) + stakeHandler(ctx, delegator1Msg2) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID := proposal.GetProposalID() + proposal.SetStatus(StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[3], OptionNo) + require.Nil(t, err) + + passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + + require.False(t, passes) +} + +func TestTallyDelgatorMultipleInherit(t *testing.T) { + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + stakeHandler := stake.NewHandler(sk) + + dummyDescription := stake.NewDescription("T", "E", "S", "T") + val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 25), dummyDescription) + stakeHandler(ctx, val1CreateMsg) + val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription) + stakeHandler(ctx, val2CreateMsg) + val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription) + stakeHandler(ctx, val3CreateMsg) + + delegator1Msg := stake.NewMsgDelegate(addrs[3], addrs[2], sdk.NewCoin("steak", 10)) + stakeHandler(ctx, delegator1Msg) + delegator1Msg2 := stake.NewMsgDelegate(addrs[3], addrs[1], sdk.NewCoin("steak", 10)) + stakeHandler(ctx, delegator1Msg2) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID := proposal.GetProposalID() + proposal.SetStatus(StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) + require.Nil(t, err) + + passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + + require.False(t, passes) +} diff --git a/x/gov/test_common.go b/x/gov/test_common.go new file mode 100644 index 000000000..ecdaa34fa --- /dev/null +++ b/x/gov/test_common.go @@ -0,0 +1,114 @@ +package gov + +import ( + "bytes" + "log" + "sort" + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/mock" + "github.com/cosmos/cosmos-sdk/x/stake" +) + +// initialize the mock application for this module +func getMockApp(t *testing.T, numGenAccs int) (*mock.App, Keeper, stake.Keeper, []sdk.AccAddress, []crypto.PubKey, []crypto.PrivKey) { + mapp := mock.NewApp() + + stake.RegisterWire(mapp.Cdc) + RegisterWire(mapp.Cdc) + + keyStake := sdk.NewKVStoreKey("stake") + keyGov := sdk.NewKVStoreKey("gov") + + ck := bank.NewKeeper(mapp.AccountMapper) + sk := stake.NewKeeper(mapp.Cdc, keyStake, ck, mapp.RegisterCodespace(stake.DefaultCodespace)) + keeper := NewKeeper(mapp.Cdc, keyGov, ck, sk, DefaultCodespace) + mapp.Router().AddRoute("gov", NewHandler(keeper)) + + require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyStake, keyGov})) + + mapp.SetEndBlocker(getEndBlocker(keeper)) + mapp.SetInitChainer(getInitChainer(mapp, keeper, sk)) + + genAccs, addrs, pubKeys, privKeys := mock.CreateGenAccounts(numGenAccs, sdk.Coins{sdk.NewCoin("steak", 42)}) + mock.SetGenesis(mapp, genAccs) + + return mapp, keeper, sk, addrs, pubKeys, privKeys +} + +// gov and stake endblocker +func getEndBlocker(keeper Keeper) sdk.EndBlocker { + return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + tags, _ := EndBlocker(ctx, keeper) + return abci.ResponseEndBlock{ + Tags: tags, + } + } +} + +// gov and stake initchainer +func getInitChainer(mapp *mock.App, keeper Keeper, stakeKeeper stake.Keeper) sdk.InitChainer { + return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + mapp.InitChainer(ctx, req) + + stakeGenesis := stake.DefaultGenesisState() + stakeGenesis.Pool.LooseTokens = 100000 + + err := stake.InitGenesis(ctx, stakeKeeper, stakeGenesis) + if err != nil { + panic(err) + } + InitGenesis(ctx, keeper, DefaultGenesisState()) + return abci.ResponseInitChain{} + } +} + +// Sorts Addresses +func SortAddresses(addrs []sdk.AccAddress) { + var byteAddrs [][]byte + for _, addr := range addrs { + byteAddrs = append(byteAddrs, addr.Bytes()) + } + SortByteArrays(byteAddrs) + for i, byteAddr := range byteAddrs { + addrs[i] = byteAddr + } +} + +// implement `Interface` in sort package. +type sortByteArrays [][]byte + +func (b sortByteArrays) Len() int { + return len(b) +} + +func (b sortByteArrays) Less(i, j int) bool { + // bytes package already implements Comparable for []byte. + switch bytes.Compare(b[i], b[j]) { + case -1: + return true + case 0, 1: + return false + default: + log.Panic("not fail-able with `bytes.Comparable` bounded [-1, 1].") + return false + } +} + +func (b sortByteArrays) Swap(i, j int) { + b[j], b[i] = b[i], b[j] +} + +// Public +func SortByteArrays(src [][]byte) [][]byte { + sorted := sortByteArrays(src) + sort.Sort(sorted) + return sorted +} diff --git a/x/gov/wire.go b/x/gov/wire.go new file mode 100644 index 000000000..405ee464e --- /dev/null +++ b/x/gov/wire.go @@ -0,0 +1,18 @@ +package gov + +import ( + "github.com/cosmos/cosmos-sdk/wire" +) + +// Register concrete types on wire codec +func RegisterWire(cdc *wire.Codec) { + + cdc.RegisterConcrete(MsgSubmitProposal{}, "cosmos-sdk/MsgSubmitProposal", nil) + cdc.RegisterConcrete(MsgDeposit{}, "cosmos-sdk/MsgDeposit", nil) + cdc.RegisterConcrete(MsgVote{}, "cosmos-sdk/MsgVote", nil) + + cdc.RegisterInterface((*Proposal)(nil), nil) + cdc.RegisterConcrete(&TextProposal{}, "gov/TextProposal", nil) +} + +var msgCdc = wire.NewCodec() diff --git a/x/ibc/app_test.go b/x/ibc/app_test.go index 90d426882..4c559cb35 100644 --- a/x/ibc/app_test.go +++ b/x/ibc/app_test.go @@ -3,15 +3,15 @@ package ibc import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/mock" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" ) // initialize the mock application for this module @@ -24,7 +24,7 @@ func getMockApp(t *testing.T) *mock.App { coinKeeper := bank.NewKeeper(mapp.AccountMapper) mapp.Router().AddRoute("ibc", NewHandler(ibcMapper, coinKeeper)) - mapp.CompleteSetup(t, []*sdk.KVStoreKey{keyIBC}) + require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyIBC})) return mapp } @@ -35,8 +35,8 @@ func TestIBCMsgs(t *testing.T) { destChain := "dest-chain" priv1 := crypto.GenPrivKeyEd25519() - addr1 := priv1.PubKey().Address() - coins := sdk.Coins{{"foocoin", 10}} + addr1 := sdk.AccAddress(priv1.PubKey().Address()) + coins := sdk.Coins{sdk.NewCoin("foocoin", 10)} var emptyCoins sdk.Coins acc := &auth.BaseAccount{ @@ -50,7 +50,7 @@ func TestIBCMsgs(t *testing.T) { // A checkTx context (true) ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) - assert.Equal(t, acc, res1) + require.Equal(t, acc, res1) packet := IBCPacket{ SrcAddr: addr1, @@ -70,10 +70,10 @@ func TestIBCMsgs(t *testing.T) { Sequence: 0, } - mock.SignCheckDeliver(t, mapp.BaseApp, transferMsg, []int64{0},[]int64{0}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{transferMsg}, []int64{0}, []int64{0}, true, priv1) mock.CheckBalance(t, mapp, addr1, emptyCoins) - mock.SignCheckDeliver(t, mapp.BaseApp, transferMsg, []int64{0}, []int64{1}, false, priv1) - mock.SignCheckDeliver(t, mapp.BaseApp, receiveMsg, []int64{0}, []int64{2}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{transferMsg}, []int64{0}, []int64{1}, false, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{receiveMsg}, []int64{0}, []int64{2}, true, priv1) mock.CheckBalance(t, mapp, addr1, coins) - mock.SignCheckDeliver(t, mapp.BaseApp, receiveMsg, []int64{0}, []int64{3}, false, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{receiveMsg}, []int64{0}, []int64{2}, false, priv1) } diff --git a/x/ibc/client/cli/README.md b/x/ibc/client/cli/README.md index ed9652fa3..ab9e8e555 100644 --- a/x/ibc/client/cli/README.md +++ b/x/ibc/client/cli/README.md @@ -58,7 +58,7 @@ I[04-02|14:09:14.453] Generated genesis file module=main p } > ADDR2=DC26002735D3AA9573707CFA6D77C12349E49868 > ID2=test-chain-4XHTPn -> NODE2=tcp://0.0.0.0:46657 +> NODE2=tcp://0.0.0.0:26657 > basecli keys add key2 --recover Enter a passphrase for your key: Repeat the passphrase: @@ -70,7 +70,7 @@ key2 DC26002735D3AA9573707CFA6D77C12349E49868 > basecoind start --home ~/.chain1 --address tcp://0.0.0.0:36658 --rpc.laddr tcp://0.0.0.0:36657 --p2p.laddr tcp://0.0.0.0:36656 ... -> basecoind start --home ~/.chain2 # --address tcp://0.0.0.0:46658 --rpc.laddr tcp://0.0.0.0:46657 --p2p.laddr tcp://0.0.0.0:46656 +> basecoind start --home ~/.chain2 # --address tcp://0.0.0.0:26658 --rpc.laddr tcp://0.0.0.0:26657 --p2p.laddr tcp://0.0.0.0:26656 ... ``` ## Check balance @@ -109,7 +109,7 @@ key2 DC26002735D3AA9573707CFA6D77C12349E49868 ## Transfer coins (addr1:chain1 -> addr2:chain2) ```console -> basecli transfer --name key1 --to $ADDR2 --amount 10mycoin --chain $ID2 --chain-id $ID1 --node $NODE1 +> basecli transfer --from key1 --to $ADDR2 --amount 10mycoin --chain $ID2 --chain-id $ID1 --node $NODE1 Password to sign with 'key1': Committed at block 1022. Hash: E16019DCC4AA08CA70AFCFBC96028ABCC51B6AD0 > basecli account $ADDR1 --node $NODE1 @@ -133,7 +133,7 @@ Committed at block 1022. Hash: E16019DCC4AA08CA70AFCFBC96028ABCC51B6AD0 ## Relay IBC packets ```console -> basecli relay --name key2 --from-chain-id $ID1 --from-chain-node $NODE1 --to-chain-id $ID2 --to-chain-node $NODE2 --chain-id $ID2 +> basecli relay --from key2 --from-chain-id $ID1 --from-chain-node $NODE1 --to-chain-id $ID2 --to-chain-node $NODE2 --chain-id $ID2 Password to sign with 'key2': I[04-03|16:18:59.984] Detected IBC packet number=0 I[04-03|16:19:00.869] Relayed IBC packet number=0 diff --git a/x/ibc/client/cli/ibctx.go b/x/ibc/client/cli/ibctx.go index 906758dd7..f5505d9a4 100644 --- a/x/ibc/client/cli/ibctx.go +++ b/x/ibc/client/cli/ibctx.go @@ -2,7 +2,6 @@ package cli import ( "encoding/hex" - "fmt" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -43,12 +42,10 @@ func IBCTransferCmd(cdc *wire.Codec) *cobra.Command { } // get password - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } @@ -59,7 +56,7 @@ func IBCTransferCmd(cdc *wire.Codec) *cobra.Command { return cmd } -func buildMsg(from sdk.Address) (sdk.Msg, error) { +func buildMsg(from sdk.AccAddress) (sdk.Msg, error) { amount := viper.GetString(flagAmount) coins, err := sdk.ParseCoins(amount) if err != nil { @@ -71,7 +68,7 @@ func buildMsg(from sdk.Address) (sdk.Msg, error) { if err != nil { return nil, err } - to := sdk.Address(bz) + to := sdk.AccAddress(bz) packet := ibc.NewIBCPacket(from, to, coins, viper.GetString(client.FlagChainID), viper.GetString(flagChain)) diff --git a/x/ibc/client/cli/relay.go b/x/ibc/client/cli/relay.go index caf96d60a..58d8f272b 100644 --- a/x/ibc/client/cli/relay.go +++ b/x/ibc/client/cli/relay.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/tmlibs/log" + "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" @@ -27,7 +27,7 @@ const ( type relayCommander struct { cdc *wire.Codec - address sdk.Address + address sdk.AccAddress decoder auth.AccountDecoder mainStore string ibcStore string @@ -54,7 +54,7 @@ func IBCRelayCmd(cdc *wire.Codec) *cobra.Command { } cmd.Flags().String(FlagFromChainID, "", "Chain ID for ibc node to check outgoing packets") - cmd.Flags().String(FlagFromChainNode, "tcp://localhost:46657", "<host>:<port> to tendermint rpc interface for this chain") + cmd.Flags().String(FlagFromChainNode, "tcp://localhost:26657", "<host>:<port> to tendermint rpc interface for this chain") cmd.Flags().String(FlagToChainID, "", "Chain ID for ibc node to broadcast incoming packets") cmd.Flags().String(FlagToChainNode, "tcp://localhost:36657", "<host>:<port> to tendermint rpc interface for this chain") @@ -71,6 +71,7 @@ func IBCRelayCmd(cdc *wire.Codec) *cobra.Command { return cmd } +// nolint: unparam func (c relayCommander) runIBCRelay(cmd *cobra.Command, args []string) { fromChainID := viper.GetString(FlagFromChainID) fromChainNode := viper.GetString(FlagFromChainNode) @@ -85,6 +86,8 @@ func (c relayCommander) runIBCRelay(cmd *cobra.Command, args []string) { c.loop(fromChainID, fromChainNode, toChainID, toChainNode) } +// This is nolinted as someone is in the process of refactoring this to remove the goto +// nolint: gocyclo func (c relayCommander) loop(fromChainID, fromChainNode, toChainID, toChainNode string) { @@ -116,7 +119,7 @@ OUTER: lengthKey := ibc.EgressLengthKey(toChainID) egressLengthbz, err := query(fromChainNode, lengthKey, c.ibcStore) if err != nil { - c.logger.Error("Error querying outgoing packet list length", "err", err) + c.logger.Error("error querying outgoing packet list length", "err", err) continue OUTER //TODO replace with continue (I think it should just to the correct place where OUTER is now) } var egressLength int64 @@ -134,14 +137,14 @@ OUTER: for i := processed; i < egressLength; i++ { egressbz, err := query(fromChainNode, ibc.EgressKey(toChainID, i), c.ibcStore) if err != nil { - c.logger.Error("Error querying egress packet", "err", err) + c.logger.Error("error querying egress packet", "err", err) continue OUTER // TODO replace to break, will break first loop then send back to the beginning (aka OUTER) } err = c.broadcastTx(seq, toChainNode, c.refine(egressbz, i, passphrase)) seq++ if err != nil { - c.logger.Error("Error broadcasting ingress packet", "err", err) + c.logger.Error("error broadcasting ingress packet", "err", err) continue OUTER // TODO replace to break, will break first loop then send back to the beginning (aka OUTER) } @@ -151,7 +154,7 @@ OUTER: } func query(node string, key []byte, storeName string) (res []byte, err error) { - return context.NewCoreContextFromViper().WithNodeURI(node).Query(key, storeName) + return context.NewCoreContextFromViper().WithNodeURI(node).QueryStore(key, storeName) } func (c relayCommander) broadcastTx(seq int64, node string, tx []byte) error { @@ -189,7 +192,7 @@ func (c relayCommander) refine(bz []byte, sequence int64, passphrase string) []b } ctx := context.NewCoreContextFromViper().WithSequence(sequence) - res, err := ctx.SignAndBuild(ctx.FromAddressName, passphrase, msg, c.cdc) + res, err := ctx.SignAndBuild(ctx.FromAddressName, passphrase, []sdk.Msg{msg}, c.cdc) if err != nil { panic(err) } diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index b77a6f5eb..bee9f955f 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -1,12 +1,11 @@ package rest import ( - "encoding/hex" "io/ioutil" "net/http" + "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/gorilla/mux" - "github.com/tendermint/go-crypto/keys" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" @@ -39,7 +38,7 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.Core destChainID := vars["destchain"] bech32addr := vars["address"] - address, err := sdk.GetAccAddressBech32(bech32addr) + to, err := sdk.AccAddressFromBech32(bech32addr) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) @@ -67,16 +66,8 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.Core return } - bz, err := hex.DecodeString(address.String()) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - to := sdk.Address(bz) - // build message - packet := ibc.NewIBCPacket(info.PubKey.Address(), to, m.Amount, m.SrcChainID, destChainID) + packet := ibc.NewIBCPacket(sdk.AccAddress(info.GetPubKey().Address()), to, m.Amount, m.SrcChainID, destChainID) msg := ibc.IBCTransferMsg{packet} // add gas to context @@ -85,7 +76,7 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.Core // sign ctx = ctx.WithAccountNumber(m.AccountNumber) ctx = ctx.WithSequence(m.Sequence) - txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, msg, cdc) + txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, []sdk.Msg{msg}, cdc) if err != nil { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(err.Error())) diff --git a/x/ibc/errors.go b/x/ibc/errors.go index 60a141eba..7a3194baf 100644 --- a/x/ibc/errors.go +++ b/x/ibc/errors.go @@ -17,9 +17,9 @@ const ( func codeToDefaultMsg(code sdk.CodeType) string { switch code { case CodeInvalidSequence: - return "Invalid IBC packet sequence" + return "invalid IBC packet sequence" case CodeIdenticalChains: - return "Source and destination chain cannot be identical" + return "source and destination chain cannot be identical" default: return sdk.CodeToDefaultMsg(code) } @@ -36,6 +36,7 @@ func ErrIdenticalChains(codespace sdk.CodespaceType) sdk.Error { // ------------------------- // Helpers +// nolint: unparam func newError(codespace sdk.CodespaceType, code sdk.CodeType, msg string) sdk.Error { msg = msgOrDefaultMsg(msg, code) return sdk.NewError(codespace, code, msg) diff --git a/x/ibc/ibc_test.go b/x/ibc/ibc_test.go index e13df4f8d..06e2bd167 100644 --- a/x/ibc/ibc_test.go +++ b/x/ibc/ibc_test.go @@ -3,12 +3,12 @@ package ibc import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" @@ -24,15 +24,15 @@ func defaultContext(key sdk.StoreKey) sdk.Context { cms := store.NewCommitMultiStore(db) cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) cms.LoadLatestVersion() - ctx := sdk.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) return ctx } -func newAddress() crypto.Address { - return crypto.GenPrivKeyEd25519().PubKey().Address() +func newAddress() sdk.AccAddress { + return sdk.AccAddress(crypto.GenPrivKeyEd25519().PubKey().Address()) } -func getCoins(ck bank.Keeper, ctx sdk.Context, addr crypto.Address) (sdk.Coins, sdk.Error) { +func getCoins(ck bank.Keeper, ctx sdk.Context, addr sdk.AccAddress) (sdk.Coins, sdk.Error) { zero := sdk.Coins(nil) coins, _, err := ck.AddCoins(ctx, addr, zero) return coins, err @@ -53,6 +53,8 @@ func makeCodec() *wire.Codec { cdc.RegisterConcrete(&auth.BaseAccount{}, "test/ibc/Account", nil) wire.RegisterCrypto(cdc) + cdc.Seal() + return cdc } @@ -62,18 +64,18 @@ func TestIBC(t *testing.T) { key := sdk.NewKVStoreKey("ibc") ctx := defaultContext(key) - am := auth.NewAccountMapper(cdc, key, &auth.BaseAccount{}) + am := auth.NewAccountMapper(cdc, key, auth.ProtoBaseAccount) ck := bank.NewKeeper(am) src := newAddress() dest := newAddress() chainid := "ibcchain" zero := sdk.Coins(nil) - mycoins := sdk.Coins{sdk.Coin{"mycoin", 10}} + mycoins := sdk.Coins{sdk.NewCoin("mycoin", 10)} coins, _, err := ck.AddCoins(ctx, src, mycoins) - assert.Nil(t, err) - assert.Equal(t, mycoins, coins) + require.Nil(t, err) + require.Equal(t, mycoins, coins) ibcm := NewMapper(cdc, key, DefaultCodespace) h := NewHandler(ibcm, ck) @@ -93,23 +95,23 @@ func TestIBC(t *testing.T) { var igs int64 egl = ibcm.getEgressLength(store, chainid) - assert.Equal(t, egl, int64(0)) + require.Equal(t, egl, int64(0)) msg = IBCTransferMsg{ IBCPacket: packet, } res = h(ctx, msg) - assert.True(t, res.IsOK()) + require.True(t, res.IsOK()) coins, err = getCoins(ck, ctx, src) - assert.Nil(t, err) - assert.Equal(t, zero, coins) + require.Nil(t, err) + require.Equal(t, zero, coins) egl = ibcm.getEgressLength(store, chainid) - assert.Equal(t, egl, int64(1)) + require.Equal(t, egl, int64(1)) igs = ibcm.GetIngressSequence(ctx, chainid) - assert.Equal(t, igs, int64(0)) + require.Equal(t, igs, int64(0)) msg = IBCReceiveMsg{ IBCPacket: packet, @@ -117,18 +119,18 @@ func TestIBC(t *testing.T) { Sequence: 0, } res = h(ctx, msg) - assert.True(t, res.IsOK()) + require.True(t, res.IsOK()) coins, err = getCoins(ck, ctx, dest) - assert.Nil(t, err) - assert.Equal(t, mycoins, coins) + require.Nil(t, err) + require.Equal(t, mycoins, coins) igs = ibcm.GetIngressSequence(ctx, chainid) - assert.Equal(t, igs, int64(1)) + require.Equal(t, igs, int64(1)) res = h(ctx, msg) - assert.False(t, res.IsOK()) + require.False(t, res.IsOK()) igs = ibcm.GetIngressSequence(ctx, chainid) - assert.Equal(t, igs, int64(1)) + require.Equal(t, igs, int64(1)) } diff --git a/x/ibc/mapper.go b/x/ibc/mapper.go index 06631179b..251699617 100644 --- a/x/ibc/mapper.go +++ b/x/ibc/mapper.go @@ -39,7 +39,7 @@ func (ibcm Mapper) PostIBCPacket(ctx sdk.Context, packet IBCPacket) sdk.Error { } store.Set(EgressKey(packet.DestChain, index), bz) - bz, err = ibcm.cdc.MarshalBinary(int64(index + 1)) + bz, err = ibcm.cdc.MarshalBinary(index + 1) if err != nil { panic(err) } diff --git a/x/ibc/types.go b/x/ibc/types.go index a311b9869..5f9ee611b 100644 --- a/x/ibc/types.go +++ b/x/ibc/types.go @@ -22,14 +22,14 @@ func init() { // IBCPacket defines a piece of data that can be send between two separate // blockchains. type IBCPacket struct { - SrcAddr sdk.Address - DestAddr sdk.Address + SrcAddr sdk.AccAddress + DestAddr sdk.AccAddress Coins sdk.Coins SrcChain string DestChain string } -func NewIBCPacket(srcAddr sdk.Address, destAddr sdk.Address, coins sdk.Coins, +func NewIBCPacket(srcAddr sdk.AccAddress, destAddr sdk.AccAddress, coins sdk.Coins, srcChain string, destChain string) IBCPacket { return IBCPacket{ @@ -43,23 +43,11 @@ func NewIBCPacket(srcAddr sdk.Address, destAddr sdk.Address, coins sdk.Coins, //nolint func (p IBCPacket) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(struct { - SrcAddr string - DestAddr string - Coins sdk.Coins - SrcChain string - DestChain string - }{ - SrcAddr: sdk.MustBech32ifyAcc(p.SrcAddr), - DestAddr: sdk.MustBech32ifyAcc(p.DestAddr), - Coins: p.Coins, - SrcChain: p.SrcChain, - DestChain: p.DestChain, - }) + b, err := msgCdc.MarshalJSON(p) if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // validator the ibc packey @@ -86,7 +74,7 @@ type IBCTransferMsg struct { func (msg IBCTransferMsg) Type() string { return "ibc" } // x/bank/tx.go MsgSend.GetSigners() -func (msg IBCTransferMsg) GetSigners() []sdk.Address { return []sdk.Address{msg.SrcAddr} } +func (msg IBCTransferMsg) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.SrcAddr} } // get the sign bytes for ibc transfer message func (msg IBCTransferMsg) GetSignBytes() []byte { @@ -106,7 +94,7 @@ func (msg IBCTransferMsg) ValidateBasic() sdk.Error { // to the destination chain. type IBCReceiveMsg struct { IBCPacket - Relayer sdk.Address + Relayer sdk.AccAddress Sequence int64 } @@ -115,21 +103,21 @@ func (msg IBCReceiveMsg) Type() string { return "ibc" } func (msg IBCReceiveMsg) ValidateBasic() sdk.Error { return msg.IBCPacket.ValidateBasic() } // x/bank/tx.go MsgSend.GetSigners() -func (msg IBCReceiveMsg) GetSigners() []sdk.Address { return []sdk.Address{msg.Relayer} } +func (msg IBCReceiveMsg) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Relayer} } // get the sign bytes for ibc receive message func (msg IBCReceiveMsg) GetSignBytes() []byte { b, err := msgCdc.MarshalJSON(struct { IBCPacket json.RawMessage - Relayer string + Relayer sdk.AccAddress Sequence int64 }{ IBCPacket: json.RawMessage(msg.IBCPacket.GetSignBytes()), - Relayer: sdk.MustBech32ifyAcc(msg.Relayer), + Relayer: msg.Relayer, Sequence: msg.Sequence, }) if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } diff --git a/x/ibc/types_test.go b/x/ibc/types_test.go index 199270b29..cfe73ed13 100644 --- a/x/ibc/types_test.go +++ b/x/ibc/types_test.go @@ -3,7 +3,7 @@ package ibc import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -23,9 +23,9 @@ func TestIBCPacketValidation(t *testing.T) { for i, tc := range cases { err := tc.packet.ValidateBasic() if tc.valid { - assert.Nil(t, err, "%d: %+v", i, err) + require.Nil(t, err, "%d: %+v", i, err) } else { - assert.NotNil(t, err, "%d", i) + require.NotNil(t, err, "%d", i) } } } @@ -37,7 +37,7 @@ func TestIBCTransferMsg(t *testing.T) { packet := constructIBCPacket(true) msg := IBCTransferMsg{packet} - assert.Equal(t, msg.Type(), "ibc") + require.Equal(t, msg.Type(), "ibc") } func TestIBCTransferMsgValidation(t *testing.T) { @@ -55,9 +55,9 @@ func TestIBCTransferMsgValidation(t *testing.T) { for i, tc := range cases { err := tc.msg.ValidateBasic() if tc.valid { - assert.Nil(t, err, "%d: %+v", i, err) + require.Nil(t, err, "%d: %+v", i, err) } else { - assert.NotNil(t, err, "%d", i) + require.NotNil(t, err, "%d", i) } } } @@ -67,9 +67,9 @@ func TestIBCTransferMsgValidation(t *testing.T) { func TestIBCReceiveMsg(t *testing.T) { packet := constructIBCPacket(true) - msg := IBCReceiveMsg{packet, sdk.Address([]byte("relayer")), 0} + msg := IBCReceiveMsg{packet, sdk.AccAddress([]byte("relayer")), 0} - assert.Equal(t, msg.Type(), "ibc") + require.Equal(t, msg.Type(), "ibc") } func TestIBCReceiveMsgValidation(t *testing.T) { @@ -80,16 +80,16 @@ func TestIBCReceiveMsgValidation(t *testing.T) { valid bool msg IBCReceiveMsg }{ - {true, IBCReceiveMsg{validPacket, sdk.Address([]byte("relayer")), 0}}, - {false, IBCReceiveMsg{invalidPacket, sdk.Address([]byte("relayer")), 0}}, + {true, IBCReceiveMsg{validPacket, sdk.AccAddress([]byte("relayer")), 0}}, + {false, IBCReceiveMsg{invalidPacket, sdk.AccAddress([]byte("relayer")), 0}}, } for i, tc := range cases { err := tc.msg.ValidateBasic() if tc.valid { - assert.Nil(t, err, "%d: %+v", i, err) + require.Nil(t, err, "%d: %+v", i, err) } else { - assert.NotNil(t, err, "%d", i) + require.NotNil(t, err, "%d", i) } } } @@ -98,9 +98,9 @@ func TestIBCReceiveMsgValidation(t *testing.T) { // Helpers func constructIBCPacket(valid bool) IBCPacket { - srcAddr := sdk.Address([]byte("source")) - destAddr := sdk.Address([]byte("destination")) - coins := sdk.Coins{{"atom", 10}} + srcAddr := sdk.AccAddress([]byte("source")) + destAddr := sdk.AccAddress([]byte("destination")) + coins := sdk.Coins{sdk.NewCoin("atom", 10)} srcChain := "source-chain" destChain := "dest-chain" diff --git a/x/mock/app.go b/x/mock/app.go new file mode 100644 index 000000000..27f3b9d46 --- /dev/null +++ b/x/mock/app.go @@ -0,0 +1,237 @@ +package mock + +import ( + "math/rand" + "os" + + bam "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" +) + +const chainID = "" + +// App extends an ABCI application, but with most of its parameters exported. +// They are exported for convenience in creating helper functions, as object +// capabilities aren't needed for testing. +type App struct { + *bam.BaseApp + Cdc *wire.Codec // Cdc is public since the codec is passed into the module anyways + KeyMain *sdk.KVStoreKey + KeyAccount *sdk.KVStoreKey + + // TODO: Abstract this out from not needing to be auth specifically + AccountMapper auth.AccountMapper + FeeCollectionKeeper auth.FeeCollectionKeeper + + GenesisAccounts []auth.Account + TotalCoinsSupply sdk.Coins +} + +// NewApp partially constructs a new app on the memstore for module and genesis +// testing. +func NewApp() *App { + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") + db := dbm.NewMemDB() + + // Create the cdc with some standard codecs + cdc := wire.NewCodec() + sdk.RegisterWire(cdc) + wire.RegisterCrypto(cdc) + auth.RegisterWire(cdc) + + // Create your application object + app := &App{ + BaseApp: bam.NewBaseApp("mock", cdc, logger, db), + Cdc: cdc, + KeyMain: sdk.NewKVStoreKey("main"), + KeyAccount: sdk.NewKVStoreKey("acc"), + TotalCoinsSupply: sdk.Coins{}, + } + + // Define the accountMapper + app.AccountMapper = auth.NewAccountMapper( + app.Cdc, + app.KeyAccount, + auth.ProtoBaseAccount, + ) + + // Initialize the app. The chainers and blockers can be overwritten before + // calling complete setup. + app.SetInitChainer(app.InitChainer) + app.SetAnteHandler(auth.NewAnteHandler(app.AccountMapper, app.FeeCollectionKeeper)) + + return app +} + +// CompleteSetup completes the application setup after the routes have been +// registered. +func (app *App) CompleteSetup(newKeys []*sdk.KVStoreKey) error { + newKeys = append(newKeys, app.KeyMain) + newKeys = append(newKeys, app.KeyAccount) + + app.MountStoresIAVL(newKeys...) + err := app.LoadLatestVersion(app.KeyMain) + + return err +} + +// InitChainer performs custom logic for initialization. +func (app *App) InitChainer(ctx sdk.Context, _ abci.RequestInitChain) abci.ResponseInitChain { + // Load the genesis accounts + for _, genacc := range app.GenesisAccounts { + acc := app.AccountMapper.NewAccountWithAddress(ctx, genacc.GetAddress()) + acc.SetCoins(genacc.GetCoins()) + app.AccountMapper.SetAccount(ctx, acc) + } + + return abci.ResponseInitChain{} +} + +// CreateGenAccounts generates genesis accounts loaded with coins, and returns +// their addresses, pubkeys, and privkeys. +func CreateGenAccounts(numAccs int, genCoins sdk.Coins) (genAccs []auth.Account, addrs []sdk.AccAddress, pubKeys []crypto.PubKey, privKeys []crypto.PrivKey) { + for i := 0; i < numAccs; i++ { + privKey := crypto.GenPrivKeyEd25519() + pubKey := privKey.PubKey() + addr := sdk.AccAddress(pubKey.Address()) + + genAcc := &auth.BaseAccount{ + Address: addr, + Coins: genCoins, + } + + genAccs = append(genAccs, genAcc) + privKeys = append(privKeys, privKey) + pubKeys = append(pubKeys, pubKey) + addrs = append(addrs, addr) + } + + return +} + +// SetGenesis sets the mock app genesis accounts. +func SetGenesis(app *App, accs []auth.Account) { + // Pass the accounts in via the application (lazy) instead of through + // RequestInitChain. + app.GenesisAccounts = accs + + app.InitChain(abci.RequestInitChain{}) + app.Commit() +} + +// GenTx generates a signed mock transaction. +func GenTx(msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKey) auth.StdTx { + // Make the transaction free + fee := auth.StdFee{ + Amount: sdk.Coins{sdk.NewCoin("foocoin", 0)}, + Gas: 100000, + } + + sigs := make([]auth.StdSignature, len(priv)) + memo := "testmemotestmemo" + + for i, p := range priv { + sig, err := p.Sign(auth.StdSignBytes(chainID, accnums[i], seq[i], fee, msgs, memo)) + if err != nil { + panic(err) + } + + sigs[i] = auth.StdSignature{ + PubKey: p.PubKey(), + Signature: sig, + AccountNumber: accnums[i], + Sequence: seq[i], + } + } + + return auth.NewStdTx(msgs, fee, sigs, memo) +} + +// GeneratePrivKeys generates a total n Ed25519 private keys. +func GeneratePrivKeys(n int) (keys []crypto.PrivKey) { + // TODO: Randomize this between ed25519 and secp256k1 + keys = make([]crypto.PrivKey, n, n) + for i := 0; i < n; i++ { + keys[i] = crypto.GenPrivKeyEd25519() + } + + return +} + +// GeneratePrivKeyAddressPairs generates a total of n private key, address +// pairs. +func GeneratePrivKeyAddressPairs(n int) (keys []crypto.PrivKey, addrs []sdk.AccAddress) { + keys = make([]crypto.PrivKey, n, n) + addrs = make([]sdk.AccAddress, n, n) + for i := 0; i < n; i++ { + keys[i] = crypto.GenPrivKeyEd25519() + addrs[i] = sdk.AccAddress(keys[i].PubKey().Address()) + } + return +} + +// RandomSetGenesis set genesis accounts with random coin values using the +// provided addresses and coin denominations. +func RandomSetGenesis(r *rand.Rand, app *App, addrs []sdk.AccAddress, denoms []string) { + accts := make([]auth.Account, len(addrs), len(addrs)) + randCoinIntervals := []BigInterval{ + {sdk.NewIntWithDecimal(1, 0), sdk.NewIntWithDecimal(1, 1)}, + {sdk.NewIntWithDecimal(1, 2), sdk.NewIntWithDecimal(1, 3)}, + {sdk.NewIntWithDecimal(1, 40), sdk.NewIntWithDecimal(1, 50)}, + } + + for i := 0; i < len(accts); i++ { + coins := make([]sdk.Coin, len(denoms), len(denoms)) + + // generate a random coin for each denomination + for j := 0; j < len(denoms); j++ { + coins[j] = sdk.Coin{Denom: denoms[j], + Amount: RandFromBigInterval(r, randCoinIntervals), + } + } + + app.TotalCoinsSupply = app.TotalCoinsSupply.Plus(coins) + baseAcc := auth.NewBaseAccountWithAddress(addrs[i]) + + (&baseAcc).SetCoins(coins) + accts[i] = &baseAcc + } + + SetGenesis(app, accts) +} + +// GetAllAccounts returns all accounts in the accountMapper. +func GetAllAccounts(mapper auth.AccountMapper, ctx sdk.Context) []auth.Account { + accounts := []auth.Account{} + appendAccount := func(acc auth.Account) (stop bool) { + accounts = append(accounts, acc) + return false + } + mapper.IterateAccounts(ctx, appendAccount) + return accounts +} + +// GenSequenceOfTxs generates a set of signed transactions of messages, such +// that they differ only by having the sequence numbers incremented between +// every transaction. +func GenSequenceOfTxs(msgs []sdk.Msg, accnums []int64, initSeqNums []int64, numToGenerate int, priv ...crypto.PrivKey) []auth.StdTx { + txs := make([]auth.StdTx, numToGenerate, numToGenerate) + for i := 0; i < numToGenerate; i++ { + txs[i] = GenTx(msgs, accnums, initSeqNums, priv...) + incrementAllSequenceNumbers(initSeqNums) + } + + return txs +} + +func incrementAllSequenceNumbers(initSeqNums []int64) { + for i := 0; i < len(initSeqNums); i++ { + initSeqNums[i]++ + } +} diff --git a/x/mock/app_test.go b/x/mock/app_test.go new file mode 100644 index 000000000..0c548280a --- /dev/null +++ b/x/mock/app_test.go @@ -0,0 +1,102 @@ +package mock + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +const msgType = "testMsg" + +var ( + numAccts = 2 + genCoins = sdk.Coins{sdk.NewCoin("foocoin", 77)} + accs, addrs, pubKeys, privKeys = CreateGenAccounts(numAccts, genCoins) +) + +// testMsg is a mock transaction that has a validation which can fail. +type testMsg struct { + signers []sdk.AccAddress + positiveNum int64 +} + +func (tx testMsg) Type() string { return msgType } +func (tx testMsg) GetMsg() sdk.Msg { return tx } +func (tx testMsg) GetMemo() string { return "" } +func (tx testMsg) GetSignBytes() []byte { return nil } +func (tx testMsg) GetSigners() []sdk.AccAddress { return tx.signers } +func (tx testMsg) GetSignatures() []auth.StdSignature { return nil } +func (tx testMsg) ValidateBasic() sdk.Error { + if tx.positiveNum >= 0 { + return nil + } + return sdk.ErrTxDecode("positiveNum should be a non-negative integer.") +} + +// getMockApp returns an initialized mock application. +func getMockApp(t *testing.T) *App { + mApp := NewApp() + + mApp.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) + require.NoError(t, mApp.CompleteSetup([]*sdk.KVStoreKey{})) + + return mApp +} + +func TestCheckAndDeliverGenTx(t *testing.T) { + mApp := getMockApp(t) + mApp.Cdc.RegisterConcrete(testMsg{}, "mock/testMsg", nil) + + SetGenesis(mApp, accs) + ctxCheck := mApp.BaseApp.NewContext(true, abci.Header{}) + + msg := testMsg{signers: []sdk.AccAddress{addrs[0]}, positiveNum: 1} + + acct := mApp.AccountMapper.GetAccount(ctxCheck, addrs[0]) + require.Equal(t, accs[0], acct.(*auth.BaseAccount)) + + SignCheckDeliver( + t, mApp.BaseApp, []sdk.Msg{msg}, + []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence()}, + true, privKeys[0], + ) + + // Signing a tx with the wrong privKey should result in an auth error + res := SignCheckDeliver( + t, mApp.BaseApp, []sdk.Msg{msg}, + []int64{accs[1].GetAccountNumber()}, []int64{accs[1].GetSequence() + 1}, + false, privKeys[1], + ) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) + + // Resigning the tx with the correct privKey should result in an OK result + SignCheckDeliver( + t, mApp.BaseApp, []sdk.Msg{msg}, + []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence() + 1}, + true, privKeys[0], + ) +} + +func TestCheckGenTx(t *testing.T) { + mApp := getMockApp(t) + mApp.Cdc.RegisterConcrete(testMsg{}, "mock/testMsg", nil) + + SetGenesis(mApp, accs) + + msg1 := testMsg{signers: []sdk.AccAddress{addrs[0]}, positiveNum: 1} + CheckGenTx( + t, mApp.BaseApp, []sdk.Msg{msg1}, + []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence()}, + true, privKeys[0], + ) + + msg2 := testMsg{signers: []sdk.AccAddress{addrs[0]}, positiveNum: -1} + CheckGenTx( + t, mApp.BaseApp, []sdk.Msg{msg2}, + []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence()}, + false, privKeys[0], + ) +} diff --git a/x/mock/doc.go b/x/mock/doc.go new file mode 100644 index 000000000..d23aac393 --- /dev/null +++ b/x/mock/doc.go @@ -0,0 +1,15 @@ +/* +Package mock provides functions for creating applications for testing. + +This module also features randomized testing, so that various modules can test +that their operations are interoperable. + +The intended method of using this randomized testing framework is that every +module provides TestAndRunTx methods for each of its desired methods of fuzzing +its own txs, and it also provides the invariants that it assumes to be true. +You then pick and choose from these tx types and invariants. To pick and choose +these, you first build a mock app with the correct keepers. Then you call the +app.RandomizedTesting method with the set of desired txs, invariants, along +with the setups each module requires. +*/ +package mock diff --git a/x/mock/random_simulate_blocks.go b/x/mock/random_simulate_blocks.go new file mode 100644 index 000000000..a37913065 --- /dev/null +++ b/x/mock/random_simulate_blocks.go @@ -0,0 +1,95 @@ +package mock + +import ( + "fmt" + "math/big" + "math/rand" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +// RandomizedTesting tests application by sending random messages. +func (app *App) RandomizedTesting( + t *testing.T, ops []TestAndRunTx, setups []RandSetup, + invariants []Invariant, numKeys int, numBlocks int, blockSize int, +) { + time := time.Now().UnixNano() + app.RandomizedTestingFromSeed(t, time, ops, setups, invariants, numKeys, numBlocks, blockSize) +} + +// RandomizedTestingFromSeed tests an application by running the provided +// operations, testing the provided invariants, but using the provided seed. +func (app *App) RandomizedTestingFromSeed( + t *testing.T, seed int64, ops []TestAndRunTx, setups []RandSetup, + invariants []Invariant, numKeys int, numBlocks int, blockSize int, +) { + log := fmt.Sprintf("Starting SingleModuleTest with randomness created with seed %d", int(seed)) + keys, addrs := GeneratePrivKeyAddressPairs(numKeys) + r := rand.New(rand.NewSource(seed)) + + for i := 0; i < len(setups); i++ { + setups[i](r, keys) + } + + RandomSetGenesis(r, app, addrs, []string{"foocoin"}) + header := abci.Header{Height: 0} + + for i := 0; i < numBlocks; i++ { + app.BeginBlock(abci.RequestBeginBlock{}) + + // Make sure invariants hold at beginning of block and when nothing was + // done. + app.assertAllInvariants(t, invariants, log) + + ctx := app.NewContext(false, header) + + // TODO: Add modes to simulate "no load", "medium load", and + // "high load" blocks. + for j := 0; j < blockSize; j++ { + logUpdate, err := ops[r.Intn(len(ops))](t, r, app, ctx, keys, log) + log += "\n" + logUpdate + + require.Nil(t, err, log) + app.assertAllInvariants(t, invariants, log) + } + + app.EndBlock(abci.RequestEndBlock{}) + header.Height++ + } +} + +func (app *App) assertAllInvariants(t *testing.T, tests []Invariant, log string) { + for i := 0; i < len(tests); i++ { + tests[i](t, app, log) + } +} + +// BigInterval is a representation of the interval [lo, hi), where +// lo and hi are both of type sdk.Int +type BigInterval struct { + lo sdk.Int + hi sdk.Int +} + +// RandFromBigInterval chooses an interval uniformly from the provided list of +// BigIntervals, and then chooses an element from an interval uniformly at random. +func RandFromBigInterval(r *rand.Rand, intervals []BigInterval) sdk.Int { + if len(intervals) == 0 { + return sdk.ZeroInt() + } + + interval := intervals[r.Intn(len(intervals))] + + lo := interval.lo + hi := interval.hi + + diff := hi.Sub(lo) + result := sdk.NewIntFromBigInt(new(big.Int).Rand(r, diff.BigInt())) + result = result.Add(lo) + + return result +} diff --git a/x/mock/test_utils.go b/x/mock/test_utils.go new file mode 100644 index 000000000..f9e1e8f5e --- /dev/null +++ b/x/mock/test_utils.go @@ -0,0 +1,71 @@ +package mock + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" +) + +// CheckBalance checks the balance of an account. +func CheckBalance(t *testing.T, app *App, addr sdk.AccAddress, exp sdk.Coins) { + ctxCheck := app.BaseApp.NewContext(true, abci.Header{}) + res := app.AccountMapper.GetAccount(ctxCheck, addr) + + require.Equal(t, exp, res.GetCoins()) +} + +// CheckGenTx checks a generated signed transaction. The result of the check is +// compared against the parameter 'expPass'. A test assertion is made using the +// parameter 'expPass' against the result. A corresponding result is returned. +func CheckGenTx( + t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accNums []int64, + seq []int64, expPass bool, priv ...crypto.PrivKey, +) sdk.Result { + tx := GenTx(msgs, accNums, seq, priv...) + res := app.Check(tx) + + if expPass { + require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) + } else { + require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) + } + + return res +} + +// SignCheckDeliver checks a generated signed transaction and simulates a +// block commitment with the given transaction. A test assertion is made using +// the parameter 'expPass' against the result. A corresponding result is +// returned. +func SignCheckDeliver( + t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accNums []int64, + seq []int64, expPass bool, priv ...crypto.PrivKey, +) sdk.Result { + tx := GenTx(msgs, accNums, seq, priv...) + res := app.Check(tx) + + if expPass { + require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) + } else { + require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) + } + + // Simulate a sending a transaction and committing a block + app.BeginBlock(abci.RequestBeginBlock{}) + res = app.Deliver(tx) + + if expPass { + require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) + } else { + require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) + } + + app.EndBlock(abci.RequestEndBlock{}) + app.Commit() + + return res +} diff --git a/x/mock/types.go b/x/mock/types.go new file mode 100644 index 000000000..50957e1c4 --- /dev/null +++ b/x/mock/types.go @@ -0,0 +1,38 @@ +package mock + +import ( + "math/rand" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tendermint/crypto" +) + +type ( + // TestAndRunTx produces a fuzzed transaction, and ensures the state + // transition was as expected. It returns a descriptive message "action" + // about what this fuzzed tx actually did, for ease of debugging. + TestAndRunTx func( + t *testing.T, r *rand.Rand, app *App, ctx sdk.Context, + privKeys []crypto.PrivKey, log string, + ) (action string, err sdk.Error) + + // RandSetup performs the random setup the mock module needs. + RandSetup func(r *rand.Rand, privKeys []crypto.PrivKey) + + // An Invariant is a function which tests a particular invariant. + // If the invariant has been broken, the function should halt the + // test and output the log. + Invariant func(t *testing.T, app *App, log string) +) + +// PeriodicInvariant returns an Invariant function closure that asserts +// a given invariant if the mock application's last block modulo the given +// period is congruent to the given offset. +func PeriodicInvariant(invariant Invariant, period int, offset int) Invariant { + return func(t *testing.T, app *App, log string) { + if int(app.LastBlockHeight())%period == offset { + invariant(t, app, log) + } + } +} diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index 2a99795c6..333b4dc83 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -5,20 +5,18 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - + "github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/stake" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" ) var ( priv1 = crypto.GenPrivKeyEd25519() - addr1 = priv1.PubKey().Address() - coins = sdk.Coins{{"foocoin", 10}} + addr1 = sdk.AccAddress(priv1.PubKey().Address()) + coins = sdk.Coins{sdk.NewCoin("foocoin", 10)} ) // initialize the mock application for this module @@ -36,7 +34,7 @@ func getMockApp(t *testing.T) (*mock.App, stake.Keeper, Keeper) { mapp.SetEndBlocker(getEndBlocker(stakeKeeper)) mapp.SetInitChainer(getInitChainer(mapp, stakeKeeper)) - mapp.CompleteSetup(t, []*sdk.KVStoreKey{keyStake, keySlashing}) + require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyStake, keySlashing})) return mapp, stakeKeeper, keeper } @@ -55,32 +53,37 @@ func getEndBlocker(keeper stake.Keeper) sdk.EndBlocker { func getInitChainer(mapp *mock.App, keeper stake.Keeper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { mapp.InitChainer(ctx, req) - stake.InitGenesis(ctx, keeper, stake.DefaultGenesisState()) + stakeGenesis := stake.DefaultGenesisState() + stakeGenesis.Pool.LooseTokens = 100000 + err := stake.InitGenesis(ctx, keeper, stakeGenesis) + if err != nil { + panic(err) + } return abci.ResponseInitChain{} } } func checkValidator(t *testing.T, mapp *mock.App, keeper stake.Keeper, - addr sdk.Address, expFound bool) stake.Validator { + addr sdk.AccAddress, expFound bool) stake.Validator { ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) validator, found := keeper.GetValidator(ctxCheck, addr1) - assert.Equal(t, expFound, found) + require.Equal(t, expFound, found) return validator } func checkValidatorSigningInfo(t *testing.T, mapp *mock.App, keeper Keeper, - addr sdk.Address, expFound bool) ValidatorSigningInfo { + addr sdk.ValAddress, expFound bool) ValidatorSigningInfo { ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) signingInfo, found := keeper.getValidatorSigningInfo(ctxCheck, addr) - assert.Equal(t, expFound, found) + require.Equal(t, expFound, found) return signingInfo } func TestSlashingMsgs(t *testing.T) { mapp, stakeKeeper, keeper := getMockApp(t) - genCoin := sdk.Coin{"steak", 42} - bondCoin := sdk.Coin{"steak", 10} + genCoin := sdk.NewCoin("steak", 42) + bondCoin := sdk.NewCoin("steak", 10) acc1 := &auth.BaseAccount{ Address: addr1, @@ -92,7 +95,7 @@ func TestSlashingMsgs(t *testing.T) { createValidatorMsg := stake.NewMsgCreateValidator( addr1, priv1.PubKey(), bondCoin, description, ) - mock.SignCheckDeliver(t, mapp.BaseApp, createValidatorMsg, []int64{0}, []int64{0}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, priv1) mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) mapp.BeginBlock(abci.RequestBeginBlock{}) @@ -100,12 +103,12 @@ func TestSlashingMsgs(t *testing.T) { require.Equal(t, addr1, validator.Owner) require.Equal(t, sdk.Bonded, validator.Status()) require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) - unrevokeMsg := MsgUnrevoke{ValidatorAddr: validator.PubKey.Address()} + unrevokeMsg := MsgUnrevoke{ValidatorAddr: sdk.AccAddress(validator.PubKey.Address())} // no signing info yet - checkValidatorSigningInfo(t, mapp, keeper, addr1, false) + checkValidatorSigningInfo(t, mapp, keeper, sdk.ValAddress(addr1), false) // unrevoke should fail with unknown validator - res := mock.SignCheck(t, mapp.BaseApp, unrevokeMsg, []int64{0}, []int64{1}, priv1) - require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeInvalidValidator), res.Code) + res := mock.CheckGenTx(t, mapp.BaseApp, []sdk.Msg{unrevokeMsg}, []int64{0}, []int64{1}, false, priv1) + require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotRevoked), res.Code) } diff --git a/x/slashing/client/cli/query.go b/x/slashing/client/cli/query.go index 948e75667..0360eb315 100644 --- a/x/slashing/client/cli/query.go +++ b/x/slashing/client/cli/query.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/tmlibs/cli" + "github.com/tendermint/tendermint/libs/cli" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" @@ -25,9 +25,9 @@ func GetCmdQuerySigningInfo(storeName string, cdc *wire.Codec) *cobra.Command { if err != nil { return err } - key := slashing.GetValidatorSigningInfoKey(pk.Address()) + key := slashing.GetValidatorSigningInfoKey(sdk.ValAddress(pk.Address())) ctx := context.NewCoreContextFromViper() - res, err := ctx.Query(key, storeName) + res, err := ctx.QueryStore(key, storeName) if err != nil { return err } diff --git a/x/slashing/client/cli/tx.go b/x/slashing/client/cli/tx.go index 19b1225ba..15458ad1a 100644 --- a/x/slashing/client/cli/tx.go +++ b/x/slashing/client/cli/tx.go @@ -1,8 +1,6 @@ package cli import ( - "fmt" - "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client/context" @@ -21,7 +19,7 @@ func GetCmdUnrevoke(cdc *wire.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - validatorAddr, err := sdk.GetAccAddressBech32(args[0]) + validatorAddr, err := sdk.AccAddressFromBech32(args[0]) if err != nil { return err } @@ -29,12 +27,10 @@ func GetCmdUnrevoke(cdc *wire.Codec) *cobra.Command { msg := slashing.NewMsgUnrevoke(validatorAddr) // build and sign the transaction, then broadcast to Tendermint - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } diff --git a/x/slashing/client/rest/query.go b/x/slashing/client/rest/query.go new file mode 100644 index 000000000..123e235ce --- /dev/null +++ b/x/slashing/client/rest/query.go @@ -0,0 +1,62 @@ +package rest + +import ( + "fmt" + "net/http" + + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/slashing" +) + +func registerQueryRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) { + r.HandleFunc( + "/slashing/signing_info/{validator}", + signingInfoHandlerFn(ctx, "slashing", cdc), + ).Methods("GET") +} + +// http request handler to query signing info +func signingInfoHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + // read parameters + vars := mux.Vars(r) + bech32validator := vars["validator"] + + validatorAddr, err := sdk.ValAddressFromBech32(bech32validator) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + key := slashing.GetValidatorSigningInfoKey(validatorAddr) + res, err := ctx.QueryStore(key, storeName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't query signing info. Error: %s", err.Error()))) + return + } + + var signingInfo slashing.ValidatorSigningInfo + err = cdc.UnmarshalBinary(res, &signingInfo) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't decode signing info. Error: %s", err.Error()))) + return + } + + output, err := cdc.MarshalJSON(signingInfo) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} diff --git a/x/slashing/client/rest/rest.go b/x/slashing/client/rest/rest.go new file mode 100644 index 000000000..156d40033 --- /dev/null +++ b/x/slashing/client/rest/rest.go @@ -0,0 +1,15 @@ +package rest + +import ( + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/wire" +) + +// RegisterRoutes registers staking-related REST handlers to a router +func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { + registerQueryRoutes(ctx, r, cdc) + registerTxRoutes(ctx, r, cdc, kb) +} diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go new file mode 100644 index 000000000..f35807544 --- /dev/null +++ b/x/slashing/client/rest/tx.go @@ -0,0 +1,103 @@ +package rest + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/crypto/keys" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/slashing" +) + +func registerTxRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { + r.HandleFunc( + "/slashing/unrevoke", + unrevokeRequestHandlerFn(cdc, kb, ctx), + ).Methods("POST") +} + +// Unrevoke TX body +type UnrevokeBody struct { + LocalAccountName string `json:"name"` + Password string `json:"password"` + ChainID string `json:"chain_id"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` + Gas int64 `json:"gas"` + ValidatorAddr string `json:"validator_addr"` +} + +func unrevokeRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var m UnrevokeBody + body, err := ioutil.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + err = json.Unmarshal(body, &m) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + info, err := kb.Get(m.LocalAccountName) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(err.Error())) + return + } + + validatorAddr, err := sdk.AccAddressFromBech32(m.ValidatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + + if !bytes.Equal(info.GetPubKey().Address(), validatorAddr) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Must use own validator address")) + return + } + + ctx = ctx.WithGas(m.Gas) + ctx = ctx.WithChainID(m.ChainID) + ctx = ctx.WithAccountNumber(m.AccountNumber) + ctx = ctx.WithSequence(m.Sequence) + + msg := slashing.NewMsgUnrevoke(validatorAddr) + + txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, []sdk.Msg{msg}, cdc) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(err.Error())) + return + } + + res, err := ctx.BroadcastTx(txBytes) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + output, err := json.MarshalIndent(res, "", " ") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} diff --git a/x/slashing/errors.go b/x/slashing/errors.go index 087dc0314..3139c1662 100644 --- a/x/slashing/errors.go +++ b/x/slashing/errors.go @@ -12,41 +12,20 @@ const ( // Default slashing codespace DefaultCodespace sdk.CodespaceType = 10 - // Invalid validator - CodeInvalidValidator CodeType = 201 - // Validator jailed - CodeValidatorJailed CodeType = 202 + CodeInvalidValidator CodeType = 101 + CodeValidatorJailed CodeType = 102 + CodeValidatorNotRevoked CodeType = 103 ) func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "That address is not associated with any known validator") + return sdk.NewError(codespace, CodeInvalidValidator, "that address is not associated with any known validator") } func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Validator does not exist for that address") + return sdk.NewError(codespace, CodeInvalidValidator, "validator does not exist for that address") } func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeValidatorJailed, "Validator jailed, cannot yet be unrevoked") + return sdk.NewError(codespace, CodeValidatorJailed, "validator jailed, cannot yet be unrevoked") } - -func codeToDefaultMsg(code CodeType) string { - switch code { - case CodeInvalidValidator: - return "Invalid Validator" - case CodeValidatorJailed: - return "Validator Jailed" - default: - return sdk.CodeToDefaultMsg(code) - } -} - -func msgOrDefaultMsg(msg string, code CodeType) string { - if msg != "" { - return msg - } - return codeToDefaultMsg(code) -} - -func newError(codespace sdk.CodespaceType, code CodeType, msg string) sdk.Error { - msg = msgOrDefaultMsg(msg, code) - return sdk.NewError(codespace, code, msg) +func ErrValidatorNotRevoked(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeValidatorNotRevoked, "validator not revoked, cannot be unrevoked") } diff --git a/x/slashing/handler.go b/x/slashing/handler.go index 5994bb8f1..3991bc222 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -26,7 +26,11 @@ func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.Result { return ErrNoValidatorForAddress(k.codespace).Result() } - addr := validator.GetPubKey().Address() + if !validator.GetRevoked() { + return ErrValidatorNotRevoked(k.codespace).Result() + } + + addr := sdk.ValAddress(validator.GetPubKey().Address()) // Signing info must exist info, found := k.getValidatorSigningInfo(ctx, addr) @@ -50,7 +54,7 @@ func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.Result { // Unrevoke the validator k.validatorSet.Unrevoke(ctx, validator.GetPubKey()) - tags := sdk.NewTags("action", []byte("unrevoke"), "validator", msg.ValidatorAddr.Bytes()) + tags := sdk.NewTags("action", []byte("unrevoke"), "validator", []byte(msg.ValidatorAddr.String())) return sdk.Result{ Tags: tags, diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go new file mode 100644 index 000000000..d5a6b15db --- /dev/null +++ b/x/slashing/handler_test.go @@ -0,0 +1,29 @@ +package slashing + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake" +) + +func TestCannotUnrevokeUnlessRevoked(t *testing.T) { + // initial setup + ctx, ck, sk, keeper := createTestInput(t) + slh := NewHandler(keeper) + amtInt := int64(100) + addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) + msg := newTestMsgCreateValidator(addr, val, amt) + got := stake.NewHandler(sk)(ctx, msg) + require.True(t, got.IsOK()) + stake.EndBlocker(ctx, sk) + require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) + require.True(t, sdk.NewRatFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower())) + + // assert non-revoked validator can't be unrevoked + got = slh(ctx, NewMsgUnrevoke(addr)) + require.False(t, got.IsOK(), "allowed unrevoke of non-revoked validator") + require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotRevoked), got.Code) +} diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index d5ae09ef2..9f1ff205b 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -5,7 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/crypto" ) // Keeper of the slashing store @@ -30,29 +30,41 @@ func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, vs sdk.ValidatorSet, codespace } // handle a validator signing two blocks at the same height -func (k Keeper) handleDoubleSign(ctx sdk.Context, height int64, timestamp int64, pubkey crypto.PubKey) { +func (k Keeper) handleDoubleSign(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight int64, timestamp int64, power int64) { logger := ctx.Logger().With("module", "x/slashing") - age := ctx.BlockHeader().Time - timestamp + time := ctx.BlockHeader().Time + age := time - timestamp + address := sdk.ValAddress(pubkey.Address()) // Double sign too old if age > MaxEvidenceAge { - logger.Info(fmt.Sprintf("Ignored double sign from %s at height %d, age of %d past max age of %d", pubkey.Address(), height, age, MaxEvidenceAge)) + logger.Info(fmt.Sprintf("Ignored double sign from %s at height %d, age of %d past max age of %d", pubkey.Address(), infractionHeight, age, MaxEvidenceAge)) return } // Double sign confirmed - logger.Info(fmt.Sprintf("Confirmed double sign from %s at height %d, age of %d less than max age of %d", pubkey.Address(), height, age, MaxEvidenceAge)) - k.validatorSet.Slash(ctx, pubkey, height, SlashFractionDoubleSign) + logger.Info(fmt.Sprintf("Confirmed double sign from %s at height %d, age of %d less than max age of %d", pubkey.Address(), infractionHeight, age, MaxEvidenceAge)) + + // Slash validator + k.validatorSet.Slash(ctx, pubkey, infractionHeight, power, SlashFractionDoubleSign) + + // Revoke validator + k.validatorSet.Revoke(ctx, pubkey) + + // Jail validator + signInfo, found := k.getValidatorSigningInfo(ctx, address) + if !found { + panic(fmt.Sprintf("Expected signing info for validator %s but not found", address)) + } + signInfo.JailedUntil = time + DoubleSignUnbondDuration + k.setValidatorSigningInfo(ctx, address, signInfo) } // handle a validator signature, must be called once per validator per block -func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, signed bool) { +func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, power int64, signed bool) { logger := ctx.Logger().With("module", "x/slashing") height := ctx.BlockHeight() - if !signed { - logger.Info(fmt.Sprintf("Absent validator %s at height %d", pubkey.Address(), height)) - } - address := pubkey.Address() + address := sdk.ValAddress(pubkey.Address()) // Local index, so counts blocks validator *should* have signed // Will use the 0-value default signing info if not present, except for start height @@ -80,11 +92,14 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, signInfo.SignedBlocksCounter++ } + if !signed { + logger.Info(fmt.Sprintf("Absent validator %s at height %d, %d signed, threshold %d", pubkey.Address(), height, signInfo.SignedBlocksCounter, MinSignedPerWindow)) + } minHeight := signInfo.StartHeight + SignedBlocksWindow if height > minHeight && signInfo.SignedBlocksCounter < MinSignedPerWindow { // Downtime confirmed, slash, revoke, and jail the validator logger.Info(fmt.Sprintf("Validator %s past min height of %d and below signed blocks threshold of %d", pubkey.Address(), minHeight, MinSignedPerWindow)) - k.validatorSet.Slash(ctx, pubkey, height, SlashFractionDowntime) + k.validatorSet.Slash(ctx, pubkey, height, power, SlashFractionDowntime) k.validatorSet.Revoke(ctx, pubkey) signInfo.JailedUntil = ctx.BlockHeader().Time + DowntimeUnbondDuration } diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 1f8f9db07..6d0bf868c 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -5,33 +5,52 @@ import ( "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" + abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake" ) +// Have to change these parameters for tests +// lest the tests take forever +func init() { + SignedBlocksWindow = 1000 + MinSignedPerWindow = SignedBlocksWindow / 2 + DowntimeUnbondDuration = 60 * 60 + DoubleSignUnbondDuration = 60 * 60 +} + // Test that a validator is slashed correctly -// when we discover evidence of equivocation +// when we discover evidence of infraction func TestHandleDoubleSign(t *testing.T) { // initial setup ctx, ck, sk, keeper := createTestInput(t) - addr, val, amt := addrs[0], pks[0], int64(100) + amtInt := int64(100) + addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(addr, val, amt)) require.True(t, got.IsOK()) stake.EndBlocker(ctx, sk) - require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins - amt}}) - require.Equal(t, sdk.NewRat(amt), sk.Validator(ctx, addr).GetPower()) + require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) + require.True(t, sdk.NewRatFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower())) + + // handle a signature to set signing info + keeper.handleValidatorSignature(ctx, val, amtInt, true) // double sign less than max age - keeper.handleDoubleSign(ctx, 0, 0, val) - require.Equal(t, sdk.NewRat(amt).Mul(sdk.NewRat(19).Quo(sdk.NewRat(20))), sk.Validator(ctx, addr).GetPower()) - ctx = ctx.WithBlockHeader(abci.Header{Time: 300}) + keeper.handleDoubleSign(ctx, val, 0, 0, amtInt) + + // should be revoked + require.True(t, sk.Validator(ctx, addr).GetRevoked()) + // unrevoke to measure power + sk.Unrevoke(ctx, val) + // power should be reduced + require.Equal(t, sdk.NewRatFromInt(amt).Mul(sdk.NewRat(19).Quo(sdk.NewRat(20))), sk.Validator(ctx, addr).GetPower()) + ctx = ctx.WithBlockHeader(abci.Header{Time: 1 + MaxEvidenceAge}) // double sign past max age - keeper.handleDoubleSign(ctx, 0, 0, val) - require.Equal(t, sdk.NewRat(amt).Mul(sdk.NewRat(19).Quo(sdk.NewRat(20))), sk.Validator(ctx, addr).GetPower()) + keeper.handleDoubleSign(ctx, val, 0, 0, amtInt) + require.Equal(t, sdk.NewRatFromInt(amt).Mul(sdk.NewRat(19).Quo(sdk.NewRat(20))), sk.Validator(ctx, addr).GetPower()) } // Test a validator through uptime, downtime, revocation, @@ -40,15 +59,16 @@ func TestHandleAbsentValidator(t *testing.T) { // initial setup ctx, ck, sk, keeper := createTestInput(t) - addr, val, amt := addrs[0], pks[0], int64(100) + amtInt := int64(100) + addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) sh := stake.NewHandler(sk) slh := NewHandler(keeper) got := sh(ctx, newTestMsgCreateValidator(addr, val, amt)) require.True(t, got.IsOK()) stake.EndBlocker(ctx, sk) - require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins - amt}}) - require.Equal(t, sdk.NewRat(amt), sk.Validator(ctx, addr).GetPower()) - info, found := keeper.getValidatorSigningInfo(ctx, val.Address()) + require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) + require.True(t, sdk.NewRatFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower())) + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) require.False(t, found) require.Equal(t, int64(0), info.StartHeight) require.Equal(t, int64(0), info.IndexOffset) @@ -57,38 +77,38 @@ func TestHandleAbsentValidator(t *testing.T) { height := int64(0) // 1000 first blocks OK - for ; height < 1000; height++ { + for ; height < SignedBlocksWindow; height++ { ctx = ctx.WithBlockHeight(height) - keeper.handleValidatorSignature(ctx, val, true) + keeper.handleValidatorSignature(ctx, val, amtInt, true) } - info, found = keeper.getValidatorSigningInfo(ctx, val.Address()) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) require.True(t, found) require.Equal(t, int64(0), info.StartHeight) require.Equal(t, SignedBlocksWindow, info.SignedBlocksCounter) - // 50 blocks missed - for ; height < 1050; height++ { + // 500 blocks missed + for ; height < SignedBlocksWindow+(SignedBlocksWindow-MinSignedPerWindow); height++ { ctx = ctx.WithBlockHeight(height) - keeper.handleValidatorSignature(ctx, val, false) + keeper.handleValidatorSignature(ctx, val, amtInt, false) } - info, found = keeper.getValidatorSigningInfo(ctx, val.Address()) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) require.True(t, found) require.Equal(t, int64(0), info.StartHeight) - require.Equal(t, SignedBlocksWindow-50, info.SignedBlocksCounter) + require.Equal(t, SignedBlocksWindow-MinSignedPerWindow, info.SignedBlocksCounter) // validator should be bonded still validator, _ := sk.GetValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) pool := sk.GetPool(ctx) - require.Equal(t, int64(100), pool.BondedTokens) + require.Equal(t, int64(amtInt), pool.BondedTokens) - // 51st block missed + // 501st block missed ctx = ctx.WithBlockHeight(height) - keeper.handleValidatorSignature(ctx, val, false) - info, found = keeper.getValidatorSigningInfo(ctx, val.Address()) + keeper.handleValidatorSignature(ctx, val, amtInt, false) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) require.True(t, found) require.Equal(t, int64(0), info.StartHeight) - require.Equal(t, SignedBlocksWindow-51, info.SignedBlocksCounter) + require.Equal(t, SignedBlocksWindow-MinSignedPerWindow-1, info.SignedBlocksCounter) // validator should have been revoked validator, _ = sk.GetValidatorByPubKey(ctx, val) @@ -99,7 +119,7 @@ func TestHandleAbsentValidator(t *testing.T) { require.False(t, got.IsOK()) // unrevocation should succeed after jail expiration - ctx = ctx.WithBlockHeader(abci.Header{Time: int64(86400 * 2)}) + ctx = ctx.WithBlockHeader(abci.Header{Time: DowntimeUnbondDuration + 1}) got = slh(ctx, NewMsgUnrevoke(addr)) require.True(t, got.IsOK()) @@ -109,26 +129,33 @@ func TestHandleAbsentValidator(t *testing.T) { // validator should have been slashed pool = sk.GetPool(ctx) - require.Equal(t, int64(99), pool.BondedTokens) + require.Equal(t, int64(amtInt-1), pool.BondedTokens) // validator start height should have been changed - info, found = keeper.getValidatorSigningInfo(ctx, val.Address()) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) require.True(t, found) require.Equal(t, height, info.StartHeight) - require.Equal(t, SignedBlocksWindow-51, info.SignedBlocksCounter) + require.Equal(t, SignedBlocksWindow-MinSignedPerWindow-1, info.SignedBlocksCounter) // validator should not be immediately revoked again height++ ctx = ctx.WithBlockHeight(height) - keeper.handleValidatorSignature(ctx, val, false) + keeper.handleValidatorSignature(ctx, val, amtInt, false) validator, _ = sk.GetValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) - // validator should be revoked again after 100 unsigned blocks - nextHeight := height + 100 + // 500 signed blocks + nextHeight := height + MinSignedPerWindow + 1 + for ; height < nextHeight; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val, amtInt, false) + } + + // validator should be revoked again after 500 unsigned blocks + nextHeight = height + MinSignedPerWindow + 1 for ; height <= nextHeight; height++ { ctx = ctx.WithBlockHeight(height) - keeper.handleValidatorSignature(ctx, val, false) + keeper.handleValidatorSignature(ctx, val, amtInt, false) } validator, _ = sk.GetValidatorByPubKey(ctx, val) require.Equal(t, sdk.Unbonded, validator.GetStatus()) @@ -142,23 +169,23 @@ func TestHandleNewValidator(t *testing.T) { ctx, ck, sk, keeper := createTestInput(t) addr, val, amt := addrs[0], pks[0], int64(100) sh := stake.NewHandler(sk) - got := sh(ctx, newTestMsgCreateValidator(addr, val, amt)) + got := sh(ctx, newTestMsgCreateValidator(addr, val, sdk.NewInt(amt))) require.True(t, got.IsOK()) stake.EndBlocker(ctx, sk) - require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins - amt}}) + require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.SubRaw(amt)}}) require.Equal(t, sdk.NewRat(amt), sk.Validator(ctx, addr).GetPower()) // 1000 first blocks not a validator - ctx = ctx.WithBlockHeight(1001) + ctx = ctx.WithBlockHeight(SignedBlocksWindow + 1) // Now a validator, for two blocks - keeper.handleValidatorSignature(ctx, val, true) - ctx = ctx.WithBlockHeight(1002) - keeper.handleValidatorSignature(ctx, val, false) + keeper.handleValidatorSignature(ctx, val, 100, true) + ctx = ctx.WithBlockHeight(SignedBlocksWindow + 2) + keeper.handleValidatorSignature(ctx, val, 100, false) - info, found := keeper.getValidatorSigningInfo(ctx, val.Address()) + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) require.True(t, found) - require.Equal(t, int64(1001), info.StartHeight) + require.Equal(t, int64(SignedBlocksWindow+1), info.StartHeight) require.Equal(t, int64(2), info.IndexOffset) require.Equal(t, int64(1), info.SignedBlocksCounter) require.Equal(t, int64(0), info.JailedUntil) diff --git a/x/slashing/msg.go b/x/slashing/msg.go index 561c92266..4b1483dce 100644 --- a/x/slashing/msg.go +++ b/x/slashing/msg.go @@ -15,30 +15,26 @@ var _ sdk.Msg = &MsgUnrevoke{} // MsgUnrevoke - struct for unrevoking revoked validator type MsgUnrevoke struct { - ValidatorAddr sdk.Address `json:"address"` // address of the validator owner + ValidatorAddr sdk.AccAddress `json:"address"` // address of the validator owner } -func NewMsgUnrevoke(validatorAddr sdk.Address) MsgUnrevoke { +func NewMsgUnrevoke(validatorAddr sdk.AccAddress) MsgUnrevoke { return MsgUnrevoke{ ValidatorAddr: validatorAddr, } } //nolint -func (msg MsgUnrevoke) Type() string { return MsgType } -func (msg MsgUnrevoke) GetSigners() []sdk.Address { return []sdk.Address{msg.ValidatorAddr} } +func (msg MsgUnrevoke) Type() string { return MsgType } +func (msg MsgUnrevoke) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.ValidatorAddr} } // get the bytes for the message signer to sign on func (msg MsgUnrevoke) GetSignBytes() []byte { - b, err := cdc.MarshalJSON(struct { - ValidatorAddr string `json:"address"` - }{ - ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), - }) + b, err := cdc.MarshalJSON(msg) if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // quick validity check diff --git a/x/slashing/msg_test.go b/x/slashing/msg_test.go index be7797107..287eb6c5c 100644 --- a/x/slashing/msg_test.go +++ b/x/slashing/msg_test.go @@ -3,14 +3,14 @@ package slashing import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" ) func TestMsgUnrevokeGetSignBytes(t *testing.T) { - addr := sdk.Address("abcd") + addr := sdk.AccAddress("abcd") msg := NewMsgUnrevoke(addr) bytes := msg.GetSignBytes() - assert.Equal(t, string(bytes), `{"address":"cosmosvaladdr1v93xxeqamr0mv"}`) + require.Equal(t, string(bytes), `{"address":"cosmosaccaddr1v93xxeqhyqz5v"}`) } diff --git a/x/slashing/params.go b/x/slashing/params.go index 3bba85fa6..ebf14f283 100644 --- a/x/slashing/params.go +++ b/x/slashing/params.go @@ -4,7 +4,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -const ( +var ( // MaxEvidenceAge - Max age for evidence - 21 days (3 weeks) // TODO Should this be a governance parameter or just modifiable with SoftwareUpgradeProposals? // MaxEvidenceAge = 60 * 60 * 24 * 7 * 3 @@ -13,17 +13,22 @@ const ( // SignedBlocksWindow - sliding window for downtime slashing // TODO Governance parameter? - // TODO Temporarily set to 100 blocks for testnets - SignedBlocksWindow int64 = 100 + // TODO Temporarily set to 40000 blocks for testnets + SignedBlocksWindow int64 = 40000 // Downtime slashing threshold - 50% // TODO Governance parameter? - MinSignedPerWindow int64 = SignedBlocksWindow / 2 + MinSignedPerWindow = SignedBlocksWindow / 2 // Downtime unbond duration // TODO Governance parameter? - // TODO Temporarily set to 10 minutes for testnets - DowntimeUnbondDuration int64 = 60 * 10 + // TODO Temporarily set to five minutes for testnets + DowntimeUnbondDuration int64 = 60 * 5 + + // Double-sign unbond duration + // TODO Governance parameter? + // TODO Temporarily set to five minutes for testnets + DoubleSignUnbondDuration int64 = 60 * 5 ) var ( diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go index acbe1738b..3118793aa 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/signing_info.go @@ -8,7 +8,7 @@ import ( ) // Stored by *validator* address (not owner address) -func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.Address) (info ValidatorSigningInfo, found bool) { +func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.ValAddress) (info ValidatorSigningInfo, found bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(GetValidatorSigningInfoKey(address)) if bz == nil { @@ -21,14 +21,14 @@ func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.Address) (i } // Stored by *validator* address (not owner address) -func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.Address, info ValidatorSigningInfo) { +func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.ValAddress, info ValidatorSigningInfo) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshalBinary(info) store.Set(GetValidatorSigningInfoKey(address), bz) } // Stored by *validator* address (not owner address) -func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.Address, index int64) (signed bool) { +func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.ValAddress, index int64) (signed bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(GetValidatorSigningBitArrayKey(address, index)) if bz == nil { @@ -41,7 +41,7 @@ func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.Address } // Stored by *validator* address (not owner address) -func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.Address, index int64, signed bool) { +func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.ValAddress, index int64, signed bool) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshalBinary(signed) store.Set(GetValidatorSigningBitArrayKey(address, index), bz) @@ -72,12 +72,12 @@ func (i ValidatorSigningInfo) HumanReadableString() string { } // Stored by *validator* address (not owner address) -func GetValidatorSigningInfoKey(v sdk.Address) []byte { +func GetValidatorSigningInfoKey(v sdk.ValAddress) []byte { return append([]byte{0x01}, v.Bytes()...) } // Stored by *validator* address (not owner address) -func GetValidatorSigningBitArrayKey(v sdk.Address, i int64) []byte { +func GetValidatorSigningBitArrayKey(v sdk.ValAddress, i int64) []byte { b := make([]byte, 8) binary.LittleEndian.PutUint64(b, uint64(i)) return append([]byte{0x02}, append(v.Bytes(), b...)...) diff --git a/x/slashing/signing_info_test.go b/x/slashing/signing_info_test.go index 26113e715..742769013 100644 --- a/x/slashing/signing_info_test.go +++ b/x/slashing/signing_info_test.go @@ -4,11 +4,13 @@ import ( "testing" "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" ) func TestGetSetValidatorSigningInfo(t *testing.T) { ctx, _, _, keeper := createTestInput(t) - info, found := keeper.getValidatorSigningInfo(ctx, addrs[0]) + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(addrs[0])) require.False(t, found) newInfo := ValidatorSigningInfo{ StartHeight: int64(4), @@ -16,8 +18,8 @@ func TestGetSetValidatorSigningInfo(t *testing.T) { JailedUntil: int64(2), SignedBlocksCounter: int64(10), } - keeper.setValidatorSigningInfo(ctx, addrs[0], newInfo) - info, found = keeper.getValidatorSigningInfo(ctx, addrs[0]) + keeper.setValidatorSigningInfo(ctx, sdk.ValAddress(addrs[0]), newInfo) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(addrs[0])) require.True(t, found) require.Equal(t, info.StartHeight, int64(4)) require.Equal(t, info.IndexOffset, int64(3)) @@ -27,9 +29,9 @@ func TestGetSetValidatorSigningInfo(t *testing.T) { func TestGetSetValidatorSigningBitArray(t *testing.T) { ctx, _, _, keeper := createTestInput(t) - signed := keeper.getValidatorSigningBitArray(ctx, addrs[0], 0) + signed := keeper.getValidatorSigningBitArray(ctx, sdk.ValAddress(addrs[0]), 0) require.False(t, signed) // treat empty key as unsigned - keeper.setValidatorSigningBitArray(ctx, addrs[0], 0, true) - signed = keeper.getValidatorSigningBitArray(ctx, addrs[0], 0) + keeper.setValidatorSigningBitArray(ctx, sdk.ValAddress(addrs[0]), 0, true) + signed = keeper.getValidatorSigningBitArray(ctx, sdk.ValAddress(addrs[0]), 0) require.True(t, signed) // now should be signed } diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index 94d323d23..823a6b96b 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -7,10 +7,10 @@ import ( "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" @@ -20,18 +20,20 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake" ) +// TODO remove dependencies on staking (should only refer to validator set type from sdk) + var ( - addrs = []sdk.Address{ - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6160"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6161"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6162"), - } pks = []crypto.PubKey{ newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50"), newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"), } - initCoins int64 = 200 + addrs = []sdk.AccAddress{ + sdk.AccAddress(pks[0].Address()), + sdk.AccAddress(pks[1].Address()), + sdk.AccAddress(pks[2].Address()), + } + initCoins sdk.Int = sdk.NewInt(200) ) func createTestCodec() *wire.Codec { @@ -55,19 +57,22 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, Keep ms.MountStoreWithDB(keySlashing, sdk.StoreTypeIAVL, db) err := ms.LoadLatestVersion() require.Nil(t, err) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewTMLogger(os.Stdout)) + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewTMLogger(os.Stdout)) cdc := createTestCodec() - accountMapper := auth.NewAccountMapper(cdc, keyAcc, &auth.BaseAccount{}) + accountMapper := auth.NewAccountMapper(cdc, keyAcc, auth.ProtoBaseAccount) ck := bank.NewKeeper(accountMapper) sk := stake.NewKeeper(cdc, keyStake, ck, stake.DefaultCodespace) genesis := stake.DefaultGenesisState() - genesis.Pool.LooseUnbondedTokens = initCoins * int64(len(addrs)) - stake.InitGenesis(ctx, sk, genesis) + genesis.Pool.LooseTokens = initCoins.MulRaw(int64(len(addrs))).Int64() + err = stake.InitGenesis(ctx, sk, genesis) + require.Nil(t, err) + for _, addr := range addrs { - ck.AddCoins(ctx, addr, sdk.Coins{ + _, _, err = ck.AddCoins(ctx, addr, sdk.Coins{ {sk.GetParams(ctx).BondDenom, initCoins}, }) } + require.Nil(t, err) keeper := NewKeeper(cdc, keySlashing, sk, DefaultCodespace) return ctx, ck, sk, keeper } @@ -82,16 +87,17 @@ func newPubKey(pk string) (res crypto.PubKey) { return pkEd } -func testAddr(addr string) sdk.Address { +func testAddr(addr string) sdk.AccAddress { res := []byte(addr) return res } -func newTestMsgCreateValidator(address sdk.Address, pubKey crypto.PubKey, amt int64) stake.MsgCreateValidator { +func newTestMsgCreateValidator(address sdk.AccAddress, pubKey crypto.PubKey, amt sdk.Int) stake.MsgCreateValidator { return stake.MsgCreateValidator{ Description: stake.Description{}, + DelegatorAddr: address, ValidatorAddr: address, PubKey: pubKey, - Bond: sdk.Coin{"steak", amt}, + Delegation: sdk.Coin{"steak", amt}, } } diff --git a/x/slashing/tick.go b/x/slashing/tick.go index 526baece0..01984f870 100644 --- a/x/slashing/tick.go +++ b/x/slashing/tick.go @@ -5,7 +5,7 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - abci "github.com/tendermint/abci/types" + abci "github.com/tendermint/tendermint/abci/types" tmtypes "github.com/tendermint/tendermint/types" ) @@ -16,29 +16,33 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, sk Keeper) (tags binary.LittleEndian.PutUint64(heightBytes, uint64(req.Header.Height)) tags = sdk.NewTags("height", heightBytes) - // Deal with any equivocation evidence + // Iterate over all the validators which *should* have signed this block + // Store whether or not they have actually signed it and slash/unbond any + // which have missed too many blocks in a row (downtime slashing) + for _, signingValidator := range req.Validators { + present := signingValidator.SignedLastBlock + pubkey, err := tmtypes.PB2TM.PubKey(signingValidator.Validator.PubKey) + if err != nil { + panic(err) + } + sk.handleValidatorSignature(ctx, pubkey, signingValidator.Validator.Power, present) + } + + // Iterate through any newly discovered evidence of infraction + // Slash any validators (and since-unbonded stake within the unbonding period) + // who contributed to valid infractions for _, evidence := range req.ByzantineValidators { pk, err := tmtypes.PB2TM.PubKey(evidence.Validator.PubKey) if err != nil { panic(err) } - switch string(evidence.Type) { + switch evidence.Type { case tmtypes.ABCIEvidenceTypeDuplicateVote: - sk.handleDoubleSign(ctx, evidence.Height, evidence.Time, pk) + sk.handleDoubleSign(ctx, pk, evidence.Height, evidence.Time, evidence.Validator.Power) default: - ctx.Logger().With("module", "x/slashing").Error(fmt.Sprintf("Ignored unknown evidence type: %s", string(evidence.Type))) + ctx.Logger().With("module", "x/slashing").Error(fmt.Sprintf("ignored unknown evidence type: %s", evidence.Type)) } } - // Iterate over all the validators which *should* have signed this block - for _, validator := range req.Validators { - present := validator.SignedLastBlock - pubkey, err := tmtypes.PB2TM.PubKey(validator.Validator.PubKey) - if err != nil { - panic(err) - } - sk.handleValidatorSignature(ctx, pubkey, present) - } - return } diff --git a/x/slashing/tick_test.go b/x/slashing/tick_test.go index adf2e5e5b..247fe0972 100644 --- a/x/slashing/tick_test.go +++ b/x/slashing/tick_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" + abci "github.com/tendermint/tendermint/abci/types" tmtypes "github.com/tendermint/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -14,18 +14,18 @@ import ( func TestBeginBlocker(t *testing.T) { ctx, ck, sk, keeper := createTestInput(t) - addr, pk, amt := addrs[2], pks[2], int64(100) + addr, pk, amt := addrs[2], pks[2], sdk.NewInt(100) // bond the validator got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(addr, pk, amt)) require.True(t, got.IsOK()) stake.EndBlocker(ctx, sk) - require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins - amt}}) - require.Equal(t, sdk.NewRat(amt), sk.Validator(ctx, addr).GetPower()) + require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) + require.True(t, sdk.NewRatFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower())) val := abci.Validator{ PubKey: tmtypes.TM2PB.PubKey(pk), - Power: amt, + Power: amt.Int64(), } // mark the validator as having signed @@ -37,7 +37,7 @@ func TestBeginBlocker(t *testing.T) { } BeginBlocker(ctx, req, keeper) - info, found := keeper.getValidatorSigningInfo(ctx, pk.Address()) + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(pk.Address())) require.True(t, found) require.Equal(t, ctx.BlockHeight(), info.StartHeight) require.Equal(t, int64(1), info.IndexOffset) @@ -46,8 +46,8 @@ func TestBeginBlocker(t *testing.T) { height := int64(0) - // for 50 blocks, mark the validator as having signed - for ; height < 50; height++ { + // for 1000 blocks, mark the validator as having signed + for ; height < SignedBlocksWindow; height++ { ctx = ctx.WithBlockHeight(height) req = abci.RequestBeginBlock{ Validators: []abci.SigningValidator{{ @@ -58,8 +58,8 @@ func TestBeginBlocker(t *testing.T) { BeginBlocker(ctx, req, keeper) } - // for 51 blocks, mark the validator as having not signed - for ; height < 102; height++ { + // for 500 blocks, mark the validator as having not signed + for ; height < ((SignedBlocksWindow * 2) - MinSignedPerWindow + 1); height++ { ctx = ctx.WithBlockHeight(height) req = abci.RequestBeginBlock{ Validators: []abci.SigningValidator{{ diff --git a/x/stake/app_test.go b/x/stake/app_test.go index 940d4db2b..d73ef9f01 100644 --- a/x/stake/app_test.go +++ b/x/stake/app_test.go @@ -5,62 +5,70 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/stretchr/testify/assert" + "github.com/cosmos/cosmos-sdk/x/mock" "github.com/stretchr/testify/require" - - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" ) var ( priv1 = crypto.GenPrivKeyEd25519() - addr1 = priv1.PubKey().Address() + addr1 = sdk.AccAddress(priv1.PubKey().Address()) priv2 = crypto.GenPrivKeyEd25519() - addr2 = priv2.PubKey().Address() - addr3 = crypto.GenPrivKeyEd25519().PubKey().Address() + addr2 = sdk.AccAddress(priv2.PubKey().Address()) + addr3 = sdk.AccAddress(crypto.GenPrivKeyEd25519().PubKey().Address()) priv4 = crypto.GenPrivKeyEd25519() - addr4 = priv4.PubKey().Address() - coins = sdk.Coins{{"foocoin", 10}} + addr4 = sdk.AccAddress(priv4.PubKey().Address()) + coins = sdk.Coins{{"foocoin", sdk.NewInt(10)}} fee = auth.StdFee{ - sdk.Coins{{"foocoin", 0}}, + sdk.Coins{{"foocoin", sdk.NewInt(0)}}, 100000, } ) -// initialize the mock application for this module +// getMockApp returns an initialized mock application for this module. func getMockApp(t *testing.T) (*mock.App, Keeper) { - mapp := mock.NewApp() + mApp := mock.NewApp() + + RegisterWire(mApp.Cdc) - RegisterWire(mapp.Cdc) keyStake := sdk.NewKVStoreKey("stake") - coinKeeper := bank.NewKeeper(mapp.AccountMapper) - keeper := NewKeeper(mapp.Cdc, keyStake, coinKeeper, mapp.RegisterCodespace(DefaultCodespace)) - mapp.Router().AddRoute("stake", NewHandler(keeper)) + coinKeeper := bank.NewKeeper(mApp.AccountMapper) + keeper := NewKeeper(mApp.Cdc, keyStake, coinKeeper, mApp.RegisterCodespace(DefaultCodespace)) - mapp.SetEndBlocker(getEndBlocker(keeper)) - mapp.SetInitChainer(getInitChainer(mapp, keeper)) + mApp.Router().AddRoute("stake", NewHandler(keeper)) + mApp.SetEndBlocker(getEndBlocker(keeper)) + mApp.SetInitChainer(getInitChainer(mApp, keeper)) - mapp.CompleteSetup(t, []*sdk.KVStoreKey{keyStake}) - return mapp, keeper + require.NoError(t, mApp.CompleteSetup([]*sdk.KVStoreKey{keyStake})) + return mApp, keeper } -// stake endblocker +// getEndBlocker returns a stake endblocker. func getEndBlocker(keeper Keeper) sdk.EndBlocker { return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { validatorUpdates := EndBlocker(ctx, keeper) + return abci.ResponseEndBlock{ ValidatorUpdates: validatorUpdates, } } } -// overwrite the mock init chainer +// getInitChainer initializes the chainer of the mock app and sets the genesis +// state. It returns an empty ResponseInitChain. func getInitChainer(mapp *mock.App, keeper Keeper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { mapp.InitChainer(ctx, req) - InitGenesis(ctx, keeper, DefaultGenesisState()) + + stakeGenesis := DefaultGenesisState() + stakeGenesis.Pool.LooseTokens = 100000 + + err := InitGenesis(ctx, keeper, stakeGenesis) + if err != nil { + panic(err) + } return abci.ResponseInitChain{} } @@ -69,32 +77,37 @@ func getInitChainer(mapp *mock.App, keeper Keeper) sdk.InitChainer { //__________________________________________________________________________________________ func checkValidator(t *testing.T, mapp *mock.App, keeper Keeper, - addr sdk.Address, expFound bool) Validator { + addr sdk.AccAddress, expFound bool) Validator { ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) - validator, found := keeper.GetValidator(ctxCheck, addr1) - assert.Equal(t, expFound, found) + validator, found := keeper.GetValidator(ctxCheck, addr) + + require.Equal(t, expFound, found) return validator } -func checkDelegation(t *testing.T, mapp *mock.App, keeper Keeper, delegatorAddr, - validatorAddr sdk.Address, expFound bool, expShares sdk.Rat) { +func checkDelegation( + t *testing.T, mapp *mock.App, keeper Keeper, delegatorAddr, + validatorAddr sdk.AccAddress, expFound bool, expShares sdk.Rat, +) { ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) delegation, found := keeper.GetDelegation(ctxCheck, delegatorAddr, validatorAddr) if expFound { - assert.True(t, found) - assert.True(sdk.RatEq(t, expShares, delegation.Shares)) + require.True(t, found) + require.True(sdk.RatEq(t, expShares, delegation.Shares)) + return } - assert.False(t, found) + + require.False(t, found) } func TestStakeMsgs(t *testing.T) { - mapp, keeper := getMockApp(t) + mApp, keeper := getMockApp(t) - genCoin := sdk.Coin{"steak", 42} - bondCoin := sdk.Coin{"steak", 10} + genCoin := sdk.NewCoin("steak", 42) + bondCoin := sdk.NewCoin("steak", 10) acc1 := &auth.BaseAccount{ Address: addr1, @@ -106,52 +119,63 @@ func TestStakeMsgs(t *testing.T) { } accs := []auth.Account{acc1, acc2} - mock.SetGenesis(mapp, accs) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin}) - mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin}) - - //////////////////// - // Create Validator + mock.SetGenesis(mApp, accs) + mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin}) + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin}) + // create validator description := NewDescription("foo_moniker", "", "", "") createValidatorMsg := NewMsgCreateValidator( addr1, priv1.PubKey(), bondCoin, description, ) - mock.SignCheckDeliver(t, mapp.BaseApp, createValidatorMsg, []int64{0}, []int64{0}, true, priv1) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) - mapp.BeginBlock(abci.RequestBeginBlock{}) - validator := checkValidator(t, mapp, keeper, addr1, true) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, priv1) + mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) + mApp.BeginBlock(abci.RequestBeginBlock{}) + + validator := checkValidator(t, mApp, keeper, addr1, true) require.Equal(t, addr1, validator.Owner) require.Equal(t, sdk.Bonded, validator.Status()) require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) + // addr1 create validator on behalf of addr2 + createValidatorMsgOnBehalfOf := NewMsgCreateValidatorOnBehalfOf(addr1, addr2, priv2.PubKey(), bondCoin, description) + + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsgOnBehalfOf}, []int64{0, 1}, []int64{1, 0}, true, priv1, priv2) + mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin).Minus(bondCoin)}) + mApp.BeginBlock(abci.RequestBeginBlock{}) + + validator = checkValidator(t, mApp, keeper, addr2, true) + require.Equal(t, addr2, validator.Owner) + require.Equal(t, sdk.Bonded, validator.Status()) + require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) + // check the bond that should have been created as well - checkDelegation(t, mapp, keeper, addr1, addr1, true, sdk.NewRat(10)) - - //////////////////// - // Edit Validator + checkDelegation(t, mApp, keeper, addr1, addr1, true, sdk.NewRat(10)) + // edit the validator description = NewDescription("bar_moniker", "", "", "") editValidatorMsg := NewMsgEditValidator(addr1, description) - mock.SignCheckDeliver(t, mapp.BaseApp, editValidatorMsg, []int64{0}, []int64{1}, true, priv1) - validator = checkValidator(t, mapp, keeper, addr1, true) + + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{2}, true, priv1) + validator = checkValidator(t, mApp, keeper, addr1, true) require.Equal(t, description, validator.Description) - //////////////////// - // Delegate - - mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin}) + // delegate + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin}) delegateMsg := NewMsgDelegate(addr2, addr1, bondCoin) - mock.SignCheckDeliver(t, mapp.BaseApp, delegateMsg, []int64{1}, []int64{0}, true, priv2) - mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) - checkDelegation(t, mapp, keeper, addr2, addr1, true, sdk.NewRat(10)) - //////////////////// - // Unbond + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{delegateMsg}, []int64{1}, []int64{1}, true, priv2) + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) + checkDelegation(t, mApp, keeper, addr2, addr1, true, sdk.NewRat(10)) - unbondMsg := NewMsgUnbond(addr2, addr1, "MAX") - mock.SignCheckDeliver(t, mapp.BaseApp, unbondMsg, []int64{1}, []int64{1}, true, priv2) - mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin}) - checkDelegation(t, mapp, keeper, addr2, addr1, false, sdk.Rat{}) + // begin unbonding + beginUnbondingMsg := NewMsgBeginUnbonding(addr2, addr1, sdk.NewRat(10)) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{2}, true, priv2) + + // delegation should exist anymore + checkDelegation(t, mApp, keeper, addr2, addr1, false, sdk.Rat{}) + + // balance should be the same because bonding not yet complete + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) } diff --git a/x/stake/client/cli/flags.go b/x/stake/client/cli/flags.go index eea7e2031..bb8923b58 100644 --- a/x/stake/client/cli/flags.go +++ b/x/stake/client/cli/flags.go @@ -6,11 +6,14 @@ import ( // nolint const ( - FlagAddressDelegator = "address-delegator" - FlagAddressValidator = "address-validator" - FlagPubKey = "pubkey" - FlagAmount = "amount" - FlagShares = "shares" + FlagAddressDelegator = "address-delegator" + FlagAddressValidator = "address-validator" + FlagAddressValidatorSrc = "addr-validator-source" + FlagAddressValidatorDst = "addr-validator-dest" + FlagPubKey = "pubkey" + FlagAmount = "amount" + FlagSharesAmount = "shares-amount" + FlagSharesPercent = "shares-percent" FlagMoniker = "moniker" FlagIdentity = "keybase-sig" @@ -20,22 +23,26 @@ const ( // common flagsets to add to various functions var ( - fsPk = flag.NewFlagSet("", flag.ContinueOnError) - fsAmount = flag.NewFlagSet("", flag.ContinueOnError) - fsShares = flag.NewFlagSet("", flag.ContinueOnError) - fsDescription = flag.NewFlagSet("", flag.ContinueOnError) - fsValidator = flag.NewFlagSet("", flag.ContinueOnError) - fsDelegator = flag.NewFlagSet("", flag.ContinueOnError) + fsPk = flag.NewFlagSet("", flag.ContinueOnError) + fsAmount = flag.NewFlagSet("", flag.ContinueOnError) + fsShares = flag.NewFlagSet("", flag.ContinueOnError) + fsDescription = flag.NewFlagSet("", flag.ContinueOnError) + fsValidator = flag.NewFlagSet("", flag.ContinueOnError) + fsDelegator = flag.NewFlagSet("", flag.ContinueOnError) + fsRedelegation = flag.NewFlagSet("", flag.ContinueOnError) ) 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, "1steak", "Amount of coins to bond") - fsShares.String(FlagShares, "", "Amount of shares to unbond, either in decimal or keyword MAX (ex. 1.23456789, 99, MAX)") - fsDescription.String(FlagMoniker, "", "validator name") - fsDescription.String(FlagIdentity, "", "optional keybase signature") - fsDescription.String(FlagWebsite, "", "optional website") - fsDescription.String(FlagDetails, "", "optional details") + fsShares.String(FlagSharesAmount, "", "Amount of source-shares to either unbond or redelegate as a positive integer or decimal") + fsShares.String(FlagSharesPercent, "", "Percent of source-shares to either unbond or redelegate as a positive integer or decimal >0 and <=1") + fsDescription.String(FlagMoniker, "[do-not-modify]", "validator name") + fsDescription.String(FlagIdentity, "[do-not-modify]", "optional keybase signature") + fsDescription.String(FlagWebsite, "[do-not-modify]", "optional website") + fsDescription.String(FlagDetails, "[do-not-modify]", "optional details") fsValidator.String(FlagAddressValidator, "", "hex address of the validator") fsDelegator.String(FlagAddressDelegator, "", "hex address of the delegator") + fsRedelegation.String(FlagAddressValidatorSrc, "", "hex address of the source validator") + fsRedelegation.String(FlagAddressValidatorDst, "", "hex address of the destination validator") } diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index eac39b9ef..c2d85d4b1 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -5,12 +5,13 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/tmlibs/cli" + "github.com/tendermint/tendermint/libs/cli" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" // XXX fix + "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) // get the command to query a validator @@ -21,18 +22,19 @@ func GetCmdQueryValidator(storeName string, cdc *wire.Codec) *cobra.Command { Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - addr, err := sdk.GetAccAddressBech32(args[0]) + addr, err := sdk.AccAddressFromBech32(args[0]) if err != nil { return err } key := stake.GetValidatorKey(addr) ctx := context.NewCoreContextFromViper() - res, err := ctx.Query(key, storeName) + res, err := ctx.QueryStore(key, storeName) if err != nil { return err + } else if len(res) == 0 { + return fmt.Errorf("No validator found with address %s", args[0]) } - validator := new(stake.Validator) - cdc.MustUnmarshalBinary(res, validator) + validator := types.MustUnmarshalValidator(cdc, addr, res) switch viper.Get(cli.OutputFlag) { case "text": @@ -74,9 +76,9 @@ func GetCmdQueryValidators(storeName string, cdc *wire.Codec) *cobra.Command { // parse out the validators var validators []stake.Validator - for _, KV := range resKVs { - var validator stake.Validator - cdc.MustUnmarshalBinary(KV.Value, &validator) + for _, kv := range resKVs { + addr := kv.Key[1:] + validator := types.MustUnmarshalValidator(cdc, addr, kv.Value) validators = append(validators, validator) } @@ -105,43 +107,42 @@ func GetCmdQueryValidators(storeName string, cdc *wire.Codec) *cobra.Command { return cmd } -// get the command to query a single delegation bond +// get the command to query a single delegation func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegation", - Short: "Query a delegations bond based on address and validator address", + Short: "Query a delegation based on address and validator address", RunE: func(cmd *cobra.Command, args []string) error { - addr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) + valAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err } - delAddr, err := sdk.GetValAddressHex(viper.GetString(FlagAddressDelegator)) + delAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) if err != nil { return err } - key := stake.GetDelegationKey(delAddr, addr, cdc) + key := stake.GetDelegationKey(delAddr, valAddr) ctx := context.NewCoreContextFromViper() - res, err := ctx.Query(key, storeName) + res, err := ctx.QueryStore(key, storeName) if err != nil { return err } - // parse out the bond - bond := new(stake.Delegation) + // parse out the delegation + delegation := types.MustUnmarshalDelegation(cdc, key, res) switch viper.Get(cli.OutputFlag) { case "text": - resp, err := bond.HumanReadableString() + resp, err := delegation.HumanReadableString() if err != nil { return err } fmt.Println(resp) case "json": - cdc.MustUnmarshalBinary(res, bond) - output, err := wire.MarshalJSONIndent(cdc, bond) + output, err := wire.MarshalJSONIndent(cdc, delegation) if err != nil { return err } @@ -157,7 +158,7 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { return cmd } -// get the command to query all the validators bonded to a delegation +// get the command to query all the delegations made from one delegator func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegations [delegator-addr]", @@ -165,11 +166,11 @@ func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - delegatorAddr, err := sdk.GetAccAddressBech32(args[0]) + delegatorAddr, err := sdk.AccAddressFromBech32(args[0]) if err != nil { return err } - key := stake.GetDelegationsKey(delegatorAddr, cdc) + key := stake.GetDelegationsKey(delegatorAddr) ctx := context.NewCoreContextFromViper() resKVs, err := ctx.QuerySubspace(cdc, key, storeName) if err != nil { @@ -178,9 +179,8 @@ func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { // parse out the validators var delegations []stake.Delegation - for _, KV := range resKVs { - var delegation stake.Delegation - cdc.MustUnmarshalBinary(KV.Value, &delegation) + for _, kv := range resKVs { + delegation := types.MustUnmarshalDelegation(cdc, kv.Key, kv.Value) delegations = append(delegations, delegation) } @@ -196,3 +196,186 @@ func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { } return cmd } + +// get the command to query a single unbonding-delegation record +func GetCmdQueryUnbondingDelegation(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbonding-delegation", + Short: "Query an unbonding-delegation record based on delegator and validator address", + RunE: func(cmd *cobra.Command, args []string) error { + + valAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidator)) + if err != nil { + return err + } + + delAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } + + key := stake.GetUBDKey(delAddr, valAddr) + ctx := context.NewCoreContextFromViper() + res, err := ctx.QueryStore(key, storeName) + if err != nil { + return err + } + + // parse out the unbonding delegation + ubd := types.MustUnmarshalUBD(cdc, key, res) + + switch viper.Get(cli.OutputFlag) { + case "text": + resp, err := ubd.HumanReadableString() + if err != nil { + return err + } + fmt.Println(resp) + case "json": + output, err := wire.MarshalJSONIndent(cdc, ubd) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + } + return nil + }, + } + + cmd.Flags().AddFlagSet(fsValidator) + cmd.Flags().AddFlagSet(fsDelegator) + return cmd +} + +// get the command to query all the unbonding-delegation records for a delegator +func GetCmdQueryUnbondingDelegations(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbonding-delegations [delegator-addr]", + Short: "Query all unbonding-delegations records for one delegator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + delegatorAddr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + key := stake.GetUBDsKey(delegatorAddr) + ctx := context.NewCoreContextFromViper() + resKVs, err := ctx.QuerySubspace(cdc, key, storeName) + if err != nil { + return err + } + + // parse out the validators + var ubds []stake.UnbondingDelegation + for _, kv := range resKVs { + ubd := types.MustUnmarshalUBD(cdc, kv.Key, kv.Value) + ubds = append(ubds, ubd) + } + + output, err := wire.MarshalJSONIndent(cdc, ubds) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + + // TODO output with proofs / machine parseable etc. + }, + } + return cmd +} + +// get the command to query a single unbonding-delegation record +func GetCmdQueryRedelegation(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbonding-delegation", + Short: "Query an unbonding-delegation record based on delegator and validator address", + RunE: func(cmd *cobra.Command, args []string) error { + + valSrcAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidatorSrc)) + if err != nil { + return err + } + valDstAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidatorDst)) + if err != nil { + return err + } + delAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } + + key := stake.GetREDKey(delAddr, valSrcAddr, valDstAddr) + ctx := context.NewCoreContextFromViper() + res, err := ctx.QueryStore(key, storeName) + if err != nil { + return err + } + + // parse out the unbonding delegation + red := types.MustUnmarshalRED(cdc, key, res) + + switch viper.Get(cli.OutputFlag) { + case "text": + resp, err := red.HumanReadableString() + if err != nil { + return err + } + fmt.Println(resp) + case "json": + output, err := wire.MarshalJSONIndent(cdc, red) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + } + return nil + }, + } + + cmd.Flags().AddFlagSet(fsRedelegation) + cmd.Flags().AddFlagSet(fsDelegator) + return cmd +} + +// get the command to query all the unbonding-delegation records for a delegator +func GetCmdQueryRedelegations(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbonding-delegations [delegator-addr]", + Short: "Query all unbonding-delegations records for one delegator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + delegatorAddr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + key := stake.GetREDsKey(delegatorAddr) + ctx := context.NewCoreContextFromViper() + resKVs, err := ctx.QuerySubspace(cdc, key, storeName) + if err != nil { + return err + } + + // parse out the validators + var reds []stake.Redelegation + for _, kv := range resKVs { + red := types.MustUnmarshalRED(cdc, kv.Key, kv.Value) + reds = append(reds, red) + } + + output, err := wire.MarshalJSONIndent(cdc, reds) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + + // TODO output with proofs / machine parseable etc. + }, + } + return cmd +} diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index daa4dd9ef..72317c82c 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -3,14 +3,17 @@ package cli import ( "fmt" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) // create create validator command @@ -25,7 +28,7 @@ func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command { if err != nil { return err } - validatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) + validatorAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err } @@ -47,15 +50,23 @@ func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command { Website: viper.GetString(FlagWebsite), Details: viper.GetString(FlagDetails), } - msg := stake.NewMsgCreateValidator(validatorAddr, pk, amount, description) + + var msg sdk.Msg + if viper.GetString(FlagAddressDelegator) != "" { + delegatorAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } + msg = stake.NewMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr, pk, amount, description) + } else { + msg = stake.NewMsgCreateValidator(validatorAddr, pk, amount, description) + } // build and sign the transaction, then broadcast to Tendermint - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } @@ -64,6 +75,7 @@ func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command { cmd.Flags().AddFlagSet(fsAmount) cmd.Flags().AddFlagSet(fsDescription) cmd.Flags().AddFlagSet(fsValidator) + cmd.Flags().AddFlagSet(fsDelegator) return cmd } @@ -74,7 +86,7 @@ func GetCmdEditValidator(cdc *wire.Codec) *cobra.Command { Short: "edit and existing validator account", RunE: func(cmd *cobra.Command, args []string) error { - validatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) + validatorAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err } @@ -89,12 +101,11 @@ func GetCmdEditValidator(cdc *wire.Codec) *cobra.Command { // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } @@ -104,19 +115,22 @@ func GetCmdEditValidator(cdc *wire.Codec) *cobra.Command { return cmd } -// create edit validator command +// delegate command func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegate", - Short: "delegate coins to an existing validator", + Short: "delegate liquid tokens to an validator", RunE: func(cmd *cobra.Command, args []string) error { amount, err := sdk.ParseCoin(viper.GetString(FlagAmount)) if err != nil { return err } - delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator)) - validatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) + delegatorAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } + validatorAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err } @@ -126,12 +140,11 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } @@ -143,43 +156,197 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { } // create edit validator command -func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { +func GetCmdRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "unbond", - Short: "unbond shares from a validator", + Use: "redelegate", + Short: "redelegate illiquid tokens from one validator to another", + } + cmd.AddCommand( + client.PostCommands( + GetCmdBeginRedelegate(storeName, cdc), + GetCmdCompleteRedelegate(cdc), + )...) + return cmd +} + +// redelegate command +func GetCmdBeginRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "begin", + Short: "begin redelegation", RunE: func(cmd *cobra.Command, args []string) error { - // check the shares before broadcasting - sharesStr := viper.GetString(FlagShares) - var shares sdk.Rat - if sharesStr != "MAX" { - var err error - shares, err = sdk.NewRatFromDecimal(sharesStr) - if err != nil { - return err - } - if !shares.GT(sdk.ZeroRat()) { - return fmt.Errorf("shares must be positive integer or decimal (ex. 123, 1.23456789)") - } + var err error + delegatorAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err } - - delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator)) - validatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) + validatorSrcAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidatorSrc)) + if err != nil { + return err + } + validatorDstAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidatorDst)) if err != nil { return err } - msg := stake.NewMsgUnbond(delegatorAddr, validatorAddr, sharesStr) + // get the shares amount + sharesAmountStr := viper.GetString(FlagSharesAmount) + sharesPercentStr := viper.GetString(FlagSharesPercent) + sharesAmount, err := getShares(storeName, cdc, sharesAmountStr, sharesPercentStr, + delegatorAddr, validatorSrcAddr) + if err != nil { + return err + } + + msg := stake.NewMsgBeginRedelegate(delegatorAddr, validatorSrcAddr, validatorDstAddr, sharesAmount) // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + if err != nil { + return err + } + + return nil + }, + } + + cmd.Flags().AddFlagSet(fsShares) + cmd.Flags().AddFlagSet(fsDelegator) + cmd.Flags().AddFlagSet(fsRedelegation) + return cmd +} + +// nolint: gocyclo +// TODO: Make this pass gocyclo linting +func getShares(storeName string, cdc *wire.Codec, sharesAmountStr, sharesPercentStr string, + delegatorAddr, validatorAddr sdk.AccAddress) (sharesAmount sdk.Rat, err error) { + + switch { + case sharesAmountStr != "" && sharesPercentStr != "": + return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both") + case sharesAmountStr == "" && sharesPercentStr == "": + return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both") + case sharesAmountStr != "": + sharesAmount, err = sdk.NewRatFromDecimal(sharesAmountStr, types.MaxBondDenominatorPrecision) + if err != nil { + return sharesAmount, err + } + if !sharesAmount.GT(sdk.ZeroRat()) { + return sharesAmount, errors.Errorf("shares amount must be positive number (ex. 123, 1.23456789)") + } + case sharesPercentStr != "": + var sharesPercent sdk.Rat + sharesPercent, err = sdk.NewRatFromDecimal(sharesPercentStr, types.MaxBondDenominatorPrecision) + if err != nil { + return sharesAmount, err + } + if !sharesPercent.GT(sdk.ZeroRat()) || !sharesPercent.LTE(sdk.OneRat()) { + return sharesAmount, errors.Errorf("shares percent must be >0 and <=1 (ex. 0.01, 0.75, 1)") + } + + // make a query to get the existing delegation shares + key := stake.GetDelegationKey(delegatorAddr, validatorAddr) + ctx := context.NewCoreContextFromViper() + resQuery, err := ctx.QueryStore(key, storeName) + if err != nil { + return sharesAmount, errors.Errorf("cannot find delegation to determine percent Error: %v", err) + } + delegation := types.MustUnmarshalDelegation(cdc, key, resQuery) + sharesAmount = sharesPercent.Mul(delegation.Shares) + } + return +} + +// redelegate command +func GetCmdCompleteRedelegate(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "complete", + Short: "complete redelegation", + RunE: func(cmd *cobra.Command, args []string) error { + + delegatorAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } + validatorSrcAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidatorSrc)) + if err != nil { + return err + } + validatorDstAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidatorDst)) + if err != nil { + return err + } + + msg := stake.NewMsgCompleteRedelegate(delegatorAddr, validatorSrcAddr, validatorDstAddr) + + // build and sign the transaction, then broadcast to Tendermint + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + if err != nil { + return err + } + + return nil + }, + } + cmd.Flags().AddFlagSet(fsDelegator) + cmd.Flags().AddFlagSet(fsRedelegation) + return cmd +} + +// create edit validator command +func GetCmdUnbond(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbond", + Short: "begin or complete unbonding shares from a validator", + } + cmd.AddCommand( + client.PostCommands( + GetCmdBeginUnbonding(storeName, cdc), + GetCmdCompleteUnbonding(cdc), + )...) + return cmd +} + +// create edit validator command +func GetCmdBeginUnbonding(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "begin", + Short: "begin unbonding", + RunE: func(cmd *cobra.Command, args []string) error { + + delegatorAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } + validatorAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidator)) + if err != nil { + return err + } + + // get the shares amount + sharesAmountStr := viper.GetString(FlagSharesAmount) + sharesPercentStr := viper.GetString(FlagSharesPercent) + sharesAmount, err := getShares(storeName, cdc, sharesAmountStr, sharesPercentStr, + delegatorAddr, validatorAddr) + if err != nil { + return err + } + + msg := stake.NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sharesAmount) + + // build and sign the transaction, then broadcast to Tendermint + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } @@ -189,3 +356,37 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { cmd.Flags().AddFlagSet(fsValidator) return cmd } + +// create edit validator command +func GetCmdCompleteUnbonding(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "complete", + Short: "complete unbonding", + RunE: func(cmd *cobra.Command, args []string) error { + + delegatorAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } + validatorAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidator)) + if err != nil { + return err + } + + msg := stake.NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) + + // build and sign the transaction, then broadcast to Tendermint + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + if err != nil { + return err + } + + return nil + }, + } + cmd.Flags().AddFlagSet(fsDelegator) + cmd.Flags().AddFlagSet(fsValidator) + return cmd +} diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index f8a2f00e5..f5a17c785 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -9,22 +9,38 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) +const storeName = "stake" + func registerQueryRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) { + r.HandleFunc( - "/stake/{delegator}/bonding_status/{validator}", - bondingStatusHandlerFn(ctx, "stake", cdc), + "/stake/{delegator}/delegation/{validator}", + delegationHandlerFn(ctx, cdc), ).Methods("GET") + + r.HandleFunc( + "/stake/{delegator}/ubd/{validator}", + ubdHandlerFn(ctx, cdc), + ).Methods("GET") + + r.HandleFunc( + "/stake/{delegator}/red/{validator_src}/{validator_dst}", + redHandlerFn(ctx, cdc), + ).Methods("GET") + r.HandleFunc( "/stake/validators", - validatorsHandlerFn(ctx, "stake", cdc), + validatorsHandlerFn(ctx, cdc), ).Methods("GET") } -// http request handler to query delegator bonding status -func bondingStatusHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { +// http request handler to query a delegation +func delegationHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // read parameters @@ -32,44 +48,163 @@ func bondingStatusHandlerFn(ctx context.CoreContext, storeName string, cdc *wire bech32delegator := vars["delegator"] bech32validator := vars["validator"] - delegatorAddr, err := sdk.GetAccAddressBech32(bech32delegator) + delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - validatorAddr, err := sdk.GetValAddressBech32(bech32validator) + validatorAddr, err := sdk.AccAddressFromBech32(bech32validator) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - key := stake.GetDelegationKey(delegatorAddr, validatorAddr, cdc) + key := stake.GetDelegationKey(delegatorAddr, validatorAddr) - res, err := ctx.Query(key, storeName) + res, err := ctx.QueryStore(key, storeName) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("Couldn't query bond. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("couldn't query delegation. Error: %s", err.Error()))) return } - // the query will return empty if there is no data for this bond + // the query will return empty if there is no data for this record if len(res) == 0 { w.WriteHeader(http.StatusNoContent) return } - var bond stake.Delegation - err = cdc.UnmarshalBinary(res, &bond) + delegation, err := types.UnmarshalDelegation(cdc, key, res) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("Couldn't decode bond. Error: %s", err.Error()))) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) return } - output, err := cdc.MarshalJSON(bond) + output, err := cdc.MarshalJSON(delegation) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} + +// http request handler to query an unbonding-delegation +func ubdHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + // read parameters + vars := mux.Vars(r) + bech32delegator := vars["delegator"] + bech32validator := vars["validator"] + + delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + validatorAddr, err := sdk.AccAddressFromBech32(bech32validator) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + key := stake.GetUBDKey(delegatorAddr, validatorAddr) + + res, err := ctx.QueryStore(key, storeName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) + return + } + + // the query will return empty if there is no data for this record + if len(res) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + + ubd, err := types.UnmarshalUBD(cdc, key, res) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) + return + } + + output, err := cdc.MarshalJSON(ubd) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} + +// http request handler to query an redelegation +func redHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + // read parameters + vars := mux.Vars(r) + bech32delegator := vars["delegator"] + bech32validatorSrc := vars["validator_src"] + bech32validatorDst := vars["validator_dst"] + + delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + validatorSrcAddr, err := sdk.AccAddressFromBech32(bech32validatorSrc) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + validatorDstAddr, err := sdk.AccAddressFromBech32(bech32validatorDst) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + key := stake.GetREDKey(delegatorAddr, validatorSrcAddr, validatorDstAddr) + + res, err := ctx.QueryStore(key, storeName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't query redelegation. Error: %s", err.Error()))) + return + } + + // the query will return empty if there is no data for this record + if len(res) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + + red, err := types.UnmarshalRED(cdc, key, res) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) + return + } + + output, err := cdc.MarshalJSON(red) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) @@ -82,9 +217,9 @@ func bondingStatusHandlerFn(ctx context.CoreContext, storeName string, cdc *wire // TODO move exist next to validator struct for maintainability type StakeValidatorOutput struct { - Owner string `json:"owner"` // in bech32 - PubKey string `json:"pub_key"` // in bech32 - Revoked bool `json:"revoked"` // has the validator been revoked from bonded status? + Owner sdk.AccAddress `json:"owner"` // in bech32 + PubKey string `json:"pub_key"` // in bech32 + Revoked bool `json:"revoked"` // has the validator been revoked from bonded status? PoolShares stake.PoolShares `json:"pool_shares"` // total shares for tokens held in the pool DelegatorShares sdk.Rat `json:"delegator_shares"` // total shares issued to a validator's delegators @@ -104,17 +239,13 @@ type StakeValidatorOutput struct { } func bech32StakeValidatorOutput(validator stake.Validator) (StakeValidatorOutput, error) { - bechOwner, err := sdk.Bech32ifyVal(validator.Owner) - if err != nil { - return StakeValidatorOutput{}, err - } bechValPubkey, err := sdk.Bech32ifyValPub(validator.PubKey) if err != nil { return StakeValidatorOutput{}, err } return StakeValidatorOutput{ - Owner: bechOwner, + Owner: validator.Owner, PubKey: bechValPubkey, Revoked: validator.Revoked, @@ -137,12 +268,12 @@ func bech32StakeValidatorOutput(validator stake.Validator) (StakeValidatorOutput // TODO bech32 // http request handler to query list of validators -func validatorsHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { +func validatorsHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { kvs, err := ctx.QuerySubspace(cdc, stake.ValidatorsKey, storeName) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("Couldn't query validators. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("couldn't query validators. Error: %s", err.Error()))) return } @@ -155,15 +286,19 @@ func validatorsHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Co // parse out the validators validators := make([]StakeValidatorOutput, len(kvs)) for i, kv := range kvs { - var validator stake.Validator - var bech32Validator StakeValidatorOutput - err = cdc.UnmarshalBinary(kv.Value, &validator) - if err == nil { - bech32Validator, err = bech32StakeValidatorOutput(validator) - } + + addr := kv.Key[1:] + validator, err := types.UnmarshalValidator(cdc, addr, kv.Value) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) + return + } + + bech32Validator, err := bech32StakeValidatorOutput(validator) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) return } validators[i] = bech32Validator diff --git a/x/stake/client/rest/rest.go b/x/stake/client/rest/rest.go index 1f3a2957d..3528d45e4 100644 --- a/x/stake/client/rest/rest.go +++ b/x/stake/client/rest/rest.go @@ -1,8 +1,8 @@ package rest import ( + "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/gorilla/mux" - "github.com/tendermint/go-crypto/keys" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/wire" diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index 77a6540ee..a0f041654 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -2,19 +2,19 @@ package rest import ( "bytes" - "encoding/json" "fmt" "io/ioutil" "net/http" + "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/gorilla/mux" - "github.com/tendermint/go-crypto/keys" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) func registerTxRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { @@ -24,38 +24,59 @@ func registerTxRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, k ).Methods("POST") } -type msgDelegateInput struct { +type msgDelegationsInput struct { DelegatorAddr string `json:"delegator_addr"` // in bech32 ValidatorAddr string `json:"validator_addr"` // in bech32 - Bond sdk.Coin `json:"bond"` + Delegation sdk.Coin `json:"delegation"` } -type msgUnbondInput struct { +type msgBeginRedelegateInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 + ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 + SharesAmount string `json:"shares"` +} +type msgCompleteRedelegateInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 + ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 +} +type msgBeginUnbondingInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + SharesAmount string `json:"shares"` +} +type msgCompleteUnbondingInput struct { DelegatorAddr string `json:"delegator_addr"` // in bech32 ValidatorAddr string `json:"validator_addr"` // in bech32 - Shares string `json:"shares"` } -type editDelegationsBody struct { - LocalAccountName string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas int64 `json:"gas"` - Delegate []msgDelegateInput `json:"delegate"` - Unbond []msgUnbondInput `json:"unbond"` +// request body for edit delegations +type EditDelegationsBody struct { + LocalAccountName string `json:"name"` + Password string `json:"password"` + ChainID string `json:"chain_id"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` + Gas int64 `json:"gas"` + Delegations []msgDelegationsInput `json:"delegations"` + BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"` + CompleteUnbondings []msgCompleteUnbondingInput `json:"complete_unbondings"` + BeginRedelegates []msgBeginRedelegateInput `json:"begin_redelegates"` + CompleteRedelegates []msgCompleteRedelegateInput `json:"complete_redelegates"` } +// nolint: gocyclo +// TODO: Split this up into several smaller functions, and remove the above nolint func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var m editDelegationsBody + var m EditDelegationsBody body, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - err = json.Unmarshal(body, &m) + err = cdc.UnmarshalJSON(body, &m) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) @@ -70,22 +91,27 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte } // build messages - messages := make([]sdk.Msg, len(m.Delegate)+len(m.Unbond)) + messages := make([]sdk.Msg, len(m.Delegations)+ + len(m.BeginRedelegates)+ + len(m.CompleteRedelegates)+ + len(m.BeginUnbondings)+ + len(m.CompleteUnbondings)) + i := 0 - for _, msg := range m.Delegate { - delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr) + for _, msg := range m.Delegations { + delegatorAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) return } - validatorAddr, err := sdk.GetValAddressBech32(msg.ValidatorAddr) + validatorAddr, err := sdk.AccAddressFromBech32(msg.ValidatorAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) return } - if !bytes.Equal(info.Address(), delegatorAddr) { + if !bytes.Equal(info.GetPubKey().Address(), delegatorAddr) { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte("Must use own delegator address")) return @@ -93,32 +119,135 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte messages[i] = stake.MsgDelegate{ DelegatorAddr: delegatorAddr, ValidatorAddr: validatorAddr, - Bond: msg.Bond, + Delegation: msg.Delegation, } i++ } - for _, msg := range m.Unbond { - delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr) + + for _, msg := range m.BeginRedelegates { + delegatorAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) return } - validatorAddr, err := sdk.GetValAddressBech32(msg.ValidatorAddr) + if !bytes.Equal(info.GetPubKey().Address(), delegatorAddr) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Must use own delegator address")) + return + } + validatorSrcAddr, err := sdk.AccAddressFromBech32(msg.ValidatorSrcAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) return } - if !bytes.Equal(info.Address(), delegatorAddr) { + validatorDstAddr, err := sdk.AccAddressFromBech32(msg.ValidatorDstAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + shares, err := sdk.NewRatFromDecimal(msg.SharesAmount, types.MaxBondDenominatorPrecision) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error()))) + return + } + messages[i] = stake.MsgBeginRedelegate{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + SharesAmount: shares, + } + i++ + } + + for _, msg := range m.CompleteRedelegates { + delegatorAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) + return + } + validatorSrcAddr, err := sdk.AccAddressFromBech32(msg.ValidatorSrcAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + validatorDstAddr, err := sdk.AccAddressFromBech32(msg.ValidatorDstAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + if !bytes.Equal(info.GetPubKey().Address(), delegatorAddr) { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte("Must use own delegator address")) return } - messages[i] = stake.MsgUnbond{ + messages[i] = stake.MsgCompleteRedelegate{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + } + i++ + } + + for _, msg := range m.BeginUnbondings { + delegatorAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) + return + } + if !bytes.Equal(info.GetPubKey().Address(), delegatorAddr) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Must use own delegator address")) + return + } + validatorAddr, err := sdk.AccAddressFromBech32(msg.ValidatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + shares, err := sdk.NewRatFromDecimal(msg.SharesAmount, types.MaxBondDenominatorPrecision) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error()))) + return + } + messages[i] = stake.MsgBeginUnbonding{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + SharesAmount: shares, + } + i++ + } + + for _, msg := range m.CompleteUnbondings { + delegatorAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) + return + } + validatorAddr, err := sdk.AccAddressFromBech32(msg.ValidatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + if !bytes.Equal(info.GetPubKey().Address(), delegatorAddr) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Must use own delegator address")) + return + } + messages[i] = stake.MsgCompleteUnbonding{ DelegatorAddr: delegatorAddr, ValidatorAddr: validatorAddr, - Shares: msg.Shares, } i++ } @@ -134,7 +263,7 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte ctx = ctx.WithSequence(m.Sequence) m.Sequence++ - txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, msg, cdc) + txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, []sdk.Msg{msg}, cdc) if err != nil { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(err.Error())) @@ -158,7 +287,7 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte results[i] = res } - output, err := json.MarshalIndent(results[:], "", " ") + output, err := wire.MarshalJSONIndent(cdc, results[:]) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) diff --git a/x/stake/delegation.go b/x/stake/delegation.go deleted file mode 100644 index dedac03e5..000000000 --- a/x/stake/delegation.go +++ /dev/null @@ -1,54 +0,0 @@ -package stake - -import ( - "bytes" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Delegation represents the bond with tokens held by an account. It is -// owned by one delegator, and is associated with the voting power of one -// pubKey. -// TODO better way of managing space -type Delegation struct { - DelegatorAddr sdk.Address `json:"delegator_addr"` - ValidatorAddr sdk.Address `json:"validator_addr"` - Shares sdk.Rat `json:"shares"` - Height int64 `json:"height"` // Last height bond updated -} - -func (b Delegation) equal(b2 Delegation) bool { - return bytes.Equal(b.DelegatorAddr, b2.DelegatorAddr) && - bytes.Equal(b.ValidatorAddr, b2.ValidatorAddr) && - b.Height == b2.Height && - b.Shares.Equal(b2.Shares) -} - -// ensure fulfills the sdk validator types -var _ sdk.Delegation = Delegation{} - -// nolint - for sdk.Delegation -func (b Delegation) GetDelegator() sdk.Address { return b.DelegatorAddr } -func (b Delegation) GetValidator() sdk.Address { return b.ValidatorAddr } -func (b Delegation) GetBondShares() sdk.Rat { return b.Shares } - -//Human Friendly pretty printer -func (b Delegation) HumanReadableString() (string, error) { - bechAcc, err := sdk.Bech32ifyAcc(b.DelegatorAddr) - if err != nil { - return "", err - } - bechVal, err := sdk.Bech32ifyAcc(b.ValidatorAddr) - if err != nil { - return "", err - } - resp := "Delegation \n" - resp += fmt.Sprintf("Delegator: %s\n", bechAcc) - resp += fmt.Sprintf("Validator: %s\n", bechVal) - resp += fmt.Sprintf("Shares: %s", b.Shares.String()) - resp += fmt.Sprintf("Height: %d", b.Height) - - return resp, nil - -} diff --git a/x/stake/errors.go b/x/stake/errors.go deleted file mode 100644 index 0ae57530a..000000000 --- a/x/stake/errors.go +++ /dev/null @@ -1,117 +0,0 @@ -// nolint -package stake - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type CodeType = sdk.CodeType - -const ( - DefaultCodespace sdk.CodespaceType = 4 - - // Gaia errors reserve 200 ~ 299. - CodeInvalidValidator CodeType = 201 - CodeInvalidBond CodeType = 202 - CodeInvalidInput CodeType = 203 - CodeValidatorJailed CodeType = 204 - CodeUnauthorized CodeType = sdk.CodeUnauthorized - CodeInternal CodeType = sdk.CodeInternal - CodeUnknownRequest CodeType = sdk.CodeUnknownRequest -) - -// NOTE: Don't stringer this, we'll put better messages in later. -func codeToDefaultMsg(code CodeType) string { - switch code { - case CodeInvalidValidator: - return "Invalid Validator" - case CodeInvalidBond: - return "Invalid Bond" - case CodeInvalidInput: - return "Invalid Input" - case CodeUnauthorized: - return "Unauthorized" - case CodeInternal: - return "Internal Error" - case CodeUnknownRequest: - return "Unknown request" - default: - return sdk.CodeToDefaultMsg(code) - } -} - -//---------------------------------------- -// Error constructors - -func ErrNotEnoughBondShares(codespace sdk.CodespaceType, shares string) sdk.Error { - return newError(codespace, CodeInvalidBond, fmt.Sprintf("not enough shares only have %v", shares)) -} -func ErrValidatorEmpty(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Cannot bond to an empty validator") -} -func ErrBadBondingDenom(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidBond, "Invalid coin denomination") -} -func ErrBadBondingAmount(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidBond, "Amount must be > 0") -} -func ErrNoBondingAcct(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "No bond account for this (address, validator) pair") -} -func ErrCommissionNegative(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Commission must be positive") -} -func ErrCommissionHuge(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Commission cannot be more than 100%") -} -func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Validator does not exist for that address") -} -func ErrBadDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Delegator does not exist for that address") -} -func ErrValidatorExistsAddr(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Validator already exist, cannot re-create validator") -} -func ErrValidatorRevoked(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Validator for this address is currently revoked") -} -func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Missing signature") -} -func ErrBondNotNominated(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Cannot bond to non-nominated account") -} -func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Validator does not exist for that address") -} -func ErrNoDelegatorForAddress(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Delegator does not contain validator bond") -} -func ErrInsufficientFunds(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidInput, "Insufficient bond shares") -} -func ErrBadShares(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidInput, "bad shares provided as input, must be MAX or decimal") -} -func ErrBadRemoveValidator(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Error removing validator") -} - -//---------------------------------------- - -// TODO group with code from x/bank/errors.go - -func msgOrDefaultMsg(msg string, code CodeType) string { - if msg != "" { - return msg - } - return codeToDefaultMsg(code) -} - -func newError(codespace sdk.CodespaceType, code CodeType, msg string) sdk.Error { - msg = msgOrDefaultMsg(msg, code) - return sdk.NewError(codespace, code, msg) -} diff --git a/x/stake/genesis.go b/x/stake/genesis.go index 43ea61d8b..177f89b76 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -1,21 +1,59 @@ package stake import ( - 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" + tmtypes "github.com/tendermint/tendermint/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"` +// InitGenesis sets the pool and parameters for the provided keeper and +// initializes the IntraTxCounter. For each validator in data, it sets that +// validator in the keeper along with manually setting the indexes. In +// addition, it also sets any delegations found in data. Finally, it updates +// the bonded validators. +func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) error { + keeper.SetPool(ctx, data.Pool) + keeper.SetNewParams(ctx, data.Params) + keeper.InitIntraTxCounter(ctx) + + for _, validator := range data.Validators { + keeper.SetValidator(ctx, validator) + + if validator.PoolShares.Amount.IsZero() { + return errors.Errorf("genesis validator cannot have zero pool shares, validator: %v", validator) + } + if validator.DelegatorShares.IsZero() { + return errors.Errorf("genesis validator cannot have zero delegator shares, validator: %v", validator) + } + + // Manually set indexes for the first time + keeper.SetValidatorByPubKeyIndex(ctx, validator) + keeper.SetValidatorByPowerIndex(ctx, validator, data.Pool) + + if validator.Status() == sdk.Bonded { + keeper.SetValidatorBondedIndex(ctx, validator) + } + } + + for _, bond := range data.Bonds { + keeper.SetDelegation(ctx, bond) + } + + keeper.UpdateBondedValidatorsFull(ctx) + return nil } -func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []Delegation) GenesisState { - return GenesisState{ +// WriteGenesis 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 { + pool := keeper.GetPool(ctx) + params := keeper.GetParams(ctx) + validators := keeper.GetAllValidators(ctx) + bonds := keeper.GetAllDelegations(ctx) + + return types.GenesisState{ Pool: pool, Params: params, Validators: validators, @@ -23,60 +61,17 @@ func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []D } } -// get raw genesis raw message for testing -func DefaultGenesisState() GenesisState { - return GenesisState{ - Pool: InitialPool(), - Params: DefaultParams(), - } -} - -// InitGenesis - store genesis parameters -func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { - store := ctx.KVStore(k.storeKey) - k.setPool(ctx, data.Pool) - k.setNewParams(ctx, data.Params) - for _, validator := range data.Validators { - - // set validator - k.setValidator(ctx, validator) - - // manually set indexes for the first time - k.setValidatorByPubKeyIndex(ctx, validator) - k.setValidatorByPowerIndex(ctx, validator, data.Pool) - if validator.Status() == sdk.Bonded { - store.Set(GetValidatorsBondedKey(validator.PubKey), validator.Owner) - } - } - for _, bond := range data.Bonds { - k.setDelegation(ctx, bond) - } - k.updateBondedValidatorsFull(ctx, store) -} - -// WriteGenesis - output genesis parameters -func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState { - pool := k.GetPool(ctx) - params := k.GetParams(ctx) - validators := k.getAllValidators(ctx) - bonds := k.getAllDelegations(ctx) - return GenesisState{ - pool, - params, - validators, - bonds, - } -} - -// WriteValidators - output current validator set -func WriteValidators(ctx sdk.Context, k Keeper) (vals []tmtypes.GenesisValidator) { - k.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) { +// 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) { vals = append(vals, tmtypes.GenesisValidator{ PubKey: validator.GetPubKey(), - Power: validator.GetPower().Evaluate(), + Power: validator.GetPower().RoundInt64(), Name: validator.GetMoniker(), }) + return false }) + return } diff --git a/x/stake/genesis_test.go b/x/stake/genesis_test.go new file mode 100644 index 000000000..4ad5b3978 --- /dev/null +++ b/x/stake/genesis_test.go @@ -0,0 +1,37 @@ +package stake + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + keep "github.com/cosmos/cosmos-sdk/x/stake/keeper" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +func TestInitGenesis(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + + pool := keeper.GetPool(ctx) + pool.UnbondedTokens = 1 + pool.UnbondedShares = sdk.OneRat() + + params := keeper.GetParams(ctx) + var delegations []Delegation + + validators := []Validator{ + NewValidator(keep.Addrs[0], keep.PKs[0], Description{Moniker: "hoop"}), + } + + genesisState := types.NewGenesisState(pool, params, validators, delegations) + err := InitGenesis(ctx, keeper, genesisState) + require.Error(t, err) + + validators[0].PoolShares.Amount = sdk.OneRat() + validators[0].DelegatorShares = sdk.OneRat() + + genesisState = types.NewGenesisState(pool, params, validators, delegations) + err = InitGenesis(ctx, keeper, genesisState) + require.NoError(t, err) +} diff --git a/x/stake/handler.go b/x/stake/handler.go index f366989b6..c355179cf 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -1,24 +1,31 @@ package stake import ( - "bytes" - sdk "github.com/cosmos/cosmos-sdk/types" - abci "github.com/tendermint/abci/types" + "github.com/cosmos/cosmos-sdk/x/stake/keeper" + "github.com/cosmos/cosmos-sdk/x/stake/tags" + "github.com/cosmos/cosmos-sdk/x/stake/types" + abci "github.com/tendermint/tendermint/abci/types" ) -func NewHandler(k Keeper) sdk.Handler { +func NewHandler(k keeper.Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { // NOTE msg already has validate basic run switch msg := msg.(type) { - case MsgCreateValidator: + case types.MsgCreateValidator: return handleMsgCreateValidator(ctx, msg, k) - case MsgEditValidator: + case types.MsgEditValidator: return handleMsgEditValidator(ctx, msg, k) - case MsgDelegate: + case types.MsgDelegate: return handleMsgDelegate(ctx, msg, k) - case MsgUnbond: - return handleMsgUnbond(ctx, msg, k) + case types.MsgBeginRedelegate: + return handleMsgBeginRedelegate(ctx, msg, k) + case types.MsgCompleteRedelegate: + return handleMsgCompleteRedelegate(ctx, msg, k) + case types.MsgBeginUnbonding: + return handleMsgBeginUnbonding(ctx, msg, k) + case types.MsgCompleteUnbonding: + return handleMsgCompleteUnbonding(ctx, msg, k) default: return sdk.ErrTxDecode("invalid message parse in staking module").Result() } @@ -26,25 +33,25 @@ func NewHandler(k Keeper) sdk.Handler { } // Called every block, process inflation, update validator set -func EndBlocker(ctx sdk.Context, k Keeper) (ValidatorUpdates []abci.Validator) { +func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Validator) { pool := k.GetPool(ctx) - // Process Validator Provisions - blockTime := ctx.BlockHeader().Time // XXX assuming in seconds, confirm + // Process types.Validator Provisions + blockTime := ctx.BlockHeader().Time if pool.InflationLastTime+blockTime >= 3600 { pool.InflationLastTime = blockTime - pool = k.processProvisions(ctx) + pool = k.ProcessProvisions(ctx) } // save the params - k.setPool(ctx, pool) + k.SetPool(ctx, pool) // reset the intra-transaction counter - k.setIntraTxCounter(ctx, 0) + k.SetIntraTxCounter(ctx, 0) // calculate validator set changes - ValidatorUpdates = k.getTendermintUpdates(ctx) - k.clearTendermintUpdates(ctx) + ValidatorUpdates = k.GetTendermintUpdates(ctx) + k.ClearTendermintUpdates(ctx) return } @@ -53,212 +60,154 @@ func EndBlocker(ctx sdk.Context, k Keeper) (ValidatorUpdates []abci.Validator) { // These functions assume everything has been authenticated, // now we just perform action and save -func handleMsgCreateValidator(ctx sdk.Context, msg MsgCreateValidator, k Keeper) sdk.Result { +func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k keeper.Keeper) sdk.Result { // check to see if the pubkey or sender has been registered before _, found := k.GetValidator(ctx, msg.ValidatorAddr) if found { - return ErrValidatorExistsAddr(k.codespace).Result() + return ErrValidatorOwnerExists(k.Codespace()).Result() } - if msg.Bond.Denom != k.GetParams(ctx).BondDenom { - return ErrBadBondingDenom(k.codespace).Result() + _, found = k.GetValidatorByPubKey(ctx, msg.PubKey) + if found { + return ErrValidatorPubKeyExists(k.Codespace()).Result() } - if ctx.IsCheckTx() { - return sdk.Result{} + if msg.Delegation.Denom != k.GetParams(ctx).BondDenom { + return ErrBadDenom(k.Codespace()).Result() } validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description) - k.setValidator(ctx, validator) - k.setValidatorByPubKeyIndex(ctx, validator) - tags := sdk.NewTags( - "action", []byte("createValidator"), - "validator", msg.ValidatorAddr.Bytes(), - "moniker", []byte(msg.Description.Moniker), - "identity", []byte(msg.Description.Identity), - ) + k.SetValidator(ctx, validator) + k.SetValidatorByPubKeyIndex(ctx, validator) - // move coins from the msg.Address account to a (self-bond) delegator account + // move coins from the msg.Address account to a (self-delegation) delegator account // the validator account and global shares are updated within here - delegateTags, err := delegate(ctx, k, msg.ValidatorAddr, msg.Bond, validator) + _, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Delegation, validator) if err != nil { return err.Result() } - tags = tags.AppendTags(delegateTags) + + tags := sdk.NewTags( + tags.Action, tags.ActionCreateValidator, + tags.DstValidator, []byte(msg.ValidatorAddr.String()), + tags.Moniker, []byte(msg.Description.Moniker), + tags.Identity, []byte(msg.Description.Identity), + ) return sdk.Result{ Tags: tags, } } -func handleMsgEditValidator(ctx sdk.Context, msg MsgEditValidator, k Keeper) sdk.Result { +func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keeper.Keeper) sdk.Result { // validator must already be registered validator, found := k.GetValidator(ctx, msg.ValidatorAddr) if !found { - return ErrBadValidatorAddr(k.codespace).Result() - } - if ctx.IsCheckTx() { - return sdk.Result{} + return ErrNoValidatorFound(k.Codespace()).Result() } - // XXX move to types // replace all editable fields (clients should autofill existing values) - validator.Description.Moniker = msg.Description.Moniker - validator.Description.Identity = msg.Description.Identity - validator.Description.Website = msg.Description.Website - validator.Description.Details = msg.Description.Details + description, err := validator.Description.UpdateDescription(msg.Description) + if err != nil { + return err.Result() + } + validator.Description = description - k.updateValidator(ctx, validator) + k.UpdateValidator(ctx, validator) tags := sdk.NewTags( - "action", []byte("editValidator"), - "validator", msg.ValidatorAddr.Bytes(), - "moniker", []byte(msg.Description.Moniker), - "identity", []byte(msg.Description.Identity), + tags.Action, tags.ActionEditValidator, + tags.DstValidator, []byte(msg.ValidatorAddr.String()), + tags.Moniker, []byte(description.Moniker), + tags.Identity, []byte(description.Identity), ) return sdk.Result{ Tags: tags, } } -func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result { +func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) sdk.Result { validator, found := k.GetValidator(ctx, msg.ValidatorAddr) if !found { - return ErrBadValidatorAddr(k.codespace).Result() + return ErrNoValidatorFound(k.Codespace()).Result() } - if msg.Bond.Denom != k.GetParams(ctx).BondDenom { - return ErrBadBondingDenom(k.codespace).Result() + if msg.Delegation.Denom != k.GetParams(ctx).BondDenom { + return ErrBadDenom(k.Codespace()).Result() } if validator.Revoked == true { - return ErrValidatorRevoked(k.codespace).Result() + return ErrValidatorRevoked(k.Codespace()).Result() } - if ctx.IsCheckTx() { - return sdk.Result{} - } - tags, err := delegate(ctx, k, msg.DelegatorAddr, msg.Bond, validator) + _, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Delegation, validator) if err != nil { return err.Result() } + + tags := sdk.NewTags( + tags.Action, tags.ActionDelegate, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.DstValidator, []byte(msg.ValidatorAddr.String()), + ) return sdk.Result{ Tags: tags, } } -// common functionality between handlers -func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, - bondAmt sdk.Coin, validator Validator) (sdk.Tags, sdk.Error) { - - // Get or create the delegator bond - bond, found := k.GetDelegation(ctx, delegatorAddr, validator.Owner) - if !found { - bond = Delegation{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validator.Owner, - Shares: sdk.ZeroRat(), - } - } - - // Account new shares, save - pool := k.GetPool(ctx) - _, _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{bondAmt}) +func handleMsgBeginUnbonding(ctx sdk.Context, msg types.MsgBeginUnbonding, k keeper.Keeper) sdk.Result { + err := k.BeginUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr, msg.SharesAmount) if err != nil { - return nil, err + return err.Result() } - validator, pool, newShares := validator.addTokensFromDel(pool, bondAmt.Amount) - bond.Shares = bond.Shares.Add(newShares) - // Update bond height - bond.Height = ctx.BlockHeight() - - k.setPool(ctx, pool) - k.setDelegation(ctx, bond) - k.updateValidator(ctx, validator) - tags := sdk.NewTags("action", []byte("delegate"), "delegator", delegatorAddr.Bytes(), "validator", validator.Owner.Bytes()) - return tags, nil + tags := sdk.NewTags( + tags.Action, tags.ActionBeginUnbonding, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.SrcValidator, []byte(msg.ValidatorAddr.String()), + ) + return sdk.Result{Tags: tags} } -func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { +func handleMsgCompleteUnbonding(ctx sdk.Context, msg types.MsgCompleteUnbonding, k keeper.Keeper) sdk.Result { - // check if bond has any shares in it unbond - bond, found := k.GetDelegation(ctx, msg.DelegatorAddr, msg.ValidatorAddr) - if !found { - return ErrNoDelegatorForAddress(k.codespace).Result() + err := k.CompleteUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr) + if err != nil { + return err.Result() } - var delShares sdk.Rat + tags := sdk.NewTags( + tags.Action, ActionCompleteUnbonding, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.SrcValidator, []byte(msg.ValidatorAddr.String()), + ) - // test that there are enough shares to unbond - if msg.Shares == "MAX" { - if !bond.Shares.GT(sdk.ZeroRat()) { - return ErrNotEnoughBondShares(k.codespace, msg.Shares).Result() - } - } else { - var err sdk.Error - delShares, err = sdk.NewRatFromDecimal(msg.Shares) - if err != nil { - return err.Result() - } - if bond.Shares.LT(delShares) { - return ErrNotEnoughBondShares(k.codespace, msg.Shares).Result() - } - } - - // get validator - validator, found := k.GetValidator(ctx, msg.ValidatorAddr) - if !found { - return ErrNoValidatorForAddress(k.codespace).Result() - } - - if ctx.IsCheckTx() { - return sdk.Result{} - } - - // retrieve the amount of bonds to remove (TODO remove redundancy already serialized) - if msg.Shares == "MAX" { - delShares = bond.Shares - } - - // subtract bond tokens from delegator bond - bond.Shares = bond.Shares.Sub(delShares) - - // remove the bond - revokeValidator := false - if bond.Shares.IsZero() { - - // if the bond is the owner of the validator then - // trigger a revoke validator - if bytes.Equal(bond.DelegatorAddr, validator.Owner) && - validator.Revoked == false { - revokeValidator = true - } - - k.removeDelegation(ctx, bond) - } else { - // Update bond height - bond.Height = ctx.BlockHeight() - k.setDelegation(ctx, bond) - } - - // Add the coins - pool := k.GetPool(ctx) - validator, pool, returnAmount := validator.removeDelShares(pool, delShares) - k.setPool(ctx, pool) - returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}} - k.coinKeeper.AddCoins(ctx, bond.DelegatorAddr, returnCoins) - - ///////////////////////////////////// - // revoke validator if necessary - if revokeValidator { - validator.Revoked = true - } - - validator = k.updateValidator(ctx, validator) - - if validator.DelegatorShares.IsZero() { - k.removeValidator(ctx, validator.Owner) - } - - tags := sdk.NewTags("action", []byte("unbond"), "delegator", msg.DelegatorAddr.Bytes(), "validator", msg.ValidatorAddr.Bytes()) - return sdk.Result{ - Tags: tags, - } + return sdk.Result{Tags: tags} +} + +func handleMsgBeginRedelegate(ctx sdk.Context, msg types.MsgBeginRedelegate, k keeper.Keeper) sdk.Result { + err := k.BeginRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr, + msg.ValidatorDstAddr, msg.SharesAmount) + if err != nil { + return err.Result() + } + + tags := sdk.NewTags( + tags.Action, tags.ActionBeginRedelegation, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.SrcValidator, []byte(msg.ValidatorSrcAddr.String()), + tags.DstValidator, []byte(msg.ValidatorDstAddr.String()), + ) + return sdk.Result{Tags: tags} +} + +func handleMsgCompleteRedelegate(ctx sdk.Context, msg types.MsgCompleteRedelegate, k keeper.Keeper) sdk.Result { + err := k.CompleteRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr, msg.ValidatorDstAddr) + if err != nil { + return err.Result() + } + + tags := sdk.NewTags( + tags.Action, tags.ActionCompleteRedelegation, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.SrcValidator, []byte(msg.ValidatorSrcAddr.String()), + tags.DstValidator, []byte(msg.ValidatorDstAddr.String()), + ) + return sdk.Result{Tags: tags} } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 6dcf3e66d..95a078802 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -1,54 +1,68 @@ package stake import ( - "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" + keep "github.com/cosmos/cosmos-sdk/x/stake/keeper" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) //______________________________________________________________________ -func newTestMsgCreateValidator(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgCreateValidator { - return MsgCreateValidator{ - Description: Description{}, - ValidatorAddr: address, - PubKey: pubKey, - Bond: sdk.Coin{"steak", amt}, - } +func newTestMsgCreateValidator(address sdk.AccAddress, pubKey crypto.PubKey, amt int64) MsgCreateValidator { + return types.NewMsgCreateValidator(address, pubKey, sdk.Coin{"steak", sdk.NewInt(amt)}, Description{}) } -func newTestMsgDelegate(delegatorAddr, validatorAddr sdk.Address, amt int64) MsgDelegate { +func newTestMsgDelegate(delegatorAddr, validatorAddr sdk.AccAddress, amt int64) MsgDelegate { return MsgDelegate{ DelegatorAddr: delegatorAddr, ValidatorAddr: validatorAddr, - Bond: sdk.Coin{"steak", amt}, + Delegation: sdk.Coin{"steak", sdk.NewInt(amt)}, } } +func newTestMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr sdk.AccAddress, valPubKey crypto.PubKey, amt int64) MsgCreateValidator { + return MsgCreateValidator{ + Description: Description{}, + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + PubKey: valPubKey, + Delegation: sdk.Coin{"steak", sdk.NewInt(amt)}, + } +} + +// retrieve params which are instant +func setInstantUnbondPeriod(keeper keep.Keeper, ctx sdk.Context) types.Params { + params := keeper.GetParams(ctx) + params.UnbondingTime = 0 + keeper.SetParams(ctx, params) + return params +} + //______________________________________________________________________ func TestValidatorByPowerIndex(t *testing.T) { - validatorAddr, validatorAddr3 := addrs[0], addrs[1] + validatorAddr, validatorAddr3 := keep.Addrs[0], keep.Addrs[1] initBond := int64(1000000) - initBondStr := "1000" - ctx, _, keeper := createTestInput(t, false, initBond) + ctx, _, keeper := keep.CreateTestInput(t, false, initBond) + _ = setInstantUnbondPeriod(keeper, ctx) // create validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], initBond) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) - assert.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) + require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) // verify the self-delegation exists bond, found := keeper.GetDelegation(ctx, validatorAddr, validatorAddr) require.True(t, found) - gotBond := bond.Shares.Evaluate() + gotBond := bond.Shares.RoundInt64() require.Equal(t, initBond, gotBond, "initBond: %v\ngotBond: %v\nbond: %v\n", initBond, gotBond, bond) @@ -57,109 +71,157 @@ func TestValidatorByPowerIndex(t *testing.T) { validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) pool := keeper.GetPool(ctx) - power := GetValidatorsByPowerKey(validator, pool) - require.True(t, keeper.validatorByPowerIndexExists(ctx, power)) + power := keep.GetValidatorsByPowerIndexKey(validator, pool) + require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power)) // create a second validator keep it bonded - msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, pks[2], int64(1000000)) + msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], int64(1000000)) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) - assert.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) + require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) // slash and revoke the first validator - keeper.Slash(ctx, pks[0], 0, sdk.NewRat(1, 2)) - keeper.Revoke(ctx, pks[0]) + keeper.Slash(ctx, keep.PKs[0], 0, initBond, sdk.NewRat(1, 2)) + keeper.Revoke(ctx, keep.PKs[0]) validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - require.Equal(t, sdk.Unbonded, validator.PoolShares.Status) // ensure is unbonded - require.Equal(t, int64(500000), validator.PoolShares.Amount.Evaluate()) // ensure is unbonded + require.Equal(t, sdk.Unbonded, validator.PoolShares.Status) // ensure is unbonded + require.Equal(t, int64(500000), validator.PoolShares.Amount.RoundInt64()) // ensure is unbonded // the old power record should have been deleted as the power changed - assert.False(t, keeper.validatorByPowerIndexExists(ctx, power)) + require.False(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power)) // but the new power record should have been created validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) pool = keeper.GetPool(ctx) - power2 := GetValidatorsByPowerKey(validator, pool) - require.True(t, keeper.validatorByPowerIndexExists(ctx, power2)) + power2 := GetValidatorsByPowerIndexKey(validator, pool) + require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power2)) // inflate a bunch for i := 0; i < 20000; i++ { - pool = keeper.processProvisions(ctx) - keeper.setPool(ctx, pool) + pool = keeper.ProcessProvisions(ctx) + keeper.SetPool(ctx, pool) } // now the new record power index should be the same as the original record - power3 := GetValidatorsByPowerKey(validator, pool) - assert.Equal(t, power2, power3) + power3 := GetValidatorsByPowerIndexKey(validator, pool) + require.Equal(t, power2, power3) // unbond self-delegation - msgUnbond := NewMsgUnbond(validatorAddr, validatorAddr, "MAX") - got = handleMsgUnbond(ctx, msgUnbond, keeper) - assert.True(t, got.IsOK(), - "got: %v\nmsgUnbond: %v\ninitBondStr: %v\n", got, msgUnbond, initBondStr) + msgBeginUnbonding := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(1000000)) + msgCompleteUnbonding := NewMsgCompleteUnbonding(validatorAddr, validatorAddr) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) // verify that by power key nolonger exists _, found = keeper.GetValidator(ctx, validatorAddr) require.False(t, found) - assert.False(t, keeper.validatorByPowerIndexExists(ctx, power3)) + require.False(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power3)) } func TestDuplicatesMsgCreateValidator(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 1000) + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) - validatorAddr := addrs[0] - pk := pks[0] - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pk, 10) - got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) - assert.True(t, got.IsOK(), "%v", got) - validator, found := keeper.GetValidator(ctx, validatorAddr) + addr1, addr2 := keep.Addrs[0], keep.Addrs[1] + pk1, pk2 := keep.PKs[0], keep.PKs[1] + + msgCreateValidator1 := newTestMsgCreateValidator(addr1, pk1, 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator1, keeper) + require.True(t, got.IsOK(), "%v", got) + validator, found := keeper.GetValidator(ctx, addr1) require.True(t, found) assert.Equal(t, sdk.Bonded, validator.Status()) - assert.Equal(t, validatorAddr, validator.Owner) - assert.Equal(t, pk, validator.PubKey) + assert.Equal(t, addr1, validator.Owner) + assert.Equal(t, pk1, validator.PubKey) assert.Equal(t, sdk.NewRat(10), validator.PoolShares.Bonded()) assert.Equal(t, sdk.NewRat(10), validator.DelegatorShares) assert.Equal(t, Description{}, validator.Description) - // one validator cannot bond twice - msgCreateValidator.PubKey = pks[1] - got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) - assert.False(t, got.IsOK(), "%v", got) + // two validators can't have the same owner address + msgCreateValidator2 := newTestMsgCreateValidator(addr1, pk2, 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator2, keeper) + require.False(t, got.IsOK(), "%v", got) + + // two validators can't have the same pubkey + msgCreateValidator3 := newTestMsgCreateValidator(addr2, pk1, 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator3, keeper) + require.False(t, got.IsOK(), "%v", got) + + // must have different pubkey and owner + msgCreateValidator4 := newTestMsgCreateValidator(addr2, pk2, 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator4, keeper) + require.True(t, got.IsOK(), "%v", got) + validator, found = keeper.GetValidator(ctx, addr2) + + require.True(t, found) + assert.Equal(t, sdk.Bonded, validator.Status()) + assert.Equal(t, addr2, validator.Owner) + assert.Equal(t, pk2, validator.PubKey) + assert.Equal(t, sdk.NewRat(10), validator.PoolShares.Bonded()) + assert.Equal(t, sdk.NewRat(10), validator.DelegatorShares) + assert.Equal(t, Description{}, validator.Description) +} + +func TestDuplicatesMsgCreateValidatorOnBehalfOf(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + + validatorAddr := keep.Addrs[0] + delegatorAddr := keep.Addrs[1] + pk := keep.PKs[0] + msgCreateValidatorOnBehalfOf := newTestMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr, pk, 10) + got := handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper) + require.True(t, got.IsOK(), "%v", got) + validator, found := keeper.GetValidator(ctx, validatorAddr) + + require.True(t, found) + require.Equal(t, sdk.Bonded, validator.Status()) + require.Equal(t, validatorAddr, validator.Owner) + require.Equal(t, pk, validator.PubKey) + require.Equal(t, sdk.NewRat(10), validator.PoolShares.Bonded()) + require.Equal(t, sdk.NewRat(10), validator.DelegatorShares) + require.Equal(t, Description{}, validator.Description) + + // one validator cannot be created twice even from different delegator + msgCreateValidatorOnBehalfOf.DelegatorAddr = keep.Addrs[2] + msgCreateValidatorOnBehalfOf.PubKey = keep.PKs[1] + got = handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper) + require.False(t, got.IsOK(), "%v", got) } func TestIncrementsMsgDelegate(t *testing.T) { initBond := int64(1000) - ctx, accMapper, keeper := createTestInput(t, false, initBond) + ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond) params := keeper.GetParams(ctx) bondAmount := int64(10) - validatorAddr, delegatorAddr := addrs[0], addrs[1] + validatorAddr, delegatorAddr := keep.Addrs[0], keep.Addrs[1] // first create validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], bondAmount) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], bondAmount) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) - assert.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got) + require.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got) validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) require.Equal(t, sdk.Bonded, validator.Status()) - assert.Equal(t, bondAmount, validator.DelegatorShares.Evaluate()) - assert.Equal(t, bondAmount, validator.PoolShares.Bonded().Evaluate(), "validator: %v", validator) + require.Equal(t, bondAmount, validator.DelegatorShares.RoundInt64()) + require.Equal(t, bondAmount, validator.PoolShares.Bonded().RoundInt64(), "validator: %v", validator) _, found = keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) require.False(t, found) bond, found := keeper.GetDelegation(ctx, validatorAddr, validatorAddr) require.True(t, found) - assert.Equal(t, bondAmount, bond.Shares.Evaluate()) + require.Equal(t, bondAmount, bond.Shares.RoundInt64()) pool := keeper.GetPool(ctx) exRate := validator.DelegatorShareExRate(pool) require.True(t, exRate.Equal(sdk.OneRat()), "expected exRate 1 got %v", exRate) - assert.Equal(t, bondAmount, pool.BondedShares.Evaluate()) - assert.Equal(t, bondAmount, pool.BondedTokens) + require.Equal(t, bondAmount, pool.BondedShares.RoundInt64()) + require.Equal(t, bondAmount, pool.BondedTokens) // just send the same msgbond multiple times msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, bondAmount) @@ -182,12 +244,12 @@ func TestIncrementsMsgDelegate(t *testing.T) { expBond := int64(i+1) * bondAmount expDelegatorShares := int64(i+2) * bondAmount // (1 self delegation) - expDelegatorAcc := initBond - expBond + expDelegatorAcc := sdk.NewInt(initBond - expBond) require.Equal(t, bond.Height, int64(i), "Incorrect bond height") - gotBond := bond.Shares.Evaluate() - gotDelegatorShares := validator.DelegatorShares.Evaluate() + gotBond := bond.Shares.RoundInt64() + gotDelegatorShares := validator.DelegatorShares.RoundInt64() gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) require.Equal(t, expBond, gotBond, @@ -204,32 +266,35 @@ func TestIncrementsMsgDelegate(t *testing.T) { func TestIncrementsMsgUnbond(t *testing.T) { initBond := int64(1000) - ctx, accMapper, keeper := createTestInput(t, false, initBond) - params := keeper.GetParams(ctx) + ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond) + params := setInstantUnbondPeriod(keeper, ctx) // create validator, delegate - validatorAddr, delegatorAddr := addrs[0], addrs[1] + validatorAddr, delegatorAddr := keep.Addrs[0], keep.Addrs[1] - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], initBond) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) - assert.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) + require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, initBond) got = handleMsgDelegate(ctx, msgDelegate, keeper) - assert.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) + require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - assert.Equal(t, initBond*2, validator.DelegatorShares.Evaluate()) - assert.Equal(t, initBond*2, validator.PoolShares.Bonded().Evaluate()) + require.Equal(t, initBond*2, validator.DelegatorShares.RoundInt64()) + require.Equal(t, initBond*2, validator.PoolShares.Bonded().RoundInt64()) // just send the same msgUnbond multiple times // TODO use decimals here - unbondShares, unbondSharesStr := int64(10), "10" - msgUnbond := NewMsgUnbond(delegatorAddr, validatorAddr, unbondSharesStr) + unbondShares := sdk.NewRat(10) + msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + msgCompleteUnbonding := NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) numUnbonds := 5 for i := 0; i < numUnbonds; i++ { - got := handleMsgUnbond(ctx, msgUnbond, keeper) + got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the accounts and the bond account have the appropriate values @@ -238,12 +303,12 @@ func TestIncrementsMsgUnbond(t *testing.T) { bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) require.True(t, found) - expBond := initBond - int64(i+1)*unbondShares - expDelegatorShares := 2*initBond - int64(i+1)*unbondShares - expDelegatorAcc := initBond - expBond + expBond := initBond - int64(i+1)*unbondShares.RoundInt64() + expDelegatorShares := 2*initBond - int64(i+1)*unbondShares.RoundInt64() + expDelegatorAcc := sdk.NewInt(initBond - expBond) - gotBond := bond.Shares.Evaluate() - gotDelegatorShares := validator.DelegatorShares.Evaluate() + gotBond := bond.Shares.RoundInt64() + gotDelegatorShares := validator.DelegatorShares.RoundInt64() gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) require.Equal(t, expBond, gotBond, @@ -266,58 +331,63 @@ func TestIncrementsMsgUnbond(t *testing.T) { initBond, } for _, c := range errorCases { - unbondShares := strconv.Itoa(int(c)) - msgUnbond := NewMsgUnbond(delegatorAddr, validatorAddr, unbondShares) - got = handleMsgUnbond(ctx, msgUnbond, keeper) + unbondShares := sdk.NewRat(int64(c)) + msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.False(t, got.IsOK(), "expected unbond msg to fail") } - leftBonded := initBond - unbondShares*int64(numUnbonds) + leftBonded := initBond - int64(numUnbonds)*unbondShares.RoundInt64() // should be unable to unbond one more than we have - unbondSharesStr = strconv.Itoa(int(leftBonded) + 1) - msgUnbond = NewMsgUnbond(delegatorAddr, validatorAddr, unbondSharesStr) - got = handleMsgUnbond(ctx, msgUnbond, keeper) - assert.False(t, got.IsOK(), - "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUnbond, unbondSharesStr, leftBonded) + unbondShares = sdk.NewRat(leftBonded + 1) + msgBeginUnbonding = NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.False(t, got.IsOK(), + "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgBeginUnbonding, unbondShares.String(), leftBonded) // should be able to unbond just what we have - unbondSharesStr = strconv.Itoa(int(leftBonded)) - msgUnbond = NewMsgUnbond(delegatorAddr, validatorAddr, unbondSharesStr) - got = handleMsgUnbond(ctx, msgUnbond, keeper) - assert.True(t, got.IsOK(), - "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUnbond, unbondSharesStr, leftBonded) + unbondShares = sdk.NewRat(leftBonded) + msgBeginUnbonding = NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), + "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgBeginUnbonding, unbondShares, leftBonded) } func TestMultipleMsgCreateValidator(t *testing.T) { initBond := int64(1000) - ctx, accMapper, keeper := createTestInput(t, false, initBond) - params := keeper.GetParams(ctx) - validatorAddrs := []sdk.Address{addrs[0], addrs[1], addrs[2]} + ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond) + params := setInstantUnbondPeriod(keeper, ctx) + + validatorAddrs := []sdk.AccAddress{keep.Addrs[0], keep.Addrs[1], keep.Addrs[2]} + delegatorAddrs := []sdk.AccAddress{keep.Addrs[3], keep.Addrs[4], keep.Addrs[5]} // bond them all for i, validatorAddr := range validatorAddrs { - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[i], 10) - got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + msgCreateValidatorOnBehalfOf := newTestMsgCreateValidatorOnBehalfOf(delegatorAddrs[i], validatorAddr, keep.PKs[i], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the account is bonded validators := keeper.GetValidators(ctx, 100) require.Equal(t, (i + 1), len(validators)) val := validators[i] - balanceExpd := initBond - 10 - balanceGot := accMapper.GetAccount(ctx, val.Owner).GetCoins().AmountOf(params.BondDenom) + balanceExpd := sdk.NewInt(initBond - 10) + balanceGot := accMapper.GetAccount(ctx, delegatorAddrs[i]).GetCoins().AmountOf(params.BondDenom) require.Equal(t, i+1, len(validators), "expected %d validators got %d, validators: %v", i+1, len(validators), validators) - require.Equal(t, 10, int(val.DelegatorShares.Evaluate()), "expected %d shares, got %d", 10, val.DelegatorShares) + require.Equal(t, 10, int(val.DelegatorShares.RoundInt64()), "expected %d shares, got %d", 10, val.DelegatorShares) require.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) } - // unbond them all + // unbond them all by revoking delegation for i, validatorAddr := range validatorAddrs { - validatorPre, found := keeper.GetValidator(ctx, validatorAddr) + _, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - msgUnbond := NewMsgUnbond(validatorAddr, validatorAddr, "10") // self-delegation - got := handleMsgUnbond(ctx, msgUnbond, keeper) + msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddrs[i], validatorAddr, sdk.NewRat(10)) // remove delegation + msgCompleteUnbonding := NewMsgCompleteUnbonding(delegatorAddrs[i], validatorAddr) + got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the account is unbonded @@ -328,18 +398,19 @@ func TestMultipleMsgCreateValidator(t *testing.T) { _, found = keeper.GetValidator(ctx, validatorAddr) require.False(t, found) - expBalance := initBond - gotBalance := accMapper.GetAccount(ctx, validatorPre.Owner).GetCoins().AmountOf(params.BondDenom) + expBalance := sdk.NewInt(initBond) + gotBalance := accMapper.GetAccount(ctx, delegatorAddrs[i]).GetCoins().AmountOf(params.BondDenom) require.Equal(t, expBalance, gotBalance, "expected account to have %d, got %d", expBalance, gotBalance) } } func TestMultipleMsgDelegate(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 1000) - validatorAddr, delegatorAddrs := addrs[0], addrs[1:] + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, delegatorAddrs := keep.Addrs[0], keep.Addrs[1:] + _ = setInstantUnbondPeriod(keeper, ctx) //first make a validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], 10) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) @@ -357,8 +428,11 @@ func TestMultipleMsgDelegate(t *testing.T) { // unbond them all for i, delegatorAddr := range delegatorAddrs { - msgUnbond := NewMsgUnbond(delegatorAddr, validatorAddr, "10") - got := handleMsgUnbond(ctx, msgUnbond, keeper) + msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewRat(10)) + msgCompleteUnbonding := NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) + got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the account is unbonded @@ -368,11 +442,12 @@ func TestMultipleMsgDelegate(t *testing.T) { } func TestRevokeValidator(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 1000) - validatorAddr, delegatorAddr := addrs[0], addrs[1] + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, delegatorAddr := keep.Addrs[0], keep.Addrs[1] + _ = setInstantUnbondPeriod(keeper, ctx) // create the validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], 10) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") @@ -381,24 +456,417 @@ func TestRevokeValidator(t *testing.T) { got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected ok, got %v", got) + validator, _ := keeper.GetValidator(ctx, validatorAddr) + // unbond the validators bond portion - msgUnbondValidator := NewMsgUnbond(validatorAddr, validatorAddr, "10") - got = handleMsgUnbond(ctx, msgUnbondValidator, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + msgBeginUnbondingValidator := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(10)) + msgCompleteUnbondingValidator := NewMsgCompleteUnbonding(validatorAddr, validatorAddr) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) + require.True(t, got.IsOK(), "expected no error") + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbondingValidator, keeper) + require.True(t, got.IsOK(), "expected no error") + validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - require.True(t, validator.Revoked) + require.True(t, validator.Revoked, "%v", validator) // test that this address cannot yet be bonded too because is revoked got = handleMsgDelegate(ctx, msgDelegate, keeper) - assert.False(t, got.IsOK(), "expected error, got %v", got) + require.False(t, got.IsOK(), "expected error, got %v", got) // test that the delegator can still withdraw their bonds - msgUnbondDelegator := NewMsgUnbond(delegatorAddr, validatorAddr, "10") - got = handleMsgUnbond(ctx, msgUnbondDelegator, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + msgBeginUnbondingDelegator := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewRat(10)) + msgCompleteUnbondingDelegator := NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingDelegator, keeper) + require.True(t, got.IsOK(), "expected no error") + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbondingDelegator, keeper) + require.True(t, got.IsOK(), "expected no error") // verify that the pubkey can now be reused got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) - assert.True(t, got.IsOK(), "expected ok, got %v", got) + require.True(t, got.IsOK(), "expected ok, got %v", got) +} + +func TestUnbondingPeriod(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr := keep.Addrs[0] + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 7 + keeper.SetParams(ctx, params) + + // create the validator + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // begin unbonding + msgBeginUnbonding := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(10)) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected no error") + + // cannot complete unbonding at same time + msgCompleteUnbonding := NewMsgCompleteUnbonding(validatorAddr, validatorAddr) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) + require.True(t, !got.IsOK(), "expected no error") + + // cannot complete unbonding at time 6 seconds later + origHeader := ctx.BlockHeader() + headerTime6 := origHeader + headerTime6.Time += 6 + ctx = ctx.WithBlockHeader(headerTime6) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) + require.True(t, !got.IsOK(), "expected no error") + + // can complete unbonding at time 7 seconds later + headerTime7 := origHeader + headerTime7.Time += 7 + ctx = ctx.WithBlockHeader(headerTime7) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) + require.True(t, got.IsOK(), "expected no error") +} + +func TestRedelegationPeriod(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, validatorAddr2 := keep.Addrs[0], keep.Addrs[1] + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 7 + keeper.SetParams(ctx, params) + + // create the validators + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // begin redelegate + msgBeginRedelegate := NewMsgBeginRedelegate(validatorAddr, validatorAddr, validatorAddr2, sdk.NewRat(10)) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error, %v", got) + + // cannot complete redelegation at same time + msgCompleteRedelegate := NewMsgCompleteRedelegate(validatorAddr, validatorAddr, validatorAddr2) + got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) + require.True(t, !got.IsOK(), "expected an error") + + // cannot complete redelegation at time 6 seconds later + origHeader := ctx.BlockHeader() + headerTime6 := origHeader + headerTime6.Time += 6 + ctx = ctx.WithBlockHeader(headerTime6) + got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) + require.True(t, !got.IsOK(), "expected an error") + + // can complete redelegation at time 7 seconds later + headerTime7 := origHeader + headerTime7.Time += 7 + ctx = ctx.WithBlockHeader(headerTime7) + got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error") +} + +func TestTransitiveRedelegation(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, validatorAddr2, validatorAddr3 := keep.Addrs[0], keep.Addrs[1], keep.Addrs[2] + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 0 + keeper.SetParams(ctx, params) + + // create the validators + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // begin redelegate + msgBeginRedelegate := NewMsgBeginRedelegate(validatorAddr, validatorAddr, validatorAddr2, sdk.NewRat(10)) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error, %v", got) + + // cannot redelegation to next validator while first delegation exists + msgBeginRedelegate = NewMsgBeginRedelegate(validatorAddr, validatorAddr2, validatorAddr3, sdk.NewRat(10)) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, !got.IsOK(), "expected an error, msg: %v", msgBeginRedelegate) + + // complete first redelegation + msgCompleteRedelegate := NewMsgCompleteRedelegate(validatorAddr, validatorAddr, validatorAddr2) + got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error") + + // now should be able to redelegate from the second validator to the third + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error") +} + +func TestUnbondingWhenExcessValidators(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr1, validatorAddr2, validatorAddr3 := keep.Addrs[0], keep.Addrs[1], keep.Addrs[2] + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 0 + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + // add three validators + msgCreateValidator := newTestMsgCreateValidator(validatorAddr1, keep.PKs[0], 50) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + require.Equal(t, 1, len(keeper.GetValidatorsBonded(ctx))) + + msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 30) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + require.Equal(t, 2, len(keeper.GetValidatorsBonded(ctx))) + + msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + require.Equal(t, 2, len(keeper.GetValidatorsBonded(ctx))) + + // unbond the valdator-2 + msgBeginUnbonding := NewMsgBeginUnbonding(validatorAddr2, validatorAddr2, sdk.NewRat(30)) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgBeginUnbonding") + + // because there are extra validators waiting to get in, the queued + // validator (aka. validator-1) should make it into the bonded group, thus + // the total number of validators should stay the same + vals := keeper.GetValidatorsBonded(ctx) + require.Equal(t, 2, len(vals), "vals %v", vals) + val1, found := keeper.GetValidator(ctx, validatorAddr1) + require.True(t, found) + require.Equal(t, sdk.Bonded, val1.Status(), "%v", val1) +} + +func TestJoiningAsCliffValidator(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr1, validatorAddr2 := keep.Addrs[0], keep.Addrs[1] + + // make sure that the cliff validator is nil to begin with + cliffVal := keeper.GetCliffValidator(ctx) + require.Equal(t, []byte(nil), cliffVal) + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 0 + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + // add the first validator + msgCreateValidator := newTestMsgCreateValidator(validatorAddr1, keep.PKs[0], 50) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // cliff validator should still be nil + cliffVal = keeper.GetCliffValidator(ctx) + require.Equal(t, []byte(nil), cliffVal) + + // Add the second validator + msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 30) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // now that we've reached maximum validators, the val-2 should be added to the cliff (top) + cliffVal = keeper.GetCliffValidator(ctx) + require.Equal(t, validatorAddr2.Bytes(), cliffVal) +} + +func TestJoiningToCreateFirstCliffValidator(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr1, validatorAddr2 := keep.Addrs[0], keep.Addrs[1] + + // make sure that the cliff validator is nil to begin with + cliffVal := keeper.GetCliffValidator(ctx) + require.Equal(t, []byte(nil), cliffVal) + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 0 + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + // add the first validator + msgCreateValidator := newTestMsgCreateValidator(validatorAddr1, keep.PKs[0], 50) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // cliff validator should still be nil + cliffVal = keeper.GetCliffValidator(ctx) + require.Equal(t, []byte(nil), cliffVal) + + // Add the second validator + msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 60) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // now that we've reached maximum validators, validator-1 should be added to the cliff (top) + cliffVal = keeper.GetCliffValidator(ctx) + require.Equal(t, validatorAddr1.Bytes(), cliffVal) +} + +func TestCliffValidator(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr1, validatorAddr2, validatorAddr3 := keep.Addrs[0], keep.Addrs[1], keep.Addrs[2] + + // make sure that the cliff validator is nil to begin with + cliffVal := keeper.GetCliffValidator(ctx) + require.Equal(t, []byte(nil), cliffVal) + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 0 + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + // add the first validator + msgCreateValidator := newTestMsgCreateValidator(validatorAddr1, keep.PKs[0], 50) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // cliff validator should still be nil + cliffVal = keeper.GetCliffValidator(ctx) + require.Equal(t, []byte(nil), cliffVal) + + // Add the second validator + msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 30) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // now that we've reached maximum validators, validator-2 should be added to the cliff (top) + cliffVal = keeper.GetCliffValidator(ctx) + require.Equal(t, validatorAddr2.Bytes(), cliffVal) + + // add the third validator, which should not make it to being bonded, + // so the cliff validator should not change because nobody has been kicked out + msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + cliffVal = keeper.GetCliffValidator(ctx) + require.Equal(t, validatorAddr2.Bytes(), cliffVal) + + // unbond valdator-2 + msgBeginUnbonding := NewMsgBeginUnbonding(validatorAddr2, validatorAddr2, sdk.NewRat(30)) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgBeginUnbonding") + + vals := keeper.GetValidatorsBonded(ctx) + require.Equal(t, 2, len(vals)) + + // now the validator set should be updated, + // where val-3 enters the validator set on the cliff + cliffVal = keeper.GetCliffValidator(ctx) + require.Equal(t, validatorAddr3.Bytes(), cliffVal) + + // unbond valdator-1 + msgBeginUnbonding = NewMsgBeginUnbonding(validatorAddr1, validatorAddr1, sdk.NewRat(50)) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgBeginUnbonding") + + // get bonded validators - should just be one + vals = keeper.GetValidatorsBonded(ctx) + require.Equal(t, 1, len(vals)) + + // cliff now should be empty + cliffVal = keeper.GetCliffValidator(ctx) + require.Equal(t, []byte(nil), cliffVal) +} + +func TestBondUnbondRedelegateSlashTwice(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + valA, valB, del := keep.Addrs[0], keep.Addrs[1], keep.Addrs[2] + + msgCreateValidator := newTestMsgCreateValidator(valA, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = newTestMsgCreateValidator(valB, keep.PKs[1], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // delegate 10 stake + msgDelegate := newTestMsgDelegate(del, valA, 10) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgDelegate") + + // a block passes + ctx = ctx.WithBlockHeight(1) + + // begin unbonding 4 stake + msgBeginUnbonding := NewMsgBeginUnbonding(del, valA, sdk.NewRat(4)) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgBeginUnbonding") + + // begin redelegate 6 stake + msgBeginRedelegate := NewMsgBeginRedelegate(del, valA, valB, sdk.NewRat(6)) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgBeginRedelegate") + + // destination delegation should have 6 shares + delegation, found := keeper.GetDelegation(ctx, del, valB) + require.True(t, found) + require.Equal(t, sdk.NewRat(6), delegation.Shares) + + // slash the validator by half + keeper.Slash(ctx, keep.PKs[0], 0, 20, sdk.NewRat(1, 2)) + + // unbonding delegation should have been slashed by half + unbonding, found := keeper.GetUnbondingDelegation(ctx, del, valA) + require.True(t, found) + require.Equal(t, int64(2), unbonding.Balance.Amount.Int64()) + + // redelegation should have been slashed by half + redelegation, found := keeper.GetRedelegation(ctx, del, valA, valB) + require.True(t, found) + require.Equal(t, int64(3), redelegation.Balance.Amount.Int64()) + + // destination delegation should have been slashed by half + delegation, found = keeper.GetDelegation(ctx, del, valB) + require.True(t, found) + require.Equal(t, sdk.NewRat(3), delegation.Shares) + + // validator power should have been reduced by half + validator, found := keeper.GetValidator(ctx, valA) + require.True(t, found) + require.Equal(t, sdk.NewRat(5), validator.GetPower()) + + // slash the validator for an infraction committed after the unbonding and redelegation begin + ctx = ctx.WithBlockHeight(3) + keeper.Slash(ctx, keep.PKs[0], 2, 10, sdk.NewRat(1, 2)) + + // unbonding delegation should be unchanged + unbonding, found = keeper.GetUnbondingDelegation(ctx, del, valA) + require.True(t, found) + require.Equal(t, int64(2), unbonding.Balance.Amount.Int64()) + + // redelegation should be unchanged + redelegation, found = keeper.GetRedelegation(ctx, del, valA, valB) + require.True(t, found) + require.Equal(t, int64(3), redelegation.Balance.Amount.Int64()) + + // destination delegation should be unchanged + delegation, found = keeper.GetDelegation(ctx, del, valB) + require.True(t, found) + require.Equal(t, sdk.NewRat(3), delegation.Shares) + + // 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) } diff --git a/x/stake/keeper.go b/x/stake/keeper.go deleted file mode 100644 index 4a2e6ff4b..000000000 --- a/x/stake/keeper.go +++ /dev/null @@ -1,842 +0,0 @@ -package stake - -import ( - "bytes" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" - "github.com/cosmos/cosmos-sdk/x/bank" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" -) - -// keeper of the staking store -type Keeper struct { - storeKey sdk.StoreKey - cdc *wire.Codec - coinKeeper bank.Keeper - - // codespace - codespace sdk.CodespaceType -} - -func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { - keeper := Keeper{ - storeKey: key, - cdc: cdc, - coinKeeper: ck, - codespace: codespace, - } - return keeper -} - -//_________________________________________________________________________ - -// get a single validator -func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.Address) (validator Validator, found bool) { - store := ctx.KVStore(k.storeKey) - return k.getValidator(store, addr) -} - -// get a single validator by pubkey -func (k Keeper) GetValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) (validator Validator, found bool) { - store := ctx.KVStore(k.storeKey) - addr := store.Get(GetValidatorByPubKeyIndexKey(pubkey)) - if addr == nil { - return validator, false - } - return k.getValidator(store, addr) -} - -// get a single validator (reuse store) -func (k Keeper) getValidator(store sdk.KVStore, addr sdk.Address) (validator Validator, found bool) { - b := store.Get(GetValidatorKey(addr)) - if b == nil { - return validator, false - } - k.cdc.MustUnmarshalBinary(b, &validator) - return validator, true -} - -// set the main record holding validator details -func (k Keeper) setValidator(ctx sdk.Context, validator Validator) { - store := ctx.KVStore(k.storeKey) - // set main store - bz := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorKey(validator.Owner), bz) -} - -func (k Keeper) setValidatorByPubKeyIndex(ctx sdk.Context, validator Validator) { - store := ctx.KVStore(k.storeKey) - // set pointer by pubkey - store.Set(GetValidatorByPubKeyIndexKey(validator.PubKey), validator.Owner) -} - -func (k Keeper) setValidatorByPowerIndex(ctx sdk.Context, validator Validator, pool Pool) { - store := ctx.KVStore(k.storeKey) - store.Set(GetValidatorsByPowerKey(validator, pool), validator.Owner) -} - -// used in testing -func (k Keeper) validatorByPowerIndexExists(ctx sdk.Context, power []byte) bool { - store := ctx.KVStore(k.storeKey) - return store.Get(power) != nil -} - -// Get the set of all validators with no limits, used during genesis dump -func (k Keeper) getAllValidators(ctx sdk.Context) (validators Validators) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) - - i := 0 - for ; ; i++ { - if !iterator.Valid() { - iterator.Close() - break - } - bz := iterator.Value() - var validator Validator - k.cdc.MustUnmarshalBinary(bz, &validator) - validators = append(validators, validator) - iterator.Next() - } - return validators -} - -// Get the set of all validators, retrieve a maxRetrieve number of records -func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve int16) (validators Validators) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) - - validators = make([]Validator, maxRetrieve) - i := 0 - for ; ; i++ { - if !iterator.Valid() || i > int(maxRetrieve-1) { - iterator.Close() - break - } - bz := iterator.Value() - var validator Validator - k.cdc.MustUnmarshalBinary(bz, &validator) - validators[i] = validator - iterator.Next() - } - return validators[:i] // trim -} - -//___________________________________________________________________________ - -// get the group of the bonded validators -func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []Validator) { - store := ctx.KVStore(k.storeKey) - - // add the actual validator power sorted store - maxValidators := k.GetParams(ctx).MaxValidators - validators = make([]Validator, maxValidators) - - iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedKey) - i := 0 - for ; iterator.Valid(); iterator.Next() { - - // sanity check - if i > int(maxValidators-1) { - panic("maxValidators is less than the number of records in ValidatorsBonded Store, store should have been updated") - } - address := iterator.Value() - validator, found := k.getValidator(store, address) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", address)) - } - - validators[i] = validator - i++ - } - iterator.Close() - return validators[:i] // trim -} - -// get the group of bonded validators sorted by power-rank -func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []Validator { - store := ctx.KVStore(k.storeKey) - maxValidators := k.GetParams(ctx).MaxValidators - validators := make([]Validator, maxValidators) - iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerKey) // largest to smallest - i := 0 - for { - if !iterator.Valid() || i > int(maxValidators-1) { - iterator.Close() - break - } - address := iterator.Value() - validator, found := k.getValidator(store, address) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", address)) - } - - // Reached to revoked validators, stop iterating - if validator.Revoked { - iterator.Close() - break - } - if validator.Status() == sdk.Bonded { - validators[i] = validator - i++ - } - iterator.Next() - } - return validators[:i] // trim -} - -//_________________________________________________________________________ -// Accumulated updates to the active/bonded validator set for tendermint - -// get the most recently updated validators -func (k Keeper) getTendermintUpdates(ctx sdk.Context) (updates []abci.Validator) { - store := ctx.KVStore(k.storeKey) - - iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) //smallest to largest - for ; iterator.Valid(); iterator.Next() { - valBytes := iterator.Value() - var val abci.Validator - k.cdc.MustUnmarshalBinary(valBytes, &val) - updates = append(updates, val) - } - iterator.Close() - return -} - -// remove all validator update entries after applied to Tendermint -func (k Keeper) clearTendermintUpdates(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - - // delete subspace - iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) - for ; iterator.Valid(); iterator.Next() { - store.Delete(iterator.Key()) - } - iterator.Close() -} - -//___________________________________________________________________________ - -// perfom all the nessisary steps for when a validator changes its power -// updates all validator stores as well as tendermint update store -// may kick out validators if new validator is entering the bonded validator group -func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) Validator { - store := ctx.KVStore(k.storeKey) - pool := k.getPool(store) - ownerAddr := validator.Owner - - // always update the main list ordered by owner address before exiting - defer func() { - bz := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorKey(ownerAddr), bz) - }() - - // retreive the old validator record - oldValidator, oldFound := k.GetValidator(ctx, ownerAddr) - - if validator.Revoked && oldValidator.Status() == sdk.Bonded { - validator = k.unbondValidator(ctx, store, validator) - - // need to also clear the cliff validator spot because the revoke has - // opened up a new spot which will be filled when - // updateValidatorsBonded is called - k.clearCliffValidator(ctx) - } - - powerIncreasing := false - if oldFound && oldValidator.PoolShares.Bonded().LT(validator.PoolShares.Bonded()) { - powerIncreasing = true - } - - // if already a validator, copy the old block height and counter, else set them - if oldFound && oldValidator.Status() == sdk.Bonded { - validator.BondHeight = oldValidator.BondHeight - validator.BondIntraTxCounter = oldValidator.BondIntraTxCounter - } else { - validator.BondHeight = ctx.BlockHeight() - counter := k.getIntraTxCounter(ctx) - validator.BondIntraTxCounter = counter - k.setIntraTxCounter(ctx, counter+1) - } - - // update the list ordered by voting power - if oldFound { - store.Delete(GetValidatorsByPowerKey(oldValidator, pool)) - } - valPower := GetValidatorsByPowerKey(validator, pool) - store.Set(valPower, validator.Owner) - - // efficiency case: - // if already bonded and power increasing only need to update tendermint - if powerIncreasing && !validator.Revoked && oldValidator.Status() == sdk.Bonded { - bz := k.cdc.MustMarshalBinary(validator.abciValidator(k.cdc)) - store.Set(GetTendermintUpdatesKey(ownerAddr), bz) - return validator - } - - // efficiency case: - // if was unbonded/or is a new validator - and the new power is less than the cliff validator - cliffPower := k.getCliffValidatorPower(ctx) - if cliffPower != nil && - (!oldFound || (oldFound && oldValidator.Status() == sdk.Unbonded)) && - bytes.Compare(valPower, cliffPower) == -1 { //(valPower < cliffPower - return validator - } - - // update the validator set for this validator - updatedVal := k.updateBondedValidators(ctx, store, validator) - if updatedVal.Owner != nil { // updates to validator occured to be updated - validator = updatedVal - } - return validator -} - -// XXX TODO build in consideration for revoked -// -// Update the validator group and kick out any old validators. In addition this -// function adds (or doesn't add) a validator which has updated its bonded -// tokens to the validator group. -> this validator is specified through the -// updatedValidatorAddr term. -// -// The correct subset is retrieved by iterating through an index of the -// validators sorted by power, stored using the ValidatorsByPowerKey. Simultaniously -// the current validator records are updated in store with the -// ValidatorsBondedKey. This store is used to determine if a validator is a -// validator without needing to iterate over the subspace as we do in -// GetValidators. -// -// Optionally also return the validator from a retrieve address if the validator has been bonded -func (k Keeper) updateBondedValidators(ctx sdk.Context, store sdk.KVStore, - newValidator Validator) (updatedVal Validator) { - - kickCliffValidator := false - oldCliffValidatorAddr := k.getCliffValidator(ctx) - - // add the actual validator power sorted store - maxValidators := k.GetParams(ctx).MaxValidators - iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerKey) // largest to smallest - bondedValidatorsCount := 0 - var validator Validator - for { - if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { - - // TODO benchmark if we should read the current power and not write if it's the same - if bondedValidatorsCount == int(maxValidators) { // is cliff validator - k.setCliffValidator(ctx, validator, k.GetPool(ctx)) - } - iterator.Close() - break - } - - // either retrieve the original validator from the store, - // or under the situation that this is the "new validator" just - // use the validator provided because it has not yet been updated - // in the main validator store - ownerAddr := iterator.Value() - if bytes.Equal(ownerAddr, newValidator.Owner) { - validator = newValidator - } else { - var found bool - validator, found = k.getValidator(store, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } - } - - // if not previously a validator (and unrevoked), - // kick the cliff validator / bond this new validator - if validator.Status() != sdk.Bonded && !validator.Revoked { - kickCliffValidator = true - - validator = k.bondValidator(ctx, store, validator) - if bytes.Equal(ownerAddr, newValidator.Owner) { - updatedVal = validator - } - } - - if validator.Revoked && validator.Status() == sdk.Bonded { - panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr)) - } else { - bondedValidatorsCount++ - } - - iterator.Next() - } - - // perform the actual kicks - if oldCliffValidatorAddr != nil && kickCliffValidator { - validator, found := k.getValidator(store, oldCliffValidatorAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", oldCliffValidatorAddr)) - } - k.unbondValidator(ctx, store, validator) - } - - return -} - -// full update of the bonded validator set, many can be added/kicked -func (k Keeper) updateBondedValidatorsFull(ctx sdk.Context, store sdk.KVStore) { - // clear the current validators store, add to the ToKickOut temp store - toKickOut := make(map[string]byte) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedKey) - for ; iterator.Valid(); iterator.Next() { - ownerAddr := iterator.Value() - toKickOut[string(ownerAddr)] = 0 // set anything - } - iterator.Close() - - // add the actual validator power sorted store - maxValidators := k.GetParams(ctx).MaxValidators - iterator = sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerKey) // largest to smallest - bondedValidatorsCount := 0 - var validator Validator - for { - if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { - - if bondedValidatorsCount == int(maxValidators) { // is cliff validator - k.setCliffValidator(ctx, validator, k.GetPool(ctx)) - } - iterator.Close() - break - } - - // either retrieve the original validator from the store, - // or under the situation that this is the "new validator" just - // use the validator provided because it has not yet been updated - // in the main validator store - ownerAddr := iterator.Value() - var found bool - validator, found = k.getValidator(store, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } - - _, found = toKickOut[string(ownerAddr)] - if found { - delete(toKickOut, string(ownerAddr)) - } else { - - // if it wasn't in the toKickOut group it means - // this wasn't a previously a validator, therefor - // update the validator to enter the validator group - validator = k.bondValidator(ctx, store, validator) - } - - if validator.Revoked && validator.Status() == sdk.Bonded { - panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr)) - } else { - bondedValidatorsCount++ - } - - iterator.Next() - } - - // perform the actual kicks - for key := range toKickOut { - ownerAddr := []byte(key) - validator, found := k.getValidator(store, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } - k.unbondValidator(ctx, store, validator) - } - return -} - -// perform all the store operations for when a validator status becomes unbonded -func (k Keeper) unbondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator) Validator { - pool := k.GetPool(ctx) - - // sanity check - if validator.Status() == sdk.Unbonded { - panic(fmt.Sprintf("should not already be be unbonded, validator: %v\n", validator)) - } - - // set the status - validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) - k.setPool(ctx, pool) - - // save the now unbonded validator record - bzVal := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorKey(validator.Owner), bzVal) - - // add to accumulated changes for tendermint - bzABCI := k.cdc.MustMarshalBinary(validator.abciValidatorZero(k.cdc)) - store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI) - - // also remove from the Bonded Validators Store - store.Delete(GetValidatorsBondedKey(validator.PubKey)) - return validator -} - -// perform all the store operations for when a validator status becomes bonded -func (k Keeper) bondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator) Validator { - pool := k.GetPool(ctx) - - // sanity check - if validator.Status() == sdk.Bonded { - panic(fmt.Sprintf("should not already be be bonded, validator: %v\n", validator)) - } - - // set the status - validator, pool = validator.UpdateStatus(pool, sdk.Bonded) - k.setPool(ctx, pool) - - // save the now bonded validator record to the three referenced stores - bzVal := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorKey(validator.Owner), bzVal) - store.Set(GetValidatorsBondedKey(validator.PubKey), validator.Owner) - - // add to accumulated changes for tendermint - bzABCI := k.cdc.MustMarshalBinary(validator.abciValidator(k.cdc)) - store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI) - - return validator -} - -func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { - - // first retreive the old validator record - validator, found := k.GetValidator(ctx, address) - if !found { - return - } - - // delete the old validator record - store := ctx.KVStore(k.storeKey) - pool := k.getPool(store) - store.Delete(GetValidatorKey(address)) - store.Delete(GetValidatorByPubKeyIndexKey(validator.PubKey)) - store.Delete(GetValidatorsByPowerKey(validator, pool)) - - // delete from the current and power weighted validator groups if the validator - // is bonded - and add validator with zero power to the validator updates - if store.Get(GetValidatorsBondedKey(validator.PubKey)) == nil { - return - } - store.Delete(GetValidatorsBondedKey(validator.PubKey)) - - bz := k.cdc.MustMarshalBinary(validator.abciValidatorZero(k.cdc)) - store.Set(GetTendermintUpdatesKey(address), bz) -} - -//_____________________________________________________________________ - -// load a delegator bond -func (k Keeper) GetDelegation(ctx sdk.Context, - delegatorAddr, validatorAddr sdk.Address) (bond Delegation, found bool) { - - store := ctx.KVStore(k.storeKey) - delegatorBytes := store.Get(GetDelegationKey(delegatorAddr, validatorAddr, k.cdc)) - if delegatorBytes == nil { - return bond, false - } - - k.cdc.MustUnmarshalBinary(delegatorBytes, &bond) - return bond, true -} - -// load all delegations used during genesis dump -func (k Keeper) getAllDelegations(ctx sdk.Context) (delegations []Delegation) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, DelegationKey) - - i := 0 - for ; ; i++ { - if !iterator.Valid() { - iterator.Close() - break - } - bondBytes := iterator.Value() - var delegation Delegation - k.cdc.MustUnmarshalBinary(bondBytes, &delegation) - delegations = append(delegations, delegation) - iterator.Next() - } - return delegations[:i] // trim -} - -// load all bonds of a delegator -func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.Address, maxRetrieve int16) (bonds []Delegation) { - store := ctx.KVStore(k.storeKey) - delegatorPrefixKey := GetDelegationsKey(delegator, k.cdc) - iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest - - bonds = make([]Delegation, maxRetrieve) - i := 0 - for ; ; i++ { - if !iterator.Valid() || i > int(maxRetrieve-1) { - iterator.Close() - break - } - bondBytes := iterator.Value() - var bond Delegation - k.cdc.MustUnmarshalBinary(bondBytes, &bond) - bonds[i] = bond - iterator.Next() - } - return bonds[:i] // trim -} - -func (k Keeper) setDelegation(ctx sdk.Context, bond Delegation) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(bond) - store.Set(GetDelegationKey(bond.DelegatorAddr, bond.ValidatorAddr, k.cdc), b) -} - -func (k Keeper) removeDelegation(ctx sdk.Context, bond Delegation) { - store := ctx.KVStore(k.storeKey) - store.Delete(GetDelegationKey(bond.DelegatorAddr, bond.ValidatorAddr, k.cdc)) -} - -//_______________________________________________________________________ - -// load/save the global staking params -func (k Keeper) GetParams(ctx sdk.Context) Params { - store := ctx.KVStore(k.storeKey) - return k.getParams(store) -} -func (k Keeper) getParams(store sdk.KVStore) (params Params) { - b := store.Get(ParamKey) - if b == nil { - panic("Stored params should not have been nil") - } - - k.cdc.MustUnmarshalBinary(b, ¶ms) - return -} - -// Need a distinct function because setParams depends on an existing previous -// record of params to exist (to check if maxValidators has changed) - and we -// panic on retrieval if it doesn't exist - hence if we use setParams for the very -// first params set it will panic. -func (k Keeper) setNewParams(ctx sdk.Context, params Params) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(params) - store.Set(ParamKey, b) -} - -func (k Keeper) setParams(ctx sdk.Context, params Params) { - store := ctx.KVStore(k.storeKey) - exParams := k.getParams(store) - - // if max validator count changes, must recalculate validator set - if exParams.MaxValidators != params.MaxValidators { - k.updateBondedValidatorsFull(ctx, store) - } - b := k.cdc.MustMarshalBinary(params) - store.Set(ParamKey, b) -} - -//_______________________________________________________________________ - -// load/save the pool -func (k Keeper) GetPool(ctx sdk.Context) (pool Pool) { - store := ctx.KVStore(k.storeKey) - return k.getPool(store) -} -func (k Keeper) getPool(store sdk.KVStore) (pool Pool) { - b := store.Get(PoolKey) - if b == nil { - panic("Stored pool should not have been nil") - } - k.cdc.MustUnmarshalBinary(b, &pool) - return -} - -func (k Keeper) setPool(ctx sdk.Context, pool Pool) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(pool) - store.Set(PoolKey, b) -} - -//__________________________________________________________________________ - -// get the current in-block validator operation counter -func (k Keeper) getIntraTxCounter(ctx sdk.Context) int16 { - store := ctx.KVStore(k.storeKey) - b := store.Get(IntraTxCounterKey) - if b == nil { - return 0 - } - var counter int16 - k.cdc.MustUnmarshalBinary(b, &counter) - 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) - store.Set(IntraTxCounterKey, bz) -} - -//__________________________________________________________________________ - -// get the current validator on the cliff -func (k Keeper) getCliffValidator(ctx sdk.Context) []byte { - store := ctx.KVStore(k.storeKey) - return store.Get(ValidatorCliffKey) -} - -// get the current power of the validator on the cliff -func (k Keeper) getCliffValidatorPower(ctx sdk.Context) []byte { - store := ctx.KVStore(k.storeKey) - return store.Get(ValidatorPowerCliffKey) -} - -// set the current validator and power of the validator on the cliff -func (k Keeper) setCliffValidator(ctx sdk.Context, validator Validator, pool Pool) { - store := ctx.KVStore(k.storeKey) - bz := GetValidatorsByPowerKey(validator, pool) - store.Set(ValidatorPowerCliffKey, bz) - store.Set(ValidatorCliffKey, validator.Owner) -} - -// clear the current validator and power of the validator on the cliff -func (k Keeper) clearCliffValidator(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - store.Delete(ValidatorPowerCliffKey) - store.Delete(ValidatorCliffKey) -} - -//__________________________________________________________________________ - -// Implements ValidatorSet - -var _ sdk.ValidatorSet = Keeper{} - -// iterate through the active validator set and perform the provided function -func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) - i := int64(0) - for ; iterator.Valid(); iterator.Next() { - bz := iterator.Value() - var validator Validator - k.cdc.MustUnmarshalBinary(bz, &validator) - stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? - if stop { - break - } - i++ - } - iterator.Close() -} - -// 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)) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedKey) - i := int64(0) - for ; iterator.Valid(); iterator.Next() { - address := iterator.Value() - validator, found := k.getValidator(store, address) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", address)) - } - - stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? - if stop { - break - } - i++ - } - iterator.Close() -} - -// get the sdk.validator for a particular address -func (k Keeper) Validator(ctx sdk.Context, addr sdk.Address) sdk.Validator { - val, found := k.GetValidator(ctx, addr) - if !found { - return nil - } - return val -} - -// total power from the bond -func (k Keeper) TotalPower(ctx sdk.Context) sdk.Rat { - pool := k.GetPool(ctx) - return pool.BondedShares -} - -//__________________________________________________________________________ - -// Implements DelegationSet - -var _ sdk.ValidatorSet = Keeper{} - -// get the delegation for a particular set of delegator and validator addresses -func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.Address, addrVal sdk.Address) sdk.Delegation { - bond, ok := k.GetDelegation(ctx, addrDel, addrVal) - if !ok { - return nil - } - return bond -} - -// iterate through the active validator set and perform the provided function -func (k Keeper) IterateDelegators(ctx sdk.Context, delAddr sdk.Address, fn func(index int64, delegation sdk.Delegation) (stop bool)) { - store := ctx.KVStore(k.storeKey) - key := GetDelegationsKey(delAddr, k.cdc) - iterator := sdk.KVStorePrefixIterator(store, key) - i := int64(0) - for ; iterator.Valid(); iterator.Next() { - bz := iterator.Value() - var delegation Delegation - k.cdc.MustUnmarshalBinary(bz, &delegation) - stop := fn(i, delegation) // XXX is this safe will the fields be able to get written to? - if stop { - break - } - i++ - } - iterator.Close() -} - -// slash a validator -func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, fraction sdk.Rat) { - // TODO height ignored for now, see https://github.com/cosmos/cosmos-sdk/pull/1011#issuecomment-390253957 - logger := ctx.Logger().With("module", "x/stake") - val, found := k.GetValidatorByPubKey(ctx, pubkey) - if !found { - panic(fmt.Errorf("Attempted to slash a nonexistent validator with address %s", pubkey.Address())) - } - sharesToRemove := val.PoolShares.Amount.Mul(fraction) - pool := k.GetPool(ctx) - val, pool, burned := val.removePoolShares(pool, sharesToRemove) - k.setPool(ctx, pool) // update the pool - k.updateValidator(ctx, val) // update the validator, possibly kicking it out - logger.Info(fmt.Sprintf("Validator %s slashed by fraction %v, removed %v shares and burned %d tokens", pubkey.Address(), fraction, sharesToRemove, burned)) - return -} - -// revoke a validator -func (k Keeper) Revoke(ctx sdk.Context, pubkey crypto.PubKey) { - logger := ctx.Logger().With("module", "x/stake") - val, found := k.GetValidatorByPubKey(ctx, pubkey) - if !found { - panic(fmt.Errorf("Validator with pubkey %s not found, cannot revoke", pubkey)) - } - val.Revoked = true - k.updateValidator(ctx, val) // update the validator, now revoked - logger.Info(fmt.Sprintf("Validator %s revoked", pubkey.Address())) - return -} - -// unrevoke a validator -func (k Keeper) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) { - logger := ctx.Logger().With("module", "x/stake") - val, found := k.GetValidatorByPubKey(ctx, pubkey) - if !found { - panic(fmt.Errorf("Validator with pubkey %s not found, cannot unrevoke", pubkey)) - } - val.Revoked = false - k.updateValidator(ctx, val) // update the validator, now unrevoked - logger.Info(fmt.Sprintf("Validator %s unrevoked", pubkey.Address())) - return -} diff --git a/x/stake/store.md b/x/stake/keeper/_store.md similarity index 100% rename from x/stake/store.md rename to x/stake/keeper/_store.md diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go new file mode 100644 index 000000000..e2f96f3d9 --- /dev/null +++ b/x/stake/keeper/delegation.go @@ -0,0 +1,399 @@ +package keeper + +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// load a delegation +func (k Keeper) GetDelegation(ctx sdk.Context, + delegatorAddr, validatorAddr sdk.AccAddress) (delegation types.Delegation, found bool) { + + store := ctx.KVStore(k.storeKey) + key := GetDelegationKey(delegatorAddr, validatorAddr) + value := store.Get(key) + if value == nil { + return delegation, false + } + + delegation = types.MustUnmarshalDelegation(k.cdc, key, value) + return delegation, true +} + +// load all delegations used during genesis dump +func (k Keeper) GetAllDelegations(ctx sdk.Context) (delegations []types.Delegation) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, DelegationKey) + + i := 0 + for ; ; i++ { + if !iterator.Valid() { + break + } + delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) + delegations = append(delegations, delegation) + iterator.Next() + } + iterator.Close() + return delegations +} + +// load all delegations for a delegator +func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.AccAddress, + maxRetrieve int16) (delegations []types.Delegation) { + + store := ctx.KVStore(k.storeKey) + delegatorPrefixKey := GetDelegationsKey(delegator) + iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest + + delegations = make([]types.Delegation, maxRetrieve) + i := 0 + for ; ; i++ { + if !iterator.Valid() || i > int(maxRetrieve-1) { + break + } + delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) + delegations[i] = delegation + iterator.Next() + } + iterator.Close() + return delegations[:i] // trim +} + +// set the delegation +func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) { + store := ctx.KVStore(k.storeKey) + b := types.MustMarshalDelegation(k.cdc, delegation) + store.Set(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr), b) +} + +// remove the delegation +func (k Keeper) RemoveDelegation(ctx sdk.Context, delegation types.Delegation) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr)) +} + +//_____________________________________________________________________________________ + +// load a unbonding delegation +func (k Keeper) GetUnbondingDelegation(ctx sdk.Context, + DelegatorAddr, ValidatorAddr sdk.AccAddress) (ubd types.UnbondingDelegation, found bool) { + + store := ctx.KVStore(k.storeKey) + key := GetUBDKey(DelegatorAddr, ValidatorAddr) + value := store.Get(key) + if value == nil { + return ubd, false + } + + ubd = types.MustUnmarshalUBD(k.cdc, key, value) + return ubd, true +} + +// load all unbonding delegations from a particular validator +func (k Keeper) GetUnbondingDelegationsFromValidator(ctx sdk.Context, valAddr sdk.AccAddress) (ubds []types.UnbondingDelegation) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, GetUBDsByValIndexKey(valAddr)) + for { + if !iterator.Valid() { + break + } + key := GetUBDKeyFromValIndexKey(iterator.Key()) + value := store.Get(key) + ubd := types.MustUnmarshalUBD(k.cdc, key, value) + ubds = append(ubds, ubd) + iterator.Next() + } + iterator.Close() + return ubds +} + +// set the unbonding delegation and associated index +func (k Keeper) SetUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) { + store := ctx.KVStore(k.storeKey) + bz := types.MustMarshalUBD(k.cdc, ubd) + key := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr) + store.Set(key, bz) + store.Set(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr), []byte{}) // index, store empty bytes +} + +// remove the unbonding delegation object and associated index +func (k Keeper) RemoveUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) { + store := ctx.KVStore(k.storeKey) + key := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr) + store.Delete(key) + store.Delete(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr)) +} + +//_____________________________________________________________________________________ + +// load a redelegation +func (k Keeper) GetRedelegation(ctx sdk.Context, + DelegatorAddr, ValidatorSrcAddr, ValidatorDstAddr sdk.AccAddress) (red types.Redelegation, found bool) { + + store := ctx.KVStore(k.storeKey) + key := GetREDKey(DelegatorAddr, ValidatorSrcAddr, ValidatorDstAddr) + value := store.Get(key) + if value == nil { + return red, false + } + + red = types.MustUnmarshalRED(k.cdc, key, value) + return red, true +} + +// load all redelegations from a particular validator +func (k Keeper) GetRedelegationsFromValidator(ctx sdk.Context, valAddr sdk.AccAddress) (reds []types.Redelegation) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, GetREDsFromValSrcIndexKey(valAddr)) + for { + if !iterator.Valid() { + break + } + key := GetREDKeyFromValSrcIndexKey(iterator.Key()) + value := store.Get(key) + red := types.MustUnmarshalRED(k.cdc, key, value) + reds = append(reds, red) + iterator.Next() + } + iterator.Close() + return reds +} + +// has a redelegation +func (k Keeper) HasReceivingRedelegation(ctx sdk.Context, + DelegatorAddr, ValidatorDstAddr sdk.AccAddress) bool { + + store := ctx.KVStore(k.storeKey) + prefix := GetREDsByDelToValDstIndexKey(DelegatorAddr, ValidatorDstAddr) + iterator := sdk.KVStorePrefixIterator(store, prefix) //smallest to largest + + found := false + if iterator.Valid() { + //record found + found = true + } + iterator.Close() + return found +} + +// set a redelegation and associated index +func (k Keeper) SetRedelegation(ctx sdk.Context, red types.Redelegation) { + store := ctx.KVStore(k.storeKey) + bz := types.MustMarshalRED(k.cdc, red) + key := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr) + store.Set(key, bz) + store.Set(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr), []byte{}) + store.Set(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr), []byte{}) +} + +// remove a redelegation object and associated index +func (k Keeper) RemoveRedelegation(ctx sdk.Context, red types.Redelegation) { + store := ctx.KVStore(k.storeKey) + redKey := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr) + store.Delete(redKey) + store.Delete(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr)) + store.Delete(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr)) +} + +//_____________________________________________________________________________________ + +// Perform a delegation, set/update everything necessary within the store +func (k Keeper) Delegate(ctx sdk.Context, delegatorAddr sdk.AccAddress, bondAmt sdk.Coin, + validator types.Validator) (newShares sdk.Rat, err sdk.Error) { + + // Get or create the delegator delegation + delegation, found := k.GetDelegation(ctx, delegatorAddr, validator.Owner) + if !found { + delegation = types.Delegation{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validator.Owner, + Shares: sdk.ZeroRat(), + } + } + + // Account new shares, save + pool := k.GetPool(ctx) + _, _, err = k.coinKeeper.SubtractCoins(ctx, delegation.DelegatorAddr, sdk.Coins{bondAmt}) + if err != nil { + return + } + validator, pool, newShares = validator.AddTokensFromDel(pool, bondAmt.Amount.Int64()) + delegation.Shares = delegation.Shares.Add(newShares) + + // Update delegation height + delegation.Height = ctx.BlockHeight() + + k.SetPool(ctx, pool) + k.SetDelegation(ctx, delegation) + k.UpdateValidator(ctx, validator) + + return +} + +// unbond the the delegation return +func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddress, + shares sdk.Rat) (amount int64, err sdk.Error) { + + // check if delegation has any shares in it unbond + delegation, found := k.GetDelegation(ctx, delegatorAddr, validatorAddr) + if !found { + err = types.ErrNoDelegatorForAddress(k.Codespace()) + return + } + + // retrieve the amount to remove + if delegation.Shares.LT(shares) { + err = types.ErrNotEnoughDelegationShares(k.Codespace(), delegation.Shares.String()) + return + } + + // get validator + validator, found := k.GetValidator(ctx, validatorAddr) + if !found { + err = types.ErrNoValidatorFound(k.Codespace()) + return + } + + // subtract shares from delegator + delegation.Shares = delegation.Shares.Sub(shares) + + // remove the delegation + if delegation.Shares.IsZero() { + + // if the delegation is the owner of the validator then + // trigger a revoke validator + if bytes.Equal(delegation.DelegatorAddr, validator.Owner) && validator.Revoked == false { + validator.Revoked = true + } + k.RemoveDelegation(ctx, delegation) + } else { + // Update height + delegation.Height = ctx.BlockHeight() + k.SetDelegation(ctx, delegation) + } + + // remove the coins from the validator + pool := k.GetPool(ctx) + validator, pool, amount = validator.RemoveDelShares(pool, shares) + + k.SetPool(ctx, pool) + + // update then remove validator if necessary + validator = k.UpdateValidator(ctx, validator) + if validator.DelegatorShares.IsZero() { + k.RemoveValidator(ctx, validator.Owner) + } + + return +} + +//______________________________________________________________________________________________________ + +// complete unbonding an unbonding record +func (k Keeper) BeginUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddress, sharesAmount sdk.Rat) sdk.Error { + + returnAmount, err := k.unbond(ctx, delegatorAddr, validatorAddr, sharesAmount) + if err != nil { + return err + } + + // create the unbonding delegation + params := k.GetParams(ctx) + minTime := ctx.BlockHeader().Time + params.UnbondingTime + balance := sdk.Coin{params.BondDenom, sdk.NewInt(returnAmount)} + + ubd := types.UnbondingDelegation{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + MinTime: minTime, + Balance: balance, + InitialBalance: balance, + } + k.SetUnbondingDelegation(ctx, ubd) + return nil +} + +// complete unbonding an unbonding record +func (k Keeper) CompleteUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddress) sdk.Error { + + ubd, found := k.GetUnbondingDelegation(ctx, delegatorAddr, validatorAddr) + if !found { + return types.ErrNoUnbondingDelegation(k.Codespace()) + } + + // ensure that enough time has passed + ctxTime := ctx.BlockHeader().Time + if ubd.MinTime > ctxTime { + return types.ErrNotMature(k.Codespace(), "unbonding", "unit-time", ubd.MinTime, ctxTime) + } + + _, _, err := k.coinKeeper.AddCoins(ctx, ubd.DelegatorAddr, sdk.Coins{ubd.Balance}) + if err != nil { + return err + } + k.RemoveUnbondingDelegation(ctx, ubd) + return nil +} + +// complete unbonding an unbonding record +func (k Keeper) BeginRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAddr, + validatorDstAddr sdk.AccAddress, sharesAmount sdk.Rat) sdk.Error { + + // check if this is a transitive redelegation + if k.HasReceivingRedelegation(ctx, delegatorAddr, validatorSrcAddr) { + return types.ErrTransitiveRedelegation(k.Codespace()) + } + + returnAmount, err := k.unbond(ctx, delegatorAddr, validatorSrcAddr, sharesAmount) + if err != nil { + return err + } + + params := k.GetParams(ctx) + returnCoin := sdk.Coin{params.BondDenom, sdk.NewInt(returnAmount)} + dstValidator, found := k.GetValidator(ctx, validatorDstAddr) + if !found { + return types.ErrBadRedelegationDst(k.Codespace()) + } + sharesCreated, err := k.Delegate(ctx, delegatorAddr, returnCoin, dstValidator) + if err != nil { + return err + } + + // create the unbonding delegation + minTime := ctx.BlockHeader().Time + params.UnbondingTime + + red := types.Redelegation{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + MinTime: minTime, + SharesDst: sharesCreated, + SharesSrc: sharesAmount, + Balance: returnCoin, + InitialBalance: returnCoin, + } + k.SetRedelegation(ctx, red) + return nil +} + +// complete unbonding an ongoing redelegation +func (k Keeper) CompleteRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAddr, validatorDstAddr sdk.AccAddress) sdk.Error { + + red, found := k.GetRedelegation(ctx, delegatorAddr, validatorSrcAddr, validatorDstAddr) + if !found { + return types.ErrNoRedelegation(k.Codespace()) + } + + // ensure that enough time has passed + ctxTime := ctx.BlockHeader().Time + if red.MinTime > ctxTime { + return types.ErrNotMature(k.Codespace(), "redelegation", "unit-time", red.MinTime, ctxTime) + } + + k.RemoveRedelegation(ctx, red) + return nil +} diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go new file mode 100644 index 000000000..c0a3ee8c5 --- /dev/null +++ b/x/stake/keeper/delegation_test.go @@ -0,0 +1,260 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" + + "github.com/stretchr/testify/require" +) + +// tests GetDelegation, GetDelegations, SetDelegation, RemoveDelegation, GetDelegations +func TestDelegation(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 10) + pool := keeper.GetPool(ctx) + + //construct the validators + amts := []int64{9, 8, 7} + var validators [3]types.Validator + for i, amt := range amts { + validators[i] = types.NewValidator(addrVals[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + } + + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + + // first add a validators[0] to delegate too + bond1to1 := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: sdk.NewRat(9), + } + + // check the empty keeper first + _, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + require.False(t, found) + + // set and retrieve a record + keeper.SetDelegation(ctx, bond1to1) + resBond, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + require.True(t, bond1to1.Equal(resBond)) + + // modify a records, save, and retrieve + bond1to1.Shares = sdk.NewRat(99) + keeper.SetDelegation(ctx, bond1to1) + resBond, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + require.True(t, bond1to1.Equal(resBond)) + + // add some more records + bond1to2 := types.Delegation{addrDels[0], addrVals[1], sdk.NewRat(9), 0} + bond1to3 := types.Delegation{addrDels[0], addrVals[2], sdk.NewRat(9), 1} + bond2to1 := types.Delegation{addrDels[1], addrVals[0], sdk.NewRat(9), 2} + bond2to2 := types.Delegation{addrDels[1], addrVals[1], sdk.NewRat(9), 3} + bond2to3 := types.Delegation{addrDels[1], addrVals[2], sdk.NewRat(9), 4} + keeper.SetDelegation(ctx, bond1to2) + keeper.SetDelegation(ctx, bond1to3) + keeper.SetDelegation(ctx, bond2to1) + keeper.SetDelegation(ctx, bond2to2) + keeper.SetDelegation(ctx, bond2to3) + + // test all bond retrieve capabilities + resBonds := keeper.GetDelegations(ctx, addrDels[0], 5) + require.Equal(t, 3, len(resBonds)) + require.True(t, bond1to1.Equal(resBonds[0])) + require.True(t, bond1to2.Equal(resBonds[1])) + require.True(t, bond1to3.Equal(resBonds[2])) + resBonds = keeper.GetDelegations(ctx, addrDels[0], 3) + require.Equal(t, 3, len(resBonds)) + resBonds = keeper.GetDelegations(ctx, addrDels[0], 2) + require.Equal(t, 2, len(resBonds)) + resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) + require.Equal(t, 3, len(resBonds)) + require.True(t, bond2to1.Equal(resBonds[0])) + require.True(t, bond2to2.Equal(resBonds[1])) + require.True(t, bond2to3.Equal(resBonds[2])) + allBonds := keeper.GetAllDelegations(ctx) + require.Equal(t, 6, len(allBonds)) + require.True(t, bond1to1.Equal(allBonds[0])) + require.True(t, bond1to2.Equal(allBonds[1])) + require.True(t, bond1to3.Equal(allBonds[2])) + require.True(t, bond2to1.Equal(allBonds[3])) + require.True(t, bond2to2.Equal(allBonds[4])) + require.True(t, bond2to3.Equal(allBonds[5])) + + // delete a record + keeper.RemoveDelegation(ctx, bond2to3) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[2]) + require.False(t, found) + resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) + require.Equal(t, 2, len(resBonds)) + require.True(t, bond2to1.Equal(resBonds[0])) + require.True(t, bond2to2.Equal(resBonds[1])) + + // delete all the records from delegator 2 + keeper.RemoveDelegation(ctx, bond2to1) + keeper.RemoveDelegation(ctx, bond2to2) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[0]) + require.False(t, found) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[1]) + require.False(t, found) + resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) + require.Equal(t, 0, len(resBonds)) +} + +// tests Get/Set/Remove UnbondingDelegation +func TestUnbondingDelegation(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + + ubd := types.UnbondingDelegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + CreationHeight: 0, + MinTime: 0, + Balance: sdk.NewCoin("steak", 5), + } + + // set and retrieve a record + keeper.SetUnbondingDelegation(ctx, ubd) + resBond, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + require.True(t, ubd.Equal(resBond)) + + // modify a records, save, and retrieve + ubd.Balance = sdk.NewCoin("steak", 21) + keeper.SetUnbondingDelegation(ctx, ubd) + resBond, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + require.True(t, ubd.Equal(resBond)) + + // delete a record + keeper.RemoveUnbondingDelegation(ctx, ubd) + _, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.False(t, found) +} + +func TestUnbondDelegation(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + pool.LooseTokens = 10 + + //create a validator and a delegator to that validator + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, issuedShares := validator.AddTokensFromDel(pool, 10) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + + pool = keeper.GetPool(ctx) + require.Equal(t, int64(10), pool.BondedTokens) + require.Equal(t, int64(10), validator.PoolShares.Bonded().RoundInt64()) + + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + var err error + var amount int64 + amount, err = keeper.unbond(ctx, addrDels[0], addrVals[0], sdk.NewRat(6)) + require.NoError(t, err) + require.Equal(t, int64(6), amount) // shares to be added to an unbonding delegation / redelegation + + delegation, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + validator, found = keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + pool = keeper.GetPool(ctx) + + require.Equal(t, int64(4), delegation.Shares.RoundInt64()) + require.Equal(t, int64(4), validator.PoolShares.Bonded().RoundInt64()) + require.Equal(t, int64(6), pool.LooseTokens, "%v", pool) + require.Equal(t, int64(4), pool.BondedTokens) +} + +// Make sure that that the retrieving the delegations doesn't affect the state +func TestGetRedelegationsFromValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + + rd := types.Redelegation{ + DelegatorAddr: addrDels[0], + ValidatorSrcAddr: addrVals[0], + ValidatorDstAddr: addrVals[1], + CreationHeight: 0, + MinTime: 0, + SharesSrc: sdk.NewRat(5), + SharesDst: sdk.NewRat(5), + } + + // set and retrieve a record + keeper.SetRedelegation(ctx, rd) + resBond, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + + // get the redelegations one time + redelegations := keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resBond)) + + // get the redelegations a second time, should be exactly the same + redelegations = keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resBond)) +} + +// tests Get/Set/Remove/Has UnbondingDelegation +func TestRedelegation(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + + rd := types.Redelegation{ + DelegatorAddr: addrDels[0], + ValidatorSrcAddr: addrVals[0], + ValidatorDstAddr: addrVals[1], + CreationHeight: 0, + MinTime: 0, + SharesSrc: sdk.NewRat(5), + SharesDst: sdk.NewRat(5), + } + + // test shouldn't have and redelegations + has := keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1]) + require.False(t, has) + + // set and retrieve a record + keeper.SetRedelegation(ctx, rd) + resBond, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + + redelegations := keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resBond)) + + // check if has the redelegation + has = keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1]) + require.True(t, has) + + // modify a records, save, and retrieve + rd.SharesSrc = sdk.NewRat(21) + rd.SharesDst = sdk.NewRat(21) + keeper.SetRedelegation(ctx, rd) + + resBond, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + require.True(t, rd.Equal(resBond)) + + redelegations = keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resBond)) + + // delete a record + keeper.RemoveRedelegation(ctx, rd) + _, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.False(t, found) +} diff --git a/x/stake/inflation.go b/x/stake/keeper/inflation.go similarity index 66% rename from x/stake/inflation.go rename to x/stake/keeper/inflation.go index fe3f59435..26b515879 100644 --- a/x/stake/inflation.go +++ b/x/stake/keeper/inflation.go @@ -1,35 +1,32 @@ -package stake +package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) const ( hrsPerYr = 8766 // as defined by a julian year of 365.25 days - precision = 100000000000 // increased to this precision for accuracy with tests on tick_test.go + precision = 100000000000 // increased to this precision for accuracy ) -var hrsPerYrRat = sdk.NewRat(hrsPerYr) // as defined by a julian year of 365.25 days +var hrsPerYrRat = sdk.NewRat(hrsPerYr) // process provisions for an hour period -func (k Keeper) processProvisions(ctx sdk.Context) Pool { +func (k Keeper) ProcessProvisions(ctx sdk.Context) types.Pool { pool := k.GetPool(ctx) - pool.Inflation = k.nextInflation(ctx) + pool.Inflation = k.NextInflation(ctx) - // Because the validators hold a relative bonded share (`GlobalStakeShare`), when - // more bonded tokens are added proportionally to all validators the only term - // which needs to be updated is the `BondedPool`. So for each previsions cycle: - - provisions := pool.Inflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat).Evaluate() + provisions := pool.Inflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat).RoundInt64() // TODO add to the fees provisions - pool.LooseUnbondedTokens += provisions + pool.LooseTokens += provisions return pool } // get the next inflation rate for the hour -func (k Keeper) nextInflation(ctx sdk.Context) (inflation sdk.Rat) { +func (k Keeper) NextInflation(ctx sdk.Context) (inflation sdk.Rat) { params := k.GetParams(ctx) pool := k.GetPool(ctx) @@ -40,7 +37,7 @@ func (k Keeper) nextInflation(ctx sdk.Context) (inflation sdk.Rat) { // 7% and 20%. // (1 - bondedRatio/GoalBonded) * InflationRateChange - inflationRateChangePerYear := sdk.OneRat().Sub(pool.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) + inflationRateChangePerYear := sdk.OneRat().Sub(pool.BondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYrRat) // increase the new annual inflation for this next cycle diff --git a/x/stake/inflation_test.go b/x/stake/keeper/inflation_test.go similarity index 72% rename from x/stake/inflation_test.go rename to x/stake/keeper/inflation_test.go index 0d5183f4c..28efc0c59 100644 --- a/x/stake/inflation_test.go +++ b/x/stake/keeper/inflation_test.go @@ -1,27 +1,28 @@ -package stake +package keeper import ( "math/rand" "strconv" "testing" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) //changing the int in NewSource will allow you to test different, deterministic, sets of operations var r = rand.New(rand.NewSource(6595)) func TestGetInflation(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) + ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) params := keeper.GetParams(ctx) hrsPerYrRat := sdk.NewRat(hrsPerYr) // Governing Mechanism: - // bondedRatio = BondedTokens / TotalSupply - // inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange + // BondedRatio = BondedTokens / TotalSupply + // inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange tests := []struct { name string @@ -52,21 +53,21 @@ func TestGetInflation(t *testing.T) { {"test 8", 67, 33, sdk.NewRat(15, 100), sdk.ZeroRat()}, } for _, tc := range tests { - pool.BondedTokens, pool.LooseUnbondedTokens = tc.setBondedTokens, tc.setLooseTokens + pool.BondedTokens, pool.LooseTokens = tc.setBondedTokens, tc.setLooseTokens pool.Inflation = tc.setInflation - keeper.setPool(ctx, pool) + keeper.SetPool(ctx, pool) - inflation := keeper.nextInflation(ctx) + inflation := keeper.NextInflation(ctx) diffInflation := inflation.Sub(tc.setInflation) - assert.True(t, diffInflation.Equal(tc.expectedChange), + require.True(t, diffInflation.Equal(tc.expectedChange), "Name: %v\nDiff: %v\nExpected: %v\n", tc.name, diffInflation, tc.expectedChange) } } // Test that provisions are correctly added to the pool and validators each hour for 1 year func TestProcessProvisions(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) + ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) var ( @@ -77,6 +78,7 @@ func TestProcessProvisions(t *testing.T) { validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000} bondedValidators uint16 = 2 ) + pool.LooseTokens = initialTotalTokens // create some validators some bonded, some unbonded _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) @@ -97,7 +99,7 @@ func TestProcessProvisions(t *testing.T) { // Tests that the hourly rate of change of inflation will be positive, negative, or zero, depending on bonded ratio and inflation rate // Cycles through the whole gambit of inflation possibilities, starting at 7% inflation, up to 20%, back down to 7% (it takes ~11.4 years) func TestHourlyInflationRateOfChange(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) + ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) var ( @@ -108,6 +110,7 @@ func TestHourlyInflationRateOfChange(t *testing.T) { validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000} bondedValidators uint16 = 1 ) + pool.LooseTokens = initialTotalTokens // create some validators some bonded, some unbonded _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) @@ -130,7 +133,7 @@ func TestHourlyInflationRateOfChange(t *testing.T) { //Test that a large unbonding will significantly lower the bonded ratio func TestLargeUnbond(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) + ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) var ( @@ -144,32 +147,33 @@ func TestLargeUnbond(t *testing.T) { validatorTokens = []int64{300000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000} bondedValidators uint16 = 7 ) + pool.LooseTokens = initialTotalTokens _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens) pool = keeper.GetPool(ctx) - validator, found := keeper.GetValidator(ctx, addrs[0]) - assert.True(t, found) + validator, found := keeper.GetValidator(ctx, Addrs[0]) + require.True(t, found) // initialBondedRatio that we can use to compare to the new values after the unbond - initialBondedRatio := pool.bondedRatio() + initialBondedRatio := pool.BondedRatio() // validator[0] will be unbonded, bringing us from 75% bonded ratio to ~50% (unbonding 300,000,000) - pool, validator, _, _ = OpBondOrUnbond(r, pool, validator) - keeper.setPool(ctx, pool) + pool, validator, _, _ = types.OpBondOrUnbond(r, pool, validator) + keeper.SetPool(ctx, pool) // process provisions after the bonding, to compare the difference in expProvisions and expInflation _, expProvisionsAfter, pool := updateProvisions(t, keeper, pool, ctx, 0) bondedShares = bondedShares.Sub(bondSharesVal0) - val0UnbondedTokens = pool.unbondedShareExRate().Mul(validator.PoolShares.Unbonded()).Evaluate() - unbondedShares = unbondedShares.Add(sdk.NewRat(val0UnbondedTokens, 1).Mul(pool.unbondedShareExRate())) + val0UnbondedTokens = pool.UnbondedShareExRate().Mul(validator.PoolShares.Unbonded()).RoundInt64() + unbondedShares = unbondedShares.Add(sdk.NewRat(val0UnbondedTokens, 1).Mul(pool.UnbondedShareExRate())) // unbonded shares should increase - assert.True(t, unbondedShares.GT(sdk.NewRat(300000000, 1))) + require.True(t, unbondedShares.GT(sdk.NewRat(300000000, 1))) // Ensure that new bonded ratio is less than old bonded ratio , because before they were increasing (i.e. 50% < 75) - assert.True(t, (pool.bondedRatio().LT(initialBondedRatio))) + require.True(t, (pool.BondedRatio().LT(initialBondedRatio))) // Final check that the pool equals initial values + provisions and adjustments we recorded pool = keeper.GetPool(ctx) @@ -178,7 +182,7 @@ func TestLargeUnbond(t *testing.T) { //Test that a large bonding will significantly increase the bonded ratio func TestLargeBond(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) + ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) var ( @@ -190,33 +194,34 @@ func TestLargeBond(t *testing.T) { validatorTokens = []int64{400000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 400000000} bondedValidators uint16 = 1 ) + pool.LooseTokens = initialTotalTokens _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens) pool = keeper.GetPool(ctx) - validator, found := keeper.GetValidator(ctx, addrs[9]) - assert.True(t, found) + validator, found := keeper.GetValidator(ctx, Addrs[9]) + require.True(t, found) // initialBondedRatio that we can use to compare to the new values after the unbond - initialBondedRatio := pool.bondedRatio() + initialBondedRatio := pool.BondedRatio() - params := DefaultParams() + params := types.DefaultParams() params.MaxValidators = bondedValidators + 1 //must do this to allow for an extra validator to bond - keeper.setParams(ctx, params) + keeper.SetParams(ctx, params) // validator[9] will be bonded, bringing us from 25% to ~50% (bonding 400,000,000 tokens) - pool, validator, _, _ = OpBondOrUnbond(r, pool, validator) - keeper.setPool(ctx, pool) + pool, _, _, _ = types.OpBondOrUnbond(r, pool, validator) + keeper.SetPool(ctx, pool) // process provisions after the bonding, to compare the difference in expProvisions and expInflation _, expProvisionsAfter, pool := updateProvisions(t, keeper, pool, ctx, 0) unbondedShares = unbondedShares.Sub(unbondedSharesVal9) // unbonded shares should decrease - assert.True(t, unbondedShares.LT(sdk.NewRat(1200000000, 1))) + require.True(t, unbondedShares.LT(sdk.NewRat(1200000000, 1))) // Ensure that new bonded ratio is greater than old bonded ratio (i.e. 50% > 25%) - assert.True(t, (pool.bondedRatio().GT(initialBondedRatio))) + require.True(t, (pool.BondedRatio().GT(initialBondedRatio))) // Final check that the pool equals initial values + provisions and adjustments we recorded pool = keeper.GetPool(ctx) @@ -225,19 +230,19 @@ func TestLargeBond(t *testing.T) { // Tests that inflation increases or decreases as expected when we do a random operation on 20 different validators func TestInflationWithRandomOperations(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - params := DefaultParams() - keeper.setParams(ctx, params) + ctx, _, keeper := CreateTestInput(t, false, 0) + params := types.DefaultParams() + keeper.SetParams(ctx, params) numValidators := 20 // start off by randomly setting up 20 validators - pool, validators := randomSetup(r, numValidators) + pool, validators := types.RandomSetup(r, numValidators) require.Equal(t, numValidators, len(validators)) for i := 0; i < len(validators); i++ { - keeper.setValidator(ctx, validators[i]) + keeper.SetValidator(ctx, validators[i]) } - keeper.setPool(ctx, pool) + keeper.SetPool(ctx, pool) // Used to rotate validators so each random operation is applied to a different validator validatorCounter := 0 @@ -246,30 +251,30 @@ func TestInflationWithRandomOperations(t *testing.T) { for i := 0; i < numValidators; i++ { pool := keeper.GetPool(ctx) - // Get inflation before randomOperation, for comparison later + // Get inflation before RandomOperation, for comparison later previousInflation := pool.Inflation // Perform the random operation, and record how validators are modified - poolMod, validatorMod, tokens, msg := randomOperation(r)(r, pool, validators[validatorCounter]) - validatorsMod := make([]Validator, len(validators)) + poolMod, validatorMod, tokens, msg := types.RandomOperation(r)(r, pool, validators[validatorCounter]) + validatorsMod := make([]types.Validator, len(validators)) copy(validatorsMod[:], validators[:]) require.Equal(t, numValidators, len(validators), "i %v", validatorCounter) require.Equal(t, numValidators, len(validatorsMod), "i %v", validatorCounter) validatorsMod[validatorCounter] = validatorMod - assertInvariants(t, msg, + types.AssertInvariants(t, msg, pool, validators, poolMod, validatorsMod, tokens) // set pool and validators after the random operation pool = poolMod - keeper.setPool(ctx, pool) + keeper.SetPool(ctx, pool) validators = validatorsMod // Must set inflation here manually, as opposed to most other tests in this suite, where we call keeper.processProvisions(), which updates pool.Inflation - updatedInflation := keeper.nextInflation(ctx) + updatedInflation := keeper.NextInflation(ctx) pool.Inflation = updatedInflation - keeper.setPool(ctx, pool) + keeper.SetPool(ctx, pool) // Ensure inflation changes as expected when random operations are applied. checkInflation(t, pool, previousInflation, updatedInflation, msg) @@ -281,19 +286,19 @@ func TestInflationWithRandomOperations(t *testing.T) { ////////////////////////////////HELPER FUNCTIONS BELOW///////////////////////////////////// // Final check on the global pool values for what the total tokens accumulated from each hour of provisions -func checkFinalPoolValues(t *testing.T, pool Pool, initialTotalTokens, cumulativeExpProvs int64) { +func checkFinalPoolValues(t *testing.T, pool types.Pool, initialTotalTokens, cumulativeExpProvs int64) { calculatedTotalTokens := initialTotalTokens + cumulativeExpProvs - assert.Equal(t, calculatedTotalTokens, pool.TokenSupply()) + require.Equal(t, calculatedTotalTokens, pool.TokenSupply()) } // Processes provisions are added to the pool correctly every hour // Returns expected Provisions, expected Inflation, and pool, to help with cumulative calculations back in main Tests -func updateProvisions(t *testing.T, keeper Keeper, pool Pool, ctx sdk.Context, hr int) (sdk.Rat, int64, Pool) { - expInflation := keeper.nextInflation(ctx) - expProvisions := (expInflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat)).Evaluate() +func updateProvisions(t *testing.T, keeper Keeper, pool types.Pool, ctx sdk.Context, hr int) (sdk.Rat, int64, types.Pool) { + expInflation := keeper.NextInflation(ctx) + expProvisions := (expInflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat)).RoundInt64() startTotalSupply := pool.TokenSupply() - pool = keeper.processProvisions(ctx) - keeper.setPool(ctx, pool) + pool = keeper.ProcessProvisions(ctx) + keeper.SetPool(ctx, pool) //check provisions were added to pool require.Equal(t, startTotalSupply+expProvisions, pool.TokenSupply()) @@ -304,18 +309,20 @@ func updateProvisions(t *testing.T, keeper Keeper, pool Pool, ctx sdk.Context, h // Deterministic setup of validators and pool // Allows you to decide how many validators to setup // Allows you to pick which validators are bonded by adjusting the MaxValidators of params -func setupTestValidators(pool Pool, keeper Keeper, ctx sdk.Context, validatorTokens []int64, maxValidators uint16) ([]Validator, Keeper, Pool) { - params := DefaultParams() +func setupTestValidators(pool types.Pool, keeper Keeper, ctx sdk.Context, validatorTokens []int64, + maxValidators uint16) ([]types.Validator, Keeper, types.Pool) { + + params := types.DefaultParams() params.MaxValidators = maxValidators - keeper.setParams(ctx, params) + keeper.SetParams(ctx, params) numValidators := len(validatorTokens) - validators := make([]Validator, numValidators) + validators := make([]types.Validator, numValidators) for i := 0; i < numValidators; i++ { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i], pool, _ = validators[i].addTokensFromDel(pool, validatorTokens[i]) - keeper.setPool(ctx, pool) - validators[i] = keeper.updateValidator(ctx, validators[i]) //will kick out lower power validators. Keep this in mind when setting up the test validators order + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, validatorTokens[i]) + keeper.SetPool(ctx, pool) + validators[i] = keeper.UpdateValidator(ctx, validators[i]) //will kick out lower power validators. Keep this in mind when setting up the test validators order pool = keeper.GetPool(ctx) } @@ -323,48 +330,49 @@ func setupTestValidators(pool Pool, keeper Keeper, ctx sdk.Context, validatorTok } // Checks that the deterministic validator setup you wanted matches the values in the pool -func checkValidatorSetup(t *testing.T, pool Pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens int64) { - assert.Equal(t, initialTotalTokens, pool.TokenSupply()) - assert.Equal(t, initialBondedTokens, pool.BondedTokens) - assert.Equal(t, initialUnbondedTokens, pool.UnbondedTokens) +func checkValidatorSetup(t *testing.T, pool types.Pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens int64) { + require.Equal(t, initialTotalTokens, pool.TokenSupply(), "%v", pool) + require.Equal(t, initialBondedTokens, pool.BondedTokens, "%v", pool) + require.Equal(t, initialUnbondedTokens, pool.UnbondedTokens, "%v", pool) // test initial bonded ratio - assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(initialBondedTokens, initialTotalTokens)), "%v", pool.bondedRatio()) + require.True(t, pool.BondedRatio().Equal(sdk.NewRat(initialBondedTokens, initialTotalTokens)), "%v", pool.BondedRatio()) // test the value of validator shares - assert.True(t, pool.bondedShareExRate().Equal(sdk.OneRat()), "%v", pool.bondedShareExRate()) + require.True(t, pool.BondedShareExRate().Equal(sdk.OneRat()), "%v", pool.BondedShareExRate()) } // Checks that The inflation will correctly increase or decrease after an update to the pool -func checkInflation(t *testing.T, pool Pool, previousInflation, updatedInflation sdk.Rat, msg string) { +// nolint: gocyclo +func checkInflation(t *testing.T, pool types.Pool, previousInflation, updatedInflation sdk.Rat, msg string) { inflationChange := updatedInflation.Sub(previousInflation) switch { //BELOW 67% - Rate of change positive and increasing, while we are between 7% <= and < 20% inflation - case pool.bondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)): - assert.Equal(t, true, inflationChange.GT(sdk.ZeroRat()), msg) + case pool.BondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)): + require.Equal(t, true, inflationChange.GT(sdk.ZeroRat()), msg) //BELOW 67% - Rate of change should be 0 while inflation continually stays at 20% until we reach 67% bonded ratio - case pool.bondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(20, 100)): + case pool.BondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(20, 100)): if previousInflation.Equal(sdk.NewRat(20, 100)) { - assert.Equal(t, true, inflationChange.IsZero(), msg) + require.Equal(t, true, inflationChange.IsZero(), msg) //This else statement covers the one off case where we first hit 20%, but we still needed a positive ROC to get to 67% bonded ratio (i.e. we went from 19.99999% to 20%) } else { - assert.Equal(t, true, inflationChange.GT(sdk.ZeroRat()), msg) + require.Equal(t, true, inflationChange.GT(sdk.ZeroRat()), msg) } //ABOVE 67% - Rate of change should be negative while the bond is above 67, and should stay negative until we reach inflation of 7% - case pool.bondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)) && updatedInflation.GT(sdk.NewRat(7, 100)): - assert.Equal(t, true, inflationChange.LT(sdk.ZeroRat()), msg) + case pool.BondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)) && updatedInflation.GT(sdk.NewRat(7, 100)): + require.Equal(t, true, inflationChange.LT(sdk.ZeroRat()), msg) //ABOVE 67% - Rate of change should be 0 while inflation continually stays at 7%. - case pool.bondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(7, 100)): + case pool.BondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(7, 100)): if previousInflation.Equal(sdk.NewRat(7, 100)) { - assert.Equal(t, true, inflationChange.IsZero(), msg) + require.Equal(t, true, inflationChange.IsZero(), msg) //This else statement covers the one off case where we first hit 7%, but we still needed a negative ROC to continue to get down to 67%. (i.e. we went from 7.00001% to 7%) } else { - assert.Equal(t, true, inflationChange.LT(sdk.ZeroRat()), msg) + require.Equal(t, true, inflationChange.LT(sdk.ZeroRat()), msg) } } } diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go new file mode 100644 index 000000000..187649c5f --- /dev/null +++ b/x/stake/keeper/keeper.go @@ -0,0 +1,122 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// keeper of the stake store +type Keeper struct { + storeKey sdk.StoreKey + cdc *wire.Codec + coinKeeper bank.Keeper + + // codespace + codespace sdk.CodespaceType +} + +func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { + keeper := Keeper{ + storeKey: key, + cdc: cdc, + coinKeeper: ck, + codespace: codespace, + } + return keeper +} + +//_________________________________________________________________________ + +// return the codespace +func (k Keeper) Codespace() sdk.CodespaceType { + return k.codespace +} + +//_________________________________________________________________________ +// some generic reads/writes that don't need their own files + +// load/save the global staking params +func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { + store := ctx.KVStore(k.storeKey) + + b := store.Get(ParamKey) + if b == nil { + panic("Stored params should not have been nil") + } + + k.cdc.MustUnmarshalBinary(b, ¶ms) + return +} + +// Need a distinct function because setParams depends on an existing previous +// record of params to exist (to check if maxValidators has changed) - and we +// panic on retrieval if it doesn't exist - hence if we use setParams for the very +// first params set it will panic. +func (k Keeper) SetNewParams(ctx sdk.Context, params types.Params) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinary(params) + store.Set(ParamKey, b) +} + +// set the params +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + store := ctx.KVStore(k.storeKey) + exParams := k.GetParams(ctx) + + // if max validator count changes, must recalculate validator set + if exParams.MaxValidators != params.MaxValidators { + k.UpdateBondedValidatorsFull(ctx) + } + b := k.cdc.MustMarshalBinary(params) + store.Set(ParamKey, b) +} + +//_______________________________________________________________________ + +// load/save the pool +func (k Keeper) GetPool(ctx sdk.Context) (pool types.Pool) { + store := ctx.KVStore(k.storeKey) + b := store.Get(PoolKey) + if b == nil { + panic("Stored pool should not have been nil") + } + k.cdc.MustUnmarshalBinary(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) + store.Set(PoolKey, b) +} + +//__________________________________________________________________________ + +// get the current in-block validator operation counter +func (k Keeper) InitIntraTxCounter(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + b := store.Get(IntraTxCounterKey) + if b == nil { + k.SetIntraTxCounter(ctx, 0) + } +} + +// get the current in-block validator operation counter +func (k Keeper) GetIntraTxCounter(ctx sdk.Context) int16 { + store := ctx.KVStore(k.storeKey) + b := store.Get(IntraTxCounterKey) + var counter int16 + k.cdc.MustUnmarshalBinary(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) + store.Set(IntraTxCounterKey, bz) +} diff --git a/x/stake/keeper/keeper_test.go b/x/stake/keeper/keeper_test.go new file mode 100644 index 000000000..15fecf3f2 --- /dev/null +++ b/x/stake/keeper/keeper_test.go @@ -0,0 +1,39 @@ +package keeper + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +func TestParams(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + expParams := types.DefaultParams() + + //check that the empty keeper loads the default + resParams := keeper.GetParams(ctx) + require.True(t, expParams.Equal(resParams)) + + //modify a params, save, and retrieve + expParams.MaxValidators = 777 + keeper.SetParams(ctx, expParams) + resParams = keeper.GetParams(ctx) + require.True(t, expParams.Equal(resParams)) +} + +func TestPool(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + expPool := types.InitialPool() + + //check that the empty keeper loads the default + resPool := keeper.GetPool(ctx) + require.True(t, expPool.Equal(resPool)) + + //modify a params, save, and retrieve + expPool.BondedTokens = 777 + keeper.SetPool(ctx, expPool) + resPool = keeper.GetPool(ctx) + require.True(t, expPool.Equal(resPool)) +} diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go new file mode 100644 index 000000000..ac7fe6e5f --- /dev/null +++ b/x/stake/keeper/key.go @@ -0,0 +1,232 @@ +package keeper + +import ( + "encoding/binary" + + "github.com/tendermint/tendermint/crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// TODO remove some of these prefixes once have working multistore + +//nolint +var ( + // Keys for store prefixes + ParamKey = []byte{0x00} // key for parameters relating to staking + PoolKey = []byte{0x01} // key for the staking pools + ValidatorsKey = []byte{0x02} // prefix for each key to a validator + ValidatorsByPubKeyIndexKey = []byte{0x03} // prefix for each key to a validator index, by pubkey + ValidatorsBondedIndexKey = []byte{0x04} // prefix for each key to a validator index, for bonded validators + ValidatorsByPowerIndexKey = []byte{0x05} // prefix for each key to a validator index, sorted by power + ValidatorCliffIndexKey = []byte{0x06} // key for the validator index of the cliff validator + ValidatorPowerCliffKey = []byte{0x07} // key for the power of the validator on the cliff + TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated + IntraTxCounterKey = []byte{0x09} // key for intra-block tx index + DelegationKey = []byte{0x0A} // key for a delegation + UnbondingDelegationKey = []byte{0x0B} // key for an unbonding-delegation + UnbondingDelegationByValIndexKey = []byte{0x0C} // prefix for each key for an unbonding-delegation, by validator owner + RedelegationKey = []byte{0x0D} // key for a redelegation + RedelegationByValSrcIndexKey = []byte{0x0E} // prefix for each key for an redelegation, by source validator owner + RedelegationByValDstIndexKey = []byte{0x0F} // prefix for each key for an redelegation, by destination validator owner +) + +const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch + +// get the key for the validator with address. +// VALUE: stake/types.Validator +func GetValidatorKey(ownerAddr sdk.AccAddress) []byte { + return append(ValidatorsKey, ownerAddr.Bytes()...) +} + +// get the key for the validator with pubkey. +// VALUE: validator owner address ([]byte) +func GetValidatorByPubKeyIndexKey(pubkey crypto.PubKey) []byte { + return append(ValidatorsByPubKeyIndexKey, pubkey.Bytes()...) +} + +// get the key for the current validator group +// VALUE: none (key rearrangement with GetValKeyFromValBondedIndexKey) +func GetValidatorsBondedIndexKey(ownerAddr sdk.AccAddress) []byte { + return append(ValidatorsBondedIndexKey, ownerAddr.Bytes()...) +} + +// Get the validator owner address from ValBondedIndexKey +func GetAddressFromValBondedIndexKey(IndexKey []byte) []byte { + return IndexKey[1:] // remove prefix bytes +} + +// get the validator by power index. power index is the key used in the power-store, +// and represents the relative power ranking of the validator. +// VALUE: validator owner address ([]byte) +func GetValidatorsByPowerIndexKey(validator types.Validator, pool types.Pool) []byte { + // NOTE the address doesn't need to be stored because counter bytes must always be different + return getValidatorPowerRank(validator, pool) +} + +// get the power ranking of a validator +// NOTE the larger values are of higher value +func getValidatorPowerRank(validator types.Validator, pool types.Pool) []byte { + + power := validator.EquivalentBondedShares(pool) + powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first) + + revokedBytes := make([]byte, 1) + if validator.Revoked { + revokedBytes[0] = byte(0x00) + } else { + revokedBytes[0] = byte(0x01) + } + + // heightBytes and counterBytes represent strings like powerBytes does + heightBytes := make([]byte, binary.MaxVarintLen64) + binary.BigEndian.PutUint64(heightBytes, ^uint64(validator.BondHeight)) // invert height (older validators first) + counterBytes := make([]byte, 2) + binary.BigEndian.PutUint16(counterBytes, ^uint16(validator.BondIntraTxCounter)) // invert counter (first txns have priority) + + return append(append(append(append( + ValidatorsByPowerIndexKey, + revokedBytes...), + powerBytes...), + heightBytes...), + counterBytes...) +} + +// get the key for the accumulated update validators. +// VALUE: abci.Validator +// note records using these keys should never persist between blocks +func GetTendermintUpdatesKey(ownerAddr sdk.AccAddress) []byte { + return append(TendermintUpdatesKey, ownerAddr.Bytes()...) +} + +//________________________________________________________________________________ + +// get the key for delegator bond with validator. +// VALUE: stake/types.Delegation +func GetDelegationKey(delegatorAddr, validatorAddr sdk.AccAddress) []byte { + return append(GetDelegationsKey(delegatorAddr), validatorAddr.Bytes()...) +} + +// get the prefix for a delegator for all validators +func GetDelegationsKey(delegatorAddr sdk.AccAddress) []byte { + return append(DelegationKey, delegatorAddr.Bytes()...) +} + +//________________________________________________________________________________ + +// get the key for an unbonding delegation by delegator and validator addr. +// VALUE: stake/types.UnbondingDelegation +func GetUBDKey(delegatorAddr, validatorAddr sdk.AccAddress) []byte { + return append( + GetUBDsKey(delegatorAddr.Bytes()), + validatorAddr.Bytes()...) +} + +// get the index-key for an unbonding delegation, stored by validator-index +// VALUE: none (key rearrangement used) +func GetUBDByValIndexKey(delegatorAddr, validatorAddr sdk.AccAddress) []byte { + return append(GetUBDsByValIndexKey(validatorAddr), delegatorAddr.Bytes()...) +} + +// rearrange the ValIndexKey to get the UBDKey +func GetUBDKeyFromValIndexKey(IndexKey []byte) []byte { + addrs := IndexKey[1:] // remove prefix bytes + if len(addrs) != 2*sdk.AddrLen { + panic("unexpected key length") + } + valAddr := addrs[:sdk.AddrLen] + delAddr := addrs[sdk.AddrLen:] + return GetUBDKey(delAddr, valAddr) +} + +//______________ + +// get the prefix for all unbonding delegations from a delegator +func GetUBDsKey(delegatorAddr sdk.AccAddress) []byte { + return append(UnbondingDelegationKey, delegatorAddr.Bytes()...) +} + +// get the prefix keyspace for the indexes of unbonding delegations for a validator +func GetUBDsByValIndexKey(validatorAddr sdk.AccAddress) []byte { + return append(UnbondingDelegationByValIndexKey, validatorAddr.Bytes()...) +} + +//________________________________________________________________________________ + +// get the key for a redelegation +// VALUE: stake/types.RedelegationKey +func GetREDKey(delegatorAddr, validatorSrcAddr, validatorDstAddr sdk.AccAddress) []byte { + return append(append( + GetREDsKey(delegatorAddr.Bytes()), + validatorSrcAddr.Bytes()...), + validatorDstAddr.Bytes()...) +} + +// get the index-key for a redelegation, stored by source-validator-index +// VALUE: none (key rearrangement used) +func GetREDByValSrcIndexKey(delegatorAddr, validatorSrcAddr, validatorDstAddr sdk.AccAddress) []byte { + return append(append( + GetREDsFromValSrcIndexKey(validatorSrcAddr), + delegatorAddr.Bytes()...), + validatorDstAddr.Bytes()...) +} + +// get the index-key for a redelegation, stored by destination-validator-index +// VALUE: none (key rearrangement used) +func GetREDByValDstIndexKey(delegatorAddr, validatorSrcAddr, validatorDstAddr sdk.AccAddress) []byte { + return append(append( + GetREDsToValDstIndexKey(validatorDstAddr), + delegatorAddr.Bytes()...), + validatorSrcAddr.Bytes()...) +} + +// rearrange the ValSrcIndexKey to get the REDKey +func GetREDKeyFromValSrcIndexKey(IndexKey []byte) []byte { + addrs := IndexKey[1:] // remove prefix bytes + if len(addrs) != 3*sdk.AddrLen { + panic("unexpected key length") + } + valSrcAddr := addrs[:sdk.AddrLen] + delAddr := addrs[sdk.AddrLen : 2*sdk.AddrLen] + valDstAddr := addrs[2*sdk.AddrLen:] + + return GetREDKey(delAddr, valSrcAddr, valDstAddr) +} + +// rearrange the ValDstIndexKey to get the REDKey +func GetREDKeyFromValDstIndexKey(IndexKey []byte) []byte { + addrs := IndexKey[1:] // remove prefix bytes + if len(addrs) != 3*sdk.AddrLen { + panic("unexpected key length") + } + valDstAddr := addrs[:sdk.AddrLen] + delAddr := addrs[sdk.AddrLen : 2*sdk.AddrLen] + valSrcAddr := addrs[2*sdk.AddrLen:] + return GetREDKey(delAddr, valSrcAddr, valDstAddr) +} + +//______________ + +// get the prefix keyspace for redelegations from a delegator +func GetREDsKey(delegatorAddr sdk.AccAddress) []byte { + return append(RedelegationKey, delegatorAddr.Bytes()...) +} + +// get the prefix keyspace for all redelegations redelegating away from a source validator +func GetREDsFromValSrcIndexKey(validatorSrcAddr sdk.AccAddress) []byte { + return append(RedelegationByValSrcIndexKey, validatorSrcAddr.Bytes()...) +} + +// get the prefix keyspace for all redelegations redelegating towards a destination validator +func GetREDsToValDstIndexKey(validatorDstAddr sdk.AccAddress) []byte { + return append(RedelegationByValDstIndexKey, validatorDstAddr.Bytes()...) +} + +// get the prefix keyspace for all redelegations redelegating towards a destination validator +// from a particular delegator +func GetREDsByDelToValDstIndexKey(delegatorAddr, validatorDstAddr sdk.AccAddress) []byte { + return append( + GetREDsToValDstIndexKey(validatorDstAddr), + delegatorAddr.Bytes()...) +} diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go new file mode 100644 index 000000000..989e95166 --- /dev/null +++ b/x/stake/keeper/sdk_types.go @@ -0,0 +1,101 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// Implements ValidatorSet +var _ sdk.ValidatorSet = Keeper{} + +// iterate through the active validator set and perform the provided function +func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + addr := iterator.Key()[1:] + validator := types.MustUnmarshalValidator(k.cdc, addr, iterator.Value()) + stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? + if stop { + break + } + i++ + } + iterator.Close() +} + +// 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)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + address := GetAddressFromValBondedIndexKey(iterator.Key()) + validator, found := k.GetValidator(ctx, address) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", address)) + } + + stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? + if stop { + break + } + i++ + } + iterator.Close() +} + +// get the sdk.validator for a particular address +func (k Keeper) Validator(ctx sdk.Context, address sdk.AccAddress) sdk.Validator { + val, found := k.GetValidator(ctx, address) + if !found { + return nil + } + return val +} + +// total power from the bond +func (k Keeper) TotalPower(ctx sdk.Context) sdk.Rat { + pool := k.GetPool(ctx) + return pool.BondedShares +} + +//__________________________________________________________________________ + +// Implements DelegationSet + +var _ sdk.DelegationSet = Keeper{} + +// Returns self as it is both a validatorset and delegationset +func (k Keeper) GetValidatorSet() sdk.ValidatorSet { + return k +} + +// get the delegation for a particular set of delegator and validator addresses +func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.AccAddress, addrVal sdk.AccAddress) sdk.Delegation { + bond, ok := k.GetDelegation(ctx, addrDel, addrVal) + if !ok { + return nil + } + return bond +} + +// iterate through the active validator set and perform the provided function +func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.AccAddress, fn func(index int64, delegation sdk.Delegation) (stop bool)) { + store := ctx.KVStore(k.storeKey) + key := GetDelegationsKey(delAddr) + iterator := sdk.KVStorePrefixIterator(store, key) + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) + stop := fn(i, delegation) // XXX is this safe will the fields be able to get written to? + if stop { + break + } + i++ + } + iterator.Close() +} diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go new file mode 100644 index 000000000..44bc2aade --- /dev/null +++ b/x/stake/keeper/slash.go @@ -0,0 +1,235 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/tendermint/tendermint/crypto" +) + +// Slash a validator for an infraction committed at a known height +// Find the contributing stake at that height and burn the specified slashFactor +// of it, updating unbonding delegation & redelegations appropriately +// +// CONTRACT: +// slashFactor is non-negative +// CONTRACT: +// Infraction committed equal to or less than an unbonding period in the past, +// so all unbonding delegations and redelegations from that height are stored +// CONTRACT: +// Infraction committed at the current height or at a past height, +// not at a height in the future +func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight int64, power int64, slashFactor sdk.Rat) { + logger := ctx.Logger().With("module", "x/stake") + + if slashFactor.LT(sdk.ZeroRat()) { + panic(fmt.Errorf("attempted to slash with a negative slashFactor: %v", slashFactor)) + } + + // Amount of slashing = slash slashFactor * power at time of infraction + slashAmount := sdk.NewRat(power).Mul(slashFactor).RoundInt() + // ref https://github.com/cosmos/cosmos-sdk/issues/1348 + // ref https://github.com/cosmos/cosmos-sdk/issues/1471 + + validator, found := k.GetValidatorByPubKey(ctx, pubkey) + if !found { + // If not found, the validator must have been overslashed and removed - so we don't need to do anything + // NOTE: Correctness dependent on invariant that unbonding delegations / redelegations must also have been completely + // slashed in this case - which we don't explicitly check, but should be true. + // Log the slash attempt for future reference (maybe we should tag it too) + logger.Error(fmt.Sprintf("WARNING: Ignored attempt to slash a nonexistent validator with address %s, we recommend you investigate immediately", pubkey.Address())) + return + } + ownerAddress := validator.GetOwner() + + // Track remaining slash amount for the validator + // This will decrease when we slash unbondings and + // redelegations, as that stake has since unbonded + remainingSlashAmount := slashAmount + + switch { + case infractionHeight > ctx.BlockHeight(): + // Can't slash infractions in the future + panic(fmt.Sprintf("impossible attempt to slash future infraction at height %d but we are at height %d", infractionHeight, ctx.BlockHeight())) + + case infractionHeight == ctx.BlockHeight(): + // Special-case slash at current height for efficiency - we don't need to look through unbonding delegations or redelegations + logger.Info(fmt.Sprintf("Slashing at current height %d, not scanning unbonding delegations & redelegations", infractionHeight)) + + case infractionHeight < ctx.BlockHeight(): + // Iterate through unbonding delegations from slashed validator + unbondingDelegations := k.GetUnbondingDelegationsFromValidator(ctx, ownerAddress) + for _, unbondingDelegation := range unbondingDelegations { + amountSlashed := k.slashUnbondingDelegation(ctx, unbondingDelegation, infractionHeight, slashFactor) + if amountSlashed.IsZero() { + continue + } + remainingSlashAmount = remainingSlashAmount.Sub(amountSlashed) + } + + // Iterate through redelegations from slashed validator + redelegations := k.GetRedelegationsFromValidator(ctx, ownerAddress) + for _, redelegation := range redelegations { + amountSlashed := k.slashRedelegation(ctx, validator, redelegation, infractionHeight, slashFactor) + if amountSlashed.IsZero() { + continue + } + remainingSlashAmount = remainingSlashAmount.Sub(amountSlashed) + } + + } + + // Cannot decrease balance below zero + sharesToRemove := sdk.MinInt(remainingSlashAmount, validator.PoolShares.Amount.RoundInt()) + + // Get the current pool + pool := k.GetPool(ctx) + // remove shares from the validator + validator, pool, burned := validator.RemovePoolShares(pool, sdk.NewRatFromInt(sharesToRemove)) + // burn tokens + pool.LooseTokens -= burned + // update the pool + k.SetPool(ctx, pool) + // update the validator, possibly kicking it out + validator = k.UpdateValidator(ctx, validator) + // remove validator if it has been reduced to zero shares + if validator.PoolShares.Amount.IsZero() { + k.RemoveValidator(ctx, validator.Owner) + } + + // Log that a slash occurred! + logger.Info(fmt.Sprintf("Validator %s slashed by slashFactor %v, removed %v shares and burned %d tokens", pubkey.Address(), slashFactor, sharesToRemove, burned)) + + // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 + return +} + +// revoke a validator +func (k Keeper) Revoke(ctx sdk.Context, pubkey crypto.PubKey) { + k.setRevoked(ctx, pubkey, true) + logger := ctx.Logger().With("module", "x/stake") + logger.Info(fmt.Sprintf("Validator %s revoked", pubkey.Address())) + // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 + return +} + +// unrevoke a validator +func (k Keeper) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) { + k.setRevoked(ctx, pubkey, false) + logger := ctx.Logger().With("module", "x/stake") + logger.Info(fmt.Sprintf("Validator %s unrevoked", pubkey.Address())) + // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 + return +} + +// set the revoked flag on a validator +func (k Keeper) setRevoked(ctx sdk.Context, pubkey crypto.PubKey, revoked bool) { + validator, found := k.GetValidatorByPubKey(ctx, pubkey) + if !found { + panic(fmt.Errorf("Validator with pubkey %s not found, cannot set revoked to %v", pubkey, revoked)) + } + validator.Revoked = revoked + k.UpdateValidator(ctx, validator) // update validator, possibly unbonding or bonding it + return +} + +// slash an unbonding delegation and update the pool +// return the amount that would have been slashed assuming +// the unbonding delegation had enough stake to slash +// (the amount actually slashed may be less if there's +// insufficient stake remaining) +func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation types.UnbondingDelegation, infractionHeight int64, slashFactor sdk.Rat) (slashAmount sdk.Int) { + now := ctx.BlockHeader().Time + + // If unbonding started before this height, stake didn't contribute to infraction + if unbondingDelegation.CreationHeight < infractionHeight { + return sdk.ZeroInt() + } + + if unbondingDelegation.MinTime < now { + // Unbonding delegation no longer eligible for slashing, skip it + // TODO Settle and delete it automatically? + return sdk.ZeroInt() + } + + // Calculate slash amount proportional to stake contributing to infraction + slashAmount = sdk.NewRatFromInt(unbondingDelegation.InitialBalance.Amount, sdk.OneInt()).Mul(slashFactor).RoundInt() + + // Don't slash more tokens than held + // Possible since the unbonding delegation may already + // have been slashed, and slash amounts are calculated + // according to stake held at time of infraction + unbondingSlashAmount := sdk.MinInt(slashAmount, unbondingDelegation.Balance.Amount) + + // Update unbonding delegation if necessary + if !unbondingSlashAmount.IsZero() { + unbondingDelegation.Balance.Amount = unbondingDelegation.Balance.Amount.Sub(unbondingSlashAmount) + k.SetUnbondingDelegation(ctx, unbondingDelegation) + pool := k.GetPool(ctx) + // Burn loose tokens + // Ref https://github.com/cosmos/cosmos-sdk/pull/1278#discussion_r198657760 + pool.LooseTokens -= slashAmount.Int64() + k.SetPool(ctx, pool) + } + + return +} + +// slash a redelegation and update the pool +// return the amount that would have been slashed assuming +// the unbonding delegation had enough stake to slash +// (the amount actually slashed may be less if there's +// insufficient stake remaining) +func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, redelegation types.Redelegation, infractionHeight int64, slashFactor sdk.Rat) (slashAmount sdk.Int) { + now := ctx.BlockHeader().Time + + // If redelegation started before this height, stake didn't contribute to infraction + if redelegation.CreationHeight < infractionHeight { + return sdk.ZeroInt() + } + + if redelegation.MinTime < now { + // Redelegation no longer eligible for slashing, skip it + // TODO Delete it automatically? + return sdk.ZeroInt() + } + + // Calculate slash amount proportional to stake contributing to infraction + slashAmount = sdk.NewRatFromInt(redelegation.InitialBalance.Amount, sdk.OneInt()).Mul(slashFactor).RoundInt() + + // Don't slash more tokens than held + // Possible since the redelegation may already + // have been slashed, and slash amounts are calculated + // according to stake held at time of infraction + redelegationSlashAmount := sdk.MinInt(slashAmount, redelegation.Balance.Amount) + + // Update redelegation if necessary + if !redelegationSlashAmount.IsZero() { + redelegation.Balance.Amount = redelegation.Balance.Amount.Sub(redelegationSlashAmount) + k.SetRedelegation(ctx, redelegation) + } + + // Unbond from target validator + sharesToUnbond := slashFactor.Mul(redelegation.SharesDst) + if !sharesToUnbond.IsZero() { + delegation, found := k.GetDelegation(ctx, redelegation.DelegatorAddr, redelegation.ValidatorDstAddr) + if !found { + // If deleted, delegation has zero shares, and we can't unbond any more + return slashAmount + } + 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)) + } + // Burn loose tokens + pool := k.GetPool(ctx) + pool.LooseTokens -= tokensToBurn + k.SetPool(ctx, pool) + } + + return slashAmount +} diff --git a/x/stake/keeper/slash_test.go b/x/stake/keeper/slash_test.go new file mode 100644 index 000000000..f9cd8229a --- /dev/null +++ b/x/stake/keeper/slash_test.go @@ -0,0 +1,480 @@ +package keeper + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +// setup helper function +// creates two validators +func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) { + // setup + ctx, _, keeper := CreateTestInput(t, false, amt) + params := keeper.GetParams(ctx) + pool := keeper.GetPool(ctx) + numVals := 3 + pool.LooseTokens = amt * int64(numVals) + + // add numVals validators + for i := 0; i < numVals; i++ { + validator := types.NewValidator(addrVals[i], PKs[i], types.Description{}) + validator, pool, _ = validator.AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + keeper.UpdateValidator(ctx, validator) + keeper.SetValidatorByPubKeyIndex(ctx, validator) + } + + return ctx, keeper, params +} + +// tests Revoke, Unrevoke +func TestRevocation(t *testing.T) { + // setup + ctx, keeper, _ := setupHelper(t, 10) + addr := addrVals[0] + pk := PKs[0] + + // initial state + val, found := keeper.GetValidator(ctx, addr) + require.True(t, found) + require.False(t, val.GetRevoked()) + + // test revoke + keeper.Revoke(ctx, pk) + val, found = keeper.GetValidator(ctx, addr) + require.True(t, found) + require.True(t, val.GetRevoked()) + + // test unrevoke + keeper.Unrevoke(ctx, pk) + val, found = keeper.GetValidator(ctx, addr) + require.True(t, found) + require.False(t, val.GetRevoked()) + +} + +// tests slashUnbondingDelegation +func TestSlashUnbondingDelegation(t *testing.T) { + ctx, keeper, params := setupHelper(t, 10) + fraction := sdk.NewRat(1, 2) + + // set an unbonding delegation + ubd := types.UnbondingDelegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + CreationHeight: 0, + // expiration timestamp (beyond which the unbonding delegation shouldn't be slashed) + MinTime: 0, + InitialBalance: sdk.NewCoin(params.BondDenom, 10), + Balance: sdk.NewCoin(params.BondDenom, 10), + } + keeper.SetUnbondingDelegation(ctx, ubd) + + // unbonding started prior to the infraction height, stake didn't contribute + slashAmount := keeper.slashUnbondingDelegation(ctx, ubd, 1, fraction) + require.Equal(t, int64(0), slashAmount.Int64()) + + // after the expiration time, no longer eligible for slashing + ctx = ctx.WithBlockHeader(abci.Header{Time: int64(10)}) + keeper.SetUnbondingDelegation(ctx, ubd) + slashAmount = keeper.slashUnbondingDelegation(ctx, ubd, 0, fraction) + require.Equal(t, int64(0), slashAmount.Int64()) + + // test valid slash, before expiration timestamp and to which stake contributed + oldPool := keeper.GetPool(ctx) + ctx = ctx.WithBlockHeader(abci.Header{Time: int64(0)}) + keeper.SetUnbondingDelegation(ctx, ubd) + slashAmount = keeper.slashUnbondingDelegation(ctx, ubd, 0, fraction) + require.Equal(t, int64(5), slashAmount.Int64()) + ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + // initialbalance unchanged + require.Equal(t, sdk.NewCoin(params.BondDenom, 10), ubd.InitialBalance) + // balance decreased + require.Equal(t, sdk.NewCoin(params.BondDenom, 5), ubd.Balance) + newPool := keeper.GetPool(ctx) + require.Equal(t, int64(5), oldPool.LooseTokens-newPool.LooseTokens) +} + +// tests slashRedelegation +func TestSlashRedelegation(t *testing.T) { + ctx, keeper, params := setupHelper(t, 10) + fraction := sdk.NewRat(1, 2) + + // set a redelegation + rd := types.Redelegation{ + DelegatorAddr: addrDels[0], + ValidatorSrcAddr: addrVals[0], + ValidatorDstAddr: addrVals[1], + CreationHeight: 0, + // expiration timestamp (beyond which the redelegation shouldn't be slashed) + MinTime: 0, + SharesSrc: sdk.NewRat(10), + SharesDst: sdk.NewRat(10), + InitialBalance: sdk.NewCoin(params.BondDenom, 10), + Balance: sdk.NewCoin(params.BondDenom, 10), + } + keeper.SetRedelegation(ctx, rd) + + // set the associated delegation + del := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[1], + Shares: sdk.NewRat(10), + } + keeper.SetDelegation(ctx, del) + + // started redelegating prior to the current height, stake didn't contribute to infraction + validator, found := keeper.GetValidator(ctx, addrVals[1]) + require.True(t, found) + slashAmount := keeper.slashRedelegation(ctx, validator, rd, 1, fraction) + require.Equal(t, int64(0), slashAmount.Int64()) + + // after the expiration time, no longer eligible for slashing + ctx = ctx.WithBlockHeader(abci.Header{Time: int64(10)}) + keeper.SetRedelegation(ctx, rd) + validator, found = keeper.GetValidator(ctx, addrVals[1]) + require.True(t, found) + slashAmount = keeper.slashRedelegation(ctx, validator, rd, 0, fraction) + require.Equal(t, int64(0), slashAmount.Int64()) + + // test valid slash, before expiration timestamp and to which stake contributed + oldPool := keeper.GetPool(ctx) + ctx = ctx.WithBlockHeader(abci.Header{Time: int64(0)}) + keeper.SetRedelegation(ctx, rd) + validator, found = keeper.GetValidator(ctx, addrVals[1]) + require.True(t, found) + slashAmount = keeper.slashRedelegation(ctx, validator, rd, 0, fraction) + require.Equal(t, int64(5), slashAmount.Int64()) + rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + // initialbalance unchanged + require.Equal(t, sdk.NewCoin(params.BondDenom, 10), rd.InitialBalance) + // balance decreased + require.Equal(t, sdk.NewCoin(params.BondDenom, 5), rd.Balance) + // shares decreased + del, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[1]) + require.True(t, found) + require.Equal(t, int64(5), del.Shares.RoundInt64()) + // pool bonded tokens decreased + newPool := keeper.GetPool(ctx) + require.Equal(t, int64(5), oldPool.BondedTokens-newPool.BondedTokens) +} + +// tests Slash at a future height (must panic) +func TestSlashAtFutureHeight(t *testing.T) { + ctx, keeper, _ := setupHelper(t, 10) + pk := PKs[0] + fraction := sdk.NewRat(1, 2) + require.Panics(t, func() { keeper.Slash(ctx, pk, 1, 10, fraction) }) +} + +// tests Slash at the current height +func TestSlashAtCurrentHeight(t *testing.T) { + ctx, keeper, _ := setupHelper(t, 10) + pk := PKs[0] + fraction := sdk.NewRat(1, 2) + + oldPool := keeper.GetPool(ctx) + validator, found := keeper.GetValidatorByPubKey(ctx, pk) + require.True(t, found) + keeper.Slash(ctx, pk, ctx.BlockHeight(), 10, fraction) + + // read updated state + validator, found = keeper.GetValidatorByPubKey(ctx, pk) + require.True(t, found) + newPool := keeper.GetPool(ctx) + + // power decreased + require.Equal(t, sdk.NewRat(5), validator.GetPower()) + // pool bonded shares decreased + require.Equal(t, sdk.NewRat(5).RoundInt64(), oldPool.BondedShares.Sub(newPool.BondedShares).RoundInt64()) +} + +// tests Slash at a previous height with an unbonding delegation +func TestSlashWithUnbondingDelegation(t *testing.T) { + ctx, keeper, params := setupHelper(t, 10) + pk := PKs[0] + fraction := sdk.NewRat(1, 2) + + // set an unbonding delegation + ubd := types.UnbondingDelegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + CreationHeight: 11, + // expiration timestamp (beyond which the unbonding delegation shouldn't be slashed) + MinTime: 0, + InitialBalance: sdk.NewCoin(params.BondDenom, 4), + Balance: sdk.NewCoin(params.BondDenom, 4), + } + keeper.SetUnbondingDelegation(ctx, ubd) + + // slash validator for the first time + ctx = ctx.WithBlockHeight(12) + oldPool := keeper.GetPool(ctx) + validator, found := keeper.GetValidatorByPubKey(ctx, pk) + require.True(t, found) + keeper.Slash(ctx, pk, 10, 10, fraction) + + // read updating unbonding delegation + ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + // balance decreased + require.Equal(t, sdk.NewInt(2), ubd.Balance.Amount) + // read updated pool + newPool := keeper.GetPool(ctx) + // bonded tokens burned + require.Equal(t, int64(3), oldPool.BondedTokens-newPool.BondedTokens) + // read updated validator + validator, found = keeper.GetValidatorByPubKey(ctx, pk) + require.True(t, found) + // power decreased by 3 - 6 stake originally bonded at the time of infraction + // was still bonded at the time of discovery and was slashed by half, 4 stake + // bonded at the time of discovery hadn't been bonded at the time of infraction + // and wasn't slashed + require.Equal(t, sdk.NewRat(7), validator.GetPower()) + + // slash validator again + ctx = ctx.WithBlockHeight(13) + keeper.Slash(ctx, pk, 9, 10, fraction) + ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + // balance decreased again + require.Equal(t, sdk.NewInt(0), ubd.Balance.Amount) + // read updated pool + newPool = keeper.GetPool(ctx) + // bonded tokens burned again + require.Equal(t, int64(6), oldPool.BondedTokens-newPool.BondedTokens) + // read updated validator + validator, found = keeper.GetValidatorByPubKey(ctx, pk) + require.True(t, found) + // power decreased by 3 again + require.Equal(t, sdk.NewRat(4), validator.GetPower()) + + // slash validator again + // all originally bonded stake has been slashed, so this will have no effect + // on the unbonding delegation, but it will slash stake bonded since the infraction + // this may not be the desirable behaviour, ref https://github.com/cosmos/cosmos-sdk/issues/1440 + ctx = ctx.WithBlockHeight(13) + keeper.Slash(ctx, pk, 9, 10, fraction) + ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + // balance unchanged + require.Equal(t, sdk.NewInt(0), ubd.Balance.Amount) + // read updated pool + newPool = keeper.GetPool(ctx) + // bonded tokens burned again + require.Equal(t, int64(9), oldPool.BondedTokens-newPool.BondedTokens) + // read updated validator + validator, found = keeper.GetValidatorByPubKey(ctx, pk) + require.True(t, found) + // power decreased by 3 again + require.Equal(t, sdk.NewRat(1), validator.GetPower()) + + // slash validator again + // all originally bonded stake has been slashed, so this will have no effect + // on the unbonding delegation, but it will slash stake bonded since the infraction + // this may not be the desirable behaviour, ref https://github.com/cosmos/cosmos-sdk/issues/1440 + ctx = ctx.WithBlockHeight(13) + keeper.Slash(ctx, pk, 9, 10, fraction) + ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + // balance unchanged + require.Equal(t, sdk.NewInt(0), ubd.Balance.Amount) + // read updated pool + newPool = keeper.GetPool(ctx) + // just 1 bonded token burned again since that's all the validator now has + require.Equal(t, int64(10), oldPool.BondedTokens-newPool.BondedTokens) + // read updated validator + // power decreased by 1 again, validator is out of stake + // ergo validator should have been removed from the store + _, found = keeper.GetValidatorByPubKey(ctx, pk) + require.False(t, found) +} + +// tests Slash at a previous height with a redelegation +func TestSlashWithRedelegation(t *testing.T) { + ctx, keeper, params := setupHelper(t, 10) + pk := PKs[0] + fraction := sdk.NewRat(1, 2) + + // set a redelegation + rd := types.Redelegation{ + DelegatorAddr: addrDels[0], + ValidatorSrcAddr: addrVals[0], + ValidatorDstAddr: addrVals[1], + CreationHeight: 11, + MinTime: 0, + SharesSrc: sdk.NewRat(6), + SharesDst: sdk.NewRat(6), + InitialBalance: sdk.NewCoin(params.BondDenom, 6), + Balance: sdk.NewCoin(params.BondDenom, 6), + } + keeper.SetRedelegation(ctx, rd) + + // set the associated delegation + del := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[1], + Shares: sdk.NewRat(6), + } + keeper.SetDelegation(ctx, del) + + // slash validator + ctx = ctx.WithBlockHeight(12) + oldPool := keeper.GetPool(ctx) + validator, found := keeper.GetValidatorByPubKey(ctx, pk) + require.True(t, found) + keeper.Slash(ctx, pk, 10, 10, fraction) + + // read updating redelegation + rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + // balance decreased + require.Equal(t, sdk.NewInt(3), rd.Balance.Amount) + // read updated pool + newPool := keeper.GetPool(ctx) + // bonded tokens burned + require.Equal(t, int64(5), oldPool.BondedTokens-newPool.BondedTokens) + // read updated validator + validator, found = keeper.GetValidatorByPubKey(ctx, pk) + require.True(t, found) + // power decreased by 2 - 4 stake originally bonded at the time of infraction + // was still bonded at the time of discovery and was slashed by half, 4 stake + // bonded at the time of discovery hadn't been bonded at the time of infraction + // and wasn't slashed + require.Equal(t, sdk.NewRat(8), validator.GetPower()) + + // slash the validator again + ctx = ctx.WithBlockHeight(12) + validator, found = keeper.GetValidatorByPubKey(ctx, pk) + require.True(t, found) + keeper.Slash(ctx, pk, 10, 10, sdk.NewRat(3, 4)) + + // read updating redelegation + rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + // balance decreased, now zero + require.Equal(t, sdk.NewInt(0), rd.Balance.Amount) + // read updated pool + newPool = keeper.GetPool(ctx) + // 7 bonded tokens burned + require.Equal(t, int64(12), oldPool.BondedTokens-newPool.BondedTokens) + // read updated validator + validator, found = keeper.GetValidatorByPubKey(ctx, pk) + require.True(t, found) + // power decreased by 4 + require.Equal(t, sdk.NewRat(4), validator.GetPower()) + + // slash the validator again, by 100% + ctx = ctx.WithBlockHeight(12) + validator, found = keeper.GetValidatorByPubKey(ctx, pk) + require.True(t, found) + keeper.Slash(ctx, pk, 10, 10, sdk.OneRat()) + + // read updating redelegation + rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + // balance still zero + require.Equal(t, sdk.NewInt(0), rd.Balance.Amount) + // read updated pool + newPool = keeper.GetPool(ctx) + // four more bonded tokens burned + require.Equal(t, int64(16), oldPool.BondedTokens-newPool.BondedTokens) + // read updated validator + // validator decreased to zero power, should have been removed from the store + _, found = keeper.GetValidatorByPubKey(ctx, pk) + require.False(t, found) + + // slash the validator again, by 100% + // no stake remains to be slashed + ctx = ctx.WithBlockHeight(12) + // validator no longer in the store + _, found = keeper.GetValidatorByPubKey(ctx, pk) + require.False(t, found) + keeper.Slash(ctx, pk, 10, 10, sdk.OneRat()) + + // read updating redelegation + rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + // balance still zero + require.Equal(t, sdk.NewInt(0), rd.Balance.Amount) + // read updated pool + newPool = keeper.GetPool(ctx) + // no more bonded tokens burned + require.Equal(t, int64(16), oldPool.BondedTokens-newPool.BondedTokens) + // read updated validator + // power still zero, still not in the store + _, found = keeper.GetValidatorByPubKey(ctx, pk) + require.False(t, found) +} + +// tests Slash at a previous height with both an unbonding delegation and a redelegation +func TestSlashBoth(t *testing.T) { + ctx, keeper, params := setupHelper(t, 10) + fraction := sdk.NewRat(1, 2) + + // set a redelegation + rdA := types.Redelegation{ + DelegatorAddr: addrDels[0], + ValidatorSrcAddr: addrVals[0], + ValidatorDstAddr: addrVals[1], + CreationHeight: 11, + // expiration timestamp (beyond which the redelegation shouldn't be slashed) + MinTime: 0, + SharesSrc: sdk.NewRat(6), + SharesDst: sdk.NewRat(6), + InitialBalance: sdk.NewCoin(params.BondDenom, 6), + Balance: sdk.NewCoin(params.BondDenom, 6), + } + keeper.SetRedelegation(ctx, rdA) + + // set the associated delegation + delA := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[1], + Shares: sdk.NewRat(6), + } + keeper.SetDelegation(ctx, delA) + + // set an unbonding delegation + ubdA := types.UnbondingDelegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + CreationHeight: 11, + // expiration timestamp (beyond which the unbonding delegation shouldn't be slashed) + MinTime: 0, + InitialBalance: sdk.NewCoin(params.BondDenom, 4), + Balance: sdk.NewCoin(params.BondDenom, 4), + } + keeper.SetUnbondingDelegation(ctx, ubdA) + + // slash validator + ctx = ctx.WithBlockHeight(12) + oldPool := keeper.GetPool(ctx) + validator, found := keeper.GetValidatorByPubKey(ctx, PKs[0]) + require.True(t, found) + keeper.Slash(ctx, PKs[0], 10, 10, fraction) + + // read updating redelegation + rdA, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + // balance decreased + require.Equal(t, sdk.NewInt(3), rdA.Balance.Amount) + // read updated pool + newPool := keeper.GetPool(ctx) + // loose tokens burned + require.Equal(t, int64(2), oldPool.LooseTokens-newPool.LooseTokens) + // bonded tokens burned + require.Equal(t, int64(3), oldPool.BondedTokens-newPool.BondedTokens) + // read updated validator + validator, found = keeper.GetValidatorByPubKey(ctx, PKs[0]) + require.True(t, found) + // power not decreased, all stake was bonded since + require.Equal(t, sdk.NewRat(10), validator.GetPower()) +} diff --git a/x/stake/test_common.go b/x/stake/keeper/test_common.go similarity index 52% rename from x/stake/test_common.go rename to x/stake/keeper/test_common.go index 3cea0b281..21073a1ff 100644 --- a/x/stake/test_common.go +++ b/x/stake/keeper/test_common.go @@ -1,4 +1,4 @@ -package stake +package keeper import ( "bytes" @@ -8,45 +8,62 @@ import ( "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) // dummy addresses used for testing var ( - addrs = createTestAddrs(100) - pks = createTestPubKeys(100) - emptyAddr sdk.Address + Addrs = createTestAddrs(100) + PKs = createTestPubKeys(100) + emptyAddr sdk.AccAddress emptyPubkey crypto.PubKey + + addrDels = []sdk.AccAddress{ + Addrs[0], + Addrs[1], + } + addrVals = []sdk.AccAddress{ + Addrs[2], + Addrs[3], + Addrs[4], + Addrs[5], + Addrs[6], + } ) //_______________________________________________________________________________________ // intended to be used with require/assert: require.True(ValEq(...)) -func ValEq(t *testing.T, exp, got Validator) (*testing.T, bool, string, Validator, Validator) { - return t, exp.equal(got), "expected:\t%v\ngot:\t\t%v", exp, got +func ValEq(t *testing.T, exp, got types.Validator) (*testing.T, bool, string, types.Validator, types.Validator) { + return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got } //_______________________________________________________________________________________ -func makeTestCodec() *wire.Codec { +// create a codec used only for testing +func MakeTestCodec() *wire.Codec { var cdc = wire.NewCodec() // Register Msgs cdc.RegisterInterface((*sdk.Msg)(nil), nil) cdc.RegisterConcrete(bank.MsgSend{}, "test/stake/Send", nil) cdc.RegisterConcrete(bank.MsgIssue{}, "test/stake/Issue", nil) - cdc.RegisterConcrete(MsgCreateValidator{}, "test/stake/CreateValidator", nil) - cdc.RegisterConcrete(MsgEditValidator{}, "test/stake/EditValidator", nil) - cdc.RegisterConcrete(MsgUnbond{}, "test/stake/Unbond", nil) + cdc.RegisterConcrete(types.MsgCreateValidator{}, "test/stake/CreateValidator", nil) + cdc.RegisterConcrete(types.MsgEditValidator{}, "test/stake/EditValidator", nil) + cdc.RegisterConcrete(types.MsgBeginUnbonding{}, "test/stake/BeginUnbonding", nil) + cdc.RegisterConcrete(types.MsgCompleteUnbonding{}, "test/stake/CompleteUnbonding", nil) + cdc.RegisterConcrete(types.MsgBeginRedelegate{}, "test/stake/BeginRedelegate", nil) + cdc.RegisterConcrete(types.MsgCompleteRedelegate{}, "test/stake/CompleteRedelegate", nil) // Register AppAccount cdc.RegisterInterface((*auth.Account)(nil), nil) @@ -56,8 +73,9 @@ func makeTestCodec() *wire.Codec { return cdc } -func paramsNoInflation() Params { - return Params{ +// default params without inflation +func ParamsNoInflation() types.Params { + return types.Params{ InflationRateChange: sdk.ZeroRat(), InflationMax: sdk.ZeroRat(), InflationMin: sdk.ZeroRat(), @@ -68,7 +86,7 @@ func paramsNoInflation() Params { } // hogpodge of all sorts of input required for testing -func createTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, auth.AccountMapper, Keeper) { +func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, auth.AccountMapper, Keeper) { keyStake := sdk.NewKVStoreKey("stake") keyAcc := sdk.NewKVStoreKey("acc") @@ -80,29 +98,34 @@ func createTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context err := ms.LoadLatestVersion() require.Nil(t, err) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, nil, log.NewNopLogger()) - cdc := makeTestCodec() + ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, log.NewNopLogger()) + cdc := MakeTestCodec() accountMapper := auth.NewAccountMapper( - cdc, // amino codec - keyAcc, // target store - &auth.BaseAccount{}, // prototype + cdc, // amino codec + keyAcc, // target store + auth.ProtoBaseAccount, // prototype ) ck := bank.NewKeeper(accountMapper) - keeper := NewKeeper(cdc, keyStake, ck, DefaultCodespace) - keeper.setPool(ctx, InitialPool()) - keeper.setNewParams(ctx, DefaultParams()) + keeper := NewKeeper(cdc, keyStake, ck, types.DefaultCodespace) + keeper.SetPool(ctx, types.InitialPool()) + keeper.SetNewParams(ctx, types.DefaultParams()) + keeper.InitIntraTxCounter(ctx) - // fill all the addresses with some coins - for _, addr := range addrs { - ck.AddCoins(ctx, addr, sdk.Coins{ - {keeper.GetParams(ctx).BondDenom, initCoins}, + // fill all the addresses with some coins, set the loose pool tokens simultaneously + for _, addr := range Addrs { + pool := keeper.GetPool(ctx) + _, _, err := ck.AddCoins(ctx, addr, sdk.Coins{ + {keeper.GetParams(ctx).BondDenom, sdk.NewInt(initCoins)}, }) + require.Nil(t, err) + pool.LooseTokens += initCoins + keeper.SetPool(ctx, pool) } return ctx, accountMapper, keeper } -func newPubKey(pk string) (res crypto.PubKey) { +func NewPubKey(pk string) (res crypto.PubKey) { pkBytes, err := hex.DecodeString(pk) if err != nil { panic(err) @@ -114,21 +137,18 @@ func newPubKey(pk string) (res crypto.PubKey) { } // for incode address generation -func testAddr(addr string, bech string) sdk.Address { +func TestAddr(addr string, bech string) sdk.AccAddress { - res, err := sdk.GetAccAddressHex(addr) - if err != nil { - panic(err) - } - bechexpected, err := sdk.Bech32ifyAcc(res) + res, err := sdk.AccAddressFromHex(addr) if err != nil { panic(err) } + bechexpected := res.String() if bech != bechexpected { panic("Bech encoding doesn't match reference") } - bechres, err := sdk.GetAccAddressBech32(bech) + bechres, err := sdk.AccAddressFromBech32(bech) if err != nil { panic(err) } @@ -139,8 +159,9 @@ func testAddr(addr string, bech string) sdk.Address { return res } -func createTestAddrs(numAddrs int) []sdk.Address { - var addresses []sdk.Address +// nolint: unparam +func createTestAddrs(numAddrs int) []sdk.AccAddress { + var addresses []sdk.AccAddress var buffer bytes.Buffer // start at 100 so we can make up to 999 test addresses with valid test addresses @@ -149,14 +170,15 @@ func createTestAddrs(numAddrs int) []sdk.Address { buffer.WriteString("A58856F0FD53BF058B4909A21AEC019107BA6") //base address string buffer.WriteString(numString) //adding on final two digits to make addresses unique - res, _ := sdk.GetAccAddressHex(buffer.String()) - bech, _ := sdk.Bech32ifyAcc(res) - addresses = append(addresses, testAddr(buffer.String(), bech)) + res, _ := sdk.AccAddressFromHex(buffer.String()) + bech := res.String() + addresses = append(addresses, TestAddr(buffer.String(), bech)) buffer.Reset() } return addresses } +// nolint: unparam func createTestPubKeys(numPubKeys int) []crypto.PubKey { var publicKeys []crypto.PubKey var buffer bytes.Buffer @@ -166,8 +188,16 @@ func createTestPubKeys(numPubKeys int) []crypto.PubKey { numString := strconv.Itoa(i) buffer.WriteString("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AF") //base pubkey string buffer.WriteString(numString) //adding on final two digits to make pubkeys unique - publicKeys = append(publicKeys, newPubKey(buffer.String())) + publicKeys = append(publicKeys, NewPubKey(buffer.String())) buffer.Reset() } return publicKeys } + +//_____________________________________________________________________________________ + +// does a certain by-power index record exist +func ValidatorByPowerIndexExists(ctx sdk.Context, keeper Keeper, power []byte) bool { + store := ctx.KVStore(keeper.storeKey) + return store.Get(power) != nil +} diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go new file mode 100644 index 000000000..6962ca28a --- /dev/null +++ b/x/stake/keeper/validator.go @@ -0,0 +1,551 @@ +package keeper + +import ( + "bytes" + "fmt" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// get a single validator +func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.AccAddress) (validator types.Validator, found bool) { + store := ctx.KVStore(k.storeKey) + value := store.Get(GetValidatorKey(addr)) + if value == nil { + return validator, false + } + validator = types.MustUnmarshalValidator(k.cdc, addr, value) + return validator, true +} + +// get a single validator by pubkey +func (k Keeper) GetValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) (validator types.Validator, found bool) { + store := ctx.KVStore(k.storeKey) + addr := store.Get(GetValidatorByPubKeyIndexKey(pubkey)) + if addr == nil { + return validator, false + } + return k.GetValidator(ctx, addr) +} + +// set the main record holding validator details +func (k Keeper) SetValidator(ctx sdk.Context, validator types.Validator) { + store := ctx.KVStore(k.storeKey) + bz := types.MustMarshalValidator(k.cdc, validator) + store.Set(GetValidatorKey(validator.Owner), bz) +} + +// validator index +func (k Keeper) SetValidatorByPubKeyIndex(ctx sdk.Context, validator types.Validator) { + store := ctx.KVStore(k.storeKey) + store.Set(GetValidatorByPubKeyIndexKey(validator.PubKey), validator.Owner) +} + +// validator index +func (k Keeper) SetValidatorByPowerIndex(ctx sdk.Context, validator types.Validator, pool types.Pool) { + store := ctx.KVStore(k.storeKey) + store.Set(GetValidatorsByPowerIndexKey(validator, pool), validator.Owner) +} + +// validator index +func (k Keeper) SetValidatorBondedIndex(ctx sdk.Context, validator types.Validator) { + store := ctx.KVStore(k.storeKey) + store.Set(GetValidatorsBondedIndexKey(validator.Owner), []byte{}) +} + +// used in testing +func (k Keeper) validatorByPowerIndexExists(ctx sdk.Context, power []byte) bool { + store := ctx.KVStore(k.storeKey) + return store.Get(power) != nil +} + +// Get the set of all validators with no limits, used during genesis dump +func (k Keeper) GetAllValidators(ctx sdk.Context) (validators []types.Validator) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) + + i := 0 + for ; ; i++ { + if !iterator.Valid() { + break + } + addr := iterator.Key()[1:] + validator := types.MustUnmarshalValidator(k.cdc, addr, iterator.Value()) + validators = append(validators, validator) + iterator.Next() + } + iterator.Close() + return validators +} + +// Get the set of all validators, retrieve a maxRetrieve number of records +func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve int16) (validators []types.Validator) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) + + validators = make([]types.Validator, maxRetrieve) + i := 0 + for ; ; i++ { + if !iterator.Valid() || i > int(maxRetrieve-1) { + break + } + addr := iterator.Key()[1:] + validator := types.MustUnmarshalValidator(k.cdc, addr, iterator.Value()) + validators[i] = validator + iterator.Next() + } + iterator.Close() + return validators[:i] // trim +} + +//___________________________________________________________________________ + +// get the group of the bonded validators +func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validator) { + store := ctx.KVStore(k.storeKey) + + // add the actual validator power sorted store + maxValidators := k.GetParams(ctx).MaxValidators + validators = make([]types.Validator, maxValidators) + + iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + i := 0 + for ; iterator.Valid(); iterator.Next() { + + // sanity check + if i > int(maxValidators-1) { + panic("maxValidators is less than the number of records in ValidatorsBonded Store, store should have been updated") + } + address := GetAddressFromValBondedIndexKey(iterator.Key()) + validator, found := k.GetValidator(ctx, address) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", address)) + } + + validators[i] = validator + i++ + } + iterator.Close() + return validators[:i] // trim +} + +// get the group of bonded validators sorted by power-rank +func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []types.Validator { + store := ctx.KVStore(k.storeKey) + maxValidators := k.GetParams(ctx).MaxValidators + validators := make([]types.Validator, maxValidators) + iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) // largest to smallest + i := 0 + for { + if !iterator.Valid() || i > int(maxValidators-1) { + break + } + address := iterator.Value() + validator, found := k.GetValidator(ctx, address) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", address)) + } + if validator.Status() == sdk.Bonded { + validators[i] = validator + i++ + } + iterator.Next() + } + iterator.Close() + return validators[:i] // trim +} + +//_________________________________________________________________________ +// Accumulated updates to the active/bonded validator set for tendermint + +// get the most recently updated validators +func (k Keeper) GetTendermintUpdates(ctx sdk.Context) (updates []abci.Validator) { + store := ctx.KVStore(k.storeKey) + + iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) //smallest to largest + for ; iterator.Valid(); iterator.Next() { + valBytes := iterator.Value() + var val abci.Validator + k.cdc.MustUnmarshalBinary(valBytes, &val) + updates = append(updates, val) + } + iterator.Close() + return +} + +// remove all validator update entries after applied to Tendermint +func (k Keeper) ClearTendermintUpdates(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + + // delete subspace + iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) + for ; iterator.Valid(); iterator.Next() { + store.Delete(iterator.Key()) + } + iterator.Close() +} + +//___________________________________________________________________________ + +// perfom all the nessisary steps for when a validator changes its power +// updates all validator stores as well as tendermint update store +// may kick out validators if new validator is entering the bonded validator group +// nolint: gocyclo +// TODO: Remove above nolint, function needs to be simplified +func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) types.Validator { + store := ctx.KVStore(k.storeKey) + pool := k.GetPool(ctx) + ownerAddr := validator.Owner + + // always update the main list ordered by owner address before exiting + defer func() { + k.SetValidator(ctx, validator) + }() + + // retrieve the old validator record + oldValidator, oldFound := k.GetValidator(ctx, ownerAddr) + + if validator.Revoked && oldValidator.Status() == sdk.Bonded { + validator = k.unbondValidator(ctx, validator) + + // need to also clear the cliff validator spot because the revoke has + // opened up a new spot which will be filled when + // updateValidatorsBonded is called + k.clearCliffValidator(ctx) + } + + powerIncreasing := false + if oldFound && oldValidator.PoolShares.Bonded().LT(validator.PoolShares.Bonded()) { + powerIncreasing = true + } + + // if already a validator, copy the old block height and counter, else set them + if oldFound && oldValidator.Status() == sdk.Bonded { + validator.BondHeight = oldValidator.BondHeight + validator.BondIntraTxCounter = oldValidator.BondIntraTxCounter + } else { + validator.BondHeight = ctx.BlockHeight() + counter := k.GetIntraTxCounter(ctx) + validator.BondIntraTxCounter = counter + k.SetIntraTxCounter(ctx, counter+1) + } + + // update the list ordered by voting power + if oldFound { + store.Delete(GetValidatorsByPowerIndexKey(oldValidator, pool)) + } + valPower := GetValidatorsByPowerIndexKey(validator, pool) + store.Set(valPower, validator.Owner) + + // efficiency case: + // if already bonded and power increasing only need to update tendermint + if powerIncreasing && !validator.Revoked && oldValidator.Status() == sdk.Bonded { + bz := k.cdc.MustMarshalBinary(validator.ABCIValidator()) + store.Set(GetTendermintUpdatesKey(ownerAddr), bz) + return validator + } + + // efficiency case: + // if was unbonded/or is a new validator - and the new power is less than the cliff validator + cliffPower := k.GetCliffValidatorPower(ctx) + if cliffPower != nil && + (!oldFound || (oldFound && oldValidator.Status() == sdk.Unbonded)) && + bytes.Compare(valPower, cliffPower) == -1 { //(valPower < cliffPower + return validator + } + + // update the validator set for this validator + updatedVal := k.UpdateBondedValidators(ctx, validator) + if updatedVal.Owner != nil { // updates to validator occurred to be updated + validator = updatedVal + } + // if decreased in power but still bonded, update Tendermint validator + // (if updatedVal is set, the validator has changed bonding status) + stillBonded := oldFound && oldValidator.Status() == sdk.Bonded && updatedVal.Owner == nil + if stillBonded && oldValidator.PoolShares.Bonded().GT(validator.PoolShares.Bonded()) { + bz := k.cdc.MustMarshalBinary(validator.ABCIValidator()) + store.Set(GetTendermintUpdatesKey(ownerAddr), bz) + } + return validator +} + +// Update the validator group and kick out any old validators. In addition this +// function adds (or doesn't add) a validator which has updated its bonded +// tokens to the validator group. -> this validator is specified through the +// updatedValidatorAddr term. +// +// The correct subset is retrieved by iterating through an index of the +// validators sorted by power, stored using the ValidatorsByPowerIndexKey. +// Simultaneously the current validator records are updated in store with the +// ValidatorsBondedIndexKey. This store is used to determine if a validator is a +// validator without needing to iterate over the subspace as we do in +// GetValidators. +// +// Optionally also return the validator from a retrieve address if the validator has been bonded +// nolint: gocyclo +// TODO: Remove the above golint +func (k Keeper) UpdateBondedValidators(ctx sdk.Context, + affectedValidator types.Validator) (updatedVal types.Validator) { + + store := ctx.KVStore(k.storeKey) + + kickCliffValidator := false + oldCliffValidatorAddr := k.GetCliffValidator(ctx) + + // add the actual validator power sorted store + maxValidators := k.GetParams(ctx).MaxValidators + iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) // largest to smallest + bondedValidatorsCount := 0 + var validator types.Validator + for { + if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { + + // TODO benchmark if we should read the current power and not write if it's the same + if bondedValidatorsCount == int(maxValidators) { // is cliff validator + k.setCliffValidator(ctx, validator, k.GetPool(ctx)) + } else if len(oldCliffValidatorAddr) > 0 { + k.clearCliffValidator(ctx) + } + break + } + + // either retrieve the original validator from the store, or under the + // situation that this is the "new validator" just use the validator + // provided because it has not yet been updated in the main validator + // store + ownerAddr := iterator.Value() + if bytes.Equal(ownerAddr, affectedValidator.Owner) { + validator = affectedValidator + } else { + var found bool + validator, found = k.GetValidator(ctx, ownerAddr) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) + } + } + + // if not previously a validator (and unrevoked), + // kick the cliff validator / bond this new validator + if validator.Status() != sdk.Bonded && !validator.Revoked { + kickCliffValidator = true + + validator = k.bondValidator(ctx, validator) + if bytes.Equal(ownerAddr, affectedValidator.Owner) { + updatedVal = validator + } + } + + if !validator.Revoked { + bondedValidatorsCount++ + } else { + if validator.Status() == sdk.Bonded { + panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr)) + } + } + + iterator.Next() + } + iterator.Close() + + // perform the actual kicks + if oldCliffValidatorAddr != nil && kickCliffValidator { + validator, found := k.GetValidator(ctx, oldCliffValidatorAddr) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", oldCliffValidatorAddr)) + } + k.unbondValidator(ctx, validator) + } + + return +} + +// full update of the bonded validator set, many can be added/kicked +func (k Keeper) UpdateBondedValidatorsFull(ctx sdk.Context) { + + store := ctx.KVStore(k.storeKey) + + // clear the current validators store, add to the ToKickOut temp store + toKickOut := make(map[string]byte) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + for ; iterator.Valid(); iterator.Next() { + ownerAddr := GetAddressFromValBondedIndexKey(iterator.Key()) + toKickOut[string(ownerAddr)] = 0 // set anything + } + iterator.Close() + + // add the actual validator power sorted store + maxValidators := k.GetParams(ctx).MaxValidators + iterator = sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) // largest to smallest + bondedValidatorsCount := 0 + var validator types.Validator + for { + if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { + + if bondedValidatorsCount == int(maxValidators) { // is cliff validator + k.setCliffValidator(ctx, validator, k.GetPool(ctx)) + } + break + } + + // either retrieve the original validator from the store, + // or under the situation that this is the "new validator" just + // use the validator provided because it has not yet been updated + // in the main validator store + ownerAddr := iterator.Value() + var found bool + validator, found = k.GetValidator(ctx, ownerAddr) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) + } + + _, found = toKickOut[string(ownerAddr)] + if found { + delete(toKickOut, string(ownerAddr)) + } else { + + // if it wasn't in the toKickOut group it means + // this wasn't a previously a validator, therefor + // update the validator to enter the validator group + validator = k.bondValidator(ctx, validator) + } + + if !validator.Revoked { + bondedValidatorsCount++ + } else { + if validator.Status() == sdk.Bonded { + panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr)) + } + } + + iterator.Next() + } + iterator.Close() + + // perform the actual kicks + kickOutValidators(k, ctx, toKickOut) + return +} + +func kickOutValidators(k Keeper, ctx sdk.Context, toKickOut map[string]byte) { + for key := range toKickOut { + ownerAddr := []byte(key) + validator, found := k.GetValidator(ctx, ownerAddr) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) + } + k.unbondValidator(ctx, validator) + } +} + +// perform all the store operations for when a validator status becomes unbonded +func (k Keeper) unbondValidator(ctx sdk.Context, validator types.Validator) types.Validator { + + store := ctx.KVStore(k.storeKey) + pool := k.GetPool(ctx) + + // sanity check + if validator.Status() == sdk.Unbonded { + panic(fmt.Sprintf("should not already be unbonded, validator: %v\n", validator)) + } + + // set the status + validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) + k.SetPool(ctx, pool) + + // save the now unbonded validator record + k.SetValidator(ctx, validator) + + // add to accumulated changes for tendermint + bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidatorZero()) + store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI) + + // also remove from the Bonded types.Validators Store + store.Delete(GetValidatorsBondedIndexKey(validator.Owner)) + return validator +} + +// perform all the store operations for when a validator status becomes bonded +func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types.Validator { + + store := ctx.KVStore(k.storeKey) + pool := k.GetPool(ctx) + + // sanity check + if validator.Status() == sdk.Bonded { + panic(fmt.Sprintf("should not already be bonded, validator: %v\n", validator)) + } + + // set the status + validator, pool = validator.UpdateStatus(pool, sdk.Bonded) + k.SetPool(ctx, pool) + + // save the now bonded validator record to the three referenced stores + k.SetValidator(ctx, validator) + store.Set(GetValidatorsBondedIndexKey(validator.Owner), []byte{}) + + // add to accumulated changes for tendermint + bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidator()) + store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI) + + return validator +} + +// remove the validator record and associated indexes +func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.AccAddress) { + + // first retrieve the old validator record + validator, found := k.GetValidator(ctx, address) + if !found { + return + } + + // delete the old validator record + store := ctx.KVStore(k.storeKey) + pool := k.GetPool(ctx) + store.Delete(GetValidatorKey(address)) + store.Delete(GetValidatorByPubKeyIndexKey(validator.PubKey)) + store.Delete(GetValidatorsByPowerIndexKey(validator, pool)) + + // delete from the current and power weighted validator groups if the validator + // is bonded - and add validator with zero power to the validator updates + if store.Get(GetValidatorsBondedIndexKey(validator.Owner)) == nil { + return + } + store.Delete(GetValidatorsBondedIndexKey(validator.Owner)) + + bz := k.cdc.MustMarshalBinary(validator.ABCIValidatorZero()) + store.Set(GetTendermintUpdatesKey(address), bz) +} + +//__________________________________________________________________________ + +// get the current validator on the cliff +func (k Keeper) GetCliffValidator(ctx sdk.Context) []byte { + store := ctx.KVStore(k.storeKey) + return store.Get(ValidatorCliffIndexKey) +} + +// get the current power of the validator on the cliff +func (k Keeper) GetCliffValidatorPower(ctx sdk.Context) []byte { + store := ctx.KVStore(k.storeKey) + return store.Get(ValidatorPowerCliffKey) +} + +// set the current validator and power of the validator on the cliff +func (k Keeper) setCliffValidator(ctx sdk.Context, validator types.Validator, pool types.Pool) { + store := ctx.KVStore(k.storeKey) + bz := GetValidatorsByPowerIndexKey(validator, pool) + store.Set(ValidatorPowerCliffKey, bz) + store.Set(ValidatorCliffIndexKey, validator.Owner) +} + +// clear the current validator and power of the validator on the cliff +func (k Keeper) clearCliffValidator(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + store.Delete(ValidatorPowerCliffKey) + store.Delete(ValidatorCliffIndexKey) +} diff --git a/x/stake/keeper/validator_test.go b/x/stake/keeper/validator_test.go new file mode 100644 index 000000000..4e962420a --- /dev/null +++ b/x/stake/keeper/validator_test.go @@ -0,0 +1,738 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSetValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 10) + pool := keeper.GetPool(ctx) + + // test how the validator is set from a purely unbonbed pool + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, _ = validator.AddTokensFromDel(pool, 10) + require.Equal(t, sdk.Unbonded, validator.Status()) + assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Unbonded())) + assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares)) + keeper.SetPool(ctx, pool) + keeper.UpdateValidator(ctx, validator) + + // after the save the validator should be bonded + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, sdk.Bonded, validator.Status()) + assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) + assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares)) + + // Check each store for being saved + resVal, found := keeper.GetValidator(ctx, addrVals[0]) + assert.True(ValEq(t, validator, resVal)) + require.True(t, found) + + resVals := keeper.GetValidatorsBonded(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validator, resVals[0])) + + resVals = keeper.GetValidatorsByPower(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validator, resVals[0])) + + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 1, len(updates)) + require.Equal(t, validator.ABCIValidator(), updates[0]) + +} + +func TestUpdateValidatorByPowerIndex(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + + // create a random pool + pool.LooseTokens = 10000 + pool.BondedTokens = 1234 + pool.BondedShares = sdk.NewRat(124) + pool.UnbondingTokens = 13934 + pool.UnbondingShares = sdk.NewRat(145) + pool.UnbondedTokens = 154 + pool.UnbondedShares = sdk.NewRat(1333) + keeper.SetPool(ctx, pool) + + // add a validator + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, delSharesCreated := validator.AddTokensFromDel(pool, 100) + require.Equal(t, sdk.Unbonded, validator.Status()) + require.Equal(t, int64(100), validator.PoolShares.Tokens(pool).RoundInt64()) + keeper.SetPool(ctx, pool) + keeper.UpdateValidator(ctx, validator) + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, int64(100), validator.PoolShares.Tokens(pool).RoundInt64(), "\nvalidator %v\npool %v", validator, pool) + + pool = keeper.GetPool(ctx) + power := GetValidatorsByPowerIndexKey(validator, pool) + require.True(t, keeper.validatorByPowerIndexExists(ctx, power)) + + // burn half the delegator shares + validator, pool, burned := validator.RemoveDelShares(pool, delSharesCreated.Quo(sdk.NewRat(2))) + require.Equal(t, int64(50), burned) + keeper.SetPool(ctx, pool) // update the pool + keeper.UpdateValidator(ctx, validator) // update the validator, possibly kicking it out + require.False(t, keeper.validatorByPowerIndexExists(ctx, power)) + + pool = keeper.GetPool(ctx) + validator, found = keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + power = GetValidatorsByPowerIndexKey(validator, pool) + require.True(t, keeper.validatorByPowerIndexExists(ctx, power)) +} + +func TestSlashToZeroPowerRemoved(t *testing.T) { + // initialize setup + ctx, _, keeper := CreateTestInput(t, false, 100) + pool := keeper.GetPool(ctx) + + // add a validator + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, _ = validator.AddTokensFromDel(pool, 100) + require.Equal(t, sdk.Unbonded, validator.Status()) + require.Equal(t, int64(100), validator.PoolShares.Tokens(pool).RoundInt64()) + keeper.SetPool(ctx, pool) + keeper.SetValidatorByPubKeyIndex(ctx, validator) + validator = keeper.UpdateValidator(ctx, validator) + require.Equal(t, int64(100), validator.PoolShares.Tokens(pool).RoundInt64(), "\nvalidator %v\npool %v", validator, pool) + + // slash the validator by 100% + keeper.Slash(ctx, PKs[0], 0, 100, sdk.OneRat()) + // validator should have been deleted + _, found := keeper.GetValidator(ctx, addrVals[0]) + require.False(t, found) +} + +// This function tests UpdateValidator, GetValidator, GetValidatorsBonded, RemoveValidator +func TestValidatorBasics(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + pool := keeper.GetPool(ctx) + + //construct the validators + var validators [3]types.Validator + amts := []int64{9, 8, 7} + for i, amt := range amts { + validators[i] = types.NewValidator(addrVals[i], PKs[i], types.Description{}) + validators[i].PoolShares = types.NewUnbondedShares(sdk.ZeroRat()) + validators[i].AddTokensFromDel(pool, amt) + } + + // check the empty keeper first + _, found := keeper.GetValidator(ctx, addrVals[0]) + require.False(t, found) + resVals := keeper.GetValidatorsBonded(ctx) + assert.Zero(t, len(resVals)) + + // set and retrieve a record + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + resVal, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + assert.True(ValEq(t, validators[0], resVal)) + + resVals = keeper.GetValidatorsBonded(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validators[0], resVals[0])) + + // modify a records, save, and retrieve + validators[0].PoolShares = types.NewBondedShares(sdk.NewRat(10)) + validators[0].DelegatorShares = sdk.NewRat(10) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + resVal, found = keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + assert.True(ValEq(t, validators[0], resVal)) + + resVals = keeper.GetValidatorsBonded(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validators[0], resVals[0])) + + // add other validators + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + resVal, found = keeper.GetValidator(ctx, addrVals[1]) + require.True(t, found) + assert.True(ValEq(t, validators[1], resVal)) + resVal, found = keeper.GetValidator(ctx, addrVals[2]) + require.True(t, found) + assert.True(ValEq(t, validators[2], resVal)) + + resVals = keeper.GetValidatorsBonded(ctx) + require.Equal(t, 3, len(resVals)) + assert.True(ValEq(t, validators[0], resVals[0])) // order doesn't matter here + assert.True(ValEq(t, validators[1], resVals[1])) + assert.True(ValEq(t, validators[2], resVals[2])) + + // remove a record + keeper.RemoveValidator(ctx, validators[1].Owner) + _, found = keeper.GetValidator(ctx, addrVals[1]) + require.False(t, found) +} + +// test how the validators are sorted, tests GetValidatorsByPower +func GetValidatorSortingUnmixed(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + // initialize some validators into the state + amts := []int64{0, 100, 1, 400, 200} + n := len(amts) + var validators [5]types.Validator + for i, amt := range amts { + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i].PoolShares = types.NewBondedShares(sdk.NewRat(amt)) + validators[i].DelegatorShares = sdk.NewRat(amt) + keeper.UpdateValidator(ctx, validators[i]) + } + + // first make sure everything made it in to the gotValidator group + resValidators := keeper.GetValidatorsByPower(ctx) + assert.Equal(t, n, len(resValidators)) + assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators) + assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators) + assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators) + assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators) + assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators) + + // test a basic increase in voting power + validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(500)) + keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + assert.True(ValEq(t, validators[3], resValidators[0])) + + // test a decrease in voting power + validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(300)) + keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + assert.True(ValEq(t, validators[3], resValidators[0])) + assert.True(ValEq(t, validators[4], resValidators[1])) + + // test equal voting power, different age + validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(200)) + ctx = ctx.WithBlockHeight(10) + keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + assert.True(ValEq(t, validators[3], resValidators[0])) + assert.True(ValEq(t, validators[4], resValidators[1])) + require.Equal(t, int64(0), resValidators[0].BondHeight, "%v", resValidators) + require.Equal(t, int64(0), resValidators[1].BondHeight, "%v", resValidators) + + // no change in voting power - no change in sort + ctx = ctx.WithBlockHeight(20) + keeper.UpdateValidator(ctx, validators[4]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + assert.True(ValEq(t, validators[3], resValidators[0])) + assert.True(ValEq(t, validators[4], resValidators[1])) + + // change in voting power of both validators, both still in v-set, no age change + validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(300)) + validators[4].PoolShares = types.NewBondedShares(sdk.NewRat(300)) + keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + ctx = ctx.WithBlockHeight(30) + keeper.UpdateValidator(ctx, validators[4]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n, "%v", resValidators) + assert.True(ValEq(t, validators[3], resValidators[0])) + assert.True(ValEq(t, validators[4], resValidators[1])) +} + +func GetValidatorSortingMixed(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + // now 2 max resValidators + params := keeper.GetParams(ctx) + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + // initialize some validators into the state + amts := []int64{0, 100, 1, 400, 200} + + n := len(amts) + var validators [5]types.Validator + for i, amt := range amts { + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i].DelegatorShares = sdk.NewRat(amt) + } + validators[0].PoolShares = types.NewUnbondedShares(sdk.NewRat(amts[0])) + validators[1].PoolShares = types.NewUnbondedShares(sdk.NewRat(amts[1])) + validators[2].PoolShares = types.NewUnbondedShares(sdk.NewRat(amts[2])) + validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(amts[3])) + validators[4].PoolShares = types.NewBondedShares(sdk.NewRat(amts[4])) + for i := range amts { + keeper.UpdateValidator(ctx, validators[i]) + } + val0, found := keeper.GetValidator(ctx, Addrs[0]) + require.True(t, found) + val1, found := keeper.GetValidator(ctx, Addrs[1]) + require.True(t, found) + val2, found := keeper.GetValidator(ctx, Addrs[2]) + require.True(t, found) + val3, found := keeper.GetValidator(ctx, Addrs[3]) + require.True(t, found) + val4, found := keeper.GetValidator(ctx, Addrs[4]) + require.True(t, found) + require.Equal(t, sdk.Unbonded, val0.Status()) + require.Equal(t, sdk.Unbonded, val1.Status()) + require.Equal(t, sdk.Unbonded, val2.Status()) + require.Equal(t, sdk.Bonded, val3.Status()) + require.Equal(t, sdk.Bonded, val4.Status()) + + // first make sure everything made it in to the gotValidator group + resValidators := keeper.GetValidatorsByPower(ctx) + assert.Equal(t, n, len(resValidators)) + assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators) + assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators) + assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators) + assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators) + assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators) +} + +// TODO separate out into multiple tests +func TestGetValidatorsEdgeCases(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + var found bool + + // now 2 max resValidators + params := keeper.GetParams(ctx) + nMax := uint16(2) + params.MaxValidators = nMax + keeper.SetParams(ctx, params) + + // initialize some validators into the state + amts := []int64{0, 100, 400, 400} + var validators [4]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + validators[i] = keeper.UpdateValidator(ctx, validators[i]) + } + for i := range amts { + validators[i], found = keeper.GetValidator(ctx, validators[i].Owner) + require.True(t, found) + } + resValidators := keeper.GetValidatorsByPower(ctx) + require.Equal(t, nMax, uint16(len(resValidators))) + assert.True(ValEq(t, validators[2], resValidators[0])) + assert.True(ValEq(t, validators[3], resValidators[1])) + + pool := keeper.GetPool(ctx) + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 500) + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, nMax, uint16(len(resValidators))) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) + + // A validator which leaves the gotValidator set due to a decrease in voting power, + // then increases to the original voting power, does not get its spot back in the + // case of a tie. + + // validator 3 enters bonded validator set + ctx = ctx.WithBlockHeight(40) + + validators[3], found = keeper.GetValidator(ctx, validators[3].Owner) + require.True(t, found) + validators[3], pool, _ = validators[3].AddTokensFromDel(pool, 1) + keeper.SetPool(ctx, pool) + validators[3] = keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, nMax, uint16(len(resValidators))) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[3], resValidators[1])) + + // validator 3 kicked out temporarily + validators[3], pool, _ = validators[3].RemoveDelShares(pool, sdk.NewRat(201)) + keeper.SetPool(ctx, pool) + validators[3] = keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, nMax, uint16(len(resValidators))) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) + + // validator 4 does not get spot back + validators[3], pool, _ = validators[3].AddTokensFromDel(pool, 200) + keeper.SetPool(ctx, pool) + validators[3] = keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, nMax, uint16(len(resValidators))) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) + validator, exists := keeper.GetValidator(ctx, validators[3].Owner) + require.Equal(t, exists, true) + require.Equal(t, int64(40), validator.BondHeight) +} + +func TestValidatorBondHeight(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + // now 2 max resValidators + params := keeper.GetParams(ctx) + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + // initialize some validators into the state + pool := keeper.GetPool(ctx) + var validators [3]types.Validator + validators[0] = types.NewValidator(Addrs[0], PKs[0], types.Description{}) + validators[1] = types.NewValidator(Addrs[1], PKs[1], types.Description{}) + validators[2] = types.NewValidator(Addrs[2], PKs[2], types.Description{}) + + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 200) + validators[1], pool, _ = validators[1].AddTokensFromDel(pool, 100) + validators[2], pool, _ = validators[2].AddTokensFromDel(pool, 100) + + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + //////////////////////////////////////// + // If two validators both increase to the same voting power in the same block, + // the one with the first transaction should become bonded + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + resValidators := keeper.GetValidatorsByPower(ctx) + require.Equal(t, uint16(len(resValidators)), params.MaxValidators) + + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[1], resValidators[1])) + validators[1], pool, _ = validators[1].AddTokensFromDel(pool, 50) + validators[2], pool, _ = validators[2].AddTokensFromDel(pool, 50) + keeper.SetPool(ctx, pool) + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, params.MaxValidators, uint16(len(resValidators))) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) +} + +func TestFullValidatorSetPowerChange(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + params := keeper.GetParams(ctx) + max := 2 + params.MaxValidators = uint16(2) + keeper.SetParams(ctx, params) + + // initialize some validators into the state + amts := []int64{0, 100, 400, 400, 200} + var validators [5]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + keeper.UpdateValidator(ctx, validators[i]) + } + for i := range amts { + var found bool + validators[i], found = keeper.GetValidator(ctx, validators[i].Owner) + require.True(t, found) + } + assert.Equal(t, sdk.Unbonded, validators[0].Status()) + assert.Equal(t, sdk.Unbonded, validators[1].Status()) + assert.Equal(t, sdk.Bonded, validators[2].Status()) + assert.Equal(t, sdk.Bonded, validators[3].Status()) + assert.Equal(t, sdk.Unbonded, validators[4].Status()) + resValidators := keeper.GetValidatorsByPower(ctx) + assert.Equal(t, max, len(resValidators)) + assert.True(ValEq(t, validators[2], resValidators[0])) // in the order of txs + assert.True(ValEq(t, validators[3], resValidators[1])) + + // test a swap in voting power + pool := keeper.GetPool(ctx) + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 600) + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + resValidators = keeper.GetValidatorsByPower(ctx) + assert.Equal(t, max, len(resValidators)) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) +} + +// clear the tracked changes to the gotValidator set +func TestClearTendermintUpdates(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{100, 400, 200} + validators := make([]types.Validator, len(amts)) + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + keeper.UpdateValidator(ctx, validators[i]) + } + + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, len(amts), len(updates)) + keeper.ClearTendermintUpdates(ctx) + updates = keeper.GetTendermintUpdates(ctx) + require.Equal(t, 0, len(updates)) +} + +func TestGetTendermintUpdatesAllNone(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{10, 20} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + + // test from nothing to something + // tendermintUpdate set: {} -> {c1, c3} + require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + + updates := keeper.GetTendermintUpdates(ctx) + assert.Equal(t, 2, len(updates)) + assert.Equal(t, validators[0].ABCIValidator(), updates[0]) + assert.Equal(t, validators[1].ABCIValidator(), updates[1]) + + // test from something to nothing + // tendermintUpdate set: {} -> {c1, c2, c3, c4} + keeper.ClearTendermintUpdates(ctx) + require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + keeper.RemoveValidator(ctx, validators[0].Owner) + keeper.RemoveValidator(ctx, validators[1].Owner) + + updates = keeper.GetTendermintUpdates(ctx) + assert.Equal(t, 2, len(updates)) + assert.Equal(t, tmtypes.TM2PB.PubKey(validators[0].PubKey), updates[0].PubKey) + assert.Equal(t, tmtypes.TM2PB.PubKey(validators[1].PubKey), updates[1].PubKey) + assert.Equal(t, int64(0), updates[0].Power) + assert.Equal(t, int64(0), updates[1].Power) +} + +func TestGetTendermintUpdatesIdentical(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{10, 20} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // test identical, + // tendermintUpdate set: {} -> {} + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) +} + +func TestGetTendermintUpdatesSingleValueChange(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{10, 20} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // test single value change + // tendermintUpdate set: {} -> {c1'} + validators[0].PoolShares = types.NewBondedShares(sdk.NewRat(600)) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + + updates := keeper.GetTendermintUpdates(ctx) + + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[0].ABCIValidator(), updates[0]) +} + +func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{10, 20} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // test multiple value change + // tendermintUpdate set: {c1, c3} -> {c1', c3'} + pool := keeper.GetPool(ctx) + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 190) + validators[1], pool, _ = validators[1].AddTokensFromDel(pool, 80) + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 2, len(updates)) + require.Equal(t, validators[0].ABCIValidator(), updates[0]) + require.Equal(t, validators[1].ABCIValidator(), updates[1]) +} + +func TestGetTendermintUpdatesInserted(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{10, 20, 5, 15, 25} + var validators [5]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // test validtor added at the beginning + // tendermintUpdate set: {} -> {c0} + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[2].ABCIValidator(), updates[0]) + + // test validtor added at the beginning + // tendermintUpdate set: {} -> {c0} + keeper.ClearTendermintUpdates(ctx) + validators[3] = keeper.UpdateValidator(ctx, validators[3]) + updates = keeper.GetTendermintUpdates(ctx) + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[3].ABCIValidator(), updates[0]) + + // test validtor added at the end + // tendermintUpdate set: {} -> {c0} + keeper.ClearTendermintUpdates(ctx) + validators[4] = keeper.UpdateValidator(ctx, validators[4]) + updates = keeper.GetTendermintUpdates(ctx) + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[4].ABCIValidator(), updates[0]) +} + +func TestGetTendermintUpdatesWithCliffValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + params := types.DefaultParams() + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + amts := []int64{10, 20, 5} + var validators [5]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // test validator added at the end but not inserted in the valset + // tendermintUpdate set: {} -> {} + keeper.UpdateValidator(ctx, validators[2]) + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 0, len(updates)) + + // test validator change its power and become a gotValidator (pushing out an existing) + // tendermintUpdate set: {} -> {c0, c4} + keeper.ClearTendermintUpdates(ctx) + require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + pool := keeper.GetPool(ctx) + validators[2], pool, _ = validators[2].AddTokensFromDel(pool, 10) + keeper.SetPool(ctx, pool) + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + + updates = keeper.GetTendermintUpdates(ctx) + require.Equal(t, 2, len(updates), "%v", updates) + require.Equal(t, validators[0].ABCIValidatorZero(), updates[0]) + require.Equal(t, validators[2].ABCIValidator(), updates[1]) +} + +func TestGetTendermintUpdatesPowerDecrease(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{100, 100} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // check initial power + require.Equal(t, sdk.NewRat(100).RoundInt64(), validators[0].GetPower().RoundInt64()) + require.Equal(t, sdk.NewRat(100).RoundInt64(), validators[1].GetPower().RoundInt64()) + + // test multiple value change + // tendermintUpdate set: {c1, c3} -> {c1', c3'} + pool := keeper.GetPool(ctx) + validators[0], pool, _ = validators[0].RemoveDelShares(pool, sdk.NewRat(20)) + validators[1], pool, _ = validators[1].RemoveDelShares(pool, sdk.NewRat(30)) + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + + // power has changed + require.Equal(t, sdk.NewRat(80).RoundInt64(), validators[0].GetPower().RoundInt64()) + require.Equal(t, sdk.NewRat(70).RoundInt64(), validators[1].GetPower().RoundInt64()) + + // Tendermint updates should reflect power change + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 2, len(updates)) + require.Equal(t, validators[0].ABCIValidator(), updates[0]) + require.Equal(t, validators[1].ABCIValidator(), updates[1]) +} diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go deleted file mode 100644 index 20062f00e..000000000 --- a/x/stake/keeper_keys.go +++ /dev/null @@ -1,89 +0,0 @@ -package stake - -import ( - "encoding/binary" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" - crypto "github.com/tendermint/go-crypto" -) - -// TODO remove some of these prefixes once have working multistore - -//nolint -var ( - // Keys for store prefixes - ParamKey = []byte{0x00} // key for parameters relating to staking - PoolKey = []byte{0x01} // key for the staking pools - ValidatorsKey = []byte{0x02} // prefix for each key to a validator - ValidatorsByPubKeyIndexKey = []byte{0x03} // prefix for each key to a validator by pubkey - ValidatorsBondedKey = []byte{0x04} // prefix for each key to bonded/actively validating validators - ValidatorsByPowerKey = []byte{0x05} // prefix for each key to a validator sorted by power - ValidatorCliffKey = []byte{0x06} // key for block-local tx index - ValidatorPowerCliffKey = []byte{0x07} // key for block-local tx index - TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated - DelegationKey = []byte{0x09} // prefix for each key to a delegator's bond - IntraTxCounterKey = []byte{0x10} // key for block-local tx index -) - -const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch - -// get the key for the validator with address -func GetValidatorKey(ownerAddr sdk.Address) []byte { - return append(ValidatorsKey, ownerAddr.Bytes()...) -} - -// get the key for the validator with pubkey -func GetValidatorByPubKeyIndexKey(pubkey crypto.PubKey) []byte { - return append(ValidatorsByPubKeyIndexKey, pubkey.Bytes()...) -} - -// get the key for the current validator group, ordered like tendermint -func GetValidatorsBondedKey(pk crypto.PubKey) []byte { - addr := pk.Address() - return append(ValidatorsBondedKey, addr.Bytes()...) -} - -// get the key for the validator used in the power-store -func GetValidatorsByPowerKey(validator Validator, pool Pool) []byte { - - power := validator.EquivalentBondedShares(pool) - powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first) - - // TODO ensure that the key will be a readable string.. probably should add seperators and have - revokedBytes := make([]byte, 1) - if validator.Revoked { - revokedBytes[0] = byte(0x01) - } else { - revokedBytes[0] = byte(0x00) - } - // heightBytes and counterBytes represent strings like powerBytes does - heightBytes := make([]byte, binary.MaxVarintLen64) - binary.BigEndian.PutUint64(heightBytes, ^uint64(validator.BondHeight)) // invert height (older validators first) - counterBytes := make([]byte, 2) - binary.BigEndian.PutUint16(counterBytes, ^uint16(validator.BondIntraTxCounter)) // invert counter (first txns have priority) - return append(ValidatorsByPowerKey, - append(revokedBytes, - append(powerBytes, - append(heightBytes, - append(counterBytes, validator.Owner.Bytes()...)...)...)...)...) // TODO don't technically need to store owner -} - -// get the key for the accumulated update validators -func GetTendermintUpdatesKey(ownerAddr sdk.Address) []byte { - return append(TendermintUpdatesKey, ownerAddr.Bytes()...) -} - -// get the key for delegator bond with validator -func GetDelegationKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte { - return append(GetDelegationsKey(delegatorAddr, cdc), validatorAddr.Bytes()...) -} - -// get the prefix for a delegator for all validators -func GetDelegationsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { - res, err := cdc.MarshalBinary(&delegatorAddr) - if err != nil { - panic(err) - } - return append(DelegationKey, res...) -} diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go deleted file mode 100644 index 76bfc507a..000000000 --- a/x/stake/keeper_test.go +++ /dev/null @@ -1,792 +0,0 @@ -package stake - -import ( - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - tmtypes "github.com/tendermint/tendermint/types" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var ( - addrDels = []sdk.Address{ - addrs[0], - addrs[1], - } - addrVals = []sdk.Address{ - addrs[2], - addrs[3], - addrs[4], - addrs[5], - addrs[6], - } -) - -func TestUpdateValidatorByPowerIndex(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - pool := keeper.GetPool(ctx) - - // create a random pool - pool.BondedTokens = 1234 - pool.BondedShares = sdk.NewRat(124) - pool.UnbondingTokens = 13934 - pool.UnbondingShares = sdk.NewRat(145) - pool.UnbondedTokens = 154 - pool.UnbondedShares = sdk.NewRat(1333) - keeper.setPool(ctx, pool) - - // add a validator - validator := NewValidator(addrVals[0], pks[0], Description{}) - validator, pool, delSharesCreated := validator.addTokensFromDel(pool, 100) - require.Equal(t, sdk.Unbonded, validator.Status()) - assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate()) - keeper.setPool(ctx, pool) - keeper.updateValidator(ctx, validator) - validator, found := keeper.GetValidator(ctx, addrVals[0]) - require.True(t, found) - assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate(), "\nvalidator %v\npool %v", validator, pool) - - pool = keeper.GetPool(ctx) - power := GetValidatorsByPowerKey(validator, pool) - assert.True(t, keeper.validatorByPowerIndexExists(ctx, power)) - - // burn half the delegator shares - validator, pool, burned := validator.removeDelShares(pool, delSharesCreated.Quo(sdk.NewRat(2))) - assert.Equal(t, int64(50), burned) - keeper.setPool(ctx, pool) // update the pool - keeper.updateValidator(ctx, validator) // update the validator, possibly kicking it out - assert.False(t, keeper.validatorByPowerIndexExists(ctx, power)) - - pool = keeper.GetPool(ctx) - validator, found = keeper.GetValidator(ctx, addrVals[0]) - power = GetValidatorsByPowerKey(validator, pool) - assert.True(t, keeper.validatorByPowerIndexExists(ctx, power)) -} - -func TestSetValidator(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - pool := keeper.GetPool(ctx) - - // test how the validator is set from a purely unbonbed pool - validator := NewValidator(addrVals[0], pks[0], Description{}) - validator, pool, _ = validator.addTokensFromDel(pool, 10) - require.Equal(t, sdk.Unbonded, validator.Status()) - assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Unbonded())) - assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares)) - keeper.setPool(ctx, pool) - keeper.updateValidator(ctx, validator) - - // after the save the validator should be bonded - validator, found := keeper.GetValidator(ctx, addrVals[0]) - require.True(t, found) - require.Equal(t, sdk.Bonded, validator.Status()) - assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) - assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares)) - - // Check each store for being saved - resVal, found := keeper.GetValidator(ctx, addrVals[0]) - assert.True(ValEq(t, validator, resVal)) - - resVals := keeper.GetValidatorsBonded(ctx) - require.Equal(t, 1, len(resVals)) - assert.True(ValEq(t, validator, resVals[0])) - - resVals = keeper.GetValidatorsByPower(ctx) - require.Equal(t, 1, len(resVals)) - assert.True(ValEq(t, validator, resVals[0])) - - updates := keeper.getTendermintUpdates(ctx) - require.Equal(t, 1, len(updates)) - assert.Equal(t, validator.abciValidator(keeper.cdc), updates[0]) - -} - -// This function tests updateValidator, GetValidator, GetValidatorsBonded, removeValidator -func TestValidatorBasics(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - pool := keeper.GetPool(ctx) - - //construct the validators - var validators [3]Validator - amts := []int64{9, 8, 7} - for i, amt := range amts { - validators[i] = NewValidator(addrVals[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.ZeroRat()) - validators[i].addTokensFromDel(pool, amt) - } - - // check the empty keeper first - _, found := keeper.GetValidator(ctx, addrVals[0]) - assert.False(t, found) - resVals := keeper.GetValidatorsBonded(ctx) - assert.Zero(t, len(resVals)) - - // set and retrieve a record - validators[0] = keeper.updateValidator(ctx, validators[0]) - resVal, found := keeper.GetValidator(ctx, addrVals[0]) - require.True(t, found) - assert.True(ValEq(t, validators[0], resVal)) - - resVals = keeper.GetValidatorsBonded(ctx) - require.Equal(t, 1, len(resVals)) - assert.True(ValEq(t, validators[0], resVals[0])) - - // modify a records, save, and retrieve - validators[0].PoolShares = NewBondedShares(sdk.NewRat(10)) - validators[0].DelegatorShares = sdk.NewRat(10) - validators[0] = keeper.updateValidator(ctx, validators[0]) - resVal, found = keeper.GetValidator(ctx, addrVals[0]) - require.True(t, found) - assert.True(ValEq(t, validators[0], resVal)) - - resVals = keeper.GetValidatorsBonded(ctx) - require.Equal(t, 1, len(resVals)) - assert.True(ValEq(t, validators[0], resVals[0])) - - // add other validators - validators[1] = keeper.updateValidator(ctx, validators[1]) - validators[2] = keeper.updateValidator(ctx, validators[2]) - resVal, found = keeper.GetValidator(ctx, addrVals[1]) - require.True(t, found) - assert.True(ValEq(t, validators[1], resVal)) - resVal, found = keeper.GetValidator(ctx, addrVals[2]) - require.True(t, found) - assert.True(ValEq(t, validators[2], resVal)) - - resVals = keeper.GetValidatorsBonded(ctx) - require.Equal(t, 3, len(resVals)) - assert.True(ValEq(t, validators[0], resVals[2])) // order doesn't matter here - assert.True(ValEq(t, validators[1], resVals[1])) - assert.True(ValEq(t, validators[2], resVals[0])) - - // remove a record - keeper.removeValidator(ctx, validators[1].Owner) - _, found = keeper.GetValidator(ctx, addrVals[1]) - assert.False(t, found) -} - -// test how the validators are sorted, tests GetValidatorsByPower -func GetValidatorSortingUnmixed(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - // initialize some validators into the state - amts := []int64{0, 100, 1, 400, 200} - n := len(amts) - var validators [5]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewBondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - keeper.updateValidator(ctx, validators[i]) - } - - // first make sure everything made it in to the gotValidator group - resValidators := keeper.GetValidatorsByPower(ctx) - require.Equal(t, n, len(resValidators)) - assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators) - assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators) - assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators) - assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators) - assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators) - - // test a basic increase in voting power - validators[3].PoolShares = NewBondedShares(sdk.NewRat(500)) - keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, len(resValidators), n) - assert.True(ValEq(t, validators[3], resValidators[0])) - - // test a decrease in voting power - validators[3].PoolShares = NewBondedShares(sdk.NewRat(300)) - keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, len(resValidators), n) - assert.True(ValEq(t, validators[3], resValidators[0])) - assert.True(ValEq(t, validators[4], resValidators[1])) - - // test equal voting power, different age - validators[3].PoolShares = NewBondedShares(sdk.NewRat(200)) - ctx = ctx.WithBlockHeight(10) - keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, len(resValidators), n) - assert.True(ValEq(t, validators[3], resValidators[0])) - assert.True(ValEq(t, validators[4], resValidators[1])) - assert.Equal(t, int64(0), resValidators[0].BondHeight, "%v", resValidators) - assert.Equal(t, int64(0), resValidators[1].BondHeight, "%v", resValidators) - - // no change in voting power - no change in sort - ctx = ctx.WithBlockHeight(20) - keeper.updateValidator(ctx, validators[4]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, len(resValidators), n) - assert.True(ValEq(t, validators[3], resValidators[0])) - assert.True(ValEq(t, validators[4], resValidators[1])) - - // change in voting power of both validators, both still in v-set, no age change - validators[3].PoolShares = NewBondedShares(sdk.NewRat(300)) - validators[4].PoolShares = NewBondedShares(sdk.NewRat(300)) - keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, len(resValidators), n) - ctx = ctx.WithBlockHeight(30) - keeper.updateValidator(ctx, validators[4]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, len(resValidators), n, "%v", resValidators) - assert.True(ValEq(t, validators[3], resValidators[0])) - assert.True(ValEq(t, validators[4], resValidators[1])) -} - -func GetValidatorSortingMixed(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - // now 2 max resValidators - params := keeper.GetParams(ctx) - params.MaxValidators = 2 - keeper.setParams(ctx, params) - - // initialize some validators into the state - amts := []int64{0, 100, 1, 400, 200} - - n := len(amts) - var validators [5]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(amts[0])) - validators[1].PoolShares = NewUnbondedShares(sdk.NewRat(amts[1])) - validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(amts[2])) - validators[3].PoolShares = NewBondedShares(sdk.NewRat(amts[3])) - validators[4].PoolShares = NewBondedShares(sdk.NewRat(amts[4])) - for i := range amts { - keeper.updateValidator(ctx, validators[i]) - } - val0, found := keeper.GetValidator(ctx, addrs[0]) - require.True(t, found) - val1, found := keeper.GetValidator(ctx, addrs[1]) - require.True(t, found) - val2, found := keeper.GetValidator(ctx, addrs[2]) - require.True(t, found) - val3, found := keeper.GetValidator(ctx, addrs[3]) - require.True(t, found) - val4, found := keeper.GetValidator(ctx, addrs[4]) - require.True(t, found) - assert.Equal(t, sdk.Unbonded, val0.Status()) - assert.Equal(t, sdk.Unbonded, val1.Status()) - assert.Equal(t, sdk.Unbonded, val2.Status()) - assert.Equal(t, sdk.Bonded, val3.Status()) - assert.Equal(t, sdk.Bonded, val4.Status()) - - // first make sure everything made it in to the gotValidator group - resValidators := keeper.GetValidatorsByPower(ctx) - require.Equal(t, n, len(resValidators)) - assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators) - assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators) - assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators) - assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators) - assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators) -} - -// TODO seperate out into multiple tests -func TestGetValidatorsEdgeCases(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - var found bool - - // now 2 max resValidators - params := keeper.GetParams(ctx) - nMax := uint16(2) - params.MaxValidators = nMax - keeper.setParams(ctx, params) - - // initialize some validators into the state - amts := []int64{0, 100, 400, 400} - var validators [4]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - validators[i] = keeper.updateValidator(ctx, validators[i]) - } - for i := range amts { - validators[i], found = keeper.GetValidator(ctx, validators[i].Owner) - require.True(t, found) - } - resValidators := keeper.GetValidatorsByPower(ctx) - require.Equal(t, nMax, uint16(len(resValidators))) - assert.True(ValEq(t, validators[2], resValidators[0])) - assert.True(ValEq(t, validators[3], resValidators[1])) - - validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(500)) - validators[0] = keeper.updateValidator(ctx, validators[0]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, nMax, uint16(len(resValidators))) - assert.True(ValEq(t, validators[0], resValidators[0])) - assert.True(ValEq(t, validators[2], resValidators[1])) - - // A validator which leaves the gotValidator set due to a decrease in voting power, - // then increases to the original voting power, does not get its spot back in the - // case of a tie. - - // validator 3 enters bonded validator set - ctx = ctx.WithBlockHeight(40) - - validators[3], found = keeper.GetValidator(ctx, validators[3].Owner) - require.True(t, found) - validators[3].PoolShares = NewUnbondedShares(sdk.NewRat(401)) - validators[3] = keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, nMax, uint16(len(resValidators))) - assert.True(ValEq(t, validators[0], resValidators[0])) - assert.True(ValEq(t, validators[3], resValidators[1])) - - // validator 3 kicked out temporarily - validators[3].PoolShares = NewBondedShares(sdk.NewRat(200)) - validators[3] = keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, nMax, uint16(len(resValidators))) - assert.True(ValEq(t, validators[0], resValidators[0])) - assert.True(ValEq(t, validators[2], resValidators[1])) - - // validator 4 does not get spot back - validators[3].PoolShares = NewBondedShares(sdk.NewRat(400)) - validators[3] = keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, nMax, uint16(len(resValidators))) - assert.True(ValEq(t, validators[0], resValidators[0])) - assert.True(ValEq(t, validators[2], resValidators[1])) - validator, exists := keeper.GetValidator(ctx, validators[3].Owner) - require.Equal(t, exists, true) - require.Equal(t, int64(40), validator.BondHeight) -} - -func TestValidatorBondHeight(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - // now 2 max resValidators - params := keeper.GetParams(ctx) - params.MaxValidators = 2 - keeper.setParams(ctx, params) - - // initialize some validators into the state - var validators [3]Validator - validators[0] = NewValidator(addrs[0], pks[0], Description{}) - validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(200)) - validators[0].DelegatorShares = sdk.NewRat(200) - validators[1] = NewValidator(addrs[1], pks[1], Description{}) - validators[1].PoolShares = NewUnbondedShares(sdk.NewRat(100)) - validators[1].DelegatorShares = sdk.NewRat(100) - validators[2] = NewValidator(addrs[2], pks[2], Description{}) - validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(100)) - validators[2].DelegatorShares = sdk.NewRat(100) - - validators[0] = keeper.updateValidator(ctx, validators[0]) - //////////////////////////////////////// - // If two validators both increase to the same voting power in the same block, - // the one with the first transaction should become bonded - validators[1] = keeper.updateValidator(ctx, validators[1]) - validators[2] = keeper.updateValidator(ctx, validators[2]) - resValidators := keeper.GetValidatorsByPower(ctx) - require.Equal(t, uint16(len(resValidators)), params.MaxValidators) - - assert.True(ValEq(t, validators[0], resValidators[0])) - assert.True(ValEq(t, validators[1], resValidators[1])) - validators[1].PoolShares = NewUnbondedShares(sdk.NewRat(150)) - validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(150)) - validators[2] = keeper.updateValidator(ctx, validators[2]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, params.MaxValidators, uint16(len(resValidators))) - validators[1] = keeper.updateValidator(ctx, validators[1]) - assert.True(ValEq(t, validators[0], resValidators[0])) - assert.True(ValEq(t, validators[2], resValidators[1])) -} - -func TestFullValidatorSetPowerChange(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - params := keeper.GetParams(ctx) - max := 2 - params.MaxValidators = uint16(2) - keeper.setParams(ctx, params) - - // initialize some validators into the state - amts := []int64{0, 100, 400, 400, 200} - var validators [5]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - keeper.updateValidator(ctx, validators[i]) - } - for i := range amts { - var found bool - validators[i], found = keeper.GetValidator(ctx, validators[i].Owner) - require.True(t, found) - } - assert.Equal(t, sdk.Unbonded, validators[0].Status()) - assert.Equal(t, sdk.Unbonded, validators[1].Status()) - assert.Equal(t, sdk.Bonded, validators[2].Status()) - assert.Equal(t, sdk.Bonded, validators[3].Status()) - assert.Equal(t, sdk.Unbonded, validators[4].Status()) - resValidators := keeper.GetValidatorsByPower(ctx) - require.Equal(t, max, len(resValidators)) - assert.True(ValEq(t, validators[2], resValidators[0])) // in the order of txs - assert.True(ValEq(t, validators[3], resValidators[1])) - - // test a swap in voting power - validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(600)) - validators[0] = keeper.updateValidator(ctx, validators[0]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, max, len(resValidators)) - assert.True(ValEq(t, validators[0], resValidators[0])) - assert.True(ValEq(t, validators[2], resValidators[1])) -} - -// clear the tracked changes to the gotValidator set -func TestClearTendermintUpdates(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - amts := []int64{100, 400, 200} - validators := make([]Validator, len(amts)) - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - keeper.updateValidator(ctx, validators[i]) - } - - updates := keeper.getTendermintUpdates(ctx) - assert.Equal(t, len(amts), len(updates)) - keeper.clearTendermintUpdates(ctx) - updates = keeper.getTendermintUpdates(ctx) - assert.Equal(t, 0, len(updates)) -} - -func TestGetTendermintUpdatesAllNone(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - amts := []int64{10, 20} - var validators [2]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - - // test from nothing to something - // tendermintUpdate set: {} -> {c1, c3} - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - - updates := keeper.getTendermintUpdates(ctx) - require.Equal(t, 2, len(updates)) - assert.Equal(t, validators[0].abciValidator(keeper.cdc), updates[0]) - assert.Equal(t, validators[1].abciValidator(keeper.cdc), updates[1]) - - // test from something to nothing - // tendermintUpdate set: {} -> {c1, c2, c3, c4} - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - keeper.removeValidator(ctx, validators[0].Owner) - keeper.removeValidator(ctx, validators[1].Owner) - - updates = keeper.getTendermintUpdates(ctx) - require.Equal(t, 2, len(updates)) - assert.Equal(t, tmtypes.TM2PB.PubKey(validators[0].PubKey), updates[0].PubKey) - assert.Equal(t, tmtypes.TM2PB.PubKey(validators[1].PubKey), updates[1].PubKey) - assert.Equal(t, int64(0), updates[0].Power) - assert.Equal(t, int64(0), updates[1].Power) -} - -func TestGetTendermintUpdatesIdentical(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - amts := []int64{10, 20} - var validators [2]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - // test identical, - // tendermintUpdate set: {} -> {} - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) -} - -func TestGetTendermintUpdatesSingleValueChange(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - amts := []int64{10, 20} - var validators [2]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - // test single value change - // tendermintUpdate set: {} -> {c1'} - validators[0].PoolShares = NewBondedShares(sdk.NewRat(600)) - validators[0] = keeper.updateValidator(ctx, validators[0]) - - updates := keeper.getTendermintUpdates(ctx) - - require.Equal(t, 1, len(updates)) - assert.Equal(t, validators[0].abciValidator(keeper.cdc), updates[0]) -} - -func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - amts := []int64{10, 20} - var validators [2]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - // test multiple value change - // tendermintUpdate set: {c1, c3} -> {c1', c3'} - validators[0].PoolShares = NewBondedShares(sdk.NewRat(200)) - validators[1].PoolShares = NewBondedShares(sdk.NewRat(100)) - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - - updates := keeper.getTendermintUpdates(ctx) - require.Equal(t, 2, len(updates)) - require.Equal(t, validators[0].abciValidator(keeper.cdc), updates[0]) - require.Equal(t, validators[1].abciValidator(keeper.cdc), updates[1]) -} - -func TestGetTendermintUpdatesInserted(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - amts := []int64{10, 20, 5, 15, 25} - var validators [5]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - // test validtor added at the beginning - // tendermintUpdate set: {} -> {c0} - validators[2] = keeper.updateValidator(ctx, validators[2]) - updates := keeper.getTendermintUpdates(ctx) - require.Equal(t, 1, len(updates)) - require.Equal(t, validators[2].abciValidator(keeper.cdc), updates[0]) - - // test validtor added at the beginning - // tendermintUpdate set: {} -> {c0} - keeper.clearTendermintUpdates(ctx) - validators[3] = keeper.updateValidator(ctx, validators[3]) - updates = keeper.getTendermintUpdates(ctx) - require.Equal(t, 1, len(updates)) - require.Equal(t, validators[3].abciValidator(keeper.cdc), updates[0]) - - // test validtor added at the end - // tendermintUpdate set: {} -> {c0} - keeper.clearTendermintUpdates(ctx) - validators[4] = keeper.updateValidator(ctx, validators[4]) - updates = keeper.getTendermintUpdates(ctx) - require.Equal(t, 1, len(updates)) - require.Equal(t, validators[4].abciValidator(keeper.cdc), updates[0]) -} - -func TestGetTendermintUpdatesNotValidatorCliff(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - params := DefaultParams() - params.MaxValidators = 2 - keeper.setParams(ctx, params) - - amts := []int64{10, 20, 5} - var validators [5]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - // test validator added at the end but not inserted in the valset - // tendermintUpdate set: {} -> {} - keeper.updateValidator(ctx, validators[2]) - updates := keeper.getTendermintUpdates(ctx) - require.Equal(t, 0, len(updates)) - - // test validator change its power and become a gotValidator (pushing out an existing) - // tendermintUpdate set: {} -> {c0, c4} - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(15)) - validators[2] = keeper.updateValidator(ctx, validators[2]) - - updates = keeper.getTendermintUpdates(ctx) - require.Equal(t, 2, len(updates), "%v", updates) - require.Equal(t, validators[0].abciValidatorZero(keeper.cdc), updates[0]) - require.Equal(t, validators[2].abciValidator(keeper.cdc), updates[1]) -} - -// tests GetDelegation, GetDelegations, SetDelegation, removeDelegation, GetBonds -func TestBond(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - //construct the validators - amts := []int64{9, 8, 7} - var validators [3]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrVals[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - - // first add a validators[0] to delegate too - validators[0] = keeper.updateValidator(ctx, validators[0]) - - bond1to1 := Delegation{ - DelegatorAddr: addrDels[0], - ValidatorAddr: addrVals[0], - Shares: sdk.NewRat(9), - } - - // check the empty keeper first - _, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.False(t, found) - - // set and retrieve a record - keeper.setDelegation(ctx, bond1to1) - resBond, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.True(t, found) - assert.True(t, bond1to1.equal(resBond)) - - // modify a records, save, and retrieve - bond1to1.Shares = sdk.NewRat(99) - keeper.setDelegation(ctx, bond1to1) - resBond, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.True(t, found) - assert.True(t, bond1to1.equal(resBond)) - - // add some more records - validators[1] = keeper.updateValidator(ctx, validators[1]) - validators[2] = keeper.updateValidator(ctx, validators[2]) - bond1to2 := Delegation{addrDels[0], addrVals[1], sdk.NewRat(9), 0} - bond1to3 := Delegation{addrDels[0], addrVals[2], sdk.NewRat(9), 1} - bond2to1 := Delegation{addrDels[1], addrVals[0], sdk.NewRat(9), 2} - bond2to2 := Delegation{addrDels[1], addrVals[1], sdk.NewRat(9), 3} - bond2to3 := Delegation{addrDels[1], addrVals[2], sdk.NewRat(9), 4} - keeper.setDelegation(ctx, bond1to2) - keeper.setDelegation(ctx, bond1to3) - keeper.setDelegation(ctx, bond2to1) - keeper.setDelegation(ctx, bond2to2) - keeper.setDelegation(ctx, bond2to3) - - // test all bond retrieve capabilities - resBonds := keeper.GetDelegations(ctx, addrDels[0], 5) - require.Equal(t, 3, len(resBonds)) - assert.True(t, bond1to1.equal(resBonds[0])) - assert.True(t, bond1to2.equal(resBonds[1])) - assert.True(t, bond1to3.equal(resBonds[2])) - resBonds = keeper.GetDelegations(ctx, addrDels[0], 3) - require.Equal(t, 3, len(resBonds)) - resBonds = keeper.GetDelegations(ctx, addrDels[0], 2) - require.Equal(t, 2, len(resBonds)) - resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) - require.Equal(t, 3, len(resBonds)) - assert.True(t, bond2to1.equal(resBonds[0])) - assert.True(t, bond2to2.equal(resBonds[1])) - assert.True(t, bond2to3.equal(resBonds[2])) - allBonds := keeper.getAllDelegations(ctx) - require.Equal(t, 6, len(allBonds)) - assert.True(t, bond1to1.equal(allBonds[0])) - assert.True(t, bond1to2.equal(allBonds[1])) - assert.True(t, bond1to3.equal(allBonds[2])) - assert.True(t, bond2to1.equal(allBonds[3])) - assert.True(t, bond2to2.equal(allBonds[4])) - assert.True(t, bond2to3.equal(allBonds[5])) - - // delete a record - keeper.removeDelegation(ctx, bond2to3) - _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[2]) - assert.False(t, found) - resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) - require.Equal(t, 2, len(resBonds)) - assert.True(t, bond2to1.equal(resBonds[0])) - assert.True(t, bond2to2.equal(resBonds[1])) - - // delete all the records from delegator 2 - keeper.removeDelegation(ctx, bond2to1) - keeper.removeDelegation(ctx, bond2to2) - _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[0]) - assert.False(t, found) - _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[1]) - assert.False(t, found) - resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) - require.Equal(t, 0, len(resBonds)) -} - -func TestParams(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - expParams := DefaultParams() - - //check that the empty keeper loads the default - resParams := keeper.GetParams(ctx) - assert.True(t, expParams.equal(resParams)) - - //modify a params, save, and retrieve - expParams.MaxValidators = 777 - keeper.setParams(ctx, expParams) - resParams = keeper.GetParams(ctx) - assert.True(t, expParams.equal(resParams)) -} - -func TestPool(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - expPool := InitialPool() - - //check that the empty keeper loads the default - resPool := keeper.GetPool(ctx) - assert.True(t, expPool.equal(resPool)) - - //modify a params, save, and retrieve - expPool.BondedTokens = 777 - keeper.setPool(ctx, expPool) - resPool = keeper.GetPool(ctx) - assert.True(t, expPool.equal(resPool)) -} diff --git a/x/stake/msg.go b/x/stake/msg.go deleted file mode 100644 index a0922bb87..000000000 --- a/x/stake/msg.go +++ /dev/null @@ -1,243 +0,0 @@ -package stake - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - crypto "github.com/tendermint/go-crypto" -) - -// name to idetify transaction types -const MsgType = "stake" - -// XXX remove: think it makes more sense belonging with the Params so we can -// initialize at genesis - to allow for the same tests we should should make -// the ValidateBasic() function a return from an initializable function -// ValidateBasic(bondDenom string) function -const StakingToken = "steak" - -//Verify interface at compile time -var _, _, _, _ sdk.Msg = &MsgCreateValidator{}, &MsgEditValidator{}, &MsgDelegate{}, &MsgUnbond{} - -//______________________________________________________________________ - -// MsgCreateValidator - struct for unbonding transactions -type MsgCreateValidator struct { - Description - ValidatorAddr sdk.Address `json:"address"` - PubKey crypto.PubKey `json:"pubkey"` - Bond sdk.Coin `json:"bond"` -} - -func NewMsgCreateValidator(validatorAddr sdk.Address, pubkey crypto.PubKey, - bond sdk.Coin, description Description) MsgCreateValidator { - return MsgCreateValidator{ - Description: description, - ValidatorAddr: validatorAddr, - PubKey: pubkey, - Bond: bond, - } -} - -//nolint -func (msg MsgCreateValidator) Type() string { return MsgType } -func (msg MsgCreateValidator) GetSigners() []sdk.Address { - return []sdk.Address{msg.ValidatorAddr} -} - -// get the bytes for the message signer to sign on -func (msg MsgCreateValidator) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(struct { - Description - ValidatorAddr string `json:"address"` - PubKey string `json:"pubkey"` - Bond sdk.Coin `json:"bond"` - }{ - Description: msg.Description, - ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), - PubKey: sdk.MustBech32ifyValPub(msg.PubKey), - }) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgCreateValidator) ValidateBasic() sdk.Error { - if msg.ValidatorAddr == nil { - return ErrValidatorEmpty(DefaultCodespace) - } - if msg.Bond.Denom != StakingToken { - return ErrBadBondingDenom(DefaultCodespace) - } - if msg.Bond.Amount <= 0 { - return ErrBadBondingAmount(DefaultCodespace) - } - empty := Description{} - if msg.Description == empty { - return newError(DefaultCodespace, CodeInvalidInput, "description must be included") - } - return nil -} - -//______________________________________________________________________ - -// MsgEditValidator - struct for editing a validator -type MsgEditValidator struct { - Description - ValidatorAddr sdk.Address `json:"address"` -} - -func NewMsgEditValidator(validatorAddr sdk.Address, description Description) MsgEditValidator { - return MsgEditValidator{ - Description: description, - ValidatorAddr: validatorAddr, - } -} - -//nolint -func (msg MsgEditValidator) Type() string { return MsgType } -func (msg MsgEditValidator) GetSigners() []sdk.Address { - return []sdk.Address{msg.ValidatorAddr} -} - -// get the bytes for the message signer to sign on -func (msg MsgEditValidator) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(struct { - Description - ValidatorAddr string `json:"address"` - }{ - Description: msg.Description, - ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), - }) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgEditValidator) ValidateBasic() sdk.Error { - if msg.ValidatorAddr == nil { - return ErrValidatorEmpty(DefaultCodespace) - } - empty := Description{} - if msg.Description == empty { - return newError(DefaultCodespace, CodeInvalidInput, "Transaction must include some information to modify") - } - return nil -} - -//______________________________________________________________________ - -// MsgDelegate - struct for bonding transactions -type MsgDelegate struct { - DelegatorAddr sdk.Address `json:"delegator_addr"` - ValidatorAddr sdk.Address `json:"validator_addr"` - Bond sdk.Coin `json:"bond"` -} - -func NewMsgDelegate(delegatorAddr, validatorAddr sdk.Address, bond sdk.Coin) MsgDelegate { - return MsgDelegate{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - Bond: bond, - } -} - -//nolint -func (msg MsgDelegate) Type() string { return MsgType } -func (msg MsgDelegate) GetSigners() []sdk.Address { - return []sdk.Address{msg.DelegatorAddr} -} - -// get the bytes for the message signer to sign on -func (msg MsgDelegate) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(struct { - DelegatorAddr string `json:"delegator_addr"` - ValidatorAddr string `json:"validator_addr"` - Bond sdk.Coin `json:"bond"` - }{ - DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), - ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), - Bond: msg.Bond, - }) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgDelegate) ValidateBasic() sdk.Error { - if msg.DelegatorAddr == nil { - return ErrBadDelegatorAddr(DefaultCodespace) - } - if msg.ValidatorAddr == nil { - return ErrBadValidatorAddr(DefaultCodespace) - } - if msg.Bond.Denom != StakingToken { - return ErrBadBondingDenom(DefaultCodespace) - } - if msg.Bond.Amount <= 0 { - return ErrBadBondingAmount(DefaultCodespace) - } - return nil -} - -//______________________________________________________________________ - -// MsgUnbond - struct for unbonding transactions -type MsgUnbond struct { - DelegatorAddr sdk.Address `json:"delegator_addr"` - ValidatorAddr sdk.Address `json:"validator_addr"` - Shares string `json:"shares"` -} - -func NewMsgUnbond(delegatorAddr, validatorAddr sdk.Address, shares string) MsgUnbond { - return MsgUnbond{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - Shares: shares, - } -} - -//nolint -func (msg MsgUnbond) Type() string { return MsgType } -func (msg MsgUnbond) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} } - -// get the bytes for the message signer to sign on -func (msg MsgUnbond) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(struct { - DelegatorAddr string `json:"delegator_addr"` - ValidatorAddr string `json:"validator_addr"` - Shares string `json:"shares"` - }{ - DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), - ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), - Shares: msg.Shares, - }) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgUnbond) ValidateBasic() sdk.Error { - if msg.DelegatorAddr == nil { - return ErrBadDelegatorAddr(DefaultCodespace) - } - if msg.ValidatorAddr == nil { - return ErrBadValidatorAddr(DefaultCodespace) - } - if msg.Shares != "MAX" { - rat, err := sdk.NewRatFromDecimal(msg.Shares) - if err != nil { - return ErrBadShares(DefaultCodespace) - } - if rat.IsZero() || rat.LT(sdk.ZeroRat()) { - return ErrBadShares(DefaultCodespace) - } - } - return nil -} diff --git a/x/stake/msg_test.go b/x/stake/msg_test.go deleted file mode 100644 index 863613a03..000000000 --- a/x/stake/msg_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package stake - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - sdk "github.com/cosmos/cosmos-sdk/types" - crypto "github.com/tendermint/go-crypto" -) - -var ( - coinPos = sdk.Coin{"steak", 1000} - coinZero = sdk.Coin{"steak", 0} - coinNeg = sdk.Coin{"steak", -10000} - coinPosNotAtoms = sdk.Coin{"foo", 10000} - coinZeroNotAtoms = sdk.Coin{"foo", 0} - coinNegNotAtoms = sdk.Coin{"foo", -10000} -) - -// test ValidateBasic for MsgCreateValidator -func TestMsgCreateValidator(t *testing.T) { - tests := []struct { - name, moniker, identity, website, details string - validatorAddr sdk.Address - pubkey crypto.PubKey - bond sdk.Coin - expectPass bool - }{ - {"basic good", "a", "b", "c", "d", addrs[0], pks[0], coinPos, true}, - {"partial description", "", "", "c", "", addrs[0], pks[0], coinPos, true}, - {"empty description", "", "", "", "", addrs[0], pks[0], coinPos, false}, - {"empty address", "a", "b", "c", "d", emptyAddr, pks[0], coinPos, false}, - {"empty pubkey", "a", "b", "c", "d", addrs[0], emptyPubkey, coinPos, true}, - {"empty bond", "a", "b", "c", "d", addrs[0], pks[0], coinZero, false}, - {"negative bond", "a", "b", "c", "d", addrs[0], pks[0], coinNeg, false}, - {"negative bond", "a", "b", "c", "d", addrs[0], pks[0], coinNeg, false}, - {"wrong staking token", "a", "b", "c", "d", addrs[0], pks[0], coinPosNotAtoms, false}, - } - - for _, tc := range tests { - description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgCreateValidator(tc.validatorAddr, tc.pubkey, tc.bond, description) - if tc.expectPass { - assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// test ValidateBasic for MsgEditValidator -func TestMsgEditValidator(t *testing.T) { - tests := []struct { - name, moniker, identity, website, details string - validatorAddr sdk.Address - expectPass bool - }{ - {"basic good", "a", "b", "c", "d", addrs[0], true}, - {"partial description", "", "", "c", "", addrs[0], true}, - {"empty description", "", "", "", "", addrs[0], false}, - {"empty address", "a", "b", "c", "d", emptyAddr, false}, - } - - for _, tc := range tests { - description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgEditValidator(tc.validatorAddr, description) - if tc.expectPass { - assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// test ValidateBasic for MsgDelegate -func TestMsgDelegate(t *testing.T) { - tests := []struct { - name string - delegatorAddr sdk.Address - validatorAddr sdk.Address - bond sdk.Coin - expectPass bool - }{ - {"basic good", addrs[0], addrs[1], coinPos, true}, - {"self bond", addrs[0], addrs[0], coinPos, true}, - {"empty delegator", emptyAddr, addrs[0], coinPos, false}, - {"empty validator", addrs[0], emptyAddr, coinPos, false}, - {"empty bond", addrs[0], addrs[1], coinZero, false}, - {"negative bond", addrs[0], addrs[1], coinNeg, false}, - {"wrong staking token", addrs[0], addrs[1], coinPosNotAtoms, false}, - } - - for _, tc := range tests { - msg := NewMsgDelegate(tc.delegatorAddr, tc.validatorAddr, tc.bond) - if tc.expectPass { - assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// test ValidateBasic for MsgUnbond -func TestMsgUnbond(t *testing.T) { - tests := []struct { - name string - delegatorAddr sdk.Address - validatorAddr sdk.Address - shares string - expectPass bool - }{ - {"max unbond", addrs[0], addrs[1], "MAX", true}, - {"decimal unbond", addrs[0], addrs[1], "0.1", true}, - {"negative decimal unbond", addrs[0], addrs[1], "-0.1", false}, - {"zero unbond", addrs[0], addrs[1], "0.0", false}, - {"invalid decimal", addrs[0], addrs[0], "sunny", false}, - {"empty delegator", emptyAddr, addrs[0], "0.1", false}, - {"empty validator", addrs[0], emptyAddr, "0.1", false}, - } - - for _, tc := range tests { - msg := NewMsgUnbond(tc.delegatorAddr, tc.validatorAddr, tc.shares) - if tc.expectPass { - assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// TODO introduce with go-amino -//func TestSerializeMsg(t *testing.T) { - -//// make sure all types construct properly -//bondAmt := 1234321 -//bond := sdk.Coin{Denom: "atom", Amount: int64(bondAmt)} - -//tests := []struct { -//tx sdk.Msg -//}{ -//{NewMsgCreateValidator(addrs[0], pks[0], bond, Description{})}, -//{NewMsgEditValidator(addrs[0], Description{})}, -//{NewMsgDelegate(addrs[0], addrs[1], bond)}, -//{NewMsgUnbond(addrs[0], addrs[1], strconv.Itoa(bondAmt))}, -//} - -//for i, tc := range tests { -//var tx sdk.Tx -//bs := wire.BinaryBytes(tc.tx) -//err := wire.ReadBinaryBytes(bs, &tx) -//if assert.NoError(t, err, "%d", i) { -//assert.Equal(t, tc.tx, tx, "%d", i) -//} -//} -//} diff --git a/x/stake/pool_test.go b/x/stake/pool_test.go deleted file mode 100644 index 99cfa5a12..000000000 --- a/x/stake/pool_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package stake - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestBondedRatio(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - pool := keeper.GetPool(ctx) - pool.LooseUnbondedTokens = 1 - pool.BondedTokens = 2 - - // bonded pool / total supply - require.Equal(t, pool.bondedRatio(), sdk.NewRat(2).Quo(sdk.NewRat(3))) - - // avoids divide-by-zero - pool.LooseUnbondedTokens = 0 - pool.BondedTokens = 0 - require.Equal(t, pool.bondedRatio(), sdk.ZeroRat()) -} - -func TestBondedShareExRate(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - pool := keeper.GetPool(ctx) - pool.BondedTokens = 3 - pool.BondedShares = sdk.NewRat(10) - - // bonded pool / bonded shares - require.Equal(t, pool.bondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) - pool.BondedShares = sdk.ZeroRat() - - // avoids divide-by-zero - require.Equal(t, pool.bondedShareExRate(), sdk.OneRat()) -} - -func TestUnbondingShareExRate(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - pool := keeper.GetPool(ctx) - pool.UnbondingTokens = 3 - pool.UnbondingShares = sdk.NewRat(10) - - // unbonding pool / unbonding shares - require.Equal(t, pool.unbondingShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) - pool.UnbondingShares = sdk.ZeroRat() - - // avoids divide-by-zero - require.Equal(t, pool.unbondingShareExRate(), sdk.OneRat()) -} - -func TestUnbondedShareExRate(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - pool := keeper.GetPool(ctx) - pool.UnbondedTokens = 3 - pool.UnbondedShares = sdk.NewRat(10) - - // unbonded pool / unbonded shares - require.Equal(t, pool.unbondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) - pool.UnbondedShares = sdk.ZeroRat() - - // avoids divide-by-zero - require.Equal(t, pool.unbondedShareExRate(), sdk.OneRat()) -} - -func TestAddTokensBonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - poolA := keeper.GetPool(ctx) - assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat()) - poolB, sharesB := poolA.addTokensBonded(10) - assert.Equal(t, poolB.bondedShareExRate(), sdk.OneRat()) - - // correct changes to bonded shares and bonded pool - assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Add(sharesB.Amount)) - assert.Equal(t, poolB.BondedTokens, poolA.BondedTokens+10) - - // same number of bonded shares / tokens when exchange rate is one - assert.True(t, poolB.BondedShares.Equal(sdk.NewRat(poolB.BondedTokens))) -} - -func TestRemoveSharesBonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - poolA := keeper.GetPool(ctx) - assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat()) - poolB, tokensB := poolA.removeSharesBonded(sdk.NewRat(10)) - assert.Equal(t, poolB.bondedShareExRate(), sdk.OneRat()) - - // correct changes to bonded shares and bonded pool - assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Sub(sdk.NewRat(10))) - assert.Equal(t, poolB.BondedTokens, poolA.BondedTokens-tokensB) - - // same number of bonded shares / tokens when exchange rate is one - assert.True(t, poolB.BondedShares.Equal(sdk.NewRat(poolB.BondedTokens))) -} - -func TestAddTokensUnbonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - poolA := keeper.GetPool(ctx) - assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat()) - poolB, sharesB := poolA.addTokensUnbonded(10) - assert.Equal(t, poolB.unbondedShareExRate(), sdk.OneRat()) - - // correct changes to unbonded shares and unbonded pool - assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Add(sharesB.Amount)) - assert.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens+10) - - // same number of unbonded shares / tokens when exchange rate is one - assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedTokens))) -} - -func TestRemoveSharesUnbonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - poolA := keeper.GetPool(ctx) - assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat()) - poolB, tokensB := poolA.removeSharesUnbonded(sdk.NewRat(10)) - assert.Equal(t, poolB.unbondedShareExRate(), sdk.OneRat()) - - // correct changes to unbonded shares and bonded pool - assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Sub(sdk.NewRat(10))) - assert.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens-tokensB) - - // same number of unbonded shares / tokens when exchange rate is one - assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedTokens))) -} diff --git a/x/stake/shares.go b/x/stake/shares.go deleted file mode 100644 index e30fa3738..000000000 --- a/x/stake/shares.go +++ /dev/null @@ -1,134 +0,0 @@ -package stake - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// kind of shares -type PoolShareKind byte - -// pool shares held by a validator -type PoolShares struct { - Status sdk.BondStatus `json:"status"` - Amount sdk.Rat `json:"amount"` // total shares of type ShareKind -} - -// only the vitals - does not check bond height of IntraTxCounter -func (s PoolShares) Equal(s2 PoolShares) bool { - return s.Status == s2.Status && - s.Amount.Equal(s2.Amount) -} - -func NewUnbondedShares(amount sdk.Rat) PoolShares { - return PoolShares{ - Status: sdk.Unbonded, - Amount: amount, - } -} - -func NewUnbondingShares(amount sdk.Rat) PoolShares { - return PoolShares{ - Status: sdk.Unbonding, - Amount: amount, - } -} - -func NewBondedShares(amount sdk.Rat) PoolShares { - return PoolShares{ - Status: sdk.Bonded, - Amount: amount, - } -} - -//_________________________________________________________________________________________________________ - -// amount of unbonded shares -func (s PoolShares) Unbonded() sdk.Rat { - if s.Status == sdk.Unbonded { - return s.Amount - } - return sdk.ZeroRat() -} - -// amount of unbonding shares -func (s PoolShares) Unbonding() sdk.Rat { - if s.Status == sdk.Unbonding { - return s.Amount - } - return sdk.ZeroRat() -} - -// amount of bonded shares -func (s PoolShares) Bonded() sdk.Rat { - if s.Status == sdk.Bonded { - return s.Amount - } - return sdk.ZeroRat() -} - -//_________________________________________________________________________________________________________ - -// equivalent amount of shares if the shares were unbonded -func (s PoolShares) ToUnbonded(p Pool) PoolShares { - var amount sdk.Rat - switch s.Status { - case sdk.Bonded: - exRate := p.bondedShareExRate().Quo(p.unbondedShareExRate()) // (tok/bondedshr)/(tok/unbondedshr) = unbondedshr/bondedshr - amount = s.Amount.Mul(exRate) // bondedshr*unbondedshr/bondedshr = unbondedshr - case sdk.Unbonding: - exRate := p.unbondingShareExRate().Quo(p.unbondedShareExRate()) // (tok/unbondingshr)/(tok/unbondedshr) = unbondedshr/unbondingshr - amount = s.Amount.Mul(exRate) // unbondingshr*unbondedshr/unbondingshr = unbondedshr - case sdk.Unbonded: - amount = s.Amount - } - return NewUnbondedShares(amount) -} - -// equivalent amount of shares if the shares were unbonding -func (s PoolShares) ToUnbonding(p Pool) PoolShares { - var amount sdk.Rat - switch s.Status { - case sdk.Bonded: - exRate := p.bondedShareExRate().Quo(p.unbondingShareExRate()) // (tok/bondedshr)/(tok/unbondingshr) = unbondingshr/bondedshr - amount = s.Amount.Mul(exRate) // bondedshr*unbondingshr/bondedshr = unbondingshr - case sdk.Unbonding: - amount = s.Amount - case sdk.Unbonded: - exRate := p.unbondedShareExRate().Quo(p.unbondingShareExRate()) // (tok/unbondedshr)/(tok/unbondingshr) = unbondingshr/unbondedshr - amount = s.Amount.Mul(exRate) // unbondedshr*unbondingshr/unbondedshr = unbondingshr - } - return NewUnbondingShares(amount) -} - -// equivalent amount of shares if the shares were bonded -func (s PoolShares) ToBonded(p Pool) PoolShares { - var amount sdk.Rat - switch s.Status { - case sdk.Bonded: - amount = s.Amount - case sdk.Unbonding: - exRate := p.unbondingShareExRate().Quo(p.bondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr - amount = s.Amount.Mul(exRate) // ubshr*bshr/ubshr = bshr - case sdk.Unbonded: - exRate := p.unbondedShareExRate().Quo(p.bondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr - amount = s.Amount.Mul(exRate) // ubshr*bshr/ubshr = bshr - } - return NewUnbondedShares(amount) -} - -//_________________________________________________________________________________________________________ - -// TODO better tests -// get the equivalent amount of tokens contained by the shares -func (s PoolShares) Tokens(p Pool) sdk.Rat { - switch s.Status { - case sdk.Bonded: - return p.bondedShareExRate().Mul(s.Amount) // (tokens/shares) * shares - case sdk.Unbonding: - return p.unbondingShareExRate().Mul(s.Amount) - case sdk.Unbonded: - return p.unbondedShareExRate().Mul(s.Amount) - default: - panic("unknown share kind") - } -} diff --git a/x/stake/stake.go b/x/stake/stake.go new file mode 100644 index 000000000..410856489 --- /dev/null +++ b/x/stake/stake.go @@ -0,0 +1,143 @@ +// nolint +package stake + +import ( + "github.com/cosmos/cosmos-sdk/x/stake/keeper" + "github.com/cosmos/cosmos-sdk/x/stake/tags" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +type ( + Keeper = keeper.Keeper + Validator = types.Validator + Description = types.Description + Delegation = types.Delegation + UnbondingDelegation = types.UnbondingDelegation + Redelegation = types.Redelegation + Params = types.Params + Pool = types.Pool + PoolShares = types.PoolShares + MsgCreateValidator = types.MsgCreateValidator + MsgEditValidator = types.MsgEditValidator + MsgDelegate = types.MsgDelegate + MsgBeginUnbonding = types.MsgBeginUnbonding + MsgCompleteUnbonding = types.MsgCompleteUnbonding + MsgBeginRedelegate = types.MsgBeginRedelegate + MsgCompleteRedelegate = types.MsgCompleteRedelegate + GenesisState = types.GenesisState +) + +var ( + NewKeeper = keeper.NewKeeper + + GetValidatorKey = keeper.GetValidatorKey + GetValidatorByPubKeyIndexKey = keeper.GetValidatorByPubKeyIndexKey + GetValidatorsBondedIndexKey = keeper.GetValidatorsBondedIndexKey + GetValidatorsByPowerIndexKey = keeper.GetValidatorsByPowerIndexKey + GetTendermintUpdatesKey = keeper.GetTendermintUpdatesKey + GetDelegationKey = keeper.GetDelegationKey + GetDelegationsKey = keeper.GetDelegationsKey + ParamKey = keeper.ParamKey + PoolKey = keeper.PoolKey + ValidatorsKey = keeper.ValidatorsKey + ValidatorsByPubKeyIndexKey = keeper.ValidatorsByPubKeyIndexKey + ValidatorsBondedIndexKey = keeper.ValidatorsBondedIndexKey + ValidatorsByPowerIndexKey = keeper.ValidatorsByPowerIndexKey + ValidatorCliffIndexKey = keeper.ValidatorCliffIndexKey + ValidatorPowerCliffKey = keeper.ValidatorPowerCliffKey + TendermintUpdatesKey = keeper.TendermintUpdatesKey + DelegationKey = keeper.DelegationKey + IntraTxCounterKey = keeper.IntraTxCounterKey + GetUBDKey = keeper.GetUBDKey + GetUBDByValIndexKey = keeper.GetUBDByValIndexKey + GetUBDsKey = keeper.GetUBDsKey + GetUBDsByValIndexKey = keeper.GetUBDsByValIndexKey + GetREDKey = keeper.GetREDKey + GetREDByValSrcIndexKey = keeper.GetREDByValSrcIndexKey + GetREDByValDstIndexKey = keeper.GetREDByValDstIndexKey + GetREDsKey = keeper.GetREDsKey + GetREDsFromValSrcIndexKey = keeper.GetREDsFromValSrcIndexKey + GetREDsToValDstIndexKey = keeper.GetREDsToValDstIndexKey + GetREDsByDelToValDstIndexKey = keeper.GetREDsByDelToValDstIndexKey + + DefaultParams = types.DefaultParams + InitialPool = types.InitialPool + NewUnbondedShares = types.NewUnbondedShares + NewUnbondingShares = types.NewUnbondingShares + NewBondedShares = types.NewBondedShares + NewValidator = types.NewValidator + NewDescription = types.NewDescription + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + RegisterWire = types.RegisterWire + + NewMsgCreateValidator = types.NewMsgCreateValidator + NewMsgCreateValidatorOnBehalfOf = types.NewMsgCreateValidatorOnBehalfOf + NewMsgEditValidator = types.NewMsgEditValidator + NewMsgDelegate = types.NewMsgDelegate + NewMsgBeginUnbonding = types.NewMsgBeginUnbonding + NewMsgCompleteUnbonding = types.NewMsgCompleteUnbonding + NewMsgBeginRedelegate = types.NewMsgBeginRedelegate + NewMsgCompleteRedelegate = types.NewMsgCompleteRedelegate +) + +const ( + DefaultCodespace = types.DefaultCodespace + CodeInvalidValidator = types.CodeInvalidValidator + CodeInvalidDelegation = types.CodeInvalidDelegation + CodeInvalidInput = types.CodeInvalidInput + CodeValidatorJailed = types.CodeValidatorJailed + CodeUnauthorized = types.CodeUnauthorized + CodeInternal = types.CodeInternal + CodeUnknownRequest = types.CodeUnknownRequest +) + +var ( + ErrNilValidatorAddr = types.ErrNilValidatorAddr + ErrNoValidatorFound = types.ErrNoValidatorFound + ErrValidatorOwnerExists = types.ErrValidatorOwnerExists + ErrValidatorPubKeyExists = types.ErrValidatorPubKeyExists + ErrValidatorRevoked = types.ErrValidatorRevoked + ErrBadRemoveValidator = types.ErrBadRemoveValidator + ErrDescriptionLength = types.ErrDescriptionLength + ErrCommissionNegative = types.ErrCommissionNegative + ErrCommissionHuge = types.ErrCommissionHuge + + ErrNilDelegatorAddr = types.ErrNilDelegatorAddr + ErrBadDenom = types.ErrBadDenom + ErrBadDelegationAmount = types.ErrBadDelegationAmount + ErrNoDelegation = types.ErrNoDelegation + ErrBadDelegatorAddr = types.ErrBadDelegatorAddr + ErrNoDelegatorForAddress = types.ErrNoDelegatorForAddress + ErrInsufficientShares = types.ErrInsufficientShares + ErrDelegationValidatorEmpty = types.ErrDelegationValidatorEmpty + ErrNotEnoughDelegationShares = types.ErrNotEnoughDelegationShares + ErrBadSharesAmount = types.ErrBadSharesAmount + ErrBadSharesPercent = types.ErrBadSharesPercent + + ErrNotMature = types.ErrNotMature + ErrNoUnbondingDelegation = types.ErrNoUnbondingDelegation + ErrNoRedelegation = types.ErrNoRedelegation + ErrBadRedelegationDst = types.ErrBadRedelegationDst + + ErrBothShareMsgsGiven = types.ErrBothShareMsgsGiven + ErrNeitherShareMsgsGiven = types.ErrNeitherShareMsgsGiven + ErrMissingSignature = types.ErrMissingSignature +) + +var ( + ActionCreateValidator = tags.ActionCreateValidator + ActionEditValidator = tags.ActionEditValidator + ActionDelegate = tags.ActionDelegate + ActionBeginUnbonding = tags.ActionBeginUnbonding + ActionCompleteUnbonding = tags.ActionCompleteUnbonding + ActionBeginRedelegation = tags.ActionBeginRedelegation + ActionCompleteRedelegation = tags.ActionCompleteRedelegation + + TagAction = tags.Action + TagSrcValidator = tags.SrcValidator + TagDstValidator = tags.DstValidator + TagDelegator = tags.Delegator + TagMoniker = tags.Moniker + TagIdentity = tags.Identity +) diff --git a/x/stake/tags/tags.go b/x/stake/tags/tags.go new file mode 100644 index 000000000..edb4eda07 --- /dev/null +++ b/x/stake/tags/tags.go @@ -0,0 +1,23 @@ +// nolint +package tags + +import ( + "github.com/cosmos/cosmos-sdk/types" +) + +var ( + ActionCreateValidator = []byte("create-validator") + ActionEditValidator = []byte("edit-validator") + ActionDelegate = []byte("delegate") + ActionBeginUnbonding = []byte("begin-unbonding") + ActionCompleteUnbonding = []byte("complete-unbonding") + ActionBeginRedelegation = []byte("begin-redelegation") + ActionCompleteRedelegation = []byte("complete-redelegation") + + Action = types.TagAction + SrcValidator = types.TagSrcValidator + DstValidator = types.TagDstValidator + Delegator = types.TagDelegator + Moniker = "moniker" + Identity = "Identity" +) diff --git a/x/stake/types/delegation.go b/x/stake/types/delegation.go new file mode 100644 index 000000000..2ceedd5f3 --- /dev/null +++ b/x/stake/types/delegation.go @@ -0,0 +1,279 @@ +package types + +import ( + "bytes" + "errors" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +// Delegation represents the bond with tokens held by an account. It is +// owned by one delegator, and is associated with the voting power of one +// pubKey. +type Delegation struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` + ValidatorAddr sdk.AccAddress `json:"validator_addr"` + Shares sdk.Rat `json:"shares"` + Height int64 `json:"height"` // Last height bond updated +} + +type delegationValue struct { + Shares sdk.Rat + Height int64 +} + +// return the delegation without fields contained within the key for the store +func MustMarshalDelegation(cdc *wire.Codec, delegation Delegation) []byte { + val := delegationValue{ + delegation.Shares, + delegation.Height, + } + return cdc.MustMarshalBinary(val) +} + +// return the delegation without fields contained within the key for the store +func MustUnmarshalDelegation(cdc *wire.Codec, key, value []byte) Delegation { + delegation, err := UnmarshalDelegation(cdc, key, value) + if err != nil { + panic(err) + } + return delegation +} + +// return the delegation without fields contained within the key for the store +func UnmarshalDelegation(cdc *wire.Codec, key, value []byte) (delegation Delegation, err error) { + var storeValue delegationValue + err = cdc.UnmarshalBinary(value, &storeValue) + if err != nil { + return + } + + addrs := key[1:] // remove prefix bytes + if len(addrs) != 2*sdk.AddrLen { + err = errors.New("unexpected key length") + return + } + delAddr := sdk.AccAddress(addrs[:sdk.AddrLen]) + valAddr := sdk.AccAddress(addrs[sdk.AddrLen:]) + + return Delegation{ + DelegatorAddr: delAddr, + ValidatorAddr: valAddr, + Shares: storeValue.Shares, + Height: storeValue.Height, + }, nil +} + +// nolint +func (d Delegation) Equal(d2 Delegation) bool { + return bytes.Equal(d.DelegatorAddr, d2.DelegatorAddr) && + bytes.Equal(d.ValidatorAddr, d2.ValidatorAddr) && + d.Height == d2.Height && + d.Shares.Equal(d2.Shares) +} + +// ensure fulfills the sdk validator types +var _ sdk.Delegation = Delegation{} + +// nolint - for sdk.Delegation +func (d Delegation) GetDelegator() sdk.AccAddress { return d.DelegatorAddr } +func (d Delegation) GetValidator() sdk.AccAddress { return d.ValidatorAddr } +func (d Delegation) GetBondShares() sdk.Rat { return d.Shares } + +// HumanReadableString returns a human readable string representation of a +// Delegation. An error is returned if the Delegation's delegator or validator +// addresses cannot be Bech32 encoded. +func (d Delegation) HumanReadableString() (string, error) { + resp := "Delegation \n" + resp += fmt.Sprintf("Delegator: %s\n", d.DelegatorAddr) + resp += fmt.Sprintf("Validator: %s\n", d.ValidatorAddr) + resp += fmt.Sprintf("Shares: %s", d.Shares.String()) + resp += fmt.Sprintf("Height: %d", d.Height) + + return resp, nil +} + +// UnbondingDelegation reflects a delegation's passive unbonding queue. +type UnbondingDelegation struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // delegator + ValidatorAddr sdk.AccAddress `json:"validator_addr"` // validator unbonding from owner addr + CreationHeight int64 `json:"creation_height"` // height which the unbonding took place + MinTime int64 `json:"min_time"` // unix time for unbonding completion + InitialBalance sdk.Coin `json:"initial_balance"` // atoms initially scheduled to receive at completion + Balance sdk.Coin `json:"balance"` // atoms to receive at completion +} + +type ubdValue struct { + CreationHeight int64 + MinTime int64 + InitialBalance sdk.Coin + Balance sdk.Coin +} + +// return the unbonding delegation without fields contained within the key for the store +func MustMarshalUBD(cdc *wire.Codec, ubd UnbondingDelegation) []byte { + val := ubdValue{ + ubd.CreationHeight, + ubd.MinTime, + ubd.InitialBalance, + ubd.Balance, + } + return cdc.MustMarshalBinary(val) +} + +// unmarshal a unbonding delegation from a store key and value +func MustUnmarshalUBD(cdc *wire.Codec, key, value []byte) UnbondingDelegation { + ubd, err := UnmarshalUBD(cdc, key, value) + if err != nil { + panic(err) + } + return ubd +} + +// unmarshal a unbonding delegation from a store key and value +func UnmarshalUBD(cdc *wire.Codec, key, value []byte) (ubd UnbondingDelegation, err error) { + var storeValue ubdValue + err = cdc.UnmarshalBinary(value, &storeValue) + if err != nil { + return + } + + addrs := key[1:] // remove prefix bytes + if len(addrs) != 2*sdk.AddrLen { + err = errors.New("unexpected key length") + return + } + delAddr := sdk.AccAddress(addrs[:sdk.AddrLen]) + valAddr := sdk.AccAddress(addrs[sdk.AddrLen:]) + + return UnbondingDelegation{ + DelegatorAddr: delAddr, + ValidatorAddr: valAddr, + CreationHeight: storeValue.CreationHeight, + MinTime: storeValue.MinTime, + InitialBalance: storeValue.InitialBalance, + Balance: storeValue.Balance, + }, nil +} + +// nolint +func (d UnbondingDelegation) Equal(d2 UnbondingDelegation) bool { + bz1 := MsgCdc.MustMarshalBinary(&d) + bz2 := MsgCdc.MustMarshalBinary(&d2) + return bytes.Equal(bz1, bz2) +} + +// HumanReadableString returns a human readable string representation of an +// UnbondingDelegation. An error is returned if the UnbondingDelegation's +// delegator or validator addresses cannot be Bech32 encoded. +func (d UnbondingDelegation) HumanReadableString() (string, error) { + resp := "Unbonding Delegation \n" + resp += fmt.Sprintf("Delegator: %s\n", d.DelegatorAddr) + resp += fmt.Sprintf("Validator: %s\n", d.ValidatorAddr) + resp += fmt.Sprintf("Creation height: %v\n", d.CreationHeight) + resp += fmt.Sprintf("Min time to unbond (unix): %v\n", d.MinTime) + resp += fmt.Sprintf("Expected balance: %s", d.Balance.String()) + + return resp, nil + +} + +// Redelegation reflects a delegation's passive re-delegation queue. +type Redelegation struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // delegator + ValidatorSrcAddr sdk.AccAddress `json:"validator_src_addr"` // validator redelegation source owner addr + ValidatorDstAddr sdk.AccAddress `json:"validator_dst_addr"` // validator redelegation destination owner addr + CreationHeight int64 `json:"creation_height"` // height which the redelegation took place + MinTime int64 `json:"min_time"` // unix time for redelegation completion + InitialBalance sdk.Coin `json:"initial_balance"` // initial balance when redelegation started + Balance sdk.Coin `json:"balance"` // current balance + SharesSrc sdk.Rat `json:"shares_src"` // amount of source shares redelegating + SharesDst sdk.Rat `json:"shares_dst"` // amount of destination shares redelegating +} + +type redValue struct { + CreationHeight int64 + MinTime int64 + InitialBalance sdk.Coin + Balance sdk.Coin + SharesSrc sdk.Rat + SharesDst sdk.Rat +} + +// return the redelegation without fields contained within the key for the store +func MustMarshalRED(cdc *wire.Codec, red Redelegation) []byte { + val := redValue{ + red.CreationHeight, + red.MinTime, + red.InitialBalance, + red.Balance, + red.SharesSrc, + red.SharesDst, + } + return cdc.MustMarshalBinary(val) +} + +// unmarshal a redelegation from a store key and value +func MustUnmarshalRED(cdc *wire.Codec, key, value []byte) Redelegation { + red, err := UnmarshalRED(cdc, key, value) + if err != nil { + panic(err) + } + return red +} + +// unmarshal a redelegation from a store key and value +func UnmarshalRED(cdc *wire.Codec, key, value []byte) (red Redelegation, err error) { + var storeValue redValue + err = cdc.UnmarshalBinary(value, &storeValue) + if err != nil { + return + } + + addrs := key[1:] // remove prefix bytes + if len(addrs) != 3*sdk.AddrLen { + err = errors.New("unexpected key length") + return + } + delAddr := sdk.AccAddress(addrs[:sdk.AddrLen]) + valSrcAddr := sdk.AccAddress(addrs[sdk.AddrLen : 2*sdk.AddrLen]) + valDstAddr := sdk.AccAddress(addrs[2*sdk.AddrLen:]) + + return Redelegation{ + DelegatorAddr: delAddr, + ValidatorSrcAddr: valSrcAddr, + ValidatorDstAddr: valDstAddr, + CreationHeight: storeValue.CreationHeight, + MinTime: storeValue.MinTime, + InitialBalance: storeValue.InitialBalance, + Balance: storeValue.Balance, + SharesSrc: storeValue.SharesSrc, + SharesDst: storeValue.SharesDst, + }, nil +} + +// nolint +func (d Redelegation) Equal(d2 Redelegation) bool { + bz1 := MsgCdc.MustMarshalBinary(&d) + bz2 := MsgCdc.MustMarshalBinary(&d2) + return bytes.Equal(bz1, bz2) +} + +// HumanReadableString returns a human readable string representation of a +// Redelegation. An error is returned if the UnbondingDelegation's delegator or +// validator addresses cannot be Bech32 encoded. +func (d Redelegation) HumanReadableString() (string, error) { + resp := "Redelegation \n" + resp += fmt.Sprintf("Delegator: %s\n", d.DelegatorAddr) + resp += fmt.Sprintf("Source Validator: %s\n", d.ValidatorSrcAddr) + resp += fmt.Sprintf("Destination Validator: %s\n", d.ValidatorDstAddr) + resp += fmt.Sprintf("Creation height: %v\n", d.CreationHeight) + resp += fmt.Sprintf("Min time to unbond (unix): %v\n", d.MinTime) + resp += fmt.Sprintf("Source shares: %s", d.SharesSrc.String()) + resp += fmt.Sprintf("Destination shares: %s", d.SharesDst.String()) + + return resp, nil + +} diff --git a/x/stake/types/delegation_test.go b/x/stake/types/delegation_test.go new file mode 100644 index 000000000..640f5d979 --- /dev/null +++ b/x/stake/types/delegation_test.go @@ -0,0 +1,116 @@ +package types + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestDelegationEqual(t *testing.T) { + d1 := Delegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + Shares: sdk.NewRat(100), + } + d2 := Delegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + Shares: sdk.NewRat(100), + } + + ok := d1.Equal(d2) + require.True(t, ok) + + d2.ValidatorAddr = addr3 + d2.Shares = sdk.NewRat(200) + + ok = d1.Equal(d2) + require.False(t, ok) +} + +func TestDelegationHumanReadableString(t *testing.T) { + d := Delegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + Shares: sdk.NewRat(100), + } + + // NOTE: Being that the validator's keypair is random, we cannot test the + // actual contents of the string. + valStr, err := d.HumanReadableString() + require.Nil(t, err) + require.NotEmpty(t, valStr) +} + +func TestUnbondingDelegationEqual(t *testing.T) { + ud1 := UnbondingDelegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + } + ud2 := UnbondingDelegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + } + + ok := ud1.Equal(ud2) + require.True(t, ok) + + ud2.ValidatorAddr = addr3 + ud2.MinTime = 20 * 20 * 2 + + ok = ud1.Equal(ud2) + require.False(t, ok) +} + +func TestUnbondingDelegationHumanReadableString(t *testing.T) { + ud := UnbondingDelegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + } + + // NOTE: Being that the validator's keypair is random, we cannot test the + // actual contents of the string. + valStr, err := ud.HumanReadableString() + require.Nil(t, err) + require.NotEmpty(t, valStr) +} + +func TestRedelegationEqual(t *testing.T) { + r1 := Redelegation{ + DelegatorAddr: addr1, + ValidatorSrcAddr: addr2, + ValidatorDstAddr: addr3, + } + r2 := Redelegation{ + DelegatorAddr: addr1, + ValidatorSrcAddr: addr2, + ValidatorDstAddr: addr3, + } + + ok := r1.Equal(r2) + require.True(t, ok) + + r2.SharesDst = sdk.NewRat(10) + r2.SharesSrc = sdk.NewRat(20) + r2.MinTime = 20 * 20 * 2 + + ok = r1.Equal(r2) + require.False(t, ok) +} + +func TestRedelegationHumanReadableString(t *testing.T) { + r := Redelegation{ + DelegatorAddr: addr1, + ValidatorSrcAddr: addr2, + ValidatorDstAddr: addr3, + SharesDst: sdk.NewRat(10), + SharesSrc: sdk.NewRat(20), + } + + // NOTE: Being that the validator's keypair is random, we cannot test the + // actual contents of the string. + valStr, err := r.HumanReadableString() + require.Nil(t, err) + require.NotEmpty(t, valStr) +} diff --git a/x/stake/types/errors.go b/x/stake/types/errors.go new file mode 100644 index 000000000..237340b89 --- /dev/null +++ b/x/stake/types/errors.go @@ -0,0 +1,146 @@ +// nolint +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type CodeType = sdk.CodeType + +const ( + DefaultCodespace sdk.CodespaceType = 4 + + CodeInvalidValidator CodeType = 101 + CodeInvalidDelegation CodeType = 102 + CodeInvalidInput CodeType = 103 + CodeValidatorJailed CodeType = 104 + CodeUnauthorized CodeType = sdk.CodeUnauthorized + CodeInternal CodeType = sdk.CodeInternal + CodeUnknownRequest CodeType = sdk.CodeUnknownRequest +) + +//validator +func ErrNilValidatorAddr(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, "validator address is nil") +} + +func ErrNoValidatorFound(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "validator does not exist for that address") +} + +func ErrValidatorOwnerExists(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "validator already exist for this owner-address, must use new validator-owner address") +} + +func ErrValidatorPubKeyExists(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "validator already exist for this pubkey, must use new validator pubkey") +} + +func ErrValidatorRevoked(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "validator for this address is currently revoked") +} + +func ErrBadRemoveValidator(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "error removing validator") +} + +func ErrDescriptionLength(codespace sdk.CodespaceType, descriptor string, got, max int) sdk.Error { + msg := fmt.Sprintf("bad description length for %v, got length %v, max is %v", descriptor, got, max) + return sdk.NewError(codespace, CodeInvalidValidator, msg) +} + +func ErrCommissionNegative(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission must be positive") +} + +func ErrCommissionHuge(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission cannot be more than 100%") +} + +func ErrNilDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, "delegator address is nil") +} + +func ErrBadDenom(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "invalid coin denomination") +} + +func ErrBadDelegationAmount(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "amount must be > 0") +} + +func ErrNoDelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "no delegation for this (address, validator) pair") +} + +func ErrBadDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "delegator does not exist for that address") +} + +func ErrNoDelegatorForAddress(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "delegator does not contain this delegation") +} + +func ErrInsufficientShares(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "insufficient delegation shares") +} + +func ErrDelegationValidatorEmpty(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "cannot delegate to an empty validator") +} + +func ErrNotEnoughDelegationShares(codespace sdk.CodespaceType, shares string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, fmt.Sprintf("not enough shares only have %v", shares)) +} + +func ErrBadSharesAmount(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "shares must be > 0") +} + +func ErrBadSharesPrecision(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, + fmt.Sprintf("shares denominator must be < %s, try reducing the number of decimal points", + maximumBondingRationalDenominator.String()), + ) +} + +func ErrBadSharesPercent(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "shares percent must be >0 and <=1") +} + +func ErrNotMature(codespace sdk.CodespaceType, operation, descriptor string, got, min int64) sdk.Error { + msg := fmt.Sprintf("%v is not mature requires a min %v of %v, currently it is %v", + operation, descriptor, got, min) + return sdk.NewError(codespace, CodeUnauthorized, msg) +} + +func ErrNoUnbondingDelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "no unbonding delegation found") +} + +func ErrNoRedelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "no redelegation found") +} + +func ErrBadRedelegationDst(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "redelegation validator not found") +} + +func ErrTransitiveRedelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, + "redelegation to this validator already in progress, first redelegation to this validator must complete before next redelegation") +} + +func ErrBothShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, "both shares amount and shares percent provided") +} + +func ErrNeitherShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, "neither shares amount nor shares percent provided") +} + +func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "missing signature") +} diff --git a/x/stake/types/genesis.go b/x/stake/types/genesis.go new file mode 100644 index 000000000..d08c6b899 --- /dev/null +++ b/x/stake/types/genesis.go @@ -0,0 +1,26 @@ +package 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"` +} + +func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []Delegation) GenesisState { + return GenesisState{ + Pool: pool, + Params: params, + Validators: validators, + Bonds: bonds, + } +} + +// get raw genesis raw message for testing +func DefaultGenesisState() GenesisState { + return GenesisState{ + Pool: InitialPool(), + Params: DefaultParams(), + } +} diff --git a/x/stake/types/msg.go b/x/stake/types/msg.go new file mode 100644 index 000000000..08fd80128 --- /dev/null +++ b/x/stake/types/msg.go @@ -0,0 +1,411 @@ +package types + +import ( + "math" + "reflect" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tendermint/crypto" +) + +// name to idetify transaction types +const MsgType = "stake" + +// Maximum amount of decimal points in the decimal representation of rationals +// used in MsgBeginUnbonding / MsgBeginRedelegate +const MaxBondDenominatorPrecision = 8 + +// Verify interface at compile time +var _, _, _ sdk.Msg = &MsgCreateValidator{}, &MsgEditValidator{}, &MsgDelegate{} +var _, _ sdk.Msg = &MsgBeginUnbonding{}, &MsgCompleteUnbonding{} +var _, _ sdk.Msg = &MsgBeginRedelegate{}, &MsgCompleteRedelegate{} + +// Initialize Int for the denominator +var maximumBondingRationalDenominator sdk.Int = sdk.NewInt(int64(math.Pow10(MaxBondDenominatorPrecision))) + +//______________________________________________________________________ + +// MsgCreateValidator - struct for unbonding transactions +type MsgCreateValidator struct { + Description + DelegatorAddr sdk.AccAddress `json:"delegator_address"` + ValidatorAddr sdk.AccAddress `json:"validator_address"` + PubKey crypto.PubKey `json:"pubkey"` + Delegation sdk.Coin `json:"delegation"` +} + +// Default way to create validator. Delegator address and validator address are the same +func NewMsgCreateValidator(validatorAddr sdk.AccAddress, pubkey crypto.PubKey, + selfDelegation sdk.Coin, description Description) MsgCreateValidator { + return MsgCreateValidator{ + Description: description, + DelegatorAddr: validatorAddr, + ValidatorAddr: validatorAddr, + PubKey: pubkey, + Delegation: selfDelegation, + } +} + +// Creates validator msg by delegator address on behalf of validator address +func NewMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr sdk.AccAddress, pubkey crypto.PubKey, + delegation sdk.Coin, description Description) MsgCreateValidator { + return MsgCreateValidator{ + Description: description, + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + PubKey: pubkey, + Delegation: delegation, + } +} + +//nolint +func (msg MsgCreateValidator) Type() string { return MsgType } + +// Return address(es) that must sign over msg.GetSignBytes() +func (msg MsgCreateValidator) GetSigners() []sdk.AccAddress { + // delegator is first signer so delegator pays fees + addrs := []sdk.AccAddress{msg.DelegatorAddr} + if !reflect.DeepEqual(msg.DelegatorAddr, msg.ValidatorAddr) { + // if validator addr is not same as delegator addr, validator must sign msg as well + addrs = append(addrs, msg.ValidatorAddr) + } + return addrs +} + +// get the bytes for the message signer to sign on +func (msg MsgCreateValidator) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + Description + DelegatorAddr sdk.AccAddress `json:"delegator_address"` + ValidatorAddr sdk.AccAddress `json:"validator_address"` + PubKey string `json:"pubkey"` + Delegation sdk.Coin `json:"delegation"` + }{ + Description: msg.Description, + ValidatorAddr: msg.ValidatorAddr, + PubKey: sdk.MustBech32ifyValPub(msg.PubKey), + Delegation: msg.Delegation, + }) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// quick validity check +func (msg MsgCreateValidator) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if !(msg.Delegation.Amount.GT(sdk.ZeroInt())) { + return ErrBadDelegationAmount(DefaultCodespace) + } + empty := Description{} + if msg.Description == empty { + return sdk.NewError(DefaultCodespace, CodeInvalidInput, "description must be included") + } + return nil +} + +//______________________________________________________________________ + +// MsgEditValidator - struct for editing a validator +type MsgEditValidator struct { + Description + ValidatorAddr sdk.AccAddress `json:"address"` +} + +func NewMsgEditValidator(validatorAddr sdk.AccAddress, description Description) MsgEditValidator { + return MsgEditValidator{ + Description: description, + ValidatorAddr: validatorAddr, + } +} + +//nolint +func (msg MsgEditValidator) Type() string { return MsgType } +func (msg MsgEditValidator) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.ValidatorAddr} +} + +// get the bytes for the message signer to sign on +func (msg MsgEditValidator) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + Description + ValidatorAddr sdk.AccAddress `json:"address"` + }{ + Description: msg.Description, + ValidatorAddr: msg.ValidatorAddr, + }) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// quick validity check +func (msg MsgEditValidator) ValidateBasic() sdk.Error { + if msg.ValidatorAddr == nil { + return sdk.NewError(DefaultCodespace, CodeInvalidInput, "nil validator address") + } + empty := Description{} + if msg.Description == empty { + return sdk.NewError(DefaultCodespace, CodeInvalidInput, "transaction must include some information to modify") + } + return nil +} + +//______________________________________________________________________ + +// MsgDelegate - struct for bonding transactions +type MsgDelegate struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` + ValidatorAddr sdk.AccAddress `json:"validator_addr"` + Delegation sdk.Coin `json:"delegation"` +} + +func NewMsgDelegate(delegatorAddr, validatorAddr sdk.AccAddress, delegation sdk.Coin) MsgDelegate { + return MsgDelegate{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + Delegation: delegation, + } +} + +//nolint +func (msg MsgDelegate) Type() string { return MsgType } +func (msg MsgDelegate) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.DelegatorAddr} +} + +// get the bytes for the message signer to sign on +func (msg MsgDelegate) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// quick validity check +func (msg MsgDelegate) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if !(msg.Delegation.Amount.GT(sdk.ZeroInt())) { + return ErrBadDelegationAmount(DefaultCodespace) + } + return nil +} + +//______________________________________________________________________ + +// MsgDelegate - struct for bonding transactions +type MsgBeginRedelegate struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` + ValidatorSrcAddr sdk.AccAddress `json:"validator_src_addr"` + ValidatorDstAddr sdk.AccAddress `json:"validator_dst_addr"` + SharesAmount sdk.Rat `json:"shares_amount"` +} + +func NewMsgBeginRedelegate(delegatorAddr, validatorSrcAddr, + validatorDstAddr sdk.AccAddress, sharesAmount sdk.Rat) MsgBeginRedelegate { + + return MsgBeginRedelegate{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + SharesAmount: sharesAmount, + } +} + +//nolint +func (msg MsgBeginRedelegate) Type() string { return MsgType } +func (msg MsgBeginRedelegate) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.DelegatorAddr} +} + +// get the bytes for the message signer to sign on +func (msg MsgBeginRedelegate) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` + ValidatorSrcAddr sdk.AccAddress `json:"validator_src_addr"` + ValidatorDstAddr sdk.AccAddress `json:"validator_dst_addr"` + SharesAmount string `json:"shares"` + }{ + DelegatorAddr: msg.DelegatorAddr, + ValidatorSrcAddr: msg.ValidatorSrcAddr, + ValidatorDstAddr: msg.ValidatorDstAddr, + SharesAmount: msg.SharesAmount.String(), + }) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// quick validity check +func (msg MsgBeginRedelegate) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorSrcAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if msg.ValidatorDstAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if msg.SharesAmount.LTE(sdk.ZeroRat()) { + return ErrBadSharesAmount(DefaultCodespace) + } + if msg.SharesAmount.Denom().GT(maximumBondingRationalDenominator) { + return ErrBadSharesPrecision(DefaultCodespace) + } + return nil +} + +// MsgDelegate - struct for bonding transactions +type MsgCompleteRedelegate struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` + ValidatorSrcAddr sdk.AccAddress `json:"validator_source_addr"` + ValidatorDstAddr sdk.AccAddress `json:"validator_destination_addr"` +} + +func NewMsgCompleteRedelegate(delegatorAddr, validatorSrcAddr, + validatorDstAddr sdk.AccAddress) MsgCompleteRedelegate { + + return MsgCompleteRedelegate{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + } +} + +//nolint +func (msg MsgCompleteRedelegate) Type() string { return MsgType } +func (msg MsgCompleteRedelegate) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.DelegatorAddr} +} + +// get the bytes for the message signer to sign on +func (msg MsgCompleteRedelegate) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// quick validity check +func (msg MsgCompleteRedelegate) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorSrcAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if msg.ValidatorDstAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + return nil +} + +//______________________________________________________________________ + +// MsgBeginUnbonding - struct for unbonding transactions +type MsgBeginUnbonding struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` + ValidatorAddr sdk.AccAddress `json:"validator_addr"` + SharesAmount sdk.Rat `json:"shares_amount"` +} + +func NewMsgBeginUnbonding(delegatorAddr, validatorAddr sdk.AccAddress, sharesAmount sdk.Rat) MsgBeginUnbonding { + return MsgBeginUnbonding{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + SharesAmount: sharesAmount, + } +} + +//nolint +func (msg MsgBeginUnbonding) Type() string { return MsgType } +func (msg MsgBeginUnbonding) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.DelegatorAddr} } + +// get the bytes for the message signer to sign on +func (msg MsgBeginUnbonding) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` + ValidatorAddr sdk.AccAddress `json:"validator_addr"` + SharesAmount string `json:"shares_amount"` + }{ + DelegatorAddr: msg.DelegatorAddr, + ValidatorAddr: msg.ValidatorAddr, + SharesAmount: msg.SharesAmount.String(), + }) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// quick validity check +func (msg MsgBeginUnbonding) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if msg.SharesAmount.LTE(sdk.ZeroRat()) { + return ErrBadSharesAmount(DefaultCodespace) + } + if msg.SharesAmount.Denom().GT(maximumBondingRationalDenominator) { + return ErrBadSharesPrecision(DefaultCodespace) + } + return nil +} + +// MsgCompleteUnbonding - struct for unbonding transactions +type MsgCompleteUnbonding struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` + ValidatorAddr sdk.AccAddress `json:"validator_addr"` +} + +func NewMsgCompleteUnbonding(delegatorAddr, validatorAddr sdk.AccAddress) MsgCompleteUnbonding { + return MsgCompleteUnbonding{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + } +} + +//nolint +func (msg MsgCompleteUnbonding) Type() string { return MsgType } +func (msg MsgCompleteUnbonding) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.DelegatorAddr} +} + +// get the bytes for the message signer to sign on +func (msg MsgCompleteUnbonding) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// quick validity check +func (msg MsgCompleteUnbonding) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + return nil +} diff --git a/x/stake/types/msg_test.go b/x/stake/types/msg_test.go new file mode 100644 index 000000000..73eef084a --- /dev/null +++ b/x/stake/types/msg_test.go @@ -0,0 +1,239 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tendermint/crypto" +) + +var ( + coinPos = sdk.NewCoin("steak", 1000) + coinZero = sdk.NewCoin("steak", 0) + coinNeg = sdk.NewCoin("steak", -10000) +) + +// test ValidateBasic for MsgCreateValidator +func TestMsgCreateValidator(t *testing.T) { + tests := []struct { + name, moniker, identity, website, details string + validatorAddr sdk.AccAddress + pubkey crypto.PubKey + bond sdk.Coin + expectPass bool + }{ + {"basic good", "a", "b", "c", "d", addr1, pk1, coinPos, true}, + {"partial description", "", "", "c", "", addr1, pk1, coinPos, true}, + {"empty description", "", "", "", "", addr1, pk1, coinPos, false}, + {"empty address", "a", "b", "c", "d", emptyAddr, pk1, coinPos, false}, + {"empty pubkey", "a", "b", "c", "d", addr1, emptyPubkey, coinPos, true}, + {"empty bond", "a", "b", "c", "d", addr1, pk1, coinZero, false}, + {"negative bond", "a", "b", "c", "d", addr1, pk1, coinNeg, false}, + {"negative bond", "a", "b", "c", "d", addr1, pk1, coinNeg, false}, + } + + for _, tc := range tests { + description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) + msg := NewMsgCreateValidator(tc.validatorAddr, tc.pubkey, tc.bond, description) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgEditValidator +func TestMsgEditValidator(t *testing.T) { + tests := []struct { + name, moniker, identity, website, details string + validatorAddr sdk.AccAddress + expectPass bool + }{ + {"basic good", "a", "b", "c", "d", addr1, true}, + {"partial description", "", "", "c", "", addr1, true}, + {"empty description", "", "", "", "", addr1, false}, + {"empty address", "a", "b", "c", "d", emptyAddr, false}, + } + + for _, tc := range tests { + description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) + msg := NewMsgEditValidator(tc.validatorAddr, description) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic and GetSigners for MsgCreateValidatorOnBehalfOf +func TestMsgCreateValidatorOnBehalfOf(t *testing.T) { + tests := []struct { + name, moniker, identity, website, details string + delegatorAddr sdk.AccAddress + validatorAddr sdk.AccAddress + validatorPubKey crypto.PubKey + bond sdk.Coin + expectPass bool + }{ + {"basic good", "a", "b", "c", "d", addr1, addr2, pk2, coinPos, true}, + {"partial description", "", "", "c", "", addr1, addr2, pk2, coinPos, true}, + {"empty description", "", "", "", "", addr1, addr2, pk2, coinPos, false}, + {"empty delegator address", "a", "b", "c", "d", emptyAddr, addr2, pk2, coinPos, false}, + {"empty validator address", "a", "b", "c", "d", addr1, emptyAddr, pk2, coinPos, false}, + {"empty pubkey", "a", "b", "c", "d", addr1, addr2, emptyPubkey, coinPos, true}, + {"empty bond", "a", "b", "c", "d", addr1, addr2, pk2, coinZero, false}, + {"negative bond", "a", "b", "c", "d", addr1, addr2, pk2, coinNeg, false}, + {"negative bond", "a", "b", "c", "d", addr1, addr2, pk2, coinNeg, false}, + } + + for _, tc := range tests { + description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) + msg := NewMsgCreateValidatorOnBehalfOf(tc.delegatorAddr, tc.validatorAddr, tc.validatorPubKey, tc.bond, description) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } + + msg := NewMsgCreateValidator(addr1, pk1, coinPos, Description{}) + addrs := msg.GetSigners() + require.Equal(t, []sdk.AccAddress{addr1}, addrs, "Signers on default msg is wrong") + + msg = NewMsgCreateValidatorOnBehalfOf(addr2, addr1, pk1, coinPos, Description{}) + addrs = msg.GetSigners() + require.Equal(t, []sdk.AccAddress{addr2, addr1}, addrs, "Signers for onbehalfof msg is wrong") +} + +// test ValidateBasic for MsgDelegate +func TestMsgDelegate(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.AccAddress + validatorAddr sdk.AccAddress + bond sdk.Coin + expectPass bool + }{ + {"basic good", addr1, addr2, coinPos, true}, + {"self bond", addr1, addr1, coinPos, true}, + {"empty delegator", emptyAddr, addr1, coinPos, false}, + {"empty validator", addr1, emptyAddr, coinPos, false}, + {"empty bond", addr1, addr2, coinZero, false}, + {"negative bond", addr1, addr2, coinNeg, false}, + } + + for _, tc := range tests { + msg := NewMsgDelegate(tc.delegatorAddr, tc.validatorAddr, tc.bond) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgUnbond +func TestMsgBeginRedelegate(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.AccAddress + validatorSrcAddr sdk.AccAddress + validatorDstAddr sdk.AccAddress + sharesAmount sdk.Rat + expectPass bool + }{ + {"regular", addr1, addr2, addr3, sdk.NewRat(1, 10), true}, + {"negative decimal", addr1, addr2, addr3, sdk.NewRat(-1, 10), false}, + {"zero amount", addr1, addr2, addr3, sdk.ZeroRat(), false}, + {"empty delegator", emptyAddr, addr1, addr3, sdk.NewRat(1, 10), false}, + {"empty source validator", addr1, emptyAddr, addr3, sdk.NewRat(1, 10), false}, + {"empty destination validator", addr1, addr2, emptyAddr, sdk.NewRat(1, 10), false}, + } + + for _, tc := range tests { + msg := NewMsgBeginRedelegate(tc.delegatorAddr, tc.validatorSrcAddr, tc.validatorDstAddr, tc.sharesAmount) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgUnbond +func TestMsgCompleteRedelegate(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.AccAddress + validatorSrcAddr sdk.AccAddress + validatorDstAddr sdk.AccAddress + expectPass bool + }{ + {"regular", addr1, addr2, addr3, true}, + {"empty delegator", emptyAddr, addr1, addr3, false}, + {"empty source validator", addr1, emptyAddr, addr3, false}, + {"empty destination validator", addr1, addr2, emptyAddr, false}, + } + + for _, tc := range tests { + msg := NewMsgCompleteRedelegate(tc.delegatorAddr, tc.validatorSrcAddr, tc.validatorDstAddr) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgUnbond +func TestMsgBeginUnbonding(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.AccAddress + validatorAddr sdk.AccAddress + sharesAmount sdk.Rat + expectPass bool + }{ + {"regular", addr1, addr2, sdk.NewRat(1, 10), true}, + {"negative decimal", addr1, addr2, sdk.NewRat(-1, 10), false}, + {"zero amount", addr1, addr2, sdk.ZeroRat(), false}, + {"empty delegator", emptyAddr, addr1, sdk.NewRat(1, 10), false}, + {"empty validator", addr1, emptyAddr, sdk.NewRat(1, 10), false}, + } + + for _, tc := range tests { + msg := NewMsgBeginUnbonding(tc.delegatorAddr, tc.validatorAddr, tc.sharesAmount) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgUnbond +func TestMsgCompleteUnbonding(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.AccAddress + validatorAddr sdk.AccAddress + expectPass bool + }{ + {"regular", addr1, addr2, true}, + {"empty delegator", emptyAddr, addr1, false}, + {"empty validator", addr1, emptyAddr, false}, + } + + for _, tc := range tests { + msg := NewMsgCompleteUnbonding(tc.delegatorAddr, tc.validatorAddr) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} diff --git a/x/stake/params.go b/x/stake/types/params.go similarity index 66% rename from x/stake/params.go rename to x/stake/types/params.go index 026bd871f..5a7dd6ef5 100644 --- a/x/stake/params.go +++ b/x/stake/types/params.go @@ -1,4 +1,4 @@ -package stake +package types import ( "bytes" @@ -6,6 +6,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +// defaultUnbondingTime reflects three weeks in seconds as the default +// unbonding time. +const defaultUnbondingTime int64 = 60 * 60 * 24 * 3 + // Params defines the high level settings for staking type Params struct { InflationRateChange sdk.Rat `json:"inflation_rate_change"` // maximum annual change in inflation rate @@ -13,23 +17,27 @@ type Params struct { InflationMin sdk.Rat `json:"inflation_min"` // minimum inflation rate GoalBonded sdk.Rat `json:"goal_bonded"` // Goal of percent bonded atoms + UnbondingTime int64 `json:"unbonding_time"` + MaxValidators uint16 `json:"max_validators"` // maximum number of validators BondDenom string `json:"bond_denom"` // bondable coin denomination } -func (p Params) equal(p2 Params) bool { - bz1 := msgCdc.MustMarshalBinary(&p) - bz2 := msgCdc.MustMarshalBinary(&p2) +// 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) return bytes.Equal(bz1, bz2) } -// default params +// DefaultParams returns a default set of parameters. func DefaultParams() Params { return Params{ InflationRateChange: sdk.NewRat(13, 100), InflationMax: sdk.NewRat(20, 100), InflationMin: sdk.NewRat(7, 100), GoalBonded: sdk.NewRat(67, 100), + UnbondingTime: defaultUnbondingTime, MaxValidators: 100, BondDenom: "steak", } diff --git a/x/stake/types/params_test.go b/x/stake/types/params_test.go new file mode 100644 index 000000000..c18700ef4 --- /dev/null +++ b/x/stake/types/params_test.go @@ -0,0 +1,21 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParamsEqual(t *testing.T) { + p1 := DefaultParams() + p2 := DefaultParams() + + ok := p1.Equal(p2) + require.True(t, ok) + + p2.UnbondingTime = 60 * 60 * 24 * 2 + p2.BondDenom = "soup" + + ok = p1.Equal(p2) + require.False(t, ok) +} diff --git a/x/stake/pool.go b/x/stake/types/pool.go similarity index 55% rename from x/stake/pool.go rename to x/stake/types/pool.go index ba1890ce5..8ef187f07 100644 --- a/x/stake/pool.go +++ b/x/stake/types/pool.go @@ -1,39 +1,41 @@ -package stake +package types import ( "bytes" + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" ) // Pool - dynamic parameters of the current state type Pool struct { - LooseUnbondedTokens int64 `json:"loose_unbonded_tokens"` // tokens not associated with any validator - UnbondedTokens int64 `json:"unbonded_tokens"` // reserve of unbonded tokens held with validators - UnbondingTokens int64 `json:"unbonding_tokens"` // tokens moving from bonded to unbonded pool - BondedTokens int64 `json:"bonded_tokens"` // reserve of bonded tokens - UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool - UnbondingShares sdk.Rat `json:"unbonding_shares"` // shares moving from Bonded to Unbonded Pool - BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool - InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time - Inflation sdk.Rat `json:"inflation"` // current annual inflation rate + LooseTokens int64 `json:"loose_tokens"` // tokens not associated with any validator + UnbondedTokens int64 `json:"unbonded_tokens"` // reserve of unbonded tokens held with validators + UnbondingTokens int64 `json:"unbonding_tokens"` // tokens moving from bonded to unbonded pool + BondedTokens int64 `json:"bonded_tokens"` // reserve of bonded tokens + UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool + UnbondingShares sdk.Rat `json:"unbonding_shares"` // shares moving from Bonded to Unbonded Pool + BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool + InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time + Inflation sdk.Rat `json:"inflation"` // current annual inflation rate DateLastCommissionReset int64 `json:"date_last_commission_reset"` // unix timestamp for last commission accounting reset (daily) // Fee Related - PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calcualtions + PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calculations } -func (p Pool) equal(p2 Pool) bool { - bz1 := msgCdc.MustMarshalBinary(&p) - bz2 := msgCdc.MustMarshalBinary(&p2) +// nolint +func (p Pool) Equal(p2 Pool) bool { + bz1 := MsgCdc.MustMarshalBinary(&p) + bz2 := MsgCdc.MustMarshalBinary(&p2) return bytes.Equal(bz1, bz2) } // initial pool for testing func InitialPool() Pool { return Pool{ - LooseUnbondedTokens: 0, + LooseTokens: 0, BondedTokens: 0, UnbondingTokens: 0, UnbondedTokens: 0, @@ -51,13 +53,13 @@ func InitialPool() Pool { // Sum total of all staking tokens in the pool func (p Pool) TokenSupply() int64 { - return p.LooseUnbondedTokens + p.UnbondedTokens + p.UnbondingTokens + p.BondedTokens + return p.LooseTokens + p.UnbondedTokens + p.UnbondingTokens + p.BondedTokens } //____________________________________________________________________ // get the bond ratio of the global state -func (p Pool) bondedRatio() sdk.Rat { +func (p Pool) BondedRatio() sdk.Rat { if p.TokenSupply() > 0 { return sdk.NewRat(p.BondedTokens, p.TokenSupply()) } @@ -65,7 +67,7 @@ func (p Pool) bondedRatio() sdk.Rat { } // get the exchange rate of bonded token per issued share -func (p Pool) bondedShareExRate() sdk.Rat { +func (p Pool) BondedShareExRate() sdk.Rat { if p.BondedShares.IsZero() { return sdk.OneRat() } @@ -73,7 +75,7 @@ func (p Pool) bondedShareExRate() sdk.Rat { } // get the exchange rate of unbonding tokens held in validators per issued share -func (p Pool) unbondingShareExRate() sdk.Rat { +func (p Pool) UnbondingShareExRate() sdk.Rat { if p.UnbondingShares.IsZero() { return sdk.OneRat() } @@ -81,7 +83,7 @@ func (p Pool) unbondingShareExRate() sdk.Rat { } // get the exchange rate of unbonded tokens held in validators per issued share -func (p Pool) unbondedShareExRate() sdk.Rat { +func (p Pool) UnbondedShareExRate() sdk.Rat { if p.UnbondedShares.IsZero() { return sdk.OneRat() } @@ -91,43 +93,67 @@ func (p Pool) unbondedShareExRate() sdk.Rat { //_______________________________________________________________________ func (p Pool) addTokensUnbonded(amount int64) (p2 Pool, issuedShares PoolShares) { - issuedSharesAmount := sdk.NewRat(amount).Quo(p.unbondedShareExRate()) // tokens * (shares/tokens) + issuedSharesAmount := sdk.NewRat(amount).Quo(p.UnbondedShareExRate()) // tokens * (shares/tokens) p.UnbondedShares = p.UnbondedShares.Add(issuedSharesAmount) p.UnbondedTokens += amount + p.LooseTokens -= amount + if p.LooseTokens < 0 { + panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p)) + } return p, NewUnbondedShares(issuedSharesAmount) } func (p Pool) removeSharesUnbonded(shares sdk.Rat) (p2 Pool, removedTokens int64) { - removedTokens = p.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + removedTokens = p.UnbondedShareExRate().Mul(shares).RoundInt64() // (tokens/shares) * shares p.UnbondedShares = p.UnbondedShares.Sub(shares) p.UnbondedTokens -= removedTokens + p.LooseTokens += removedTokens + if p.UnbondedTokens < 0 { + panic(fmt.Sprintf("sanity check: unbonded tokens negative, pool: %v", p)) + } return p, removedTokens } func (p Pool) addTokensUnbonding(amount int64) (p2 Pool, issuedShares PoolShares) { - issuedSharesAmount := sdk.NewRat(amount).Quo(p.unbondingShareExRate()) // tokens * (shares/tokens) + issuedSharesAmount := sdk.NewRat(amount).Quo(p.UnbondingShareExRate()) // tokens * (shares/tokens) p.UnbondingShares = p.UnbondingShares.Add(issuedSharesAmount) p.UnbondingTokens += amount + p.LooseTokens -= amount + if p.LooseTokens < 0 { + panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p)) + } return p, NewUnbondingShares(issuedSharesAmount) } func (p Pool) removeSharesUnbonding(shares sdk.Rat) (p2 Pool, removedTokens int64) { - removedTokens = p.unbondingShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + removedTokens = p.UnbondingShareExRate().Mul(shares).RoundInt64() // (tokens/shares) * shares p.UnbondingShares = p.UnbondingShares.Sub(shares) p.UnbondingTokens -= removedTokens + p.LooseTokens += removedTokens + if p.UnbondedTokens < 0 { + panic(fmt.Sprintf("sanity check: unbonding tokens negative, pool: %v", p)) + } return p, removedTokens } func (p Pool) addTokensBonded(amount int64) (p2 Pool, issuedShares PoolShares) { - issuedSharesAmount := sdk.NewRat(amount).Quo(p.bondedShareExRate()) // tokens * (shares/tokens) + issuedSharesAmount := sdk.NewRat(amount).Quo(p.BondedShareExRate()) // tokens * (shares/tokens) p.BondedShares = p.BondedShares.Add(issuedSharesAmount) p.BondedTokens += amount + p.LooseTokens -= amount + if p.LooseTokens < 0 { + panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p)) + } return p, NewBondedShares(issuedSharesAmount) } func (p Pool) removeSharesBonded(shares sdk.Rat) (p2 Pool, removedTokens int64) { - removedTokens = p.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + removedTokens = p.BondedShareExRate().Mul(shares).RoundInt64() // (tokens/shares) * shares p.BondedShares = p.BondedShares.Sub(shares) p.BondedTokens -= removedTokens + p.LooseTokens += removedTokens + if p.UnbondedTokens < 0 { + panic(fmt.Sprintf("sanity check: bonded tokens negative, pool: %v", p)) + } return p, removedTokens } diff --git a/x/stake/types/pool_test.go b/x/stake/types/pool_test.go new file mode 100644 index 000000000..3a52646f6 --- /dev/null +++ b/x/stake/types/pool_test.go @@ -0,0 +1,140 @@ +package types + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestPoolEqual(t *testing.T) { + p1 := InitialPool() + p2 := InitialPool() + + ok := p1.Equal(p2) + require.True(t, ok) + + p2.BondedTokens = 3 + p2.BondedShares = sdk.NewRat(10) + + ok = p1.Equal(p2) + require.False(t, ok) +} + +func TestBondedRatio(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 1 + pool.BondedTokens = 2 + + // bonded pool / total supply + require.Equal(t, pool.BondedRatio(), sdk.NewRat(2).Quo(sdk.NewRat(3))) + + // avoids divide-by-zero + pool.LooseTokens = 0 + pool.BondedTokens = 0 + require.Equal(t, pool.BondedRatio(), sdk.ZeroRat()) +} + +func TestBondedShareExRate(t *testing.T) { + pool := InitialPool() + pool.BondedTokens = 3 + pool.BondedShares = sdk.NewRat(10) + + // bonded pool / bonded shares + require.Equal(t, pool.BondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) + pool.BondedShares = sdk.ZeroRat() + + // avoids divide-by-zero + require.Equal(t, pool.BondedShareExRate(), sdk.OneRat()) +} + +func TestUnbondingShareExRate(t *testing.T) { + pool := InitialPool() + pool.UnbondingTokens = 3 + pool.UnbondingShares = sdk.NewRat(10) + + // unbonding pool / unbonding shares + require.Equal(t, pool.UnbondingShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) + pool.UnbondingShares = sdk.ZeroRat() + + // avoids divide-by-zero + require.Equal(t, pool.UnbondingShareExRate(), sdk.OneRat()) +} + +func TestUnbondedShareExRate(t *testing.T) { + pool := InitialPool() + pool.UnbondedTokens = 3 + pool.UnbondedShares = sdk.NewRat(10) + + // unbonded pool / unbonded shares + require.Equal(t, pool.UnbondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) + pool.UnbondedShares = sdk.ZeroRat() + + // avoids divide-by-zero + require.Equal(t, pool.UnbondedShareExRate(), sdk.OneRat()) +} + +func TestAddTokensBonded(t *testing.T) { + poolA := InitialPool() + poolA.LooseTokens = 10 + require.Equal(t, poolA.BondedShareExRate(), sdk.OneRat()) + + poolB, sharesB := poolA.addTokensBonded(10) + require.Equal(t, poolB.BondedShareExRate(), sdk.OneRat()) + + // correct changes to bonded shares and bonded pool + require.Equal(t, poolB.BondedShares, poolA.BondedShares.Add(sharesB.Amount)) + require.Equal(t, poolB.BondedTokens, poolA.BondedTokens+10) + + // same number of bonded shares / tokens when exchange rate is one + require.True(t, poolB.BondedShares.Equal(sdk.NewRat(poolB.BondedTokens))) +} + +func TestRemoveSharesBonded(t *testing.T) { + poolA := InitialPool() + poolA.LooseTokens = 10 + require.Equal(t, poolA.BondedShareExRate(), sdk.OneRat()) + + poolB, tokensB := poolA.removeSharesBonded(sdk.NewRat(10)) + require.Equal(t, poolB.BondedShareExRate(), sdk.OneRat()) + + // correct changes to bonded shares and bonded pool + require.Equal(t, poolB.BondedShares, poolA.BondedShares.Sub(sdk.NewRat(10))) + require.Equal(t, poolB.BondedTokens, poolA.BondedTokens-tokensB) + + // same number of bonded shares / tokens when exchange rate is one + require.True(t, poolB.BondedShares.Equal(sdk.NewRat(poolB.BondedTokens))) +} + +func TestAddTokensUnbonded(t *testing.T) { + poolA := InitialPool() + poolA.LooseTokens = 10 + require.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat()) + + poolB, sharesB := poolA.addTokensUnbonded(10) + require.Equal(t, poolB.UnbondedShareExRate(), sdk.OneRat()) + + // correct changes to unbonded shares and unbonded pool + require.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Add(sharesB.Amount)) + require.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens+10) + + // same number of unbonded shares / tokens when exchange rate is one + require.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedTokens))) +} + +func TestRemoveSharesUnbonded(t *testing.T) { + poolA := InitialPool() + poolA.UnbondedTokens = 10 + poolA.UnbondedShares = sdk.NewRat(10) + require.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat()) + + poolB, tokensB := poolA.removeSharesUnbonded(sdk.NewRat(10)) + require.Equal(t, poolB.UnbondedShareExRate(), sdk.OneRat()) + + // correct changes to unbonded shares and bonded pool + require.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Sub(sdk.NewRat(10))) + require.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens-tokensB) + + // same number of unbonded shares / tokens when exchange rate is one + require.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedTokens))) +} diff --git a/x/stake/types/shares.go b/x/stake/types/shares.go new file mode 100644 index 000000000..5a2cb2be6 --- /dev/null +++ b/x/stake/types/shares.go @@ -0,0 +1,149 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// PoolShares reflects the shares of a validator in a pool. +type PoolShares struct { + Status sdk.BondStatus `json:"status"` + Amount sdk.Rat `json:"amount"` +} + +// Equal returns a boolean determining of two PoolShares are identical. +func (s PoolShares) Equal(s2 PoolShares) bool { + return s.Status == s2.Status && + s.Amount.Equal(s2.Amount) +} + +// NewUnbondedShares returns a new PoolShares with a specified unbonded amount. +func NewUnbondedShares(amount sdk.Rat) PoolShares { + return PoolShares{ + Status: sdk.Unbonded, + Amount: amount, + } +} + +// NewUnbondingShares returns a new PoolShares with a specified unbonding +// amount. +func NewUnbondingShares(amount sdk.Rat) PoolShares { + return PoolShares{ + Status: sdk.Unbonding, + Amount: amount, + } +} + +// NewBondedShares returns a new PoolSahres with a specified bonding amount. +func NewBondedShares(amount sdk.Rat) PoolShares { + return PoolShares{ + Status: sdk.Bonded, + Amount: amount, + } +} + +// Unbonded returns the amount of unbonded shares. +func (s PoolShares) Unbonded() sdk.Rat { + if s.Status == sdk.Unbonded { + return s.Amount + } + return sdk.ZeroRat() +} + +// Unbonding returns the amount of unbonding shares. +func (s PoolShares) Unbonding() sdk.Rat { + if s.Status == sdk.Unbonding { + return s.Amount + } + return sdk.ZeroRat() +} + +// Bonded returns amount of bonded shares. +func (s PoolShares) Bonded() sdk.Rat { + if s.Status == sdk.Bonded { + return s.Amount + } + return sdk.ZeroRat() +} + +// ToUnbonded returns the equivalent amount of pool shares if the shares were +// unbonded. +func (s PoolShares) ToUnbonded(p Pool) PoolShares { + var amount sdk.Rat + + switch s.Status { + case sdk.Bonded: + // (tok/bondedshr)/(tok/unbondedshr) = unbondedshr/bondedshr + exRate := p.BondedShareExRate().Quo(p.UnbondedShareExRate()) + // bondedshr*unbondedshr/bondedshr = unbondedshr + amount = s.Amount.Mul(exRate) + case sdk.Unbonding: + // (tok/unbondingshr)/(tok/unbondedshr) = unbondedshr/unbondingshr + exRate := p.UnbondingShareExRate().Quo(p.UnbondedShareExRate()) + // unbondingshr*unbondedshr/unbondingshr = unbondedshr + amount = s.Amount.Mul(exRate) + case sdk.Unbonded: + amount = s.Amount + } + + return NewUnbondedShares(amount) +} + +// ToUnbonding returns the equivalent amount of pool shares if the shares were +// unbonding. +func (s PoolShares) ToUnbonding(p Pool) PoolShares { + var amount sdk.Rat + + switch s.Status { + case sdk.Bonded: + // (tok/bondedshr)/(tok/unbondingshr) = unbondingshr/bondedshr + exRate := p.BondedShareExRate().Quo(p.UnbondingShareExRate()) + // bondedshr*unbondingshr/bondedshr = unbondingshr + amount = s.Amount.Mul(exRate) + case sdk.Unbonding: + amount = s.Amount + case sdk.Unbonded: + // (tok/unbondedshr)/(tok/unbondingshr) = unbondingshr/unbondedshr + exRate := p.UnbondedShareExRate().Quo(p.UnbondingShareExRate()) + // unbondedshr*unbondingshr/unbondedshr = unbondingshr + amount = s.Amount.Mul(exRate) + } + + return NewUnbondingShares(amount) +} + +// ToBonded the equivalent amount of pool shares if the shares were bonded. +func (s PoolShares) ToBonded(p Pool) PoolShares { + var amount sdk.Rat + + switch s.Status { + case sdk.Bonded: + amount = s.Amount + case sdk.Unbonding: + // (tok/ubshr)/(tok/bshr) = bshr/ubshr + exRate := p.UnbondingShareExRate().Quo(p.BondedShareExRate()) + // ubshr*bshr/ubshr = bshr + amount = s.Amount.Mul(exRate) + case sdk.Unbonded: + // (tok/ubshr)/(tok/bshr) = bshr/ubshr + exRate := p.UnbondedShareExRate().Quo(p.BondedShareExRate()) + // ubshr*bshr/ubshr = bshr + amount = s.Amount.Mul(exRate) + } + + return NewUnbondedShares(amount) +} + +// Tokens returns the equivalent amount of tokens contained by the pool shares +// for a given pool. +func (s PoolShares) Tokens(p Pool) sdk.Rat { + switch s.Status { + case sdk.Bonded: + return p.BondedShareExRate().Mul(s.Amount) + case sdk.Unbonding: + return p.UnbondingShareExRate().Mul(s.Amount) + case sdk.Unbonded: + return p.UnbondedShareExRate().Mul(s.Amount) + default: + panic("unknown share kind") + } +} diff --git a/x/stake/types/shares_test.go b/x/stake/types/shares_test.go new file mode 100644 index 000000000..8a374606c --- /dev/null +++ b/x/stake/types/shares_test.go @@ -0,0 +1,35 @@ +package types + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestPoolSharesTokens(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 10 + + val := Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: NewBondedShares(sdk.NewRat(100)), + DelegatorShares: sdk.NewRat(100), + } + + pool.BondedTokens = val.PoolShares.Bonded().RoundInt64() + pool.BondedShares = val.PoolShares.Bonded() + + poolShares := NewBondedShares(sdk.NewRat(50)) + tokens := poolShares.Tokens(pool) + require.Equal(t, int64(50), tokens.RoundInt64()) + + poolShares = NewUnbondingShares(sdk.NewRat(50)) + tokens = poolShares.Tokens(pool) + require.Equal(t, int64(50), tokens.RoundInt64()) + + poolShares = NewUnbondedShares(sdk.NewRat(50)) + tokens = poolShares.Tokens(pool) + require.Equal(t, int64(50), tokens.RoundInt64()) +} diff --git a/x/stake/types/test_utils.go b/x/stake/types/test_utils.go new file mode 100644 index 000000000..d0622d46a --- /dev/null +++ b/x/stake/types/test_utils.go @@ -0,0 +1,212 @@ +package types + +import ( + "fmt" + "math/rand" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" +) + +var ( + pk1 = crypto.GenPrivKeyEd25519().PubKey() + pk2 = crypto.GenPrivKeyEd25519().PubKey() + pk3 = crypto.GenPrivKeyEd25519().PubKey() + addr1 = sdk.AccAddress(pk1.Address()) + addr2 = sdk.AccAddress(pk2.Address()) + addr3 = sdk.AccAddress(pk3.Address()) + + emptyAddr sdk.AccAddress + emptyPubkey crypto.PubKey +) + +// Operation reflects any operation that transforms staking state. It takes in +// a RNG instance, pool, validator and returns an updated pool, updated +// validator, delta tokens, and descriptive message. +type Operation func(r *rand.Rand, pool Pool, c Validator) (Pool, Validator, int64, string) + +// OpBondOrUnbond implements an operation that bonds or unbonds a validator +// depending on current status. +// nolint: unparam +func OpBondOrUnbond(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { + var ( + msg string + newStatus sdk.BondStatus + ) + + if val.Status() == sdk.Bonded { + msg = fmt.Sprintf("sdk.Unbonded previously bonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + newStatus = sdk.Unbonded + + } else if val.Status() == sdk.Unbonded { + msg = fmt.Sprintf("sdk.Bonded previously unbonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + newStatus = sdk.Bonded + } + + val, pool = val.UpdateStatus(pool, newStatus) + return pool, val, 0, msg +} + +// OpAddTokens implements an operation that adds a random number of tokens to a +// validator. +func OpAddTokens(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { + msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + + tokens := int64(r.Int31n(1000)) + val, pool, _ = val.AddTokensFromDel(pool, tokens) + msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) + + // Tokens are removed so for accounting must be negative + return pool, val, -1 * tokens, msg +} + +// OpRemoveShares implements an operation that removes a random number of +// shares from a validator. +func OpRemoveShares(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { + var shares sdk.Rat + for { + shares = sdk.NewRat(int64(r.Int31n(1000))) + if shares.LT(val.DelegatorShares) { + break + } + } + + msg := fmt.Sprintf("Removed %v shares from validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + shares, val.Owner, val.Status(), val.PoolShares, val.DelegatorShares, val.DelegatorShareExRate(pool)) + + val, pool, tokens := val.RemoveDelShares(pool, shares) + return pool, val, tokens, msg +} + +// RandomOperation returns a random staking operation. +func RandomOperation(r *rand.Rand) Operation { + operations := []Operation{ + OpBondOrUnbond, + OpAddTokens, + OpRemoveShares, + } + r.Shuffle(len(operations), func(i, j int) { + operations[i], operations[j] = operations[j], operations[i] + }) + + return operations[0] +} + +// AssertInvariants ensures invariants that should always be true are true. +// nolint: unparam +func AssertInvariants(t *testing.T, msg string, + pOrig Pool, cOrig []Validator, pMod Pool, vMods []Validator, tokens int64) { + + // total tokens conserved + require.Equal(t, + pOrig.UnbondedTokens+pOrig.BondedTokens, + pMod.UnbondedTokens+pMod.BondedTokens+tokens, + "Tokens not conserved - msg: %v\n, pOrig.PoolShares.Bonded(): %v, pOrig.PoolShares.Unbonded(): %v, pMod.PoolShares.Bonded(): %v, pMod.PoolShares.Unbonded(): %v, pOrig.UnbondedTokens: %v, pOrig.BondedTokens: %v, pMod.UnbondedTokens: %v, pMod.BondedTokens: %v, tokens: %v\n", + msg, + pOrig.BondedShares, pOrig.UnbondedShares, + pMod.BondedShares, pMod.UnbondedShares, + pOrig.UnbondedTokens, pOrig.BondedTokens, + pMod.UnbondedTokens, pMod.BondedTokens, tokens) + + // Nonnegative bonded shares + require.False(t, pMod.BondedShares.LT(sdk.ZeroRat()), + "Negative bonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", + msg, pOrig, pMod, tokens) + + // Nonnegative unbonded shares + require.False(t, pMod.UnbondedShares.LT(sdk.ZeroRat()), + "Negative unbonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", + msg, pOrig, pMod, tokens) + + // Nonnegative bonded ex rate + require.False(t, pMod.BondedShareExRate().LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative BondedShareExRate: %d", + msg, pMod.BondedShareExRate().RoundInt64()) + + // Nonnegative unbonded ex rate + require.False(t, pMod.UnbondedShareExRate().LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative UnbondedShareExRate: %d", + msg, pMod.UnbondedShareExRate().RoundInt64()) + + for _, vMod := range vMods { + // Nonnegative ex rate + require.False(t, vMod.DelegatorShareExRate(pMod).LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative validator.DelegatorShareExRate(): %v (validator.Owner: %s)", + msg, + vMod.DelegatorShareExRate(pMod), + vMod.Owner, + ) + + // Nonnegative poolShares + require.False(t, vMod.PoolShares.Bonded().LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative validator.PoolShares.Bonded(): %v (validator.DelegatorShares: %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", + msg, + vMod.PoolShares.Bonded(), + vMod.DelegatorShares, + vMod.DelegatorShareExRate(pMod), + vMod.Owner, + ) + + // Nonnegative delShares + require.False(t, vMod.DelegatorShares.LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative validator.DelegatorShares: %v (validator.PoolShares.Bonded(): %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", + msg, + vMod.DelegatorShares, + vMod.PoolShares.Bonded(), + vMod.DelegatorShareExRate(pMod), + vMod.Owner, + ) + } +} + +// TODO: refactor this random setup + +// randomValidator generates a random validator. +// nolint: unparam +func randomValidator(r *rand.Rand, i int) Validator { + poolSharesAmt := sdk.NewRat(int64(r.Int31n(10000))) + delShares := sdk.NewRat(int64(r.Int31n(10000))) + + var pShares PoolShares + + if r.Float64() < float64(0.5) { + pShares = NewBondedShares(poolSharesAmt) + } else { + pShares = NewUnbondedShares(poolSharesAmt) + } + + return Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: pShares, + DelegatorShares: delShares, + } +} + +// RandomSetup generates a random staking state. +func RandomSetup(r *rand.Rand, numValidators int) (Pool, []Validator) { + pool := InitialPool() + pool.LooseTokens = 100000 + + validators := make([]Validator, numValidators) + for i := 0; i < numValidators; i++ { + validator := randomValidator(r, i) + + if validator.Status() == sdk.Bonded { + pool.BondedShares = pool.BondedShares.Add(validator.PoolShares.Bonded()) + pool.BondedTokens += validator.PoolShares.Bonded().RoundInt64() + } else if validator.Status() == sdk.Unbonded { + pool.UnbondedShares = pool.UnbondedShares.Add(validator.PoolShares.Unbonded()) + pool.UnbondedTokens += validator.PoolShares.Unbonded().RoundInt64() + } + + validators[i] = validator + } + + return pool, validators +} diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go new file mode 100644 index 000000000..7b143364a --- /dev/null +++ b/x/stake/types/validator.go @@ -0,0 +1,417 @@ +package types + +import ( + "bytes" + "errors" + "fmt" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + tmtypes "github.com/tendermint/tendermint/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +const doNotModifyDescVal = "[do-not-modify]" + +// Validator defines the total amount of bond shares and their exchange rate to +// coins. Accumulation of interest is modelled as an in increase in the +// exchange rate, and slashing as a decrease. When coins are delegated to this +// validator, the validator is credited with a Delegation whose number of +// bond shares is based on the amount of coins delegated divided by the current +// exchange rate. Voting power can be calculated as total bonds multiplied by +// exchange rate. +type Validator struct { + Owner sdk.AccAddress `json:"owner"` // sender of BondTx - UnbondTx returns here + PubKey crypto.PubKey `json:"pub_key"` // pubkey of validator + Revoked bool `json:"revoked"` // has the validator been revoked from bonded status? + + PoolShares PoolShares `json:"pool_shares"` // total shares for tokens held in the pool + DelegatorShares sdk.Rat `json:"delegator_shares"` // total shares issued to a validator's delegators + + Description Description `json:"description"` // description terms for the validator + BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator + BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change + ProposerRewardPool sdk.Coins `json:"proposer_reward_pool"` // XXX reward pool collected from being the proposer + + Commission sdk.Rat `json:"commission"` // XXX the commission rate of fees charged to any delegators + CommissionMax sdk.Rat `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge + CommissionChangeRate sdk.Rat `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission + CommissionChangeToday sdk.Rat `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time) + + // fee related + PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // total shares of a global hold pools +} + +// NewValidator - initialize a new validator +func NewValidator(owner sdk.AccAddress, pubKey crypto.PubKey, description Description) Validator { + return Validator{ + Owner: owner, + PubKey: pubKey, + Revoked: false, + PoolShares: NewUnbondedShares(sdk.ZeroRat()), + DelegatorShares: sdk.ZeroRat(), + Description: description, + BondHeight: int64(0), + BondIntraTxCounter: int16(0), + ProposerRewardPool: sdk.Coins{}, + Commission: sdk.ZeroRat(), + CommissionMax: sdk.ZeroRat(), + CommissionChangeRate: sdk.ZeroRat(), + CommissionChangeToday: sdk.ZeroRat(), + PrevBondedShares: sdk.ZeroRat(), + } +} + +// what's kept in the store value +type validatorValue struct { + PubKey crypto.PubKey + Revoked bool + PoolShares PoolShares + DelegatorShares sdk.Rat + Description Description + BondHeight int64 + BondIntraTxCounter int16 + ProposerRewardPool sdk.Coins + Commission sdk.Rat + CommissionMax sdk.Rat + CommissionChangeRate sdk.Rat + CommissionChangeToday sdk.Rat + PrevBondedShares sdk.Rat +} + +// return the redelegation without fields contained within the key for the store +func MustMarshalValidator(cdc *wire.Codec, validator Validator) []byte { + val := validatorValue{ + PubKey: validator.PubKey, + Revoked: validator.Revoked, + PoolShares: validator.PoolShares, + DelegatorShares: validator.DelegatorShares, + Description: validator.Description, + BondHeight: validator.BondHeight, + BondIntraTxCounter: validator.BondIntraTxCounter, + ProposerRewardPool: validator.ProposerRewardPool, + Commission: validator.Commission, + CommissionMax: validator.CommissionMax, + CommissionChangeRate: validator.CommissionChangeRate, + CommissionChangeToday: validator.CommissionChangeToday, + PrevBondedShares: validator.PrevBondedShares, + } + return cdc.MustMarshalBinary(val) +} + +// unmarshal a redelegation from a store key and value +func MustUnmarshalValidator(cdc *wire.Codec, ownerAddr, value []byte) Validator { + validator, err := UnmarshalValidator(cdc, ownerAddr, value) + if err != nil { + panic(err) + } + + return validator +} + +// unmarshal a redelegation from a store key and value +func UnmarshalValidator(cdc *wire.Codec, ownerAddr, value []byte) (validator Validator, err error) { + var storeValue validatorValue + err = cdc.UnmarshalBinary(value, &storeValue) + if err != nil { + return + } + + if len(ownerAddr) != 20 { + err = errors.New("unexpected address length") + return + } + + return Validator{ + Owner: ownerAddr, + PubKey: storeValue.PubKey, + Revoked: storeValue.Revoked, + PoolShares: storeValue.PoolShares, + DelegatorShares: storeValue.DelegatorShares, + Description: storeValue.Description, + BondHeight: storeValue.BondHeight, + BondIntraTxCounter: storeValue.BondIntraTxCounter, + ProposerRewardPool: storeValue.ProposerRewardPool, + Commission: storeValue.Commission, + CommissionMax: storeValue.CommissionMax, + CommissionChangeRate: storeValue.CommissionChangeRate, + CommissionChangeToday: storeValue.CommissionChangeToday, + PrevBondedShares: storeValue.PrevBondedShares, + }, nil +} + +// only the vitals - does not check bond height of IntraTxCounter +func (v Validator) Equal(c2 Validator) bool { + return v.PubKey.Equals(c2.PubKey) && + bytes.Equal(v.Owner, c2.Owner) && + v.PoolShares.Equal(c2.PoolShares) && + v.DelegatorShares.Equal(c2.DelegatorShares) && + v.Description == c2.Description && + v.ProposerRewardPool.IsEqual(c2.ProposerRewardPool) && + v.Commission.Equal(c2.Commission) && + v.CommissionMax.Equal(c2.CommissionMax) && + v.CommissionChangeRate.Equal(c2.CommissionChangeRate) && + v.CommissionChangeToday.Equal(c2.CommissionChangeToday) && + v.PrevBondedShares.Equal(c2.PrevBondedShares) +} + +// Description - description fields for a validator +type Description struct { + Moniker string `json:"moniker"` // name + Identity string `json:"identity"` // optional identity signature (ex. UPort or Keybase) + Website string `json:"website"` // optional website link + Details string `json:"details"` // optional details +} + +// NewDescription returns a new Description with the provided values. +func NewDescription(moniker, identity, website, details string) Description { + return Description{ + Moniker: moniker, + Identity: identity, + Website: website, + Details: details, + } +} + +// UpdateDescription updates the fields of a given description. An error is +// returned if the resulting description contains an invalid length. +func (d Description) UpdateDescription(d2 Description) (Description, sdk.Error) { + if d.Moniker == doNotModifyDescVal { + d2.Moniker = d.Moniker + } + if d.Identity == doNotModifyDescVal { + d2.Identity = d.Identity + } + if d.Website == doNotModifyDescVal { + d2.Website = d.Website + } + if d.Details == doNotModifyDescVal { + d2.Details = d.Details + } + + return Description{ + Moniker: d2.Moniker, + Identity: d2.Identity, + Website: d2.Website, + Details: d2.Details, + }.EnsureLength() +} + +// EnsureLength ensures the length of a validator's description. +func (d Description) EnsureLength() (Description, sdk.Error) { + if len(d.Moniker) > 70 { + return d, ErrDescriptionLength(DefaultCodespace, "moniker", len(d.Moniker), 70) + } + if len(d.Identity) > 3000 { + return d, ErrDescriptionLength(DefaultCodespace, "identity", len(d.Identity), 3000) + } + if len(d.Website) > 140 { + return d, ErrDescriptionLength(DefaultCodespace, "website", len(d.Website), 140) + } + if len(d.Details) > 280 { + return d, ErrDescriptionLength(DefaultCodespace, "details", len(d.Details), 280) + } + + return d, nil +} + +// ABCIValidator returns an abci.Validator from a staked validator type. +func (v Validator) ABCIValidator() abci.Validator { + return abci.Validator{ + PubKey: tmtypes.TM2PB.PubKey(v.PubKey), + Power: v.PoolShares.Bonded().RoundInt64(), + } +} + +// ABCIValidatorZero returns an abci.Validator from a staked validator type +// with with zero power used for validator updates. +func (v Validator) ABCIValidatorZero() abci.Validator { + return abci.Validator{ + PubKey: tmtypes.TM2PB.PubKey(v.PubKey), + Power: 0, + } +} + +// Status returns the validator's bond status inferred from the pool shares. +func (v Validator) Status() sdk.BondStatus { + return v.PoolShares.Status +} + +// UpdateStatus updates the location of the shares within a validator if it's +// bond status has changed. +func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, Pool) { + var tokens int64 + + switch v.Status() { + case sdk.Unbonded: + if NewStatus == sdk.Unbonded { + return v, pool + } + pool, tokens = pool.removeSharesUnbonded(v.PoolShares.Amount) + + case sdk.Unbonding: + if NewStatus == sdk.Unbonding { + return v, pool + } + pool, tokens = pool.removeSharesUnbonding(v.PoolShares.Amount) + + case sdk.Bonded: + if NewStatus == sdk.Bonded { + // Return if nothing needs switching + return v, pool + } + pool, tokens = pool.removeSharesBonded(v.PoolShares.Amount) + } + + switch NewStatus { + case sdk.Unbonded: + pool, v.PoolShares = pool.addTokensUnbonded(tokens) + case sdk.Unbonding: + pool, v.PoolShares = pool.addTokensUnbonding(tokens) + case sdk.Bonded: + pool, v.PoolShares = pool.addTokensBonded(tokens) + } + + return v, pool +} + +// RemovePoolShares removes pool shares from a validator. It returns +// corresponding tokens, which could be burned (e.g. when slashing a validator) +// or redistributed elsewhere. +func (v Validator) RemovePoolShares(pool Pool, poolShares sdk.Rat) (Validator, Pool, int64) { + var tokens int64 + + switch v.Status() { + case sdk.Unbonded: + pool, tokens = pool.removeSharesUnbonded(poolShares) + case sdk.Unbonding: + pool, tokens = pool.removeSharesUnbonding(poolShares) + case sdk.Bonded: + pool, tokens = pool.removeSharesBonded(poolShares) + } + + v.PoolShares.Amount = v.PoolShares.Amount.Sub(poolShares) + return v, pool, tokens +} + +// EquivalentBondedShares ... +// +// TODO: Remove should only be tokens get the power or potential power for a +// validator if bonded, the power is the BondedShares if not bonded, the power +// is the amount of bonded shares which the the validator would have it was +// bonded. +func (v Validator) EquivalentBondedShares(pool Pool) (eqBondedShares sdk.Rat) { + return v.PoolShares.ToBonded(pool).Amount +} + +//_________________________________________________________________________________________________________ + +// AddTokensFromDel adds tokens to a validator +func (v Validator) AddTokensFromDel(pool Pool, amount int64) (Validator, Pool, sdk.Rat) { + var ( + poolShares PoolShares + equivalentBondedShares sdk.Rat + ) + + // bondedShare/delegatedShare + exRate := v.DelegatorShareExRate(pool) + + switch v.Status() { + case sdk.Unbonded: + pool, poolShares = pool.addTokensUnbonded(amount) + case sdk.Unbonding: + pool, poolShares = pool.addTokensUnbonding(amount) + case sdk.Bonded: + pool, poolShares = pool.addTokensBonded(amount) + } + v.PoolShares.Amount = v.PoolShares.Amount.Add(poolShares.Amount) + equivalentBondedShares = poolShares.ToBonded(pool).Amount + + // bondedShare/(bondedShare/delegatedShare) = delegatedShare + issuedDelegatorShares := equivalentBondedShares.Quo(exRate) + v.DelegatorShares = v.DelegatorShares.Add(issuedDelegatorShares) + + return v, pool, issuedDelegatorShares +} + +// RemoveDelShares removes delegator shares from a validator. +// +// NOTE: This function assumes the shares have already been updated for the +// validator status. +func (v Validator) RemoveDelShares(pool Pool, delShares sdk.Rat) (Validator, Pool, int64) { + amount := v.DelegatorShareExRate(pool).Mul(delShares) + eqBondedSharesToRemove := NewBondedShares(amount) + v.DelegatorShares = v.DelegatorShares.Sub(delShares) + + var createdCoins int64 + + switch v.Status() { + case sdk.Unbonded: + unbondedShares := eqBondedSharesToRemove.ToUnbonded(pool).Amount + pool, createdCoins = pool.removeSharesUnbonded(unbondedShares) + v.PoolShares.Amount = v.PoolShares.Amount.Sub(unbondedShares) + case sdk.Unbonding: + unbondingShares := eqBondedSharesToRemove.ToUnbonding(pool).Amount + pool, createdCoins = pool.removeSharesUnbonding(unbondingShares) + v.PoolShares.Amount = v.PoolShares.Amount.Sub(unbondingShares) + case sdk.Bonded: + pool, createdCoins = pool.removeSharesBonded(eqBondedSharesToRemove.Amount) + v.PoolShares.Amount = v.PoolShares.Amount.Sub(eqBondedSharesToRemove.Amount) + } + + return v, pool, createdCoins +} + +// DelegatorShareExRate gets the exchange rate of tokens over delegator shares. +// UNITS: eq-val-bonded-shares/delegator-shares +func (v Validator) DelegatorShareExRate(pool Pool) sdk.Rat { + if v.DelegatorShares.IsZero() { + return sdk.OneRat() + } + + eqBondedShares := v.PoolShares.ToBonded(pool).Amount + return eqBondedShares.Quo(v.DelegatorShares) +} + +//______________________________________________________________________ + +// ensure fulfills the sdk validator types +var _ sdk.Validator = Validator{} + +// nolint - for sdk.Validator +func (v Validator) GetRevoked() bool { return v.Revoked } +func (v Validator) GetMoniker() string { return v.Description.Moniker } +func (v Validator) GetStatus() sdk.BondStatus { return v.Status() } +func (v Validator) GetOwner() sdk.AccAddress { return v.Owner } +func (v Validator) GetPubKey() crypto.PubKey { return v.PubKey } +func (v Validator) GetPower() sdk.Rat { return v.PoolShares.Bonded() } +func (v Validator) GetDelegatorShares() sdk.Rat { return v.DelegatorShares } +func (v Validator) GetBondHeight() int64 { return v.BondHeight } + +// HumanReadableString returns a human readable string representation of a +// validator. An error is returned if the owner or the owner's public key +// cannot be converted to Bech32 format. +func (v Validator) HumanReadableString() (string, error) { + bechVal, err := sdk.Bech32ifyValPub(v.PubKey) + if err != nil { + return "", err + } + + resp := "Validator \n" + resp += fmt.Sprintf("Owner: %s\n", v.Owner) + resp += fmt.Sprintf("Validator: %s\n", bechVal) + resp += fmt.Sprintf("Shares: Status %s, Amount: %s\n", sdk.BondStatusToString(v.PoolShares.Status), v.PoolShares.Amount.FloatString()) + resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares.FloatString()) + resp += fmt.Sprintf("Description: %s\n", v.Description) + resp += fmt.Sprintf("Bond Height: %d\n", v.BondHeight) + resp += fmt.Sprintf("Proposer Reward Pool: %s\n", v.ProposerRewardPool.String()) + resp += fmt.Sprintf("Commission: %s\n", v.Commission.String()) + resp += fmt.Sprintf("Max Commission Rate: %s\n", v.CommissionMax.String()) + resp += fmt.Sprintf("Commission Change Rate: %s\n", v.CommissionChangeRate.String()) + resp += fmt.Sprintf("Commission Change Today: %s\n", v.CommissionChangeToday.String()) + resp += fmt.Sprintf("Previously Bonded Stares: %s\n", v.PrevBondedShares.String()) + + return resp, nil +} diff --git a/x/stake/types/validator_test.go b/x/stake/types/validator_test.go new file mode 100644 index 000000000..f978a6d6e --- /dev/null +++ b/x/stake/types/validator_test.go @@ -0,0 +1,319 @@ +package types + +import ( + "fmt" + "math/rand" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + tmtypes "github.com/tendermint/tendermint/types" +) + +func TestValidatorEqual(t *testing.T) { + val1 := NewValidator(addr1, pk1, Description{}) + val2 := NewValidator(addr1, pk1, Description{}) + + ok := val1.Equal(val2) + require.True(t, ok) + + val2 = NewValidator(addr2, pk2, Description{}) + + ok = val1.Equal(val2) + require.False(t, ok) +} + +func TestUpdateDescription(t *testing.T) { + d1 := Description{ + Moniker: doNotModifyDescVal, + Identity: doNotModifyDescVal, + Website: doNotModifyDescVal, + Details: doNotModifyDescVal, + } + d2 := Description{ + Website: "https://validator.cosmos", + Details: "Test validator", + } + + d, err := d1.UpdateDescription(d2) + require.Nil(t, err) + require.Equal(t, d, d1) +} + +func TestABCIValidator(t *testing.T) { + val := NewValidator(addr1, pk1, Description{}) + + abciVal := val.ABCIValidator() + require.Equal(t, tmtypes.TM2PB.PubKey(val.PubKey), abciVal.PubKey) + require.Equal(t, val.PoolShares.Bonded().RoundInt64(), abciVal.Power) +} + +func TestABCIValidatorZero(t *testing.T) { + val := NewValidator(addr1, pk1, Description{}) + + abciVal := val.ABCIValidatorZero() + require.Equal(t, tmtypes.TM2PB.PubKey(val.PubKey), abciVal.PubKey) + require.Equal(t, int64(0), abciVal.Power) +} + +func TestRemovePoolShares(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 10 + + val := Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: NewBondedShares(sdk.NewRat(100)), + DelegatorShares: sdk.NewRat(100), + } + + pool.BondedTokens = val.PoolShares.Bonded().RoundInt64() + pool.BondedShares = val.PoolShares.Bonded() + + val, pool = val.UpdateStatus(pool, sdk.Bonded) + val, pool, tk := val.RemovePoolShares(pool, sdk.NewRat(10)) + require.Equal(t, int64(90), val.PoolShares.Amount.RoundInt64()) + require.Equal(t, int64(90), pool.BondedTokens) + require.Equal(t, int64(90), pool.BondedShares.RoundInt64()) + require.Equal(t, int64(20), pool.LooseTokens) + require.Equal(t, int64(10), tk) + + val, pool = val.UpdateStatus(pool, sdk.Unbonded) + val, pool, tk = val.RemovePoolShares(pool, sdk.NewRat(10)) + require.Equal(t, int64(80), val.PoolShares.Amount.RoundInt64()) + require.Equal(t, int64(0), pool.BondedTokens) + require.Equal(t, int64(0), pool.BondedShares.RoundInt64()) + require.Equal(t, int64(30), pool.LooseTokens) + require.Equal(t, int64(10), tk) +} + +func TestAddTokensValidatorBonded(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 10 + val := NewValidator(addr1, pk1, Description{}) + val, pool = val.UpdateStatus(pool, sdk.Bonded) + val, pool, delShares := val.AddTokensFromDel(pool, 10) + + require.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) + require.Equal(t, sdk.OneRat(), pool.BondedShareExRate()) + require.Equal(t, sdk.OneRat(), pool.UnbondingShareExRate()) + require.Equal(t, sdk.OneRat(), pool.UnbondedShareExRate()) + + assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) + assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Bonded())) +} + +func TestAddTokensValidatorUnbonding(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 10 + val := NewValidator(addr1, pk1, Description{}) + val, pool = val.UpdateStatus(pool, sdk.Unbonding) + val, pool, delShares := val.AddTokensFromDel(pool, 10) + + require.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) + require.Equal(t, sdk.OneRat(), pool.BondedShareExRate()) + require.Equal(t, sdk.OneRat(), pool.UnbondingShareExRate()) + require.Equal(t, sdk.OneRat(), pool.UnbondedShareExRate()) + + assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) + assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonding())) +} + +func TestAddTokensValidatorUnbonded(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 10 + val := NewValidator(addr1, pk1, Description{}) + val, pool = val.UpdateStatus(pool, sdk.Unbonded) + val, pool, delShares := val.AddTokensFromDel(pool, 10) + + require.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) + require.Equal(t, sdk.OneRat(), pool.BondedShareExRate()) + require.Equal(t, sdk.OneRat(), pool.UnbondingShareExRate()) + require.Equal(t, sdk.OneRat(), pool.UnbondedShareExRate()) + + assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) + assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonded())) +} + +// TODO refactor to make simpler like the AddToken tests above +func TestRemoveDelShares(t *testing.T) { + poolA := InitialPool() + poolA.LooseTokens = 10 + valA := Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: NewBondedShares(sdk.NewRat(100)), + DelegatorShares: sdk.NewRat(100), + } + poolA.BondedTokens = valA.PoolShares.Bonded().RoundInt64() + poolA.BondedShares = valA.PoolShares.Bonded() + require.Equal(t, valA.DelegatorShareExRate(poolA), sdk.OneRat()) + require.Equal(t, poolA.BondedShareExRate(), sdk.OneRat()) + require.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat()) + valB, poolB, coinsB := valA.RemoveDelShares(poolA, sdk.NewRat(10)) + + // coins were created + require.Equal(t, coinsB, int64(10)) + // pool shares were removed + require.Equal(t, valB.PoolShares.Bonded(), valA.PoolShares.Bonded().Sub(sdk.NewRat(10).Mul(valA.DelegatorShareExRate(poolA)))) + // conservation of tokens + require.Equal(t, poolB.UnbondedTokens+poolB.BondedTokens+coinsB, poolA.UnbondedTokens+poolA.BondedTokens) + + // specific case from random tests + poolShares := sdk.NewRat(5102) + delShares := sdk.NewRat(115) + val := Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: NewBondedShares(poolShares), + DelegatorShares: delShares, + } + pool := Pool{ + BondedShares: sdk.NewRat(248305), + UnbondedShares: sdk.NewRat(232147), + BondedTokens: 248305, + UnbondedTokens: 232147, + InflationLastTime: 0, + Inflation: sdk.NewRat(7, 100), + } + shares := sdk.NewRat(29) + msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + msg = fmt.Sprintf("Removed %v shares from %s", shares, msg) + _, newPool, tokens := val.RemoveDelShares(pool, shares) + require.Equal(t, + tokens+newPool.UnbondedTokens+newPool.BondedTokens, + pool.BondedTokens+pool.UnbondedTokens, + "Tokens were not conserved: %s", msg) +} + +func TestUpdateStatus(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 100 + + val := NewValidator(addr1, pk1, Description{}) + val, pool, _ = val.AddTokensFromDel(pool, 100) + require.Equal(t, int64(0), val.PoolShares.Bonded().RoundInt64()) + require.Equal(t, int64(0), val.PoolShares.Unbonding().RoundInt64()) + require.Equal(t, int64(100), val.PoolShares.Unbonded().RoundInt64()) + require.Equal(t, int64(0), pool.BondedTokens) + require.Equal(t, int64(0), pool.UnbondingTokens) + require.Equal(t, int64(100), pool.UnbondedTokens) + + val, pool = val.UpdateStatus(pool, sdk.Unbonding) + require.Equal(t, int64(0), val.PoolShares.Bonded().RoundInt64()) + require.Equal(t, int64(100), val.PoolShares.Unbonding().RoundInt64()) + require.Equal(t, int64(0), val.PoolShares.Unbonded().RoundInt64()) + require.Equal(t, int64(0), pool.BondedTokens) + require.Equal(t, int64(100), pool.UnbondingTokens) + require.Equal(t, int64(0), pool.UnbondedTokens) + + val, pool = val.UpdateStatus(pool, sdk.Bonded) + require.Equal(t, int64(100), val.PoolShares.Bonded().RoundInt64()) + require.Equal(t, int64(0), val.PoolShares.Unbonding().RoundInt64()) + require.Equal(t, int64(0), val.PoolShares.Unbonded().RoundInt64()) + require.Equal(t, int64(100), pool.BondedTokens) + require.Equal(t, int64(0), pool.UnbondingTokens) + require.Equal(t, int64(0), pool.UnbondedTokens) +} + +func TestPossibleOverflow(t *testing.T) { + poolShares := sdk.NewRat(2159) + delShares := sdk.NewRat(391432570689183511).Quo(sdk.NewRat(40113011844664)) + val := Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: NewBondedShares(poolShares), + DelegatorShares: delShares, + } + pool := Pool{ + LooseTokens: 100, + BondedShares: poolShares, + UnbondedShares: sdk.ZeroRat(), + BondedTokens: poolShares.RoundInt64(), + UnbondedTokens: 0, + InflationLastTime: 0, + Inflation: sdk.NewRat(7, 100), + } + tokens := int64(71) + msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + newValidator, _, _ := val.AddTokensFromDel(pool, tokens) + + msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) + require.False(t, newValidator.DelegatorShareExRate(pool).LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative DelegatorShareExRate(): %v", + msg, newValidator.DelegatorShareExRate(pool)) +} + +// run random operations in a random order on a random single-validator state, assert invariants hold +func TestSingleValidatorIntegrationInvariants(t *testing.T) { + r := rand.New(rand.NewSource(41)) + + for i := 0; i < 10; i++ { + poolOrig, validatorsOrig := RandomSetup(r, 1) + require.Equal(t, 1, len(validatorsOrig)) + + // sanity check + AssertInvariants(t, "no operation", + poolOrig, validatorsOrig, + poolOrig, validatorsOrig, 0) + + for j := 0; j < 5; j++ { + poolMod, validatorMod, tokens, msg := RandomOperation(r)(r, poolOrig, validatorsOrig[0]) + + validatorsMod := make([]Validator, len(validatorsOrig)) + copy(validatorsMod[:], validatorsOrig[:]) + require.Equal(t, 1, len(validatorsOrig), "j %v", j) + require.Equal(t, 1, len(validatorsMod), "j %v", j) + validatorsMod[0] = validatorMod + + AssertInvariants(t, msg, + poolOrig, validatorsOrig, + poolMod, validatorsMod, tokens) + + poolOrig = poolMod + validatorsOrig = validatorsMod + } + } +} + +// run random operations in a random order on a random multi-validator state, assert invariants hold +func TestMultiValidatorIntegrationInvariants(t *testing.T) { + r := rand.New(rand.NewSource(42)) + + for i := 0; i < 10; i++ { + poolOrig, validatorsOrig := RandomSetup(r, 100) + + AssertInvariants(t, "no operation", + poolOrig, validatorsOrig, + poolOrig, validatorsOrig, 0) + + for j := 0; j < 5; j++ { + index := int(r.Int31n(int32(len(validatorsOrig)))) + poolMod, validatorMod, tokens, msg := RandomOperation(r)(r, poolOrig, validatorsOrig[index]) + validatorsMod := make([]Validator, len(validatorsOrig)) + copy(validatorsMod[:], validatorsOrig[:]) + validatorsMod[index] = validatorMod + + AssertInvariants(t, msg, + poolOrig, validatorsOrig, + poolMod, validatorsMod, tokens) + + poolOrig = poolMod + validatorsOrig = validatorsMod + + } + } +} + +func TestHumanReadableString(t *testing.T) { + val := NewValidator(addr1, pk1, Description{}) + + // NOTE: Being that the validator's keypair is random, we cannot test the + // actual contents of the string. + valStr, err := val.HumanReadableString() + require.Nil(t, err) + require.NotEmpty(t, valStr) +} diff --git a/x/stake/types/wire.go b/x/stake/types/wire.go new file mode 100644 index 000000000..86f9f5f09 --- /dev/null +++ b/x/stake/types/wire.go @@ -0,0 +1,27 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/wire" +) + +// Register concrete types on wire codec +func RegisterWire(cdc *wire.Codec) { + cdc.RegisterConcrete(MsgCreateValidator{}, "cosmos-sdk/MsgCreateValidator", nil) + cdc.RegisterConcrete(MsgEditValidator{}, "cosmos-sdk/MsgEditValidator", nil) + cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil) + cdc.RegisterConcrete(MsgBeginUnbonding{}, "cosmos-sdk/BeginUnbonding", nil) + cdc.RegisterConcrete(MsgCompleteUnbonding{}, "cosmos-sdk/CompleteUnbonding", nil) + cdc.RegisterConcrete(MsgBeginRedelegate{}, "cosmos-sdk/BeginRedelegate", nil) + cdc.RegisterConcrete(MsgCompleteRedelegate{}, "cosmos-sdk/CompleteRedelegate", nil) +} + +// generic sealed codec to be used throughout sdk +var MsgCdc *wire.Codec + +func init() { + cdc := wire.NewCodec() + RegisterWire(cdc) + wire.RegisterCrypto(cdc) + MsgCdc = cdc + //MsgCdc = cdc.Seal() //TODO use when upgraded to go-amino 0.9.10 +} diff --git a/x/stake/validator.go b/x/stake/validator.go deleted file mode 100644 index 3b135c12b..000000000 --- a/x/stake/validator.go +++ /dev/null @@ -1,286 +0,0 @@ -package stake - -import ( - "bytes" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - tmtypes "github.com/tendermint/tendermint/types" -) - -// Validator defines the total amount of bond shares and their exchange rate to -// coins. Accumulation of interest is modelled as an in increase in the -// exchange rate, and slashing as a decrease. When coins are delegated to this -// validator, the validator is credited with a Delegation whose number of -// bond shares is based on the amount of coins delegated divided by the current -// exchange rate. Voting power can be calculated as total bonds multiplied by -// exchange rate. -type Validator struct { - Owner sdk.Address `json:"owner"` // sender of BondTx - UnbondTx returns here - PubKey crypto.PubKey `json:"pub_key"` // pubkey of validator - Revoked bool `json:"revoked"` // has the validator been revoked from bonded status? - - PoolShares PoolShares `json:"pool_shares"` // total shares for tokens held in the pool - DelegatorShares sdk.Rat `json:"delegator_shares"` // total shares issued to a validator's delegators - - Description Description `json:"description"` // description terms for the validator - BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator - BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change - ProposerRewardPool sdk.Coins `json:"proposer_reward_pool"` // XXX reward pool collected from being the proposer - - Commission sdk.Rat `json:"commission"` // XXX the commission rate of fees charged to any delegators - CommissionMax sdk.Rat `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge - CommissionChangeRate sdk.Rat `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission - CommissionChangeToday sdk.Rat `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time) - - // fee related - PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // total shares of a global hold pools -} - -// Validators - list of Validators -type Validators []Validator - -// NewValidator - initialize a new validator -func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Description) Validator { - return Validator{ - Owner: owner, - PubKey: pubKey, - Revoked: false, - PoolShares: NewUnbondedShares(sdk.ZeroRat()), - DelegatorShares: sdk.ZeroRat(), - Description: description, - BondHeight: int64(0), - BondIntraTxCounter: int16(0), - ProposerRewardPool: sdk.Coins{}, - Commission: sdk.ZeroRat(), - CommissionMax: sdk.ZeroRat(), - CommissionChangeRate: sdk.ZeroRat(), - CommissionChangeToday: sdk.ZeroRat(), - PrevBondedShares: sdk.ZeroRat(), - } -} - -// only the vitals - does not check bond height of IntraTxCounter -func (v Validator) equal(c2 Validator) bool { - return v.PubKey.Equals(c2.PubKey) && - bytes.Equal(v.Owner, c2.Owner) && - v.PoolShares.Equal(c2.PoolShares) && - v.DelegatorShares.Equal(c2.DelegatorShares) && - v.Description == c2.Description && - //v.BondHeight == c2.BondHeight && - //v.BondIntraTxCounter == c2.BondIntraTxCounter && // counter is always changing - v.ProposerRewardPool.IsEqual(c2.ProposerRewardPool) && - v.Commission.Equal(c2.Commission) && - v.CommissionMax.Equal(c2.CommissionMax) && - v.CommissionChangeRate.Equal(c2.CommissionChangeRate) && - v.CommissionChangeToday.Equal(c2.CommissionChangeToday) && - v.PrevBondedShares.Equal(c2.PrevBondedShares) -} - -// Description - description fields for a validator -type Description struct { - Moniker string `json:"moniker"` - Identity string `json:"identity"` - Website string `json:"website"` - Details string `json:"details"` -} - -func NewDescription(moniker, identity, website, details string) Description { - return Description{ - Moniker: moniker, - Identity: identity, - Website: website, - Details: details, - } -} - -//XXX updateDescription function which enforce limit to number of description characters - -// abci validator from stake validator type -func (v Validator) abciValidator(cdc *wire.Codec) abci.Validator { - return abci.Validator{ - PubKey: tmtypes.TM2PB.PubKey(v.PubKey), - Power: v.PoolShares.Bonded().Evaluate(), - } -} - -// abci validator from stake validator type -// with zero power used for validator updates -func (v Validator) abciValidatorZero(cdc *wire.Codec) abci.Validator { - return abci.Validator{ - PubKey: tmtypes.TM2PB.PubKey(v.PubKey), - Power: 0, - } -} - -// abci validator from stake validator type -func (v Validator) Status() sdk.BondStatus { - return v.PoolShares.Status -} - -// update the location of the shares within a validator if its bond status has changed -func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, Pool) { - var tokens int64 - - switch v.Status() { - case sdk.Unbonded: - if NewStatus == sdk.Unbonded { - return v, pool - } - pool, tokens = pool.removeSharesUnbonded(v.PoolShares.Amount) - - case sdk.Unbonding: - if NewStatus == sdk.Unbonding { - return v, pool - } - pool, tokens = pool.removeSharesUnbonding(v.PoolShares.Amount) - - case sdk.Bonded: - if NewStatus == sdk.Bonded { // return if nothing needs switching - return v, pool - } - pool, tokens = pool.removeSharesBonded(v.PoolShares.Amount) - } - - switch NewStatus { - case sdk.Unbonded: - pool, v.PoolShares = pool.addTokensUnbonded(tokens) - case sdk.Unbonding: - pool, v.PoolShares = pool.addTokensUnbonding(tokens) - case sdk.Bonded: - pool, v.PoolShares = pool.addTokensBonded(tokens) - } - return v, pool -} - -// Remove pool shares -// Returns corresponding tokens, which could be burned (e.g. when slashing -// a validator) or redistributed elsewhere -func (v Validator) removePoolShares(pool Pool, poolShares sdk.Rat) (Validator, Pool, int64) { - var tokens int64 - switch v.Status() { - case sdk.Unbonded: - pool, tokens = pool.removeSharesUnbonded(poolShares) - case sdk.Unbonding: - pool, tokens = pool.removeSharesUnbonding(poolShares) - case sdk.Bonded: - pool, tokens = pool.removeSharesBonded(poolShares) - } - v.PoolShares.Amount = v.PoolShares.Amount.Sub(poolShares) - return v, pool, tokens -} - -// XXX TEST -// get the power or potential power for a validator -// if bonded, the power is the BondedShares -// if not bonded, the power is the amount of bonded shares which the -// the validator would have it was bonded -func (v Validator) EquivalentBondedShares(pool Pool) (eqBondedShares sdk.Rat) { - return v.PoolShares.ToBonded(pool).Amount -} - -//_________________________________________________________________________________________________________ - -// XXX Audit this function further to make sure it's correct -// add tokens to a validator -func (v Validator) addTokensFromDel(pool Pool, - amount int64) (validator2 Validator, p2 Pool, issuedDelegatorShares sdk.Rat) { - - exRate := v.DelegatorShareExRate(pool) // bshr/delshr - - var poolShares PoolShares - var equivalentBondedShares sdk.Rat - switch v.Status() { - case sdk.Unbonded: - pool, poolShares = pool.addTokensUnbonded(amount) - case sdk.Unbonding: - pool, poolShares = pool.addTokensUnbonding(amount) - case sdk.Bonded: - pool, poolShares = pool.addTokensBonded(amount) - } - v.PoolShares.Amount = v.PoolShares.Amount.Add(poolShares.Amount) - equivalentBondedShares = poolShares.ToBonded(pool).Amount - - issuedDelegatorShares = equivalentBondedShares.Quo(exRate) // bshr/(bshr/delshr) = delshr - v.DelegatorShares = v.DelegatorShares.Add(issuedDelegatorShares) - - return v, pool, issuedDelegatorShares -} - -// remove delegator shares from a validator -// NOTE this function assumes the shares have already been updated for the validator status -func (v Validator) removeDelShares(pool Pool, - delShares sdk.Rat) (validator2 Validator, p2 Pool, createdCoins int64) { - - amount := v.DelegatorShareExRate(pool).Mul(delShares) - eqBondedSharesToRemove := NewBondedShares(amount) - v.DelegatorShares = v.DelegatorShares.Sub(delShares) - - switch v.Status() { - case sdk.Unbonded: - unbondedShares := eqBondedSharesToRemove.ToUnbonded(pool).Amount - pool, createdCoins = pool.removeSharesUnbonded(unbondedShares) - v.PoolShares.Amount = v.PoolShares.Amount.Sub(unbondedShares) - case sdk.Unbonding: - unbondingShares := eqBondedSharesToRemove.ToUnbonding(pool).Amount - pool, createdCoins = pool.removeSharesUnbonding(unbondingShares) - v.PoolShares.Amount = v.PoolShares.Amount.Sub(unbondingShares) - case sdk.Bonded: - pool, createdCoins = pool.removeSharesBonded(eqBondedSharesToRemove.Amount) - v.PoolShares.Amount = v.PoolShares.Amount.Sub(eqBondedSharesToRemove.Amount) - } - return v, pool, createdCoins -} - -// get the exchange rate of tokens over delegator shares -// UNITS: eq-val-bonded-shares/delegator-shares -func (v Validator) DelegatorShareExRate(pool Pool) sdk.Rat { - if v.DelegatorShares.IsZero() { - return sdk.OneRat() - } - eqBondedShares := v.PoolShares.ToBonded(pool).Amount - return eqBondedShares.Quo(v.DelegatorShares) -} - -//______________________________________________________________________ - -// ensure fulfills the sdk validator types -var _ sdk.Validator = Validator{} - -// nolint - for sdk.Validator -func (v Validator) GetMoniker() string { return v.Description.Moniker } -func (v Validator) GetStatus() sdk.BondStatus { return v.Status() } -func (v Validator) GetOwner() sdk.Address { return v.Owner } -func (v Validator) GetPubKey() crypto.PubKey { return v.PubKey } -func (v Validator) GetPower() sdk.Rat { return v.PoolShares.Bonded() } -func (v Validator) GetBondHeight() int64 { return v.BondHeight } - -//Human Friendly pretty printer -func (v Validator) HumanReadableString() (string, error) { - bechOwner, err := sdk.Bech32ifyAcc(v.Owner) - if err != nil { - return "", err - } - bechVal, err := sdk.Bech32ifyValPub(v.PubKey) - if err != nil { - return "", err - } - resp := "Validator \n" - resp += fmt.Sprintf("Owner: %s\n", bechOwner) - resp += fmt.Sprintf("Validator: %s\n", bechVal) - resp += fmt.Sprintf("Shares: Status %s, Amount: %s\n", sdk.BondStatusToString(v.PoolShares.Status), v.PoolShares.Amount.String()) - resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares.String()) - resp += fmt.Sprintf("Description: %s\n", v.Description) - resp += fmt.Sprintf("Bond Height: %d\n", v.BondHeight) - resp += fmt.Sprintf("Proposer Reward Pool: %s\n", v.ProposerRewardPool.String()) - resp += fmt.Sprintf("Commission: %s\n", v.Commission.String()) - resp += fmt.Sprintf("Max Commission Rate: %s\n", v.CommissionMax.String()) - resp += fmt.Sprintf("Comission Change Rate: %s\n", v.CommissionChangeRate.String()) - resp += fmt.Sprintf("Commission Change Today: %s\n", v.CommissionChangeToday.String()) - resp += fmt.Sprintf("Previously Bonded Stares: %s\n", v.PrevBondedShares.String()) - - return resp, nil -} diff --git a/x/stake/validator_test.go b/x/stake/validator_test.go deleted file mode 100644 index 5834e5989..000000000 --- a/x/stake/validator_test.go +++ /dev/null @@ -1,408 +0,0 @@ -package stake - -import ( - "fmt" - "math/rand" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAddTokensValidatorBonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - pool := keeper.GetPool(ctx) - val := NewValidator(addrs[0], pks[0], Description{}) - val, pool = val.UpdateStatus(pool, sdk.Bonded) - val, pool, delShares := val.addTokensFromDel(pool, 10) - - assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) - assert.Equal(t, sdk.OneRat(), pool.bondedShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondingShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondedShareExRate()) - - assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) - assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Bonded())) -} - -func TestAddTokensValidatorUnbonding(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - pool := keeper.GetPool(ctx) - val := NewValidator(addrs[0], pks[0], Description{}) - val, pool = val.UpdateStatus(pool, sdk.Unbonding) - val, pool, delShares := val.addTokensFromDel(pool, 10) - - assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) - assert.Equal(t, sdk.OneRat(), pool.bondedShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondingShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondedShareExRate()) - - assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) - assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonding())) -} - -func TestAddTokensValidatorUnbonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - pool := keeper.GetPool(ctx) - val := NewValidator(addrs[0], pks[0], Description{}) - val, pool = val.UpdateStatus(pool, sdk.Unbonded) - val, pool, delShares := val.addTokensFromDel(pool, 10) - - assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) - assert.Equal(t, sdk.OneRat(), pool.bondedShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondingShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondedShareExRate()) - - assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) - assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonded())) -} - -// TODO refactor to make simpler like the AddToken tests above -func TestRemoveShares(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - poolA := keeper.GetPool(ctx) - valA := Validator{ - Owner: addrs[0], - PubKey: pks[0], - PoolShares: NewBondedShares(sdk.NewRat(9)), - DelegatorShares: sdk.NewRat(9), - } - poolA.BondedTokens = valA.PoolShares.Bonded().Evaluate() - poolA.BondedShares = valA.PoolShares.Bonded() - assert.Equal(t, valA.DelegatorShareExRate(poolA), sdk.OneRat()) - assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat()) - assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat()) - valB, poolB, coinsB := valA.removeDelShares(poolA, sdk.NewRat(10)) - - // coins were created - assert.Equal(t, coinsB, int64(10)) - // pool shares were removed - assert.Equal(t, valB.PoolShares.Bonded(), valA.PoolShares.Bonded().Sub(sdk.NewRat(10).Mul(valA.DelegatorShareExRate(poolA)))) - // conservation of tokens - assert.Equal(t, poolB.UnbondedTokens+poolB.BondedTokens+coinsB, poolA.UnbondedTokens+poolA.BondedTokens) - - // specific case from random tests - poolShares := sdk.NewRat(5102) - delShares := sdk.NewRat(115) - val := Validator{ - Owner: addrs[0], - PubKey: pks[0], - PoolShares: NewBondedShares(poolShares), - DelegatorShares: delShares, - } - pool := Pool{ - BondedShares: sdk.NewRat(248305), - UnbondedShares: sdk.NewRat(232147), - BondedTokens: 248305, - UnbondedTokens: 232147, - InflationLastTime: 0, - Inflation: sdk.NewRat(7, 100), - } - shares := sdk.NewRat(29) - msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) - msg = fmt.Sprintf("Removed %v shares from %s", shares, msg) - _, newPool, tokens := val.removeDelShares(pool, shares) - require.Equal(t, - tokens+newPool.UnbondedTokens+newPool.BondedTokens, - pool.BondedTokens+pool.UnbondedTokens, - "Tokens were not conserved: %s", msg) -} - -func TestUpdateStatus(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - pool := keeper.GetPool(ctx) - - val := NewValidator(addrs[0], pks[0], Description{}) - val, pool, _ = val.addTokensFromDel(pool, 100) - assert.Equal(t, int64(0), val.PoolShares.Bonded().Evaluate()) - assert.Equal(t, int64(0), val.PoolShares.Unbonding().Evaluate()) - assert.Equal(t, int64(100), val.PoolShares.Unbonded().Evaluate()) - assert.Equal(t, int64(0), pool.BondedTokens) - assert.Equal(t, int64(0), pool.UnbondingTokens) - assert.Equal(t, int64(100), pool.UnbondedTokens) - - val, pool = val.UpdateStatus(pool, sdk.Unbonding) - assert.Equal(t, int64(0), val.PoolShares.Bonded().Evaluate()) - assert.Equal(t, int64(100), val.PoolShares.Unbonding().Evaluate()) - assert.Equal(t, int64(0), val.PoolShares.Unbonded().Evaluate()) - assert.Equal(t, int64(0), pool.BondedTokens) - assert.Equal(t, int64(100), pool.UnbondingTokens) - assert.Equal(t, int64(0), pool.UnbondedTokens) - - val, pool = val.UpdateStatus(pool, sdk.Bonded) - assert.Equal(t, int64(100), val.PoolShares.Bonded().Evaluate()) - assert.Equal(t, int64(0), val.PoolShares.Unbonding().Evaluate()) - assert.Equal(t, int64(0), val.PoolShares.Unbonded().Evaluate()) - assert.Equal(t, int64(100), pool.BondedTokens) - assert.Equal(t, int64(0), pool.UnbondingTokens) - assert.Equal(t, int64(0), pool.UnbondedTokens) -} - -//________________________________________________________________________________ -// TODO refactor this random setup - -// generate a random validator -func randomValidator(r *rand.Rand, i int) Validator { - - poolSharesAmt := sdk.NewRat(int64(r.Int31n(10000))) - delShares := sdk.NewRat(int64(r.Int31n(10000))) - - var pShares PoolShares - if r.Float64() < float64(0.5) { - pShares = NewBondedShares(poolSharesAmt) - } else { - pShares = NewUnbondedShares(poolSharesAmt) - } - return Validator{ - Owner: addrs[i], - PubKey: pks[i], - PoolShares: pShares, - DelegatorShares: delShares, - } -} - -// generate a random staking state -func randomSetup(r *rand.Rand, numValidators int) (Pool, Validators) { - pool := InitialPool() - - validators := make([]Validator, numValidators) - for i := 0; i < numValidators; i++ { - validator := randomValidator(r, i) - if validator.Status() == sdk.Bonded { - pool.BondedShares = pool.BondedShares.Add(validator.PoolShares.Bonded()) - pool.BondedTokens += validator.PoolShares.Bonded().Evaluate() - } else if validator.Status() == sdk.Unbonded { - pool.UnbondedShares = pool.UnbondedShares.Add(validator.PoolShares.Unbonded()) - pool.UnbondedTokens += validator.PoolShares.Unbonded().Evaluate() - } - validators[i] = validator - } - return pool, validators -} - -// any operation that transforms staking state -// takes in RNG instance, pool, validator -// returns updated pool, updated validator, delta tokens, descriptive message -type Operation func(r *rand.Rand, pool Pool, c Validator) (Pool, Validator, int64, string) - -// operation: bond or unbond a validator depending on current status -func OpBondOrUnbond(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { - var msg string - var newStatus sdk.BondStatus - if val.Status() == sdk.Bonded { - msg = fmt.Sprintf("sdk.Unbonded previously bonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) - newStatus = sdk.Unbonded - - } else if val.Status() == sdk.Unbonded { - msg = fmt.Sprintf("sdk.Bonded previously unbonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) - newStatus = sdk.Bonded - } - val, pool = val.UpdateStatus(pool, newStatus) - return pool, val, 0, msg -} - -// operation: add a random number of tokens to a validator -func OpAddTokens(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { - tokens := int64(r.Int31n(1000)) - msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) - val, pool, _ = val.addTokensFromDel(pool, tokens) - msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) - return pool, val, -1 * tokens, msg // tokens are removed so for accounting must be negative -} - -// operation: remove a random number of shares from a validator -func OpRemoveShares(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { - var shares sdk.Rat - for { - shares = sdk.NewRat(int64(r.Int31n(1000))) - if shares.LT(val.DelegatorShares) { - break - } - } - - msg := fmt.Sprintf("Removed %v shares from validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - shares, val.Owner, val.Status(), val.PoolShares, val.DelegatorShares, val.DelegatorShareExRate(pool)) - - val, pool, tokens := val.removeDelShares(pool, shares) - return pool, val, tokens, msg -} - -// pick a random staking operation -func randomOperation(r *rand.Rand) Operation { - operations := []Operation{ - OpBondOrUnbond, - OpAddTokens, - OpRemoveShares, - } - r.Shuffle(len(operations), func(i, j int) { - operations[i], operations[j] = operations[j], operations[i] - }) - return operations[0] -} - -// ensure invariants that should always be true are true -func assertInvariants(t *testing.T, msg string, - pOrig Pool, cOrig Validators, pMod Pool, vMods Validators, tokens int64) { - - // total tokens conserved - require.Equal(t, - pOrig.UnbondedTokens+pOrig.BondedTokens, - pMod.UnbondedTokens+pMod.BondedTokens+tokens, - "Tokens not conserved - msg: %v\n, pOrig.PoolShares.Bonded(): %v, pOrig.PoolShares.Unbonded(): %v, pMod.PoolShares.Bonded(): %v, pMod.PoolShares.Unbonded(): %v, pOrig.UnbondedTokens: %v, pOrig.BondedTokens: %v, pMod.UnbondedTokens: %v, pMod.BondedTokens: %v, tokens: %v\n", - msg, - pOrig.BondedShares, pOrig.UnbondedShares, - pMod.BondedShares, pMod.UnbondedShares, - pOrig.UnbondedTokens, pOrig.BondedTokens, - pMod.UnbondedTokens, pMod.BondedTokens, tokens) - - // nonnegative bonded shares - require.False(t, pMod.BondedShares.LT(sdk.ZeroRat()), - "Negative bonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", - msg, pOrig, pMod, tokens) - - // nonnegative unbonded shares - require.False(t, pMod.UnbondedShares.LT(sdk.ZeroRat()), - "Negative unbonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", - msg, pOrig, pMod, tokens) - - // nonnegative bonded ex rate - require.False(t, pMod.bondedShareExRate().LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative bondedShareExRate: %d", - msg, pMod.bondedShareExRate().Evaluate()) - - // nonnegative unbonded ex rate - require.False(t, pMod.unbondedShareExRate().LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative unbondedShareExRate: %d", - msg, pMod.unbondedShareExRate().Evaluate()) - - for _, vMod := range vMods { - - // nonnegative ex rate - require.False(t, vMod.DelegatorShareExRate(pMod).LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative validator.DelegatorShareExRate(): %v (validator.Owner: %s)", - msg, - vMod.DelegatorShareExRate(pMod), - vMod.Owner, - ) - - // nonnegative poolShares - require.False(t, vMod.PoolShares.Bonded().LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative validator.PoolShares.Bonded(): %v (validator.DelegatorShares: %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", - msg, - vMod.PoolShares.Bonded(), - vMod.DelegatorShares, - vMod.DelegatorShareExRate(pMod), - vMod.Owner, - ) - - // nonnegative delShares - require.False(t, vMod.DelegatorShares.LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative validator.DelegatorShares: %v (validator.PoolShares.Bonded(): %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", - msg, - vMod.DelegatorShares, - vMod.PoolShares.Bonded(), - vMod.DelegatorShareExRate(pMod), - vMod.Owner, - ) - - } - -} - -func TestPossibleOverflow(t *testing.T) { - poolShares := sdk.NewRat(2159) - delShares := sdk.NewRat(391432570689183511).Quo(sdk.NewRat(40113011844664)) - val := Validator{ - Owner: addrs[0], - PubKey: pks[0], - PoolShares: NewBondedShares(poolShares), - DelegatorShares: delShares, - } - pool := Pool{ - BondedShares: poolShares, - UnbondedShares: sdk.ZeroRat(), - BondedTokens: poolShares.Evaluate(), - UnbondedTokens: 0, - InflationLastTime: 0, - Inflation: sdk.NewRat(7, 100), - } - tokens := int64(71) - msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) - newValidator, _, _ := val.addTokensFromDel(pool, tokens) - - msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) - require.False(t, newValidator.DelegatorShareExRate(pool).LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative DelegatorShareExRate(): %v", - msg, newValidator.DelegatorShareExRate(pool)) -} - -// run random operations in a random order on a random single-validator state, assert invariants hold -func TestSingleValidatorIntegrationInvariants(t *testing.T) { - r := rand.New(rand.NewSource(41)) - - for i := 0; i < 10; i++ { - poolOrig, validatorsOrig := randomSetup(r, 1) - require.Equal(t, 1, len(validatorsOrig)) - - // sanity check - assertInvariants(t, "no operation", - poolOrig, validatorsOrig, - poolOrig, validatorsOrig, 0) - - for j := 0; j < 5; j++ { - poolMod, validatorMod, tokens, msg := randomOperation(r)(r, poolOrig, validatorsOrig[0]) - - validatorsMod := make([]Validator, len(validatorsOrig)) - copy(validatorsMod[:], validatorsOrig[:]) - require.Equal(t, 1, len(validatorsOrig), "j %v", j) - require.Equal(t, 1, len(validatorsMod), "j %v", j) - validatorsMod[0] = validatorMod - - assertInvariants(t, msg, - poolOrig, validatorsOrig, - poolMod, validatorsMod, tokens) - - poolOrig = poolMod - validatorsOrig = validatorsMod - } - } -} - -// run random operations in a random order on a random multi-validator state, assert invariants hold -func TestMultiValidatorIntegrationInvariants(t *testing.T) { - r := rand.New(rand.NewSource(42)) - - for i := 0; i < 10; i++ { - poolOrig, validatorsOrig := randomSetup(r, 100) - - assertInvariants(t, "no operation", - poolOrig, validatorsOrig, - poolOrig, validatorsOrig, 0) - - for j := 0; j < 5; j++ { - index := int(r.Int31n(int32(len(validatorsOrig)))) - poolMod, validatorMod, tokens, msg := randomOperation(r)(r, poolOrig, validatorsOrig[index]) - validatorsMod := make([]Validator, len(validatorsOrig)) - copy(validatorsMod[:], validatorsOrig[:]) - validatorsMod[index] = validatorMod - - assertInvariants(t, msg, - poolOrig, validatorsOrig, - poolMod, validatorsMod, tokens) - - poolOrig = poolMod - validatorsOrig = validatorsMod - - } - } -} diff --git a/x/stake/view_slash_keeper.go b/x/stake/view_slash_keeper.go deleted file mode 100644 index cb7d16ce9..000000000 --- a/x/stake/view_slash_keeper.go +++ /dev/null @@ -1,29 +0,0 @@ -package stake - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// keeper to view information & slash validators -// will be used by governance module -type ViewSlashKeeper struct { - keeper Keeper -} - -// NewViewSlashKeeper creates a keeper restricted to -// viewing information & slashing validators -func NewViewSlashKeeper(k Keeper) ViewSlashKeeper { - return ViewSlashKeeper{k} -} - -// load a delegator bond -func (v ViewSlashKeeper) GetDelegation(ctx sdk.Context, - delegatorAddr sdk.Address, validatorAddr sdk.Address) (bond Delegation, found bool) { - return v.keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) -} - -// load n delegator bonds -func (v ViewSlashKeeper) GetDelegations(ctx sdk.Context, - delegator sdk.Address, maxRetrieve int16) (bonds []Delegation) { - return v.keeper.GetDelegations(ctx, delegator, maxRetrieve) -} diff --git a/x/stake/view_slash_keeper_test.go b/x/stake/view_slash_keeper_test.go deleted file mode 100644 index 7f481fc60..000000000 --- a/x/stake/view_slash_keeper_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package stake - -import ( - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// tests GetDelegation, GetDelegations -func TestViewSlashBond(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - //construct the validators - amts := []int64{9, 8, 7} - var validators [3]Validator - for i, amt := range amts { - validators[i] = Validator{ - Owner: addrVals[i], - PubKey: pks[i], - PoolShares: NewUnbondedShares(sdk.NewRat(amt)), - DelegatorShares: sdk.NewRat(amt), - } - } - - // first add a validators[0] to delegate too - keeper.updateValidator(ctx, validators[0]) - - bond1to1 := Delegation{ - DelegatorAddr: addrDels[0], - ValidatorAddr: addrVals[0], - Shares: sdk.NewRat(9), - } - - viewSlashKeeper := NewViewSlashKeeper(keeper) - - // check the empty keeper first - _, found := viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.False(t, found) - - // set and retrieve a record - keeper.setDelegation(ctx, bond1to1) - resBond, found := viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.True(t, found) - assert.True(t, bond1to1.equal(resBond)) - - // modify a records, save, and retrieve - bond1to1.Shares = sdk.NewRat(99) - keeper.setDelegation(ctx, bond1to1) - resBond, found = viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.True(t, found) - assert.True(t, bond1to1.equal(resBond)) - - // add some more records - keeper.updateValidator(ctx, validators[1]) - keeper.updateValidator(ctx, validators[2]) - bond1to2 := Delegation{addrDels[0], addrVals[1], sdk.NewRat(9), 0} - bond1to3 := Delegation{addrDels[0], addrVals[2], sdk.NewRat(9), 1} - bond2to1 := Delegation{addrDels[1], addrVals[0], sdk.NewRat(9), 2} - bond2to2 := Delegation{addrDels[1], addrVals[1], sdk.NewRat(9), 3} - bond2to3 := Delegation{addrDels[1], addrVals[2], sdk.NewRat(9), 4} - keeper.setDelegation(ctx, bond1to2) - keeper.setDelegation(ctx, bond1to3) - keeper.setDelegation(ctx, bond2to1) - keeper.setDelegation(ctx, bond2to2) - keeper.setDelegation(ctx, bond2to3) - - // test all bond retrieve capabilities - resBonds := viewSlashKeeper.GetDelegations(ctx, addrDels[0], 5) - require.Equal(t, 3, len(resBonds)) - assert.True(t, bond1to1.equal(resBonds[0])) - assert.True(t, bond1to2.equal(resBonds[1])) - assert.True(t, bond1to3.equal(resBonds[2])) - resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[0], 3) - require.Equal(t, 3, len(resBonds)) - resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[0], 2) - require.Equal(t, 2, len(resBonds)) - resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[1], 5) - require.Equal(t, 3, len(resBonds)) - assert.True(t, bond2to1.equal(resBonds[0])) - assert.True(t, bond2to2.equal(resBonds[1])) - assert.True(t, bond2to3.equal(resBonds[2])) - -} diff --git a/x/stake/wire.go b/x/stake/wire.go deleted file mode 100644 index c0b0be71f..000000000 --- a/x/stake/wire.go +++ /dev/null @@ -1,20 +0,0 @@ -package stake - -import ( - "github.com/cosmos/cosmos-sdk/wire" -) - -// Register concrete types on wire codec -func RegisterWire(cdc *wire.Codec) { - cdc.RegisterConcrete(MsgCreateValidator{}, "cosmos-sdk/MsgCreateValidator", nil) - cdc.RegisterConcrete(MsgEditValidator{}, "cosmos-sdk/MsgEditValidator", nil) - cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil) - cdc.RegisterConcrete(MsgUnbond{}, "cosmos-sdk/MsgUnbond", nil) -} - -var msgCdc = wire.NewCodec() - -func init() { - RegisterWire(msgCdc) - wire.RegisterCrypto(msgCdc) -}