Merge pull request #3568 from cosmos/release/v0.31.0

Release v0.31.0
This commit is contained in:
Ethan Buchman 2019-02-09 10:29:47 -05:00 committed by GitHub
commit 09688d03f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
380 changed files with 13552 additions and 8171 deletions

View File

@ -3,7 +3,7 @@ version: 2
defaults: &linux_defaults defaults: &linux_defaults
working_directory: /go/src/github.com/cosmos/cosmos-sdk working_directory: /go/src/github.com/cosmos/cosmos-sdk
docker: docker:
- image: circleci/golang:1.11.4 - image: circleci/golang:1.11.5
environment: environment:
GOBIN: /tmp/workspace/bin GOBIN: /tmp/workspace/bin
@ -17,7 +17,7 @@ macos_config: &macos_defaults
xcode: "10.1.0" xcode: "10.1.0"
working_directory: /Users/distiller/project/src/github.com/cosmos/cosmos-sdk working_directory: /Users/distiller/project/src/github.com/cosmos/cosmos-sdk
environment: environment:
GO_VERSION: "1.11.4" GO_VERSION: "1.11.5"
set_macos_env: &macos_env set_macos_env: &macos_env
run: run:
@ -82,6 +82,7 @@ jobs:
name: Get metalinter name: Get metalinter
command: | command: |
export PATH="$GOBIN:$PATH" export PATH="$GOBIN:$PATH"
make devtools-clean
make devtools make devtools
- run: - run:
name: Lint source name: Lint source
@ -171,7 +172,7 @@ jobs:
name: Test multi-seed Gaia simulation long name: Test multi-seed Gaia simulation long
command: | command: |
export PATH="$GOBIN:$PATH" export PATH="$GOBIN:$PATH"
scripts/multisim.sh 800 50 TestFullGaiaSimulation scripts/multisim.sh 500 50 TestFullGaiaSimulation
test_sim_gaia_multi_seed: test_sim_gaia_multi_seed:
<<: *linux_defaults <<: *linux_defaults
@ -200,11 +201,10 @@ jobs:
name: Run tests name: Run tests
command: | command: |
export PATH="$GOBIN:$PATH" export PATH="$GOBIN:$PATH"
make install
export VERSION="$(git describe --tags --long | sed 's/v\(.*\)/\1/')" export VERSION="$(git describe --tags --long | sed 's/v\(.*\)/\1/')"
for pkg in $(go list github.com/cosmos/cosmos-sdk/... | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test | grep -v '/simulation' | circleci tests split --split-by=timings); do for pkg in $(go list ./... | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test | grep -v '/simulation' | circleci tests split --split-by=timings); do
id=$(basename "$pkg") id=$(echo "$pkg" | sed 's|[/.]|_|g')
GOCACHE=off go test -timeout 8m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log" GOCACHE=off go test -timeout 8m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic -tags='ledger test_ledger_mock' "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"
done done
- persist_to_workspace: - persist_to_workspace:
root: /tmp/workspace root: /tmp/workspace
@ -226,10 +226,20 @@ jobs:
command: | command: |
set -ex set -ex
echo "--> Concatenating profiles:"
ls /tmp/workspace/profiles/
echo "mode: atomic" > coverage.txt echo "mode: atomic" > coverage.txt
for prof in $(ls /tmp/workspace/profiles/); do for prof in $(ls /tmp/workspace/profiles/); do
tail -n +2 /tmp/workspace/profiles/"$prof" >> coverage.txt tail -n +2 /tmp/workspace/profiles/"$prof" >> coverage.txt
done done
- run:
name: filter out DONTCOVER
command: |
excludelist="$(find ./ -type f -name '*.go' | xargs grep -l 'DONTCOVER' | xargs realpath --relative-to=$GOPATH/src)"
for filename in ${excludelist}; do
echo "Excluding ${filename} ..."
sed -i "\%${filename}:%d" coverage.txt
done
- run: - run:
name: upload name: upload
command: bash <(curl -s https://codecov.io/bash) -f coverage.txt command: bash <(curl -s https://codecov.io/bash) -f coverage.txt
@ -243,7 +253,7 @@ jobs:
GOPATH: /home/circleci/.go_workspace/ GOPATH: /home/circleci/.go_workspace/
GOOS: linux GOOS: linux
GOARCH: amd64 GOARCH: amd64
GO_VERSION: "1.11.4" GO_VERSION: "1.11.5"
parallelism: 1 parallelism: 1
steps: steps:
- checkout - checkout
@ -380,9 +390,7 @@ workflows:
- test_cover: - test_cover:
requires: requires:
- setup_dependencies - setup_dependencies
- localnet: - localnet
requires:
- setup_dependencies
- upload_coverage: - upload_coverage:
requires: requires:
- test_cover - test_cover

4
.github/CODEOWNERS vendored
View File

@ -4,4 +4,6 @@
* @ebuchman @rigelrozanski @cwgoes * @ebuchman @rigelrozanski @cwgoes
# Precious documentation # Precious documentation
/docs/ @zramsay @jolesbi /docs/README.md @zramsay
/docs/DOCS_README.md @zramsay
/docs/.vuepress/ @zramsay

View File

@ -1,5 +1,127 @@
# Changelog # Changelog
## 0.31.0
BREAKING CHANGES
* Gaia REST API (`gaiacli advanced rest-server`)
* [\#3284](https://github.com/cosmos/cosmos-sdk/issues/3284) Rename the `name`
field to `from` in the `base_req` body.
* [\#3485](https://github.com/cosmos/cosmos-sdk/pull/3485) Error responses are now JSON objects.
* [\#3477][distribution] endpoint changed "all_delegation_rewards" -> "delegator_total_rewards"
* Gaia CLI (`gaiacli`)
- [#3399](https://github.com/cosmos/cosmos-sdk/pull/3399) Add `gaiad validate-genesis` command to facilitate checking of genesis files
- [\#1894](https://github.com/cosmos/cosmos-sdk/issues/1894) `version` prints out short info by default. Add `--long` flag. Proper handling of `--format` flag introduced.
- [\#3465](https://github.com/cosmos/cosmos-sdk/issues/3465) `gaiacli rest-server` switched back to insecure mode by default:
- `--insecure` flag is removed.
- `--tls` is now used to enable secure layer.
- [\#3451](https://github.com/cosmos/cosmos-sdk/pull/3451) `gaiacli` now returns transactions in plain text including tags.
- [\#3497](https://github.com/cosmos/cosmos-sdk/issues/3497) `gaiad init` now takes moniker as required arguments, not as parameter.
* [\#3501](https://github.com/cosmos/cosmos-sdk/issues/3501) Change validator
address Bech32 encoding to consensus address in `tendermint-validator-set`.
* Gaia
* [\#3457](https://github.com/cosmos/cosmos-sdk/issues/3457) Changed governance tally validatorGovInfo to use sdk.Int power instead of sdk.Dec
* [\#3495](https://github.com/cosmos/cosmos-sdk/issues/3495) Added Validator Minimum Self Delegation
* Reintroduce OR semantics for tx fees
* SDK
* [\#2513](https://github.com/cosmos/cosmos-sdk/issues/2513) Tendermint updates are adjusted by 10^-6 relative to staking tokens,
* [\#3487](https://github.com/cosmos/cosmos-sdk/pull/3487) Move HTTP/REST utilities out of client/utils into a new dedicated client/rest package.
* [\#3490](https://github.com/cosmos/cosmos-sdk/issues/3490) ReadRESTReq() returns bool to avoid callers to write error responses twice.
* [\#3502](https://github.com/cosmos/cosmos-sdk/pull/3502) Fixes issue when comparing genesis states
* [\#3514](https://github.com/cosmos/cosmos-sdk/pull/3514) Various clean ups:
- Replace all GetKeyBase\* functions family in favor of NewKeyBaseFromDir and NewKeyBaseFromHomeFlag.
- Remove Get prefix from all TxBuilder's getters.
* [\#3522](https://github.com/cosmos/cosmos-sdk/pull/3522) Get rid of double negatives: Coins.IsNotNegative() -> Coins.IsAnyNegative().
* [\#3561](https://github.com/cosmos/cosmos-sdk/issues/3561) Don't unnecessarily store denominations in staking
FEATURES
* Gaia REST API
* [\#2358](https://github.com/cosmos/cosmos-sdk/issues/2358) Add distribution module REST interface
* Gaia CLI (`gaiacli`)
* [\#3429](https://github.com/cosmos/cosmos-sdk/issues/3429) Support querying
for all delegator distribution rewards.
* [\#3449](https://github.com/cosmos/cosmos-sdk/issues/3449) Proof verification now works with absence proofs
* [\#3484](https://github.com/cosmos/cosmos-sdk/issues/3484) Add support
vesting accounts to the add-genesis-account command.
* Gaia
- [\#3397](https://github.com/cosmos/cosmos-sdk/pull/3397) Implement genesis file sanitization to avoid failures at chain init.
* [\#3428](https://github.com/cosmos/cosmos-sdk/issues/3428) Run the simulation from a particular genesis state loaded from a file
* SDK
* [\#3270](https://github.com/cosmos/cosmos-sdk/issues/3270) [x/staking] limit number of ongoing unbonding delegations /redelegations per pair/trio
* [\#3477][distribution] new query endpoint "delegator_validators"
* [\#3514](https://github.com/cosmos/cosmos-sdk/pull/3514) Provided a lazy loading implementation of Keybase that locks the underlying
storage only for the time needed to perform the required operation. Also added Keybase reference to TxBuilder struct.
* [types] [\#2580](https://github.com/cosmos/cosmos-sdk/issues/2580) Addresses now Bech32 empty addresses to an empty string
IMPROVEMENTS
* Gaia REST API
* [\#3284](https://github.com/cosmos/cosmos-sdk/issues/3284) Update Gaia Lite
REST service to support the following:
* Automatic account number and sequence population when fields are omitted
* Generate only functionality no longer requires access to a local Keybase
* `from` field in the `base_req` body can be a Keybase name or account address
* [\#3423](https://github.com/cosmos/cosmos-sdk/issues/3423) Allow simulation
(auto gas) to work with generate only.
* [\#3514](https://github.com/cosmos/cosmos-sdk/pull/3514) REST server calls to keybase does not lock the underlying storage anymore.
* [\#3523](https://github.com/cosmos/cosmos-sdk/pull/3523) Added `/tx/encode` endpoint to serialize a JSON tx to base64-encoded Amino.
* Gaia CLI (`gaiacli`)
* [\#3476](https://github.com/cosmos/cosmos-sdk/issues/3476) New `withdraw-all-rewards` command to withdraw all delegations rewards for delegators.
* [\#3497](https://github.com/cosmos/cosmos-sdk/issues/3497) `gaiad gentx` supports `--ip` and `--node-id` flags to override defaults.
* [\#3518](https://github.com/cosmos/cosmos-sdk/issues/3518) Fix flow in
`keys add` to show the mnemonic by default.
* [\#3517](https://github.com/cosmos/cosmos-sdk/pull/3517) Increased test coverage
* [\#3523](https://github.com/cosmos/cosmos-sdk/pull/3523) Added `tx encode` command to serialize a JSON tx to base64-encoded Amino.
* Gaia
* [\#3418](https://github.com/cosmos/cosmos-sdk/issues/3418) Add vesting account
genesis validation checks to `GaiaValidateGenesisState`.
* [\#3420](https://github.com/cosmos/cosmos-sdk/issues/3420) Added maximum length to governance proposal descriptions and titles
* [\#3256](https://github.com/cosmos/cosmos-sdk/issues/3256) Add gas consumption
for tx size in the ante handler.
* [\#3454](https://github.com/cosmos/cosmos-sdk/pull/3454) Add `--jail-whitelist` to `gaiad export` to enable testing of complex exports
* [\#3424](https://github.com/cosmos/cosmos-sdk/issues/3424) Allow generation of gentxs with empty memo field.
* [\#3507](https://github.com/cosmos/cosmos-sdk/issues/3507) General cleanup, removal of unnecessary struct fields, undelegation bugfix, and comment clarification in x/staking and x/slashing
* SDK
* [\#2605] x/params add subkey accessing
* [\#2986](https://github.com/cosmos/cosmos-sdk/pull/2986) Store Refactor
* [\#3435](https://github.com/cosmos/cosmos-sdk/issues/3435) Test that store implementations do not allow nil values
* [\#2509](https://github.com/cosmos/cosmos-sdk/issues/2509) Sanitize all usage of Dec.RoundInt64()
* [\#556](https://github.com/cosmos/cosmos-sdk/issues/556) Increase `BaseApp`
test coverage.
* [\#3357](https://github.com/cosmos/cosmos-sdk/issues/3357) develop state-transitions.md for staking spec, missing states added to `state.md`
* [\#3552](https://github.com/cosmos/cosmos-sdk/pull/3552) Validate bit length when
deserializing `Int` types.
BUG FIXES
* Gaia CLI (`gaiacli`)
- [\#3417](https://github.com/cosmos/cosmos-sdk/pull/3417) Fix `q slashing signing-info` panic by ensuring safety of user input and properly returning not found error
- [\#3345](https://github.com/cosmos/cosmos-sdk/issues/3345) Upgrade ledger-cosmos-go dependency to v0.9.3 to pull
https://github.com/ZondaX/ledger-cosmos-go/commit/ed9aa39ce8df31bad1448c72d3d226bf2cb1a8d1 in order to fix a derivation path issue that causes `gaiacli keys add --recover`
to malfunction.
- [\#3419](https://github.com/cosmos/cosmos-sdk/pull/3419) Fix `q distr slashes` panic
- [\#3453](https://github.com/cosmos/cosmos-sdk/pull/3453) The `rest-server` command didn't respect persistent flags such as `--chain-id` and `--trust-node` if they were
passed on the command line.
- [\#3441](https://github.com/cosmos/cosmos-sdk/pull/3431) Improved resource management and connection handling (ledger devices). Fixes issue with DER vs BER signatures.
* Gaia
* [\#3486](https://github.com/cosmos/cosmos-sdk/pull/3486) Use AmountOf in
vesting accounts instead of zipping/aligning denominations.
## 0.30.0 ## 0.30.0
BREAKING CHANGES BREAKING CHANGES

93
Gopkg.lock generated
View File

@ -1,22 +1,6 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:7736fc6da04620727f8f3aa2ced8d77be8e074a302820937aa5993848c769b27"
name = "github.com/ZondaX/hid-go"
packages = ["."]
pruneopts = "UT"
revision = "48b08affede2cea076a3cf13b2e3f72ed262b743"
version = "v0.4.0"
[[projects]]
digest = "1:1ba351898f7efc68c7c9ff3145b920e478f716b077fdaaf06b967c5d883fa988"
name = "github.com/ZondaX/ledger-go"
packages = ["."]
pruneopts = "UT"
revision = "c3225ab10c2f53397d4aa419a588466493572b22"
version = "v0.4.0"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:09a7f74eb6bb3c0f14d8926610c87f569c5cff68e978d30e9a3540aeb626fdf0" digest = "1:09a7f74eb6bb3c0f14d8926610c87f569c5cff68e978d30e9a3540aeb626fdf0"
@ -42,12 +26,11 @@
version = "v0.1.0" version = "v0.1.0"
[[projects]] [[projects]]
branch = "master"
digest = "1:093bf93a65962e8191e3e8cd8fc6c363f83d43caca9739c906531ba7210a9904" digest = "1:093bf93a65962e8191e3e8cd8fc6c363f83d43caca9739c906531ba7210a9904"
name = "github.com/btcsuite/btcd" name = "github.com/btcsuite/btcd"
packages = ["btcec"] packages = ["btcec"]
pruneopts = "UT" pruneopts = "UT"
revision = "7d2daa5bfef28c5e282571bc06416516936115ee" revision = "ed77733ec07dfc8a513741138419b8d9d3de9d2d"
[[projects]] [[projects]]
digest = "1:386de157f7d19259a7f9c81f26ce011223ce0f090353c1152ffdf730d7d10ac2" digest = "1:386de157f7d19259a7f9c81f26ce011223ce0f090353c1152ffdf730d7d10ac2"
@ -71,6 +54,14 @@
revision = "346938d642f2ec3594ed81d874461961cd0faa76" revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0" version = "v1.1.0"
[[projects]]
digest = "1:fed20bf7f0da387c96d4cfc140a95572e5aba4bb984beb7de910e090ae39849b"
name = "github.com/ethereum/go-ethereum"
packages = ["crypto/secp256k1"]
pruneopts = "UT"
revision = "7fa3509e2eaf1a4ebc12344590e5699406690f15"
version = "v1.8.22"
[[projects]] [[projects]]
digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd" digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd"
name = "github.com/fsnotify/fsnotify" name = "github.com/fsnotify/fsnotify"
@ -149,20 +140,12 @@
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a" revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
[[projects]] [[projects]]
digest = "1:c79fb010be38a59d657c48c6ba1d003a8aa651fa56b579d959d74573b7dff8e1" digest = "1:ca59b1175189b3f0e9f1793d2c350114be36eaabbe5b9f554b35edee1de50aea"
name = "github.com/gorilla/context"
packages = ["."]
pruneopts = "UT"
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
version = "v1.1.1"
[[projects]]
digest = "1:e73f5b0152105f18bc131fba127d9949305c8693f8a762588a82a48f61756f5f"
name = "github.com/gorilla/mux" name = "github.com/gorilla/mux"
packages = ["."] packages = ["."]
pruneopts = "UT" pruneopts = "UT"
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf" revision = "a7962380ca08b5a188038c69871b8d3fbdf31e89"
version = "v1.6.2" version = "v1.7.0"
[[projects]] [[projects]]
digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d" digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d"
@ -296,11 +279,10 @@
name = "github.com/prometheus/client_model" name = "github.com/prometheus/client_model"
packages = ["go"] packages = ["go"]
pruneopts = "UT" pruneopts = "UT"
revision = "5c3871d89910bfb32f5fcab2aa4b9ec68e65a99f" revision = "fd36f4220a901265f90734c3183c5f0c91daa0b8"
[[projects]] [[projects]]
branch = "master" digest = "1:35cf6bdf68db765988baa9c4f10cc5d7dda1126a54bd62e252dbcd0b1fc8da90"
digest = "1:db712fde5d12d6cdbdf14b777f0c230f4ff5ab0be8e35b239fc319953ed577a4"
name = "github.com/prometheus/common" name = "github.com/prometheus/common"
packages = [ packages = [
"expfmt", "expfmt",
@ -308,11 +290,12 @@
"model", "model",
] ]
pruneopts = "UT" pruneopts = "UT"
revision = "4724e9255275ce38f7179b2478abeae4e28c904f" revision = "cfeb6f9992ffa54aaa4f2170ade4067ee478b250"
version = "v0.2.0"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:d39e7c7677b161c2dd4c635a2ac196460608c7d8ba5337cc8cae5825a2681f8f" digest = "1:c65f369bae3dff3a0382e38f3fe4f62cdfecba59cb6429ee323b75afdd4f3ba3"
name = "github.com/prometheus/procfs" name = "github.com/prometheus/procfs"
packages = [ packages = [
".", ".",
@ -321,7 +304,7 @@
"xfs", "xfs",
] ]
pruneopts = "UT" pruneopts = "UT"
revision = "1dc9a6cbc91aacc3e8b2d63db4d2e957a5394ac4" revision = "de1b801bf34b80cd00f14087dc5a994bfe0296bc"
[[projects]] [[projects]]
digest = "1:ea0700160aca4ef099f4e06686a665a87691f4248dddd40796925eda2e46bd64" digest = "1:ea0700160aca4ef099f4e06686a665a87691f4248dddd40796925eda2e46bd64"
@ -347,15 +330,15 @@
version = "v1.6.0" version = "v1.6.0"
[[projects]] [[projects]]
digest = "1:6a4a11ba764a56d2758899ec6f3848d24698d48442ebce85ee7a3f63284526cd" digest = "1:3e39bafd6c2f4bf3c76c3bfd16a2e09e016510ad5db90dc02b88e2f565d6d595"
name = "github.com/spf13/afero" name = "github.com/spf13/afero"
packages = [ packages = [
".", ".",
"mem", "mem",
] ]
pruneopts = "UT" pruneopts = "UT"
revision = "d40851caa0d747393da1ffb28f7f9d8b4eeffebd" revision = "f4711e4db9e9a1d3887343acb72b2bbfc2f686f5"
version = "v1.1.2" version = "v1.2.1"
[[projects]] [[projects]]
digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc" digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc"
@ -453,7 +436,7 @@
version = "v0.12.0" version = "v0.12.0"
[[projects]] [[projects]]
digest = "1:22a0fe58c626dd09549eb9451688fab5a2c8bef04d478c907f747d6151d431fd" digest = "1:89f6fe8d02b427996828fbf43720ed1297a2e92c930b98dd302767b5ad796579"
name = "github.com/tendermint/tendermint" name = "github.com/tendermint/tendermint"
packages = [ packages = [
"abci/client", "abci/client",
@ -519,15 +502,31 @@
"version", "version",
] ]
pruneopts = "UT" pruneopts = "UT"
revision = "v0.29.0" revision = "v0.30.0-rc0"
[[projects]] [[projects]]
digest = "1:a7485b2a69f996923f9d3406a9a853fd8eb31818515e985a830d71f88f6a925b" digest = "1:b73f5e117bc7c6e8fc47128f20db48a873324ad5cfeeebfc505e85c58682b5e4"
name = "github.com/zondax/hid"
packages = ["."]
pruneopts = "T"
revision = "302fd402163c34626286195dfa9adac758334acc"
version = "v0.9.0"
[[projects]]
digest = "1:fca24169988a61ea725d1326de30910d8049fe68bcbc194d28803f9a76dda380"
name = "github.com/zondax/ledger-cosmos-go" name = "github.com/zondax/ledger-cosmos-go"
packages = ["."] packages = ["."]
pruneopts = "UT" pruneopts = "UT"
revision = "d4aed6d929a703bb555a2d79fe9c470afe61f648" revision = "69fdb8ce5e5b9d9c3b22b9248e117b231d4f06dd"
version = "v0.9.2" version = "v0.9.7"
[[projects]]
digest = "1:f8e4c0b959174a1fa5946b12f1f2ac7ea5651bef20a9e4a8dac55dbffcaa6cd6"
name = "github.com/zondax/ledger-go"
packages = ["."]
pruneopts = "UT"
revision = "69c15f1333a9b6866e5f66096561c7d138894bc5"
version = "v0.8.0"
[[projects]] [[projects]]
digest = "1:6f6dc6060c4e9ba73cf28aa88f12a69a030d3d19d518ef8e931879eaa099628d" digest = "1:6f6dc6060c4e9ba73cf28aa88f12a69a030d3d19d518ef8e931879eaa099628d"
@ -612,7 +611,7 @@
revision = "383e8b2c3b9e36c4076b235b32537292176bae20" revision = "383e8b2c3b9e36c4076b235b32537292176bae20"
[[projects]] [[projects]]
digest = "1:9edd250a3c46675d0679d87540b30c9ed253b19bd1fd1af08f4f5fb3c79fc487" digest = "1:9ab5a33d8cb5c120602a34d2e985ce17956a4e8c2edce7e6961568f95e40c09a"
name = "google.golang.org/grpc" name = "google.golang.org/grpc"
packages = [ packages = [
".", ".",
@ -648,8 +647,8 @@
"tap", "tap",
] ]
pruneopts = "UT" pruneopts = "UT"
revision = "df014850f6dee74ba2fc94874043a9f3f75fbfd8" revision = "a02b0774206b209466313a0b525d2c738fe407eb"
version = "v1.17.0" version = "v1.18.0"
[[projects]] [[projects]]
digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96"
@ -680,7 +679,7 @@
"github.com/spf13/viper", "github.com/spf13/viper",
"github.com/stretchr/testify/assert", "github.com/stretchr/testify/assert",
"github.com/stretchr/testify/require", "github.com/stretchr/testify/require",
"github.com/syndtr/goleveldb/leveldb/opt", "github.com/tendermint/btcd/btcec",
"github.com/tendermint/go-amino", "github.com/tendermint/go-amino",
"github.com/tendermint/iavl", "github.com/tendermint/iavl",
"github.com/tendermint/tendermint/abci/server", "github.com/tendermint/tendermint/abci/server",

View File

@ -40,14 +40,18 @@
[[override]] [[override]]
name = "github.com/tendermint/tendermint" name = "github.com/tendermint/tendermint"
revision = "v0.29.0" revision = "v0.30.0-rc0"
[[constraint]] [[constraint]]
name = "github.com/zondax/ledger-cosmos-go" name = "github.com/zondax/ledger-cosmos-go"
version = "=v0.9.2" version = "=v0.9.7"
## deps without releases: ## deps without releases:
[[constraint]]
name = "github.com/btcsuite/btcd"
revision = "ed77733ec07dfc8a513741138419b8d9d3de9d2d"
[[override]] [[override]]
name = "golang.org/x/crypto" name = "golang.org/x/crypto"
source = "https://github.com/tendermint/crypto" source = "https://github.com/tendermint/crypto"
@ -84,3 +88,6 @@
[prune] [prune]
go-tests = true go-tests = true
unused-packages = true unused-packages = true
[[prune.project]]
name = "github.com/zondax/hid"
unused-packages = false

View File

@ -1,6 +1,6 @@
PACKAGES_NOSIMULATION=$(shell go list ./... | grep -v '/simulation') PACKAGES_NOSIMULATION=$(shell go list ./... | grep -v '/simulation')
PACKAGES_SIMTEST=$(shell go list ./... | grep '/simulation') PACKAGES_SIMTEST=$(shell go list ./... | grep '/simulation')
VERSION := $(subst v,,$(shell git describe --tags --long)) VERSION := $(shell echo $(shell git describe --tags) | sed 's/^v//')
COMMIT := $(shell git log -1 --format='%H') COMMIT := $(shell git log -1 --format='%H')
BUILD_TAGS = netgo BUILD_TAGS = netgo
CAT := $(if $(filter $(OS),Windows_NT),type,cat) CAT := $(if $(filter $(OS),Windows_NT),type,cat)
@ -15,7 +15,7 @@ GOTOOLS = \
github.com/rakyll/statik github.com/rakyll/statik
GOBIN ?= $(GOPATH)/bin GOBIN ?= $(GOPATH)/bin
all: devtools get_vendor_deps install test_lint test all: devtools vendor-deps install test_lint test
# The below include contains the tools target. # The below include contains the tools target.
include scripts/Makefile include scripts/Makefile
@ -99,6 +99,7 @@ update_dev_tools:
go get -u github.com/tendermint/lint/golint go get -u github.com/tendermint/lint/golint
devtools: devtools-stamp devtools: devtools-stamp
devtools-clean: tools-clean
devtools-stamp: tools devtools-stamp: tools
@echo "--> Downloading linters (this may take awhile)" @echo "--> Downloading linters (this may take awhile)"
$(GOPATH)/src/github.com/alecthomas/gometalinter/scripts/install.sh -b $(GOBIN) $(GOPATH)/src/github.com/alecthomas/gometalinter/scripts/install.sh -b $(GOBIN)
@ -122,7 +123,10 @@ draw_deps: tools
@goviz -i github.com/cosmos/cosmos-sdk/cmd/gaia/cmd/gaiad -d 2 | dot -Tpng -o dependency-graph.png @goviz -i github.com/cosmos/cosmos-sdk/cmd/gaia/cmd/gaiad -d 2 | dot -Tpng -o dependency-graph.png
clean: clean:
rm -f devtools-stamp vendor-deps rm -f devtools-stamp vendor-deps snapcraft-local.yaml
distclean: clean
rm -rf vendor/
######################################## ########################################
### Documentation ### Documentation
@ -140,8 +144,14 @@ test: test_unit
test_cli: test_cli:
@go test -p 4 `go list github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test` -tags=cli_test @go test -p 4 `go list github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test` -tags=cli_test
test_ledger:
# First test with mock
@go test `go list github.com/cosmos/cosmos-sdk/crypto` -tags='cgo ledger test_ledger_mock'
# Now test with a real device
@go test -v `go list github.com/cosmos/cosmos-sdk/crypto` -tags='cgo ledger'
test_unit: test_unit:
@VERSION=$(VERSION) go test $(PACKAGES_NOSIMULATION) @VERSION=$(VERSION) go test $(PACKAGES_NOSIMULATION) -tags='test_ledger_mock'
test_race: test_race:
@VERSION=$(VERSION) go test -race $(PACKAGES_NOSIMULATION) @VERSION=$(VERSION) go test -race $(PACKAGES_NOSIMULATION)
@ -150,9 +160,15 @@ test_sim_gaia_nondeterminism:
@echo "Running nondeterminism test..." @echo "Running nondeterminism test..."
@go test ./cmd/gaia/app -run TestAppStateDeterminism -SimulationEnabled=true -v -timeout 10m @go test ./cmd/gaia/app -run TestAppStateDeterminism -SimulationEnabled=true -v -timeout 10m
test_sim_gaia_custom_genesis_fast:
@echo "Running custom genesis simulation..."
@echo "By default, ${HOME}/.gaiad/config/genesis.json will be used."
@go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationGenesis=${HOME}/.gaiad/config/genesis.json \
-SimulationEnabled=true -SimulationNumBlocks=100 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=99 -SimulationPeriod=5 -v -timeout 24h
test_sim_gaia_fast: test_sim_gaia_fast:
@echo "Running quick Gaia simulation. This may take several minutes..." @echo "Running quick Gaia simulation. This may take several minutes..."
@go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=99 -v -timeout 24h @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=100 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=99 -SimulationPeriod=5 -v -timeout 24h
test_sim_gaia_import_export: test_sim_gaia_import_export:
@echo "Running Gaia import/export simulation. This may take several minutes..." @echo "Running Gaia import/export simulation. This may take several minutes..."
@ -162,6 +178,11 @@ test_sim_gaia_simulation_after_import:
@echo "Running Gaia simulation-after-import. This may take several minutes..." @echo "Running Gaia simulation-after-import. This may take several minutes..."
@bash scripts/multisim.sh 50 5 TestGaiaSimulationAfterImport @bash scripts/multisim.sh 50 5 TestGaiaSimulationAfterImport
test_sim_gaia_custom_genesis_multi_seed:
@echo "Running multi-seed custom genesis simulation..."
@echo "By default, ${HOME}/.gaiad/config/genesis.json will be used."
@bash scripts/multisim.sh 400 5 TestFullGaiaSimulation ${HOME}/.gaiad/config/genesis.json
test_sim_gaia_multi_seed: test_sim_gaia_multi_seed:
@echo "Running multi-seed Gaia simulation. This may take awhile!" @echo "Running multi-seed Gaia simulation. This may take awhile!"
@bash scripts/multisim.sh 400 5 TestFullGaiaSimulation @bash scripts/multisim.sh 400 5 TestFullGaiaSimulation
@ -171,14 +192,16 @@ SIM_BLOCK_SIZE ?= 200
SIM_COMMIT ?= true SIM_COMMIT ?= true
test_sim_gaia_benchmark: test_sim_gaia_benchmark:
@echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!" @echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
@go test -benchmem -run=^$$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$$ -SimulationEnabled=true -SimulationNumBlocks=$(SIM_NUM_BLOCKS) -SimulationBlockSize=$(SIM_BLOCK_SIZE) -SimulationCommit=$(SIM_COMMIT) -timeout 24h @go test -benchmem -run=^$$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$$ \
-SimulationEnabled=true -SimulationNumBlocks=$(SIM_NUM_BLOCKS) -SimulationBlockSize=$(SIM_BLOCK_SIZE) -SimulationCommit=$(SIM_COMMIT) -timeout 24h
test_sim_gaia_profile: test_sim_gaia_profile:
@echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!" @echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
@go test -benchmem -run=^$$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$$ -SimulationEnabled=true -SimulationNumBlocks=$(SIM_NUM_BLOCKS) -SimulationBlockSize=$(SIM_BLOCK_SIZE) -SimulationCommit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out @go test -benchmem -run=^$$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$$ \
-SimulationEnabled=true -SimulationNumBlocks=$(SIM_NUM_BLOCKS) -SimulationBlockSize=$(SIM_BLOCK_SIZE) -SimulationCommit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out
test_cover: test_cover:
@export VERSION=$(VERSION); bash tests/test_cover.sh @export VERSION=$(VERSION); bash -x tests/test_cover.sh
test_lint: test_lint:
gometalinter --config=tools/gometalinter.json ./... gometalinter --config=tools/gometalinter.json ./...
@ -235,12 +258,21 @@ localnet-start: localnet-stop
localnet-stop: localnet-stop:
docker-compose down docker-compose down
########################################
### Packaging
snapcraft-local.yaml: snapcraft-local.yaml.in
sed "s/@VERSION@/${VERSION}/g" < $< > $@
# To avoid unintended conflicts with file names, always add to .PHONY # To avoid unintended conflicts with file names, always add to .PHONY
# unless there is a reason not to. # unless there is a reason not to.
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
.PHONY: build install install_debug dist \ .PHONY: build install install_debug dist clean distclean \
check_tools check_dev_tools get_vendor_deps draw_deps test test_cli test_unit \ check_tools check_dev_tools get_vendor_deps draw_deps test test_cli test_unit \
test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update \ test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update \
build-linux build-docker-gaiadnode localnet-start localnet-stop \ build-linux build-docker-gaiadnode localnet-start localnet-stop \
format check-ledger test_sim_gaia_nondeterminism test_sim_modules test_sim_gaia_fast \ format check-ledger test_sim_gaia_nondeterminism test_sim_modules test_sim_gaia_fast \
test_sim_gaia_multi_seed test_sim_gaia_import_export update_tools update_dev_tools test_sim_gaia_custom_genesis_fast test_sim_gaia_custom_genesis_multi_seed \
test_sim_gaia_multi_seed test_sim_gaia_import_export update_tools update_dev_tools \
devtools-clean

View File

@ -2,9 +2,9 @@
BREAKING CHANGES BREAKING CHANGES
* Gaia REST API (`gaiacli advanced rest-server`) * Gaia REST API
* Gaia CLI (`gaiacli`) * Gaia CLI
* Gaia * Gaia
@ -12,12 +12,11 @@ BREAKING CHANGES
* Tendermint * Tendermint
FEATURES FEATURES
* Gaia REST API (`gaiacli advanced rest-server`) * Gaia REST API
* Gaia CLI (`gaiacli`) * Gaia CLI
* Gaia * Gaia
@ -28,9 +27,9 @@ FEATURES
IMPROVEMENTS IMPROVEMENTS
* Gaia REST API (`gaiacli advanced rest-server`) * Gaia REST API
* Gaia CLI (`gaiacli`) * Gaia CLI
* Gaia * Gaia
@ -41,12 +40,12 @@ IMPROVEMENTS
BUG FIXES BUG FIXES
* Gaia REST API (`gaiacli advanced rest-server`) * Gaia REST API
* Gaia CLI (`gaiacli`) * Gaia CLI
* Gaia * Gaia
* SDK * SDK
* Tendermint * Tendermint

View File

@ -3,6 +3,7 @@
[![version](https://img.shields.io/github/tag/cosmos/cosmos-sdk.svg)](https://github.com/cosmos/cosmos-sdk/releases/latest) [![version](https://img.shields.io/github/tag/cosmos/cosmos-sdk.svg)](https://github.com/cosmos/cosmos-sdk/releases/latest)
[![CircleCI](https://circleci.com/gh/cosmos/cosmos-sdk/tree/master.svg?style=shield)](https://circleci.com/gh/cosmos/cosmos-sdk/tree/master) [![CircleCI](https://circleci.com/gh/cosmos/cosmos-sdk/tree/master.svg?style=shield)](https://circleci.com/gh/cosmos/cosmos-sdk/tree/master)
[![Snap Status](https://build.snapcraft.io/badge/cosmos/cosmos-sdk.svg)](https://build.snapcraft.io/user/cosmos/cosmos-sdk)
[![codecov](https://codecov.io/gh/cosmos/cosmos-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/cosmos/cosmos-sdk) [![codecov](https://codecov.io/gh/cosmos/cosmos-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/cosmos/cosmos-sdk)
[![Go Report Card](https://goreportcard.com/badge/github.com/cosmos/cosmos-sdk)](https://goreportcard.com/report/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)
[![license](https://img.shields.io/github/license/cosmos/cosmos-sdk.svg)](https://github.com/cosmos/cosmos-sdk/blob/master/LICENSE) [![license](https://img.shields.io/github/license/cosmos/cosmos-sdk.svg)](https://github.com/cosmos/cosmos-sdk/blob/master/LICENSE)
@ -17,7 +18,7 @@ It is being used to build `Gaia`, the first implementation of the Cosmos Hub.
**WARNING**: The SDK has mostly stabilized, but we are still making some **WARNING**: The SDK has mostly stabilized, but we are still making some
breaking changes. breaking changes.
**Note**: Requires [Go 1.11.4+](https://golang.org/dl/) **Note**: Requires [Go 1.11.5+](https://golang.org/dl/)
## Cosmos Hub Public Testnet ## Cosmos Hub Public Testnet

View File

@ -3,11 +3,13 @@ package baseapp
import ( import (
"fmt" "fmt"
"io" "io"
"reflect"
"runtime/debug" "runtime/debug"
"strings" "strings"
"errors"
"github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/proto"
"github.com/pkg/errors"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/crypto/tmhash"
@ -41,7 +43,7 @@ const (
// BaseApp reflects the ABCI application implementation. // BaseApp reflects the ABCI application implementation.
type BaseApp struct { type BaseApp struct {
// initialized on creation // initialized on creation
Logger log.Logger logger log.Logger
name string // application name from abci.Info name string // application name from abci.Info
db dbm.DB // common DB backend db dbm.DB // common DB backend
cms sdk.CommitMultiStore // Main (uncached) state cms sdk.CommitMultiStore // Main (uncached) state
@ -50,19 +52,18 @@ type BaseApp struct {
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
// set upon LoadVersion or LoadLatestVersion. // set upon LoadVersion or LoadLatestVersion.
mainKey *sdk.KVStoreKey // Main KVStore in cms baseKey *sdk.KVStoreKey // Main KVStore in cms
// may be nil anteHandler sdk.AnteHandler // ante handler for fee and auth
anteHandler sdk.AnteHandler // ante handler for fee and auth initChainer sdk.InitChainer // initialize state with validators and state blob
initChainer sdk.InitChainer // initialize state with validators and state blob beginBlocker sdk.BeginBlocker // logic to run before any txs
beginBlocker sdk.BeginBlocker // logic to run before any txs endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes
endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes addrPeerFilter sdk.PeerFilter // filter peers by address and port
addrPeerFilter sdk.PeerFilter // filter peers by address and port idPeerFilter sdk.PeerFilter // filter peers by node ID
pubkeyPeerFilter sdk.PeerFilter // filter peers by public key fauxMerkleMode bool // if true, IAVL MountStores uses MountStoresDB for simulation speed.
fauxMerkleMode bool // if true, IAVL MountStores uses MountStoresDB for simulation speed.
//-------------------- // --------------------
// Volatile // Volatile state
// checkState is set on initialization and reset on Commit. // checkState is set on initialization and reset on Commit.
// deliverState is set in InitChain and BeginBlock and cleared on Commit. // deliverState is set in InitChain and BeginBlock and cleared on Commit.
// See methods setCheckState and setDeliverState. // See methods setCheckState and setDeliverState.
@ -71,27 +72,30 @@ type BaseApp struct {
voteInfos []abci.VoteInfo // absent validators from begin block voteInfos []abci.VoteInfo // absent validators from begin block
// consensus params // consensus params
// TODO move this in the future to baseapp param store on main store. // TODO: Move this in the future to baseapp param store on main store.
consensusParams *abci.ConsensusParams consensusParams *abci.ConsensusParams
// The minimum gas prices a validator is willing to accept for processing a // The minimum gas prices a validator is willing to accept for processing a
// transaction. This is mainly used for DoS and spam prevention. // transaction. This is mainly used for DoS and spam prevention.
minGasPrices sdk.DecCoins minGasPrices sdk.DecCoins
// flag for sealing // flag for sealing options and parameters to a BaseApp
sealed bool sealed bool
} }
var _ abci.Application = (*BaseApp)(nil) var _ abci.Application = (*BaseApp)(nil)
// NewBaseApp returns a reference to an initialized BaseApp. // NewBaseApp returns a reference to an initialized BaseApp. It accepts a
// variadic number of option functions, which act on the BaseApp to set
// configuration choices.
// //
// NOTE: The db is used to store the version number for now. // NOTE: The db is used to store the version number for now.
// Accepts a user-defined txDecoder func NewBaseApp(
// Accepts variable number of option functions, which act on the BaseApp to set configuration choices name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp),
func NewBaseApp(name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp)) *BaseApp { ) *BaseApp {
app := &BaseApp{ app := &BaseApp{
Logger: logger, logger: logger,
name: name, name: name,
db: db, db: db,
cms: store.NewCommitMultiStore(db), cms: store.NewCommitMultiStore(db),
@ -103,112 +107,120 @@ func NewBaseApp(name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecod
for _, option := range options { for _, option := range options {
option(app) option(app)
} }
return app return app
} }
// BaseApp Name // Name returns the name of the BaseApp.
func (app *BaseApp) Name() string { func (app *BaseApp) Name() string {
return app.name return app.name
} }
// Logger returns the logger of the BaseApp.
func (app *BaseApp) Logger() log.Logger {
return app.logger
}
// SetCommitMultiStoreTracer sets the store tracer on the BaseApp's underlying // SetCommitMultiStoreTracer sets the store tracer on the BaseApp's underlying
// CommitMultiStore. // CommitMultiStore.
func (app *BaseApp) SetCommitMultiStoreTracer(w io.Writer) { func (app *BaseApp) SetCommitMultiStoreTracer(w io.Writer) {
app.cms.WithTracer(w) app.cms.SetTracer(w)
} }
// Mount IAVL or DB stores to the provided keys in the BaseApp multistore // MountStores mounts all IAVL or DB stores to the provided keys in the BaseApp
func (app *BaseApp) MountStores(keys ...*sdk.KVStoreKey) { // multistore.
func (app *BaseApp) MountStores(keys ...sdk.StoreKey) {
for _, key := range keys { for _, key := range keys {
if !app.fauxMerkleMode { switch key.(type) {
app.MountStore(key, sdk.StoreTypeIAVL) case *sdk.KVStoreKey:
} else { if !app.fauxMerkleMode {
// StoreTypeDB doesn't do anything upon commit, and it doesn't app.MountStore(key, sdk.StoreTypeIAVL)
// retain history, but it's useful for faster simulation. } else {
app.MountStore(key, sdk.StoreTypeDB) // StoreTypeDB doesn't do anything upon commit, and it doesn't
// retain history, but it's useful for faster simulation.
app.MountStore(key, sdk.StoreTypeDB)
}
case *sdk.TransientStoreKey:
app.MountStore(key, sdk.StoreTypeTransient)
default:
panic("Unrecognized store key type " + reflect.TypeOf(key).Name())
} }
} }
} }
// Mount stores to the provided keys in the BaseApp multistore // MountStoreWithDB mounts a store to the provided key in the BaseApp
func (app *BaseApp) MountStoresTransient(keys ...*sdk.TransientStoreKey) { // multistore, using a specified DB.
for _, key := range keys {
app.MountStore(key, sdk.StoreTypeTransient)
}
}
// Mount a store to the provided key in the BaseApp multistore, using a specified DB
func (app *BaseApp) MountStoreWithDB(key sdk.StoreKey, typ sdk.StoreType, db dbm.DB) { func (app *BaseApp) MountStoreWithDB(key sdk.StoreKey, typ sdk.StoreType, db dbm.DB) {
app.cms.MountStoreWithDB(key, typ, db) app.cms.MountStoreWithDB(key, typ, db)
} }
// Mount a store to the provided key in the BaseApp multistore, using the default DB // MountStore mounts a store to the provided key in the BaseApp multistore,
// using the default DB.
func (app *BaseApp) MountStore(key sdk.StoreKey, typ sdk.StoreType) { func (app *BaseApp) MountStore(key sdk.StoreKey, typ sdk.StoreType) {
app.cms.MountStoreWithDB(key, typ, nil) app.cms.MountStoreWithDB(key, typ, nil)
} }
// load latest application version // LoadLatestVersion loads the latest application version. It will panic if
// panics if called more than once on a running baseapp // called more than once on a running BaseApp.
func (app *BaseApp) LoadLatestVersion(mainKey *sdk.KVStoreKey) error { func (app *BaseApp) LoadLatestVersion(baseKey *sdk.KVStoreKey) error {
err := app.cms.LoadLatestVersion() err := app.cms.LoadLatestVersion()
if err != nil { if err != nil {
return err return err
} }
return app.initFromMainStore(mainKey) return app.initFromMainStore(baseKey)
} }
// load application version // LoadVersion loads the BaseApp application version. It will panic if called
// panics if called more than once on a running baseapp // more than once on a running baseapp.
func (app *BaseApp) LoadVersion(version int64, mainKey *sdk.KVStoreKey) error { func (app *BaseApp) LoadVersion(version int64, baseKey *sdk.KVStoreKey) error {
err := app.cms.LoadVersion(version) err := app.cms.LoadVersion(version)
if err != nil { if err != nil {
return err return err
} }
return app.initFromMainStore(mainKey) return app.initFromMainStore(baseKey)
} }
// the last CommitID of the multistore // LastCommitID returns the last CommitID of the multistore.
func (app *BaseApp) LastCommitID() sdk.CommitID { func (app *BaseApp) LastCommitID() sdk.CommitID {
return app.cms.LastCommitID() return app.cms.LastCommitID()
} }
// the last committed block height // LastBlockHeight returns the last committed block height.
func (app *BaseApp) LastBlockHeight() int64 { func (app *BaseApp) LastBlockHeight() int64 {
return app.cms.LastCommitID().Version return app.cms.LastCommitID().Version
} }
// initializes the remaining logic from app.cms // initializes the remaining logic from app.cms
func (app *BaseApp) initFromMainStore(mainKey *sdk.KVStoreKey) error { func (app *BaseApp) initFromMainStore(baseKey *sdk.KVStoreKey) error {
mainStore := app.cms.GetKVStore(baseKey)
// main store should exist.
mainStore := app.cms.GetKVStore(mainKey)
if mainStore == nil { if mainStore == nil {
return errors.New("baseapp expects MultiStore with 'main' KVStore") return errors.New("baseapp expects MultiStore with 'main' KVStore")
} }
// memoize mainKey // memoize baseKey
if app.mainKey != nil { if app.baseKey != nil {
panic("app.mainKey expected to be nil; duplicate init?") panic("app.baseKey expected to be nil; duplicate init?")
} }
app.mainKey = mainKey app.baseKey = baseKey
// load consensus params from the main store // Load the consensus params from the main store. If the consensus params are
// nil, it will be saved later during InitChain.
//
// TODO: assert that InitChain hasn't yet been called.
consensusParamsBz := mainStore.Get(mainConsensusParamsKey) consensusParamsBz := mainStore.Get(mainConsensusParamsKey)
if consensusParamsBz != nil { if consensusParamsBz != nil {
var consensusParams = &abci.ConsensusParams{} var consensusParams = &abci.ConsensusParams{}
err := proto.Unmarshal(consensusParamsBz, consensusParams) err := proto.Unmarshal(consensusParamsBz, consensusParams)
if err != nil { if err != nil {
panic(err) panic(err)
} }
app.setConsensusParams(consensusParams) app.setConsensusParams(consensusParams)
} else {
// It will get saved later during InitChain.
// TODO assert that InitChain hasn't yet been called.
} }
// Needed for `gaiad export`, which inits from store but never calls initchain // needed for `gaiad export`, which inits from store but never calls initchain
app.setCheckState(abci.Header{}) app.setCheckState(abci.Header{})
app.Seal() app.Seal()
return nil return nil
@ -218,42 +230,45 @@ func (app *BaseApp) setMinGasPrices(gasPrices sdk.DecCoins) {
app.minGasPrices = gasPrices app.minGasPrices = gasPrices
} }
// NewContext returns a new Context with the correct store, the given header, and nil txBytes. // Router returns the router of the BaseApp.
func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context { func (app *BaseApp) Router() Router {
if isCheckTx { if app.sealed {
return sdk.NewContext(app.checkState.ms, header, true, app.Logger). // We cannot return a router when the app is sealed because we can't have
WithMinGasPrices(app.minGasPrices) // any routes modified which would cause unexpected routing behavior.
panic("Router() on sealed BaseApp")
} }
return app.router
return sdk.NewContext(app.deliverState.ms, header, false, app.Logger)
} }
type state struct { // QueryRouter returns the QueryRouter of a BaseApp.
ms sdk.CacheMultiStore func (app *BaseApp) QueryRouter() QueryRouter { return app.queryRouter }
ctx sdk.Context
}
func (st *state) CacheMultiStore() sdk.CacheMultiStore { // Seal seals a BaseApp. It prohibits any further modifications to a BaseApp.
return st.ms.CacheMultiStore() func (app *BaseApp) Seal() { app.sealed = true }
}
func (st *state) Context() sdk.Context { // IsSealed returns true if the BaseApp is sealed and false otherwise.
return st.ctx func (app *BaseApp) IsSealed() bool { return app.sealed }
}
// setCheckState sets checkState with the cached multistore and
// the context wrapping it.
// It is called by InitChain() and Commit()
func (app *BaseApp) setCheckState(header abci.Header) { func (app *BaseApp) setCheckState(header abci.Header) {
ms := app.cms.CacheMultiStore() ms := app.cms.CacheMultiStore()
app.checkState = &state{ app.checkState = &state{
ms: ms, ms: ms,
ctx: sdk.NewContext(ms, header, true, app.Logger).WithMinGasPrices(app.minGasPrices), ctx: sdk.NewContext(ms, header, true, app.logger).WithMinGasPrices(app.minGasPrices),
} }
} }
// setCheckState sets checkState with the cached multistore and
// the context wrapping it.
// It is called by InitChain() and BeginBlock(),
// and deliverState is set nil on Commit().
func (app *BaseApp) setDeliverState(header abci.Header) { func (app *BaseApp) setDeliverState(header abci.Header) {
ms := app.cms.CacheMultiStore() ms := app.cms.CacheMultiStore()
app.deliverState = &state{ app.deliverState = &state{
ms: ms, ms: ms,
ctx: sdk.NewContext(ms, header, false, app.Logger), ctx: sdk.NewContext(ms, header, false, app.logger),
} }
} }
@ -268,7 +283,7 @@ func (app *BaseApp) storeConsensusParams(consensusParams *abci.ConsensusParams)
if err != nil { if err != nil {
panic(err) panic(err)
} }
mainStore := app.cms.GetKVStore(app.mainKey) mainStore := app.cms.GetKVStore(app.baseKey)
mainStore.Set(mainConsensusParamsKey, consensusParamsBz) mainStore.Set(mainConsensusParamsKey, consensusParamsBz)
} }
@ -280,11 +295,10 @@ func (app *BaseApp) getMaximumBlockGas() (maxGas uint64) {
return uint64(app.consensusParams.BlockSize.MaxGas) return uint64(app.consensusParams.BlockSize.MaxGas)
} }
//______________________________________________________________________________ // ----------------------------------------------------------------------------
// ABCI // ABCI
// Implements ABCI // Info implements the ABCI interface.
func (app *BaseApp) Info(req abci.RequestInfo) abci.ResponseInfo { func (app *BaseApp) Info(req abci.RequestInfo) abci.ResponseInfo {
lastCommitID := app.cms.LastCommitID() lastCommitID := app.cms.LastCommitID()
@ -295,23 +309,23 @@ func (app *BaseApp) Info(req abci.RequestInfo) abci.ResponseInfo {
} }
} }
// Implements ABCI // SetOption implements the ABCI interface.
func (app *BaseApp) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOption) { func (app *BaseApp) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOption) {
// TODO: Implement // TODO: Implement!
return return
} }
// Implements ABCI // InitChain implements the ABCI interface. It runs the initialization logic
// InitChain runs the initialization logic directly on the CommitMultiStore. // directly on the CommitMultiStore.
func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) { func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) {
// Stash the consensus params in the cms main store and memoize. // stash the consensus params in the cms main store and memoize
if req.ConsensusParams != nil { if req.ConsensusParams != nil {
app.setConsensusParams(req.ConsensusParams) app.setConsensusParams(req.ConsensusParams)
app.storeConsensusParams(req.ConsensusParams) app.storeConsensusParams(req.ConsensusParams)
} }
// Initialize the deliver state and check state with ChainID and run initChain // initialize the deliver state and check state with ChainID and run initChain
app.setDeliverState(abci.Header{ChainID: req.ChainId}) app.setDeliverState(abci.Header{ChainID: req.ChainId})
app.setCheckState(abci.Header{ChainID: req.ChainId}) app.setCheckState(abci.Header{ChainID: req.ChainId})
@ -325,12 +339,12 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC
res = app.initChainer(app.deliverState.ctx, req) res = app.initChainer(app.deliverState.ctx, req)
// NOTE: we don't commit, but BeginBlock for block 1 // NOTE: We don't commit, but BeginBlock for block 1 starts from this
// starts from this deliverState // deliverState.
return return
} }
// Filter peers by address / port // FilterPeerByAddrPort filters peers by address/port.
func (app *BaseApp) FilterPeerByAddrPort(info string) abci.ResponseQuery { func (app *BaseApp) FilterPeerByAddrPort(info string) abci.ResponseQuery {
if app.addrPeerFilter != nil { if app.addrPeerFilter != nil {
return app.addrPeerFilter(info) return app.addrPeerFilter(info)
@ -338,15 +352,16 @@ func (app *BaseApp) FilterPeerByAddrPort(info string) abci.ResponseQuery {
return abci.ResponseQuery{} return abci.ResponseQuery{}
} }
// Filter peers by public key // FilterPeerByIDfilters peers by node ID.
func (app *BaseApp) FilterPeerByPubKey(info string) abci.ResponseQuery { func (app *BaseApp) FilterPeerByID(info string) abci.ResponseQuery {
if app.pubkeyPeerFilter != nil { if app.idPeerFilter != nil {
return app.pubkeyPeerFilter(info) return app.idPeerFilter(info)
} }
return abci.ResponseQuery{} return abci.ResponseQuery{}
} }
// Splits a string path using the delimter '/'. i.e. "this/is/funny" becomes []string{"this", "is", "funny"} // Splits a string path using the delimiter '/'.
// e.g. "this/is/funny" becomes []string{"this", "is", "funny"}
func splitPath(requestPath string) (path []string) { func splitPath(requestPath string) (path []string) {
path = strings.Split(requestPath, "/") path = strings.Split(requestPath, "/")
// first element is empty string // first element is empty string
@ -356,22 +371,26 @@ func splitPath(requestPath string) (path []string) {
return path return path
} }
// Implements ABCI. // Query implements the ABCI interface. It delegates to CommitMultiStore if it
// Delegates to CommitMultiStore if it implements Queryable // implements Queryable.
func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) { func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
path := splitPath(req.Path) path := splitPath(req.Path)
if len(path) == 0 { if len(path) == 0 {
msg := "no query path provided" msg := "no query path provided"
return sdk.ErrUnknownRequest(msg).QueryResult() return sdk.ErrUnknownRequest(msg).QueryResult()
} }
switch path[0] { switch path[0] {
// "/app" prefix for special application queries // "/app" prefix for special application queries
case "app": case "app":
return handleQueryApp(app, path, req) return handleQueryApp(app, path, req)
case "store": case "store":
return handleQueryStore(app, path, req) return handleQueryStore(app, path, req)
case "p2p": case "p2p":
return handleQueryP2P(app, path, req) return handleQueryP2P(app, path, req)
case "custom": case "custom":
return handleQueryCustom(app, path, req) return handleQueryCustom(app, path, req)
} }
@ -383,6 +402,7 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) { func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
if len(path) >= 2 { if len(path) >= 2 {
var result sdk.Result var result sdk.Result
switch path[1] { switch path[1] {
case "simulate": case "simulate":
txBytes := req.Data txBytes := req.Data
@ -390,19 +410,20 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abc
if err != nil { if err != nil {
result = err.Result() result = err.Result()
} else { } else {
result = app.Simulate(tx) result = app.Simulate(txBytes, tx)
} }
case "version": case "version":
return abci.ResponseQuery{ return abci.ResponseQuery{
Code: uint32(sdk.CodeOK), Code: uint32(sdk.CodeOK),
Codespace: string(sdk.CodespaceRoot), Codespace: string(sdk.CodespaceRoot),
Value: []byte(version.GetVersion()), Value: []byte(version.Version),
} }
default: default:
result = sdk.ErrUnknownRequest(fmt.Sprintf("Unknown query: %s", path)).Result() result = sdk.ErrUnknownRequest(fmt.Sprintf("Unknown query: %s", path)).Result()
} }
// Encode with json
value := codec.Cdc.MustMarshalBinaryLengthPrefixed(result) value := codec.Cdc.MustMarshalBinaryLengthPrefixed(result)
return abci.ResponseQuery{ return abci.ResponseQuery{
Code: uint32(sdk.CodeOK), Code: uint32(sdk.CodeOK),
@ -410,6 +431,7 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abc
Value: value, Value: value,
} }
} }
msg := "Expected second parameter to be either simulate or version, neither was present" msg := "Expected second parameter to be either simulate or version, neither was present"
return sdk.ErrUnknownRequest(msg).QueryResult() return sdk.ErrUnknownRequest(msg).QueryResult()
} }
@ -421,51 +443,57 @@ func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) (res a
msg := "multistore doesn't support queries" msg := "multistore doesn't support queries"
return sdk.ErrUnknownRequest(msg).QueryResult() return sdk.ErrUnknownRequest(msg).QueryResult()
} }
req.Path = "/" + strings.Join(path[1:], "/") req.Path = "/" + strings.Join(path[1:], "/")
return queryable.Query(req) return queryable.Query(req)
} }
// nolint: unparam func handleQueryP2P(app *BaseApp, path []string, _ abci.RequestQuery) (res abci.ResponseQuery) {
func handleQueryP2P(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
// "/p2p" prefix for p2p queries // "/p2p" prefix for p2p queries
if len(path) >= 4 { if len(path) >= 4 {
if path[1] == "filter" { cmd, typ, arg := path[1], path[2], path[3]
if path[2] == "addr" { switch cmd {
return app.FilterPeerByAddrPort(path[3]) case "filter":
switch typ {
case "addr":
return app.FilterPeerByAddrPort(arg)
case "id":
return app.FilterPeerByID(arg)
} }
if path[2] == "pubkey" { default:
// 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" msg := "Expected second parameter to be filter"
return sdk.ErrUnknownRequest(msg).QueryResult() return sdk.ErrUnknownRequest(msg).QueryResult()
} }
} }
msg := "Expected path is p2p filter <addr|pubkey> <parameter>" msg := "Expected path is p2p filter <addr|id> <parameter>"
return sdk.ErrUnknownRequest(msg).QueryResult() return sdk.ErrUnknownRequest(msg).QueryResult()
} }
func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) { func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
// path[0] should be "custom" because "/custom" prefix is required for keeper queries. // path[0] should be "custom" because "/custom" prefix is required for keeper
// the queryRouter routes using path[1]. For example, in the path "custom/gov/proposal", queryRouter routes using "gov" // queries.
//
// The queryRouter routes using path[1]. For example, in the path
// "custom/gov/proposal", queryRouter routes using "gov".
if len(path) < 2 || path[1] == "" { if len(path) < 2 || path[1] == "" {
return sdk.ErrUnknownRequest("No route for custom query specified").QueryResult() return sdk.ErrUnknownRequest("No route for custom query specified").QueryResult()
} }
querier := app.queryRouter.Route(path[1]) querier := app.queryRouter.Route(path[1])
if querier == nil { if querier == nil {
return sdk.ErrUnknownRequest(fmt.Sprintf("no custom querier found for route %s", path[1])).QueryResult() return sdk.ErrUnknownRequest(fmt.Sprintf("no custom querier found for route %s", path[1])).QueryResult()
} }
// Cache wrap the commit-multistore for safety. // cache wrap the commit-multistore for safety
ctx := sdk.NewContext( ctx := sdk.NewContext(
app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger, app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.logger,
).WithMinGasPrices(app.minGasPrices) ).WithMinGasPrices(app.minGasPrices)
// Passes the rest of the path as an argument to the querier. // Passes the rest of the path as an argument to the querier.
// For example, in the path "custom/gov/proposal/test", the gov querier gets []string{"proposal", "test"} as the path //
// For example, in the path "custom/gov/proposal/test", the gov querier gets
// []string{"proposal", "test"} as the path.
resBytes, err := querier(ctx, path[2:], req) resBytes, err := querier(ctx, path[2:], req)
if err != nil { if err != nil {
return abci.ResponseQuery{ return abci.ResponseQuery{
@ -474,6 +502,7 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res
Log: err.ABCILog(), Log: err.ABCILog(),
} }
} }
return abci.ResponseQuery{ return abci.ResponseQuery{
Code: uint32(sdk.CodeOK), Code: uint32(sdk.CodeOK),
Value: resBytes, Value: resBytes,
@ -483,8 +512,7 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res
// BeginBlock implements the ABCI application interface. // BeginBlock implements the ABCI application interface.
func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) { func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) {
if app.cms.TracingEnabled() { if app.cms.TracingEnabled() {
app.cms.ResetTraceContext() app.cms.SetTracingContext(sdk.TraceContext(
app.cms.WithTracingContext(sdk.TraceContext(
map[string]interface{}{"blockHeight": req.Header.Height}, map[string]interface{}{"blockHeight": req.Header.Height},
)) ))
} }
@ -517,20 +545,20 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg
} }
// set the signed validators for addition to context in deliverTx // set the signed validators for addition to context in deliverTx
// TODO: communicate this result to the address to pubkey map in slashing
app.voteInfos = req.LastCommitInfo.GetVotes() app.voteInfos = req.LastCommitInfo.GetVotes()
return return
} }
// CheckTx implements ABCI // CheckTx implements the ABCI interface. It runs the "basic checks" to see
// CheckTx runs the "basic checks" to see whether or not a transaction can possibly be executed, // whether or not a transaction can possibly be executed, first decoding, then
// first decoding, then the ante handler (which checks signatures/fees/ValidateBasic), // the ante handler (which checks signatures/fees/ValidateBasic), then finally
// then finally the route match to see whether a handler exists. CheckTx does not run the actual // the route match to see whether a handler exists.
// Msg handler function(s). //
// NOTE:CheckTx does not run the actual Msg handler function(s).
func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) { func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
// Decode the Tx.
var result sdk.Result var result sdk.Result
var tx, err = app.txDecoder(txBytes)
tx, err := app.txDecoder(txBytes)
if err != nil { if err != nil {
result = err.Result() result = err.Result()
} else { } else {
@ -547,22 +575,17 @@ func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
} }
} }
// Implements ABCI // DeliverTx implements the ABCI interface.
func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) { func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
// Decode the Tx.
var tx, err = app.txDecoder(txBytes)
var result sdk.Result var result sdk.Result
tx, err := app.txDecoder(txBytes)
if err != nil { if err != nil {
result = err.Result() result = err.Result()
} else { } else {
result = app.runTx(runTxModeDeliver, txBytes, tx) result = app.runTx(runTxModeDeliver, txBytes, tx)
} }
// 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{ return abci.ResponseDeliverTx{
Code: uint32(result.Code), Code: uint32(result.Code),
Codespace: string(result.Codespace), Codespace: string(result.Codespace),
@ -574,11 +597,10 @@ func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
} }
} }
// Basic validator for msgs // validateBasicTxMsgs executes basic validator calls for messages.
func validateBasicTxMsgs(msgs []sdk.Msg) sdk.Error { func validateBasicTxMsgs(msgs []sdk.Msg) sdk.Error {
if msgs == nil || len(msgs) == 0 { if msgs == nil || len(msgs) == 0 {
// TODO: probably shouldn't be ErrInternal. Maybe new ErrInvalidMessage, or ? return sdk.ErrUnknownRequest("Tx.GetMsgs() must return at least one message in list")
return sdk.ErrInternal("Tx.GetMsgs() must return at least one message in list")
} }
for _, msg := range msgs { for _, msg := range msgs {
@ -598,22 +620,25 @@ func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) (ctx sdk.Con
WithTxBytes(txBytes). WithTxBytes(txBytes).
WithVoteInfos(app.voteInfos). WithVoteInfos(app.voteInfos).
WithConsensusParams(app.consensusParams) WithConsensusParams(app.consensusParams)
if mode == runTxModeSimulate { if mode == runTxModeSimulate {
ctx, _ = ctx.CacheContext() ctx, _ = ctx.CacheContext()
} }
return return
} }
// Iterates through msgs and executes them // runMsgs iterates through all the messages and executes them.
func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (result sdk.Result) { func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (result sdk.Result) {
// accumulate results
logs := make([]string, 0, len(msgs)) logs := make([]string, 0, len(msgs))
var data []byte // NOTE: we just append them all (?!) var data []byte // NOTE: we just append them all (?!)
var tags sdk.Tags // also just append them all var tags sdk.Tags // also just append them all
var code sdk.CodeType var code sdk.CodeType
var codespace sdk.CodespaceType var codespace sdk.CodespaceType
for msgIdx, msg := range msgs { for msgIdx, msg := range msgs {
// Match route. // match message route
msgRoute := msg.Route() msgRoute := msg.Route()
handler := app.router.Route(msgRoute) handler := app.router.Route(msgRoute)
if handler == nil { if handler == nil {
@ -621,20 +646,20 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re
} }
var msgResult sdk.Result var msgResult sdk.Result
// Skip actual execution for CheckTx
// skip actual execution for CheckTx mode
if mode != runTxModeCheck { if mode != runTxModeCheck {
msgResult = handler(ctx, msg) msgResult = handler(ctx, msg)
} }
// NOTE: GasWanted is determined by ante handler and // NOTE: GasWanted is determined by ante handler and GasUsed by the GasMeter.
// GasUsed by the GasMeter
// Append Data and Tags // Result.Data must be length prefixed in order to separate each result
data = append(data, msgResult.Data...) data = append(data, msgResult.Data...)
tags = append(tags, sdk.MakeTag(sdk.TagAction, []byte(msg.Type()))) tags = append(tags, sdk.MakeTag(sdk.TagAction, msg.Type()))
tags = append(tags, msgResult.Tags...) tags = append(tags, msgResult.Tags...)
// Stop execution and return on first failed message. // stop execution and return on first failed message
if !msgResult.IsOK() { if !msgResult.IsOK() {
logs = append(logs, fmt.Sprintf("Msg %d failed: %s", msgIdx, msgResult.Log)) logs = append(logs, fmt.Sprintf("Msg %d failed: %s", msgIdx, msgResult.Log))
code = msgResult.Code code = msgResult.Code
@ -642,19 +667,17 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re
break break
} }
// Construct usable logs in multi-message transactions. // construct usable logs in multi-message transactions
logs = append(logs, fmt.Sprintf("Msg %d: %s", msgIdx, msgResult.Log)) logs = append(logs, fmt.Sprintf("Msg %d: %s", msgIdx, msgResult.Log))
} }
// Set the final gas values.
result = sdk.Result{ result = sdk.Result{
Code: code, Code: code,
Codespace: codespace, Codespace: codespace,
Data: data, Data: data,
Log: strings.Join(logs, "\n"), Log: strings.Join(logs, "\n"),
GasUsed: ctx.GasMeter().GasConsumed(), GasUsed: ctx.GasMeter().GasConsumed(),
// TODO: FeeAmount/FeeDenom Tags: tags,
Tags: tags,
} }
return result return result
@ -679,7 +702,7 @@ func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (
// TODO: https://github.com/cosmos/cosmos-sdk/issues/2824 // TODO: https://github.com/cosmos/cosmos-sdk/issues/2824
msCache := ms.CacheMultiStore() msCache := ms.CacheMultiStore()
if msCache.TracingEnabled() { if msCache.TracingEnabled() {
msCache = msCache.WithTracingContext( msCache = msCache.SetTracingContext(
sdk.TraceContext( sdk.TraceContext(
map[string]interface{}{ map[string]interface{}{
"txHash": fmt.Sprintf("%X", tmhash.Sum(txBytes)), "txHash": fmt.Sprintf("%X", tmhash.Sum(txBytes)),
@ -719,7 +742,10 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
if r := recover(); r != nil { if r := recover(); r != nil {
switch rType := r.(type) { switch rType := r.(type) {
case sdk.ErrorOutOfGas: case sdk.ErrorOutOfGas:
log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor) log := fmt.Sprintf(
"out of gas in location: %v; gasWanted: %d, gasUsed: %d",
rType.Descriptor, gasWanted, ctx.GasMeter().GasConsumed(),
)
result = sdk.ErrOutOfGas(log).Result() result = sdk.ErrOutOfGas(log).Result()
default: default:
log := fmt.Sprintf("recovered: %v\nstack:\n%v", r, string(debug.Stack())) log := fmt.Sprintf("recovered: %v\nstack:\n%v", r, string(debug.Stack()))
@ -731,11 +757,11 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
result.GasUsed = ctx.GasMeter().GasConsumed() result.GasUsed = ctx.GasMeter().GasConsumed()
}() }()
// If BlockGasMeter() panics it will be caught by the above recover and // If BlockGasMeter() panics it will be caught by the above recover and will
// return an error - in any case BlockGasMeter will consume gas past // return an error - in any case BlockGasMeter will consume gas past the limit.
// the limit. //
// NOTE: this must exist in a separate defer function for the // NOTE: This must exist in a separate defer function for the above recovery
// above recovery to recover from this one // to recover from this one.
defer func() { defer func() {
if mode == runTxModeDeliver { if mode == runTxModeDeliver {
ctx.BlockGasMeter().ConsumeGas( ctx.BlockGasMeter().ConsumeGas(
@ -744,7 +770,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
) )
if ctx.BlockGasMeter().GasConsumed() < startingGas { if ctx.BlockGasMeter().GasConsumed() < startingGas {
panic(sdk.ErrorGasOverflow{"tx gas summation"}) panic(sdk.ErrorGasOverflow{Descriptor: "tx gas summation"})
} }
} }
}() }()
@ -754,7 +780,6 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
return err.Result() return err.Result()
} }
// Execute the ante handler if one is defined.
if app.anteHandler != nil { if app.anteHandler != nil {
var anteCtx sdk.Context var anteCtx sdk.Context
var msCache sdk.CacheMultiStore var msCache sdk.CacheMultiStore
@ -780,12 +805,13 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
ctx = newCtx.WithMultiStore(ms) ctx = newCtx.WithMultiStore(ms)
} }
gasWanted = result.GasWanted
if abort { if abort {
return result return result
} }
msCache.Write() msCache.Write()
gasWanted = result.GasWanted
} }
if mode == runTxModeCheck { if mode == runTxModeCheck {
@ -810,10 +836,10 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
return return
} }
// EndBlock implements the ABCI application interface. // EndBlock implements the ABCI interface.
func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) { func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) {
if app.deliverState.ms.TracingEnabled() { if app.deliverState.ms.TracingEnabled() {
app.deliverState.ms = app.deliverState.ms.ResetTraceContext().(sdk.CacheMultiStore) app.deliverState.ms = app.deliverState.ms.SetTracingContext(nil).(sdk.CacheMultiStore)
} }
if app.endBlocker != nil { if app.endBlocker != nil {
@ -823,27 +849,41 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc
return return
} }
// Implements ABCI // Commit implements the ABCI interface.
func (app *BaseApp) Commit() (res abci.ResponseCommit) { func (app *BaseApp) Commit() (res abci.ResponseCommit) {
header := app.deliverState.ctx.BlockHeader() header := app.deliverState.ctx.BlockHeader()
// Write the Deliver state and commit the MultiStore // write the Deliver state and commit the MultiStore
app.deliverState.ms.Write() app.deliverState.ms.Write()
commitID := app.cms.Commit() commitID := app.cms.Commit()
// TODO: this is missing a module identifier and dumps byte array app.logger.Debug("Commit synced", "commit", fmt.Sprintf("%X", commitID))
app.Logger.Debug("Commit synced",
"commit", fmt.Sprintf("%X", commitID),
)
// Reset the Check state to the latest committed // Reset the Check state to the latest committed.
//
// NOTE: safe because Tendermint holds a lock on the mempool for Commit. // NOTE: safe because Tendermint holds a lock on the mempool for Commit.
// Use the header from this latest block. // Use the header from this latest block.
app.setCheckState(header) app.setCheckState(header)
// Empty the Deliver state // empty/reset the deliver state
app.deliverState = nil app.deliverState = nil
return abci.ResponseCommit{ return abci.ResponseCommit{
Data: commitID.Hash, Data: commitID.Hash,
} }
} }
// ----------------------------------------------------------------------------
// State
type state struct {
ms sdk.CacheMultiStore
ctx sdk.Context
}
func (st *state) CacheMultiStore() sdk.CacheMultiStore {
return st.ms.CacheMultiStore()
}
func (st *state) Context() sdk.Context {
return st.ctx
}

View File

@ -7,7 +7,7 @@ import (
"os" "os"
"testing" "testing"
"github.com/cosmos/cosmos-sdk/store" store "github.com/cosmos/cosmos-sdk/store/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -21,14 +21,10 @@ import (
) )
var ( var (
// make some cap keys
capKey1 = sdk.NewKVStoreKey("key1") capKey1 = sdk.NewKVStoreKey("key1")
capKey2 = sdk.NewKVStoreKey("key2") capKey2 = sdk.NewKVStoreKey("key2")
) )
//------------------------------------------------------------------------------------------
// Helpers for setup. Most tests should be able to use setupBaseApp
func defaultLogger() log.Logger { func defaultLogger() log.Logger {
return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
} }
@ -70,9 +66,6 @@ func setupBaseApp(t *testing.T, options ...func(*BaseApp)) *BaseApp {
return app return app
} }
//------------------------------------------------------------------------------------------
// test mounting and loading stores
func TestMountStores(t *testing.T) { func TestMountStores(t *testing.T) {
app := setupBaseApp(t) app := setupBaseApp(t)
@ -137,6 +130,41 @@ func TestLoadVersion(t *testing.T) {
testLoadVersionHelper(t, app, int64(2), commitID2) testLoadVersionHelper(t, app, int64(2), commitID2)
} }
func TestLoadVersionInvalid(t *testing.T) {
logger := log.NewNopLogger()
pruningOpt := SetPruning(store.PruneSyncable)
db := dbm.NewMemDB()
name := t.Name()
app := NewBaseApp(name, logger, db, nil, pruningOpt)
capKey := sdk.NewKVStoreKey(MainStoreKey)
app.MountStores(capKey)
err := app.LoadLatestVersion(capKey)
require.Nil(t, err)
// require error when loading an invalid version
err = app.LoadVersion(-1, capKey)
require.Error(t, err)
header := abci.Header{Height: 1}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
res := app.Commit()
commitID1 := sdk.CommitID{1, res.Data}
// create a new app with the stores mounted under the same cap key
app = NewBaseApp(name, logger, db, nil, pruningOpt)
app.MountStores(capKey)
// require we can load the latest version
err = app.LoadVersion(1, capKey)
require.Nil(t, err)
testLoadVersionHelper(t, app, int64(1), commitID1)
// require error when loading an invalid version
err = app.LoadVersion(2, capKey)
require.Error(t, err)
}
func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, expectedID sdk.CommitID) { func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, expectedID sdk.CommitID) {
lastHeight := app.LastBlockHeight() lastHeight := app.LastBlockHeight()
lastID := app.LastCommitID() lastID := app.LastCommitID()
@ -157,39 +185,21 @@ func testChangeNameHelper(name string) func(*BaseApp) {
} }
} }
// Test that the app hash is static
// TODO: https://github.com/cosmos/cosmos-sdk/issues/520
/*func TestStaticAppHash(t *testing.T) {
app := newBaseApp(t.Name())
// make a cap key and mount the store
capKey := sdk.NewKVStoreKey(MainStoreKey)
app.MountStores(capKey)
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
require.Nil(t, err)
// execute some blocks
header := abci.Header{Height: 1}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
res := app.Commit()
commitID1 := sdk.CommitID{1, res.Data}
header = abci.Header{Height: 2}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
res = app.Commit()
commitID2 := sdk.CommitID{2, res.Data}
require.Equal(t, commitID1.Hash, commitID2.Hash)
}
*/
//------------------------------------------------------------------------------------------
// test some basic abci/baseapp functionality
// Test that txs can be unmarshalled and read and that // Test that txs can be unmarshalled and read and that
// correct error codes are returned when not // correct error codes are returned when not
func TestTxDecoder(t *testing.T) { func TestTxDecoder(t *testing.T) {
// TODO codec := codec.New()
registerTestCodec(codec)
app := newBaseApp(t.Name())
tx := newTxCounter(1, 0)
txBytes := codec.MustMarshalBinaryLengthPrefixed(tx)
dTx, err := app.txDecoder(txBytes)
require.NoError(t, err)
cTx := dTx.(txTest)
require.Equal(t, tx.Counter, cTx.Counter)
} }
// Test that Info returns the latest committed state. // Test that Info returns the latest committed state.
@ -210,8 +220,46 @@ func TestInfo(t *testing.T) {
// TODO // TODO
} }
//------------------------------------------------------------------------------------------ func TestBaseAppOptionSeal(t *testing.T) {
// InitChain, BeginBlock, EndBlock app := setupBaseApp(t)
require.Panics(t, func() {
app.SetName("")
})
require.Panics(t, func() {
app.SetDB(nil)
})
require.Panics(t, func() {
app.SetCMS(nil)
})
require.Panics(t, func() {
app.SetInitChainer(nil)
})
require.Panics(t, func() {
app.SetBeginBlocker(nil)
})
require.Panics(t, func() {
app.SetEndBlocker(nil)
})
require.Panics(t, func() {
app.SetAnteHandler(nil)
})
require.Panics(t, func() {
app.SetAddrPeerFilter(nil)
})
require.Panics(t, func() {
app.SetIDPeerFilter(nil)
})
require.Panics(t, func() {
app.SetFauxMerkleMode()
})
}
func TestSetMinGasPrices(t *testing.T) {
minGasPrices := sdk.DecCoins{sdk.NewDecCoin("stake", 5000)}
app := newBaseApp(t.Name(), SetMinGasPrices(minGasPrices.String()))
require.Equal(t, minGasPrices, app.minGasPrices)
}
func TestInitChainer(t *testing.T) { func TestInitChainer(t *testing.T) {
name := t.Name() name := t.Name()
@ -280,11 +328,6 @@ func TestInitChainer(t *testing.T) {
require.Equal(t, value, res.Value) require.Equal(t, value, res.Value)
} }
//------------------------------------------------------------------------------------------
// 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.
// Simple tx with a list of Msgs. // Simple tx with a list of Msgs.
type txTest struct { type txTest struct {
Msgs []sdk.Msg Msgs []sdk.Msg
@ -417,9 +460,6 @@ func handlerMsgCounter(t *testing.T, capKey *sdk.KVStoreKey, deliverKey []byte)
} }
} }
//-----------------------------------------------------------------
// simple int mapper
func i2b(i int64) []byte { func i2b(i int64) []byte {
return []byte{byte(i)} return []byte{byte(i)}
} }
@ -655,20 +695,20 @@ func TestSimulateTx(t *testing.T) {
app.BeginBlock(abci.RequestBeginBlock{}) app.BeginBlock(abci.RequestBeginBlock{})
tx := newTxCounter(count, count) tx := newTxCounter(count, count)
txBytes, err := cdc.MarshalBinaryLengthPrefixed(tx)
require.Nil(t, err)
// simulate a message, check gas reported // simulate a message, check gas reported
result := app.Simulate(tx) result := app.Simulate(txBytes, tx)
require.True(t, result.IsOK(), result.Log) require.True(t, result.IsOK(), result.Log)
require.Equal(t, gasConsumed, result.GasUsed) require.Equal(t, gasConsumed, result.GasUsed)
// simulate again, same result // simulate again, same result
result = app.Simulate(tx) result = app.Simulate(txBytes, tx)
require.True(t, result.IsOK(), result.Log) require.True(t, result.IsOK(), result.Log)
require.Equal(t, gasConsumed, result.GasUsed) require.Equal(t, gasConsumed, result.GasUsed)
// simulate by calling Query with encoded tx // simulate by calling Query with encoded tx
txBytes, err := cdc.MarshalBinaryLengthPrefixed(tx)
require.Nil(t, err)
query := abci.RequestQuery{ query := abci.RequestQuery{
Path: "/app/simulate", Path: "/app/simulate",
Data: txBytes, Data: txBytes,
@ -686,10 +726,6 @@ func TestSimulateTx(t *testing.T) {
} }
} }
//-------------------------------------------------------------------------------------------
// Tx failure cases
// TODO: add more
func TestRunInvalidTransaction(t *testing.T) { func TestRunInvalidTransaction(t *testing.T) {
anteOpt := func(bapp *BaseApp) { anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) { bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
@ -707,7 +743,7 @@ func TestRunInvalidTransaction(t *testing.T) {
{ {
emptyTx := &txTest{} emptyTx := &txTest{}
err := app.Deliver(emptyTx) err := app.Deliver(emptyTx)
require.EqualValues(t, sdk.CodeInternal, err.Code) require.EqualValues(t, sdk.CodeUnknownRequest, err.Code)
require.EqualValues(t, sdk.CodespaceRoot, err.Codespace) require.EqualValues(t, sdk.CodespaceRoot, err.Codespace)
} }
@ -777,11 +813,6 @@ func TestTxGasLimits(t *testing.T) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) { bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted)) 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() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
switch rType := r.(type) { switch rType := r.(type) {
@ -866,11 +897,6 @@ func TestMaxBlockGasLimits(t *testing.T) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) { bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted)) 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() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
switch rType := r.(type) { switch rType := r.(type) {
@ -1031,3 +1057,162 @@ func TestBaseAppAnteHandler(t *testing.T) {
app.EndBlock(abci.RequestEndBlock{}) app.EndBlock(abci.RequestEndBlock{})
app.Commit() app.Commit()
} }
func TestGasConsumptionBadTx(t *testing.T) {
gasWanted := uint64(5)
anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasWanted))
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 = gasWanted
res.GasUsed = newCtx.GasMeter().GasConsumed()
default:
panic(r)
}
}
}()
txTest := tx.(txTest)
newCtx.GasMeter().ConsumeGas(uint64(txTest.Counter), "counter-ante")
if txTest.FailOnAnte {
return newCtx, sdk.ErrInternal("ante handler failure").Result(), true
}
res = sdk.Result{
GasWanted: gasWanted,
}
return
})
}
routerOpt := func(bapp *BaseApp) {
bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
count := msg.(msgCounter).Counter
ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler")
return sdk.Result{}
})
}
cdc := codec.New()
registerTestCodec(cdc)
app := setupBaseApp(t, anteOpt, routerOpt)
app.InitChain(abci.RequestInitChain{
ConsensusParams: &abci.ConsensusParams{
BlockSize: &abci.BlockSizeParams{
MaxGas: 9,
},
},
})
app.InitChain(abci.RequestInitChain{})
app.BeginBlock(abci.RequestBeginBlock{})
tx := newTxCounter(5, 0)
tx.setFailOnAnte(true)
txBytes, err := cdc.MarshalBinaryLengthPrefixed(tx)
require.NoError(t, err)
res := app.DeliverTx(txBytes)
require.False(t, res.IsOK(), fmt.Sprintf("%v", res))
// require next tx to fail due to black gas limit
tx = newTxCounter(5, 0)
txBytes, err = cdc.MarshalBinaryLengthPrefixed(tx)
require.NoError(t, err)
res = app.DeliverTx(txBytes)
require.False(t, res.IsOK(), fmt.Sprintf("%v", res))
}
// Test that we can only query from the latest committed state.
func TestQuery(t *testing.T) {
key, value := []byte("hello"), []byte("goodbye")
anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
store := ctx.KVStore(capKey1)
store.Set(key, value)
return
})
}
routerOpt := func(bapp *BaseApp) {
bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
store := ctx.KVStore(capKey1)
store.Set(key, value)
return sdk.Result{}
})
}
app := setupBaseApp(t, anteOpt, routerOpt)
app.InitChain(abci.RequestInitChain{})
// NOTE: "/store/key1" tells us KVStore
// and the final "/key" says to use the data as the
// key in the given KVStore ...
query := abci.RequestQuery{
Path: "/store/key1/key",
Data: key,
}
tx := newTxCounter(0, 0)
// query is empty before we do anything
res := app.Query(query)
require.Equal(t, 0, len(res.Value))
// query is still empty after a CheckTx
resTx := app.Check(tx)
require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx))
res = app.Query(query)
require.Equal(t, 0, len(res.Value))
// query is still empty after a DeliverTx before we commit
app.BeginBlock(abci.RequestBeginBlock{})
resTx = app.Deliver(tx)
require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx))
res = app.Query(query)
require.Equal(t, 0, len(res.Value))
// query returns correct value after Commit
app.Commit()
res = app.Query(query)
require.Equal(t, value, res.Value)
}
// Test p2p filter queries
func TestP2PQuery(t *testing.T) {
addrPeerFilterOpt := func(bapp *BaseApp) {
bapp.SetAddrPeerFilter(func(addrport string) abci.ResponseQuery {
require.Equal(t, "1.1.1.1:8000", addrport)
return abci.ResponseQuery{Code: uint32(3)}
})
}
idPeerFilterOpt := func(bapp *BaseApp) {
bapp.SetIDPeerFilter(func(id string) abci.ResponseQuery {
require.Equal(t, "testid", id)
return abci.ResponseQuery{Code: uint32(4)}
})
}
app := setupBaseApp(t, addrPeerFilterOpt, idPeerFilterOpt)
addrQuery := abci.RequestQuery{
Path: "/p2p/filter/addr/1.1.1.1:8000",
}
res := app.Query(addrQuery)
require.Equal(t, uint32(3), res.Code)
idQuery := abci.RequestQuery{
Path: "/p2p/filter/id/testid",
}
res = app.Query(idQuery)
require.Equal(t, uint32(4), res.Code)
}

View File

@ -1,21 +1,23 @@
package baseapp package baseapp
import ( import (
"github.com/tendermint/tendermint/abci/server" "regexp"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
cmn "github.com/tendermint/tendermint/libs/common"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
) )
var isAlphaNumeric = regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString
// nolint - Mostly for testing // nolint - Mostly for testing
func (app *BaseApp) Check(tx sdk.Tx) (result sdk.Result) { func (app *BaseApp) Check(tx sdk.Tx) (result sdk.Result) {
return app.runTx(runTxModeCheck, nil, tx) return app.runTx(runTxModeCheck, nil, tx)
} }
// nolint - full tx execution // nolint - full tx execution
func (app *BaseApp) Simulate(tx sdk.Tx) (result sdk.Result) { func (app *BaseApp) Simulate(txBytes []byte, tx sdk.Tx) (result sdk.Result) {
return app.runTx(runTxModeSimulate, nil, tx) return app.runTx(runTxModeSimulate, txBytes, tx)
} }
// nolint // nolint
@ -23,27 +25,13 @@ func (app *BaseApp) Deliver(tx sdk.Tx) (result sdk.Result) {
return app.runTx(runTxModeDeliver, nil, tx) return app.runTx(runTxModeDeliver, nil, tx)
} }
// RunForever BasecoinApp execution and cleanup // Context with current {check, deliver}State of the app
func RunForever(app abci.Application) { // used by tests
func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context {
// Start the ABCI server if isCheckTx {
srv, err := server.NewServer("0.0.0.0:26658", "socket", app) return sdk.NewContext(app.checkState.ms, header, true, app.logger).
if err != nil { WithMinGasPrices(app.minGasPrices)
cmn.Exit(err.Error())
return
}
err = srv.Start()
if err != nil {
cmn.Exit(err.Error())
return
} }
// Wait forever return sdk.NewContext(app.deliverState.ms, header, false, app.logger)
cmn.TrapSignal(func() {
// Cleanup
err := srv.Stop()
if err != nil {
cmn.Exit(err.Error())
}
})
} }

View File

@ -18,7 +18,7 @@ func SetPruning(opts sdk.PruningOptions) func(*BaseApp) {
return func(bap *BaseApp) { bap.cms.SetPruning(opts) } return func(bap *BaseApp) { bap.cms.SetPruning(opts) }
} }
// SetMinimumGasPrices returns an option that sets the minimum gas prices on the app. // SetMinGasPrices returns an option that sets the minimum gas prices on the app.
func SetMinGasPrices(gasPricesStr string) func(*BaseApp) { func SetMinGasPrices(gasPricesStr string) func(*BaseApp) {
gasPrices, err := sdk.ParseDecCoins(gasPricesStr) gasPrices, err := sdk.ParseDecCoins(gasPricesStr)
if err != nil { if err != nil {
@ -84,11 +84,11 @@ func (app *BaseApp) SetAddrPeerFilter(pf sdk.PeerFilter) {
app.addrPeerFilter = pf app.addrPeerFilter = pf
} }
func (app *BaseApp) SetPubKeyPeerFilter(pf sdk.PeerFilter) { func (app *BaseApp) SetIDPeerFilter(pf sdk.PeerFilter) {
if app.sealed { if app.sealed {
panic("SetPubKeyPeerFilter() on sealed BaseApp") panic("SetIDPeerFilter() on sealed BaseApp")
} }
app.pubkeyPeerFilter = pf app.idPeerFilter = pf
} }
func (app *BaseApp) SetFauxMerkleMode() { func (app *BaseApp) SetFauxMerkleMode() {
@ -97,25 +97,3 @@ func (app *BaseApp) SetFauxMerkleMode() {
} }
app.fauxMerkleMode = true app.fauxMerkleMode = true
} }
//----------------------------------------
// TODO: move these out of this file?
func (app *BaseApp) Router() Router {
if app.sealed {
panic("Router() on sealed BaseApp")
}
return app.router
}
func (app *BaseApp) QueryRouter() QueryRouter {
return app.queryRouter
}
func (app *BaseApp) Seal() { app.sealed = true }
func (app *BaseApp) IsSealed() bool { return app.sealed }
func (app *BaseApp) enforceSeal() {
if !app.sealed {
panic("enforceSeal() on BaseApp but not sealed")
}
}

View File

@ -1,54 +0,0 @@
package baseapp
/*
XXX Make this work with MultiStore.
XXX It will require some interfaces updates in store/types.go.
if len(reqQuery.Data) == 0 {
resQuery.Log = "Query cannot be zero length"
resQuery.Code = abci.CodeType_EncodingError
return
}
// set the query response height to current
tree := app.state.Committed()
height := reqQuery.Height
if height == 0 {
// TODO: once the rpc actually passes in non-zero
// heights we can use to query right after a tx
// we must retrun most recent, even if apphash
// is not yet in the blockchain
withProof := app.CommittedHeight() - 1
if tree.Tree.VersionExists(withProof) {
height = withProof
} else {
height = app.CommittedHeight()
}
}
resQuery.Height = height
switch reqQuery.Path {
case "/store", "/key": // Get by key
key := reqQuery.Data // Data holds the key bytes
resQuery.Key = key
if reqQuery.Prove {
value, proof, err := tree.GetVersionedWithProof(key, height)
if err != nil {
resQuery.Log = err.Error()
break
}
resQuery.Value = value
resQuery.Proof = proof.Bytes()
} else {
value := tree.Get(key)
resQuery.Value = value
}
default:
resQuery.Code = abci.CodeType_UnknownRequest
resQuery.Log = cmn.Fmt("Unexpected Query path: %v", reqQuery.Path)
}
return
*/

View File

@ -1,97 +0,0 @@
package baseapp
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Test that we can only query from the latest committed state.
func TestQuery(t *testing.T) {
key, value := []byte("hello"), []byte("goodbye")
anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
store := ctx.KVStore(capKey1)
store.Set(key, value)
return
})
}
routerOpt := func(bapp *BaseApp) {
bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
store := ctx.KVStore(capKey1)
store.Set(key, value)
return sdk.Result{}
})
}
app := setupBaseApp(t, anteOpt, routerOpt)
app.InitChain(abci.RequestInitChain{})
// NOTE: "/store/key1" tells us KVStore
// and the final "/key" says to use the data as the
// key in the given KVStore ...
query := abci.RequestQuery{
Path: "/store/key1/key",
Data: key,
}
tx := newTxCounter(0, 0)
// query is empty before we do anything
res := app.Query(query)
require.Equal(t, 0, len(res.Value))
// query is still empty after a CheckTx
resTx := app.Check(tx)
require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx))
res = app.Query(query)
require.Equal(t, 0, len(res.Value))
// query is still empty after a DeliverTx before we commit
app.BeginBlock(abci.RequestBeginBlock{})
resTx = app.Deliver(tx)
require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx))
res = app.Query(query)
require.Equal(t, 0, len(res.Value))
// query returns correct value after Commit
app.Commit()
res = app.Query(query)
require.Equal(t, value, res.Value)
}
// Test p2p filter queries
func TestP2PQuery(t *testing.T) {
addrPeerFilterOpt := func(bapp *BaseApp) {
bapp.SetAddrPeerFilter(func(addrport string) abci.ResponseQuery {
require.Equal(t, "1.1.1.1:8000", addrport)
return abci.ResponseQuery{Code: uint32(3)}
})
}
pubkeyPeerFilterOpt := func(bapp *BaseApp) {
bapp.SetPubKeyPeerFilter(func(pubkey string) abci.ResponseQuery {
require.Equal(t, "testpubkey", pubkey)
return abci.ResponseQuery{Code: uint32(4)}
})
}
app := setupBaseApp(t, addrPeerFilterOpt, pubkeyPeerFilterOpt)
addrQuery := abci.RequestQuery{
Path: "/p2p/filter/addr/1.1.1.1:8000",
}
res := app.Query(addrQuery)
require.Equal(t, uint32(3), res.Code)
pubkeyQuery := abci.RequestQuery{
Path: "/p2p/filter/pubkey/testpubkey",
}
res = app.Query(pubkeyQuery)
require.Equal(t, uint32(4), res.Code)
}

View File

@ -1,6 +1,8 @@
package baseapp package baseapp
import ( import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
) )
@ -10,32 +12,34 @@ type QueryRouter interface {
Route(path string) (h sdk.Querier) Route(path string) (h sdk.Querier)
} }
type queryrouter struct { type queryRouter struct {
routes map[string]sdk.Querier routes map[string]sdk.Querier
} }
// nolint // NewQueryRouter returns a reference to a new queryRouter.
// NewRouter - create new router //
// TODO either make Function unexported or make return type (router) Exported // TODO: Either make the function private or make return type (queryRouter) public.
func NewQueryRouter() *queryrouter { func NewQueryRouter() *queryRouter { // nolint: golint
return &queryrouter{ return &queryRouter{
routes: map[string]sdk.Querier{}, routes: map[string]sdk.Querier{},
} }
} }
// AddRoute - Adds an sdk.Querier to the route provided. Panics on duplicate // AddRoute adds a query path to the router with a given Querier. It will panic
func (rtr *queryrouter) AddRoute(r string, q sdk.Querier) QueryRouter { // if a duplicate route is given. The route must be alphanumeric.
if !isAlphaNumeric(r) { func (qrt *queryRouter) AddRoute(path string, q sdk.Querier) QueryRouter {
if !isAlphaNumeric(path) {
panic("route expressions can only contain alphanumeric characters") panic("route expressions can only contain alphanumeric characters")
} }
if rtr.routes[r] != nil { if qrt.routes[path] != nil {
panic("route has already been initialized") panic(fmt.Sprintf("route %s has already been initialized", path))
} }
rtr.routes[r] = q
return rtr qrt.routes[path] = q
return qrt
} }
// Returns the sdk.Querier for a certain route path // Route returns the Querier for a given query route path.
func (rtr *queryrouter) Route(path string) (h sdk.Querier) { func (qrt *queryRouter) Route(path string) sdk.Querier {
return rtr.routes[path] return qrt.routes[path]
} }

View File

@ -0,0 +1,33 @@
package baseapp
import (
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
var testQuerier = func(_ sdk.Context, _ []string, _ abci.RequestQuery) (res []byte, err sdk.Error) {
return nil, nil
}
func TestQueryRouter(t *testing.T) {
qr := NewQueryRouter()
// require panic on invalid route
require.Panics(t, func() {
qr.AddRoute("*", testQuerier)
})
qr.AddRoute("testRoute", testQuerier)
q := qr.Route("testRoute")
require.NotNil(t, q)
// require panic on duplicate route
require.Panics(t, func() {
qr.AddRoute("testRoute", testQuerier)
})
}

View File

@ -1,7 +1,7 @@
package baseapp package baseapp
import ( import (
"regexp" "fmt"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
) )
@ -12,44 +12,36 @@ type Router interface {
Route(path string) (h sdk.Handler) Route(path string) (h sdk.Handler)
} }
// map a transaction type to a handler and an initgenesis function
type route struct {
r string
h sdk.Handler
}
type router struct { type router struct {
routes []route routes map[string]sdk.Handler
} }
// nolint // NewRouter returns a reference to a new router.
// NewRouter - create new router //
// TODO either make Function unexported or make return type (router) Exported // TODO: Either make the function private or make return type (router) public.
func NewRouter() *router { func NewRouter() *router { // nolint: golint
return &router{ return &router{
routes: make([]route, 0), routes: make(map[string]sdk.Handler),
} }
} }
var isAlphaNumeric = regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString // AddRoute adds a route path to the router with a given handler. The route must
// be alphanumeric.
// AddRoute - TODO add description func (rtr *router) AddRoute(path string, h sdk.Handler) Router {
func (rtr *router) AddRoute(r string, h sdk.Handler) Router { if !isAlphaNumeric(path) {
if !isAlphaNumeric(r) {
panic("route expressions can only contain alphanumeric characters") panic("route expressions can only contain alphanumeric characters")
} }
rtr.routes = append(rtr.routes, route{r, h}) if rtr.routes[path] != nil {
panic(fmt.Sprintf("route %s has already been initialized", path))
}
rtr.routes[path] = h
return rtr return rtr
} }
// Route - TODO add description // Route returns a handler for a given route path.
// TODO handle expressive matches. //
func (rtr *router) Route(path string) (h sdk.Handler) { // TODO: Handle expressive matches.
for _, route := range rtr.routes { func (rtr *router) Route(path string) sdk.Handler {
if route.r == path { return rtr.routes[path]
return route.h
}
}
return nil
} }

31
baseapp/router_test.go Normal file
View File

@ -0,0 +1,31 @@
package baseapp
import (
"testing"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
)
var testHandler = func(_ sdk.Context, _ sdk.Msg) sdk.Result {
return sdk.Result{}
}
func TestRouter(t *testing.T) {
rtr := NewRouter()
// require panic on invalid route
require.Panics(t, func() {
rtr.AddRoute("*", testHandler)
})
rtr.AddRoute("testRoute", testHandler)
h := rtr.Route("testRoute")
require.NotNil(t, h)
// require panic on duplicate route
require.Panics(t, func() {
rtr.AddRoute("testRoute", testHandler)
})
}

View File

@ -1,22 +0,0 @@
{
"chain_id": "foo_bar_chain",
"app_options": {
"accounts": [{
"pub_key": {
"type": "ed25519",
"data": "6880db93598e283a67c4d88fc67a8858aa2de70f713fe94a5109e29c137100c2"
},
"coins": [
{
"denom": "blank",
"amount": 12345
},
{
"denom": "ETH",
"amount": 654321
}
]
}],
"plugin_options": ["plugin1/key1", "value1", "plugin1/key2", "value2"]
}
}

View File

@ -1,39 +0,0 @@
{
"chain_id": "addr_accounts_chain",
"app_options": {
"accounts": [{
"name": "alice",
"pub_key": {
"type": "ed25519",
"data": "DBD9A46C45868F0A37C92B53113C09B048FBD87B5FBC2F8B199052973B8FAA36"
},
"coins": [
{
"denom": "one",
"amount": 111
}
]
}, {
"name": "bob",
"address": "C471FB670E44D219EE6DF2FC284BE38793ACBCE1",
"coins": [
{
"denom": "two",
"amount": 222
}
]
}, {
"name": "sam",
"pub_key": {
"type": "secp256k1",
"data": "02AA8342F63CCCCE6DDB128525BA048CE0B2993DA3B4308746E1F216361A87651E"
},
"coins": [
{
"denom": "four",
"amount": 444
}
]
}]
}
}

View File

@ -1,52 +0,0 @@
{
"chain_id": "addr_accounts_chain",
"app_options": {
"accounts": [{
"name": "alice",
"pub_key": {
"type": "ed25519",
"data": "DBD9A46C45868F0A37C92B53113C09B048FBD87B5FBC2F8B199052973B8FAA36"
},
"coins": [
{
"denom": "one",
"amount": 111
}
]
}, {
"name": "bob",
"address": "C471FB670E44D219EE6DF2FC284BE38793ACBCE1",
"coins": [
{
"denom": "two",
"amount": 222
}
]
}, {
"name": "carl",
"address": "1234ABCDD18E8EFE3FFC4B0506BF9BF8E5B0D9E9",
"pub_key": {
"type": "ed25519",
"data": "177C0AC45E86257F0708DC085D592AB22AAEECD1D26381B757F7C96135921858"
},
"coins": [
{
"denom": "three",
"amount": 333
}
]
}, {
"name": "sam",
"pub_key": {
"type": "secp256k1",
"data": "02AA8342F63CCCCE6DDB128525BA048CE0B2993DA3B4308746E1F216361A87651E"
},
"coins": [
{
"denom": "four",
"amount": 444
}
]
}]
}
}

View File

@ -1,3 +0,0 @@
{
"chain_id": "foo_bar_chain"
}

View File

@ -9,8 +9,6 @@ import (
"github.com/tendermint/tendermint/libs/cli" "github.com/tendermint/tendermint/libs/cli"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/pelletier/go-toml" "github.com/pelletier/go-toml"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -32,7 +30,7 @@ func init() {
// ConfigCmd returns a CLI command to interactively create a // ConfigCmd returns a CLI command to interactively create a
// Gaia CLI config file. // Gaia CLI config file.
func ConfigCmd() *cobra.Command { func ConfigCmd(defaultCLIHome string) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "config <key> [value]", Use: "config <key> [value]",
Short: "Create or query a Gaia CLI configuration file", Short: "Create or query a Gaia CLI configuration file",
@ -40,7 +38,7 @@ func ConfigCmd() *cobra.Command {
Args: cobra.RangeArgs(0, 2), Args: cobra.RangeArgs(0, 2),
} }
cmd.Flags().String(cli.HomeFlag, app.DefaultCLIHome, cmd.Flags().String(cli.HomeFlag, defaultCLIHome,
"set client's home directory for configuration") "set client's home directory for configuration")
cmd.Flags().Bool(flagGet, false, cmd.Flags().Bool(flagGet, false,
"print configuration value or its default if unset") "print configuration value or its default if unset")

View File

@ -2,169 +2,79 @@ package context
import ( import (
"fmt" "fmt"
"io"
"github.com/pkg/errors" sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/tendermint/abci/types"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
) )
// TODO: This should get deleted eventually, and perhaps
// ctypes.ResultBroadcastTx be stripped of unused fields, and
// ctypes.ResultBroadcastTxCommit returned for tendermint RPC BroadcastTxSync.
//
// The motivation is that we want a unified type to return, and the better
// option is the one that can hold CheckTx/DeliverTx responses optionally.
func resultBroadcastTxToCommit(res *ctypes.ResultBroadcastTx) *ctypes.ResultBroadcastTxCommit {
return &ctypes.ResultBroadcastTxCommit{
Hash: res.Hash,
// NOTE: other fields are unused for async.
}
}
// BroadcastTx broadcasts a transactions either synchronously or asynchronously // BroadcastTx broadcasts a transactions either synchronously or asynchronously
// based on the context parameters. The result of the broadcast is parsed into // based on the context parameters. The result of the broadcast is parsed into
// an intermediate structure which is logged if the context has a logger // an intermediate structure which is logged if the context has a logger
// defined. // defined.
func (ctx CLIContext) BroadcastTx(txBytes []byte) (*ctypes.ResultBroadcastTxCommit, error) { func (ctx CLIContext) BroadcastTx(txBytes []byte) (res sdk.TxResponse, err error) {
if ctx.Async { if ctx.Async {
res, err := ctx.broadcastTxAsync(txBytes) if res, err = ctx.BroadcastTxAsync(txBytes); err != nil {
if err != nil { return
return nil, err
} }
return
resCommit := resultBroadcastTxToCommit(res)
return resCommit, err
} }
return ctx.broadcastTxCommit(txBytes) if res, err = ctx.BroadcastTxAndAwaitCommit(txBytes); err != nil {
return
}
return
} }
// BroadcastTxAndAwaitCommit broadcasts transaction bytes to a Tendermint node // BroadcastTxAndAwaitCommit broadcasts transaction bytes to a Tendermint node
// and waits for a commit. // and waits for a commit.
func (ctx CLIContext) BroadcastTxAndAwaitCommit(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) { func (ctx CLIContext) BroadcastTxAndAwaitCommit(tx []byte) (sdk.TxResponse, error) {
node, err := ctx.GetNode() node, err := ctx.GetNode()
if err != nil { if err != nil {
return nil, err return sdk.TxResponse{}, err
} }
res, err := node.BroadcastTxCommit(tx) res, err := node.BroadcastTxCommit(tx)
if err != nil { if err != nil {
return res, err return sdk.NewResponseFormatBroadcastTxCommit(res), err
} }
if !res.CheckTx.IsOK() { if !res.CheckTx.IsOK() {
return res, errors.Errorf(res.CheckTx.Log) return sdk.NewResponseFormatBroadcastTxCommit(res), fmt.Errorf(res.CheckTx.Log)
} }
if !res.DeliverTx.IsOK() { if !res.DeliverTx.IsOK() {
return res, errors.Errorf(res.DeliverTx.Log) return sdk.NewResponseFormatBroadcastTxCommit(res), fmt.Errorf(res.DeliverTx.Log)
} }
return res, err return sdk.NewResponseFormatBroadcastTxCommit(res), err
} }
// BroadcastTxSync broadcasts transaction bytes to a Tendermint node // BroadcastTxSync broadcasts transaction bytes to a Tendermint node synchronously.
// synchronously. func (ctx CLIContext) BroadcastTxSync(tx []byte) (sdk.TxResponse, error) {
func (ctx CLIContext) BroadcastTxSync(tx []byte) (*ctypes.ResultBroadcastTx, error) {
node, err := ctx.GetNode() node, err := ctx.GetNode()
if err != nil { if err != nil {
return nil, err return sdk.TxResponse{}, err
} }
res, err := node.BroadcastTxSync(tx) res, err := node.BroadcastTxSync(tx)
if err != nil { if err != nil {
return res, err return sdk.NewResponseFormatBroadcastTx(res), err
} }
return res, err return sdk.NewResponseFormatBroadcastTx(res), err
} }
// BroadcastTxAsync broadcasts transaction bytes to a Tendermint node // BroadcastTxAsync broadcasts transaction bytes to a Tendermint node asynchronously.
// asynchronously. func (ctx CLIContext) BroadcastTxAsync(tx []byte) (sdk.TxResponse, error) {
func (ctx CLIContext) BroadcastTxAsync(tx []byte) (*ctypes.ResultBroadcastTx, error) {
node, err := ctx.GetNode() node, err := ctx.GetNode()
if err != nil { if err != nil {
return nil, err return sdk.TxResponse{}, err
} }
res, err := node.BroadcastTxAsync(tx) res, err := node.BroadcastTxAsync(tx)
if err != nil { if err != nil {
return res, err return sdk.NewResponseFormatBroadcastTx(res), err
} }
return res, err return sdk.NewResponseFormatBroadcastTx(res), err
}
func (ctx CLIContext) broadcastTxAsync(txBytes []byte) (*ctypes.ResultBroadcastTx, error) {
res, err := ctx.BroadcastTxAsync(txBytes)
if err != nil {
return res, err
}
if ctx.Output != nil {
if ctx.OutputFormat == "json" {
type toJSON struct {
TxHash string
}
resJSON := toJSON{res.Hash.String()}
bz, err := ctx.Codec.MarshalJSON(resJSON)
if err != nil {
return res, err
}
ctx.Output.Write(bz)
io.WriteString(ctx.Output, "\n")
} else {
io.WriteString(ctx.Output, fmt.Sprintf("async tx sent (tx hash: %s)\n", res.Hash))
}
}
return res, nil
}
func (ctx CLIContext) broadcastTxCommit(txBytes []byte) (*ctypes.ResultBroadcastTxCommit, error) {
res, err := ctx.BroadcastTxAndAwaitCommit(txBytes)
if err != nil {
return res, err
}
if ctx.OutputFormat == "json" {
// Since JSON is intended for automated scripts, always include response in
// JSON mode.
type toJSON struct {
Height int64
TxHash string
Response abci.ResponseDeliverTx
}
if ctx.Output != nil {
resJSON := toJSON{res.Height, res.Hash.String(), res.DeliverTx}
bz, err := ctx.Codec.MarshalJSON(resJSON)
if err != nil {
return res, err
}
ctx.Output.Write(bz)
io.WriteString(ctx.Output, "\n")
}
return res, nil
}
if ctx.Output != nil {
resStr := fmt.Sprintf("Committed at block %d (tx hash: %s)\n", res.Height, res.Hash.String())
if ctx.PrintResponse {
resStr = fmt.Sprintf("Committed at block %d (tx hash: %s, response: %+v)\n",
res.Height, res.Hash.String(), res.DeliverTx,
)
}
io.WriteString(ctx.Output, resStr)
}
return res, nil
} }

View File

@ -8,7 +8,9 @@ import (
"path/filepath" "path/filepath"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
cryptokeys "github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -19,13 +21,12 @@ import (
tmliteProxy "github.com/tendermint/tendermint/lite/proxy" tmliteProxy "github.com/tendermint/tendermint/lite/proxy"
rpcclient "github.com/tendermint/tendermint/rpc/client" rpcclient "github.com/tendermint/tendermint/rpc/client"
"github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types"
cskeys "github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/types"
) )
var ( var (
verifier tmlite.Verifier verifier tmlite.Verifier
verifierHome string
) )
// CLIContext implements a typical CLI context created in SDK modules for // CLIContext implements a typical CLI context created in SDK modules for
@ -34,6 +35,7 @@ type CLIContext struct {
Codec *codec.Codec Codec *codec.Codec
AccDecoder auth.AccountDecoder AccDecoder auth.AccountDecoder
Client rpcclient.Client Client rpcclient.Client
Keybase cryptokeys.Keybase
Output io.Writer Output io.Writer
OutputFormat string OutputFormat string
Height int64 Height int64
@ -45,10 +47,11 @@ type CLIContext struct {
Async bool Async bool
PrintResponse bool PrintResponse bool
Verifier tmlite.Verifier Verifier tmlite.Verifier
VerifierHome string
Simulate bool Simulate bool
GenerateOnly bool GenerateOnly bool
fromAddress types.AccAddress FromAddress sdk.AccAddress
fromName string FromName string
Indent bool Indent bool
} }
@ -63,11 +66,16 @@ func NewCLIContext() CLIContext {
} }
from := viper.GetString(client.FlagFrom) from := viper.GetString(client.FlagFrom)
fromAddress, fromName := fromFields(from) fromAddress, fromName, err := GetFromFields(from)
if err != nil {
fmt.Printf("failed to get from fields: %v", err)
os.Exit(1)
}
// We need to use a single verifier for all contexts // We need to use a single verifier for all contexts
if verifier == nil { if verifier == nil || verifierHome != viper.GetString(cli.HomeFlag) {
verifier = createVerifier() verifier = createVerifier()
verifierHome = viper.GetString(cli.HomeFlag)
} }
return CLIContext{ return CLIContext{
@ -85,8 +93,8 @@ func NewCLIContext() CLIContext {
Verifier: verifier, Verifier: verifier,
Simulate: viper.GetBool(client.FlagDryRun), Simulate: viper.GetBool(client.FlagDryRun),
GenerateOnly: viper.GetBool(client.FlagGenerateOnly), GenerateOnly: viper.GetBool(client.FlagGenerateOnly),
fromAddress: fromAddress, FromAddress: fromAddress,
fromName: fromName, FromName: fromName,
Indent: viper.GetBool(client.FlagIndentResponse), Indent: viper.GetBool(client.FlagIndentResponse),
} }
} }
@ -137,37 +145,6 @@ func createVerifier() tmlite.Verifier {
return verifier return verifier
} }
func fromFields(from string) (fromAddr types.AccAddress, fromName string) {
if from == "" {
return nil, ""
}
keybase, err := keys.GetKeyBase()
if err != nil {
fmt.Println("no keybase found")
os.Exit(1)
}
var info cskeys.Info
if addr, err := types.AccAddressFromBech32(from); err == nil {
info, err = keybase.GetByAddress(addr)
if err != nil {
fmt.Printf("could not find key %s\n", from)
os.Exit(1)
}
} else {
info, err = keybase.Get(from)
if err != nil {
fmt.Printf("could not find key %s\n", from)
os.Exit(1)
}
}
fromAddr = info.GetAddress()
fromName = info.GetName()
return
}
// WithCodec returns a copy of the context with an updated codec. // WithCodec returns a copy of the context with an updated codec.
func (ctx CLIContext) WithCodec(cdc *codec.Codec) CLIContext { func (ctx CLIContext) WithCodec(cdc *codec.Codec) CLIContext {
ctx.Codec = cdc ctx.Codec = cdc
@ -255,6 +232,19 @@ func (ctx CLIContext) WithSimulation(simulate bool) CLIContext {
return ctx return ctx
} }
// WithFromName returns a copy of the context with an updated from account name.
func (ctx CLIContext) WithFromName(name string) CLIContext {
ctx.FromName = name
return ctx
}
// WithFromAddress returns a copy of the context with an updated from account
// address.
func (ctx CLIContext) WithFromAddress(addr sdk.AccAddress) CLIContext {
ctx.FromAddress = addr
return ctx
}
// PrintOutput prints output while respecting output and indent flags // PrintOutput prints output while respecting output and indent flags
// NOTE: pass in marshalled structs that have been unmarshaled // NOTE: pass in marshalled structs that have been unmarshaled
// because this function will panic on marshaling errors // because this function will panic on marshaling errors
@ -279,3 +269,31 @@ func (ctx CLIContext) PrintOutput(toPrint fmt.Stringer) (err error) {
fmt.Println(string(out)) fmt.Println(string(out))
return return
} }
// GetFromFields returns a from account address and Keybase name given either
// an address or key name.
func GetFromFields(from string) (sdk.AccAddress, string, error) {
if from == "" {
return nil, "", nil
}
keybase, err := keys.NewKeyBaseFromHomeFlag()
if err != nil {
return nil, "", err
}
var info cryptokeys.Info
if addr, err := sdk.AccAddressFromBech32(from); err == nil {
info, err = keybase.GetByAddress(addr)
if err != nil {
return nil, "", err
}
} else {
info, err = keybase.Get(from)
if err != nil {
return nil, "", err
}
}
return info.GetAddress(), info.GetName(), nil
}

View File

@ -1,7 +1,7 @@
package context package context
import ( import (
"github.com/pkg/errors" "fmt"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
) )
@ -9,7 +9,7 @@ import (
// ErrInvalidAccount returns a standardized error reflecting that a given // ErrInvalidAccount returns a standardized error reflecting that a given
// account address does not exist. // account address does not exist.
func ErrInvalidAccount(addr sdk.AccAddress) error { func ErrInvalidAccount(addr sdk.AccAddress) error {
return errors.Errorf(`No account with address %s was found in the state. return fmt.Errorf(`No account with address %s was found in the state.
Are you sure there has been a transaction involving it?`, addr) Are you sure there has been a transaction involving it?`, addr)
} }
@ -17,6 +17,6 @@ Are you sure there has been a transaction involving it?`, addr)
// height can't be verified. The reason is that the base checkpoint of the certifier is // height can't be verified. The reason is that the base checkpoint of the certifier is
// newer than the given height // newer than the given height
func ErrVerifyCommit(height int64) error { func ErrVerifyCommit(height int64) error {
return errors.Errorf(`The height of base truststore in gaia-lite is higher than height %d. return fmt.Errorf(`The height of base truststore in gaia-lite is higher than height %d.
Can't verify blockchain proof at this height. Please set --trust-node to true and try again`, height) Can't verify blockchain proof at this height. Please set --trust-node to true and try again`, height)
} }

View File

@ -18,7 +18,7 @@ import (
rpcclient "github.com/tendermint/tendermint/rpc/client" rpcclient "github.com/tendermint/tendermint/rpc/client"
tmtypes "github.com/tendermint/tendermint/types" tmtypes "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/store" "github.com/cosmos/cosmos-sdk/store/rootmulti"
) )
// GetNode returns an RPC client. If the context's client is not defined, an // GetNode returns an RPC client. If the context's client is not defined, an
@ -82,13 +82,13 @@ func (ctx CLIContext) GetAccount(address []byte) (auth.Account, error) {
} }
// GetFromAddress returns the from address from the context's name. // GetFromAddress returns the from address from the context's name.
func (ctx CLIContext) GetFromAddress() (sdk.AccAddress, error) { func (ctx CLIContext) GetFromAddress() sdk.AccAddress {
return ctx.fromAddress, nil return ctx.FromAddress
} }
// GetFromName returns the key name for the current context. // GetFromName returns the key name for the current context.
func (ctx CLIContext) GetFromName() (string, error) { func (ctx CLIContext) GetFromName() string {
return ctx.fromName, nil return ctx.FromName
} }
// GetAccountNumber returns the next account number for the given account // GetAccountNumber returns the next account number for the given account
@ -116,11 +116,7 @@ func (ctx CLIContext) GetAccountSequence(address []byte) (uint64, error) {
// EnsureAccountExists ensures that an account exists for a given context. An // EnsureAccountExists ensures that an account exists for a given context. An
// error is returned if it does not. // error is returned if it does not.
func (ctx CLIContext) EnsureAccountExists() error { func (ctx CLIContext) EnsureAccountExists() error {
addr, err := ctx.GetFromAddress() addr := ctx.GetFromAddress()
if err != nil {
return err
}
accountBytes, err := ctx.QueryStore(auth.AddressStoreKey(addr), ctx.AccountStore) accountBytes, err := ctx.QueryStore(auth.AddressStoreKey(addr), ctx.AccountStore)
if err != nil { if err != nil {
return err return err
@ -169,7 +165,7 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro
resp := result.Response resp := result.Response
if !resp.IsOK() { if !resp.IsOK() {
return res, errors.Errorf(resp.Log) return res, errors.New(resp.Log)
} }
// data from trusted node or subspace query doesn't need verification // data from trusted node or subspace query doesn't need verification
@ -211,7 +207,7 @@ func (ctx CLIContext) verifyProof(queryPath string, resp abci.ResponseQuery) err
} }
// TODO: Instead of reconstructing, stash on CLIContext field? // TODO: Instead of reconstructing, stash on CLIContext field?
prt := store.DefaultProofRuntime() prt := rootmulti.DefaultProofRuntime()
// TODO: Better convention for path? // TODO: Better convention for path?
storeName, err := parseQueryStorePath(queryPath) storeName, err := parseQueryStorePath(queryPath)
@ -223,6 +219,13 @@ func (ctx CLIContext) verifyProof(queryPath string, resp abci.ResponseQuery) err
kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL) kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL)
kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL) kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL)
if resp.Value == nil {
err = prt.VerifyAbsence(resp.Proof, commit.Header.AppHash, kp.String())
if err != nil {
return errors.Wrap(err, "failed to prove merkle proof")
}
return nil
}
err = prt.VerifyValue(resp.Proof, commit.Header.AppHash, kp.String(), resp.Value) err = prt.VerifyValue(resp.Proof, commit.Header.AppHash, kp.String(), resp.Value)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to prove merkle proof") return errors.Wrap(err, "failed to prove merkle proof")
@ -251,7 +254,7 @@ func isQueryStoreWithProof(path string) bool {
return false return false
case paths[0] != "store": case paths[0] != "store":
return false return false
case store.RequireProof("/" + paths[2]): case rootmulti.RequireProof("/" + paths[2]):
return true return true
} }

9
client/errors.go Normal file
View File

@ -0,0 +1,9 @@
package client
import "errors"
// common errors for CLI and REST clients
var (
ErrInvalidGasAdjustment = errors.New("invalid gas adjustment")
ErrInvalidSigner = errors.New("tx intended signer does not match the given signer")
)

View File

@ -11,9 +11,9 @@ import (
// nolint // nolint
const ( const (
// DefaultGasAdjustment is applied to gas estimates to avoid tx // DefaultGasAdjustment is applied to gas estimates to avoid tx execution
// execution failures due to state changes that might // failures due to state changes that might occur between the tx simulation
// occur between the tx simulation and the actual run. // and the actual run.
DefaultGasAdjustment = 1.0 DefaultGasAdjustment = 1.0
DefaultGasLimit = 200000 DefaultGasLimit = 200000
GasFlagAuto = "auto" GasFlagAuto = "auto"
@ -40,7 +40,7 @@ const (
FlagListenAddr = "laddr" FlagListenAddr = "laddr"
FlagCORS = "cors" FlagCORS = "cors"
FlagMaxOpenConnections = "max-open" FlagMaxOpenConnections = "max-open"
FlagInsecure = "insecure" FlagTLS = "tls"
FlagSSLHosts = "ssl-hosts" FlagSSLHosts = "ssl-hosts"
FlagSSLCertFile = "ssl-certfile" FlagSSLCertFile = "ssl-certfile"
FlagSSLKeyFile = "ssl-keyfile" FlagSSLKeyFile = "ssl-keyfile"
@ -103,21 +103,15 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
// RegisterRestServerFlags registers the flags required for rest server // RegisterRestServerFlags registers the flags required for rest server
func RegisterRestServerFlags(cmd *cobra.Command) *cobra.Command { func RegisterRestServerFlags(cmd *cobra.Command) *cobra.Command {
cmd = GetCommands(cmd)[0]
cmd.Flags().String(FlagListenAddr, "tcp://localhost:1317", "The address for the server to listen on") cmd.Flags().String(FlagListenAddr, "tcp://localhost:1317", "The address for the server to listen on")
cmd.Flags().Bool(FlagInsecure, false, "Do not set up SSL/TLS layer") cmd.Flags().Bool(FlagTLS, false, "Enable SSL/TLS layer")
cmd.Flags().String(FlagSSLHosts, "", "Comma-separated hostnames and IPs to generate a certificate for") cmd.Flags().String(FlagSSLHosts, "", "Comma-separated hostnames and IPs to generate a certificate for")
cmd.Flags().String(FlagSSLCertFile, "", "Path to a SSL certificate file. If not supplied, a self-signed certificate will be generated.") cmd.Flags().String(FlagSSLCertFile, "", "Path to a SSL certificate file. If not supplied, a self-signed certificate will be generated.")
cmd.Flags().String(FlagSSLKeyFile, "", "Path to a key file; ignored if a certificate file is not supplied.") cmd.Flags().String(FlagSSLKeyFile, "", "Path to a key file; ignored if a certificate file is not supplied.")
cmd.Flags().String(FlagCORS, "", "Set the domains that can make CORS requests (* for all)") cmd.Flags().String(FlagCORS, "", "Set the domains that can make CORS requests (* for all)")
cmd.Flags().String(FlagChainID, "", "Chain ID of Tendermint node")
cmd.Flags().String(FlagNode, "tcp://localhost:26657", "Address of the node to connect to")
cmd.Flags().Int(FlagMaxOpenConnections, 1000, "The number of maximum open connections") cmd.Flags().Int(FlagMaxOpenConnections, 1000, "The number of maximum open connections")
cmd.Flags().Bool(FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)")
cmd.Flags().Bool(FlagIndentResponse, false, "Add indent to JSON response")
viper.BindPFlag(FlagTrustNode, cmd.Flags().Lookup(FlagTrustNode))
viper.BindPFlag(FlagChainID, cmd.Flags().Lookup(FlagChainID))
viper.BindPFlag(FlagNode, cmd.Flags().Lookup(FlagNode))
return cmd return cmd
} }

View File

@ -6,18 +6,35 @@ import (
"os" "os"
"strings" "strings"
"errors"
"github.com/bgentry/speakeasy" "github.com/bgentry/speakeasy"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/pkg/errors"
) )
// MinPassLength is the minimum acceptable password length // MinPassLength is the minimum acceptable password length
const MinPassLength = 8 const MinPassLength = 8
var currentStdin *bufio.Reader
func init() {
currentStdin = bufio.NewReader(os.Stdin)
}
// BufferStdin is used to allow reading prompts for stdin // BufferStdin is used to allow reading prompts for stdin
// multiple times, when we read from non-tty // multiple times, when we read from non-tty
func BufferStdin() *bufio.Reader { func BufferStdin() *bufio.Reader {
return bufio.NewReader(os.Stdin) return currentStdin
}
// OverrideStdin allows to temporarily override stdin
func OverrideStdin(newStdin *bufio.Reader) (cleanUp func()) {
prevStdin := currentStdin
currentStdin = newStdin
cleanUp = func() {
currentStdin = prevStdin
}
return cleanUp
} }
// GetPassword will prompt for a password one-time (to sign a tx) // GetPassword will prompt for a password one-time (to sign a tx)
@ -36,18 +53,12 @@ func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) {
if len(pass) < MinPassLength { if len(pass) < MinPassLength {
// Return the given password to the upstream client so it can handle a // Return the given password to the upstream client so it can handle a
// non-STDIN failure gracefully. // non-STDIN failure gracefully.
return pass, errors.Errorf("password must be at least %d characters", MinPassLength) return pass, fmt.Errorf("password must be at least %d characters", MinPassLength)
} }
return pass, nil return pass, nil
} }
// GetSeed will request a seed phrase from stdin and trims off
// leading/trailing spaces
func GetSeed(prompt string, buf *bufio.Reader) (string, error) {
return GetString(prompt, buf)
}
// GetCheckPassword will prompt for a password twice to verify they // GetCheckPassword will prompt for a password twice to verify they
// match (for creating a new password). // match (for creating a new password).
// It enforces the password length. Only parses password once if // It enforces the password length. Only parses password once if

View File

@ -6,18 +6,9 @@ import (
"github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/crypto/keys"
) )
// GetKeyBase initializes a keybase based on the given db.
// The KeyBase manages all activity requiring access to a key.
func GetKeyBase(db dbm.DB) keys.Keybase {
keybase := keys.New(
db,
)
return keybase
}
// MockKeyBase generates an in-memory keybase that will be discarded // MockKeyBase generates an in-memory keybase that will be discarded
// useful for --dry-run to generate a seed phrase without // useful for --dry-run to generate a seed phrase without
// storing the key // storing the key
func MockKeyBase() keys.Keybase { func MockKeyBase() keys.Keybase {
return GetKeyBase(dbm.NewMemDB()) return keys.New(dbm.NewMemDB())
} }

View File

@ -9,26 +9,26 @@ import (
"os" "os"
"sort" "sort"
"github.com/tendermint/tendermint/crypto/multisig" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/crypto/keys"
sdk "github.com/cosmos/cosmos-sdk/types"
"errors"
"github.com/cosmos/go-bip39"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/libs/cli"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/go-bip39"
ccrypto "github.com/cosmos/cosmos-sdk/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd" "github.com/tendermint/tendermint/crypto/multisig"
sdk "github.com/cosmos/cosmos-sdk/types" "github.com/tendermint/tendermint/libs/cli"
) )
const ( const (
flagInteractive = "interactive" flagInteractive = "interactive"
flagBIP44Path = "bip44-path"
flagRecover = "recover" flagRecover = "recover"
flagNoBackup = "no-backup" flagNoBackup = "no-backup"
flagDryRun = "dry-run" flagDryRun = "dry-run"
@ -38,6 +38,11 @@ const (
flagNoSort = "nosort" flagNoSort = "nosort"
) )
const (
maxValidAccountValue = int(0x80000000 - 1)
maxValidIndexalue = int(0x80000000 - 1)
)
func addKeyCommand() *cobra.Command { func addKeyCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "add <name>", Use: "add <name>",
@ -68,7 +73,6 @@ the flag --nosort is set.
cmd.Flags().String(FlagPublicKey, "", "Parse a public key in bech32 format and save it to disk") cmd.Flags().String(FlagPublicKey, "", "Parse a public key in bech32 format and save it to disk")
cmd.Flags().BoolP(flagInteractive, "i", false, "Interactively prompt user for BIP39 passphrase and mnemonic") cmd.Flags().BoolP(flagInteractive, "i", false, "Interactively prompt user for BIP39 passphrase and mnemonic")
cmd.Flags().Bool(client.FlagUseLedger, false, "Store a local reference to a private key on a Ledger device") cmd.Flags().Bool(client.FlagUseLedger, false, "Store a local reference to a private key on a Ledger device")
cmd.Flags().String(flagBIP44Path, "44'/118'/0'/0/0", "BIP44 path from which to derive a private key")
cmd.Flags().Bool(flagRecover, false, "Provide seed phrase to recover existing key instead of creating") 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(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().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
@ -86,7 +90,7 @@ input
output output
- armor encrypted private key (saved to file) - armor encrypted private key (saved to file)
*/ */
func runAddCmd(cmd *cobra.Command, args []string) error { func runAddCmd(_ *cobra.Command, args []string) error {
var kb keys.Keybase var kb keys.Keybase
var err error var err error
var encryptPassword string var encryptPassword string
@ -95,24 +99,25 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
name := args[0] name := args[0]
interactive := viper.GetBool(flagInteractive) interactive := viper.GetBool(flagInteractive)
showMnemonic := !viper.GetBool(flagNoBackup)
if viper.GetBool(flagDryRun) { if viper.GetBool(flagDryRun) {
// we throw this away, so don't enforce args, // we throw this away, so don't enforce args,
// we want to get a new random seed phrase quickly // we want to get a new random seed phrase quickly
kb = client.MockKeyBase() kb = client.MockKeyBase()
encryptPassword = "throwing-this-key-away" encryptPassword = app.DefaultKeyPass
} else { } else {
kb, err = GetKeyBaseWithWritePerm() kb, err = NewKeyBaseFromHomeFlag()
if err != nil { if err != nil {
return err return err
} }
_, err := kb.Get(name) _, err = kb.Get(name)
if err == nil { if err == nil {
// account exists, ask for user confirmation // account exists, ask for user confirmation
if response, err := client.GetConfirmation( if response, err2 := client.GetConfirmation(
fmt.Sprintf("override the existing name %s", name), buf); err != nil || !response { fmt.Sprintf("override the existing name %s", name), buf); err2 != nil || !response {
return err return err2
} }
} }
@ -144,6 +149,7 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
if _, err := kb.CreateOffline(name, pk); err != nil { if _, err := kb.CreateOffline(name, pk); err != nil {
return err return err
} }
fmt.Fprintf(os.Stderr, "Key %q saved to disk.", name) fmt.Fprintf(os.Stderr, "Key %q saved to disk.", name)
return nil return nil
} }
@ -164,51 +170,37 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return err return err
} }
kb.CreateOffline(name, pk) _, err = kb.CreateOffline(name, pk)
if err != nil {
return err
}
return nil return nil
} }
bipFlag := cmd.Flags().Lookup(flagBIP44Path) account := uint32(viper.GetInt(flagAccount))
bip44Params, err := getBIP44ParamsAndPath(bipFlag.Value.String(), bipFlag.Changed || !interactive) index := uint32(viper.GetInt(flagIndex))
if err != nil {
return err
}
// If we're using ledger, only thing we need is the path. So generate key and // If we're using ledger, only thing we need is the path. So generate key and we're done.
// we're done.
if viper.GetBool(client.FlagUseLedger) { if viper.GetBool(client.FlagUseLedger) {
account := uint32(viper.GetInt(flagAccount)) info, err := kb.CreateLedger(name, keys.Secp256k1, account, index)
index := uint32(viper.GetInt(flagIndex))
path := ccrypto.DerivationPath{44, 118, account, 0, index}
info, err := kb.CreateLedger(name, path, keys.Secp256k1)
if err != nil { if err != nil {
return err return err
} }
printCreate(info, "") return printCreate(info, false, "")
return nil
}
// Recover key from seed passphrase
if viper.GetBool(flagRecover) {
seed, err := client.GetSeed(
"Enter your recovery seed phrase:", buf)
if err != nil {
return err
}
info, err := kb.CreateKey(name, seed, encryptPassword)
if err != nil {
return err
}
// print out results without the seed phrase
viper.Set(flagNoBackup, true)
printCreate(info, "")
return nil
} }
// Get bip39 mnemonic
var mnemonic string var mnemonic string
if interactive { var bip39Passphrase string
mnemonic, err = client.GetString("Enter your bip39 mnemonic, or hit enter to generate one.", buf)
if interactive || viper.GetBool(flagRecover) {
bip39Message := "Enter your bip39 mnemonic"
if !viper.GetBool(flagRecover) {
bip39Message = "Enter your bip39 mnemonic, or hit enter to generate one."
}
mnemonic, err = client.GetString(bip39Message, buf)
if err != nil { if err != nil {
return err return err
} }
@ -227,8 +219,12 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
} }
} }
// get bip39 passphrase if !bip39.IsMnemonicValid(mnemonic) {
var bip39Passphrase string fmt.Fprintf(os.Stderr, "Error: Mnemonic is not valid")
return nil
}
// override bip39 passphrase
if interactive { if interactive {
bip39Passphrase, err = client.GetString( bip39Passphrase, err = client.GetString(
"Enter your bip39 passphrase. This is combined with the mnemonic to derive the seed. "+ "Enter your bip39 passphrase. This is combined with the mnemonic to derive the seed. "+
@ -250,173 +246,158 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
} }
} }
info, err := kb.Derive(name, mnemonic, bip39Passphrase, encryptPassword, *bip44Params) info, err := kb.CreateAccount(name, mnemonic, keys.DefaultBIP39Passphrase, encryptPassword, account, index)
if err != nil { if err != nil {
return err return err
} }
printCreate(info, mnemonic)
return nil
}
func getBIP44ParamsAndPath(path string, flagSet bool) (*hd.BIP44Params, error) { // Recover key from seed passphrase
buf := client.BufferStdin() if viper.GetBool(flagRecover) {
bip44Path := path // Hide mnemonic from output
showMnemonic = false
// if it wasn't set in the flag, give it a chance to overide interactively mnemonic = ""
if !flagSet {
var err error
bip44Path, err = client.GetString(fmt.Sprintf("Enter your bip44 path. Default is %s\n", path), buf)
if err != nil {
return nil, err
}
if len(bip44Path) == 0 {
bip44Path = path
}
} }
bip44params, err := hd.NewParamsFromPath(bip44Path) return printCreate(info, showMnemonic, mnemonic)
if err != nil {
return nil, err
}
return bip44params, nil
} }
func printCreate(info keys.Info, seed string) { func printCreate(info keys.Info, showMnemonic bool, mnemonic string) error {
output := viper.Get(cli.OutputFlag) output := viper.Get(cli.OutputFlag)
switch output { switch output {
case "text": case OutputFormatText:
fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr)
printKeyInfo(info, Bech32KeyOutput) printKeyInfo(info, Bech32KeyOutput)
// print seed unless requested not to. // print mnemonic unless requested not to.
if !viper.GetBool(client.FlagUseLedger) && !viper.GetBool(flagNoBackup) { if showMnemonic {
fmt.Fprintln(os.Stderr, "\n**Important** write this seed phrase in a safe place.") fmt.Fprintln(os.Stderr, "\n**Important** write this mnemonic phrase in a safe place.")
fmt.Fprintln(os.Stderr, "It is the only way to recover your account if you ever forget your password.") fmt.Fprintln(os.Stderr, "It is the only way to recover your account if you ever forget your password.")
fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, seed) fmt.Fprintln(os.Stderr, mnemonic)
} }
case "json": case OutputFormatJSON:
out, err := Bech32KeyOutput(info) out, err := Bech32KeyOutput(info)
if err != nil { if err != nil {
panic(err) return err
} }
if !viper.GetBool(flagNoBackup) {
out.Seed = seed if showMnemonic {
out.Mnemonic = mnemonic
} }
var jsonString []byte var jsonString []byte
if viper.GetBool(client.FlagIndentResponse) { if viper.GetBool(client.FlagIndentResponse) {
jsonString, err = cdc.MarshalJSONIndent(out, "", " ") jsonString, err = cdc.MarshalJSONIndent(out, "", " ")
} else { } else {
jsonString, err = cdc.MarshalJSON(out) jsonString, err = cdc.MarshalJSON(out)
} }
if err != nil { if err != nil {
panic(err) // really shouldn't happen... return err
} }
fmt.Fprintln(os.Stderr, string(jsonString)) fmt.Fprintln(os.Stderr, string(jsonString))
default: default:
panic(fmt.Sprintf("I can't speak: %s", output)) return fmt.Errorf("I can't speak: %s", output)
} }
}
// function to just a new seed to display in the UI before actually persisting it in the keybase return nil
func getSeed(algo keys.SigningAlgo) string {
kb := client.MockKeyBase()
pass := "throwing-this-key-away"
name := "inmemorykey"
_, seed, _ := kb.CreateMnemonic(name, keys.English, pass, algo)
return seed
}
func printPrefixed(msg string) {
fmt.Fprintln(os.Stderr, msg)
}
func printStep() {
printPrefixed("-------------------------------------")
} }
///////////////////////////// /////////////////////////////
// REST // REST
// new key request REST body // function to just create a new seed to display in the UI before actually persisting it in the keybase
type NewKeyBody struct { func generateMnemonic(algo keys.SigningAlgo) string {
Name string `json:"name"` kb := client.MockKeyBase()
Password string `json:"password"` pass := app.DefaultKeyPass
Seed string `json:"seed"` name := "inmemorykey"
_, seed, _ := kb.CreateMnemonic(name, keys.English, pass, algo)
return seed
}
// CheckAndWriteErrorResponse will check for errors and return
// a given error message when corresponding
//TODO: Move to utils/rest or similar
func CheckAndWriteErrorResponse(w http.ResponseWriter, httpErr int, err error) bool {
if err != nil {
w.WriteHeader(httpErr)
_, _ = w.Write([]byte(err.Error()))
return true
}
return false
} }
// add new key REST handler // add new key REST handler
func AddNewKeyRequestHandler(indent bool) http.HandlerFunc { func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var kb keys.Keybase var kb keys.Keybase
var m NewKeyBody var m AddNewKey
kb, err := GetKeyBaseWithWritePerm() kb, err := NewKeyBaseFromHomeFlag()
if err != nil { if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return return
} }
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return return
} }
err = json.Unmarshal(body, &m) err = json.Unmarshal(body, &m)
if err != nil { if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return return
} }
// Check parameters
if m.Name == "" { if m.Name == "" {
w.WriteHeader(http.StatusBadRequest) CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingName())
err = errMissingName()
w.Write([]byte(err.Error()))
return return
} }
if m.Password == "" { if m.Password == "" {
w.WriteHeader(http.StatusBadRequest) CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingPassword())
err = errMissingPassword()
w.Write([]byte(err.Error()))
return return
} }
// check if already exists mnemonic := m.Mnemonic
infos, err := kb.List() // if mnemonic is empty, generate one
for _, info := range infos { if mnemonic == "" {
if info.GetName() == m.Name { mnemonic = generateMnemonic(keys.Secp256k1)
w.WriteHeader(http.StatusConflict) }
err = errKeyNameConflict(m.Name) if !bip39.IsMnemonicValid(mnemonic) {
w.Write([]byte(err.Error())) CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidMnemonic())
return }
}
if m.Account < 0 || m.Account > maxValidAccountValue {
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidAccountNumber())
return
}
if m.Index < 0 || m.Index > maxValidIndexalue {
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidIndexNumber())
return
}
_, err = kb.Get(m.Name)
if err == nil {
CheckAndWriteErrorResponse(w, http.StatusConflict, errKeyNameConflict(m.Name))
return
} }
// create account // create account
seed := m.Seed account := uint32(m.Account)
if seed == "" { index := uint32(m.Index)
seed = getSeed(keys.Secp256k1) info, err := kb.CreateAccount(m.Name, mnemonic, keys.DefaultBIP39Passphrase, m.Password, account, index)
} if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
info, err := kb.CreateKey(m.Name, seed, m.Password)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return return
} }
keyOutput, err := Bech32KeyOutput(info) keyOutput, err := Bech32KeyOutput(info)
if err != nil { if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return return
} }
keyOutput.Seed = seed keyOutput.Mnemonic = mnemonic
PostProcessResponse(w, cdc, keyOutput, indent) PostProcessResponse(w, cdc, keyOutput, indent)
} }
@ -426,22 +407,17 @@ func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
func SeedRequestHandler(w http.ResponseWriter, r *http.Request) { func SeedRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
algoType := vars["type"] algoType := vars["type"]
// algo type defaults to secp256k1 // algo type defaults to secp256k1
if algoType == "" { if algoType == "" {
algoType = "secp256k1" algoType = "secp256k1"
} }
algo := keys.SigningAlgo(algoType)
seed := getSeed(algo) algo := keys.SigningAlgo(algoType)
seed := generateMnemonic(algo)
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.Write([]byte(seed)) _, _ = w.Write([]byte(seed))
}
// RecoverKeyBody is recover key request REST body
type RecoverKeyBody struct {
Password string `json:"password"`
Seed string `json:"seed"`
} }
// RecoverRequestHandler performs key recover request // RecoverRequestHandler performs key recover request
@ -449,67 +425,66 @@ func RecoverRequestHandler(indent bool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
name := vars["name"] name := vars["name"]
var m RecoverKeyBody var m RecoverKey
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
err = cdc.UnmarshalJSON(body, &m)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return return
} }
err = cdc.UnmarshalJSON(body, &m)
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
return
}
kb, err := NewKeyBaseFromHomeFlag()
CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err)
if name == "" { if name == "" {
w.WriteHeader(http.StatusBadRequest) CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingName())
err = errMissingName()
w.Write([]byte(err.Error()))
return return
} }
if m.Password == "" { if m.Password == "" {
w.WriteHeader(http.StatusBadRequest) CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingPassword())
err = errMissingPassword()
w.Write([]byte(err.Error()))
return
}
if m.Seed == "" {
w.WriteHeader(http.StatusBadRequest)
err = errMissingSeed()
w.Write([]byte(err.Error()))
return return
} }
kb, err := GetKeyBaseWithWritePerm() mnemonic := m.Mnemonic
if err != nil { if !bip39.IsMnemonicValid(mnemonic) {
w.WriteHeader(http.StatusInternalServerError) CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidMnemonic())
w.Write([]byte(err.Error()))
return
}
// check if already exists
infos, err := kb.List()
for _, info := range infos {
if info.GetName() == name {
w.WriteHeader(http.StatusConflict)
err = errKeyNameConflict(name)
w.Write([]byte(err.Error()))
return
}
} }
info, err := kb.CreateKey(name, m.Seed, m.Password) if m.Mnemonic == "" {
if err != nil { CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingMnemonic())
w.WriteHeader(http.StatusInternalServerError) return
w.Write([]byte(err.Error())) }
if m.Account < 0 || m.Account > maxValidAccountValue {
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidAccountNumber())
return
}
if m.Index < 0 || m.Index > maxValidIndexalue {
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidIndexNumber())
return
}
_, err = kb.Get(name)
if err == nil {
CheckAndWriteErrorResponse(w, http.StatusConflict, errKeyNameConflict(name))
return
}
account := uint32(m.Account)
index := uint32(m.Index)
info, err := kb.CreateAccount(name, mnemonic, keys.DefaultBIP39Passphrase, m.Password, account, index)
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
return return
} }
keyOutput, err := Bech32KeyOutput(info) keyOutput, err := Bech32KeyOutput(info)
if err != nil { if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return return
} }

103
client/keys/add_test.go Normal file
View File

@ -0,0 +1,103 @@
package keys
import (
"bufio"
"net/http"
"strings"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/libs/cli"
"github.com/cosmos/cosmos-sdk/tests"
"github.com/cosmos/cosmos-sdk/client"
"github.com/stretchr/testify/assert"
)
func Test_runAddCmdBasic(t *testing.T) {
cmd := addKeyCommand()
assert.NotNil(t, cmd)
// Missing input (enter password)
err := runAddCmd(cmd, []string{"keyname"})
assert.EqualError(t, err, "EOF")
// Prepare a keybase
kbHome, kbCleanUp := tests.NewTestCaseDir(t)
assert.NotNil(t, kbHome)
defer kbCleanUp()
viper.Set(cli.HomeFlag, kbHome)
/// Test Text
viper.Set(cli.OutputFlag, OutputFormatText)
// Now enter password
cleanUp1 := client.OverrideStdin(bufio.NewReader(strings.NewReader("test1234\ntest1234\n")))
defer cleanUp1()
err = runAddCmd(cmd, []string{"keyname1"})
assert.NoError(t, err)
/// Test Text - Replace? >> FAIL
viper.Set(cli.OutputFlag, OutputFormatText)
// Now enter password
cleanUp2 := client.OverrideStdin(bufio.NewReader(strings.NewReader("test1234\ntest1234\n")))
defer cleanUp2()
err = runAddCmd(cmd, []string{"keyname1"})
assert.Error(t, err)
/// Test Text - Replace? Answer >> PASS
viper.Set(cli.OutputFlag, OutputFormatText)
// Now enter password
cleanUp3 := client.OverrideStdin(bufio.NewReader(strings.NewReader("y\ntest1234\ntest1234\n")))
defer cleanUp3()
err = runAddCmd(cmd, []string{"keyname1"})
assert.NoError(t, err)
// Check JSON
viper.Set(cli.OutputFlag, OutputFormatJSON)
// Now enter password
cleanUp4 := client.OverrideStdin(bufio.NewReader(strings.NewReader("test1234\ntest1234\n")))
defer cleanUp4()
err = runAddCmd(cmd, []string{"keyname2"})
assert.NoError(t, err)
}
type MockResponseWriter struct {
dataHeaderStatus int
dataBody []byte
}
func (MockResponseWriter) Header() http.Header {
panic("Unexpected call!")
}
func (w *MockResponseWriter) Write(data []byte) (int, error) {
w.dataBody = append(w.dataBody, data...)
return len(data), nil
}
func (w *MockResponseWriter) WriteHeader(statusCode int) {
w.dataHeaderStatus = statusCode
}
func TestCheckAndWriteErrorResponse(t *testing.T) {
mockRW := MockResponseWriter{}
mockRW.WriteHeader(100)
assert.Equal(t, 100, mockRW.dataHeaderStatus)
detected := CheckAndWriteErrorResponse(&mockRW, http.StatusBadRequest, errors.New("some ERROR"))
require.True(t, detected)
require.Equal(t, http.StatusBadRequest, mockRW.dataHeaderStatus)
require.Equal(t, "some ERROR", string(mockRW.dataBody[:]))
mockRW = MockResponseWriter{}
detected = CheckAndWriteErrorResponse(&mockRW, http.StatusBadRequest, nil)
require.False(t, detected)
require.Equal(t, 0, mockRW.dataHeaderStatus)
require.Equal(t, "", string(mockRW.dataBody[:]))
}

100
client/keys/codec_test.go Normal file
View File

@ -0,0 +1,100 @@
package keys
import (
"fmt"
"reflect"
"testing"
"github.com/stretchr/testify/require"
)
type testCases struct {
Keys []KeyOutput
Answers []KeyOutput
JSON [][]byte
}
func getTestCases() testCases {
return testCases{
[]KeyOutput{
{"A", "B", "C", "D", "E"},
{"A", "B", "C", "D", ""},
{"", "B", "C", "D", ""},
{"", "", "", "", ""},
},
make([]KeyOutput, 4),
[][]byte{
[]byte(`{"name":"A","type":"B","address":"C","pub_key":"D","mnemonic":"E"}`),
[]byte(`{"name":"A","type":"B","address":"C","pub_key":"D"}`),
[]byte(`{"name":"","type":"B","address":"C","pub_key":"D"}`),
[]byte(`{"name":"","type":"","address":"","pub_key":""}`),
},
}
}
func TestMarshalJSON(t *testing.T) {
type args struct {
o KeyOutput
}
data := getTestCases()
tests := []struct {
name string
args args
want []byte
wantErr bool
}{
{"basic", args{data.Keys[0]}, []byte(data.JSON[0]), false},
{"mnemonic is optional", args{data.Keys[1]}, []byte(data.JSON[1]), false},
// REVIEW: Are the next results expected??
{"empty name", args{data.Keys[2]}, []byte(data.JSON[2]), false},
{"empty object", args{data.Keys[3]}, []byte(data.JSON[3]), false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := MarshalJSON(tt.args.o)
if (err != nil) != tt.wantErr {
t.Errorf("MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
return
}
fmt.Printf("%s\n", got)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("MarshalJSON() = %v, want %v", got, tt.want)
}
})
}
}
func TestUnmarshalJSON(t *testing.T) {
type args struct {
bz []byte
ptr interface{}
}
data := getTestCases()
tests := []struct {
name string
args args
wantErr bool
}{
{"basic", args{data.JSON[0], &data.Answers[0]}, false},
{"mnemonic is optional", args{data.JSON[1], &data.Answers[1]}, false},
// REVIEW: Are the next results expected??
{"empty name", args{data.JSON[2], &data.Answers[2]}, false},
{"empty object", args{data.JSON[3], &data.Answers[3]}, false},
}
for idx, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := UnmarshalJSON(tt.args.bz, tt.args.ptr); (err != nil) != tt.wantErr {
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
}
// Confirm deserialized objects are the same
require.Equal(t, data.Keys[idx], data.Answers[idx])
})
}
}

View File

@ -13,8 +13,8 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
keys "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/crypto/keys"
keyerror "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -49,7 +49,7 @@ gaiacli.
func runDeleteCmd(cmd *cobra.Command, args []string) error { func runDeleteCmd(cmd *cobra.Command, args []string) error {
name := args[0] name := args[0]
kb, err := GetKeyBaseWithWritePerm() kb, err := NewKeyBaseFromHomeFlag()
if err != nil { if err != nil {
return err return err
} }
@ -91,6 +91,17 @@ func runDeleteCmd(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func confirmDeletion(buf *bufio.Reader) error {
answer, err := client.GetConfirmation("Key reference will be deleted. Continue?", buf)
if err != nil {
return err
}
if !answer {
return errors.New("aborted")
}
return nil
}
//////////////////////// ////////////////////////
// REST // REST
@ -110,42 +121,31 @@ func DeleteKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
err := decoder.Decode(&m) err := decoder.Decode(&m)
if err != nil { if err != nil {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} }
kb, err = GetKeyBaseWithWritePerm() kb, err = NewKeyBaseFromHomeFlag()
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} }
err = kb.Delete(name, m.Password, false) err = kb.Delete(name, m.Password, false)
if keyerror.IsErrKeyNotFound(err) { if keyerror.IsErrKeyNotFound(err) {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} else if keyerror.IsErrWrongPassword(err) { } else if keyerror.IsErrWrongPassword(err) {
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} else if err != nil { } else if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} }
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
func confirmDeletion(buf *bufio.Reader) error {
answer, err := client.GetConfirmation("Key reference will be deleted. Continue?", buf)
if err != nil {
return err
}
if !answer {
return errors.New("aborted")
}
return nil
}

102
client/keys/delete_test.go Normal file
View File

@ -0,0 +1,102 @@
package keys
import (
"bufio"
"strings"
"testing"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/tests"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/libs/cli"
)
func Test_runDeleteCmd(t *testing.T) {
deleteKeyCommand := deleteKeyCommand()
yesF, _ := deleteKeyCommand.Flags().GetBool(flagYes)
forceF, _ := deleteKeyCommand.Flags().GetBool(flagForce)
assert.False(t, yesF)
assert.False(t, forceF)
fakeKeyName1 := "runDeleteCmd_Key1"
fakeKeyName2 := "runDeleteCmd_Key2"
// Now add a temporary keybase
kbHome, cleanUp := tests.NewTestCaseDir(t)
defer cleanUp()
viper.Set(cli.HomeFlag, kbHome)
// Now
kb, err := NewKeyBaseFromHomeFlag()
assert.NoError(t, err)
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", 0, 0)
assert.NoError(t, err)
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", 0, 1)
assert.NoError(t, err)
err = runDeleteCmd(deleteKeyCommand, []string{"blah"})
require.Error(t, err)
require.Equal(t, "Key blah not found", err.Error())
// User confirmation missing
err = runDeleteCmd(deleteKeyCommand, []string{fakeKeyName1})
require.Error(t, err)
require.Equal(t, "EOF", err.Error())
{
_, err = kb.Get(fakeKeyName1)
require.NoError(t, err)
// Now there is a confirmation
cleanUp := client.OverrideStdin(bufio.NewReader(strings.NewReader("y\n")))
defer cleanUp()
err = runDeleteCmd(deleteKeyCommand, []string{fakeKeyName1})
require.NoError(t, err)
_, err = kb.Get(fakeKeyName1)
require.Error(t, err) // Key1 is gone
}
viper.Set(flagYes, true)
_, err = kb.Get(fakeKeyName2)
require.NoError(t, err)
err = runDeleteCmd(deleteKeyCommand, []string{fakeKeyName2})
require.NoError(t, err)
_, err = kb.Get(fakeKeyName2)
require.Error(t, err) // Key2 is gone
// TODO: Write another case for !keys.Local
}
func Test_confirmDeletion(t *testing.T) {
type args struct {
buf *bufio.Reader
}
answerYes := bufio.NewReader(strings.NewReader("y\n"))
answerYes2 := bufio.NewReader(strings.NewReader("Y\n"))
answerNo := bufio.NewReader(strings.NewReader("n\n"))
answerInvalid := bufio.NewReader(strings.NewReader("245\n"))
tests := []struct {
name string
args args
wantErr bool
}{
{"Y", args{answerYes}, false},
{"y", args{answerYes2}, false},
{"N", args{answerNo}, true},
{"BAD", args{answerInvalid}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := confirmDeletion(tt.args.buf); (err != nil) != tt.wantErr {
t.Errorf("confirmDeletion() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -3,7 +3,7 @@ package keys
import "fmt" import "fmt"
func errKeyNameConflict(name string) error { func errKeyNameConflict(name string) error {
return fmt.Errorf("acount with name %s already exists", name) return fmt.Errorf("account with name %s already exists", name)
} }
func errMissingName() error { func errMissingName() error {
@ -14,6 +14,18 @@ func errMissingPassword() error {
return fmt.Errorf("you have to specify a password for the locally stored account") return fmt.Errorf("you have to specify a password for the locally stored account")
} }
func errMissingSeed() error { func errMissingMnemonic() error {
return fmt.Errorf("you have to specify seed for key recover") return fmt.Errorf("you have to specify a mnemonic for key recovery")
}
func errInvalidMnemonic() error {
return fmt.Errorf("the mnemonic is invalid")
}
func errInvalidAccountNumber() error {
return fmt.Errorf("the account number is invalid")
}
func errInvalidIndexNumber() error {
return fmt.Errorf("the index number is invalid")
} }

View File

View File

@ -6,19 +6,18 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// CMD func listKeysCmd() *cobra.Command {
return &cobra.Command{
// listKeysCmd represents the list command Use: "list",
var listKeysCmd = &cobra.Command{ Short: "List all keys",
Use: "list", Long: `Return a list of all public keys stored by this key manager
Short: "List all keys",
Long: `Return a list of all public keys stored by this key manager
along with their associated name and address.`, along with their associated name and address.`,
RunE: runListCmd, RunE: runListCmd,
}
} }
func runListCmd(cmd *cobra.Command, args []string) error { func runListCmd(cmd *cobra.Command, args []string) error {
kb, err := GetKeyBase() kb, err := NewKeyBaseFromHomeFlag()
if err != nil { if err != nil {
return err return err
} }
@ -36,16 +35,16 @@ func runListCmd(cmd *cobra.Command, args []string) error {
// query key list REST handler // query key list REST handler
func QueryKeysRequestHandler(indent bool) http.HandlerFunc { func QueryKeysRequestHandler(indent bool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
kb, err := GetKeyBase() kb, err := NewKeyBaseFromHomeFlag()
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} }
infos, err := kb.List() infos, err := kb.List()
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} }
// an empty list will be JSONized as null, but we want to keep the empty list // an empty list will be JSONized as null, but we want to keep the empty list
@ -56,7 +55,7 @@ func QueryKeysRequestHandler(indent bool) http.HandlerFunc {
keysOutput, err := Bech32KeysOutput(infos) keysOutput, err := Bech32KeysOutput(infos)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} }
PostProcessResponse(w, cdc, keysOutput, indent) PostProcessResponse(w, cdc, keysOutput, indent)

55
client/keys/list_test.go Normal file
View File

@ -0,0 +1,55 @@
package keys
import (
"testing"
"github.com/cosmos/cosmos-sdk/tests"
"github.com/stretchr/testify/assert"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/libs/cli"
"github.com/spf13/cobra"
)
func Test_runListCmd(t *testing.T) {
type args struct {
cmd *cobra.Command
args []string
}
cmdBasic := listKeysCmd()
// Prepare some keybases
kbHome1, cleanUp1 := tests.NewTestCaseDir(t)
defer cleanUp1()
// Do nothing, leave home1 empty
kbHome2, cleanUp2 := tests.NewTestCaseDir(t)
defer cleanUp2()
viper.Set(cli.HomeFlag, kbHome2)
kb, err := NewKeyBaseFromHomeFlag()
assert.NoError(t, err)
_, err = kb.CreateAccount("something", tests.TestMnemonic, "", "", 0, 0)
assert.NoError(t, err)
testData := []struct {
name string
kbDir string
args args
wantErr bool
}{
{"invalid keybase", "/dev/null", args{cmdBasic, []string{}}, true},
{"keybase: empty", kbHome1, args{cmdBasic, []string{}}, false},
{"keybase: w/key", kbHome2, args{cmdBasic, []string{}}, false},
}
for _, tt := range testData {
t.Run(tt.name, func(t *testing.T) {
viper.Set(cli.HomeFlag, tt.kbDir)
if err := runListCmd(tt.args.cmd, tt.args.args); (err != nil) != tt.wantErr {
t.Errorf("runListCmd() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -4,11 +4,10 @@ import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
bip39 "github.com/bartekn/go-bip39"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
bip39 "github.com/bartekn/go-bip39"
) )
const ( const (
@ -45,9 +44,7 @@ func runMnemonicCmd(cmd *cobra.Command, args []string) error {
if len(inputEntropy) < 43 { if len(inputEntropy) < 43 {
return fmt.Errorf("256-bits is 43 characters in Base-64, and 100 in Base-6. You entered %v, and probably want more", len(inputEntropy)) return fmt.Errorf("256-bits is 43 characters in Base-64, and 100 in Base-6. You entered %v, and probably want more", len(inputEntropy))
} }
conf, err := client.GetConfirmation( conf, err := client.GetConfirmation(fmt.Sprintf("> Input length: %d", len(inputEntropy)), buf)
fmt.Sprintf("> Input length: %d", len(inputEntropy)),
buf)
if err != nil { if err != nil {
return err return err
} }
@ -58,7 +55,6 @@ func runMnemonicCmd(cmd *cobra.Command, args []string) error {
// hash input entropy to get entropy seed // hash input entropy to get entropy seed
hashedEntropy := sha256.Sum256([]byte(inputEntropy)) hashedEntropy := sha256.Sum256([]byte(inputEntropy))
entropySeed = hashedEntropy[:] entropySeed = hashedEntropy[:]
printStep()
} else { } else {
// read entropy seed straight from crypto.Rand // read entropy seed straight from crypto.Rand
var err error var err error

View File

@ -0,0 +1,59 @@
package keys
import (
"bufio"
"strings"
"testing"
"github.com/cosmos/cosmos-sdk/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_RunMnemonicCmdNormal(t *testing.T) {
cmdBasic := mnemonicKeyCommand()
err := runMnemonicCmd(cmdBasic, []string{})
require.NoError(t, err)
}
func Test_RunMnemonicCmdUser(t *testing.T) {
cmdUser := mnemonicKeyCommand()
err := cmdUser.Flags().Set(flagUserEntropy, "1")
assert.NoError(t, err)
err = runMnemonicCmd(cmdUser, []string{})
require.Error(t, err)
require.Equal(t, "EOF", err.Error())
// Try again
cleanUp := client.OverrideStdin(bufio.NewReader(strings.NewReader("Hi!\n")))
defer cleanUp()
err = runMnemonicCmd(cmdUser, []string{})
require.Error(t, err)
require.Equal(t,
"256-bits is 43 characters in Base-64, and 100 in Base-6. You entered 3, and probably want more",
err.Error())
// Now provide "good" entropy :)
fakeEntropy := strings.Repeat(":)", 40) + "\ny\n" // entropy + accept count
cleanUp2 := client.OverrideStdin(bufio.NewReader(strings.NewReader(fakeEntropy)))
defer cleanUp2()
err = runMnemonicCmd(cmdUser, []string{})
require.NoError(t, err)
// Now provide "good" entropy but no answer
fakeEntropy = strings.Repeat(":)", 40) + "\n" // entropy + accept count
cleanUp3 := client.OverrideStdin(bufio.NewReader(strings.NewReader(fakeEntropy)))
defer cleanUp3()
err = runMnemonicCmd(cmdUser, []string{})
require.Error(t, err)
// Now provide "good" entropy but say no
fakeEntropy = strings.Repeat(":)", 40) + "\nn\n" // entropy + accept count
cleanUp4 := client.OverrideStdin(bufio.NewReader(strings.NewReader(fakeEntropy)))
defer cleanUp4()
err = runMnemonicCmd(cmdUser, []string{})
require.NoError(t, err)
}

View File

@ -22,7 +22,7 @@ func Commands() *cobra.Command {
cmd.AddCommand( cmd.AddCommand(
mnemonicKeyCommand(), mnemonicKeyCommand(),
addKeyCommand(), addKeyCommand(),
listKeysCmd, listKeysCmd(),
showKeysCmd(), showKeysCmd(),
client.LineBreak, client.LineBreak,
deleteKeyCommand(), deleteKeyCommand(),

22
client/keys/root_test.go Normal file
View File

@ -0,0 +1,22 @@
package keys
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/gorilla/mux"
)
func TestCommands(t *testing.T) {
rootCommands := Commands()
assert.NotNil(t, rootCommands)
// Commands are registered
assert.Equal(t, 7, len(rootCommands.Commands()))
}
func TestRegisterRoutes(t *testing.T) {
fakeRouter := mux.Router{}
RegisterRoutes(&fakeRouter, false)
}

View File

@ -8,8 +8,9 @@ import (
"github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/crypto/keys"
"errors"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/tendermint/tendermint/crypto/multisig" "github.com/tendermint/tendermint/crypto/multisig"
@ -92,7 +93,12 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) {
isShowAddr := viper.GetBool(FlagAddress) isShowAddr := viper.GetBool(FlagAddress)
isShowPubKey := viper.GetBool(FlagPublicKey) isShowPubKey := viper.GetBool(FlagPublicKey)
isOutputSet := cmd.Flag(cli.OutputFlag).Changed
isOutputSet := false
tmp := cmd.Flag(cli.OutputFlag)
if tmp != nil {
isOutputSet = tmp.Changed
}
if isShowAddr && isShowPubKey { if isShowAddr && isShowPubKey {
return errors.New("cannot use both --address and --pubkey at once") return errors.New("cannot use both --address and --pubkey at once")
@ -160,25 +166,25 @@ func GetKeyRequestHandler(indent bool) http.HandlerFunc {
bechKeyOut, err := getBechKeyOut(bechPrefix) bechKeyOut, err := getBechKeyOut(bechPrefix)
if err != nil { if err != nil {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} }
info, err := GetKeyInfo(name) info, err := GetKeyInfo(name)
if keyerror.IsErrKeyNotFound(err) { if keyerror.IsErrKeyNotFound(err) {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} else if err != nil { } else if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} }
keyOutput, err := bechKeyOut(info) keyOutput, err := bechKeyOut(info)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} }

145
client/keys/show_test.go Normal file
View File

@ -0,0 +1,145 @@
package keys
import (
"testing"
"github.com/cosmos/cosmos-sdk/tests"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/stretchr/testify/assert"
"github.com/cosmos/cosmos-sdk/crypto/keys"
)
func Test_multiSigKey_Properties(t *testing.T) {
tmpKey1 := secp256k1.GenPrivKeySecp256k1([]byte("mySecret"))
tmp := multiSigKey{
name: "myMultisig",
key: tmpKey1.PubKey(),
}
assert.Equal(t, "myMultisig", tmp.GetName())
assert.Equal(t, keys.TypeLocal, tmp.GetType())
assert.Equal(t, "015ABFFB09DB738A45745A91E8C401423ECE4016", tmp.GetPubKey().Address().String())
assert.Equal(t, "cosmos1q9dtl7cfmdec53t5t2g733qpgglvusqk6xdntl", tmp.GetAddress().String())
}
func Test_showKeysCmd(t *testing.T) {
cmd := showKeysCmd()
assert.NotNil(t, cmd)
assert.Equal(t, "false", cmd.Flag(FlagAddress).DefValue)
assert.Equal(t, "false", cmd.Flag(FlagPublicKey).DefValue)
}
func Test_runShowCmd(t *testing.T) {
cmd := showKeysCmd()
err := runShowCmd(cmd, []string{"invalid"})
assert.EqualError(t, err, "Key invalid not found")
err = runShowCmd(cmd, []string{"invalid1", "invalid2"})
assert.EqualError(t, err, "Key invalid1 not found")
// Prepare a key base
// Now add a temporary keybase
kbHome, cleanUp := tests.NewTestCaseDir(t)
defer cleanUp()
viper.Set(cli.HomeFlag, kbHome)
fakeKeyName1 := "runShowCmd_Key1"
fakeKeyName2 := "runShowCmd_Key2"
kb, err := NewKeyBaseFromHomeFlag()
assert.NoError(t, err)
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", 0, 0)
assert.NoError(t, err)
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", 0, 1)
assert.NoError(t, err)
// Now try single key
err = runShowCmd(cmd, []string{fakeKeyName1})
assert.EqualError(t, err, "invalid Bech32 prefix encoding provided: ")
// Now try single key - set bech to acc
viper.Set(FlagBechPrefix, "acc")
err = runShowCmd(cmd, []string{fakeKeyName1})
assert.NoError(t, err)
// Now try multisig key - set bech to acc
viper.Set(FlagBechPrefix, "acc")
err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2})
assert.EqualError(t, err, "threshold must be a positive integer")
// Now try multisig key - set bech to acc + threshold=2
viper.Set(FlagBechPrefix, "acc")
viper.Set(flagMultiSigThreshold, 2)
err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2})
assert.NoError(t, err)
// TODO: Capture stdout and compare
}
func Test_validateMultisigThreshold(t *testing.T) {
type args struct {
k int
nKeys int
}
tests := []struct {
name string
args args
wantErr bool
}{
{"zeros", args{0, 0}, true},
{"1-0", args{1, 0}, true},
{"1-1", args{1, 1}, false},
{"1-2", args{1, 1}, false},
{"1-2", args{2, 1}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := validateMultisigThreshold(tt.args.k, tt.args.nKeys); (err != nil) != tt.wantErr {
t.Errorf("validateMultisigThreshold() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_getBechKeyOut(t *testing.T) {
type args struct {
bechPrefix string
}
tests := []struct {
name string
args args
want bechKeyOutFn
wantErr bool
}{
{"empty", args{""}, nil, true},
{"wrong", args{"???"}, nil, true},
{"acc", args{"acc"}, Bech32KeyOutput, false},
{"val", args{"val"}, Bech32ValKeyOutput, false},
{"cons", args{"cons"}, Bech32ConsKeyOutput, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := getBechKeyOut(tt.args.bechPrefix)
if (err != nil) != tt.wantErr {
t.Errorf("getBechKeyOut() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr {
assert.NotNil(t, got)
}
// TODO: Still not possible to compare functions
// Maybe in next release: https://github.com/stretchr/testify/issues/182
//if &got != &tt.want {
// t.Errorf("getBechKeyOut() = %v, want %v", got, tt.want)
//}
})
}
}

38
client/keys/types.go Normal file
View File

@ -0,0 +1,38 @@
package keys
// used for outputting keys.Info over REST
type KeyOutput struct {
Name string `json:"name"`
Type string `json:"type"`
Address string `json:"address"`
PubKey string `json:"pub_key"`
Mnemonic string `json:"mnemonic,omitempty"`
}
// AddNewKey request a new key
type AddNewKey struct {
Name string `json:"name"`
Password string `json:"password"`
Mnemonic string `json:"mnemonic"`
Account int `json:"account,string,omitempty"`
Index int `json:"index,string,omitempty"`
}
// RecoverKeyBody recovers a key
type RecoverKey struct {
Password string `json:"password"`
Mnemonic string `json:"mnemonic"`
Account int `json:"account,string,omitempty"`
Index int `json:"index,string,omitempty"`
}
// UpdateKeyReq requests updating a key
type UpdateKeyReq struct {
OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"`
}
// DeleteKeyReq requests deleting a key
type DeleteKeyReq struct {
Password string `json:"password"`
}

View File

@ -8,7 +8,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
keys "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -29,7 +29,7 @@ func runUpdateCmd(cmd *cobra.Command, args []string) error {
name := args[0] name := args[0]
buf := client.BufferStdin() buf := client.BufferStdin()
kb, err := GetKeyBaseWithWritePerm() kb, err := NewKeyBaseFromHomeFlag()
if err != nil { if err != nil {
return err return err
} }
@ -73,14 +73,14 @@ func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
err := decoder.Decode(&m) err := decoder.Decode(&m)
if err != nil { if err != nil {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} }
kb, err = GetKeyBaseWithWritePerm() kb, err = NewKeyBaseFromHomeFlag()
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} }
@ -89,15 +89,15 @@ func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
err = kb.Update(name, m.OldPassword, getNewpass) err = kb.Update(name, m.OldPassword, getNewpass)
if keyerror.IsErrKeyNotFound(err) { if keyerror.IsErrKeyNotFound(err) {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} else if keyerror.IsErrWrongPassword(err) { } else if keyerror.IsErrWrongPassword(err) {
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} else if err != nil { } else if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} }

View File

@ -0,0 +1,63 @@
package keys
import (
"bufio"
"strings"
"testing"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/tests"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/libs/cli"
"github.com/stretchr/testify/assert"
)
func Test_updateKeyCommand(t *testing.T) {
cmd := updateKeyCommand()
assert.NotNil(t, cmd)
// No flags or defaults to validate
}
func Test_runUpdateCmd(t *testing.T) {
fakeKeyName1 := "runUpdateCmd_Key1"
fakeKeyName2 := "runUpdateCmd_Key2"
cmd := updateKeyCommand()
// fails because it requests a password
err := runUpdateCmd(cmd, []string{fakeKeyName1})
assert.EqualError(t, err, "EOF")
cleanUp := client.OverrideStdin(bufio.NewReader(strings.NewReader("pass1234\n")))
defer cleanUp()
// try again
err = runUpdateCmd(cmd, []string{fakeKeyName1})
assert.EqualError(t, err, "Key runUpdateCmd_Key1 not found")
// Prepare a key base
// Now add a temporary keybase
kbHome, cleanUp1 := tests.NewTestCaseDir(t)
defer cleanUp1()
viper.Set(cli.HomeFlag, kbHome)
kb, err := NewKeyBaseFromHomeFlag()
assert.NoError(t, err)
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", 0, 0)
assert.NoError(t, err)
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", 0, 1)
assert.NoError(t, err)
// Try again now that we have keys
cleanUp2 := client.OverrideStdin(bufio.NewReader(strings.NewReader("pass1234\nNew1234\nNew1234")))
defer cleanUp2()
// Incorrect key type
err = runUpdateCmd(cmd, []string{fakeKeyName1})
assert.EqualError(t, err, "locally stored key required. Received: keys.offlineInfo")
// TODO: Check for other type types?
}

View File

@ -6,9 +6,7 @@ import (
"path/filepath" "path/filepath"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/syndtr/goleveldb/leveldb/opt"
"github.com/tendermint/tendermint/libs/cli" "github.com/tendermint/tendermint/libs/cli"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
@ -17,17 +15,18 @@ import (
) )
// KeyDBName is the directory under root where we store the keys // KeyDBName is the directory under root where we store the keys
const KeyDBName = "keys" const (
KeyDBName = "keys"
// keybase is used to make GetKeyBase a singleton OutputFormatText = "text"
var keybase keys.Keybase OutputFormatJSON = "json"
)
type bechKeyOutFn func(keyInfo keys.Info) (KeyOutput, error) type bechKeyOutFn func(keyInfo keys.Info) (KeyOutput, error)
// GetKeyInfo returns key info for a given name. An error is returned if the // GetKeyInfo returns key info for a given name. An error is returned if the
// keybase cannot be retrieved or getting the info fails. // keybase cannot be retrieved or getting the info fails.
func GetKeyInfo(name string) (keys.Info, error) { func GetKeyInfo(name string) (keys.Info, error) {
keybase, err := GetKeyBase() keybase, err := NewKeyBaseFromHomeFlag()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -73,58 +72,22 @@ func ReadPassphraseFromStdin(name string) (string, error) {
return passphrase, nil return passphrase, nil
} }
// TODO make keybase take a database not load from the directory // NewKeyBaseFromHomeFlag initializes a Keybase based on the configuration.
func NewKeyBaseFromHomeFlag() (keys.Keybase, error) {
// GetKeyBase initializes a read-only KeyBase based on the configuration.
func GetKeyBase() (keys.Keybase, error) {
rootDir := viper.GetString(cli.HomeFlag) rootDir := viper.GetString(cli.HomeFlag)
return GetKeyBaseFromDir(rootDir) return NewKeyBaseFromDir(rootDir)
} }
// GetKeyBaseWithWritePerm initialize a keybase based on the configuration with write permissions. // NewKeyBaseFromDir initializes a keybase at a particular dir.
func GetKeyBaseWithWritePerm() (keys.Keybase, error) { func NewKeyBaseFromDir(rootDir string) (keys.Keybase, error) {
rootDir := viper.GetString(cli.HomeFlag) return getLazyKeyBaseFromDir(rootDir)
return GetKeyBaseFromDirWithWritePerm(rootDir)
} }
// GetKeyBaseFromDirWithWritePerm initializes a keybase at a particular dir with write permissions. // NewInMemoryKeyBase returns a storage-less keybase.
func GetKeyBaseFromDirWithWritePerm(rootDir string) (keys.Keybase, error) { func NewInMemoryKeyBase() keys.Keybase { return keys.NewInMemory() }
return getKeyBaseFromDirWithOpts(rootDir, nil)
}
// GetKeyBaseFromDir initializes a read-only keybase at a particular dir. func getLazyKeyBaseFromDir(rootDir string) (keys.Keybase, error) {
func GetKeyBaseFromDir(rootDir string) (keys.Keybase, error) { return keys.NewLazyKeybase(KeyDBName, filepath.Join(rootDir, "keys")), nil
// Disabled because of the inability to create a new keys database directory
// in the instance of when ReadOnly is set to true.
//
// ref: syndtr/goleveldb#240
// return getKeyBaseFromDirWithOpts(rootDir, &opt.Options{ReadOnly: true})
return getKeyBaseFromDirWithOpts(rootDir, nil)
}
func getKeyBaseFromDirWithOpts(rootDir string, o *opt.Options) (keys.Keybase, error) {
if keybase == nil {
db, err := dbm.NewGoLevelDBWithOpts(KeyDBName, filepath.Join(rootDir, "keys"), o)
if err != nil {
return nil, err
}
keybase = client.GetKeyBase(db)
}
return keybase, nil
}
// used to set the keybase manually in test
func SetKeyBase(kb keys.Keybase) {
keybase = kb
}
// used for outputting keys.Info over REST
type KeyOutput struct {
Name string `json:"name"`
Type string `json:"type"`
Address string `json:"address"`
PubKey string `json:"pub_key"`
Seed string `json:"seed,omitempty"`
} }
// create a list of KeyOutput in bech32 format // create a list of KeyOutput in bech32 format
@ -198,7 +161,7 @@ func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) {
} }
switch viper.Get(cli.OutputFlag) { switch viper.Get(cli.OutputFlag) {
case "text": case OutputFormatText:
fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n") fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n")
printKeyOutput(ko) printKeyOutput(ko)
case "json": case "json":
@ -217,12 +180,12 @@ func printInfos(infos []keys.Info) {
panic(err) panic(err)
} }
switch viper.Get(cli.OutputFlag) { switch viper.Get(cli.OutputFlag) {
case "text": case OutputFormatText:
fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n") fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n")
for _, ko := range kos { for _, ko := range kos {
printKeyOutput(ko) printKeyOutput(ko)
} }
case "json": case OutputFormatJSON:
out, err := MarshalJSON(kos) out, err := MarshalJSON(kos)
if err != nil { if err != nil {
panic(err) panic(err)
@ -266,12 +229,12 @@ func PostProcessResponse(w http.ResponseWriter, cdc *codec.Codec, response inter
} }
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error())) _, _ = w.Write([]byte(err.Error()))
return return
} }
case []byte: case []byte:
output = response.([]byte) output = response.([]byte)
} }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.Write(output) _, _ = w.Write(output)
} }

View File

@ -1,41 +0,0 @@
package keys
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/crypto/keys"
)
func TestGetKeyBaseLocks(t *testing.T) {
dir, err := ioutil.TempDir("", "cosmos-sdk-keys")
require.Nil(t, err)
defer os.RemoveAll(dir)
// Acquire db
kb, err := GetKeyBaseFromDirWithWritePerm(dir)
require.Nil(t, err)
_, _, err = kb.CreateMnemonic("foo", keys.English, "12345678", keys.Secp256k1)
require.Nil(t, err)
// Reset global variable
keybase = nil
// Try to acquire another keybase from the same storage
_, err = GetKeyBaseFromDirWithWritePerm(dir)
require.NotNil(t, err)
_, err = GetKeyBaseFromDirWithWritePerm(dir)
require.NotNil(t, err)
// Close the db and try to acquire the lock
kb.CloseDB()
kb, err = GetKeyBaseFromDirWithWritePerm(dir)
require.Nil(t, err)
// Try to acquire another read-only keybase from the same storage
_, err = GetKeyBaseFromDir(dir)
require.Nil(t, err)
kb.CloseDB()
}

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,6 @@ import (
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
keybase "github.com/cosmos/cosmos-sdk/crypto/keys" keybase "github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/server"
@ -52,25 +51,9 @@ func NewRestServer(cdc *codec.Codec) *RestServer {
} }
} }
func (rs *RestServer) setKeybase(kb keybase.Keybase) {
// If a keybase is passed in, set it and return
if kb != nil {
rs.KeyBase = kb
return
}
// Otherwise get the keybase and set it
kb, err := keys.GetKeyBase() //XXX
if err != nil {
fmt.Printf("Failed to open Keybase: %s, exiting...", err)
os.Exit(1)
}
rs.KeyBase = kb
}
// Start starts the rest server // Start starts the rest server
func (rs *RestServer) Start(listenAddr string, sslHosts string, func (rs *RestServer) Start(listenAddr string, sslHosts string,
certFile string, keyFile string, maxOpen int, insecure bool) (err error) { certFile string, keyFile string, maxOpen int, secure bool) (err error) {
server.TrapSignal(func() { server.TrapSignal(func() {
err := rs.listener.Close() err := rs.listener.Close()
@ -84,10 +67,11 @@ func (rs *RestServer) Start(listenAddr string, sslHosts string,
if err != nil { if err != nil {
return return
} }
rs.log.Info("Starting Gaia Lite REST service...") rs.log.Info(fmt.Sprintf("Starting Gaia Lite REST service (chain-id: %q)...",
viper.GetString(client.FlagChainID)))
// launch rest-server in insecure mode // launch rest-server in insecure mode
if insecure { if !secure {
return rpcserver.StartHTTPServer(rs.listener, rs.Mux, rs.log) return rpcserver.StartHTTPServer(rs.listener, rs.Mux, rs.log)
} }
@ -135,7 +119,6 @@ func ServeCommand(cdc *codec.Codec, registerRoutesFn func(*RestServer)) *cobra.C
RunE: func(cmd *cobra.Command, args []string) (err error) { RunE: func(cmd *cobra.Command, args []string) (err error) {
rs := NewRestServer(cdc) rs := NewRestServer(cdc)
rs.setKeybase(nil)
registerRoutesFn(rs) registerRoutesFn(rs)
// Start the rest server and return error if one exists // Start the rest server and return error if one exists
@ -145,15 +128,13 @@ func ServeCommand(cdc *codec.Codec, registerRoutesFn func(*RestServer)) *cobra.C
viper.GetString(client.FlagSSLCertFile), viper.GetString(client.FlagSSLCertFile),
viper.GetString(client.FlagSSLKeyFile), viper.GetString(client.FlagSSLKeyFile),
viper.GetInt(client.FlagMaxOpenConnections), viper.GetInt(client.FlagMaxOpenConnections),
viper.GetBool(client.FlagInsecure)) viper.GetBool(client.FlagTLS))
return err return err
}, },
} }
client.RegisterRestServerFlags(cmd) return client.RegisterRestServerFlags(cmd)
return cmd
} }
func (rs *RestServer) registerSwaggerUI() { func (rs *RestServer) registerSwaggerUI() {

File diff suppressed because it is too large Load Diff

View File

@ -15,20 +15,14 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/tendermint/tendermint/crypto/secp256k1" "github.com/spf13/viper"
ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/stretchr/testify/require"
cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/slashing"
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/rest"
"github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/client/utils"
gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app" gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
crkeys "github.com/cosmos/cosmos-sdk/crypto/keys" crkeys "github.com/cosmos/cosmos-sdk/crypto/keys"
@ -36,16 +30,24 @@ import (
"github.com/cosmos/cosmos-sdk/tests" "github.com/cosmos/cosmos-sdk/tests"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
txbuilder "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
bankrest "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
distrrest "github.com/cosmos/cosmos-sdk/x/distribution/client/rest"
"github.com/cosmos/cosmos-sdk/x/gov"
govrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils" gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
"github.com/cosmos/cosmos-sdk/x/slashing"
slashingrest "github.com/cosmos/cosmos-sdk/x/slashing/client/rest"
"github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/staking"
stakingrest "github.com/cosmos/cosmos-sdk/x/staking/client/rest"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
tmcfg "github.com/tendermint/tendermint/config" tmcfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/tendermint/tendermint/libs/cli" "github.com/tendermint/tendermint/libs/cli"
dbm "github.com/tendermint/tendermint/libs/db" dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
@ -53,16 +55,9 @@ import (
"github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/p2p"
pvm "github.com/tendermint/tendermint/privval" pvm "github.com/tendermint/tendermint/privval"
"github.com/tendermint/tendermint/proxy" "github.com/tendermint/tendermint/proxy"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
tmrpc "github.com/tendermint/tendermint/rpc/lib/server" tmrpc "github.com/tendermint/tendermint/rpc/lib/server"
tmtypes "github.com/tendermint/tendermint/types" tmtypes "github.com/tendermint/tendermint/types"
txbuilder "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
authRest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
bankRest "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
govRest "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
slashingRest "github.com/cosmos/cosmos-sdk/x/slashing/client/rest"
stakingRest "github.com/cosmos/cosmos-sdk/x/staking/client/rest"
) )
// makePathname creates a unique pathname for each test. It will panic if it // makePathname creates a unique pathname for each test. It will panic if it
@ -104,30 +99,6 @@ func GetConfig() *tmcfg.Config {
return config return config
} }
// GetKeyBase returns the LCD test keybase. It also requires that a directory
// could be made and a keybase could be fetched.
//
// NOTE: memDB cannot be used because the request is expecting to interact with
// the default location.
func GetKeyBase(t *testing.T) crkeys.Keybase {
dir, err := ioutil.TempDir("", "lcd_test")
require.NoError(t, err)
viper.Set(cli.HomeFlag, dir)
keybase, err := keys.GetKeyBaseWithWritePerm()
require.NoError(t, err)
return keybase
}
// GetTestKeyBase fetches the current testing keybase
func GetTestKeyBase(t *testing.T) crkeys.Keybase {
keybase, err := keys.GetKeyBaseWithWritePerm()
require.NoError(t, err)
return keybase
}
// CreateAddr adds an address to the key store and returns an address and seed. // CreateAddr adds an address to the key store and returns an address and seed.
// It also requires that the key could be created. // It also requires that the key could be created.
func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (sdk.AccAddress, string) { func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (sdk.AccAddress, string) {
@ -143,14 +114,6 @@ func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (sdk.Acc
return sdk.AccAddress(info.GetPubKey().Address()), seed return sdk.AccAddress(info.GetPubKey().Address()), seed
} }
// Type that combines an Address with the pnemonic of the private key to that address
type AddrSeed struct {
Address sdk.AccAddress
Seed string
Name string
Password string
}
// CreateAddr adds multiple address to the key store and returns the addresses and associated seeds in lexographical order by address. // CreateAddr adds multiple address to the key store and returns the addresses and associated seeds in lexographical order by address.
// It also requires that the keys could be created. // It also requires that the keys could be created.
func CreateAddrs(t *testing.T, kb crkeys.Keybase, numAddrs int) (addrs []sdk.AccAddress, seeds, names, passwords []string) { func CreateAddrs(t *testing.T, kb crkeys.Keybase, numAddrs int) (addrs []sdk.AccAddress, seeds, names, passwords []string) {
@ -167,7 +130,7 @@ func CreateAddrs(t *testing.T, kb crkeys.Keybase, numAddrs int) (addrs []sdk.Acc
password := "1234567890" password := "1234567890"
info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1) info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1)
require.NoError(t, err) require.NoError(t, err)
addrSeeds = append(addrSeeds, AddrSeed{Address: sdk.AccAddress(info.GetPubKey().Address()), Seed: seed, Name: name, Password: password}) addrSeeds = append(addrSeeds, rest.AddrSeed{Address: sdk.AccAddress(info.GetPubKey().Address()), Seed: seed, Name: name, Password: password})
} }
sort.Sort(addrSeeds) sort.Sort(addrSeeds)
@ -182,14 +145,14 @@ func CreateAddrs(t *testing.T, kb crkeys.Keybase, numAddrs int) (addrs []sdk.Acc
return addrs, seeds, names, passwords return addrs, seeds, names, passwords
} }
// implement `Interface` in sort package. // AddrSeedSlice implements `Interface` in sort package.
type AddrSeedSlice []AddrSeed type AddrSeedSlice []rest.AddrSeed
func (b AddrSeedSlice) Len() int { func (b AddrSeedSlice) Len() int {
return len(b) return len(b)
} }
// Sorts lexographically by Address // Less sorts lexicographically by Address
func (b AddrSeedSlice) Less(i, j int) bool { func (b AddrSeedSlice) Less(i, j int) bool {
// bytes package already implements Comparable for []byte. // bytes package already implements Comparable for []byte.
switch bytes.Compare(b[i].Address.Bytes(), b[j].Address.Bytes()) { switch bytes.Compare(b[i].Address.Bytes(), b[j].Address.Bytes()) {
@ -206,14 +169,27 @@ func (b AddrSeedSlice) Swap(i, j int) {
b[j], b[i] = b[i], b[j] b[j], b[i] = b[i], b[j]
} }
// InitClientHome initialises client home dir.
func InitClientHome(t *testing.T, dir string) string {
var err error
if dir == "" {
dir, err = ioutil.TempDir("", "lcd_test")
require.NoError(t, err)
}
// TODO: this should be set in NewRestServer
// and pass down the CLIContext to achieve
// parallelism.
viper.Set(cli.HomeFlag, dir)
return dir
}
// TODO: Make InitializeTestLCD safe to call in multiple tests at the same time // TODO: Make InitializeTestLCD safe to call in multiple tests at the same time
// InitializeTestLCD starts Tendermint and the LCD in process, listening on // InitializeTestLCD starts Tendermint and the LCD in process, listening on
// their respective sockets where nValidators is the total number of validators // their respective sockets where nValidators is the total number of validators
// and initAddrs are the accounts to initialize with some steak tokens. It // and initAddrs are the accounts to initialize with some steak tokens. It
// returns a cleanup function, a set of validator public keys, and a port. // returns a cleanup function, a set of validator public keys, and a port.
func InitializeTestLCD( func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress, minting bool) (
t *testing.T, nValidators int, initAddrs []sdk.AccAddress, cleanup func(), valConsPubKeys []crypto.PubKey, valOperAddrs []sdk.ValAddress, port string) {
) (cleanup func(), valConsPubKeys []crypto.PubKey, valOperAddrs []sdk.ValAddress, port string) {
if nValidators < 1 { if nValidators < 1 {
panic("InitializeTestLCD must use at least one validator") panic("InitializeTestLCD must use at least one validator")
@ -248,17 +224,21 @@ func InitializeTestLCD(
operPrivKey := secp256k1.GenPrivKey() operPrivKey := secp256k1.GenPrivKey()
operAddr := operPrivKey.PubKey().Address() operAddr := operPrivKey.PubKey().Address()
pubKey := privVal.GetPubKey() pubKey := privVal.GetPubKey()
delegation := 100
power := int64(100)
if i > 0 { if i > 0 {
pubKey = ed25519.GenPrivKey().PubKey() pubKey = ed25519.GenPrivKey().PubKey()
delegation = 1 power = 1
} }
startTokens := staking.TokensFromTendermintPower(power)
msg := staking.NewMsgCreateValidator( msg := staking.NewMsgCreateValidator(
sdk.ValAddress(operAddr), sdk.ValAddress(operAddr),
pubKey, pubKey,
sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(int64(delegation))), sdk.NewCoin(staking.DefaultBondDenom, startTokens),
staking.Description{Moniker: fmt.Sprintf("validator-%d", i+1)}, staking.NewDescription(fmt.Sprintf("validator-%d", i+1), "", "", ""),
staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
sdk.OneInt(),
) )
stdSignMsg := txbuilder.StdSignMsg{ stdSignMsg := txbuilder.StdSignMsg{
ChainID: genDoc.ChainID, ChainID: genDoc.ChainID,
@ -269,12 +249,14 @@ func InitializeTestLCD(
tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{{Signature: sig, PubKey: operPrivKey.PubKey()}}, "") tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{{Signature: sig, PubKey: operPrivKey.PubKey()}}, "")
txBytes, err := cdc.MarshalJSON(tx) txBytes, err := cdc.MarshalJSON(tx)
require.Nil(t, err) require.Nil(t, err)
genTxs = append(genTxs, txBytes) genTxs = append(genTxs, txBytes)
valConsPubKeys = append(valConsPubKeys, pubKey) valConsPubKeys = append(valConsPubKeys, pubKey)
valOperAddrs = append(valOperAddrs, sdk.ValAddress(operAddr)) valOperAddrs = append(valOperAddrs, sdk.ValAddress(operAddr))
accAuth := auth.NewBaseAccountWithAddress(sdk.AccAddress(operAddr)) accAuth := auth.NewBaseAccountWithAddress(sdk.AccAddress(operAddr))
accAuth.Coins = sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 150)} accTokens := staking.TokensFromTendermintPower(150)
accAuth.Coins = sdk.Coins{sdk.NewCoin(staking.DefaultBondDenom, accTokens)}
accs = append(accs, gapp.NewGenesisAccount(&accAuth)) accs = append(accs, gapp.NewGenesisAccount(&accAuth))
} }
@ -288,10 +270,34 @@ func InitializeTestLCD(
// add some tokens to init accounts // add some tokens to init accounts
for _, addr := range initAddrs { for _, addr := range initAddrs {
accAuth := auth.NewBaseAccountWithAddress(addr) accAuth := auth.NewBaseAccountWithAddress(addr)
accAuth.Coins = sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 100)} accTokens := staking.TokensFromTendermintPower(100)
accAuth.Coins = sdk.Coins{sdk.NewCoin(staking.DefaultBondDenom, accTokens)}
acc := gapp.NewGenesisAccount(&accAuth) acc := gapp.NewGenesisAccount(&accAuth)
genesisState.Accounts = append(genesisState.Accounts, acc) genesisState.Accounts = append(genesisState.Accounts, acc)
genesisState.StakingData.Pool.NotBondedTokens = genesisState.StakingData.Pool.NotBondedTokens.Add(sdk.NewInt(100)) genesisState.StakingData.Pool.NotBondedTokens = genesisState.StakingData.Pool.NotBondedTokens.Add(accTokens)
}
inflationMin := sdk.ZeroDec()
if minting {
inflationMin = sdk.MustNewDecFromStr("10000.0")
genesisState.MintData.Params.InflationMax = sdk.MustNewDecFromStr("15000.0")
} else {
genesisState.MintData.Params.InflationMax = inflationMin
}
genesisState.MintData.Minter.Inflation = inflationMin
genesisState.MintData.Params.InflationMin = inflationMin
// double check inflation is set according to the minting boolean flag
if minting {
require.Equal(t, sdk.MustNewDecFromStr("15000.0"),
genesisState.MintData.Params.InflationMax)
require.Equal(t, sdk.MustNewDecFromStr("10000.0"), genesisState.MintData.Minter.Inflation)
require.Equal(t, sdk.MustNewDecFromStr("10000.0"),
genesisState.MintData.Params.InflationMin)
} else {
require.Equal(t, sdk.ZeroDec(), genesisState.MintData.Params.InflationMax)
require.Equal(t, sdk.ZeroDec(), genesisState.MintData.Minter.Inflation)
require.Equal(t, sdk.ZeroDec(), genesisState.MintData.Params.InflationMin)
} }
appState, err := codec.MarshalJSONIndent(cdc, genesisState) appState, err := codec.MarshalJSONIndent(cdc, genesisState)
@ -306,9 +312,6 @@ func InitializeTestLCD(
viper.Set(client.FlagChainID, genDoc.ChainID) viper.Set(client.FlagChainID, genDoc.ChainID)
// TODO Set to false once the upstream Tendermint proof verification issue is fixed. // TODO Set to false once the upstream Tendermint proof verification issue is fixed.
viper.Set(client.FlagTrustNode, true) viper.Set(client.FlagTrustNode, true)
dir, err := ioutil.TempDir("", "lcd_test")
require.NoError(t, err)
viper.Set(cli.HomeFlag, dir)
node, err := startTM(config, logger, genDoc, privVal, app) node, err := startTM(config, logger, genDoc, privVal, app)
require.NoError(t, err) require.NoError(t, err)
@ -339,6 +342,7 @@ func startTM(
tmcfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc, tmcfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc,
privVal tmtypes.PrivValidator, app abci.Application, privVal tmtypes.PrivValidator, app abci.Application,
) (*nm.Node, error) { ) (*nm.Node, error) {
genDocProvider := func() (*tmtypes.GenesisDoc, error) { return genDoc, nil } genDocProvider := func() (*tmtypes.GenesisDoc, error) { return genDoc, nil }
dbProvider := func(*nm.DBContext) (dbm.DB, error) { return dbm.NewMemDB(), nil } dbProvider := func(*nm.DBContext) (dbm.DB, error) { return dbm.NewMemDB(), nil }
nodeKey, err := p2p.LoadOrGenNodeKey(tmcfg.NodeKeyFile()) nodeKey, err := p2p.LoadOrGenNodeKey(tmcfg.NodeKeyFile())
@ -373,7 +377,6 @@ func startTM(
// startLCD starts the LCD. // startLCD starts the LCD.
func startLCD(logger log.Logger, listenAddr string, cdc *codec.Codec, t *testing.T) (net.Listener, error) { func startLCD(logger log.Logger, listenAddr string, cdc *codec.Codec, t *testing.T) (net.Listener, error) {
rs := NewRestServer(cdc) rs := NewRestServer(cdc)
rs.setKeybase(GetTestKeyBase(t))
registerRoutes(rs) registerRoutes(rs)
listener, err := tmrpc.Listen(listenAddr, tmrpc.Config{}) listener, err := tmrpc.Listen(listenAddr, tmrpc.Config{})
if err != nil { if err != nil {
@ -388,11 +391,12 @@ func registerRoutes(rs *RestServer) {
keys.RegisterRoutes(rs.Mux, rs.CliCtx.Indent) keys.RegisterRoutes(rs.Mux, rs.CliCtx.Indent)
rpc.RegisterRoutes(rs.CliCtx, rs.Mux) rpc.RegisterRoutes(rs.CliCtx, rs.Mux)
tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
authRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, auth.StoreKey) authrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, auth.StoreKey)
bankRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) bankrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
stakingRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) distrrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, distr.StoreKey)
slashingRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) stakingrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
govRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) slashingrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
govrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
} }
// Request makes a test LCD test request. It returns a response object and a // Request makes a test LCD test request. It returns a response object and a
@ -498,8 +502,8 @@ func getValidatorSets(t *testing.T, port string, height int, expectFail bool) rp
} }
// GET /txs/{hash} get tx by hash // GET /txs/{hash} get tx by hash
func getTransaction(t *testing.T, port string, hash string) tx.Info { func getTransaction(t *testing.T, port string, hash string) sdk.TxResponse {
var tx tx.Info var tx sdk.TxResponse
res, body := Request(t, port, "GET", fmt.Sprintf("/txs/%s", hash), nil) res, body := Request(t, port, "GET", fmt.Sprintf("/txs/%s", hash), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
@ -511,8 +515,8 @@ func getTransaction(t *testing.T, port string, hash string) tx.Info {
// POST /txs broadcast txs // POST /txs broadcast txs
// GET /txs search transactions // GET /txs search transactions
func getTransactions(t *testing.T, port string, tags ...string) []tx.Info { func getTransactions(t *testing.T, port string, tags ...string) []sdk.TxResponse {
var txs []tx.Info var txs []sdk.TxResponse
if len(tags) == 0 { if len(tags) == 0 {
return txs return txs
} }
@ -539,10 +543,11 @@ func getKeys(t *testing.T, port string) []keys.KeyOutput {
} }
// POST /keys Create a new account locally // POST /keys Create a new account locally
func doKeysPost(t *testing.T, port, name, password, seed string) keys.KeyOutput { func doKeysPost(t *testing.T, port, name, password, mnemonic string, account int, index int) keys.KeyOutput {
pk := postKeys{name, password, seed} pk := keys.AddNewKey{name, password, mnemonic, account, index}
req, err := cdc.MarshalJSON(pk) req, err := cdc.MarshalJSON(pk)
require.NoError(t, err) require.NoError(t, err)
res, body := Request(t, port, "POST", "/keys", req) res, body := Request(t, port, "POST", "/keys", req)
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
@ -552,12 +557,6 @@ func doKeysPost(t *testing.T, port, name, password, seed string) keys.KeyOutput
return resp return resp
} }
type postKeys struct {
Name string `json:"name"`
Password string `json:"password"`
Seed string `json:"seed"`
}
// GET /keys/seed Create a new seed to create a new account defaultValidFor // GET /keys/seed Create a new seed to create a new account defaultValidFor
func getKeysSeed(t *testing.T, port string) string { func getKeysSeed(t *testing.T, port string) string {
res, body := Request(t, port, "GET", "/keys/seed", nil) res, body := Request(t, port, "GET", "/keys/seed", nil)
@ -569,14 +568,17 @@ func getKeysSeed(t *testing.T, port string) string {
return body return body
} }
// POST /keys/{name}/recover Recover a account from a seed // POST /keys/{name}/recove Recover a account from a seed
func doRecoverKey(t *testing.T, port, recoverName, recoverPassword, seed string) { func doRecoverKey(t *testing.T, port, recoverName, recoverPassword, mnemonic string, account uint32, index uint32) {
jsonStr := []byte(fmt.Sprintf(`{"password":"%s", "seed":"%s"}`, recoverPassword, seed)) pk := keys.RecoverKey{recoverPassword, mnemonic, int(account), int(index)}
res, body := Request(t, port, "POST", fmt.Sprintf("/keys/%s/recover", recoverName), jsonStr) req, err := cdc.MarshalJSON(pk)
require.NoError(t, err)
res, body := Request(t, port, "POST", fmt.Sprintf("/keys/%s/recover", recoverName), req)
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
var resp keys.KeyOutput var resp keys.KeyOutput
err := codec.Cdc.UnmarshalJSON([]byte(body), &resp) err = codec.Cdc.UnmarshalJSON([]byte(body), &resp)
require.Nil(t, err, body) require.Nil(t, err, body)
addr1Bech32 := resp.Address addr1Bech32 := resp.Address
@ -596,7 +598,7 @@ func getKey(t *testing.T, port, name string) keys.KeyOutput {
// PUT /keys/{name} Update the password for this account in the KMS // PUT /keys/{name} Update the password for this account in the KMS
func updateKey(t *testing.T, port, name, oldPassword, newPassword string, fail bool) { func updateKey(t *testing.T, port, name, oldPassword, newPassword string, fail bool) {
kr := updateKeyReq{oldPassword, newPassword} kr := keys.UpdateKeyReq{oldPassword, newPassword}
req, err := cdc.MarshalJSON(kr) req, err := cdc.MarshalJSON(kr)
require.NoError(t, err) require.NoError(t, err)
keyEndpoint := fmt.Sprintf("/keys/%s", name) keyEndpoint := fmt.Sprintf("/keys/%s", name)
@ -608,14 +610,9 @@ func updateKey(t *testing.T, port, name, oldPassword, newPassword string, fail b
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
} }
type updateKeyReq struct {
OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"`
}
// DELETE /keys/{name} Remove an account // DELETE /keys/{name} Remove an account
func deleteKey(t *testing.T, port, name, password string) { func deleteKey(t *testing.T, port, name, password string) {
dk := deleteKeyReq{password} dk := keys.DeleteKeyReq{password}
req, err := cdc.MarshalJSON(dk) req, err := cdc.MarshalJSON(dk)
require.NoError(t, err) require.NoError(t, err)
keyEndpoint := fmt.Sprintf("/keys/%s", name) keyEndpoint := fmt.Sprintf("/keys/%s", name)
@ -623,10 +620,6 @@ func deleteKey(t *testing.T, port, name, password string) {
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
} }
type deleteKeyReq struct {
Password string `json:"password"`
}
// GET /auth/accounts/{address} Get the account information on blockchain // GET /auth/accounts/{address} Get the account information on blockchain
func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account { func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account {
res, body := Request(t, port, "GET", fmt.Sprintf("/auth/accounts/%s", addr.String()), nil) res, body := Request(t, port, "GET", fmt.Sprintf("/auth/accounts/%s", addr.String()), nil)
@ -646,7 +639,7 @@ func doSign(t *testing.T, port, name, password, chainID string, accnum, sequence
var signedMsg auth.StdTx var signedMsg auth.StdTx
payload := authrest.SignBody{ payload := authrest.SignBody{
Tx: msg, Tx: msg,
BaseReq: utils.NewBaseReq( BaseReq: rest.NewBaseReq(
name, password, "", chainID, "", "", accnum, sequence, nil, nil, false, false, name, password, "", chainID, "", "", accnum, sequence, nil, nil, false, false,
), ),
} }
@ -659,26 +652,21 @@ func doSign(t *testing.T, port, name, password, chainID string, accnum, sequence
} }
// POST /tx/broadcast Send a signed Tx // POST /tx/broadcast Send a signed Tx
func doBroadcast(t *testing.T, port string, msg auth.StdTx) ctypes.ResultBroadcastTxCommit { func doBroadcast(t *testing.T, port string, msg auth.StdTx) sdk.TxResponse {
tx := broadcastReq{Tx: msg, Return: "block"} tx := rest.BroadcastReq{Tx: msg, Return: "block"}
req, err := cdc.MarshalJSON(tx) req, err := cdc.MarshalJSON(tx)
require.Nil(t, err) require.Nil(t, err)
res, body := Request(t, port, "POST", "/tx/broadcast", req) res, body := Request(t, port, "POST", "/tx/broadcast", req)
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
var resultTx ctypes.ResultBroadcastTxCommit var resultTx sdk.TxResponse
require.Nil(t, cdc.UnmarshalJSON([]byte(body), &resultTx)) require.Nil(t, cdc.UnmarshalJSON([]byte(body), &resultTx))
return resultTx return resultTx
} }
type broadcastReq struct {
Tx auth.StdTx `json:"tx"`
Return string `json:"return"`
}
// GET /bank/balances/{address} Get the account balances // GET /bank/balances/{address} Get the account balances
// POST /bank/accounts/{address}/transfers Send coins (build -> sign -> send) // POST /bank/accounts/{address}/transfers Send coins (build -> sign -> send)
func doTransfer(t *testing.T, port, seed, name, memo, password string, addr sdk.AccAddress, fees sdk.Coins) (receiveAddr sdk.AccAddress, resultTx ctypes.ResultBroadcastTxCommit) { func doTransfer(t *testing.T, port, seed, name, memo, password string, addr sdk.AccAddress, fees sdk.Coins) (receiveAddr sdk.AccAddress, resultTx sdk.TxResponse) {
res, body, receiveAddr := doTransferWithGas(t, port, seed, name, memo, password, addr, "", 1.0, false, false, fees) res, body, receiveAddr := doTransferWithGas(t, port, seed, name, memo, password, addr, "", 1.0, false, false, fees)
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
@ -688,29 +676,71 @@ func doTransfer(t *testing.T, port, seed, name, memo, password string, addr sdk.
return receiveAddr, resultTx return receiveAddr, resultTx
} }
func doTransferWithGas(t *testing.T, port, seed, name, memo, password string, addr sdk.AccAddress, gas string, func doTransferWithGas(
gasAdjustment float64, simulate, generateOnly bool, fees sdk.Coins) ( t *testing.T, port, seed, from, memo, password string, addr sdk.AccAddress,
res *http.Response, body string, receiveAddr sdk.AccAddress) { gas string, gasAdjustment float64, simulate, generateOnly bool, fees sdk.Coins,
) (res *http.Response, body string, receiveAddr sdk.AccAddress) {
// create receive address // create receive address
kb := client.MockKeyBase() kb := client.MockKeyBase()
receiveInfo, _, err := kb.CreateMnemonic("receive_address", cryptoKeys.English, gapp.DefaultKeyPass, cryptoKeys.SigningAlgo("secp256k1"))
require.Nil(t, err)
receiveAddr = sdk.AccAddress(receiveInfo.GetPubKey().Address())
receiveInfo, _, err := kb.CreateMnemonic(
"receive_address", crkeys.English, gapp.DefaultKeyPass, crkeys.SigningAlgo("secp256k1"),
)
require.Nil(t, err)
receiveAddr = sdk.AccAddress(receiveInfo.GetPubKey().Address())
acc := getAccount(t, port, addr) acc := getAccount(t, port, addr)
accnum := acc.GetAccountNumber() accnum := acc.GetAccountNumber()
sequence := acc.GetSequence() sequence := acc.GetSequence()
chainID := viper.GetString(client.FlagChainID) chainID := viper.GetString(client.FlagChainID)
baseReq := utils.NewBaseReq( if generateOnly {
name, password, memo, chainID, gas, // generate only txs do not use a Keybase so the address must be used
from = addr.String()
}
baseReq := rest.NewBaseReq(
from, password, memo, chainID, gas,
fmt.Sprintf("%f", gasAdjustment), accnum, sequence, fees, nil, fmt.Sprintf("%f", gasAdjustment), accnum, sequence, fees, nil,
generateOnly, simulate, generateOnly, simulate,
) )
sr := sendReq{ sr := rest.SendReq{
Amount: sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 1)}, Amount: sdk.Coins{sdk.NewInt64Coin(staking.DefaultBondDenom, 1)},
BaseReq: baseReq,
}
req, err := cdc.MarshalJSON(sr)
require.NoError(t, err)
res, body = Request(t, port, "POST", fmt.Sprintf("/bank/accounts/%s/transfers", receiveAddr), req)
return
}
func doTransferWithGasAccAuto(
t *testing.T, port, seed, from, memo, password string, gas string,
gasAdjustment float64, simulate, generateOnly bool, fees sdk.Coins,
) (res *http.Response, body string, receiveAddr sdk.AccAddress) {
// create receive address
kb := client.MockKeyBase()
receiveInfo, _, err := kb.CreateMnemonic(
"receive_address", crkeys.English, gapp.DefaultKeyPass, crkeys.SigningAlgo("secp256k1"),
)
require.Nil(t, err)
receiveAddr = sdk.AccAddress(receiveInfo.GetPubKey().Address())
chainID := viper.GetString(client.FlagChainID)
baseReq := rest.NewBaseReq(
from, password, memo, chainID, gas,
fmt.Sprintf("%f", gasAdjustment), 0, 0, fees, nil, generateOnly, simulate,
)
sr := rest.SendReq{
Amount: sdk.Coins{sdk.NewInt64Coin(staking.DefaultBondDenom, 1)},
BaseReq: baseReq, BaseReq: baseReq,
} }
@ -722,8 +752,8 @@ func doTransferWithGas(t *testing.T, port, seed, name, memo, password string, ad
} }
type sendReq struct { type sendReq struct {
Amount sdk.Coins `json:"amount"` Amount sdk.Coins `json:"amount"`
BaseReq utils.BaseReq `json:"base_req"` BaseReq rest.BaseReq `json:"base_req"`
} }
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@ -732,24 +762,25 @@ type sendReq struct {
// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation // POST /staking/delegators/{delegatorAddr}/delegations Submit delegation
func doDelegate(t *testing.T, port, name, password string, func doDelegate(t *testing.T, port, name, password string,
delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount int64, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Int, fees sdk.Coins) (resultTx sdk.TxResponse) {
acc := getAccount(t, port, delAddr) acc := getAccount(t, port, delAddr)
accnum := acc.GetAccountNumber() accnum := acc.GetAccountNumber()
sequence := acc.GetSequence() sequence := acc.GetSequence()
chainID := viper.GetString(client.FlagChainID) chainID := viper.GetString(client.FlagChainID)
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
msg := msgDelegationsInput{ msg := msgDelegationsInput{
BaseReq: baseReq, BaseReq: baseReq,
DelegatorAddr: delAddr, DelegatorAddr: delAddr,
ValidatorAddr: valAddr, ValidatorAddr: valAddr,
Delegation: sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, amount), Delegation: sdk.NewCoin(staking.DefaultBondDenom, amount),
} }
req, err := cdc.MarshalJSON(msg) req, err := cdc.MarshalJSON(msg)
require.NoError(t, err) require.NoError(t, err)
res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/delegations", delAddr.String()), req) res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/delegations", delAddr.String()), req)
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
var result ctypes.ResultBroadcastTxCommit var result sdk.TxResponse
err = cdc.UnmarshalJSON([]byte(body), &result) err = cdc.UnmarshalJSON([]byte(body), &result)
require.Nil(t, err) require.Nil(t, err)
@ -757,7 +788,7 @@ func doDelegate(t *testing.T, port, name, password string,
} }
type msgDelegationsInput struct { type msgDelegationsInput struct {
BaseReq utils.BaseReq `json:"base_req"` BaseReq rest.BaseReq `json:"base_req"`
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32
ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32 ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32
Delegation sdk.Coin `json:"delegation"` Delegation sdk.Coin `json:"delegation"`
@ -765,18 +796,18 @@ type msgDelegationsInput struct {
// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation // POST /staking/delegators/{delegatorAddr}/delegations Submit delegation
func doUndelegate(t *testing.T, port, name, password string, func doUndelegate(t *testing.T, port, name, password string,
delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount int64, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Int, fees sdk.Coins) (resultTx sdk.TxResponse) {
acc := getAccount(t, port, delAddr) acc := getAccount(t, port, delAddr)
accnum := acc.GetAccountNumber() accnum := acc.GetAccountNumber()
sequence := acc.GetSequence() sequence := acc.GetSequence()
chainID := viper.GetString(client.FlagChainID) chainID := viper.GetString(client.FlagChainID)
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
msg := msgUndelegateInput{ msg := msgUndelegateInput{
BaseReq: baseReq, BaseReq: baseReq,
DelegatorAddr: delAddr, DelegatorAddr: delAddr,
ValidatorAddr: valAddr, ValidatorAddr: valAddr,
SharesAmount: sdk.NewDec(amount), SharesAmount: sdk.NewDecFromInt(amount),
} }
req, err := cdc.MarshalJSON(msg) req, err := cdc.MarshalJSON(msg)
require.NoError(t, err) require.NoError(t, err)
@ -784,7 +815,7 @@ func doUndelegate(t *testing.T, port, name, password string,
res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/unbonding_delegations", delAddr), req) res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/unbonding_delegations", delAddr), req)
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
var result ctypes.ResultBroadcastTxCommit var result sdk.TxResponse
err = cdc.UnmarshalJSON([]byte(body), &result) err = cdc.UnmarshalJSON([]byte(body), &result)
require.Nil(t, err) require.Nil(t, err)
@ -792,7 +823,7 @@ func doUndelegate(t *testing.T, port, name, password string,
} }
type msgUndelegateInput struct { type msgUndelegateInput struct {
BaseReq utils.BaseReq `json:"base_req"` BaseReq rest.BaseReq `json:"base_req"`
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32
ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32 ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32
SharesAmount sdk.Dec `json:"shares"` SharesAmount sdk.Dec `json:"shares"`
@ -800,21 +831,22 @@ type msgUndelegateInput struct {
// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation // POST /staking/delegators/{delegatorAddr}/delegations Submit delegation
func doBeginRedelegation(t *testing.T, port, name, password string, func doBeginRedelegation(t *testing.T, port, name, password string,
delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, amount int64, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, amount sdk.Int,
fees sdk.Coins) (resultTx sdk.TxResponse) {
acc := getAccount(t, port, delAddr) acc := getAccount(t, port, delAddr)
accnum := acc.GetAccountNumber() accnum := acc.GetAccountNumber()
sequence := acc.GetSequence() sequence := acc.GetSequence()
chainID := viper.GetString(client.FlagChainID) chainID := viper.GetString(client.FlagChainID)
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
msg := msgBeginRedelegateInput{ msg := rest.MsgBeginRedelegateInput{
BaseReq: baseReq, BaseReq: baseReq,
DelegatorAddr: delAddr, DelegatorAddr: delAddr,
ValidatorSrcAddr: valSrcAddr, ValidatorSrcAddr: valSrcAddr,
ValidatorDstAddr: valDstAddr, ValidatorDstAddr: valDstAddr,
SharesAmount: sdk.NewDec(amount), SharesAmount: sdk.NewDecFromInt(amount),
} }
req, err := cdc.MarshalJSON(msg) req, err := cdc.MarshalJSON(msg)
require.NoError(t, err) require.NoError(t, err)
@ -822,7 +854,7 @@ func doBeginRedelegation(t *testing.T, port, name, password string,
res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/redelegations", delAddr), req) res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/redelegations", delAddr), req)
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
var result ctypes.ResultBroadcastTxCommit var result sdk.TxResponse
err = cdc.UnmarshalJSON([]byte(body), &result) err = cdc.UnmarshalJSON([]byte(body), &result)
require.Nil(t, err) require.Nil(t, err)
@ -830,7 +862,7 @@ func doBeginRedelegation(t *testing.T, port, name, password string,
} }
type msgBeginRedelegateInput struct { type msgBeginRedelegateInput struct {
BaseReq utils.BaseReq `json:"base_req"` BaseReq rest.BaseReq `json:"base_req"`
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32
ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` // in bech32 ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` // in bech32
ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` // in bech32 ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` // in bech32
@ -911,7 +943,7 @@ func getDelegatorValidator(t *testing.T, port string, delegatorAddr sdk.AccAddre
} }
// GET /staking/delegators/{delegatorAddr}/txs Get all staking txs (i.e msgs) from a delegator // GET /staking/delegators/{delegatorAddr}/txs Get all staking txs (i.e msgs) from a delegator
func getBondingTxs(t *testing.T, port string, delegatorAddr sdk.AccAddress, query string) []tx.Info { func getBondingTxs(t *testing.T, port string, delegatorAddr sdk.AccAddress, query string) []sdk.TxResponse {
var res *http.Response var res *http.Response
var body string var body string
@ -922,7 +954,7 @@ func getBondingTxs(t *testing.T, port string, delegatorAddr sdk.AccAddress, quer
} }
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
var txs []tx.Info var txs []sdk.TxResponse
err := cdc.UnmarshalJSON([]byte(body), &txs) err := cdc.UnmarshalJSON([]byte(body), &txs)
require.Nil(t, err) require.Nil(t, err)
@ -1033,19 +1065,21 @@ func getStakingParams(t *testing.T, port string) staking.Params {
// ICS 22 - Gov // ICS 22 - Gov
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// POST /gov/proposals Submit a proposal // POST /gov/proposals Submit a proposal
func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, amount int64, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress,
amount sdk.Int, fees sdk.Coins) (resultTx sdk.TxResponse) {
acc := getAccount(t, port, proposerAddr) acc := getAccount(t, port, proposerAddr)
accnum := acc.GetAccountNumber() accnum := acc.GetAccountNumber()
sequence := acc.GetSequence() sequence := acc.GetSequence()
chainID := viper.GetString(client.FlagChainID) chainID := viper.GetString(client.FlagChainID)
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
pr := postProposalReq{ pr := rest.PostProposalReq{
Title: "Test", Title: "Test",
Description: "test", Description: "test",
ProposalType: "Text", ProposalType: "Text",
Proposer: proposerAddr, Proposer: proposerAddr,
InitialDeposit: sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))}, InitialDeposit: sdk.Coins{sdk.NewCoin(staking.DefaultBondDenom, amount)},
BaseReq: baseReq, BaseReq: baseReq,
} }
@ -1056,7 +1090,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA
res, body := Request(t, port, "POST", "/gov/proposals", req) res, body := Request(t, port, "POST", "/gov/proposals", req)
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
var results ctypes.ResultBroadcastTxCommit var results sdk.TxResponse
err = cdc.UnmarshalJSON([]byte(body), &results) err = cdc.UnmarshalJSON([]byte(body), &results)
require.Nil(t, err) require.Nil(t, err)
@ -1064,7 +1098,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA
} }
type postProposalReq struct { type postProposalReq struct {
BaseReq utils.BaseReq `json:"base_req"` BaseReq rest.BaseReq `json:"base_req"`
Title string `json:"title"` // Title of the proposal Title string `json:"title"` // Title of the proposal
Description string `json:"description"` // Description of the proposal Description string `json:"description"` // Description of the proposal
ProposalType string `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} ProposalType string `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
@ -1128,17 +1162,18 @@ func getProposalsFilterStatus(t *testing.T, port string, status gov.ProposalStat
} }
// POST /gov/proposals/{proposalId}/deposits Deposit tokens to a proposal // POST /gov/proposals/{proposalId}/deposits Deposit tokens to a proposal
func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64, amount int64, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64,
amount sdk.Int, fees sdk.Coins) (resultTx sdk.TxResponse) {
acc := getAccount(t, port, proposerAddr) acc := getAccount(t, port, proposerAddr)
accnum := acc.GetAccountNumber() accnum := acc.GetAccountNumber()
sequence := acc.GetSequence() sequence := acc.GetSequence()
chainID := viper.GetString(client.FlagChainID) chainID := viper.GetString(client.FlagChainID)
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
dr := depositReq{ dr := rest.DepositReq{
Depositor: proposerAddr, Depositor: proposerAddr,
Amount: sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))}, Amount: sdk.Coins{sdk.NewCoin(staking.DefaultBondDenom, amount)},
BaseReq: baseReq, BaseReq: baseReq,
} }
@ -1148,7 +1183,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk
res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), req) res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), req)
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
var results ctypes.ResultBroadcastTxCommit var results sdk.TxResponse
err = cdc.UnmarshalJSON([]byte(body), &results) err = cdc.UnmarshalJSON([]byte(body), &results)
require.Nil(t, err) require.Nil(t, err)
@ -1156,7 +1191,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk
} }
type depositReq struct { type depositReq struct {
BaseReq utils.BaseReq `json:"base_req"` BaseReq rest.BaseReq `json:"base_req"`
Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor
Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit
} }
@ -1182,15 +1217,15 @@ func getTally(t *testing.T, port string, proposalID uint64) gov.TallyResult {
} }
// POST /gov/proposals/{proposalId}/votes Vote a proposal // POST /gov/proposals/{proposalId}/votes Vote a proposal
func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64, option string, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64, option string, fees sdk.Coins) (resultTx sdk.TxResponse) {
// get the account to get the sequence // get the account to get the sequence
acc := getAccount(t, port, proposerAddr) acc := getAccount(t, port, proposerAddr)
accnum := acc.GetAccountNumber() accnum := acc.GetAccountNumber()
sequence := acc.GetSequence() sequence := acc.GetSequence()
chainID := viper.GetString(client.FlagChainID) chainID := viper.GetString(client.FlagChainID)
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
vr := voteReq{ vr := rest.VoteReq{
Voter: proposerAddr, Voter: proposerAddr,
Option: option, Option: option,
BaseReq: baseReq, BaseReq: baseReq,
@ -1202,7 +1237,7 @@ func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.Ac
res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/votes", proposalID), req) res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/votes", proposalID), req)
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
var results ctypes.ResultBroadcastTxCommit var results sdk.TxResponse
err = cdc.UnmarshalJSON([]byte(body), &results) err = cdc.UnmarshalJSON([]byte(body), &results)
require.Nil(t, err) require.Nil(t, err)
@ -1210,7 +1245,7 @@ func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.Ac
} }
type voteReq struct { type voteReq struct {
BaseReq utils.BaseReq `json:"base_req"` BaseReq rest.BaseReq `json:"base_req"`
Voter sdk.AccAddress `json:"voter"` // address of the voter Voter sdk.AccAddress `json:"voter"` // address of the voter
Option string `json:"option"` // option from OptionSet chosen by the voter Option string `json:"option"` // option from OptionSet chosen by the voter
} }
@ -1318,11 +1353,11 @@ func getSigningInfo(t *testing.T, port string, validatorPubKey string) slashing.
// TODO: Test this functionality, it is not currently in any of the tests // TODO: Test this functionality, it is not currently in any of the tests
// POST /slashing/validators/{validatorAddr}/unjail Unjail a jailed validator // POST /slashing/validators/{validatorAddr}/unjail Unjail a jailed validator
func doUnjail(t *testing.T, port, seed, name, password string, func doUnjail(t *testing.T, port, seed, name, password string,
valAddr sdk.ValAddress, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { valAddr sdk.ValAddress, fees sdk.Coins) (resultTx sdk.TxResponse) {
chainID := viper.GetString(client.FlagChainID) chainID := viper.GetString(client.FlagChainID)
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", 1, 1, fees, nil, false, false) baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", 1, 1, fees, nil, false, false)
ur := unjailReq{ ur := rest.UnjailReq{
BaseReq: baseReq, BaseReq: baseReq,
} }
req, err := cdc.MarshalJSON(ur) req, err := cdc.MarshalJSON(ur)
@ -1330,13 +1365,47 @@ func doUnjail(t *testing.T, port, seed, name, password string,
res, body := Request(t, port, "POST", fmt.Sprintf("/slashing/validators/%s/unjail", valAddr.String()), req) res, body := Request(t, port, "POST", fmt.Sprintf("/slashing/validators/%s/unjail", valAddr.String()), req)
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
var results []ctypes.ResultBroadcastTxCommit var results sdk.TxResponse
err = cdc.UnmarshalJSON([]byte(body), &results) err = cdc.UnmarshalJSON([]byte(body), &results)
require.Nil(t, err) require.Nil(t, err)
return results[0] return results
} }
type unjailReq struct { type unjailReq struct {
BaseReq utils.BaseReq `json:"base_req"` BaseReq rest.BaseReq `json:"base_req"`
}
// ICS24 - fee distribution
// POST /distribution/delegators/{delgatorAddr}/rewards Withdraw delegator rewards
func doWithdrawDelegatorAllRewards(t *testing.T, port, seed, name, password string,
delegatorAddr sdk.AccAddress, fees sdk.Coins) (resultTx sdk.TxResponse) {
// get the account to get the sequence
acc := getAccount(t, port, delegatorAddr)
accnum := acc.GetAccountNumber()
sequence := acc.GetSequence()
chainID := viper.GetString(client.FlagChainID)
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
wr := struct {
BaseReq rest.BaseReq `json:"base_req"`
}{BaseReq: baseReq}
req := cdc.MustMarshalJSON(wr)
res, body := Request(t, port, "POST", fmt.Sprintf("/distribution/delegators/%s/rewards", delegatorAddr), req)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var results sdk.TxResponse
cdc.MustUnmarshalJSON([]byte(body), &results)
return results
}
func mustParseDecCoins(dcstring string) sdk.DecCoins {
dcoins, err := sdk.ParseDecCoins(dcstring)
if err != nil {
panic(err)
}
return dcoins
} }

255
client/rest/rest.go Normal file
View File

@ -0,0 +1,255 @@
package rest
import (
"fmt"
"net/http"
"strconv"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
)
//-----------------------------------------------------------------------------
// Basic HTTP utilities
// ErrorResponse defines the attributes of a JSON error response.
type ErrorResponse struct {
Code int `json:"code,omitempty"`
Message string `json:"message"`
}
// NewErrorResponse creates a new ErrorResponse instance.
func NewErrorResponse(code int, msg string) ErrorResponse {
return ErrorResponse{Code: code, Message: msg}
}
// WriteErrorResponse prepares and writes a HTTP error
// given a status code and an error message.
func WriteErrorResponse(w http.ResponseWriter, status int, err string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
w.Write(codec.Cdc.MustMarshalJSON(NewErrorResponse(0, err)))
}
// WriteSimulationResponse prepares and writes an HTTP
// response for transactions simulations.
func WriteSimulationResponse(w http.ResponseWriter, cdc *codec.Codec, gas uint64) {
gasEst := GasEstimateResponse{GasEstimate: gas}
resp, err := cdc.MarshalJSON(gasEst)
if err != nil {
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(resp)
}
// ParseInt64OrReturnBadRequest converts s to a int64 value.
func ParseInt64OrReturnBadRequest(w http.ResponseWriter, s string) (n int64, ok bool) {
var err error
n, err = strconv.ParseInt(s, 10, 64)
if err != nil {
err := fmt.Errorf("'%s' is not a valid int64", s)
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return n, false
}
return n, true
}
// ParseUint64OrReturnBadRequest converts s to a uint64 value.
func ParseUint64OrReturnBadRequest(w http.ResponseWriter, s string) (n uint64, ok bool) {
var err error
n, err = strconv.ParseUint(s, 10, 64)
if err != nil {
err := fmt.Errorf("'%s' is not a valid uint64", s)
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return n, false
}
return n, true
}
// ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a
// default value, defaultIfEmpty, if the string is empty.
func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEmpty float64) (n float64, ok bool) {
if len(s) == 0 {
return defaultIfEmpty, true
}
n, err := strconv.ParseFloat(s, 64)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return n, false
}
return n, true
}
//-----------------------------------------------------------------------------
// Building / Sending utilities
// CompleteAndBroadcastTxREST implements a utility function that facilitates
// sending a series of messages in a signed tx. In addition, it will handle
// tx gas simulation and estimation.
//
// NOTE: Also see CompleteAndBroadcastTxCLI.
func CompleteAndBroadcastTxREST(
w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext,
baseReq BaseReq, msgs []sdk.Msg, cdc *codec.Codec,
) {
gasAdj, ok := ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment)
if !ok {
return
}
simAndExec, gas, err := client.ParseGas(baseReq.Gas)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
// derive the from account address and name from the Keybase
fromAddress, fromName, err := context.GetFromFields(baseReq.From)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress)
txBldr := authtxb.NewTxBuilder(
utils.GetTxEncoder(cdc), baseReq.AccountNumber,
baseReq.Sequence, gas, gasAdj, baseReq.Simulate,
baseReq.ChainID, baseReq.Memo, baseReq.Fees, baseReq.GasPrices,
)
txBldr, err = utils.PrepareTxBuilder(txBldr, cliCtx)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
if baseReq.Simulate || simAndExec {
if gasAdj < 0 {
WriteErrorResponse(w, http.StatusBadRequest, client.ErrInvalidGasAdjustment.Error())
return
}
txBldr, err = utils.EnrichWithGas(txBldr, cliCtx, msgs)
if err != nil {
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
if baseReq.Simulate {
WriteSimulationResponse(w, cdc, txBldr.Gas())
return
}
}
txBytes, err := txBldr.BuildAndSign(cliCtx.GetFromName(), baseReq.Password, msgs)
if keyerror.IsErrKeyNotFound(err) {
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
} else if keyerror.IsErrWrongPassword(err) {
WriteErrorResponse(w, http.StatusUnauthorized, err.Error())
return
} else if err != nil {
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
res, err := cliCtx.BroadcastTx(txBytes)
if err != nil {
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
PostProcessResponse(w, cdc, res, cliCtx.Indent)
}
// PostProcessResponse performs post processing for a REST response.
func PostProcessResponse(w http.ResponseWriter, cdc *codec.Codec, response interface{}, indent bool) {
var output []byte
switch response.(type) {
default:
var err error
if indent {
output, err = cdc.MarshalJSONIndent(response, "", " ")
} else {
output, err = cdc.MarshalJSON(response)
}
if err != nil {
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
case []byte:
output = response.([]byte)
}
w.Header().Set("Content-Type", "application/json")
w.Write(output)
}
// WriteGenerateStdTxResponse writes response for the generate only mode.
func WriteGenerateStdTxResponse(
w http.ResponseWriter, cdc *codec.Codec, cliCtx context.CLIContext, br BaseReq, msgs []sdk.Msg,
) {
gasAdj, ok := ParseFloat64OrReturnBadRequest(w, br.GasAdjustment, client.DefaultGasAdjustment)
if !ok {
return
}
simAndExec, gas, err := client.ParseGas(br.Gas)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
txBldr := authtxb.NewTxBuilder(
utils.GetTxEncoder(cdc), br.AccountNumber, br.Sequence, gas, gasAdj,
br.Simulate, br.ChainID, br.Memo, br.Fees, br.GasPrices,
)
if simAndExec {
if gasAdj < 0 {
WriteErrorResponse(w, http.StatusBadRequest, client.ErrInvalidGasAdjustment.Error())
return
}
txBldr, err = utils.EnrichWithGas(txBldr, cliCtx, msgs)
if err != nil {
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
}
stdMsg, err := txBldr.BuildSignMsg(msgs)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
output, err := cdc.MarshalJSON(auth.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo))
if err != nil {
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(output)
return
}

187
client/rest/types.go Normal file
View File

@ -0,0 +1,187 @@
package rest
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
)
// GasEstimateResponse defines a response definition for tx gas estimation.
type GasEstimateResponse struct {
GasEstimate uint64 `json:"gas_estimate"`
}
// BaseReq defines a structure that can be embedded in other request structures
// that all share common "base" fields.
type BaseReq struct {
From string `json:"from"`
Password string `json:"password"`
Memo string `json:"memo"`
ChainID string `json:"chain_id"`
AccountNumber uint64 `json:"account_number"`
Sequence uint64 `json:"sequence"`
Fees sdk.Coins `json:"fees"`
GasPrices sdk.DecCoins `json:"gas_prices"`
Gas string `json:"gas"`
GasAdjustment string `json:"gas_adjustment"`
GenerateOnly bool `json:"generate_only"`
Simulate bool `json:"simulate"`
}
// NewBaseReq creates a new basic request instance and sanitizes its values
func NewBaseReq(
from, password, memo, chainID string, gas, gasAdjustment string,
accNumber, seq uint64, fees sdk.Coins, gasPrices sdk.DecCoins, genOnly, simulate bool,
) BaseReq {
return BaseReq{
From: strings.TrimSpace(from),
Password: password,
Memo: strings.TrimSpace(memo),
ChainID: strings.TrimSpace(chainID),
Fees: fees,
GasPrices: gasPrices,
Gas: strings.TrimSpace(gas),
GasAdjustment: strings.TrimSpace(gasAdjustment),
AccountNumber: accNumber,
Sequence: seq,
GenerateOnly: genOnly,
Simulate: simulate,
}
}
// Sanitize performs basic sanitization on a BaseReq object.
func (br BaseReq) Sanitize() BaseReq {
return NewBaseReq(
br.From, br.Password, br.Memo, br.ChainID, br.Gas, br.GasAdjustment,
br.AccountNumber, br.Sequence, br.Fees, br.GasPrices, br.GenerateOnly, br.Simulate,
)
}
// ValidateBasic performs basic validation of a BaseReq. If custom validation
// logic is needed, the implementing request handler should perform those
// checks manually.
func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool {
if !br.GenerateOnly && !br.Simulate {
switch {
case len(br.Password) == 0:
WriteErrorResponse(w, http.StatusUnauthorized, "password required but not specified")
return false
case len(br.ChainID) == 0:
WriteErrorResponse(w, http.StatusUnauthorized, "chain-id required but not specified")
return false
case !br.Fees.IsZero() && !br.GasPrices.IsZero():
// both fees and gas prices were provided
WriteErrorResponse(w, http.StatusBadRequest, "cannot provide both fees and gas prices")
return false
case !br.Fees.IsValid() && !br.GasPrices.IsValid():
// neither fees or gas prices were provided
WriteErrorResponse(w, http.StatusPaymentRequired, "invalid fees or gas prices provided")
return false
}
}
if len(br.From) == 0 {
WriteErrorResponse(w, http.StatusUnauthorized, "name or address required but not specified")
return false
}
return true
}
/*
ReadRESTReq is a simple convenience wrapper that reads the body and
unmarshals to the req interface. Returns false if errors occurred.
Usage:
type SomeReq struct {
BaseReq `json:"base_req"`
CustomField string `json:"custom_field"`
}
req := new(SomeReq)
if ok := ReadRESTReq(w, r, cdc, req); !ok {
return
}
*/
func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req interface{}) bool {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return false
}
err = cdc.UnmarshalJSON(body, req)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to decode JSON payload: %s", err))
return false
}
return true
}
// AddrSeed combines an Address with the mnemonic of the private key to that address
type AddrSeed struct {
Address sdk.AccAddress
Seed string
Name string
Password string
}
// SendReq requests sending an amount of coins
type SendReq struct {
Amount sdk.Coins `json:"amount"`
BaseReq BaseReq `json:"base_req"`
}
// MsgBeginRedelegateInput request to begin a redelegation
type MsgBeginRedelegateInput struct {
BaseReq BaseReq `json:"base_req"`
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32
ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` // in bech32
ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` // in bech32
SharesAmount sdk.Dec `json:"shares"`
}
// PostProposalReq requests a proposals
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 sdk.AccAddress `json:"proposer"` // Address of the proposer
InitialDeposit sdk.Coins `json:"initial_deposit"` // Coins to add to the proposal's deposit
}
// BroadcastReq requests broadcasting a transaction
type BroadcastReq struct {
Tx auth.StdTx `json:"tx"`
Return string `json:"return"`
}
// DepositReq requests a deposit of an amount of coins
type DepositReq struct {
BaseReq BaseReq `json:"base_req"`
Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor
Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit
}
// VoteReq requests sending a vote
type VoteReq struct {
BaseReq BaseReq `json:"base_req"`
Voter sdk.AccAddress `json:"voter"` // address of the voter
Option string `json:"option"` // option from OptionSet chosen by the voter
}
// UnjailReq request unjailing
type UnjailReq struct {
BaseReq BaseReq `json:"base_req"`
}

View File

@ -5,6 +5,8 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/cosmos/cosmos-sdk/client/rest"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
@ -12,8 +14,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
tmliteProxy "github.com/tendermint/tendermint/lite/proxy" tmliteProxy "github.com/tendermint/tendermint/lite/proxy"
"github.com/cosmos/cosmos-sdk/client/utils"
) )
//BlockCommand returns the verified block data for a given heights //BlockCommand returns the verified block data for a given heights
@ -131,7 +131,7 @@ func BlockRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
w.Write([]byte(err.Error())) w.Write([]byte(err.Error()))
return return
} }
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent) rest.PostProcessResponse(w, cdc, output, cliCtx.Indent)
} }
} }
@ -150,6 +150,6 @@ func LatestBlockRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
w.Write([]byte(err.Error())) w.Write([]byte(err.Error()))
return return
} }
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent) rest.PostProcessResponse(w, cdc, output, cliCtx.Indent)
} }
} }

View File

@ -4,10 +4,11 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/cosmos/cosmos-sdk/client/rest"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/version"
) )
@ -26,7 +27,7 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
// cli version REST handler endpoint // cli version REST handler endpoint
func CLIVersionRequestHandler(w http.ResponseWriter, r *http.Request) { func CLIVersionRequestHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.Write([]byte(fmt.Sprintf("{\"version\": \"%s\"}", version.GetVersion()))) w.Write([]byte(fmt.Sprintf("{\"version\": \"%s\"}", version.Version)))
} }
// connected node version REST handler endpoint // connected node version REST handler endpoint
@ -34,7 +35,7 @@ func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
version, err := cliCtx.Query("/app/version", nil) version, err := cliCtx.Query("/app/version", nil)
if err != nil { if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }

View File

@ -5,6 +5,8 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/cosmos/cosmos-sdk/client/rest"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -12,7 +14,6 @@ import (
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
) )
// StatusCommand returns the status of the network // StatusCommand returns the status of the network
@ -77,7 +78,7 @@ func NodeInfoRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
} }
nodeInfo := status.NodeInfo nodeInfo := status.NodeInfo
utils.PostProcessResponse(w, cdc, nodeInfo, cliCtx.Indent) rest.PostProcessResponse(w, cdc, nodeInfo, cliCtx.Indent)
} }
} }

View File

@ -5,6 +5,11 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/client/rest"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -14,33 +19,59 @@ import (
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
) )
// TODO these next two functions feel kinda hacky based on their placement // TODO these next two functions feel kinda hacky based on their placement
//ValidatorCommand returns the validator set for a given height //ValidatorCommand returns the validator set for a given height
func ValidatorCommand() *cobra.Command { func ValidatorCommand(cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "tendermint-validator-set [height]", Use: "tendermint-validator-set [height]",
Short: "Get the full tendermint validator set at given height", Short: "Get the full tendermint validator set at given height",
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
RunE: printValidators, RunE: func(cmd *cobra.Command, args []string) error {
var height *int64
// optional height
if len(args) > 0 {
h, err := strconv.Atoi(args[0])
if err != nil {
return err
}
if h > 0 {
tmp := int64(h)
height = &tmp
}
}
cliCtx := context.NewCLIContext().WithCodec(cdc)
result, err := getValidators(cliCtx, height)
if err != nil {
return err
}
return cliCtx.PrintOutput(result)
},
} }
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode))
cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)")
viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode))
cmd.Flags().Bool(client.FlagIndentResponse, false, "indent JSON response")
viper.BindPFlag(client.FlagIndentResponse, cmd.Flags().Lookup(client.FlagIndentResponse))
return cmd return cmd
} }
// Validator output in bech32 format // Validator output in bech32 format
type ValidatorOutput struct { type ValidatorOutput struct {
Address sdk.ValAddress `json:"address"` // in bech32 Address sdk.ConsAddress `json:"address"`
PubKey string `json:"pub_key"` // in bech32 PubKey string `json:"pub_key"`
ProposerPriority int64 `json:"proposer_priority"` ProposerPriority int64 `json:"proposer_priority"`
VotingPower int64 `json:"voting_power"` VotingPower int64 `json:"voting_power"`
} }
// Validators at a certain height output in bech32 format // Validators at a certain height output in bech32 format
@ -49,6 +80,27 @@ type ResultValidatorsOutput struct {
Validators []ValidatorOutput `json:"validators"` Validators []ValidatorOutput `json:"validators"`
} }
func (rvo ResultValidatorsOutput) String() string {
var b strings.Builder
b.WriteString(fmt.Sprintf("block height: %d\n", rvo.BlockHeight))
for _, val := range rvo.Validators {
b.WriteString(
fmt.Sprintf(`
Address: %s
Pubkey: %s
ProposerPriority: %d
VotingPower: %d
`,
val.Address, val.PubKey, val.ProposerPriority, val.VotingPower,
),
)
}
return b.String()
}
func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error) { func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error) {
bechValPubkey, err := sdk.Bech32ifyConsPub(validator.PubKey) bechValPubkey, err := sdk.Bech32ifyConsPub(validator.PubKey)
if err != nil { if err != nil {
@ -56,33 +108,33 @@ func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error
} }
return ValidatorOutput{ return ValidatorOutput{
Address: sdk.ValAddress(validator.Address), Address: sdk.ConsAddress(validator.Address),
PubKey: bechValPubkey, PubKey: bechValPubkey,
ProposerPriority: validator.ProposerPriority, ProposerPriority: validator.ProposerPriority,
VotingPower: validator.VotingPower, VotingPower: validator.VotingPower,
}, nil }, nil
} }
func getValidators(cliCtx context.CLIContext, height *int64) ([]byte, error) { func getValidators(cliCtx context.CLIContext, height *int64) (ResultValidatorsOutput, error) {
// get the node // get the node
node, err := cliCtx.GetNode() node, err := cliCtx.GetNode()
if err != nil { if err != nil {
return nil, err return ResultValidatorsOutput{}, err
} }
validatorsRes, err := node.Validators(height) validatorsRes, err := node.Validators(height)
if err != nil { if err != nil {
return nil, err return ResultValidatorsOutput{}, err
} }
if !cliCtx.TrustNode { if !cliCtx.TrustNode {
check, err := cliCtx.Verify(validatorsRes.BlockHeight) check, err := cliCtx.Verify(validatorsRes.BlockHeight)
if err != nil { if err != nil {
return nil, err return ResultValidatorsOutput{}, err
} }
if !bytes.Equal(check.ValidatorsHash, tmtypes.NewValidatorSet(validatorsRes.Validators).Hash()) { if !bytes.Equal(check.ValidatorsHash, tmtypes.NewValidatorSet(validatorsRes.Validators).Hash()) {
return nil, fmt.Errorf("got invalid validatorset") return ResultValidatorsOutput{}, fmt.Errorf("received invalid validatorset")
} }
} }
@ -94,40 +146,11 @@ func getValidators(cliCtx context.CLIContext, height *int64) ([]byte, error) {
for i := 0; i < len(validatorsRes.Validators); i++ { for i := 0; i < len(validatorsRes.Validators); i++ {
outputValidatorsRes.Validators[i], err = bech32ValidatorOutput(validatorsRes.Validators[i]) outputValidatorsRes.Validators[i], err = bech32ValidatorOutput(validatorsRes.Validators[i])
if err != nil { if err != nil {
return nil, err return ResultValidatorsOutput{}, err
} }
} }
if cliCtx.Indent { return outputValidatorsRes, nil
return cdc.MarshalJSONIndent(outputValidatorsRes, "", " ")
}
return cdc.MarshalJSON(outputValidatorsRes)
}
// CMD
func printValidators(cmd *cobra.Command, args []string) error {
var height *int64
// optional height
if len(args) > 0 {
h, err := strconv.Atoi(args[0])
if err != nil {
return err
}
if h > 0 {
tmp := int64(h)
height = &tmp
}
}
output, err := getValidators(context.NewCLIContext(), height)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
} }
// REST // REST
@ -157,7 +180,7 @@ func ValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
w.Write([]byte(err.Error())) w.Write([]byte(err.Error()))
return return
} }
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent) rest.PostProcessResponse(w, cdc, output, cliCtx.Indent)
} }
} }
@ -177,6 +200,6 @@ func LatestValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerF
w.Write([]byte(err.Error())) w.Write([]byte(err.Error()))
return return
} }
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent) rest.PostProcessResponse(w, cdc, output, cliCtx.Indent)
} }
} }

View File

@ -3,10 +3,11 @@ package tx
import ( import (
"net/http" "net/http"
"github.com/cosmos/cosmos-sdk/client/rest"
"io/ioutil" "io/ioutil"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
) )
@ -32,12 +33,12 @@ func BroadcastTxRequest(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle
var m BroadcastBody var m BroadcastBody
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
err = cdc.UnmarshalJSON(body, &m) err = cdc.UnmarshalJSON(body, &m)
if err != nil { if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
var res interface{} var res interface{}
@ -49,13 +50,13 @@ func BroadcastTxRequest(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle
case flagAsync: case flagAsync:
res, err = cliCtx.BroadcastTxAsync(m.TxBytes) res, err = cliCtx.BroadcastTxAsync(m.TxBytes)
default: default:
utils.WriteErrorResponse(w, http.StatusInternalServerError, "unsupported return type. supported types: block, sync, async") rest.WriteErrorResponse(w, http.StatusInternalServerError, "unsupported return type. supported types: block, sync, async")
return return
} }
if err != nil { if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }
utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) rest.PostProcessResponse(w, cdc, res, cliCtx.Indent)
} }
} }

View File

@ -5,18 +5,15 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/tendermint/tendermint/libs/common"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/spf13/cobra" "github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
ctypes "github.com/tendermint/tendermint/rpc/core/types" ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/client/rest"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
@ -99,26 +96,13 @@ func ValidateTxResult(cliCtx context.CLIContext, res *ctypes.ResultTx) error {
return nil return nil
} }
func formatTxResult(cdc *codec.Codec, res *ctypes.ResultTx) (Info, error) { func formatTxResult(cdc *codec.Codec, res *ctypes.ResultTx) (sdk.TxResponse, error) {
tx, err := parseTx(cdc, res.Tx) tx, err := parseTx(cdc, res.Tx)
if err != nil { if err != nil {
return Info{}, err return sdk.TxResponse{}, err
} }
return Info{ return sdk.NewResponseResultTx(res, tx), nil
Hash: res.Hash,
Height: res.Height,
Tx: tx,
Result: res.TxResult,
}, nil
}
// Info is used to prepare info to display
type Info struct {
Hash common.HexBytes `json:"hash"`
Height int64 `json:"height"`
Tx sdk.Tx `json:"tx"`
Result abci.ResponseDeliverTx `json:"result"`
} }
func parseTx(cdc *codec.Codec, txBytes []byte) (sdk.Tx, error) { func parseTx(cdc *codec.Codec, txBytes []byte) (sdk.Tx, error) {
@ -142,9 +126,9 @@ func QueryTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.H
output, err := queryTx(cdc, cliCtx, hashHexStr) output, err := queryTx(cdc, cliCtx, hashHexStr)
if err != nil { if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent) rest.PostProcessResponse(w, cdc, output, cliCtx.Indent)
} }
} }

View File

@ -8,9 +8,10 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/cosmos/cosmos-sdk/client/rest"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
@ -105,7 +106,7 @@ $ gaiacli query txs --tags '<tag1>:<value1>&<tag2>:<value2>' --page 1 --limit 30
// SearchTxs performs a search for transactions for a given set of tags via // SearchTxs performs a search for transactions for a given set of tags via
// Tendermint RPC. It returns a slice of Info object containing txs and metadata. // Tendermint RPC. It returns a slice of Info object containing txs and metadata.
// An error is returned if the query fails. // An error is returned if the query fails.
func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string, page, limit int) ([]Info, error) { func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string, page, limit int) ([]sdk.TxResponse, error) {
if len(tags) == 0 { 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")
} }
@ -152,9 +153,9 @@ func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string, page,
} }
// parse the indexed txs into an array of Info // parse the indexed txs into an array of Info
func FormatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]Info, error) { func FormatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]sdk.TxResponse, error) {
var err error var err error
out := make([]Info, len(res)) out := make([]sdk.TxResponse, len(res))
for i := range res { for i := range res {
out[i], err = formatTxResult(cdc, res[i]) out[i], err = formatTxResult(cdc, res[i])
if err != nil { if err != nil {
@ -172,31 +173,31 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var tags []string var tags []string
var page, limit int var page, limit int
var txs []Info var txs []sdk.TxResponse
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, sdk.AppendMsgToErr("could not parse query parameters", err.Error())) rest.WriteErrorResponse(w, http.StatusBadRequest, sdk.AppendMsgToErr("could not parse query parameters", err.Error()))
return return
} }
if len(r.Form) == 0 { if len(r.Form) == 0 {
utils.PostProcessResponse(w, cdc, txs, cliCtx.Indent) rest.PostProcessResponse(w, cdc, txs, cliCtx.Indent)
return return
} }
tags, page, limit, err = parseHTTPArgs(r) tags, page, limit, err = parseHTTPArgs(r)
if err != nil { if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
txs, err = SearchTxs(cliCtx, cdc, tags, page, limit) txs, err = SearchTxs(cliCtx, cdc, tags, page, limit)
if err != nil { if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }
utils.PostProcessResponse(w, cdc, txs, cliCtx.Indent) rest.PostProcessResponse(w, cdc, txs, cliCtx.Indent)
} }
} }

View File

@ -1,305 +0,0 @@
package utils
import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
)
//----------------------------------------
// Basic HTTP utilities
// WriteErrorResponse prepares and writes a HTTP error
// given a status code and an error message.
func WriteErrorResponse(w http.ResponseWriter, status int, err string) {
w.WriteHeader(status)
w.Write([]byte(err))
}
// WriteSimulationResponse prepares and writes an HTTP
// response for transactions simulations.
func WriteSimulationResponse(w http.ResponseWriter, gas uint64) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(fmt.Sprintf(`{"gas_estimate":%v}`, gas)))
}
// ParseInt64OrReturnBadRequest converts s to a int64 value.
func ParseInt64OrReturnBadRequest(w http.ResponseWriter, s string) (n int64, ok bool) {
var err error
n, err = strconv.ParseInt(s, 10, 64)
if err != nil {
err := fmt.Errorf("'%s' is not a valid int64", s)
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return n, false
}
return n, true
}
// ParseUint64OrReturnBadRequest converts s to a uint64 value.
func ParseUint64OrReturnBadRequest(w http.ResponseWriter, s string) (n uint64, ok bool) {
var err error
n, err = strconv.ParseUint(s, 10, 64)
if err != nil {
err := fmt.Errorf("'%s' is not a valid uint64", s)
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return n, false
}
return n, true
}
// ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a
// default value, defaultIfEmpty, if the string is empty.
func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEmpty float64) (n float64, ok bool) {
if len(s) == 0 {
return defaultIfEmpty, true
}
n, err := strconv.ParseFloat(s, 64)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return n, false
}
return n, true
}
// WriteGenerateStdTxResponse writes response for the generate_only mode.
func WriteGenerateStdTxResponse(w http.ResponseWriter, cdc *codec.Codec, txBldr authtxb.TxBuilder, msgs []sdk.Msg) {
stdMsg, err := txBldr.Build(msgs)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
output, err := cdc.MarshalJSON(auth.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo))
if err != nil {
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
w.Write(output)
return
}
//----------------------------------------
// Building / Sending utilities
// BaseReq defines a structure that can be embedded in other request structures
// that all share common "base" fields.
type BaseReq struct {
Name string `json:"name"`
Password string `json:"password"`
Memo string `json:"memo"`
ChainID string `json:"chain_id"`
AccountNumber uint64 `json:"account_number"`
Sequence uint64 `json:"sequence"`
Fees sdk.Coins `json:"fees"`
GasPrices sdk.DecCoins `json:"gas_prices"`
Gas string `json:"gas"`
GasAdjustment string `json:"gas_adjustment"`
GenerateOnly bool `json:"generate_only"`
Simulate bool `json:"simulate"`
}
// NewBaseReq creates a new basic request instance and sanitizes its values
func NewBaseReq(
name, password, memo, chainID string, gas, gasAdjustment string,
accNumber, seq uint64, fees sdk.Coins, gasPrices sdk.DecCoins, genOnly, simulate bool,
) BaseReq {
return BaseReq{
Name: strings.TrimSpace(name),
Password: password,
Memo: strings.TrimSpace(memo),
ChainID: strings.TrimSpace(chainID),
Fees: fees,
GasPrices: gasPrices,
Gas: strings.TrimSpace(gas),
GasAdjustment: strings.TrimSpace(gasAdjustment),
AccountNumber: accNumber,
Sequence: seq,
GenerateOnly: genOnly,
Simulate: simulate,
}
}
// Sanitize performs basic sanitization on a BaseReq object.
func (br BaseReq) Sanitize() BaseReq {
return NewBaseReq(
br.Name, br.Password, br.Memo, br.ChainID, br.Gas, br.GasAdjustment,
br.AccountNumber, br.Sequence, br.Fees, br.GasPrices, br.GenerateOnly, br.Simulate,
)
}
// ValidateBasic performs basic validation of a BaseReq. If custom validation
// logic is needed, the implementing request handler should perform those
// checks manually.
func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool {
if !br.GenerateOnly && !br.Simulate {
switch {
case len(br.Password) == 0:
WriteErrorResponse(w, http.StatusUnauthorized, "password required but not specified")
return false
case len(br.ChainID) == 0:
WriteErrorResponse(w, http.StatusUnauthorized, "chain-id required but not specified")
return false
case !br.Fees.IsZero() && !br.GasPrices.IsZero():
// both fees and gas prices were provided
WriteErrorResponse(w, http.StatusBadRequest, "cannot provide both fees and gas prices")
return false
case !br.Fees.IsValid() && !br.GasPrices.IsValid():
// neither fees or gas prices were provided
WriteErrorResponse(w, http.StatusPaymentRequired, "invalid fees or gas prices provided")
return false
}
}
if len(br.Name) == 0 {
WriteErrorResponse(w, http.StatusUnauthorized, "name required but not specified")
return false
}
return true
}
/*
ReadRESTReq is a simple convenience wrapper that reads the body and
unmarshals to the req interface.
Usage:
type SomeReq struct {
BaseReq `json:"base_req"`
CustomField string `json:"custom_field"`
}
req := new(SomeReq)
err := ReadRESTReq(w, r, cdc, req)
*/
func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req interface{}) error {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return err
}
err = cdc.UnmarshalJSON(body, req)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to decode JSON payload: %s", err))
return err
}
return nil
}
// CompleteAndBroadcastTxREST implements a utility function that facilitates
// sending a series of messages in a signed transaction given a TxBuilder and a
// QueryContext. It ensures that the account exists, has a proper number and
// sequence set. In addition, it builds and signs a transaction with the
// supplied messages. Finally, it broadcasts the signed transaction to a node.
//
// NOTE: Also see CompleteAndBroadcastTxCli.
// NOTE: Also see x/stake/client/rest/tx.go delegationsRequestHandlerFn.
func CompleteAndBroadcastTxREST(
w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext,
baseReq BaseReq, msgs []sdk.Msg, cdc *codec.Codec,
) {
gasAdjustment, ok := ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment)
if !ok {
return
}
simulateAndExecute, gas, err := client.ParseGas(baseReq.Gas)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
txBldr := authtxb.NewTxBuilder(
GetTxEncoder(cdc), baseReq.AccountNumber,
baseReq.Sequence, gas, gasAdjustment, baseReq.Simulate,
baseReq.ChainID, baseReq.Memo, baseReq.Fees, baseReq.GasPrices,
)
if baseReq.Simulate || simulateAndExecute {
if gasAdjustment < 0 {
WriteErrorResponse(w, http.StatusBadRequest, "gas adjustment must be a positive float")
return
}
txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, msgs)
if err != nil {
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
if baseReq.Simulate {
WriteSimulationResponse(w, txBldr.GetGas())
return
}
}
if baseReq.GenerateOnly {
WriteGenerateStdTxResponse(w, cdc, txBldr, msgs)
return
}
txBytes, err := txBldr.BuildAndSign(baseReq.Name, baseReq.Password, msgs)
if keyerror.IsErrKeyNotFound(err) {
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
} else if keyerror.IsErrWrongPassword(err) {
WriteErrorResponse(w, http.StatusUnauthorized, err.Error())
return
} else if err != nil {
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
res, err := cliCtx.BroadcastTx(txBytes)
if err != nil {
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
PostProcessResponse(w, cdc, res, cliCtx.Indent)
}
// PostProcessResponse performs post process for rest response
func PostProcessResponse(w http.ResponseWriter, cdc *codec.Codec, response interface{}, indent bool) {
var output []byte
switch response.(type) {
default:
var err error
if indent {
output, err = cdc.MarshalJSONIndent(response, "", " ")
} else {
output, err = cdc.MarshalJSON(response)
}
if err != nil {
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
case []byte:
output = response.([]byte)
}
w.Header().Set("Content-Type", "application/json")
w.Write(output)
}

View File

@ -3,9 +3,9 @@ package utils
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"os" "os"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
"github.com/tendermint/go-amino" "github.com/tendermint/go-amino"
@ -18,55 +18,73 @@ import (
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
) )
// CompleteAndBroadcastTxCli implements a utility function that facilitates // GasEstimateResponse defines a response definition for tx gas estimation.
type GasEstimateResponse struct {
GasEstimate uint64 `json:"gas_estimate"`
}
func (gr GasEstimateResponse) String() string {
return fmt.Sprintf("gas estimate: %d", gr.GasEstimate)
}
// GenerateOrBroadcastMsgs respects CLI flags and outputs a message
func GenerateOrBroadcastMsgs(cliCtx context.CLIContext, txBldr authtxb.TxBuilder, msgs []sdk.Msg, offline bool) error {
if cliCtx.GenerateOnly {
return PrintUnsignedStdTx(txBldr, cliCtx, msgs, offline)
}
return CompleteAndBroadcastTxCLI(txBldr, cliCtx, msgs)
}
// CompleteAndBroadcastTxCLI implements a utility function that facilitates
// sending a series of messages in a signed transaction given a TxBuilder and a // sending a series of messages in a signed transaction given a TxBuilder and a
// QueryContext. It ensures that the account exists, has a proper number and // QueryContext. It ensures that the account exists, has a proper number and
// sequence set. In addition, it builds and signs a transaction with the // sequence set. In addition, it builds and signs a transaction with the
// supplied messages. Finally, it broadcasts the signed transaction to a node. // supplied messages. Finally, it broadcasts the signed transaction to a node.
// //
// NOTE: Also see CompleteAndBroadcastTxREST. // NOTE: Also see CompleteAndBroadcastTxREST.
func CompleteAndBroadcastTxCli(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error { func CompleteAndBroadcastTxCLI(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error {
txBldr, err := prepareTxBuilder(txBldr, cliCtx) txBldr, err := PrepareTxBuilder(txBldr, cliCtx)
if err != nil { if err != nil {
return err return err
} }
name, err := cliCtx.GetFromName() fromName := cliCtx.GetFromName()
if err != nil {
return err
}
if txBldr.GetSimulateAndExecute() || cliCtx.Simulate { if txBldr.SimulateAndExecute() || cliCtx.Simulate {
txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs) txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs)
if err != nil { if err != nil {
return err return err
} }
fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.GetGas())
gasEst := GasEstimateResponse{GasEstimate: txBldr.Gas()}
fmt.Fprintf(os.Stderr, "%s\n", gasEst.String())
} }
if cliCtx.Simulate { if cliCtx.Simulate {
return nil return nil
} }
passphrase, err := keys.GetPassphrase(name) passphrase, err := keys.GetPassphrase(fromName)
if err != nil { if err != nil {
return err return err
} }
// build and sign the transaction // build and sign the transaction
txBytes, err := txBldr.BuildAndSign(name, passphrase, msgs) txBytes, err := txBldr.BuildAndSign(fromName, passphrase, msgs)
if err != nil { if err != nil {
return err return err
} }
// broadcast to a Tendermint node // broadcast to a Tendermint node
_, err = cliCtx.BroadcastTx(txBytes) res, err := cliCtx.BroadcastTx(txBytes)
cliCtx.PrintOutput(res)
return err return err
} }
// EnrichCtxWithGas calculates the gas estimate that would be consumed by the // EnrichWithGas calculates the gas estimate that would be consumed by the
// transaction and set the transaction's respective value accordingly. // transaction and set the transaction's respective value accordingly.
func EnrichCtxWithGas(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, msgs []sdk.Msg) (authtxb.TxBuilder, error) { func EnrichWithGas(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (authtxb.TxBuilder, error) {
_, adjusted, err := simulateMsgs(txBldr, cliCtx, name, msgs) _, adjusted, err := simulateMsgs(txBldr, cliCtx, msgs)
if err != nil { if err != nil {
return txBldr, err return txBldr, err
} }
@ -92,7 +110,7 @@ func CalculateGas(queryFunc func(string, common.HexBytes) ([]byte, error), cdc *
// PrintUnsignedStdTx builds an unsigned StdTx and prints it to os.Stdout. // PrintUnsignedStdTx builds an unsigned StdTx and prints it to os.Stdout.
// Don't perform online validation or lookups if offline is true. // Don't perform online validation or lookups if offline is true.
func PrintUnsignedStdTx(w io.Writer, txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg, offline bool) (err error) { func PrintUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg, offline bool) (err error) {
var stdTx auth.StdTx var stdTx auth.StdTx
if offline { if offline {
stdTx, err = buildUnsignedStdTxOffline(txBldr, cliCtx, msgs) stdTx, err = buildUnsignedStdTxOffline(txBldr, cliCtx, msgs)
@ -104,7 +122,7 @@ func PrintUnsignedStdTx(w io.Writer, txBldr authtxb.TxBuilder, cliCtx context.CL
} }
json, err := cliCtx.Codec.MarshalJSON(stdTx) json, err := cliCtx.Codec.MarshalJSON(stdTx)
if err == nil { if err == nil {
fmt.Fprintf(w, "%s\n", json) fmt.Fprintf(cliCtx.Output, "%s\n", json)
} }
return return
} }
@ -115,10 +133,7 @@ func PrintUnsignedStdTx(w io.Writer, txBldr authtxb.TxBuilder, cliCtx context.CL
func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, stdTx auth.StdTx, appendSig bool, offline bool) (auth.StdTx, error) { func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, stdTx auth.StdTx, appendSig bool, offline bool) (auth.StdTx, error) {
var signedStdTx auth.StdTx var signedStdTx auth.StdTx
keybase, err := keys.GetKeyBase() keybase := txBldr.Keybase()
if err != nil {
return signedStdTx, err
}
info, err := keybase.Get(name) info, err := keybase.Get(name)
if err != nil { if err != nil {
@ -129,8 +144,7 @@ func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string,
// check whether the address is a signer // check whether the address is a signer
if !isTxSigner(sdk.AccAddress(addr), stdTx.GetSigners()) { if !isTxSigner(sdk.AccAddress(addr), stdTx.GetSigners()) {
return signedStdTx, fmt.Errorf( return signedStdTx, fmt.Errorf("%s: %s", client.ErrInvalidSigner, name)
"The generated transaction's intended signer does not match the given signer: %q", name)
} }
if !offline { if !offline {
@ -158,8 +172,7 @@ func SignStdTxWithSignerAddress(txBldr authtxb.TxBuilder, cliCtx context.CLICont
// check whether the address is a signer // check whether the address is a signer
if !isTxSigner(addr, stdTx.GetSigners()) { if !isTxSigner(addr, stdTx.GetSigners()) {
return signedStdTx, fmt.Errorf( return signedStdTx, fmt.Errorf("%s: %s", client.ErrInvalidSigner, name)
"The generated transaction's intended signer does not match the given signer: %q", name)
} }
if !offline { if !offline {
@ -179,7 +192,7 @@ func SignStdTxWithSignerAddress(txBldr authtxb.TxBuilder, cliCtx context.CLICont
func populateAccountFromState(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, func populateAccountFromState(txBldr authtxb.TxBuilder, cliCtx context.CLIContext,
addr sdk.AccAddress) (authtxb.TxBuilder, error) { addr sdk.AccAddress) (authtxb.TxBuilder, error) {
if txBldr.GetAccountNumber() == 0 { if txBldr.AccountNumber() == 0 {
accNum, err := cliCtx.GetAccountNumber(addr) accNum, err := cliCtx.GetAccountNumber(addr)
if err != nil { if err != nil {
return txBldr, err return txBldr, err
@ -187,7 +200,7 @@ func populateAccountFromState(txBldr authtxb.TxBuilder, cliCtx context.CLIContex
txBldr = txBldr.WithAccountNumber(accNum) txBldr = txBldr.WithAccountNumber(accNum)
} }
if txBldr.GetSequence() == 0 { if txBldr.Sequence() == 0 {
accSeq, err := cliCtx.GetAccountSequence(addr) accSeq, err := cliCtx.GetAccountSequence(addr)
if err != nil { if err != nil {
return txBldr, err return txBldr, err
@ -210,12 +223,12 @@ func GetTxEncoder(cdc *codec.Codec) (encoder sdk.TxEncoder) {
// nolint // nolint
// SimulateMsgs simulates the transaction and returns the gas estimate and the adjusted value. // SimulateMsgs simulates the transaction and returns the gas estimate and the adjusted value.
func simulateMsgs(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, msgs []sdk.Msg) (estimated, adjusted uint64, err error) { func simulateMsgs(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (estimated, adjusted uint64, err error) {
txBytes, err := txBldr.BuildWithPubKey(name, msgs) txBytes, err := txBldr.BuildTxForSim(msgs)
if err != nil { if err != nil {
return return
} }
estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, txBldr.GetGasAdjustment()) estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, txBldr.GasAdjustment())
return return
} }
@ -231,19 +244,17 @@ func parseQueryResponse(cdc *amino.Codec, rawRes []byte) (uint64, error) {
return simulationResult.GasUsed, nil return simulationResult.GasUsed, nil
} }
func prepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (authtxb.TxBuilder, error) { // PrepareTxBuilder populates a TxBuilder in preparation for the build of a Tx.
func PrepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (authtxb.TxBuilder, error) {
if err := cliCtx.EnsureAccountExists(); err != nil { if err := cliCtx.EnsureAccountExists(); err != nil {
return txBldr, err return txBldr, err
} }
from, err := cliCtx.GetFromAddress() from := cliCtx.GetFromAddress()
if err != nil {
return txBldr, err
}
// TODO: (ref #1903) Allow for user supplied account number without // TODO: (ref #1903) Allow for user supplied account number without
// automatically doing a manual lookup. // automatically doing a manual lookup.
if txBldr.GetAccountNumber() == 0 { if txBldr.AccountNumber() == 0 {
accNum, err := cliCtx.GetAccountNumber(from) accNum, err := cliCtx.GetAccountNumber(from)
if err != nil { if err != nil {
return txBldr, err return txBldr, err
@ -253,7 +264,7 @@ func prepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (auth
// TODO: (ref #1903) Allow for user supplied account sequence without // TODO: (ref #1903) Allow for user supplied account sequence without
// automatically doing a manual lookup. // automatically doing a manual lookup.
if txBldr.GetSequence() == 0 { if txBldr.Sequence() == 0 {
accSeq, err := cliCtx.GetAccountSequence(from) accSeq, err := cliCtx.GetAccountSequence(from)
if err != nil { if err != nil {
return txBldr, err return txBldr, err
@ -266,7 +277,7 @@ func prepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (auth
// buildUnsignedStdTx builds a StdTx as per the parameters passed in the // buildUnsignedStdTx builds a StdTx as per the parameters passed in the
// contexts. Gas is automatically estimated if gas wanted is set to 0. // contexts. Gas is automatically estimated if gas wanted is set to 0.
func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) { func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) {
txBldr, err = prepareTxBuilder(txBldr, cliCtx) txBldr, err = PrepareTxBuilder(txBldr, cliCtx)
if err != nil { if err != nil {
return return
} }
@ -274,23 +285,20 @@ func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msg
} }
func buildUnsignedStdTxOffline(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) { func buildUnsignedStdTxOffline(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) {
if txBldr.GetSimulateAndExecute() { if txBldr.SimulateAndExecute() {
var name string txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs)
name, err = cliCtx.GetFromName()
if err != nil { if err != nil {
return return
} }
txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs) fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.Gas())
if err != nil {
return
}
fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.GetGas())
} }
stdSignMsg, err := txBldr.Build(msgs)
stdSignMsg, err := txBldr.BuildSignMsg(msgs)
if err != nil { if err != nil {
return return
} }
return auth.NewStdTx(stdSignMsg.Msgs, stdSignMsg.Fee, nil, stdSignMsg.Memo), nil return auth.NewStdTx(stdSignMsg.Msgs, stdSignMsg.Fee, nil, stdSignMsg.Memo), nil
} }
@ -300,5 +308,6 @@ func isTxSigner(user sdk.AccAddress, signers []sdk.AccAddress) bool {
return true return true
} }
} }
return false return false
} }

View File

@ -102,7 +102,11 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b
) )
// add handlers // add handlers
app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper) app.bankKeeper = bank.NewBaseKeeper(
app.accountKeeper,
app.paramsKeeper.Subspace(bank.DefaultParamspace),
bank.DefaultCodespace,
)
app.feeCollectionKeeper = auth.NewFeeCollectionKeeper( app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(
app.cdc, app.cdc,
app.keyFeeCollection, app.keyFeeCollection,
@ -160,11 +164,12 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b
// initialize BaseApp // initialize BaseApp
app.MountStores(app.keyMain, app.keyAccount, app.keyStaking, app.keyMint, app.keyDistr, app.MountStores(app.keyMain, app.keyAccount, app.keyStaking, app.keyMint, app.keyDistr,
app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams) app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams,
app.tkeyParams, app.tkeyStaking, app.tkeyDistr,
)
app.SetInitChainer(app.initChainer) app.SetInitChainer(app.initChainer)
app.SetBeginBlocker(app.BeginBlocker) app.SetBeginBlocker(app.BeginBlocker)
app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper)) app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper))
app.MountStoresTransient(app.tkeyParams, app.tkeyStaking, app.tkeyDistr)
app.SetEndBlocker(app.EndBlocker) app.SetEndBlocker(app.EndBlocker)
if loadLatest { if loadLatest {
@ -228,10 +233,7 @@ func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.R
// initialize store from a genesis state // initialize store from a genesis state
func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisState) []abci.ValidatorUpdate { func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisState) []abci.ValidatorUpdate {
// sort by account number to maintain consistency genesisState.Sanitize()
sort.Slice(genesisState.Accounts, func(i, j int) bool {
return genesisState.Accounts[i].AccountNumber < genesisState.Accounts[j].AccountNumber
})
// load the accounts // load the accounts
for _, gacc := range genesisState.Accounts { for _, gacc := range genesisState.Accounts {
@ -251,13 +253,13 @@ func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisSt
// initialize module-specific stores // initialize module-specific stores
auth.InitGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper, genesisState.AuthData) auth.InitGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper, genesisState.AuthData)
bank.InitGenesis(ctx, app.bankKeeper, genesisState.BankData)
slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakingData) slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakingData)
gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData) gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData)
mint.InitGenesis(ctx, app.mintKeeper, genesisState.MintData) mint.InitGenesis(ctx, app.mintKeeper, genesisState.MintData)
// validate genesis state // validate genesis state
err = GaiaValidateGenesisState(genesisState) if err := GaiaValidateGenesisState(genesisState); err != nil {
if err != nil {
panic(err) // TODO find a way to do this w/o panics panic(err) // TODO find a way to do this w/o panics
} }
@ -354,10 +356,6 @@ func (h StakingHooks) AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAdd
h.dh.AfterValidatorBonded(ctx, consAddr, valAddr) h.dh.AfterValidatorBonded(ctx, consAddr, valAddr)
h.sh.AfterValidatorBonded(ctx, consAddr, valAddr) h.sh.AfterValidatorBonded(ctx, consAddr, valAddr)
} }
func (h StakingHooks) AfterValidatorPowerDidChange(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) {
h.dh.AfterValidatorPowerDidChange(ctx, consAddr, valAddr)
h.sh.AfterValidatorPowerDidChange(ctx, consAddr, valAddr)
}
func (h StakingHooks) AfterValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { func (h StakingHooks) AfterValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) {
h.dh.AfterValidatorBeginUnbonding(ctx, consAddr, valAddr) h.dh.AfterValidatorBeginUnbonding(ctx, consAddr, valAddr)
h.sh.AfterValidatorBeginUnbonding(ctx, consAddr, valAddr) h.sh.AfterValidatorBeginUnbonding(ctx, consAddr, valAddr)

View File

@ -4,6 +4,8 @@ import (
"os" "os"
"testing" "testing"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
@ -28,6 +30,7 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error {
genesisState := NewGenesisState( genesisState := NewGenesisState(
genaccs, genaccs,
auth.DefaultGenesisState(), auth.DefaultGenesisState(),
bank.DefaultGenesisState(),
staking.DefaultGenesisState(), staking.DefaultGenesisState(),
mint.DefaultGenesisState(), mint.DefaultGenesisState(),
distr.DefaultGenesisState(), distr.DefaultGenesisState(),
@ -55,6 +58,6 @@ func TestGaiadExport(t *testing.T) {
// Making a new app object with the db, so that initchain hasn't been called // Making a new app object with the db, so that initchain hasn't been called
newGapp := NewGaiaApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true) newGapp := NewGaiaApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true)
_, _, err := newGapp.ExportAppStateAndValidators(false) _, _, err := newGapp.ExportAppStateAndValidators(false, []string{})
require.NoError(t, err, "ExportAppStateAndValidators should not have an error") require.NoError(t, err, "ExportAppStateAndValidators should not have an error")
} }

View File

@ -23,7 +23,7 @@ func ExampleTxSendSize() {
priv2 := secp256k1.GenPrivKeySecp256k1([]byte{1}) priv2 := secp256k1.GenPrivKeySecp256k1([]byte{1})
addr2 := sdk.AccAddress(priv2.PubKey().Address()) addr2 := sdk.AccAddress(priv2.PubKey().Address())
coins := sdk.Coins{sdk.NewCoin("denom", sdk.NewInt(10))} coins := sdk.Coins{sdk.NewCoin("denom", sdk.NewInt(10))}
msg1 := bank.MsgSend{ msg1 := bank.MsgMultiSend{
Inputs: []bank.Input{bank.NewInput(addr1, coins)}, Inputs: []bank.Input{bank.NewInput(addr1, coins)},
Outputs: []bank.Output{bank.NewOutput(addr2, coins)}, Outputs: []bank.Output{bank.NewOutput(addr2, coins)},
} }

View File

@ -2,6 +2,7 @@ package app
import ( import (
"encoding/json" "encoding/json"
"log"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
tmtypes "github.com/tendermint/tendermint/types" tmtypes "github.com/tendermint/tendermint/types"
@ -9,6 +10,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
distr "github.com/cosmos/cosmos-sdk/x/distribution" distr "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/mint"
@ -17,14 +19,14 @@ import (
) )
// export the state of gaia for a genesis file // export the state of gaia for a genesis file
func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) ( func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList []string) (
appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) {
// as if they could withdraw from the start of the next block // as if they could withdraw from the start of the next block
ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()})
if forZeroHeight { if forZeroHeight {
app.prepForZeroHeightGenesis(ctx) app.prepForZeroHeightGenesis(ctx, jailWhiteList)
} }
// iterate to get the accounts // iterate to get the accounts
@ -39,6 +41,7 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (
genState := NewGenesisState( genState := NewGenesisState(
accounts, accounts,
auth.ExportGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper), auth.ExportGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper),
bank.ExportGenesis(ctx, app.bankKeeper),
staking.ExportGenesis(ctx, app.stakingKeeper), staking.ExportGenesis(ctx, app.stakingKeeper),
mint.ExportGenesis(ctx, app.mintKeeper), mint.ExportGenesis(ctx, app.mintKeeper),
distr.ExportGenesis(ctx, app.distrKeeper), distr.ExportGenesis(ctx, app.distrKeeper),
@ -54,7 +57,23 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (
} }
// prepare for fresh start at zero height // prepare for fresh start at zero height
func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) { func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string) {
applyWhiteList := false
//Check if there is a whitelist
if len(jailWhiteList) > 0 {
applyWhiteList = true
}
whiteListMap := make(map[string]bool)
for _, addr := range jailWhiteList {
_, err := sdk.ValAddressFromBech32(addr)
if err != nil {
log.Fatal(err)
}
whiteListMap[addr] = true
}
/* Just to be safe, assert the invariants on current state. */ /* Just to be safe, assert the invariants on current state. */
app.assertRuntimeInvariantsOnContext(ctx) app.assertRuntimeInvariantsOnContext(ctx)
@ -131,9 +150,11 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) {
panic("expected validator, not found") panic("expected validator, not found")
} }
validator.BondHeight = 0
validator.UnbondingHeight = 0 validator.UnbondingHeight = 0
valConsAddrs = append(valConsAddrs, validator.ConsAddress()) valConsAddrs = append(valConsAddrs, validator.ConsAddress())
if applyWhiteList && !whiteListMap[addr.String()] {
validator.Jailed = true
}
app.stakingKeeper.SetValidator(ctx, validator) app.stakingKeeper.SetValidator(ctx, validator)
counter++ counter++
@ -141,6 +162,8 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) {
iter.Close() iter.Close()
_ = app.stakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx)
/* Handle slashing state. */ /* Handle slashing state. */
// reset start height on signing infos // reset start height on signing infos

View File

@ -9,6 +9,9 @@ import (
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
"time"
"github.com/cosmos/cosmos-sdk/x/bank"
tmtypes "github.com/tendermint/tendermint/types" tmtypes "github.com/tendermint/tendermint/types"
@ -20,20 +23,19 @@ import (
"github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/mint"
"github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/slashing"
"github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/staking"
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
) )
var ( var (
// bonded tokens given to genesis validators/accounts // bonded tokens given to genesis validators/accounts
freeFermionVal = int64(100) freeFermionsAcc = staking.TokensFromTendermintPower(150)
freeFermionsAcc = sdk.NewInt(150) bondDenom = staking.DefaultBondDenom
bondDenom = stakingTypes.DefaultBondDenom
) )
// State to Unmarshal // State to Unmarshal
type GenesisState struct { type GenesisState struct {
Accounts []GenesisAccount `json:"accounts"` Accounts []GenesisAccount `json:"accounts"`
AuthData auth.GenesisState `json:"auth"` AuthData auth.GenesisState `json:"auth"`
BankData bank.GenesisState `json:"bank"`
StakingData staking.GenesisState `json:"staking"` StakingData staking.GenesisState `json:"staking"`
MintData mint.GenesisState `json:"mint"` MintData mint.GenesisState `json:"mint"`
DistrData distr.GenesisState `json:"distr"` DistrData distr.GenesisState `json:"distr"`
@ -43,6 +45,7 @@ type GenesisState struct {
} }
func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState,
bankData bank.GenesisState,
stakingData staking.GenesisState, mintData mint.GenesisState, stakingData staking.GenesisState, mintData mint.GenesisState,
distrData distr.GenesisState, govData gov.GenesisState, distrData distr.GenesisState, govData gov.GenesisState,
slashingData slashing.GenesisState) GenesisState { slashingData slashing.GenesisState) GenesisState {
@ -50,6 +53,7 @@ func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState,
return GenesisState{ return GenesisState{
Accounts: accounts, Accounts: accounts,
AuthData: authData, AuthData: authData,
BankData: bankData,
StakingData: stakingData, StakingData: stakingData,
MintData: mintData, MintData: mintData,
DistrData: distrData, DistrData: distrData,
@ -58,6 +62,17 @@ func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState,
} }
} }
// Sanitize sorts accounts and coin sets.
func (gs GenesisState) Sanitize() {
sort.Slice(gs.Accounts, func(i, j int) bool {
return gs.Accounts[i].AccountNumber < gs.Accounts[j].AccountNumber
})
for _, acc := range gs.Accounts {
acc.Coins = acc.Coins.Sort()
}
}
// GenesisAccount defines an account initialized at genesis. // GenesisAccount defines an account initialized at genesis.
type GenesisAccount struct { type GenesisAccount struct {
Address sdk.AccAddress `json:"address"` Address sdk.AccAddress `json:"address"`
@ -69,8 +84,8 @@ type GenesisAccount struct {
OriginalVesting sdk.Coins `json:"original_vesting"` // total vesting coins upon initialization OriginalVesting sdk.Coins `json:"original_vesting"` // total vesting coins upon initialization
DelegatedFree sdk.Coins `json:"delegated_free"` // delegated vested coins at time of delegation DelegatedFree sdk.Coins `json:"delegated_free"` // delegated vested coins at time of delegation
DelegatedVesting sdk.Coins `json:"delegated_vesting"` // delegated vesting coins at time of delegation DelegatedVesting sdk.Coins `json:"delegated_vesting"` // delegated vesting coins at time of delegation
StartTime int64 `json:"start_time"` // vesting start time StartTime int64 `json:"start_time"` // vesting start time (UNIX Epoch time)
EndTime int64 `json:"end_time"` // vesting end time EndTime int64 `json:"end_time"` // vesting end time (UNIX Epoch time)
} }
func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount { func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount {
@ -190,6 +205,7 @@ func NewDefaultGenesisState() GenesisState {
return GenesisState{ return GenesisState{
Accounts: nil, Accounts: nil,
AuthData: auth.DefaultGenesisState(), AuthData: auth.DefaultGenesisState(),
BankData: bank.DefaultGenesisState(),
StakingData: staking.DefaultGenesisState(), StakingData: staking.DefaultGenesisState(),
MintData: mint.DefaultGenesisState(), MintData: mint.DefaultGenesisState(),
DistrData: distr.DefaultGenesisState(), DistrData: distr.DefaultGenesisState(),
@ -216,6 +232,9 @@ func GaiaValidateGenesisState(genesisState GenesisState) error {
if err := auth.ValidateGenesis(genesisState.AuthData); err != nil { if err := auth.ValidateGenesis(genesisState.AuthData); err != nil {
return err return err
} }
if err := bank.ValidateGenesis(genesisState.BankData); err != nil {
return err
}
if err := staking.ValidateGenesis(genesisState.StakingData); err != nil { if err := staking.ValidateGenesis(genesisState.StakingData); err != nil {
return err return err
} }
@ -232,17 +251,38 @@ func GaiaValidateGenesisState(genesisState GenesisState) error {
return slashing.ValidateGenesis(genesisState.SlashingData) return slashing.ValidateGenesis(genesisState.SlashingData)
} }
// Ensures that there are no duplicate accounts in the genesis state, // validateGenesisStateAccounts performs validation of genesis accounts. It
// ensures that there are no duplicate accounts in the genesis state and any
// provided vesting accounts are valid.
func validateGenesisStateAccounts(accs []GenesisAccount) error { func validateGenesisStateAccounts(accs []GenesisAccount) error {
addrMap := make(map[string]bool, len(accs)) addrMap := make(map[string]bool, len(accs))
for i := 0; i < len(accs); i++ { for _, acc := range accs {
acc := accs[i] addrStr := acc.Address.String()
strAddr := string(acc.Address)
if _, ok := addrMap[strAddr]; ok { // disallow any duplicate accounts
return fmt.Errorf("Duplicate account in genesis state: Address %v", acc.Address) if _, ok := addrMap[addrStr]; ok {
return fmt.Errorf("duplicate account found in genesis state; address: %s", addrStr)
} }
addrMap[strAddr] = true
// validate any vesting fields
if !acc.OriginalVesting.IsZero() {
if acc.EndTime == 0 {
return fmt.Errorf("missing end time for vesting account; address: %s", addrStr)
}
if acc.StartTime >= acc.EndTime {
return fmt.Errorf(
"vesting start time must before end time; address: %s, start: %s, end: %s",
addrStr,
time.Unix(acc.StartTime, 0).UTC().Format(time.RFC3339),
time.Unix(acc.EndTime, 0).UTC().Format(time.RFC3339),
)
}
}
addrMap[addrStr] = true
} }
return nil return nil
} }

View File

@ -5,17 +5,16 @@ import (
"testing" "testing"
"time" "time"
"github.com/tendermint/tendermint/crypto/secp256k1"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/secp256k1"
tmtypes "github.com/tendermint/tendermint/types"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/staking"
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
) )
var ( var (
@ -104,41 +103,86 @@ func TestGaiaAppGenState(t *testing.T) {
func makeMsg(name string, pk crypto.PubKey) auth.StdTx { func makeMsg(name string, pk crypto.PubKey) auth.StdTx {
desc := staking.NewDescription(name, "", "", "") desc := staking.NewDescription(name, "", "", "")
comm := stakingTypes.CommissionMsg{} comm := staking.CommissionMsg{}
msg := staking.NewMsgCreateValidator(sdk.ValAddress(pk.Address()), pk, sdk.NewInt64Coin(bondDenom, msg := staking.NewMsgCreateValidator(sdk.ValAddress(pk.Address()), pk, sdk.NewInt64Coin(bondDenom,
50), desc, comm) 50), desc, comm, sdk.OneInt())
return auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, nil, "") return auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, nil, "")
} }
func TestGaiaGenesisValidation(t *testing.T) { func TestGaiaGenesisValidation(t *testing.T) {
genTxs := make([]auth.StdTx, 2) genTxs := []auth.StdTx{makeMsg("test-0", pk1), makeMsg("test-1", pk2)}
// Test duplicate accounts fails dupGenTxs := []auth.StdTx{makeMsg("test-0", pk1), makeMsg("test-1", pk1)}
genTxs[0] = makeMsg("test-0", pk1)
genTxs[1] = makeMsg("test-1", pk1) // require duplicate accounts fails validation
genesisState := makeGenesisState(t, genTxs) genesisState := makeGenesisState(t, dupGenTxs)
err := GaiaValidateGenesisState(genesisState) err := GaiaValidateGenesisState(genesisState)
require.NotNil(t, err) require.Error(t, err)
// Test bonded + jailed validator fails
// require invalid vesting account fails validation (invalid end time)
genesisState = makeGenesisState(t, genTxs) genesisState = makeGenesisState(t, genTxs)
val1 := stakingTypes.NewValidator(addr1, pk1, stakingTypes.Description{Moniker: "test #2"}) genesisState.Accounts[0].OriginalVesting = genesisState.Accounts[0].Coins
err = GaiaValidateGenesisState(genesisState)
require.Error(t, err)
genesisState.Accounts[0].StartTime = 1548888000
genesisState.Accounts[0].EndTime = 1548775410
err = GaiaValidateGenesisState(genesisState)
require.Error(t, err)
// require bonded + jailed validator fails validation
genesisState = makeGenesisState(t, genTxs)
val1 := staking.NewValidator(addr1, pk1, staking.NewDescription("test #2", "", "", ""))
val1.Jailed = true val1.Jailed = true
val1.Status = sdk.Bonded val1.Status = sdk.Bonded
genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1) genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1)
err = GaiaValidateGenesisState(genesisState) err = GaiaValidateGenesisState(genesisState)
require.NotNil(t, err) require.Error(t, err)
// Test duplicate validator fails
// require duplicate validator fails validation
val1.Jailed = false val1.Jailed = false
genesisState = makeGenesisState(t, genTxs) genesisState = makeGenesisState(t, genTxs)
val2 := stakingTypes.NewValidator(addr1, pk1, stakingTypes.Description{Moniker: "test #3"}) val2 := staking.NewValidator(addr1, pk1, staking.NewDescription("test #3", "", "", ""))
genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1) genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1)
genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val2) genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val2)
err = GaiaValidateGenesisState(genesisState) err = GaiaValidateGenesisState(genesisState)
require.NotNil(t, err) require.Error(t, err)
} }
func TestNewDefaultGenesisAccount(t *testing.T) { func TestNewDefaultGenesisAccount(t *testing.T) {
addr := secp256k1.GenPrivKeySecp256k1([]byte("")).PubKey().Address() addr := secp256k1.GenPrivKeySecp256k1([]byte("")).PubKey().Address()
acc := NewDefaultGenesisAccount(sdk.AccAddress(addr)) acc := NewDefaultGenesisAccount(sdk.AccAddress(addr))
require.Equal(t, sdk.NewInt(1000), acc.Coins.AmountOf("footoken")) require.Equal(t, sdk.NewInt(1000), acc.Coins.AmountOf("footoken"))
require.Equal(t, sdk.NewInt(150), acc.Coins.AmountOf(bondDenom)) require.Equal(t, staking.TokensFromTendermintPower(150), acc.Coins.AmountOf(bondDenom))
}
func TestGenesisStateSanitize(t *testing.T) {
genesisState := makeGenesisState(t, nil)
require.Nil(t, GaiaValidateGenesisState(genesisState))
addr1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address())
authAcc1 := auth.NewBaseAccountWithAddress(addr1)
authAcc1.SetCoins(sdk.Coins{
sdk.NewInt64Coin("bcoin", 150),
sdk.NewInt64Coin("acoin", 150),
})
authAcc1.SetAccountNumber(1)
genAcc1 := NewGenesisAccount(&authAcc1)
addr2 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address())
authAcc2 := auth.NewBaseAccountWithAddress(addr2)
authAcc2.SetCoins(sdk.Coins{
sdk.NewInt64Coin("acoin", 150),
sdk.NewInt64Coin("bcoin", 150),
})
genAcc2 := NewGenesisAccount(&authAcc2)
genesisState.Accounts = []GenesisAccount{genAcc1, genAcc2}
require.True(t, genesisState.Accounts[0].AccountNumber > genesisState.Accounts[1].AccountNumber)
require.Equal(t, genesisState.Accounts[0].Coins[0].Denom, "bcoin")
require.Equal(t, genesisState.Accounts[0].Coins[1].Denom, "acoin")
require.Equal(t, genesisState.Accounts[1].Address, addr2)
genesisState.Sanitize()
require.False(t, genesisState.Accounts[0].AccountNumber > genesisState.Accounts[1].AccountNumber)
require.Equal(t, genesisState.Accounts[1].Address, addr1)
require.Equal(t, genesisState.Accounts[1].Coins[0].Denom, "acoin")
require.Equal(t, genesisState.Accounts[1].Coins[1].Denom, "bcoin")
} }

View File

@ -38,5 +38,5 @@ func (app *GaiaApp) assertRuntimeInvariantsOnContext(ctx sdk.Context) {
} }
end := time.Now() end := time.Now()
diff := end.Sub(start) diff := end.Sub(start)
app.BaseApp.Logger.With("module", "invariants").Info("Asserted all invariants", "duration", diff) app.BaseApp.Logger().With("module", "invariants").Info("Asserted all invariants", "duration", diff)
} }

View File

@ -13,13 +13,16 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/secp256k1"
dbm "github.com/tendermint/tendermint/libs/db" dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
authsim "github.com/cosmos/cosmos-sdk/x/auth/simulation" authsim "github.com/cosmos/cosmos-sdk/x/auth/simulation"
"github.com/cosmos/cosmos-sdk/x/bank"
banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation" banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation"
distr "github.com/cosmos/cosmos-sdk/x/distribution" distr "github.com/cosmos/cosmos-sdk/x/distribution"
distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation" distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation"
@ -31,20 +34,21 @@ import (
slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation" slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation"
"github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/staking"
stakingsim "github.com/cosmos/cosmos-sdk/x/staking/simulation" stakingsim "github.com/cosmos/cosmos-sdk/x/staking/simulation"
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
) )
var ( var (
seed int64 genesisFile string
numBlocks int seed int64
blockSize int numBlocks int
enabled bool blockSize int
verbose bool enabled bool
commit bool verbose bool
period int commit bool
period int
) )
func init() { func init() {
flag.StringVar(&genesisFile, "SimulationGenesis", "", "Custom simulation genesis file")
flag.Int64Var(&seed, "SimulationSeed", 42, "Simulation random seed") flag.Int64Var(&seed, "SimulationSeed", 42, "Simulation random seed")
flag.IntVar(&numBlocks, "SimulationNumBlocks", 500, "Number of blocks") flag.IntVar(&numBlocks, "SimulationNumBlocks", 500, "Number of blocks")
flag.IntVar(&blockSize, "SimulationBlockSize", 200, "Operations per block") flag.IntVar(&blockSize, "SimulationBlockSize", 200, "Operations per block")
@ -54,10 +58,34 @@ func init() {
flag.IntVar(&period, "SimulationPeriod", 1, "Run slow invariants only once every period assertions") flag.IntVar(&period, "SimulationPeriod", 1, "Run slow invariants only once every period assertions")
} }
func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) json.RawMessage { func appStateFromGenesisFileFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) (json.RawMessage, []simulation.Account, string) {
var genesis tmtypes.GenesisDoc
cdc := MakeCodec()
bytes, err := ioutil.ReadFile(genesisFile)
if err != nil {
panic(err)
}
cdc.MustUnmarshalJSON(bytes, &genesis)
var appState GenesisState
cdc.MustUnmarshalJSON(genesis.AppState, &appState)
var newAccs []simulation.Account
for _, acc := range appState.Accounts {
// Pick a random private key, since we don't know the actual key
// This should be fine as it's only used for mock Tendermint validators
// and these keys are never actually used to sign by mock Tendermint.
privkeySeed := make([]byte, 15)
r.Read(privkeySeed)
privKey := secp256k1.GenPrivKeySecp256k1(privkeySeed)
newAccs = append(newAccs, simulation.Account{privKey, privKey.PubKey(), acc.Address})
}
return genesis.AppState, newAccs, genesis.ChainID
}
func appStateRandomizedFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) (json.RawMessage, []simulation.Account, string) {
var genesisAccounts []GenesisAccount var genesisAccounts []GenesisAccount
amount := int64(r.Intn(1e6)) amount := int64(r.Intn(1e12))
numInitiallyBonded := int64(r.Intn(250)) numInitiallyBonded := int64(r.Intn(250))
numAccs := int64(len(accs)) numAccs := int64(len(accs))
if numInitiallyBonded > numAccs { if numInitiallyBonded > numAccs {
@ -69,7 +97,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
// randomly generate some genesis accounts // randomly generate some genesis accounts
for i, acc := range accs { for i, acc := range accs {
coins := sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))} coins := sdk.Coins{sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(amount))}
bacc := auth.NewBaseAccountWithAddress(acc.Address) bacc := auth.NewBaseAccountWithAddress(acc.Address)
bacc.SetCoins(coins) bacc.SetCoins(coins)
@ -109,21 +137,24 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
authGenesis := auth.GenesisState{ authGenesis := auth.GenesisState{
Params: auth.Params{ Params: auth.Params{
MemoCostPerByte: uint64(r.Intn(10) + 1), MaxMemoCharacters: uint64(randIntBetween(r, 100, 200)),
MaxMemoCharacters: uint64(r.Intn(200-100) + 100),
TxSigLimit: uint64(r.Intn(7) + 1), TxSigLimit: uint64(r.Intn(7) + 1),
SigVerifyCostED25519: uint64(r.Intn(1000-500) + 500), TxSizeCostPerByte: uint64(randIntBetween(r, 5, 15)),
SigVerifyCostSecp256k1: uint64(r.Intn(1000-500) + 500), SigVerifyCostED25519: uint64(randIntBetween(r, 500, 1000)),
SigVerifyCostSecp256k1: uint64(randIntBetween(r, 500, 1000)),
}, },
} }
fmt.Printf("Selected randomly generated auth parameters:\n\t%+v\n", authGenesis) fmt.Printf("Selected randomly generated auth parameters:\n\t%+v\n", authGenesis)
bankGenesis := bank.NewGenesisState(r.Int63n(2) == 0)
fmt.Printf("Selected randomly generated bank parameters:\n\t%+v\n", bankGenesis)
// Random genesis states // Random genesis states
vp := time.Duration(r.Intn(2*172800)) * time.Second vp := time.Duration(r.Intn(2*172800)) * time.Second
govGenesis := gov.GenesisState{ govGenesis := gov.GenesisState{
StartingProposalID: uint64(r.Intn(100)), StartingProposalID: uint64(r.Intn(100)),
DepositParams: gov.DepositParams{ DepositParams: gov.DepositParams{
MinDeposit: sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, int64(r.Intn(1e3)))}, MinDeposit: sdk.Coins{sdk.NewInt64Coin(staking.DefaultBondDenom, int64(r.Intn(1e3)))},
MaxDepositPeriod: vp, MaxDepositPeriod: vp,
}, },
VotingParams: gov.VotingParams{ VotingParams: gov.VotingParams{
@ -142,7 +173,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
Params: staking.Params{ Params: staking.Params{
UnbondingTime: time.Duration(randIntBetween(r, 60, 60*60*24*3*2)) * time.Second, UnbondingTime: time.Duration(randIntBetween(r, 60, 60*60*24*3*2)) * time.Second,
MaxValidators: uint16(r.Intn(250)), MaxValidators: uint16(r.Intn(250)),
BondDenom: stakingTypes.DefaultBondDenom, BondDenom: staking.DefaultBondDenom,
}, },
} }
fmt.Printf("Selected randomly generated staking parameters:\n\t%+v\n", stakingGenesis) fmt.Printf("Selected randomly generated staking parameters:\n\t%+v\n", stakingGenesis)
@ -163,7 +194,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
Minter: mint.InitialMinter( Minter: mint.InitialMinter(
sdk.NewDecWithPrec(int64(r.Intn(99)), 2)), sdk.NewDecWithPrec(int64(r.Intn(99)), 2)),
Params: mint.NewParams( Params: mint.NewParams(
stakingTypes.DefaultBondDenom, staking.DefaultBondDenom,
sdk.NewDecWithPrec(int64(r.Intn(99)), 2), sdk.NewDecWithPrec(int64(r.Intn(99)), 2),
sdk.NewDecWithPrec(20, 2), sdk.NewDecWithPrec(20, 2),
sdk.NewDecWithPrec(7, 2), sdk.NewDecWithPrec(7, 2),
@ -203,6 +234,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
genesis := GenesisState{ genesis := GenesisState{
Accounts: genesisAccounts, Accounts: genesisAccounts,
AuthData: authGenesis, AuthData: authGenesis,
BankData: bankGenesis,
StakingData: stakingGenesis, StakingData: stakingGenesis,
MintData: mintGenesis, MintData: mintGenesis,
DistrData: distrGenesis, DistrData: distrGenesis,
@ -216,7 +248,14 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
panic(err) panic(err)
} }
return appState return appState, accs, "simulation"
}
func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) (json.RawMessage, []simulation.Account, string) {
if genesisFile != "" {
return appStateFromGenesisFileFn(r, accs, genesisTimestamp)
}
return appStateRandomizedFn(r, accs, genesisTimestamp)
} }
func randIntBetween(r *rand.Rand, min, max int) int { func randIntBetween(r *rand.Rand, min, max int) int {
@ -226,7 +265,8 @@ func randIntBetween(r *rand.Rand, min, max int) int {
func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation { func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation {
return []simulation.WeightedOperation{ return []simulation.WeightedOperation{
{5, authsim.SimulateDeductFee(app.accountKeeper, app.feeCollectionKeeper)}, {5, authsim.SimulateDeductFee(app.accountKeeper, app.feeCollectionKeeper)},
{100, banksim.SingleInputSendMsg(app.accountKeeper, app.bankKeeper)}, {100, banksim.SendMsg(app.accountKeeper, app.bankKeeper)},
{10, banksim.SingleInputMsgMultiSend(app.accountKeeper, app.bankKeeper)},
{50, distrsim.SimulateMsgSetWithdrawAddress(app.accountKeeper, app.distrKeeper)}, {50, distrsim.SimulateMsgSetWithdrawAddress(app.accountKeeper, app.distrKeeper)},
{50, distrsim.SimulateMsgWithdrawDelegatorReward(app.accountKeeper, app.distrKeeper)}, {50, distrsim.SimulateMsgWithdrawDelegatorReward(app.accountKeeper, app.distrKeeper)},
{50, distrsim.SimulateMsgWithdrawValidatorCommission(app.accountKeeper, app.distrKeeper)}, {50, distrsim.SimulateMsgWithdrawValidatorCommission(app.accountKeeper, app.distrKeeper)},
@ -261,8 +301,8 @@ func fauxMerkleModeOpt(bapp *baseapp.BaseApp) {
// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$ -SimulationCommit=true -cpuprofile cpu.out // /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$ -SimulationCommit=true -cpuprofile cpu.out
func BenchmarkFullGaiaSimulation(b *testing.B) { func BenchmarkFullGaiaSimulation(b *testing.B) {
// Setup Gaia application // Setup Gaia application
var logger log.Logger logger := log.NewNopLogger()
logger = log.NewNopLogger()
var db dbm.DB var db dbm.DB
dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim")
db, _ = dbm.NewGoLevelDB("Simulation", dir) db, _ = dbm.NewGoLevelDB("Simulation", dir)
@ -376,11 +416,8 @@ func TestGaiaImportExport(t *testing.T) {
fmt.Printf("Exporting genesis...\n") fmt.Printf("Exporting genesis...\n")
appState, _, err := app.ExportAppStateAndValidators(false) appState, _, err := app.ExportAppStateAndValidators(false, []string{})
if err != nil { require.NoError(t, err)
panic(err)
}
fmt.Printf("Importing genesis...\n") fmt.Printf("Importing genesis...\n")
newDir, _ := ioutil.TempDir("", "goleveldb-gaia-sim-2") newDir, _ := ioutil.TempDir("", "goleveldb-gaia-sim-2")
@ -480,7 +517,7 @@ func TestGaiaSimulationAfterImport(t *testing.T) {
fmt.Printf("Exporting genesis...\n") fmt.Printf("Exporting genesis...\n")
appState, _, err := app.ExportAppStateAndValidators(true) appState, _, err := app.ExportAppStateAndValidators(true, []string{})
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -1,12 +1,14 @@
package clitest package clitest
import ( import (
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"time" "time"
@ -14,15 +16,13 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/tests" "github.com/cosmos/cosmos-sdk/tests"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/staking"
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
) )
func TestGaiaCLIKeysAddMultisig(t *testing.T) { func TestGaiaCLIKeysAddMultisig(t *testing.T) {
@ -45,6 +45,31 @@ func TestGaiaCLIKeysAddMultisig(t *testing.T) {
require.NotEqual(t, f.KeysShow("msig3").Address, f.KeysShow("msig4").Address) require.NotEqual(t, f.KeysShow("msig3").Address, f.KeysShow("msig4").Address)
} }
func TestGaiaCLIKeysAddRecover(t *testing.T) {
t.Parallel()
f := InitFixtures(t)
f.KeysAddRecover("test-recover", "dentist task convince chimney quality leave banana trade firm crawl eternal easily")
require.Equal(t, "cosmos1qcfdf69js922qrdr4yaww3ax7gjml6pdds46f4", f.KeyAddress("test-recover").String())
}
func TestGaiaCLIKeysAddRecoverHDPath(t *testing.T) {
t.Parallel()
f := InitFixtures(t)
f.KeysAddRecoverHDPath("test-recoverHD1", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 0, 0)
require.Equal(t, "cosmos1qcfdf69js922qrdr4yaww3ax7gjml6pdds46f4", f.KeyAddress("test-recoverHD1").String())
f.KeysAddRecoverHDPath("test-recoverH2", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 1, 5)
require.Equal(t, "cosmos1pdfav2cjhry9k79nu6r8kgknnjtq6a7rykmafy", f.KeyAddress("test-recoverH2").String())
f.KeysAddRecoverHDPath("test-recoverH3", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 1, 17)
require.Equal(t, "cosmos1909k354n6wl8ujzu6kmh49w4d02ax7qvlkv4sn", f.KeyAddress("test-recoverH3").String())
f.KeysAddRecoverHDPath("test-recoverH4", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 2, 17)
require.Equal(t, "cosmos1v9plmhvyhgxk3th9ydacm7j4z357s3nhtwsjat", f.KeyAddress("test-recoverH4").String())
}
func TestGaiaCLIMinimumFees(t *testing.T) { func TestGaiaCLIMinimumFees(t *testing.T) {
t.Parallel() t.Parallel()
f := InitFixtures(t) f := InitFixtures(t)
@ -67,13 +92,13 @@ func TestGaiaCLIMinimumFees(t *testing.T) {
tests.WaitForNextNBlocksTM(1, f.Port) tests.WaitForNextNBlocksTM(1, f.Port)
// Ensure tx w/ correct fees pass // Ensure tx w/ correct fees pass
txFees := fmt.Sprintf("--fees=%s,%s", sdk.NewInt64Coin(feeDenom, 2), sdk.NewInt64Coin(fee2Denom, 2)) txFees := fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2))
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10), txFees) success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10), txFees)
require.True(f.T, success) require.True(f.T, success)
tests.WaitForNextNBlocksTM(1, f.Port) tests.WaitForNextNBlocksTM(1, f.Port)
// Ensure tx w/ improper fees fails // Ensure tx w/ improper fees fails
txFees = fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 5)) txFees = fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 1))
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 10), txFees) success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 10), txFees)
require.False(f.T, success) require.False(f.T, success)
@ -144,8 +169,9 @@ func TestGaiaCLIFeesDeduction(t *testing.T) {
require.Equal(t, fooAmt.Int64(), fooAcc.GetCoins().AmountOf(fooDenom).Int64()) require.Equal(t, fooAmt.Int64(), fooAcc.GetCoins().AmountOf(fooDenom).Int64())
// insufficient funds (coins + fees) tx fails // insufficient funds (coins + fees) tx fails
largeCoins := staking.TokensFromTendermintPower(10000000)
success, _, _ = f.TxSend( success, _, _ = f.TxSend(
keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 10000000), keyFoo, barAddr, sdk.NewCoin(fooDenom, largeCoins),
fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2))) fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)))
require.False(t, success) require.False(t, success)
@ -178,45 +204,47 @@ func TestGaiaCLISend(t *testing.T) {
barAddr := f.KeyAddress(keyBar) barAddr := f.KeyAddress(keyBar)
fooAcc := f.QueryAccount(fooAddr) fooAcc := f.QueryAccount(fooAddr)
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64()) startTokens := staking.TokensFromTendermintPower(50)
require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom))
// Send some tokens from one account to the other // Send some tokens from one account to the other
f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10)) sendTokens := staking.TokensFromTendermintPower(10)
f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens))
tests.WaitForNextNBlocksTM(1, f.Port) tests.WaitForNextNBlocksTM(1, f.Port)
// Ensure account balances match expected // Ensure account balances match expected
barAcc := f.QueryAccount(barAddr) barAcc := f.QueryAccount(barAddr)
require.Equal(t, int64(10), barAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom))
fooAcc = f.QueryAccount(fooAddr) fooAcc = f.QueryAccount(fooAddr)
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom))
// Test --dry-run // Test --dry-run
success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--dry-run") success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--dry-run")
require.True(t, success) require.True(t, success)
// Check state didn't change // Check state didn't change
fooAcc = f.QueryAccount(fooAddr) fooAcc = f.QueryAccount(fooAddr)
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom))
// test autosequencing // test autosequencing
f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10)) f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens))
tests.WaitForNextNBlocksTM(1, f.Port) tests.WaitForNextNBlocksTM(1, f.Port)
// Ensure account balances match expected // Ensure account balances match expected
barAcc = f.QueryAccount(barAddr) barAcc = f.QueryAccount(barAddr)
require.Equal(t, int64(20), barAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, sendTokens.MulRaw(2), barAcc.GetCoins().AmountOf(denom))
fooAcc = f.QueryAccount(fooAddr) fooAcc = f.QueryAccount(fooAddr)
require.Equal(t, int64(30), fooAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, startTokens.Sub(sendTokens.MulRaw(2)), fooAcc.GetCoins().AmountOf(denom))
// test memo // test memo
f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--memo='testmemo'") f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--memo='testmemo'")
tests.WaitForNextNBlocksTM(1, f.Port) tests.WaitForNextNBlocksTM(1, f.Port)
// Ensure account balances match expected // Ensure account balances match expected
barAcc = f.QueryAccount(barAddr) barAcc = f.QueryAccount(barAddr)
require.Equal(t, int64(30), barAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, sendTokens.MulRaw(3), barAcc.GetCoins().AmountOf(denom))
fooAcc = f.QueryAccount(fooAddr) fooAcc = f.QueryAccount(fooAddr)
require.Equal(t, int64(20), fooAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, startTokens.Sub(sendTokens.MulRaw(3)), fooAcc.GetCoins().AmountOf(denom))
f.Cleanup() f.Cleanup()
} }
@ -233,50 +261,48 @@ func TestGaiaCLIGasAuto(t *testing.T) {
barAddr := f.KeyAddress(keyBar) barAddr := f.KeyAddress(keyBar)
fooAcc := f.QueryAccount(fooAddr) fooAcc := f.QueryAccount(fooAddr)
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64()) startTokens := staking.TokensFromTendermintPower(50)
require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom))
// Test failure with auto gas disabled and very little gas set by hand // Test failure with auto gas disabled and very little gas set by hand
success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=10") sendTokens := staking.TokensFromTendermintPower(10)
success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=10")
require.False(t, success) require.False(t, success)
// Check state didn't change // Check state didn't change
fooAcc = f.QueryAccount(fooAddr) fooAcc = f.QueryAccount(fooAddr)
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom))
// Test failure with negative gas // Test failure with negative gas
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=-100") success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=-100")
require.False(t, success) require.False(t, success)
// Check state didn't change // Check state didn't change
fooAcc = f.QueryAccount(fooAddr) fooAcc = f.QueryAccount(fooAddr)
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom))
// Test failure with 0 gas // Test failure with 0 gas
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=0") success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=0")
require.False(t, success) require.False(t, success)
// Check state didn't change // Check state didn't change
fooAcc = f.QueryAccount(fooAddr) fooAcc = f.QueryAccount(fooAddr)
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom))
// Enable auto gas // Enable auto gas
success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=auto") success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=auto")
require.NotEmpty(t, stderr) require.NotEmpty(t, stderr)
require.True(t, success) require.True(t, success)
cdc := app.MakeCodec() cdc := app.MakeCodec()
sendResp := struct { sendResp := sdk.TxResponse{}
Height int64
TxHash string
Response abci.ResponseDeliverTx
}{}
err := cdc.UnmarshalJSON([]byte(stdout), &sendResp) err := cdc.UnmarshalJSON([]byte(stdout), &sendResp)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, sendResp.Response.GasWanted, sendResp.Response.GasUsed) require.True(t, sendResp.GasWanted >= sendResp.GasUsed)
tests.WaitForNextNBlocksTM(1, f.Port) tests.WaitForNextNBlocksTM(1, f.Port)
// Check state has changed accordingly // Check state has changed accordingly
fooAcc = f.QueryAccount(fooAddr) fooAcc = f.QueryAccount(fooAddr)
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom))
f.Cleanup() f.Cleanup()
} }
@ -289,23 +315,17 @@ func TestGaiaCLICreateValidator(t *testing.T) {
proc := f.GDStart() proc := f.GDStart()
defer proc.Stop(false) defer proc.Stop(false)
fooAddr := f.KeyAddress(keyFoo)
barAddr := f.KeyAddress(keyBar) barAddr := f.KeyAddress(keyBar)
barVal := sdk.ValAddress(barAddr) barVal := sdk.ValAddress(barAddr)
consPubKey := sdk.MustBech32ifyConsPub(ed25519.GenPrivKey().PubKey()) consPubKey := sdk.MustBech32ifyConsPub(ed25519.GenPrivKey().PubKey())
f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10)) sendTokens := staking.TokensFromTendermintPower(10)
f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens))
tests.WaitForNextNBlocksTM(1, f.Port) tests.WaitForNextNBlocksTM(1, f.Port)
barAcc := f.QueryAccount(barAddr) barAcc := f.QueryAccount(barAddr)
require.Equal(t, int64(10), barAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom))
fooAcc := f.QueryAccount(fooAddr)
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64())
defaultParams := staking.DefaultParams()
initialPool := staking.InitialPool()
initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewInt(101)) // Delegate tx on GaiaAppGenState
// Generate a create validator transaction and ensure correctness // Generate a create validator transaction and ensure correctness
success, stdout, stderr := f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewInt64Coin(denom, 2), "--generate-only") success, stdout, stderr := f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewInt64Coin(denom, 2), "--generate-only")
@ -318,21 +338,22 @@ func TestGaiaCLICreateValidator(t *testing.T) {
require.Equal(t, 0, len(msg.GetSignatures())) require.Equal(t, 0, len(msg.GetSignatures()))
// Test --dry-run // Test --dry-run
success, _, _ = f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewInt64Coin(denom, 2), "--dry-run") newValTokens := staking.TokensFromTendermintPower(2)
success, _, _ = f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewCoin(denom, newValTokens), "--dry-run")
require.True(t, success) require.True(t, success)
// Create the validator // Create the validator
f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewInt64Coin(denom, 2)) f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewCoin(denom, newValTokens))
tests.WaitForNextNBlocksTM(1, f.Port) tests.WaitForNextNBlocksTM(1, f.Port)
// Ensure funds were deducted properly // Ensure funds were deducted properly
barAcc = f.QueryAccount(barAddr) barAcc = f.QueryAccount(barAddr)
require.Equal(t, int64(8), barAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, sendTokens.Sub(newValTokens), barAcc.GetCoins().AmountOf(denom))
// Ensure that validator state is as expected // Ensure that validator state is as expected
validator := f.QueryStakingValidator(barVal) validator := f.QueryStakingValidator(barVal)
require.Equal(t, validator.OperatorAddr, barVal) require.Equal(t, validator.OperatorAddr, barVal)
require.True(sdk.IntEq(t, sdk.NewInt(2), validator.Tokens)) require.True(sdk.IntEq(t, newValTokens, validator.Tokens))
// Query delegations to the validator // Query delegations to the validator
validatorDelegations := f.QueryStakingDelegationsTo(barVal) validatorDelegations := f.QueryStakingDelegationsTo(barVal)
@ -340,27 +361,21 @@ func TestGaiaCLICreateValidator(t *testing.T) {
require.NotZero(t, validatorDelegations[0].Shares) require.NotZero(t, validatorDelegations[0].Shares)
// unbond a single share // unbond a single share
success = f.TxStakingUnbond(keyBar, "1", barVal) unbondTokens := staking.TokensFromTendermintPower(1)
success = f.TxStakingUnbond(keyBar, unbondTokens.String(), barVal)
require.True(t, success) require.True(t, success)
tests.WaitForNextNBlocksTM(1, f.Port) tests.WaitForNextNBlocksTM(1, f.Port)
// Ensure bonded staking is correct // Ensure bonded staking is correct
remainingTokens := newValTokens.Sub(unbondTokens)
validator = f.QueryStakingValidator(barVal) validator = f.QueryStakingValidator(barVal)
require.Equal(t, "1", validator.Tokens.String()) require.Equal(t, remainingTokens, validator.Tokens)
// Get unbonding delegations from the validator // Get unbonding delegations from the validator
validatorUbds := f.QueryStakingUnbondingDelegationsFrom(barVal) validatorUbds := f.QueryStakingUnbondingDelegationsFrom(barVal)
require.Len(t, validatorUbds, 1) require.Len(t, validatorUbds, 1)
require.Len(t, validatorUbds[0].Entries, 1) require.Len(t, validatorUbds[0].Entries, 1)
require.Equal(t, "1", validatorUbds[0].Entries[0].Balance.Amount.String()) require.Equal(t, remainingTokens.String(), validatorUbds[0].Entries[0].Balance.String())
// Query staking parameters
params := f.QueryStakingParameters()
require.True(t, defaultParams.Equal(params))
// Query staking pool
pool := f.QueryStakingPool()
require.Equal(t, initialPool.BondedTokens, pool.BondedTokens)
f.Cleanup() f.Cleanup()
} }
@ -380,14 +395,16 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
fooAddr := f.KeyAddress(keyFoo) fooAddr := f.KeyAddress(keyFoo)
fooAcc := f.QueryAccount(fooAddr) fooAcc := f.QueryAccount(fooAddr)
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(stakingTypes.DefaultBondDenom).Int64()) startTokens := staking.TokensFromTendermintPower(50)
require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(staking.DefaultBondDenom))
proposalsQuery := f.QueryGovProposals() proposalsQuery := f.QueryGovProposals()
require.Empty(t, proposalsQuery) require.Empty(t, proposalsQuery)
// Test submit generate only for submit proposal // Test submit generate only for submit proposal
proposalTokens := staking.TokensFromTendermintPower(5)
success, stdout, stderr := f.TxGovSubmitProposal( success, stdout, stderr := f.TxGovSubmitProposal(
keyFoo, "Text", "Test", "test", sdk.NewInt64Coin(denom, 5), "--generate-only") keyFoo, "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens), "--generate-only")
require.True(t, success) require.True(t, success)
require.Empty(t, stderr) require.Empty(t, stderr)
msg := unmarshalStdTx(t, stdout) msg := unmarshalStdTx(t, stdout)
@ -396,11 +413,11 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
require.Equal(t, 0, len(msg.GetSignatures())) require.Equal(t, 0, len(msg.GetSignatures()))
// Test --dry-run // Test --dry-run
success, _, _ = f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewInt64Coin(denom, 5), "--dry-run") success, _, _ = f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens), "--dry-run")
require.True(t, success) require.True(t, success)
// Create the proposal // Create the proposal
f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewInt64Coin(denom, 5)) f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens))
tests.WaitForNextNBlocksTM(1, f.Port) tests.WaitForNextNBlocksTM(1, f.Port)
// Ensure transaction tags can be queried // Ensure transaction tags can be queried
@ -409,7 +426,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
// Ensure deposit was deducted // Ensure deposit was deducted
fooAcc = f.QueryAccount(fooAddr) fooAcc = f.QueryAccount(fooAddr)
require.Equal(t, int64(45), fooAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, startTokens.Sub(proposalTokens), fooAcc.GetCoins().AmountOf(denom))
// Ensure propsal is directly queryable // Ensure propsal is directly queryable
proposal1 := f.QueryGovProposal(1) proposal1 := f.QueryGovProposal(1)
@ -422,10 +439,11 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
// Query the deposits on the proposal // Query the deposits on the proposal
deposit := f.QueryGovDeposit(1, fooAddr) deposit := f.QueryGovDeposit(1, fooAddr)
require.Equal(t, int64(5), deposit.Amount.AmountOf(denom).Int64()) require.Equal(t, proposalTokens, deposit.Amount.AmountOf(denom))
// Test deposit generate only // Test deposit generate only
success, stdout, stderr = f.TxGovDeposit(1, keyFoo, sdk.NewInt64Coin(denom, 10), "--generate-only") depositTokens := staking.TokensFromTendermintPower(10)
success, stdout, stderr = f.TxGovDeposit(1, keyFoo, sdk.NewCoin(denom, depositTokens), "--generate-only")
require.True(t, success) require.True(t, success)
require.Empty(t, stderr) require.Empty(t, stderr)
msg = unmarshalStdTx(t, stdout) msg = unmarshalStdTx(t, stdout)
@ -434,17 +452,17 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
require.Equal(t, 0, len(msg.GetSignatures())) require.Equal(t, 0, len(msg.GetSignatures()))
// Run the deposit transaction // Run the deposit transaction
f.TxGovDeposit(1, keyFoo, sdk.NewInt64Coin(denom, 10)) f.TxGovDeposit(1, keyFoo, sdk.NewCoin(denom, depositTokens))
tests.WaitForNextNBlocksTM(1, f.Port) tests.WaitForNextNBlocksTM(1, f.Port)
// test query deposit // test query deposit
deposits := f.QueryGovDeposits(1) deposits := f.QueryGovDeposits(1)
require.Len(t, deposits, 1) require.Len(t, deposits, 1)
require.Equal(t, int64(15), deposits[0].Amount.AmountOf(denom).Int64()) require.Equal(t, proposalTokens.Add(depositTokens), deposits[0].Amount.AmountOf(denom))
// Ensure querying the deposit returns the proper amount // Ensure querying the deposit returns the proper amount
deposit = f.QueryGovDeposit(1, fooAddr) deposit = f.QueryGovDeposit(1, fooAddr)
require.Equal(t, int64(15), deposit.Amount.AmountOf(denom).Int64()) require.Equal(t, proposalTokens.Add(depositTokens), deposit.Amount.AmountOf(denom))
// Ensure tags are set on the transaction // Ensure tags are set on the transaction
txs = f.QueryTxs(1, 50, "action:deposit", fmt.Sprintf("depositor:%s", fooAddr)) txs = f.QueryTxs(1, 50, "action:deposit", fmt.Sprintf("depositor:%s", fooAddr))
@ -452,7 +470,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
// Ensure account has expected amount of funds // Ensure account has expected amount of funds
fooAcc = f.QueryAccount(fooAddr) fooAcc = f.QueryAccount(fooAddr)
require.Equal(t, int64(35), fooAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, startTokens.Sub(proposalTokens.Add(depositTokens)), fooAcc.GetCoins().AmountOf(denom))
// Fetch the proposal and ensure it is now in the voting period // Fetch the proposal and ensure it is now in the voting period
proposal1 = f.QueryGovProposal(1) proposal1 = f.QueryGovProposal(1)
@ -496,7 +514,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
require.Equal(t, uint64(1), proposalsQuery[0].GetProposalID()) require.Equal(t, uint64(1), proposalsQuery[0].GetProposalID())
// submit a second test proposal // submit a second test proposal
f.TxGovSubmitProposal(keyFoo, "Text", "Apples", "test", sdk.NewInt64Coin(denom, 5)) f.TxGovSubmitProposal(keyFoo, "Text", "Apples", "test", sdk.NewCoin(denom, proposalTokens))
tests.WaitForNextNBlocksTM(1, f.Port) tests.WaitForNextNBlocksTM(1, f.Port)
// Test limit on proposals query // Test limit on proposals query
@ -517,12 +535,13 @@ func TestGaiaCLIQueryTxPagination(t *testing.T) {
fooAddr := f.KeyAddress(keyFoo) fooAddr := f.KeyAddress(keyFoo)
barAddr := f.KeyAddress(keyBar) barAddr := f.KeyAddress(keyBar)
accFoo := f.QueryAccount(fooAddr)
seq := accFoo.GetSequence()
for i := 1; i <= 30; i++ { for i := 1; i <= 30; i++ {
success := executeWrite(t, fmt.Sprintf( success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, int64(i)), fmt.Sprintf("--sequence=%d", seq))
"gaiacli tx send %s --amount=%dfootoken --to=%s --from=foo",
f.Flags(), i, barAddr), app.DefaultKeyPass)
require.True(t, success) require.True(t, success)
tests.WaitForNextNBlocksTM(1, f.Port) seq++
} }
// perPage = 15, 2 pages // perPage = 15, 2 pages
@ -571,7 +590,7 @@ func TestGaiaCLIValidateSignatures(t *testing.T) {
require.Empty(t, stderr) require.Empty(t, stderr)
// write unsigned tx to file // write unsigned tx to file
unsignedTxFile := writeToNewTempFile(t, stdout) unsignedTxFile := WriteToNewTempFile(t, stdout)
defer os.Remove(unsignedTxFile.Name()) defer os.Remove(unsignedTxFile.Name())
// validate we can successfully sign // validate we can successfully sign
@ -584,7 +603,7 @@ func TestGaiaCLIValidateSignatures(t *testing.T) {
require.Equal(t, fooAddr.String(), stdTx.GetSigners()[0].String()) require.Equal(t, fooAddr.String(), stdTx.GetSigners()[0].String())
// write signed tx to file // write signed tx to file
signedTxFile := writeToNewTempFile(t, stdout) signedTxFile := WriteToNewTempFile(t, stdout)
defer os.Remove(signedTxFile.Name()) defer os.Remove(signedTxFile.Name())
// validate signatures // validate signatures
@ -594,7 +613,7 @@ func TestGaiaCLIValidateSignatures(t *testing.T) {
// modify the transaction // modify the transaction
stdTx.Memo = "MODIFIED-ORIGINAL-TX-BAD" stdTx.Memo = "MODIFIED-ORIGINAL-TX-BAD"
bz := marshalStdTx(t, stdTx) bz := marshalStdTx(t, stdTx)
modSignedTxFile := writeToNewTempFile(t, string(bz)) modSignedTxFile := WriteToNewTempFile(t, string(bz))
defer os.Remove(modSignedTxFile.Name()) defer os.Remove(modSignedTxFile.Name())
// validate signature validation failure due to different transaction sig bytes // validate signature validation failure due to different transaction sig bytes
@ -616,7 +635,8 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
barAddr := f.KeyAddress(keyBar) barAddr := f.KeyAddress(keyBar)
// Test generate sendTx with default gas // Test generate sendTx with default gas
success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--generate-only") sendTokens := staking.TokensFromTendermintPower(10)
success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--generate-only")
require.True(t, success) require.True(t, success)
require.Empty(t, stderr) require.Empty(t, stderr)
msg := unmarshalStdTx(t, stdout) msg := unmarshalStdTx(t, stdout)
@ -625,7 +645,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
require.Equal(t, 0, len(msg.GetSignatures())) require.Equal(t, 0, len(msg.GetSignatures()))
// Test generate sendTx with --gas=$amount // Test generate sendTx with --gas=$amount
success, stdout, stderr = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=100", "--generate-only") success, stdout, stderr = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=100", "--generate-only")
require.True(t, success) require.True(t, success)
require.Empty(t, stderr) require.Empty(t, stderr)
msg = unmarshalStdTx(t, stdout) msg = unmarshalStdTx(t, stdout)
@ -634,7 +654,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
require.Equal(t, 0, len(msg.GetSignatures())) require.Equal(t, 0, len(msg.GetSignatures()))
// Test generate sendTx, estimate gas // Test generate sendTx, estimate gas
success, stdout, stderr = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=auto", "--generate-only") success, stdout, stderr = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=auto", "--generate-only")
require.True(t, success) require.True(t, success)
require.NotEmpty(t, stderr) require.NotEmpty(t, stderr)
msg = unmarshalStdTx(t, stdout) msg = unmarshalStdTx(t, stdout)
@ -642,7 +662,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
require.Equal(t, len(msg.Msgs), 1) require.Equal(t, len(msg.Msgs), 1)
// Write the output to disk // Write the output to disk
unsignedTxFile := writeToNewTempFile(t, stdout) unsignedTxFile := WriteToNewTempFile(t, stdout)
defer os.Remove(unsignedTxFile.Name()) defer os.Remove(unsignedTxFile.Name())
// Test sign --validate-signatures // Test sign --validate-signatures
@ -659,7 +679,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
require.Equal(t, fooAddr.String(), msg.GetSigners()[0].String()) require.Equal(t, fooAddr.String(), msg.GetSigners()[0].String())
// Write the output to disk // Write the output to disk
signedTxFile := writeToNewTempFile(t, stdout) signedTxFile := WriteToNewTempFile(t, stdout)
defer os.Remove(signedTxFile.Name()) defer os.Remove(signedTxFile.Name())
// Test sign --validate-signatures // Test sign --validate-signatures
@ -670,27 +690,26 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
// Ensure foo has right amount of funds // Ensure foo has right amount of funds
fooAcc := f.QueryAccount(fooAddr) fooAcc := f.QueryAccount(fooAddr)
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64()) startTokens := staking.TokensFromTendermintPower(50)
require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom))
// Test broadcast // Test broadcast
success, stdout, _ = f.TxBroadcast(signedTxFile.Name()) success, stdout, _ = f.TxBroadcast(signedTxFile.Name())
require.True(t, success) require.True(t, success)
var result struct { var result sdk.TxResponse
Response abci.ResponseDeliverTx
}
// Unmarshal the response and ensure that gas was properly used // Unmarshal the response and ensure that gas was properly used
require.Nil(t, app.MakeCodec().UnmarshalJSON([]byte(stdout), &result)) require.Nil(t, app.MakeCodec().UnmarshalJSON([]byte(stdout), &result))
require.Equal(t, msg.Fee.Gas, uint64(result.Response.GasUsed)) require.Equal(t, msg.Fee.Gas, uint64(result.GasUsed))
require.Equal(t, msg.Fee.Gas, uint64(result.Response.GasWanted)) require.Equal(t, msg.Fee.Gas, uint64(result.GasWanted))
tests.WaitForNextNBlocksTM(1, f.Port) tests.WaitForNextNBlocksTM(1, f.Port)
// Ensure account state // Ensure account state
barAcc := f.QueryAccount(barAddr) barAcc := f.QueryAccount(barAddr)
fooAcc = f.QueryAccount(fooAddr) fooAcc = f.QueryAccount(fooAddr)
require.Equal(t, int64(10), barAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom))
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64()) require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom))
f.Cleanup() f.Cleanup()
} }
@ -716,7 +735,7 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
require.True(t, success) require.True(t, success)
// Write the output to disk // Write the output to disk
unsignedTxFile := writeToNewTempFile(t, stdout) unsignedTxFile := WriteToNewTempFile(t, stdout)
defer os.Remove(unsignedTxFile.Name()) defer os.Remove(unsignedTxFile.Name())
// Sign with foo's key // Sign with foo's key
@ -724,7 +743,7 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
require.True(t, success) require.True(t, success)
// Write the output to disk // Write the output to disk
fooSignatureFile := writeToNewTempFile(t, stdout) fooSignatureFile := WriteToNewTempFile(t, stdout)
defer os.Remove(fooSignatureFile.Name()) defer os.Remove(fooSignatureFile.Name())
// Multisign, not enough signatures // Multisign, not enough signatures
@ -732,7 +751,7 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
require.True(t, success) require.True(t, success)
// Write the output to disk // Write the output to disk
signedTxFile := writeToNewTempFile(t, stdout) signedTxFile := WriteToNewTempFile(t, stdout)
defer os.Remove(signedTxFile.Name()) defer os.Remove(signedTxFile.Name())
// Validate the multisignature // Validate the multisignature
@ -744,6 +763,42 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
require.False(t, success) require.False(t, success)
} }
func TestGaiaCLIEncode(t *testing.T) {
t.Parallel()
f := InitFixtures(t)
// start gaiad server
proc := f.GDStart()
defer proc.Stop(false)
cdc := app.MakeCodec()
// Build a testing transaction and write it to disk
barAddr := f.KeyAddress(keyBar)
sendTokens := staking.TokensFromTendermintPower(10)
success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--generate-only", "--memo", "deadbeef")
require.True(t, success)
require.Empty(t, stderr)
// Write it to disk
jsonTxFile := WriteToNewTempFile(t, stdout)
defer os.Remove(jsonTxFile.Name())
// Run the encode command, and trim the extras from the stdout capture
success, base64Encoded, _ := f.TxEncode(jsonTxFile.Name())
require.True(t, success)
trimmedBase64 := strings.Trim(base64Encoded, "\"\n")
// Decode the base64
decodedBytes, err := base64.StdEncoding.DecodeString(trimmedBase64)
require.Nil(t, err)
// Check that the transaction decodes as epxceted
var decodedTx auth.StdTx
require.Nil(t, cdc.UnmarshalBinaryLengthPrefixed(decodedBytes, &decodedTx))
require.Equal(t, "deadbeef", decodedTx.Memo)
}
func TestGaiaCLIMultisignSortSignatures(t *testing.T) { func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
t.Parallel() t.Parallel()
f := InitFixtures(t) f := InitFixtures(t)
@ -769,7 +824,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
require.True(t, success) require.True(t, success)
// Write the output to disk // Write the output to disk
unsignedTxFile := writeToNewTempFile(t, stdout) unsignedTxFile := WriteToNewTempFile(t, stdout)
defer os.Remove(unsignedTxFile.Name()) defer os.Remove(unsignedTxFile.Name())
// Sign with foo's key // Sign with foo's key
@ -777,7 +832,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
require.True(t, success) require.True(t, success)
// Write the output to disk // Write the output to disk
fooSignatureFile := writeToNewTempFile(t, stdout) fooSignatureFile := WriteToNewTempFile(t, stdout)
defer os.Remove(fooSignatureFile.Name()) defer os.Remove(fooSignatureFile.Name())
// Sign with baz's key // Sign with baz's key
@ -785,7 +840,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
require.True(t, success) require.True(t, success)
// Write the output to disk // Write the output to disk
bazSignatureFile := writeToNewTempFile(t, stdout) bazSignatureFile := WriteToNewTempFile(t, stdout)
defer os.Remove(bazSignatureFile.Name()) defer os.Remove(bazSignatureFile.Name())
// Multisign, keys in different order // Multisign, keys in different order
@ -794,7 +849,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
require.True(t, success) require.True(t, success)
// Write the output to disk // Write the output to disk
signedTxFile := writeToNewTempFile(t, stdout) signedTxFile := WriteToNewTempFile(t, stdout)
defer os.Remove(signedTxFile.Name()) defer os.Remove(signedTxFile.Name())
// Validate the multisignature // Validate the multisignature
@ -832,7 +887,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
require.Empty(t, stderr) require.Empty(t, stderr)
// Write the output to disk // Write the output to disk
unsignedTxFile := writeToNewTempFile(t, stdout) unsignedTxFile := WriteToNewTempFile(t, stdout)
defer os.Remove(unsignedTxFile.Name()) defer os.Remove(unsignedTxFile.Name())
// Sign with foo's key // Sign with foo's key
@ -840,7 +895,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
require.True(t, success) require.True(t, success)
// Write the output to disk // Write the output to disk
fooSignatureFile := writeToNewTempFile(t, stdout) fooSignatureFile := WriteToNewTempFile(t, stdout)
defer os.Remove(fooSignatureFile.Name()) defer os.Remove(fooSignatureFile.Name())
// Sign with bar's key // Sign with bar's key
@ -848,7 +903,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
require.True(t, success) require.True(t, success)
// Write the output to disk // Write the output to disk
barSignatureFile := writeToNewTempFile(t, stdout) barSignatureFile := WriteToNewTempFile(t, stdout)
defer os.Remove(barSignatureFile.Name()) defer os.Remove(barSignatureFile.Name())
// Multisign // Multisign
@ -857,7 +912,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
require.True(t, success) require.True(t, success)
// Write the output to disk // Write the output to disk
signedTxFile := writeToNewTempFile(t, stdout) signedTxFile := WriteToNewTempFile(t, stdout)
defer os.Remove(signedTxFile.Name()) defer os.Remove(signedTxFile.Name())
// Validate the multisignature // Validate the multisignature
@ -909,10 +964,7 @@ func TestGaiadCollectGentxs(t *testing.T) {
f.UnsafeResetAll() f.UnsafeResetAll()
// Initialize keys // Initialize keys
f.KeysDelete(keyFoo)
f.KeysDelete(keyBar)
f.KeysAdd(keyFoo) f.KeysAdd(keyFoo)
f.KeysAdd(keyBar)
// Configure json output // Configure json output
f.CLIConfig("output", "json") f.CLIConfig("output", "json")
@ -932,6 +984,42 @@ func TestGaiadCollectGentxs(t *testing.T) {
f.Cleanup(gentxDir) f.Cleanup(gentxDir)
} }
func TestGaiadAddGenesisAccount(t *testing.T) {
t.Parallel()
f := NewFixtures(t)
// Reset testing path
f.UnsafeResetAll()
// Initialize keys
f.KeysDelete(keyFoo)
f.KeysDelete(keyBar)
f.KeysDelete(keyBaz)
f.KeysAdd(keyFoo)
f.KeysAdd(keyBar)
f.KeysAdd(keyBaz)
// Configure json output
f.CLIConfig("output", "json")
// Run init
f.GDInit(keyFoo)
// Add account to genesis.json
bazCoins := sdk.Coins{
sdk.NewInt64Coin("acoin", 1000000),
sdk.NewInt64Coin("bcoin", 1000000),
}
f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins)
f.AddGenesisAccount(f.KeyAddress(keyBar), bazCoins)
genesisState := f.GenesisState()
require.Equal(t, genesisState.Accounts[0].Address, f.KeyAddress(keyFoo))
require.Equal(t, genesisState.Accounts[1].Address, f.KeyAddress(keyBar))
require.True(t, genesisState.Accounts[0].Coins.IsEqual(startCoins))
require.True(t, genesisState.Accounts[1].Coins.IsEqual(bazCoins))
}
func TestSlashingGetParams(t *testing.T) { func TestSlashingGetParams(t *testing.T) {
t.Parallel() t.Parallel()
f := InitFixtures(t) f := InitFixtures(t)
@ -944,4 +1032,19 @@ func TestSlashingGetParams(t *testing.T) {
require.Equal(t, time.Duration(120000000000), params.MaxEvidenceAge) require.Equal(t, time.Duration(120000000000), params.MaxEvidenceAge)
require.Equal(t, int64(100), params.SignedBlocksWindow) require.Equal(t, int64(100), params.SignedBlocksWindow)
require.Equal(t, sdk.NewDecWithPrec(5, 1), params.MinSignedPerWindow) require.Equal(t, sdk.NewDecWithPrec(5, 1), params.MinSignedPerWindow)
sinfo := f.QuerySigningInfo(f.GDTendermint("show-validator"))
require.Equal(t, int64(0), sinfo.StartHeight)
require.False(t, sinfo.Tombstoned)
}
func TestValidateGenesis(t *testing.T) {
t.Parallel()
f := InitFixtures(t)
// start gaiad server
proc := f.GDStart()
defer proc.Stop(false)
f.ValidateGenesis()
} }

View File

@ -8,14 +8,15 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
"time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
cmn "github.com/tendermint/tendermint/libs/common" cmn "github.com/tendermint/tendermint/libs/common"
"github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/cmd/gaia/app"
appInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/tests" "github.com/cosmos/cosmos-sdk/tests"
@ -34,15 +35,22 @@ const (
feeDenom = "feetoken" feeDenom = "feetoken"
fee2Denom = "fee2token" fee2Denom = "fee2token"
keyBaz = "baz" keyBaz = "baz"
keyVesting = "vesting"
keyFooBarBaz = "foobarbaz" keyFooBarBaz = "foobarbaz"
) )
var startCoins = sdk.Coins{ var (
sdk.NewInt64Coin(feeDenom, 1000000), startCoins = sdk.Coins{
sdk.NewInt64Coin(fee2Denom, 1000000), sdk.NewCoin(feeDenom, staking.TokensFromTendermintPower(1000000)),
sdk.NewInt64Coin(fooDenom, 1000), sdk.NewCoin(fee2Denom, staking.TokensFromTendermintPower(1000000)),
sdk.NewInt64Coin(denom, 150), sdk.NewCoin(fooDenom, staking.TokensFromTendermintPower(1000)),
} sdk.NewCoin(denom, staking.TokensFromTendermintPower(150)),
}
vestingCoins = sdk.Coins{
sdk.NewCoin(feeDenom, staking.TokensFromTendermintPower(500000)),
}
)
//___________________________________________________________________________________ //___________________________________________________________________________________
// Fixtures // Fixtures
@ -76,6 +84,22 @@ func NewFixtures(t *testing.T) *Fixtures {
} }
} }
// GenesisFile returns the path of the genesis file
func (f Fixtures) GenesisFile() string {
return filepath.Join(f.GDHome, "config", "genesis.json")
}
// GenesisFile returns the application's genesis state
func (f Fixtures) GenesisState() app.GenesisState {
cdc := codec.New()
genDoc, err := appInit.LoadGenesisDoc(cdc, f.GenesisFile())
require.NoError(f.T, err)
var appState app.GenesisState
require.NoError(f.T, cdc.UnmarshalJSON(genDoc.AppState, &appState))
return appState
}
// InitFixtures is called at the beginning of a test // InitFixtures is called at the beginning of a test
// and initializes a chain with 1 validator // and initializes a chain with 1 validator
func InitFixtures(t *testing.T) (f *Fixtures) { func InitFixtures(t *testing.T) (f *Fixtures) {
@ -92,6 +116,7 @@ func InitFixtures(t *testing.T) (f *Fixtures) {
f.KeysAdd(keyFoo) f.KeysAdd(keyFoo)
f.KeysAdd(keyBar) f.KeysAdd(keyBar)
f.KeysAdd(keyBaz) f.KeysAdd(keyBaz)
f.KeysAdd(keyVesting)
f.KeysAdd(keyFooBarBaz, "--multisig-threshold=2", fmt.Sprintf( f.KeysAdd(keyFooBarBaz, "--multisig-threshold=2", fmt.Sprintf(
"--multisig=%s,%s,%s", keyFoo, keyBar, keyBaz)) "--multisig=%s,%s,%s", keyFoo, keyBar, keyBaz))
@ -104,6 +129,12 @@ func InitFixtures(t *testing.T) (f *Fixtures) {
// Start an account with tokens // Start an account with tokens
f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins) f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins)
f.AddGenesisAccount(
f.KeyAddress(keyVesting), startCoins,
fmt.Sprintf("--vesting-amount=%s", vestingCoins),
fmt.Sprintf("--vesting-start-time=%d", time.Now().UTC().UnixNano()),
fmt.Sprintf("--vesting-end-time=%d", time.Now().Add(60*time.Second).UTC().UnixNano()),
)
f.GenTx(keyFoo) f.GenTx(keyFoo)
f.CollectGenTxs() f.CollectGenTxs()
return return
@ -137,7 +168,7 @@ func (f *Fixtures) UnsafeResetAll(flags ...string) {
// GDInit is gaiad init // GDInit is gaiad init
// NOTE: GDInit sets the ChainID for the Fixtures instance // NOTE: GDInit sets the ChainID for the Fixtures instance
func (f *Fixtures) GDInit(moniker string, flags ...string) { func (f *Fixtures) GDInit(moniker string, flags ...string) {
cmd := fmt.Sprintf("gaiad init -o --moniker=%s --home=%s", moniker, f.GDHome) cmd := fmt.Sprintf("gaiad init -o --home=%s %s", f.GDHome, moniker)
_, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), app.DefaultKeyPass) _, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
var chainID string var chainID string
@ -179,6 +210,21 @@ func (f *Fixtures) GDStart(flags ...string) *tests.Process {
return proc return proc
} }
// GDTendermint returns the results of gaiad tendermint [query]
func (f *Fixtures) GDTendermint(query string) string {
cmd := fmt.Sprintf("gaiad tendermint %s --home=%s", query, f.GDHome)
success, stdout, stderr := executeWriteRetStdStreams(f.T, cmd)
require.Empty(f.T, stderr)
require.True(f.T, success)
return strings.TrimSpace(stdout)
}
// ValidateGenesis runs gaiad validate-genesis
func (f *Fixtures) ValidateGenesis() {
cmd := fmt.Sprintf("gaiad validate-genesis --home=%s", f.GDHome)
executeWriteCheckErr(f.T, cmd)
}
//___________________________________________________________________________________ //___________________________________________________________________________________
// gaiacli keys // gaiacli keys
@ -194,6 +240,18 @@ func (f *Fixtures) KeysAdd(name string, flags ...string) {
executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass) executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
} }
// KeysAddRecover prepares gaiacli keys add --recover
func (f *Fixtures) KeysAddRecover(name, mnemonic string, flags ...string) {
cmd := fmt.Sprintf("gaiacli keys add --home=%s --recover %s", f.GCLIHome, name)
executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass, mnemonic)
}
// KeysAddRecoverHDPath prepares gaiacli keys add --recover --account --index
func (f *Fixtures) KeysAddRecoverHDPath(name, mnemonic string, account uint32, index uint32, flags ...string) {
cmd := fmt.Sprintf("gaiacli keys add --home=%s --recover %s --account %d --index %d", f.GCLIHome, name, account, index)
executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass, mnemonic)
}
// KeysShow is gaiacli keys show // KeysShow is gaiacli keys show
func (f *Fixtures) KeysShow(name string, flags ...string) keys.KeyOutput { func (f *Fixtures) KeysShow(name string, flags ...string) keys.KeyOutput {
cmd := fmt.Sprintf("gaiacli keys show --home=%s %s", f.GCLIHome, name) cmd := fmt.Sprintf("gaiacli keys show --home=%s %s", f.GCLIHome, name)
@ -226,7 +284,7 @@ func (f *Fixtures) CLIConfig(key, value string, flags ...string) {
// TxSend is gaiacli tx send // TxSend is gaiacli tx send
func (f *Fixtures) TxSend(from string, to sdk.AccAddress, amount sdk.Coin, flags ...string) (bool, string, string) { func (f *Fixtures) TxSend(from string, to sdk.AccAddress, amount sdk.Coin, flags ...string) (bool, string, string) {
cmd := fmt.Sprintf("gaiacli tx send %v --amount=%s --to=%s --from=%s", f.Flags(), amount, to, from) cmd := fmt.Sprintf("gaiacli tx send %s %s %v --from=%s", to, amount, f.Flags(), from)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
} }
@ -236,12 +294,18 @@ func (f *Fixtures) TxSign(signer, fileName string, flags ...string) (bool, strin
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
} }
// TxBroadcast is gaiacli tx sign // TxBroadcast is gaiacli tx broadcast
func (f *Fixtures) TxBroadcast(fileName string, flags ...string) (bool, string, string) { func (f *Fixtures) TxBroadcast(fileName string, flags ...string) (bool, string, string) {
cmd := fmt.Sprintf("gaiacli tx broadcast %v %v", f.Flags(), fileName) cmd := fmt.Sprintf("gaiacli tx broadcast %v %v", f.Flags(), fileName)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
} }
// TxEncode is gaiacli tx encode
func (f *Fixtures) TxEncode(fileName string, flags ...string) (bool, string, string) {
cmd := fmt.Sprintf("gaiacli tx encode %v %v", f.Flags(), fileName)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
}
// TxMultisign is gaiacli tx multisign // TxMultisign is gaiacli tx multisign
func (f *Fixtures) TxMultisign(fileName, name string, signaturesFiles []string, func (f *Fixtures) TxMultisign(fileName, name string, signaturesFiles []string,
flags ...string) (bool, string, string) { flags ...string) (bool, string, string) {
@ -260,12 +324,13 @@ func (f *Fixtures) TxStakingCreateValidator(from, consPubKey string, amount sdk.
cmd := fmt.Sprintf("gaiacli tx staking create-validator %v --from=%s --pubkey=%s", f.Flags(), from, consPubKey) cmd := fmt.Sprintf("gaiacli tx staking create-validator %v --from=%s --pubkey=%s", f.Flags(), from, consPubKey)
cmd += fmt.Sprintf(" --amount=%v --moniker=%v --commission-rate=%v", amount, from, "0.05") cmd += fmt.Sprintf(" --amount=%v --moniker=%v --commission-rate=%v", amount, from, "0.05")
cmd += fmt.Sprintf(" --commission-max-rate=%v --commission-max-change-rate=%v", "0.20", "0.10") cmd += fmt.Sprintf(" --commission-max-rate=%v --commission-max-change-rate=%v", "0.20", "0.10")
cmd += fmt.Sprintf(" --min-self-delegation=%v", "1")
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
} }
// TxStakingUnbond is gaiacli tx staking unbond // TxStakingUnbond is gaiacli tx staking unbond
func (f *Fixtures) TxStakingUnbond(from, shares string, validator sdk.ValAddress, flags ...string) bool { func (f *Fixtures) TxStakingUnbond(from, shares string, validator sdk.ValAddress, flags ...string) bool {
cmd := fmt.Sprintf("gaiacli tx staking unbond %v --from=%s --validator=%s --shares-amount=%v", f.Flags(), from, validator, shares) cmd := fmt.Sprintf("gaiacli tx staking unbond %s %v --from=%s %v", validator, shares, from, f.Flags())
return executeWrite(f.T, addFlags(cmd, flags), app.DefaultKeyPass) return executeWrite(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
} }
@ -314,10 +379,10 @@ func (f *Fixtures) QueryAccount(address sdk.AccAddress, flags ...string) auth.Ba
// gaiacli query txs // gaiacli query txs
// QueryTxs is gaiacli query txs // QueryTxs is gaiacli query txs
func (f *Fixtures) QueryTxs(page, limit int, tags ...string) []tx.Info { func (f *Fixtures) QueryTxs(page, limit int, tags ...string) []sdk.TxResponse {
cmd := fmt.Sprintf("gaiacli query txs --page=%d --limit=%d --tags='%s' %v", page, limit, queryTags(tags), f.Flags()) cmd := fmt.Sprintf("gaiacli query txs --page=%d --limit=%d --tags='%s' %v", page, limit, queryTags(tags), f.Flags())
out, _ := tests.ExecuteT(f.T, cmd, "") out, _ := tests.ExecuteT(f.T, cmd, "")
var txs []tx.Info var txs []sdk.TxResponse
cdc := app.MakeCodec() cdc := app.MakeCodec()
err := cdc.UnmarshalJSON([]byte(out), &txs) err := cdc.UnmarshalJSON([]byte(out), &txs)
require.NoError(f.T, err, "out %v\n, err %v", out, err) require.NoError(f.T, err, "out %v\n, err %v", out, err)
@ -498,6 +563,18 @@ func (f *Fixtures) QueryGovDeposits(propsalID int, flags ...string) []gov.Deposi
//___________________________________________________________________________________ //___________________________________________________________________________________
// query slashing // query slashing
// QuerySigningInfo returns the signing info for a validator
func (f *Fixtures) QuerySigningInfo(val string) slashing.ValidatorSigningInfo {
cmd := fmt.Sprintf("gaiacli query slashing signing-info %s %s", val, f.Flags())
res, errStr := tests.ExecuteT(f.T, cmd, "")
require.Empty(f.T, errStr)
cdc := app.MakeCodec()
var sinfo slashing.ValidatorSigningInfo
err := cdc.UnmarshalJSON([]byte(res), &sinfo)
require.NoError(f.T, err)
return sinfo
}
// QuerySlashingParams is gaiacli query slashing params // QuerySlashingParams is gaiacli query slashing params
func (f *Fixtures) QuerySlashingParams() slashing.Params { func (f *Fixtures) QuerySlashingParams() slashing.Params {
cmd := fmt.Sprintf("gaiacli query slashing params %s", f.Flags()) cmd := fmt.Sprintf("gaiacli query slashing params %s", f.Flags())
@ -569,7 +646,8 @@ func queryTags(tags []string) (out string) {
return strings.TrimSuffix(out, "&") return strings.TrimSuffix(out, "&")
} }
func writeToNewTempFile(t *testing.T, s string) *os.File { // Write the given string to a new temporary file
func WriteToNewTempFile(t *testing.T, s string) *os.File {
fp, err := ioutil.TempFile(os.TempDir(), "cosmos_cli_test_") fp, err := ioutil.TempFile(os.TempDir(), "cosmos_cli_test_")
require.Nil(t, err) require.Nil(t, err)
_, err = fp.WriteString(s) _, err = fp.WriteString(s)

View File

@ -25,7 +25,7 @@ import (
at "github.com/cosmos/cosmos-sdk/x/auth" at "github.com/cosmos/cosmos-sdk/x/auth"
auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
dist "github.com/cosmos/cosmos-sdk/x/distribution" dist "github.com/cosmos/cosmos-sdk/x/distribution/client/rest"
gv "github.com/cosmos/cosmos-sdk/x/gov" gv "github.com/cosmos/cosmos-sdk/x/gov"
gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest" gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
sl "github.com/cosmos/cosmos-sdk/x/slashing" sl "github.com/cosmos/cosmos-sdk/x/slashing"
@ -35,6 +35,7 @@ import (
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
distcmd "github.com/cosmos/cosmos-sdk/x/distribution"
distClient "github.com/cosmos/cosmos-sdk/x/distribution/client" distClient "github.com/cosmos/cosmos-sdk/x/distribution/client"
govClient "github.com/cosmos/cosmos-sdk/x/gov/client" govClient "github.com/cosmos/cosmos-sdk/x/gov/client"
slashingClient "github.com/cosmos/cosmos-sdk/x/slashing/client" slashingClient "github.com/cosmos/cosmos-sdk/x/slashing/client"
@ -65,7 +66,7 @@ func main() {
// TODO: Make the lcd command take a list of ModuleClient // TODO: Make the lcd command take a list of ModuleClient
mc := []sdk.ModuleClients{ mc := []sdk.ModuleClients{
govClient.NewModuleClient(gv.StoreKey, cdc), govClient.NewModuleClient(gv.StoreKey, cdc),
distClient.NewModuleClient(dist.StoreKey, cdc), distClient.NewModuleClient(distcmd.StoreKey, cdc),
stakingClient.NewModuleClient(st.StoreKey, cdc), stakingClient.NewModuleClient(st.StoreKey, cdc),
slashingClient.NewModuleClient(sl.StoreKey, cdc), slashingClient.NewModuleClient(sl.StoreKey, cdc),
} }
@ -84,7 +85,7 @@ func main() {
// Construct Root Command // Construct Root Command
rootCmd.AddCommand( rootCmd.AddCommand(
rpc.StatusCommand(), rpc.StatusCommand(),
client.ConfigCmd(), client.ConfigCmd(app.DefaultCLIHome),
queryCmd(cdc, mc), queryCmd(cdc, mc),
txCmd(cdc, mc), txCmd(cdc, mc),
client.LineBreak, client.LineBreak,
@ -114,7 +115,7 @@ func queryCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command {
} }
queryCmd.AddCommand( queryCmd.AddCommand(
rpc.ValidatorCommand(), rpc.ValidatorCommand(cdc),
rpc.BlockCommand(), rpc.BlockCommand(),
tx.SearchTxCmd(cdc), tx.SearchTxCmd(cdc),
tx.QueryTxCmd(cdc), tx.QueryTxCmd(cdc),
@ -140,7 +141,8 @@ func txCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command {
client.LineBreak, client.LineBreak,
authcmd.GetSignCommand(cdc), authcmd.GetSignCommand(cdc),
authcmd.GetMultiSignCommand(cdc), authcmd.GetMultiSignCommand(cdc),
bankcmd.GetBroadcastCommand(cdc), authcmd.GetBroadcastCommand(cdc),
authcmd.GetEncodeCommand(cdc),
client.LineBreak, client.LineBreak,
) )
@ -161,6 +163,7 @@ func registerRoutes(rs *lcd.RestServer) {
tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
auth.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, at.StoreKey) auth.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, at.StoreKey)
bank.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) bank.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
dist.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, distcmd.StoreKey)
staking.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) staking.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
slashing.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) slashing.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
gov.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) gov.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)

View File

@ -43,6 +43,7 @@ func main() {
rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc)) rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc))
rootCmd.AddCommand(gaiaInit.GenTxCmd(ctx, cdc)) rootCmd.AddCommand(gaiaInit.GenTxCmd(ctx, cdc))
rootCmd.AddCommand(gaiaInit.AddGenesisAccountCmd(ctx, cdc)) rootCmd.AddCommand(gaiaInit.AddGenesisAccountCmd(ctx, cdc))
rootCmd.AddCommand(gaiaInit.ValidateGenesisCmd(ctx, cdc))
rootCmd.AddCommand(client.NewCompletionCmd(rootCmd, true)) rootCmd.AddCommand(client.NewCompletionCmd(rootCmd, true))
server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators) server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators)
@ -59,13 +60,13 @@ func main() {
func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application { func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application {
return app.NewGaiaApp( return app.NewGaiaApp(
logger, db, traceStore, true, logger, db, traceStore, true,
baseapp.SetPruning(store.NewPruningOptions(viper.GetString("pruning"))), baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))),
baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)), baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)),
) )
} }
func exportAppStateAndTMValidators( func exportAppStateAndTMValidators(
logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailWhiteList []string,
) (json.RawMessage, []tmtypes.GenesisValidator, error) { ) (json.RawMessage, []tmtypes.GenesisValidator, error) {
if height != -1 { if height != -1 {
gApp := app.NewGaiaApp(logger, db, traceStore, false) gApp := app.NewGaiaApp(logger, db, traceStore, false)
@ -73,8 +74,8 @@ func exportAppStateAndTMValidators(
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
return gApp.ExportAppStateAndValidators(forZeroHeight) return gApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
} }
gApp := app.NewGaiaApp(logger, db, traceStore, true) gApp := app.NewGaiaApp(logger, db, traceStore, true)
return gApp.ExportAppStateAndValidators(forZeroHeight) return gApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
} }

View File

@ -50,7 +50,7 @@ func runHackCmd(cmd *cobra.Command, args []string) error {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
app := NewGaiaApp(logger, db, baseapp.SetPruning(store.NewPruningOptions(viper.GetString("pruning")))) app := NewGaiaApp(logger, db, baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))))
// print some info // print some info
id := app.LastCommitID() id := app.LastCommitID()
@ -178,7 +178,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp
) )
// add handlers // add handlers
app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper) app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper, app.paramsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace)
app.stakingKeeper = staking.NewKeeper(app.cdc, app.keyStaking, app.tkeyStaking, app.bankKeeper, app.paramsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) app.stakingKeeper = staking.NewKeeper(app.cdc, app.keyStaking, app.tkeyStaking, app.bankKeeper, app.paramsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace)
app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakingKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), slashing.DefaultCodespace) app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakingKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), slashing.DefaultCodespace)

View File

@ -1,5 +1,7 @@
package init package init
// DONTCOVER
import ( import (
"encoding/json" "encoding/json"
"path/filepath" "path/filepath"
@ -44,7 +46,7 @@ func CollectGenTxsCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
return err return err
} }
genDoc, err := loadGenesisDoc(cdc, config.GenesisFile()) genDoc, err := LoadGenesisDoc(cdc, config.GenesisFile())
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,7 +1,6 @@
package init package init
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -17,7 +16,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
) )
// AddGenesisAccountCmd returns add-genesis-account cobra Command // AddGenesisAccountCmd returns add-genesis-account cobra Command.
func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]", Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]",
@ -29,27 +28,37 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command
addr, err := sdk.AccAddressFromBech32(args[0]) addr, err := sdk.AccAddressFromBech32(args[0])
if err != nil { if err != nil {
kb, err := keys.GetKeyBaseFromDir(viper.GetString(flagClientHome)) kb, err := keys.NewKeyBaseFromDir(viper.GetString(flagClientHome))
if err != nil { if err != nil {
return err return err
} }
info, err := kb.Get(args[0]) info, err := kb.Get(args[0])
if err != nil { if err != nil {
return err return err
} }
addr = info.GetAddress() addr = info.GetAddress()
} }
coins, err := sdk.ParseCoins(args[1]) coins, err := sdk.ParseCoins(args[1])
if err != nil { if err != nil {
return err return err
} }
coins.Sort()
vestingStart := viper.GetInt64(flagVestingStart)
vestingEnd := viper.GetInt64(flagVestingEnd)
vestingAmt, err := sdk.ParseCoins(viper.GetString(flagVestingAmt))
if err != nil {
return err
}
genFile := config.GenesisFile() genFile := config.GenesisFile()
if !common.FileExists(genFile) { if !common.FileExists(genFile) {
return fmt.Errorf("%s does not exist, run `gaiad init` first", genFile) return fmt.Errorf("%s does not exist, run `gaiad init` first", genFile)
} }
genDoc, err := loadGenesisDoc(cdc, genFile)
genDoc, err := LoadGenesisDoc(cdc, genFile)
if err != nil { if err != nil {
return err return err
} }
@ -59,7 +68,12 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command
return err return err
} }
appStateJSON, err := addGenesisAccount(cdc, appState, addr, coins) appState, err = addGenesisAccount(cdc, appState, addr, coins, vestingAmt, vestingStart, vestingEnd)
if err != nil {
return err
}
appStateJSON, err := cdc.MarshalJSON(appState)
if err != nil { if err != nil {
return err return err
} }
@ -70,18 +84,58 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command
cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory")
cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory")
cmd.Flags().String(flagVestingAmt, "", "amount of coins for vesting accounts")
cmd.Flags().Uint64(flagVestingStart, 0, "schedule start time (unix epoch) for vesting accounts")
cmd.Flags().Uint64(flagVestingEnd, 0, "schedule end time (unix epoch) for vesting accounts")
return cmd return cmd
} }
func addGenesisAccount(cdc *codec.Codec, appState app.GenesisState, addr sdk.AccAddress, coins sdk.Coins) (json.RawMessage, error) { func addGenesisAccount(
cdc *codec.Codec, appState app.GenesisState, addr sdk.AccAddress,
coins, vestingAmt sdk.Coins, vestingStart, vestingEnd int64,
) (app.GenesisState, error) {
for _, stateAcc := range appState.Accounts { for _, stateAcc := range appState.Accounts {
if stateAcc.Address.Equals(addr) { if stateAcc.Address.Equals(addr) {
return nil, fmt.Errorf("the application state already contains account %v", addr) return appState, fmt.Errorf("the application state already contains account %v", addr)
} }
} }
acc := auth.NewBaseAccountWithAddress(addr) acc := auth.NewBaseAccountWithAddress(addr)
acc.Coins = coins acc.Coins = coins
appState.Accounts = append(appState.Accounts, app.NewGenesisAccount(&acc))
return cdc.MarshalJSON(appState) if !vestingAmt.IsZero() {
var vacc auth.VestingAccount
bvacc := &auth.BaseVestingAccount{
BaseAccount: &acc,
OriginalVesting: vestingAmt,
EndTime: vestingEnd,
}
if bvacc.OriginalVesting.IsAllGT(acc.Coins) {
return appState, fmt.Errorf("vesting amount cannot be greater than total amount")
}
if vestingStart >= vestingEnd {
return appState, fmt.Errorf("vesting start time must before end time")
}
if vestingStart != 0 {
vacc = &auth.ContinuousVestingAccount{
BaseVestingAccount: bvacc,
StartTime: vestingStart,
}
} else {
vacc = &auth.DelayedVestingAccount{
BaseVestingAccount: bvacc,
}
}
appState.Accounts = append(appState.Accounts, app.NewGenesisAccountI(vacc))
} else {
appState.Accounts = append(appState.Accounts, app.NewGenesisAccount(&acc))
}
return appState, nil
} }

View File

@ -15,9 +15,12 @@ func TestAddGenesisAccount(t *testing.T) {
cdc := codec.New() cdc := codec.New()
addr1 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) addr1 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
type args struct { type args struct {
appState app.GenesisState appState app.GenesisState
addr sdk.AccAddress addr sdk.AccAddress
coins sdk.Coins coins sdk.Coins
vestingAmt sdk.Coins
vestingStart int64
vestingEnd int64
} }
tests := []struct { tests := []struct {
name string name string
@ -30,16 +33,55 @@ func TestAddGenesisAccount(t *testing.T) {
app.GenesisState{}, app.GenesisState{},
addr1, addr1,
sdk.Coins{}, sdk.Coins{},
sdk.Coins{},
0,
0,
}, },
false}, false,
{"dup account", args{ },
app.GenesisState{Accounts: []app.GenesisAccount{{Address: addr1}}}, {
addr1, "dup account",
sdk.Coins{}}, true}, args{
app.GenesisState{Accounts: []app.GenesisAccount{{Address: addr1}}},
addr1,
sdk.Coins{},
sdk.Coins{},
0,
0,
},
true,
},
{
"invalid vesting amount",
args{
app.GenesisState{},
addr1,
sdk.Coins{sdk.NewInt64Coin("stake", 50)},
sdk.Coins{sdk.NewInt64Coin("stake", 100)},
0,
0,
},
true,
},
{
"invalid vesting times",
args{
app.GenesisState{},
addr1,
sdk.Coins{sdk.NewInt64Coin("stake", 50)},
sdk.Coins{sdk.NewInt64Coin("stake", 50)},
1654668078,
1554668078,
},
true,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
_, err := addGenesisAccount(cdc, tt.args.appState, tt.args.addr, tt.args.coins) _, err := addGenesisAccount(
cdc, tt.args.appState, tt.args.addr, tt.args.coins,
tt.args.vestingAmt, tt.args.vestingStart, tt.args.vestingEnd,
)
require.Equal(t, tt.wantErr, (err != nil)) require.Equal(t, tt.wantErr, (err != nil))
}) })
} }

View File

@ -1,5 +1,7 @@
package init package init
// DONTCOVER
import ( import (
"bytes" "bytes"
"fmt" "fmt"
@ -26,15 +28,17 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/x/staking/client/cli" "github.com/cosmos/cosmos-sdk/x/staking/client/cli"
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
) )
const ( var (
defaultAmount = "100" + stakingTypes.DefaultBondDenom defaultTokens = staking.TokensFromTendermintPower(100)
defaultAmount = defaultTokens.String() + staking.DefaultBondDenom
defaultCommissionRate = "0.1" defaultCommissionRate = "0.1"
defaultCommissionMaxRate = "0.2" defaultCommissionMaxRate = "0.2"
defaultCommissionMaxChangeRate = "0.01" defaultCommissionMaxChangeRate = "0.01"
defaultMinSelfDelegation = "1"
) )
// GenTxCmd builds the gaiad gentx command. // GenTxCmd builds the gaiad gentx command.
@ -52,7 +56,8 @@ following delegation and commission default parameters:
commission rate: %s commission rate: %s
commission max rate: %s commission max rate: %s
commission max change rate: %s commission max change rate: %s
`, defaultAmount, defaultCommissionRate, defaultCommissionMaxRate, defaultCommissionMaxChangeRate), minimum self delegation: %s
`, defaultAmount, defaultCommissionRate, defaultCommissionMaxRate, defaultCommissionMaxChangeRate, defaultMinSelfDelegation),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
config := ctx.Config config := ctx.Config
@ -61,12 +66,19 @@ following delegation and commission default parameters:
if err != nil { if err != nil {
return err return err
} }
ip, err := server.ExternalIP()
if err != nil { // Read --nodeID, if empty take it from priv_validator.json
return err if nodeIDString := viper.GetString(cli.FlagNodeID); nodeIDString != "" {
nodeID = nodeIDString
} }
genDoc, err := loadGenesisDoc(cdc, config.GenesisFile()) ip := viper.GetString(cli.FlagIP)
if ip == "" {
fmt.Fprintf(os.Stderr, "couldn't retrieve an external IP; "+
"the tx's memo field will be unset")
}
genDoc, err := LoadGenesisDoc(cdc, config.GenesisFile())
if err != nil { if err != nil {
return err return err
} }
@ -76,7 +88,7 @@ following delegation and commission default parameters:
return err return err
} }
kb, err := keys.GetKeyBaseFromDir(viper.GetString(flagClientHome)) kb, err := keys.NewKeyBaseFromDir(viper.GetString(flagClientHome))
if err != nil { if err != nil {
return err return err
} }
@ -120,7 +132,8 @@ following delegation and commission default parameters:
// write the unsigned transaction to the buffer // write the unsigned transaction to the buffer
w := bytes.NewBuffer([]byte{}) w := bytes.NewBuffer([]byte{})
if err := utils.PrintUnsignedStdTx(w, txBldr, cliCtx, []sdk.Msg{msg}, true); err != nil { cliCtx = cliCtx.WithOutput(w)
if err = utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, true); err != nil {
return err return err
} }
@ -154,12 +167,17 @@ following delegation and commission default parameters:
}, },
} }
ip, _ := server.ExternalIP()
cmd.Flags().String(tmcli.HomeFlag, app.DefaultNodeHome, "node's home directory") cmd.Flags().String(tmcli.HomeFlag, app.DefaultNodeHome, "node's home directory")
cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory")
cmd.Flags().String(client.FlagName, "", "name of private key with which to sign the gentx") cmd.Flags().String(client.FlagName, "", "name of private key with which to sign the gentx")
cmd.Flags().String(client.FlagOutputDocument, "", cmd.Flags().String(client.FlagOutputDocument, "",
"write the genesis transaction JSON document to the given file instead of the default location") "write the genesis transaction JSON document to the given file instead of the default location")
cmd.Flags().String(cli.FlagIP, ip, "The node's public IP")
cmd.Flags().String(cli.FlagNodeID, "", "The node's NodeID")
cmd.Flags().AddFlagSet(cli.FsCommissionCreate) cmd.Flags().AddFlagSet(cli.FsCommissionCreate)
cmd.Flags().AddFlagSet(cli.FsMinSelfDelegation)
cmd.Flags().AddFlagSet(cli.FsAmount) cmd.Flags().AddFlagSet(cli.FsAmount)
cmd.Flags().AddFlagSet(cli.FsPk) cmd.Flags().AddFlagSet(cli.FsPk)
cmd.MarkFlagRequired(client.FlagName) cmd.MarkFlagRequired(client.FlagName)
@ -202,7 +220,7 @@ func prepareFlagsForTxCreateValidator(config *cfg.Config, nodeID, ip, chainID st
viper.Set(cli.FlagNodeID, nodeID) // --node-id viper.Set(cli.FlagNodeID, nodeID) // --node-id
viper.Set(cli.FlagIP, ip) // --ip viper.Set(cli.FlagIP, ip) // --ip
viper.Set(cli.FlagPubKey, sdk.MustBech32ifyConsPub(valPubKey)) // --pubkey viper.Set(cli.FlagPubKey, sdk.MustBech32ifyConsPub(valPubKey)) // --pubkey
viper.Set(cli.FlagGenesisFormat, true) // --genesis-format viper.Set(client.FlagGenerateOnly, true) // --genesis-format
viper.Set(cli.FlagMoniker, config.Moniker) // --moniker viper.Set(cli.FlagMoniker, config.Moniker) // --moniker
if config.Moniker == "" { if config.Moniker == "" {
viper.Set(cli.FlagMoniker, viper.GetString(client.FlagName)) viper.Set(cli.FlagMoniker, viper.GetString(client.FlagName))
@ -219,6 +237,9 @@ func prepareFlagsForTxCreateValidator(config *cfg.Config, nodeID, ip, chainID st
if viper.GetString(cli.FlagCommissionMaxChangeRate) == "" { if viper.GetString(cli.FlagCommissionMaxChangeRate) == "" {
viper.Set(cli.FlagCommissionMaxChangeRate, defaultCommissionMaxChangeRate) viper.Set(cli.FlagCommissionMaxChangeRate, defaultCommissionMaxChangeRate)
} }
if viper.GetString(cli.FlagMinSelfDelegation) == "" {
viper.Set(cli.FlagMinSelfDelegation, defaultMinSelfDelegation)
}
} }
func makeOutputFilepath(rootDir, nodeID string) (string, error) { func makeOutputFilepath(rootDir, nodeID string) (string, error) {

View File

@ -19,9 +19,11 @@ import (
) )
const ( const (
flagOverwrite = "overwrite" flagOverwrite = "overwrite"
flagClientHome = "home-client" flagClientHome = "home-client"
flagMoniker = "moniker" flagVestingStart = "vesting-start-time"
flagVestingEnd = "vesting-end-time"
flagVestingAmt = "vesting-amount"
) )
type printInfo struct { type printInfo struct {
@ -32,25 +34,25 @@ type printInfo struct {
AppMessage json.RawMessage `json:"app_message"` AppMessage json.RawMessage `json:"app_message"`
} }
// nolint: errcheck
func displayInfo(cdc *codec.Codec, info printInfo) error { func displayInfo(cdc *codec.Codec, info printInfo) error {
out, err := codec.MarshalJSONIndent(cdc, info) out, err := codec.MarshalJSONIndent(cdc, info)
if err != nil { if err != nil {
return err return err
} }
fmt.Fprintf(os.Stderr, "%s\n", string(out))
fmt.Fprintf(os.Stderr, "%s\n", string(out)) // nolint: errcheck
return nil return nil
} }
// get cmd to initialize all files for tendermint and application // InitCmd returns a command that initializes all files needed for Tendermint
// nolint // and the respective application.
func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { // nolint: golint
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "init", Use: "init [moniker]",
Short: "Initialize private validator, p2p, genesis, and application configuration files", Short: "Initialize private validator, p2p, genesis, and application configuration files",
Long: `Initialize validators's and node's configuration files.`, Long: `Initialize validators's and node's configuration files.`,
Args: cobra.NoArgs, Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, _ []string) error { RunE: func(_ *cobra.Command, args []string) error {
config := ctx.Config config := ctx.Config
config.SetRoot(viper.GetString(cli.HomeFlag)) config.SetRoot(viper.GetString(cli.HomeFlag))
@ -64,7 +66,7 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
return err return err
} }
config.Moniker = viper.GetString(flagMoniker) config.Moniker = args[0]
var appState json.RawMessage var appState json.RawMessage
genFile := config.GenesisFile() genFile := config.GenesisFile()
@ -81,7 +83,6 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
toPrint := newPrintInfo(config.Moniker, chainID, nodeID, "", appState) toPrint := newPrintInfo(config.Moniker, chainID, nodeID, "", appState)
cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config)
return displayInfo(cdc, toPrint) return displayInfo(cdc, toPrint)
}, },
} }
@ -89,8 +90,6 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory")
cmd.Flags().BoolP(flagOverwrite, "o", false, "overwrite the genesis.json file") cmd.Flags().BoolP(flagOverwrite, "o", false, "overwrite the genesis.json file")
cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
cmd.Flags().String(flagMoniker, "", "set the validator's moniker")
cmd.MarkFlagRequired(flagMoniker)
return cmd return cmd
} }

View File

@ -8,20 +8,16 @@ import (
"testing" "testing"
"time" "time"
"github.com/tendermint/tendermint/libs/cli"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/server/mock"
"github.com/spf13/viper"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
abciServer "github.com/tendermint/tendermint/abci/server" abciServer "github.com/tendermint/tendermint/abci/server"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/server/mock"
"github.com/spf13/viper"
) )
func TestInitCmd(t *testing.T) { func TestInitCmd(t *testing.T) {
@ -36,10 +32,7 @@ func TestInitCmd(t *testing.T) {
cdc := app.MakeCodec() cdc := app.MakeCodec()
cmd := InitCmd(ctx, cdc) cmd := InitCmd(ctx, cdc)
viper.Set(flagMoniker, "gaianode-test") require.NoError(t, cmd.RunE(nil, []string{"gaianode-test"}))
err = cmd.RunE(nil, nil)
require.NoError(t, err)
} }
func setupClientHome(t *testing.T) func() { func setupClientHome(t *testing.T) func() {
@ -64,11 +57,9 @@ func TestEmptyState(t *testing.T) {
ctx := server.NewContext(cfg, logger) ctx := server.NewContext(cfg, logger)
cdc := app.MakeCodec() cdc := app.MakeCodec()
viper.Set(flagMoniker, "gaianode-test")
cmd := InitCmd(ctx, cdc) cmd := InitCmd(ctx, cdc)
err = cmd.RunE(nil, nil) require.NoError(t, cmd.RunE(nil, []string{"gaianode-test"}))
require.NoError(t, err)
old := os.Stdout old := os.Stdout
r, w, _ := os.Pipe() r, w, _ := os.Pipe()
@ -88,7 +79,6 @@ func TestEmptyState(t *testing.T) {
w.Close() w.Close()
os.Stdout = old os.Stdout = old
out := <-outC out := <-outC
require.Contains(t, out, "WARNING: State is not initialized")
require.Contains(t, out, "genesis_time") require.Contains(t, out, "genesis_time")
require.Contains(t, out, "chain_id") require.Contains(t, out, "chain_id")
require.Contains(t, out, "consensus_params") require.Contains(t, out, "consensus_params")
@ -103,7 +93,6 @@ func TestStartStandAlone(t *testing.T) {
os.RemoveAll(home) os.RemoveAll(home)
}() }()
viper.Set(cli.HomeFlag, home) viper.Set(cli.HomeFlag, home)
viper.Set(client.FlagName, "moniker")
defer setupClientHome(t)() defer setupClientHome(t)()
logger := log.NewNopLogger() logger := log.NewNopLogger()
@ -112,8 +101,7 @@ func TestStartStandAlone(t *testing.T) {
ctx := server.NewContext(cfg, logger) ctx := server.NewContext(cfg, logger)
cdc := app.MakeCodec() cdc := app.MakeCodec()
initCmd := InitCmd(ctx, cdc) initCmd := InitCmd(ctx, cdc)
err = initCmd.RunE(nil, nil) require.NoError(t, initCmd.RunE(nil, []string{"gaianode-test"}))
require.NoError(t, err)
app, err := mock.NewApp(home, logger) app, err := mock.NewApp(home, logger)
require.Nil(t, err) require.Nil(t, err)

View File

@ -1,5 +1,7 @@
package init package init
// DONTCOVER
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -7,6 +9,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
@ -188,23 +192,31 @@ func initTestnet(config *tmconfig.Config, cdc *codec.Codec) error {
return err return err
} }
accTokens := staking.TokensFromTendermintPower(1000)
accStakingTokens := staking.TokensFromTendermintPower(500)
accs = append(accs, app.GenesisAccount{ accs = append(accs, app.GenesisAccount{
Address: addr, Address: addr,
Coins: sdk.Coins{ Coins: sdk.Coins{
sdk.NewInt64Coin(fmt.Sprintf("%stoken", nodeDirName), 1000), sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), accTokens),
sdk.NewInt64Coin(stakingtypes.DefaultBondDenom, 500), sdk.NewCoin(stakingtypes.DefaultBondDenom, accStakingTokens),
}, },
}) })
valTokens := staking.TokensFromTendermintPower(100)
msg := staking.NewMsgCreateValidator( msg := staking.NewMsgCreateValidator(
sdk.ValAddress(addr), sdk.ValAddress(addr),
valPubKeys[i], valPubKeys[i],
sdk.NewInt64Coin(stakingtypes.DefaultBondDenom, 100), sdk.NewCoin(stakingtypes.DefaultBondDenom, valTokens),
staking.NewDescription(nodeDirName, "", "", ""), staking.NewDescription(nodeDirName, "", "", ""),
staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
sdk.OneInt(),
) )
kb, err := keys.NewKeyBaseFromDir(clientDir)
if err != nil {
return err
}
tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo) tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo)
txBldr := authtx.NewTxBuilderFromCLI().WithChainID(chainID).WithMemo(memo) txBldr := authtx.NewTxBuilderFromCLI().WithChainID(chainID).WithMemo(memo).WithKeybase(kb)
signedTx, err := txBldr.SignStdTx(nodeDirName, app.DefaultKeyPass, tx, false) signedTx, err := txBldr.SignStdTx(nodeDirName, app.DefaultKeyPass, tx, false)
if err != nil { if err != nil {
@ -295,7 +307,7 @@ func collectGenFiles(
nodeID, valPubKey := nodeIDs[i], valPubKeys[i] nodeID, valPubKey := nodeIDs[i], valPubKeys[i]
initCfg := newInitConfig(chainID, gentxsDir, moniker, nodeID, valPubKey) initCfg := newInitConfig(chainID, gentxsDir, moniker, nodeID, valPubKey)
genDoc, err := loadGenesisDoc(cdc, config.GenesisFile()) genDoc, err := LoadGenesisDoc(cdc, config.GenesisFile())
if err != nil { if err != nil {
return err return err
} }

View File

@ -88,7 +88,8 @@ func InitializeNodeValidatorFiles(
return nodeID, valPubKey, nil return nodeID, valPubKey, nil
} }
func loadGenesisDoc(cdc *amino.Codec, genFile string) (genDoc types.GenesisDoc, err error) { // LoadGenesisDoc reads and unmarshals GenesisDoc from the given file.
func LoadGenesisDoc(cdc *amino.Codec, genFile string) (genDoc types.GenesisDoc, err error) {
genContents, err := ioutil.ReadFile(genFile) genContents, err := ioutil.ReadFile(genFile)
if err != nil { if err != nil {
return genDoc, err return genDoc, err

View File

@ -0,0 +1,49 @@
package init
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"testing"
"time"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/tests"
"github.com/stretchr/testify/require"
)
func TestExportGenesisFileWithTime(t *testing.T) {
t.Parallel()
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
fname := filepath.Join(dir, "genesis.json")
require.NoError(t, ExportGenesisFileWithTime(fname, "test", nil, json.RawMessage(""), time.Now()))
}
func TestLoadGenesisDoc(t *testing.T) {
t.Parallel()
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
fname := filepath.Join(dir, "genesis.json")
require.NoError(t, ExportGenesisFileWithTime(fname, "test", nil, json.RawMessage(""), time.Now()))
_, err := LoadGenesisDoc(codec.Cdc, fname)
require.NoError(t, err)
// Non-existing file
_, err = LoadGenesisDoc(codec.Cdc, "non-existing-file")
require.Error(t, err)
malformedFilename := filepath.Join(dir, "malformed")
malformedFile, err := os.Create(malformedFilename)
require.NoError(t, err)
fmt.Fprint(malformedFile, "invalidjson")
malformedFile.Close()
// Non-existing file
_, err = LoadGenesisDoc(codec.Cdc, malformedFilename)
require.Error(t, err)
}

View File

@ -0,0 +1,51 @@
package init
import (
"fmt"
"os"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/server"
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/types"
)
// Validate genesis command takes
func ValidateGenesisCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "validate-genesis [file]",
Args: cobra.RangeArgs(0, 1),
Short: "validates the genesis file at the default location or at the location passed as an arg",
RunE: func(cmd *cobra.Command, args []string) (err error) {
// Load default if passed no args, otherwise load passed file
var genesis string
if len(args) == 0 {
genesis = ctx.Config.GenesisFile()
} else {
genesis = args[0]
}
//nolint
fmt.Fprintf(os.Stderr, "validating genesis file at %s\n", genesis)
var genDoc types.GenesisDoc
if genDoc, err = LoadGenesisDoc(cdc, genesis); err != nil {
return fmt.Errorf("Error loading genesis doc from %s: %s", genesis, err.Error())
}
var genstate app.GenesisState
if err = cdc.UnmarshalJSON(genDoc.AppState, &genstate); err != nil {
return fmt.Errorf("Error unmarshaling genesis doc %s: %s", genesis, err.Error())
}
if err = app.GaiaValidateGenesisState(genstate); err != nil {
return fmt.Errorf("Error validating genesis file %s: %s", genesis, err.Error())
}
fmt.Printf("File at %s is a valid genesis file for gaiad\n", genesis)
return nil
},
}
}

View File

@ -14,6 +14,7 @@ package hd
import ( import (
"crypto/hmac" "crypto/hmac"
"crypto/sha512" "crypto/sha512"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
@ -62,19 +63,7 @@ func NewParamsFromPath(path string) (*BIP44Params, error) {
return nil, fmt.Errorf("path length is wrong. Expected 5, got %d", len(spl)) return nil, fmt.Errorf("path length is wrong. Expected 5, got %d", len(spl))
} }
if spl[0] != "44'" { // Check items can be parsed
return nil, fmt.Errorf("first field in path must be 44', got %v", spl[0])
}
if !isHardened(spl[1]) || !isHardened(spl[2]) {
return nil,
fmt.Errorf("second and third field in path must be hardened (ie. contain the suffix ', got %v and %v", spl[1], spl[2])
}
if isHardened(spl[3]) || isHardened(spl[4]) {
return nil,
fmt.Errorf("fourth and fifth field in path must not be hardened (ie. not contain the suffix ', got %v and %v", spl[3], spl[4])
}
purpose, err := hardenedInt(spl[0]) purpose, err := hardenedInt(spl[0])
if err != nil { if err != nil {
return nil, err return nil, err
@ -91,15 +80,30 @@ func NewParamsFromPath(path string) (*BIP44Params, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !(change == 0 || change == 1) {
return nil, fmt.Errorf("change field can only be 0 or 1")
}
addressIdx, err := hardenedInt(spl[4]) addressIdx, err := hardenedInt(spl[4])
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Confirm valid values
if spl[0] != "44'" {
return nil, fmt.Errorf("first field in path must be 44', got %v", spl[0])
}
if !isHardened(spl[1]) || !isHardened(spl[2]) {
return nil,
fmt.Errorf("second and third field in path must be hardened (ie. contain the suffix ', got %v and %v", spl[1], spl[2])
}
if isHardened(spl[3]) || isHardened(spl[4]) {
return nil,
fmt.Errorf("fourth and fifth field in path must not be hardened (ie. not contain the suffix ', got %v and %v", spl[3], spl[4])
}
if !(change == 0 || change == 1) {
return nil, fmt.Errorf("change field can only be 0 or 1")
}
return &BIP44Params{ return &BIP44Params{
purpose: purpose, purpose: purpose,
coinType: coinType, coinType: coinType,
@ -132,7 +136,7 @@ func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params {
return NewParams(44, 118, account, false, addressIdx) return NewParams(44, 118, account, false, addressIdx)
} }
// Return the BIP44 fields as an array. // DerivationPath returns the BIP44 fields as an array.
func (p BIP44Params) DerivationPath() []uint32 { func (p BIP44Params) DerivationPath() []uint32 {
change := uint32(0) change := uint32(0)
if p.change { if p.change {
@ -251,8 +255,10 @@ func i64(key []byte, data []byte) (IL [32]byte, IR [32]byte) {
mac := hmac.New(sha512.New, key) mac := hmac.New(sha512.New, key)
// sha512 does not err // sha512 does not err
_, _ = mac.Write(data) _, _ = mac.Write(data)
I := mac.Sum(nil) I := mac.Sum(nil)
copy(IL[:], I[:32]) copy(IL[:], I[:32])
copy(IR[:], I[32:]) copy(IR[:], I[32:])
return return
} }

View File

@ -5,9 +5,9 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/cosmos/go-bip39" "github.com/cosmos/go-bip39"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
var defaultBIP39Passphrase = "" var defaultBIP39Passphrase = ""
@ -21,7 +21,27 @@ func mnemonicToSeed(mnemonic string) []byte {
func ExampleStringifyPathParams() { func ExampleStringifyPathParams() {
path := NewParams(44, 0, 0, false, 0) path := NewParams(44, 0, 0, false, 0)
fmt.Println(path.String()) fmt.Println(path.String())
// Output: 44'/0'/0'/0/0 path = NewParams(44, 33, 7, true, 9)
fmt.Println(path.String())
// Output:
// 44'/0'/0'/0/0
// 44'/33'/7'/1/9
}
func TestStringifyFundraiserPathParams(t *testing.T) {
path := NewFundraiserParams(4, 22)
require.Equal(t, "44'/118'/4'/0/22", path.String())
path = NewFundraiserParams(4, 57)
require.Equal(t, "44'/118'/4'/0/57", path.String())
}
func TestPathToArray(t *testing.T) {
path := NewParams(44, 118, 1, false, 4)
require.Equal(t, "[44 118 1 0 4]", fmt.Sprintf("%v", path.DerivationPath()))
path = NewParams(44, 118, 2, true, 15)
require.Equal(t, "[44 118 2 1 15]", fmt.Sprintf("%v", path.DerivationPath()))
} }
func TestParamsFromPath(t *testing.T) { func TestParamsFromPath(t *testing.T) {
@ -60,6 +80,11 @@ func TestParamsFromPath(t *testing.T) {
{"44'/0'/0'/0/0'"}, // fifth field must not have ' {"44'/0'/0'/0/0'"}, // fifth field must not have '
{"44'/-1'/0'/0/0"}, // no negatives {"44'/-1'/0'/0/0"}, // no negatives
{"44'/0'/0'/-1/0"}, // no negatives {"44'/0'/0'/-1/0"}, // no negatives
{"a'/0'/0'/-1/0"}, // valid values
{"0/X/0'/-1/0"}, // valid values
{"44'/0'/X/-1/0"}, // valid values
{"44'/0'/0'/%/0"}, // valid values
{"44'/0'/0'/0/%"}, // valid values
} }
for i, c := range badCases { for i, c := range badCases {
@ -80,14 +105,39 @@ func ExampleSomeBIP32TestVecs() {
fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)") fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)")
fmt.Println() fmt.Println()
// cosmos // cosmos
priv, _ := DerivePrivateKeyForPath(master, ch, FullFundraiserPath) priv, err := DerivePrivateKeyForPath(master, ch, FullFundraiserPath)
fmt.Println(hex.EncodeToString(priv[:])) if err != nil {
fmt.Println("INVALID")
} else {
fmt.Println(hex.EncodeToString(priv[:]))
}
// bitcoin // bitcoin
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0") priv, err = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0")
fmt.Println(hex.EncodeToString(priv[:])) if err != nil {
fmt.Println("INVALID")
} else {
fmt.Println(hex.EncodeToString(priv[:]))
}
// ether // ether
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0") priv, err = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0")
fmt.Println(hex.EncodeToString(priv[:])) if err != nil {
fmt.Println("INVALID")
} else {
fmt.Println(hex.EncodeToString(priv[:]))
}
// INVALID
priv, err = DerivePrivateKeyForPath(master, ch, "X/0'/0'/0/0")
if err != nil {
fmt.Println("INVALID")
} else {
fmt.Println(hex.EncodeToString(priv[:]))
}
priv, err = DerivePrivateKeyForPath(master, ch, "-44/0'/0'/0/0")
if err != nil {
fmt.Println("INVALID")
} else {
fmt.Println(hex.EncodeToString(priv[:]))
}
fmt.Println() fmt.Println()
fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html") fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html")
@ -121,6 +171,8 @@ func ExampleSomeBIP32TestVecs() {
// bfcb217c058d8bbafd5e186eae936106ca3e943889b0b4a093ae13822fd3170c // bfcb217c058d8bbafd5e186eae936106ca3e943889b0b4a093ae13822fd3170c
// e77c3de76965ad89997451de97b95bb65ede23a6bf185a55d80363d92ee37c3d // e77c3de76965ad89997451de97b95bb65ede23a6bf185a55d80363d92ee37c3d
// 7fc4d8a8146dea344ba04c593517d3f377fa6cded36cd55aee0a0bb968e651bc // 7fc4d8a8146dea344ba04c593517d3f377fa6cded36cd55aee0a0bb968e651bc
// INVALID
// INVALID
// //
// keys generated via https://coinomi.com/recovery-phrase-tool.html // keys generated via https://coinomi.com/recovery-phrase-tool.html
// //

View File

@ -4,23 +4,23 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"os" "os"
"reflect"
"strings" "strings"
"github.com/pkg/errors" "errors"
"github.com/cosmos/go-bip39"
"github.com/cosmos/cosmos-sdk/crypto" "github.com/cosmos/cosmos-sdk/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd" "github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
"github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/go-bip39"
tmcrypto "github.com/tendermint/tendermint/crypto" tmcrypto "github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/crypto/encoding/amino"
"github.com/tendermint/tendermint/crypto/secp256k1" "github.com/tendermint/tendermint/crypto/secp256k1"
dbm "github.com/tendermint/tendermint/libs/db" dbm "github.com/tendermint/tendermint/libs/db"
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
) )
var _ Keybase = dbKeybase{} var _ Keybase = dbKeybase{}
@ -30,6 +30,7 @@ var _ Keybase = dbKeybase{}
// Find a list of all supported languages in the BIP 39 spec (word lists). // Find a list of all supported languages in the BIP 39 spec (word lists).
type Language int type Language int
//noinspection ALL
const ( const (
// English is the default language to create a mnemonic. // English is the default language to create a mnemonic.
// It is the only supported language by this package. // It is the only supported language by this package.
@ -54,7 +55,7 @@ const (
const ( const (
// used for deriving seed from mnemonic // used for deriving seed from mnemonic
defaultBIP39Passphrase = "" DefaultBIP39Passphrase = ""
// bits of entropy to draw when creating a mnemonic // bits of entropy to draw when creating a mnemonic
defaultEntropySize = 256 defaultEntropySize = 256
@ -83,6 +84,9 @@ func New(db dbm.DB) Keybase {
} }
} }
// NewInMemory creates a new keybase on top of in-memory storage instance.
func NewInMemory() Keybase { return dbKeybase{dbm.NewMemDB()} }
// CreateMnemonic generates a new key and persists it to storage, encrypted // CreateMnemonic generates a new key and persists it to storage, encrypted
// using the provided password. // using the provided password.
// It returns the generated mnemonic and the key Info. // It returns the generated mnemonic and the key Info.
@ -109,41 +113,15 @@ func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string
return return
} }
seed := bip39.NewSeed(mnemonic, defaultBIP39Passphrase) seed := bip39.NewSeed(mnemonic, DefaultBIP39Passphrase)
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath) info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
return return
} }
// TEMPORARY METHOD UNTIL WE FIGURE OUT USER FACING HD DERIVATION API // CreateAccount converts a mnemonic to a private key and persists it, encrypted with the given password.
func (kb dbKeybase) CreateKey(name, mnemonic, passwd string) (info Info, err error) { func (kb dbKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) {
words := strings.Split(mnemonic, " ") hdPath := hd.NewFundraiserParams(account, index)
if len(words) != 12 && len(words) != 24 { return kb.Derive(name, mnemonic, bip39Passwd, encryptPasswd, *hdPath)
err = fmt.Errorf("recovering only works with 12 word (fundraiser) or 24 word mnemonics, got: %v words", len(words))
return
}
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase)
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.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase)
if err != nil {
return
}
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
return
} }
func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params) (info Info, err error) { func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params) (info Info, err error) {
@ -151,29 +129,32 @@ func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string
if err != nil { if err != nil {
return return
} }
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String())
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String())
return return
} }
// CreateLedger creates a new locally-stored reference to a Ledger keypair // 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 // 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) { func (kb dbKeybase) CreateLedger(name string, algo SigningAlgo, account uint32, index uint32) (Info, error) {
if algo != Secp256k1 { if algo != Secp256k1 {
return nil, ErrUnsupportedSigningAlgo return nil, ErrUnsupportedSigningAlgo
} }
priv, err := crypto.NewPrivKeyLedgerSecp256k1(path)
hdPath := hd.NewFundraiserParams(account, index)
priv, err := crypto.NewPrivKeyLedgerSecp256k1(*hdPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
pub := priv.PubKey() pub := priv.PubKey()
return kb.writeLedgerKey(pub, path, name), nil
return kb.writeLedgerKey(name, pub, *hdPath), nil
} }
// CreateOffline creates a new reference to an offline keypair // CreateOffline creates a new reference to an offline keypair
// It returns the created key info // It returns the created key info
func (kb dbKeybase) CreateOffline(name string, pub tmcrypto.PubKey) (Info, error) { func (kb dbKeybase) CreateOffline(name string, pub tmcrypto.PubKey) (Info, error) {
return kb.writeOfflineKey(pub, name), nil return kb.writeOfflineKey(name, pub), nil
} }
func (kb *dbKeybase) persistDerivedKey(seed []byte, passwd, name, fullHdPath string) (info Info, err error) { func (kb *dbKeybase) persistDerivedKey(seed []byte, passwd, name, fullHdPath string) (info Info, err error) {
@ -187,10 +168,10 @@ func (kb *dbKeybase) persistDerivedKey(seed []byte, passwd, name, fullHdPath str
// if we have a password, use it to encrypt the private key and store it // if we have a password, use it to encrypt the private key and store it
// else store the public key only // else store the public key only
if passwd != "" { if passwd != "" {
info = kb.writeLocalKey(secp256k1.PrivKeySecp256k1(derivedPriv), name, passwd) info = kb.writeLocalKey(name, secp256k1.PrivKeySecp256k1(derivedPriv), passwd)
} else { } else {
pubk := secp256k1.PrivKeySecp256k1(derivedPriv).PubKey() pubk := secp256k1.PrivKeySecp256k1(derivedPriv).PubKey()
info = kb.writeOfflineKey(pubk, name) info = kb.writeOfflineKey(name, pubk)
} }
return return
} }
@ -362,7 +343,7 @@ func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
if err != nil { if err != nil {
return return
} }
kb.writeOfflineKey(pubKey, name) kb.writeOfflineKey(name, pubKey)
return return
} }
@ -410,10 +391,10 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro
if err != nil { if err != nil {
return err return err
} }
kb.writeLocalKey(key, name, newpass) kb.writeLocalKey(name, key, newpass)
return nil return nil
default: default:
return fmt.Errorf("locally stored key required") return fmt.Errorf("locally stored key required. Received: %v", reflect.TypeOf(info).String())
} }
} }
@ -422,29 +403,29 @@ func (kb dbKeybase) CloseDB() {
kb.db.Close() kb.db.Close()
} }
func (kb dbKeybase) writeLocalKey(priv tmcrypto.PrivKey, name, passphrase string) Info { func (kb dbKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase string) Info {
// encrypt private key using passphrase // encrypt private key using passphrase
privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase) privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase)
// make Info // make Info
pub := priv.PubKey() pub := priv.PubKey()
info := newLocalInfo(name, pub, privArmor) info := newLocalInfo(name, pub, privArmor)
kb.writeInfo(info, name) kb.writeInfo(name, info)
return info return info
} }
func (kb dbKeybase) writeLedgerKey(pub tmcrypto.PubKey, path crypto.DerivationPath, name string) Info { func (kb dbKeybase) writeLedgerKey(name string, pub tmcrypto.PubKey, path hd.BIP44Params) Info {
info := newLedgerInfo(name, pub, path) info := newLedgerInfo(name, pub, path)
kb.writeInfo(info, name) kb.writeInfo(name, info)
return info return info
} }
func (kb dbKeybase) writeOfflineKey(pub tmcrypto.PubKey, name string) Info { func (kb dbKeybase) writeOfflineKey(name string, pub tmcrypto.PubKey) Info {
info := newOfflineInfo(name, pub) info := newOfflineInfo(name, pub)
kb.writeInfo(info, name) kb.writeInfo(name, info)
return info return info
} }
func (kb dbKeybase) writeInfo(info Info, name string) { func (kb dbKeybase) writeInfo(name string, info Info) {
// write the info by key // write the info by key
key := infoKey(name) key := infoKey(name)
kb.db.SetSync(key, writeInfo(info)) kb.db.SetSync(key, writeInfo(info))

View File

@ -2,6 +2,7 @@ package keys
import ( import (
"fmt" "fmt"
"io/ioutil"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -9,26 +10,84 @@ import (
"github.com/cosmos/cosmos-sdk/crypto/keys/hd" "github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
"github.com/cosmos/cosmos-sdk/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/ed25519"
dbm "github.com/tendermint/tendermint/libs/db" dbm "github.com/tendermint/tendermint/libs/db"
"github.com/cosmos/cosmos-sdk/types"
) )
func init() { func init() {
mintkey.BcryptSecurityParameter = 1 mintkey.BcryptSecurityParameter = 1
} }
func TestKeybaseOpenClose(t *testing.T) {
dir, err := ioutil.TempDir("", "TestKeybaseOpenClose")
assert.Nil(t, err)
kb := New(dbm.NewDB("TestKeybaseOpenClose", dbm.LevelDBBackend, dir))
kb.CloseDB()
// The DB has been closed. At the moment, the expected behaviour is to panic
assert.Panics(t, func() {
_, _ = kb.CreateAccount(
"some_account",
"key pair crucial catch public canyon evil outer stage ten gym tornado",
"", "", 0, 1)
})
}
func TestLanguage(t *testing.T) {
kb := New(dbm.NewMemDB())
_, _, err := kb.CreateMnemonic("something", Japanese, "no_pass", Secp256k1)
assert.Error(t, err)
assert.Equal(t, "unsupported language: only english is supported", err.Error())
}
func TestCreateAccountInvalidMnemonic(t *testing.T) {
kb := New(dbm.NewMemDB())
_, err := kb.CreateAccount(
"some_account",
"malarkey pair crucial catch public canyon evil outer stage ten gym tornado",
"", "", 0, 1)
assert.Error(t, err)
assert.Equal(t, "Invalid mnemonic", err.Error())
}
func TestCreateLedgerUnsupportedAlgo(t *testing.T) {
kb := New(dbm.NewMemDB())
_, err := kb.CreateLedger("some_account", Ed25519, 0, 1)
assert.Error(t, err)
assert.Equal(t, "unsupported signing algo: only secp256k1 is supported", err.Error())
}
func TestCreateLedger(t *testing.T) {
kb := New(dbm.NewMemDB())
// test_cover and test_unit will result in different answers
// test_cover does not compile some dependencies so ledger is disabled
// test_unit may add a ledger mock
// both cases are acceptable
ledger, err := kb.CreateLedger("some_account", Secp256k1, 0, 1)
if err != nil {
assert.Error(t, err)
assert.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error())
assert.Nil(t, ledger)
} else {
// The mock is available, check that the address is correct
pubKey := ledger.GetPubKey()
addr, err := sdk.Bech32ifyAccPub(pubKey)
assert.NoError(t, err)
assert.Equal(t, "cosmospub1addwnpepqfsdqjr68h7wjg5wacksmqaypasnra232fkgu5sxdlnlu8j22ztxvlqvd65", addr)
}
}
// TestKeyManagement makes sure we can manipulate these keys well // TestKeyManagement makes sure we can manipulate these keys well
func TestKeyManagement(t *testing.T) { func TestKeyManagement(t *testing.T) {
// make the storage with reasonable defaults // make the storage with reasonable defaults
db := dbm.NewMemDB() db := dbm.NewMemDB()
cstore := New( cstore := New(db)
db,
)
algo := Secp256k1 algo := Secp256k1
n1, n2, n3 := "personal", "business", "other" n1, n2, n3 := "personal", "business", "other"
@ -112,9 +171,7 @@ func TestKeyManagement(t *testing.T) {
// TestSignVerify does some detailed checks on how we sign and validate // TestSignVerify does some detailed checks on how we sign and validate
// signatures // signatures
func TestSignVerify(t *testing.T) { func TestSignVerify(t *testing.T) {
cstore := New( cstore := New(dbm.NewMemDB())
dbm.NewMemDB(),
)
algo := Secp256k1 algo := Secp256k1
n1, n2, n3 := "some dude", "a dudette", "dude-ish" n1, n2, n3 := "some dude", "a dudette", "dude-ish"
@ -344,7 +401,7 @@ func TestSeedPhrase(t *testing.T) {
// let us re-create it from the mnemonic-phrase // let us re-create it from the mnemonic-phrase
params := *hd.NewFundraiserParams(0, 0) params := *hd.NewFundraiserParams(0, 0)
newInfo, err := cstore.Derive(n2, mnemonic, defaultBIP39Passphrase, p2, params) newInfo, err := cstore.Derive(n2, mnemonic, DefaultBIP39Passphrase, p2, params)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, n2, newInfo.GetName()) require.Equal(t, n2, newInfo.GetName())
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address()) require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())

166
crypto/keys/lazy_keybase.go Normal file
View File

@ -0,0 +1,166 @@
package keys
import (
"github.com/tendermint/tendermint/crypto"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/types"
)
var _ Keybase = lazyKeybase{}
type lazyKeybase struct {
name string
dir string
}
func NewLazyKeybase(name, dir string) Keybase {
return lazyKeybase{name: name, dir: dir}
}
func (lkb lazyKeybase) List() ([]Info, error) {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return nil, err
}
defer db.Close()
return New(db).List()
}
func (lkb lazyKeybase) Get(name string) (Info, error) {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return nil, err
}
defer db.Close()
return New(db).Get(name)
}
func (lkb lazyKeybase) GetByAddress(address types.AccAddress) (Info, error) {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return nil, err
}
defer db.Close()
return New(db).GetByAddress(address)
}
func (lkb lazyKeybase) Delete(name, passphrase string, skipPass bool) error {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return err
}
defer db.Close()
return New(db).Delete(name, passphrase, skipPass)
}
func (lkb lazyKeybase) Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return nil, nil, err
}
defer db.Close()
return New(db).Sign(name, passphrase, msg)
}
func (lkb lazyKeybase) CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return nil, "", err
}
defer db.Close()
return New(db).CreateMnemonic(name, language, passwd, algo)
}
func (lkb lazyKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return nil, err
}
defer db.Close()
return New(db).CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, account, index)
}
func (lkb lazyKeybase) Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error) {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return nil, err
}
defer db.Close()
return New(db).Derive(name, mnemonic, bip39Passwd, encryptPasswd, params)
}
func (lkb lazyKeybase) CreateLedger(name string, algo SigningAlgo, account uint32, index uint32) (info Info, err error) {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return nil, err
}
defer db.Close()
return New(db).CreateLedger(name, algo, account, index)
}
func (lkb lazyKeybase) CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return nil, err
}
defer db.Close()
return New(db).CreateOffline(name, pubkey)
}
func (lkb lazyKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return err
}
defer db.Close()
return New(db).Update(name, oldpass, getNewpass)
}
func (lkb lazyKeybase) Import(name string, armor string) (err error) {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return err
}
defer db.Close()
return New(db).Import(name, armor)
}
func (lkb lazyKeybase) ImportPubKey(name string, armor string) (err error) {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return err
}
defer db.Close()
return New(db).ImportPubKey(name, armor)
}
func (lkb lazyKeybase) Export(name string) (armor string, err error) {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return "", err
}
defer db.Close()
return New(db).Export(name)
}
func (lkb lazyKeybase) ExportPubKey(name string) (armor string, err error) {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return "", err
}
defer db.Close()
return New(db).ExportPubKey(name)
}
func (lkb lazyKeybase) ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) {
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
if err != nil {
return nil, err
}
defer db.Close()
return New(db).ExportPrivateKeyObject(name, passphrase)
}
func (lkb lazyKeybase) CloseDB() {}

View File

@ -3,8 +3,6 @@ package keys
import ( import (
"github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto"
ccrypto "github.com/cosmos/cosmos-sdk/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd" "github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types"
) )
@ -23,20 +21,20 @@ type Keybase interface {
// CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic // CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic
// key from that. // key from that.
CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) 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) // CreateAccount creates an account based using the BIP44 path (44'/118'/{account}'/0/{index}
// CreateFundraiserKey takes a mnemonic and derives, a password CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error)
CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error)
// Compute a BIP39 seed from th mnemonic and bip39Passwd. // Derive computes a BIP39 seed from th mnemonic and bip39Passwd.
// Derive private key from the seed using the BIP44 params. // Derive private key from the seed using the BIP44 params.
// Encrypt the key to disk using encryptPasswd. // Encrypt the key to disk using encryptPasswd.
// See https://github.com/cosmos/cosmos-sdk/issues/2095 // See https://github.com/cosmos/cosmos-sdk/issues/2095
Derive(name, mnemonic, bip39Passwd, Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error)
encryptPasswd string, params hd.BIP44Params) (Info, error)
// Create, store, and return a new Ledger key reference
CreateLedger(name string, path ccrypto.DerivationPath, algo SigningAlgo) (info Info, err error)
// Create, store, and return a new offline key reference // CreateLedger creates, stores, and returns a new Ledger key reference
CreateLedger(name string, algo SigningAlgo, account uint32, index uint32) (info Info, err error)
// CreateOffline creates, stores, and returns a new offline key reference
CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error)
// The following operations will *only* work on locally-stored keys // The following operations will *only* work on locally-stored keys
@ -46,10 +44,10 @@ type Keybase interface {
Export(name string) (armor string, err error) Export(name string) (armor string, err error)
ExportPubKey(name string) (armor string, err error) ExportPubKey(name string) (armor string, err error)
// *only* works on locally-stored keys. Temporary method until we redo the exporting API // ExportPrivateKeyObject *only* works on locally-stored keys. Temporary method until we redo the exporting API
ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error)
// Close closes the database. // CloseDB closes the database.
CloseDB() CloseDB()
} }
@ -123,12 +121,12 @@ func (i localInfo) GetAddress() types.AccAddress {
// ledgerInfo is the public information about a Ledger key // ledgerInfo is the public information about a Ledger key
type ledgerInfo struct { type ledgerInfo struct {
Name string `json:"name"` Name string `json:"name"`
PubKey crypto.PubKey `json:"pubkey"` PubKey crypto.PubKey `json:"pubkey"`
Path ccrypto.DerivationPath `json:"path"` Path hd.BIP44Params `json:"path"`
} }
func newLedgerInfo(name string, pub crypto.PubKey, path ccrypto.DerivationPath) Info { func newLedgerInfo(name string, pub crypto.PubKey, path hd.BIP44Params) Info {
return &ledgerInfo{ return &ledgerInfo{
Name: name, Name: name,
PubKey: pub, PubKey: pub,

79
crypto/ledger_mock.go Normal file
View File

@ -0,0 +1,79 @@
// +build ledger,test_ledger_mock
package crypto
import (
"github.com/btcsuite/btcd/btcec"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/tests"
"github.com/cosmos/go-bip39"
"github.com/pkg/errors"
secp256k1 "github.com/tendermint/btcd/btcec"
"github.com/tendermint/tendermint/crypto"
)
// If ledger support (build tag) has been enabled, which implies a CGO dependency,
// set the discoverLedger function which is responsible for loading the Ledger
// device at runtime or returning an error.
func init() {
discoverLedger = func() (LedgerSECP256K1, error) {
return LedgerSECP256K1Mock{}, nil
}
}
type LedgerSECP256K1Mock struct {
}
func (mock LedgerSECP256K1Mock) Close() error {
return nil
}
func (mock LedgerSECP256K1Mock) GetPublicKeySECP256K1(derivationPath []uint32) ([]byte, error) {
if derivationPath[0] != 44 {
return nil, errors.New("Invalid derivation path")
}
if derivationPath[1] != 118 {
return nil, errors.New("Invalid derivation path")
}
seed, err := bip39.NewSeedWithErrorChecking(tests.TestMnemonic, "")
if err != nil {
return nil, err
}
path := hd.NewParams(derivationPath[0], derivationPath[1], derivationPath[2], derivationPath[3] != 0, derivationPath[4])
masterPriv, ch := hd.ComputeMastersFromSeed(seed)
derivedPriv, err := hd.DerivePrivateKeyForPath(masterPriv, ch, path.String())
if err != nil {
return nil, err
}
_, pubkeyObject := secp256k1.PrivKeyFromBytes(secp256k1.S256(), derivedPriv[:])
return pubkeyObject.SerializeUncompressed(), nil
}
func (mock LedgerSECP256K1Mock) SignSECP256K1(derivationPath []uint32, message []byte) ([]byte, error) {
path := hd.NewParams(derivationPath[0], derivationPath[1], derivationPath[2], derivationPath[3] != 0, derivationPath[4])
seed, err := bip39.NewSeedWithErrorChecking(tests.TestMnemonic, "")
if err != nil {
return nil, err
}
masterPriv, ch := hd.ComputeMastersFromSeed(seed)
derivedPriv, err := hd.DerivePrivateKeyForPath(masterPriv, ch, path.String())
if err != nil {
return nil, err
}
priv, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), derivedPriv[:])
sig, err := priv.Sign(crypto.Sha256(message))
if err != nil {
return nil, err
}
// Need to return DER as the ledger does
sig2 := btcec.Signature{R: sig.R, S: sig.S}
return sig2.Serialize(), nil
}

17
crypto/ledger_notavail.go Normal file
View File

@ -0,0 +1,17 @@
// +build !cgo !ledger
// test_ledger_mock
package crypto
import (
"github.com/pkg/errors"
)
// If ledger support (build tag) has been enabled, which implies a CGO dependency,
// set the discoverLedger function which is responsible for loading the Ledger
// device at runtime or returning an error.
func init() {
discoverLedger = func() (LedgerSECP256K1, error) {
return nil, errors.New("support for ledger devices is not available in this executable")
}
}

View File

@ -1,10 +1,8 @@
// +build cgo,ledger // +build cgo,ledger,!test_ledger_mock
package crypto package crypto
import ( import ledger "github.com/zondax/ledger-cosmos-go"
ledger "github.com/zondax/ledger-cosmos-go"
)
// If ledger support (build tag) has been enabled, which implies a CGO dependency, // If ledger support (build tag) has been enabled, which implies a CGO dependency,
// set the discoverLedger function which is responsible for loading the Ledger // set the discoverLedger function which is responsible for loading the Ledger

View File

@ -2,12 +2,15 @@ package crypto
import ( import (
"fmt" "fmt"
"os"
"github.com/btcsuite/btcd/btcec"
"github.com/pkg/errors" "github.com/pkg/errors"
tmbtcec "github.com/tendermint/btcd/btcec"
secp256k1 "github.com/btcsuite/btcd/btcec"
tmcrypto "github.com/tendermint/tendermint/crypto" tmcrypto "github.com/tendermint/tendermint/crypto"
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1" tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
) )
var ( var (
@ -22,12 +25,10 @@ type (
// dependencies when Ledger support is potentially not enabled. // dependencies when Ledger support is potentially not enabled.
discoverLedgerFn func() (LedgerSECP256K1, error) discoverLedgerFn func() (LedgerSECP256K1, error)
// DerivationPath represents a Ledger derivation path.
DerivationPath []uint32
// LedgerSECP256K1 reflects an interface a Ledger API must implement for // LedgerSECP256K1 reflects an interface a Ledger API must implement for
// the SECP256K1 scheme. // the SECP256K1 scheme.
LedgerSECP256K1 interface { LedgerSECP256K1 interface {
Close() error
GetPublicKeySECP256K1([]uint32) ([]byte, error) GetPublicKeySECP256K1([]uint32) ([]byte, error)
SignSECP256K1([]uint32, []byte) ([]byte, error) SignSECP256K1([]uint32, []byte) ([]byte, error)
} }
@ -39,35 +40,25 @@ type (
// go-amino so we can view the address later, even without having the // go-amino so we can view the address later, even without having the
// ledger attached. // ledger attached.
CachedPubKey tmcrypto.PubKey CachedPubKey tmcrypto.PubKey
Path DerivationPath Path hd.BIP44Params
ledger LedgerSECP256K1
} }
) )
// NewPrivKeyLedgerSecp256k1 will generate a new key and store the public key // NewPrivKeyLedgerSecp256k1 will generate a new key and store the public key
// for later use. // for later use.
// func NewPrivKeyLedgerSecp256k1(path hd.BIP44Params) (tmcrypto.PrivKey, error) {
// CONTRACT: The ledger device, ledgerDevice, must be loaded and set prior to device, err := getLedgerDevice()
// any creation of a PrivKeyLedgerSecp256k1.
func NewPrivKeyLedgerSecp256k1(path DerivationPath) (tmcrypto.PrivKey, error) {
if discoverLedger == nil {
return nil, errors.New("no Ledger discovery function defined")
}
device, err := discoverLedger()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to create PrivKeyLedgerSecp256k1") return nil, err
} }
defer warnIfErrors(device.Close)
pkl := &PrivKeyLedgerSecp256k1{Path: path, ledger: device} pubKey, err := getPubKey(device, path)
pubKey, err := pkl.getPubKey()
if err != nil { if err != nil {
return nil, err return nil, err
} }
pkl.CachedPubKey = pubKey return PrivKeyLedgerSecp256k1{pubKey, path}, nil
return pkl, err
} }
// PubKey returns the cached public key. // PubKey returns the cached public key.
@ -75,21 +66,27 @@ func (pkl PrivKeyLedgerSecp256k1) PubKey() tmcrypto.PubKey {
return pkl.CachedPubKey return pkl.CachedPubKey
} }
// Sign returns a secp256k1 signature for the corresponding message
func (pkl PrivKeyLedgerSecp256k1) Sign(message []byte) ([]byte, error) {
device, err := getLedgerDevice()
if err != nil {
return nil, err
}
defer warnIfErrors(device.Close)
return sign(device, pkl, message)
}
// ValidateKey allows us to verify the sanity of a public key after loading it // ValidateKey allows us to verify the sanity of a public key after loading it
// from disk. // from disk.
func (pkl PrivKeyLedgerSecp256k1) ValidateKey() error { func (pkl PrivKeyLedgerSecp256k1) ValidateKey() error {
// getPubKey will return an error if the ledger is not device, err := getLedgerDevice()
pub, err := pkl.getPubKey()
if err != nil { if err != nil {
return err return err
} }
defer warnIfErrors(device.Close)
// verify this matches cached address return validateKey(device, pkl)
if !pub.Equals(pkl.CachedPubKey) {
return fmt.Errorf("cached key does not match retrieved key")
}
return nil
} }
// AssertIsPrivKeyInner implements the PrivKey interface. It performs a no-op. // AssertIsPrivKeyInner implements the PrivKey interface. It performs a no-op.
@ -104,11 +101,54 @@ func (pkl PrivKeyLedgerSecp256k1) Bytes() []byte {
// Equals implements the PrivKey interface. It makes sure two private keys // Equals implements the PrivKey interface. It makes sure two private keys
// refer to the same public key. // refer to the same public key.
func (pkl PrivKeyLedgerSecp256k1) Equals(other tmcrypto.PrivKey) bool { func (pkl PrivKeyLedgerSecp256k1) Equals(other tmcrypto.PrivKey) bool {
if ledger, ok := other.(*PrivKeyLedgerSecp256k1); ok { if otherKey, ok := other.(PrivKeyLedgerSecp256k1); ok {
return pkl.CachedPubKey.Equals(ledger.CachedPubKey) return pkl.CachedPubKey.Equals(otherKey.CachedPubKey)
}
return false
}
// warnIfErrors wraps a function and writes a warning to stderr. This is required
// to avoid ignoring errors when defer is used. Using defer may result in linter warnings.
func warnIfErrors(f func() error) {
if err := f(); err != nil {
_, _ = fmt.Fprint(os.Stderr, "received error when closing ledger connection", err)
}
}
func convertDERtoBER(signatureDER []byte) ([]byte, error) {
sigDER, err := btcec.ParseDERSignature(signatureDER[:], btcec.S256())
if err != nil {
return nil, err
}
sigBER := tmbtcec.Signature{R: sigDER.R, S: sigDER.S}
return sigBER.Serialize(), nil
}
func getLedgerDevice() (LedgerSECP256K1, error) {
if discoverLedger == nil {
return nil, errors.New("no Ledger discovery function defined")
} }
return false device, err := discoverLedger()
if err != nil {
return nil, errors.Wrap(err, "ledger nano S")
}
return device, nil
}
func validateKey(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1) error {
pub, err := getPubKey(device, pkl.Path)
if err != nil {
return err
}
// verify this matches cached address
if !pub.Equals(pkl.CachedPubKey) {
return fmt.Errorf("cached key does not match retrieved key")
}
return nil
} }
// Sign calls the ledger and stores the PubKey for future use. // Sign calls the ledger and stores the PubKey for future use.
@ -116,45 +156,37 @@ func (pkl PrivKeyLedgerSecp256k1) Equals(other tmcrypto.PrivKey) bool {
// Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, returning // Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, returning
// an error, so this should only trigger if the private key is held in memory // an error, so this should only trigger if the private key is held in memory
// for a while before use. // for a while before use.
func (pkl PrivKeyLedgerSecp256k1) Sign(msg []byte) ([]byte, error) { func sign(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte) ([]byte, error) {
sig, err := pkl.signLedgerSecp256k1(msg) err := validateKey(device, pkl)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return sig, nil sig, err := device.SignSECP256K1(pkl.Path.DerivationPath(), msg)
if err != nil {
return nil, err
}
return convertDERtoBER(sig)
} }
// getPubKey reads the pubkey the ledger itself // getPubKey reads the pubkey the ledger itself
// since this involves IO, it may return an error, which is not exposed // since this involves IO, it may return an error, which is not exposed
// in the PubKey interface, so this function allows better error handling // in the PubKey interface, so this function allows better error handling
func (pkl PrivKeyLedgerSecp256k1) getPubKey() (key tmcrypto.PubKey, err error) { func getPubKey(device LedgerSECP256K1, path hd.BIP44Params) (tmcrypto.PubKey, error) {
key, err = pkl.pubkeyLedgerSecp256k1() publicKey, err := device.GetPublicKeySECP256K1(path.DerivationPath())
if err != nil { if err != nil {
return key, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err) return nil, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err)
} }
return key, err
}
func (pkl PrivKeyLedgerSecp256k1) signLedgerSecp256k1(msg []byte) ([]byte, error) {
return pkl.ledger.SignSECP256K1(pkl.Path, msg)
}
func (pkl PrivKeyLedgerSecp256k1) pubkeyLedgerSecp256k1() (pub tmcrypto.PubKey, err error) {
key, err := pkl.ledger.GetPublicKeySECP256K1(pkl.Path)
if err != nil {
return nil, fmt.Errorf("error fetching public key: %v", err)
}
var pk tmsecp256k1.PubKeySecp256k1
// re-serialize in the 33-byte compressed format // re-serialize in the 33-byte compressed format
cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256()) cmp, err := btcec.ParsePubKey(publicKey[:], btcec.S256())
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing public key: %v", err) return nil, fmt.Errorf("error parsing public key: %v", err)
} }
copy(pk[:], cmp.SerializeCompressed())
return pk, nil var compressedPublicKey tmsecp256k1.PubKeySecp256k1
copy(compressedPublicKey[:], cmp.SerializeCompressed())
return compressedPublicKey, nil
} }

View File

@ -2,22 +2,127 @@ package crypto
import ( import (
"fmt" "fmt"
"os"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/cosmos/cosmos-sdk/tests"
tmcrypto "github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/crypto/encoding/amino"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
) )
var ledgerEnabledEnv = "TEST_WITH_LEDGER" func TestLedgerErrorHandling(t *testing.T) {
// first, try to generate a key, must return an error
// (no panic)
path := *hd.NewParams(44, 555, 0, false, 0)
_, err := NewPrivKeyLedgerSecp256k1(path)
require.Error(t, err)
}
func TestPublicKey(t *testing.T) {
path := *hd.NewFundraiserParams(0, 0)
priv, err := NewPrivKeyLedgerSecp256k1(path)
require.Nil(t, err, "%s", err)
require.NotNil(t, priv)
pubKeyAddr, err := sdk.Bech32ifyAccPub(priv.PubKey())
require.NoError(t, err)
require.Equal(t, "cosmospub1addwnpepqd87l8xhcnrrtzxnkql7k55ph8fr9jarf4hn6udwukfprlalu8lgw0urza0",
pubKeyAddr, "Is your device using test mnemonic: %s ?", tests.TestMnemonic)
require.Equal(t, "5075624b6579536563703235366b317b303334464546394344374334433633353838443342303"+
"3464542353238314239443233324342413334443646334437314145453539323131464642464531464538377d",
fmt.Sprintf("%x", priv.PubKey()))
}
func TestPublicKeyHDPath(t *testing.T) {
expectedAnswers := []string{
"cosmospub1addwnpepqd87l8xhcnrrtzxnkql7k55ph8fr9jarf4hn6udwukfprlalu8lgw0urza0",
"cosmospub1addwnpepqfsdqjr68h7wjg5wacksmqaypasnra232fkgu5sxdlnlu8j22ztxvlqvd65",
"cosmospub1addwnpepqw3xwqun6q43vtgw6p4qspq7srvxhcmvq4jrx5j5ma6xy3r7k6dtxmrkh3d",
"cosmospub1addwnpepqvez9lrp09g8w7gkv42y4yr5p6826cu28ydrhrujv862yf4njmqyyjr4pjs",
"cosmospub1addwnpepq06hw3enfrtmq8n67teytcmtnrgcr0yntmyt25kdukfjkerdc7lqg32rcz7",
"cosmospub1addwnpepqg3trf2gd0s2940nckrxherwqhgmm6xd5h4pcnrh4x7y35h6yafmcpk5qns",
"cosmospub1addwnpepqdm6rjpx6wsref8wjn7ym6ntejet430j4szpngfgc20caz83lu545vuv8hp",
"cosmospub1addwnpepqvdhtjzy2wf44dm03jxsketxc07vzqwvt3vawqqtljgsr9s7jvydjmt66ew",
"cosmospub1addwnpepqwystfpyxwcava7v3t7ndps5xzu6s553wxcxzmmnxevlzvwrlqpzz695nw9",
"cosmospub1addwnpepqw970u6gjqkccg9u3rfj99857wupj2z9fqfzy2w7e5dd7xn7kzzgkgqch0r",
}
const numIters = 10
privKeys := make([]tmcrypto.PrivKey, numIters)
// Check with device
for i := uint32(0); i < 10; i++ {
path := *hd.NewFundraiserParams(0, i)
fmt.Printf("Checking keys at %v\n", path)
priv, err := NewPrivKeyLedgerSecp256k1(path)
require.Nil(t, err, "%s", err)
require.NotNil(t, priv)
// Check other methods
require.NoError(t, priv.(PrivKeyLedgerSecp256k1).ValidateKey())
tmp := priv.(PrivKeyLedgerSecp256k1)
(&tmp).AssertIsPrivKeyInner()
pubKeyAddr, err := sdk.Bech32ifyAccPub(priv.PubKey())
require.NoError(t, err)
require.Equal(t,
expectedAnswers[i], pubKeyAddr,
"Is your device using test mnemonic: %s ?", tests.TestMnemonic)
// Store and restore
serializedPk := priv.Bytes()
require.NotNil(t, serializedPk)
require.Equal(t, 44, len(serializedPk))
privKeys[i] = priv
}
// Now check equality
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
require.Equal(t, i == j, privKeys[i].Equals(privKeys[j]))
require.Equal(t, i == j, privKeys[j].Equals(privKeys[i]))
}
}
}
func getFakeTx(accountNumber uint32) []byte {
tmp := fmt.Sprintf(
`{"account_number":"%d","chain_id":"1234","fee":{"amount":[{"amount":"150","denom":"atom"}],"gas":"5000"},"memo":"memo","msgs":[[""]],"sequence":"6"}`,
accountNumber)
return []byte(tmp)
}
func TestSignaturesHD(t *testing.T) {
for account := uint32(0); account < 100; account += 30 {
msg := getFakeTx(account)
path := *hd.NewFundraiserParams(account, account/5)
fmt.Printf("Checking signature at %v --- PLEASE REVIEW AND ACCEPT IN THE DEVICE\n", path)
priv, err := NewPrivKeyLedgerSecp256k1(path)
require.Nil(t, err, "%s", err)
pub := priv.PubKey()
sig, err := priv.Sign(msg)
require.Nil(t, err)
valid := pub.VerifyBytes(msg, sig)
require.True(t, valid, "Is your device using test mnemonic: %s ?", tests.TestMnemonic)
}
}
func TestRealLedgerSecp256k1(t *testing.T) { func TestRealLedgerSecp256k1(t *testing.T) {
if os.Getenv(ledgerEnabledEnv) == "" { msg := getFakeTx(50)
t.Skip(fmt.Sprintf("Set '%s' to run code on a real ledger", ledgerEnabledEnv)) path := *hd.NewFundraiserParams(0, 0)
}
msg := []byte("{\"account_number\":\"3\",\"chain_id\":\"1234\",\"fee\":{\"amount\":[{\"amount\":\"150\",\"denom\":\"atom\"}],\"gas\":\"5000\"},\"memo\":\"memo\",\"msgs\":[[\"%s\"]],\"sequence\":\"6\"}")
path := DerivationPath{44, 60, 0, 0, 0}
priv, err := NewPrivKeyLedgerSecp256k1(path) priv, err := NewPrivKeyLedgerSecp256k1(path)
require.Nil(t, err, "%s", err) require.Nil(t, err, "%s", err)
@ -48,17 +153,3 @@ func TestRealLedgerSecp256k1(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, pub, bpub) require.Equal(t, pub, bpub)
} }
// TestRealLedgerErrorHandling calls. These tests assume
// the ledger is not plugged in....
func TestRealLedgerErrorHandling(t *testing.T) {
if os.Getenv(ledgerEnabledEnv) != "" {
t.Skip(fmt.Sprintf("Unset '%s' to run code as if without a real Ledger", ledgerEnabledEnv))
}
// 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)
}

Some files were not shown because too many files have changed in this diff Show More