From c05e6c9c0541b08b4449be4ff4138ba4b7924859 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Thu, 16 May 2019 22:10:14 +0200 Subject: [PATCH] Initial release. Closes: cosmos/cosmos-sdk#4103 --- .circleci/config.yml | 472 +++++ .clog.yaml | 10 + .../breaking/gaiacli/3715-query-distr-rew | 2 + .../breaking/gaiacli/4027-gaiad-and-gaiac | 1 + .../breaking/gaiacli/4142-Turn-gaiacli-tx | 2 + .../4228-merge-gaiakeyutil-into-gaiacli | 2 + .pending/breaking/gaiad/4027-gaiad-and-gaiac | 2 + .pending/breaking/gaiad/4159-use-module-patt | 1 + .pending/breaking/gaiad/4272-Merge-gaiarepla | 2 + .pending/breaking/rest/3715-Update-distribu | 3 + .pending/breaking/rest/3942-Support-query-t | 1 + .pending/breaking/rest/4049-update-tag | 1 + .../bugfixes/gaiacli/3945-There-s-no-chec | 1 + .../bugfixes/gaiacli/4190-Fix-redelegatio | 1 + .../bugfixes/gaiacli/4219-Empty-mnemonic- | 1 + .../gaiacli/4345-Improved-NanoX-detection | 1 + .pending/bugfixes/gaiad/4113-Fix-incorrect-G | 1 + .../3426-remove-redundant-account-check | 1 + .../gaiacli/4227-Support-for-Ledger-App-1.5 | 1 + .../improvements/gaiad/4042-Add-description | 1 + .../improvements/gaiad/4062-Remove-cmd-gaia | 1 + .../improvements/gaiad/4064-Remove-dep-and- | 1 + .../improvements/gaiad/4080-add-missing-inv | 1 + .../improvements/gaiad/4343-Upgrade-toolcha | 1 + ...07-Return-200-status-code-on-empty-results | 1 + .../improvements/rest/4123-Fix-typo-url-er | 1 + .../improvements/rest/4129-Translate-doc-c | 1 + .../improvements/rest/4141-Fix-txs-encode- | 1 + Dockerfile | 33 + Makefile | 166 ++ app/app.go | 240 +++ app/app_test.go | 44 + app/benchmarks/txsize_test.go | 41 + app/export.go | 165 ++ app/genesis.go | 19 + app/sim_test.go | 618 +++++++ app/test_util.go | 23 + cli_test/README.md | 51 + cli_test/cli_test.go | 1251 +++++++++++++ cli_test/doc.go | 3 + cli_test/test_helpers.go | 731 ++++++++ cmd/gaiacli/main.go | 212 +++ cmd/gaiad/main.go | 94 + cmd/gaiad/replay.go | 191 ++ cmd/gaiad/testnet.go | 376 ++++ cmd/gaiadebug/README.md | 35 + cmd/gaiadebug/hack.go | 103 ++ cmd/gaiadebug/main.go | 257 +++ contrib/devtools/Makefile | 79 + contrib/devtools/install-golangci-lint.sh | 27 + contrib/gitian-build.sh | 201 +++ contrib/gitian-descriptors/gitian-darwin.yml | 116 ++ contrib/gitian-descriptors/gitian-linux.yml | 115 ++ contrib/gitian-descriptors/gitian-windows.yml | 116 ++ contrib/gitian-keys/README.md | 29 + contrib/gitian-keys/keys.txt | 2 + contrib/localnet-blocks-test.sh | 41 + docker-compose.yml | 68 + go.mod | 41 + go.sum | 300 ++++ lcd_test/helpers_test.go | 1558 +++++++++++++++++ lcd_test/lcd_test.go | 1104 ++++++++++++ sims.mk | 61 + 63 files changed, 9026 insertions(+) create mode 100644 .circleci/config.yml create mode 100644 .clog.yaml create mode 100644 .pending/breaking/gaiacli/3715-query-distr-rew create mode 100644 .pending/breaking/gaiacli/4027-gaiad-and-gaiac create mode 100644 .pending/breaking/gaiacli/4142-Turn-gaiacli-tx create mode 100644 .pending/breaking/gaiacli/4228-merge-gaiakeyutil-into-gaiacli create mode 100644 .pending/breaking/gaiad/4027-gaiad-and-gaiac create mode 100644 .pending/breaking/gaiad/4159-use-module-patt create mode 100644 .pending/breaking/gaiad/4272-Merge-gaiarepla create mode 100644 .pending/breaking/rest/3715-Update-distribu create mode 100644 .pending/breaking/rest/3942-Support-query-t create mode 100644 .pending/breaking/rest/4049-update-tag create mode 100644 .pending/bugfixes/gaiacli/3945-There-s-no-chec create mode 100644 .pending/bugfixes/gaiacli/4190-Fix-redelegatio create mode 100644 .pending/bugfixes/gaiacli/4219-Empty-mnemonic- create mode 100644 .pending/bugfixes/gaiacli/4345-Improved-NanoX-detection create mode 100644 .pending/bugfixes/gaiad/4113-Fix-incorrect-G create mode 100644 .pending/improvements/gaiacli/3426-remove-redundant-account-check create mode 100644 .pending/improvements/gaiacli/4227-Support-for-Ledger-App-1.5 create mode 100644 .pending/improvements/gaiad/4042-Add-description create mode 100644 .pending/improvements/gaiad/4062-Remove-cmd-gaia create mode 100644 .pending/improvements/gaiad/4064-Remove-dep-and- create mode 100644 .pending/improvements/gaiad/4080-add-missing-inv create mode 100644 .pending/improvements/gaiad/4343-Upgrade-toolcha create mode 100644 .pending/improvements/rest/2007-Return-200-status-code-on-empty-results create mode 100644 .pending/improvements/rest/4123-Fix-typo-url-er create mode 100644 .pending/improvements/rest/4129-Translate-doc-c create mode 100644 .pending/improvements/rest/4141-Fix-txs-encode- create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 app/app.go create mode 100644 app/app_test.go create mode 100644 app/benchmarks/txsize_test.go create mode 100644 app/export.go create mode 100644 app/genesis.go create mode 100644 app/sim_test.go create mode 100644 app/test_util.go create mode 100644 cli_test/README.md create mode 100644 cli_test/cli_test.go create mode 100644 cli_test/doc.go create mode 100644 cli_test/test_helpers.go create mode 100644 cmd/gaiacli/main.go create mode 100644 cmd/gaiad/main.go create mode 100644 cmd/gaiad/replay.go create mode 100644 cmd/gaiad/testnet.go create mode 100644 cmd/gaiadebug/README.md create mode 100644 cmd/gaiadebug/hack.go create mode 100644 cmd/gaiadebug/main.go create mode 100644 contrib/devtools/Makefile create mode 100644 contrib/devtools/install-golangci-lint.sh create mode 100755 contrib/gitian-build.sh create mode 100644 contrib/gitian-descriptors/gitian-darwin.yml create mode 100644 contrib/gitian-descriptors/gitian-linux.yml create mode 100644 contrib/gitian-descriptors/gitian-windows.yml create mode 100644 contrib/gitian-keys/README.md create mode 100644 contrib/gitian-keys/keys.txt create mode 100755 contrib/localnet-blocks-test.sh create mode 100644 docker-compose.yml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 lcd_test/helpers_test.go create mode 100644 lcd_test/lcd_test.go create mode 100644 sims.mk diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..0b910d44 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,472 @@ +version: 2 + +defaults: &linux_defaults + working_directory: /go/src/github.com/cosmos/gaia + docker: + - image: circleci/golang:1.12 + environment: + GO111MODULE: 'on' + + +############ +# +# Configure macos integration tests + +macos_config: &macos_defaults + macos: + xcode: "10.1.0" + working_directory: /Users/distiller/project/src/github.com/cosmos/cosmos-sdk + environment: + GO_VERSION: "1.12.5" + +set_macos_env: &macos_env + run: + name: Set environment + command: | + echo 'export PATH=$PATH:$HOME/go/bin' >> $BASH_ENV + echo 'export GOPATH=$HOME/project' >> $BASH_ENV + echo 'export PATH=$PATH:$HOME/go/bin:$GOPATH/bin' >> $BASH_ENV + echo 'export GO111MODULE=on' + +############ +# +# Configure docs deployment + +docs_update: &docs_deploy + working_directory: ~/repo + docker: + - image: tendermintdev/jq_curl + environment: + AWS_REGION: us-east-1 + +jobs: + setup_dependencies: + <<: *linux_defaults + steps: + - run: mkdir -p /tmp/workspace/bin + - run: mkdir -p /tmp/workspace/profiles + - checkout + - restore_cache: + keys: + - go-mod-v1-{{ checksum "go.sum" }} + - run: + name: tools + command: | + make tools TOOLS_DESTDIR=/tmp/workspace/bin + cp $GOPATH/bin/runsim /tmp/workspace/bin/ + - run: + name: binaries + command: | + export PATH=/tmp/workspace/bin:$PATH + make go-mod-cache + make install + - save_cache: + key: go-mod-v1-{{ checksum "go.sum" }} + paths: + - "/go/pkg/mod" + - persist_to_workspace: + root: /tmp/workspace + paths: + - bin + - profiles + + lint: + <<: *linux_defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - restore_cache: + keys: + - go-mod-v1-{{ checksum "go.sum" }} + - run: + name: Lint source + command: | + export PATH=/tmp/workspace/bin:$PATH + make ci-lint + + integration_tests: + <<: *linux_defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - restore_cache: + keys: + - go-mod-v1-{{ checksum "go.sum" }} + - run: + name: Test cli + command: | + export BUILDDIR=`pwd`/build + make check-build + + test_sim_gaia_nondeterminism: + <<: *linux_defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - restore_cache: + keys: + - go-mod-v1-{{ checksum "go.sum" }} + - run: + name: Test individual module simulations + command: | + make sim-gaia-nondeterminism + + test_sim_gaia_fast: + <<: *linux_defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - restore_cache: + keys: + - go-mod-v1-{{ checksum "go.sum" }} + - run: + name: Test full Gaia simulation + command: | + make sim-gaia-fast + + test_sim_gaia_import_export: + <<: *linux_defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - restore_cache: + keys: + - go-mod-v1-{{ checksum "go.sum" }} + - run: + name: Test Gaia import/export simulation + command: | + /tmp/workspace/bin/runsim -j 4 github.com/cosmos/gaia/app 50 5 TestGaiaImportExport + + test_sim_gaia_simulation_after_import: + <<: *linux_defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - restore_cache: + keys: + - go-mod-v1-{{ checksum "go.sum" }} + - run: + name: Test Gaia import/export simulation + command: | + /tmp/workspace/bin/runsim -j 4 github.com/cosmos/gaia/app 50 5 TestGaiaSimulationAfterImport + + test_sim_gaia_multi_seed_long: + <<: *linux_defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - restore_cache: + keys: + - go-mod-v1-{{ checksum "go.sum" }} + - run: + name: Test multi-seed Gaia simulation long + command: | + /tmp/workspace/bin/runsim -j 4 github.com/cosmos/gaia/app 500 50 TestFullGaiaSimulation + + test_sim_gaia_multi_seed: + <<: *linux_defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - restore_cache: + keys: + - go-mod-v1-{{ checksum "go.sum" }} + - run: + name: Test multi-seed Gaia simulation short + command: | + /tmp/workspace/bin/runsim -j 4 github.com/cosmos/gaia/app 50 10 TestFullGaiaSimulation + + test_cover: + <<: *linux_defaults + parallelism: 4 + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - run: mkdir -p /tmp/logs + - restore_cache: + keys: + - go-mod-v1-{{ checksum "go.sum" }} + - run: + name: Run tests + command: | + export VERSION="$(git describe --tags --long | sed 's/v\(.*\)/\1/')" + export GO111MODULE=on + for pkg in $(go list ./... | grep -v github.com/cosmos/gaia/cli_test | grep -v '/simulation' | circleci tests split --split-by=timings); do + id=$(echo "$pkg" | sed 's|[/.]|_|g') + go test -mod=readonly -timeout 8m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic -tags='ledger test_ledger_mock' "$pkg" | tee "/tmp/logs/$id-$RANDOM.log" + done + - persist_to_workspace: + root: /tmp/workspace + paths: + - "profiles/*" + - store_artifacts: + path: /tmp/logs + + upload_coverage: + <<: *linux_defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - run: + name: gather + command: | + set -ex + + echo "--> Concatenating profiles:" + ls /tmp/workspace/profiles/ + echo "mode: atomic" > coverage.txt + for prof in $(ls /tmp/workspace/profiles/); do + tail -n +2 /tmp/workspace/profiles/"$prof" >> coverage.txt + 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: + name: upload + command: bash <(curl -s https://codecov.io/bash) -f coverage.txt + + localnet: + working_directory: /home/circleci/.go_workspace/src/github.com/cosmos/cosmos-sdk + machine: + image: circleci/classic:latest + environment: + GOPATH: /home/circleci/.go_workspace/ + GOOS: linux + GOARCH: amd64 + GO_VERSION: "1.12.5" + parallelism: 1 + steps: + - checkout + - run: + name: run localnet and exit on failure + command: | + pushd /tmp + wget https://dl.google.com/go/go$GO_VERSION.linux-amd64.tar.gz + sudo tar -xvf go$GO_VERSION.linux-amd64.tar.gz + sudo rm -rf /usr/local/go + sudo mv go /usr/local + popd + set -x + make tools + make build-linux + make localnet-start + ./contrib/localnet-blocks-test.sh 40 5 10 localhost + + deploy_docs: + <<: *docs_deploy + steps: + - checkout + - run: + name: Trigger website build + command: | + curl --silent \ + --show-error \ + -X POST \ + --header "Content-Type: application/json" \ + -d "{\"branch\": \"$CIRCLE_BRANCH\"}" \ + "https://circleci.com/api/v1.1/project/github/$CIRCLE_PROJECT_USERNAME/$WEBSITE_REPO_NAME/build?circle-token=$TENDERBOT_API_TOKEN" > response.json + + RESULT=`jq -r '.status' response.json` + MESSAGE=`jq -r '.message' response.json` + + if [[ ${RESULT} == "null" ]] || [[ ${RESULT} -ne "200" ]]; then + echo "CircleCI API call failed: $MESSAGE" + exit 1 + else + echo "Website build started" + fi + + macos_ci: + <<: *macos_defaults + steps: + - *macos_env + - run: + name: Install go + command: | + source $BASH_ENV + curl -L -O https://dl.google.com/go/go$GO_VERSION.darwin-amd64.tar.gz + tar -C $HOME -xzf go$GO_VERSION.darwin-amd64.tar.gz + rm go$GO_VERSION.darwin-amd64.tar.gz + go version + - checkout + - run: + name: Install SDK + command: | + source $BASH_ENV + make tools + make install + - run: + name: Integration tests + command: + source $BASH_ENV + make check-build + - run: + name: Test full gaia simulation + command: | + source $BASH_ENV + make sim-gaia-fast + + docker_image: + <<: *linux_defaults + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - setup_remote_docker: + docker_layer_caching: true + - run: | + GAIAD_VERSION='' + if [ "${CIRCLE_BRANCH}" = "master" ]; then + GAIAD_VERSION="stable" + elif [ "${CIRCLE_BRANCH}" = "develop" ]; then + GAIAD_VERSION="develop" + fi + if [ -z "${GAIAD_VERSION}" ]; then + docker build . + else + docker build -t tendermint/gaia:$GAIAD_VERSION . + docker login --password-stdin -u $DOCKER_USER <<<$DOCKER_PASS + docker push tendermint/gaia:$GAIAD_VERSION + fi + + docker_tagged: + <<: *linux_defaults + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - setup_remote_docker: + docker_layer_caching: true + - run: | + docker build -t tendermint/gaia:$CIRCLE_TAG . + docker login --password-stdin -u $DOCKER_USER <<<$DOCKER_PASS + docker push tendermint/gaia:$CIRCLE_TAG + + reproducible_builds: + <<: *linux_defaults + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - setup_remote_docker: + docker_layer_caching: true + - run: + name: Build gaia + no_output_timeout: 20m + command: | + sudo apt-get install -y ruby + bash -x ./contrib/gitian-build.sh all + for os in darwin linux windows; do + cp gitian-build-${os}/result/gaia-${os}-res.yml . + rm -rf gitian-build-${os}/ + done + - store_artifacts: + path: /go/src/github.com/cosmos/gaia/gaia-darwin-res.yml + - store_artifacts: + path: /go/src/github.com/cosmos/gaia/gaia-linux-res.yml + - store_artifacts: + path: /go/src/github.com/cosmos/gaia/gaia-windows-res.yml + +workflows: + version: 2 + test-suite: + jobs: + - docker_image: + requires: + - setup_dependencies + - docker_tagged: + filters: + tags: + only: + - /^v.*/ + branches: + ignore: + - /.*/ + requires: + - setup_dependencies + - macos_ci: + filters: + branches: + only: + - master + - develop + - deploy_docs: + filters: + branches: + only: + - master + - develop + - setup_dependencies: + # filters here are needed to enable this job also for tags + filters: + tags: + only: + - /^v.*/ + - lint: + requires: + - setup_dependencies + - integration_tests: + requires: + - setup_dependencies + - test_sim_gaia_nondeterminism: + requires: + - setup_dependencies + - test_sim_gaia_fast: + requires: + - setup_dependencies + - test_sim_gaia_import_export: + requires: + - setup_dependencies + - test_sim_gaia_simulation_after_import: + requires: + - setup_dependencies + - test_sim_gaia_multi_seed: + requires: + - setup_dependencies + - test_sim_gaia_multi_seed_long: + requires: + - setup_dependencies + filters: + branches: + only: + - master + - develop + - test_cover: + requires: + - setup_dependencies + - localnet + - upload_coverage: + requires: + - test_cover + - reproducible_builds: + filters: + branches: + only: + - master + requires: + - setup_dependencies + diff --git a/.clog.yaml b/.clog.yaml new file mode 100644 index 00000000..1c7cc76c --- /dev/null +++ b/.clog.yaml @@ -0,0 +1,10 @@ +sections: + breaking: Breaking Changes + features: Features + improvements: Improvements + bugfixes: Bugfixes + +tags: + - gaiad + - gaiacli + - rest diff --git a/.pending/breaking/gaiacli/3715-query-distr-rew b/.pending/breaking/gaiacli/3715-query-distr-rew new file mode 100644 index 00000000..5303400b --- /dev/null +++ b/.pending/breaking/gaiacli/3715-query-distr-rew @@ -0,0 +1,2 @@ +#3715 query distr rewards returns per-validator +rewards along with rewards total amount. diff --git a/.pending/breaking/gaiacli/4027-gaiad-and-gaiac b/.pending/breaking/gaiacli/4027-gaiad-and-gaiac new file mode 100644 index 00000000..cd973541 --- /dev/null +++ b/.pending/breaking/gaiacli/4027-gaiad-and-gaiac @@ -0,0 +1 @@ +#4027 gaiacli version command dooes not return the checksum of the go.sum file anymore. diff --git a/.pending/breaking/gaiacli/4142-Turn-gaiacli-tx b/.pending/breaking/gaiacli/4142-Turn-gaiacli-tx new file mode 100644 index 00000000..ed4c73b5 --- /dev/null +++ b/.pending/breaking/gaiacli/4142-Turn-gaiacli-tx @@ -0,0 +1,2 @@ +#4142 Turn gaiacli tx send's --from into a required argument. +New shorter syntax: `gaiacli tx send FROM TO AMOUNT` diff --git a/.pending/breaking/gaiacli/4228-merge-gaiakeyutil-into-gaiacli b/.pending/breaking/gaiacli/4228-merge-gaiakeyutil-into-gaiacli new file mode 100644 index 00000000..e986900a --- /dev/null +++ b/.pending/breaking/gaiacli/4228-merge-gaiakeyutil-into-gaiacli @@ -0,0 +1,2 @@ +#4228 Merge gaiakeyutil functionality into gaiacli keys. +Drop `gaiakeyutil` in favor of new `gaiacli keys parse` command. Syntax and semantic are preserved. diff --git a/.pending/breaking/gaiad/4027-gaiad-and-gaiac b/.pending/breaking/gaiad/4027-gaiad-and-gaiac new file mode 100644 index 00000000..e31db9be --- /dev/null +++ b/.pending/breaking/gaiad/4027-gaiad-and-gaiac @@ -0,0 +1,2 @@ +#4027 gaiad version command does not return the checksum of the go.sum file shipped along with the source release tarball. +Go modules feature guarantees dependencies reproducibility and as long as binaries are built via the Makefile shipped with the sources, no dependendencies can break such guarantee. diff --git a/.pending/breaking/gaiad/4159-use-module-patt b/.pending/breaking/gaiad/4159-use-module-patt new file mode 100644 index 00000000..1a3d4398 --- /dev/null +++ b/.pending/breaking/gaiad/4159-use-module-patt @@ -0,0 +1 @@ +#4159 use module pattern and module manager for initialization \ No newline at end of file diff --git a/.pending/breaking/gaiad/4272-Merge-gaiarepla b/.pending/breaking/gaiad/4272-Merge-gaiarepla new file mode 100644 index 00000000..5aecf7bc --- /dev/null +++ b/.pending/breaking/gaiad/4272-Merge-gaiarepla @@ -0,0 +1,2 @@ +#4272 Merge gaiareplay functionality into gaiad replay. +Drop `gaiareplay` in favor of new `gaiad replay` command. diff --git a/.pending/breaking/rest/3715-Update-distribu b/.pending/breaking/rest/3715-Update-distribu new file mode 100644 index 00000000..1c562efa --- /dev/null +++ b/.pending/breaking/rest/3715-Update-distribu @@ -0,0 +1,3 @@ +#3715 Update /distribution/delegators/{delegatorAddr}/rewards GET endpoint +as per new specs. For a given delegation, the endpoint now returns the +comprehensive list of validator-reward tuples along with the grand total. diff --git a/.pending/breaking/rest/3942-Support-query-t b/.pending/breaking/rest/3942-Support-query-t new file mode 100644 index 00000000..a5ab3114 --- /dev/null +++ b/.pending/breaking/rest/3942-Support-query-t @@ -0,0 +1 @@ +#3942 Update pagination data in txs query. diff --git a/.pending/breaking/rest/4049-update-tag b/.pending/breaking/rest/4049-update-tag new file mode 100644 index 00000000..6a200b71 --- /dev/null +++ b/.pending/breaking/rest/4049-update-tag @@ -0,0 +1 @@ +#4049 update tag MsgWithdrawValidatorCommission to match type \ No newline at end of file diff --git a/.pending/bugfixes/gaiacli/3945-There-s-no-chec b/.pending/bugfixes/gaiacli/3945-There-s-no-chec new file mode 100644 index 00000000..e8ab0605 --- /dev/null +++ b/.pending/bugfixes/gaiacli/3945-There-s-no-chec @@ -0,0 +1 @@ +#3945 There's no check for chain-id in TxBuilder.SignStdTx \ No newline at end of file diff --git a/.pending/bugfixes/gaiacli/4190-Fix-redelegatio b/.pending/bugfixes/gaiacli/4190-Fix-redelegatio new file mode 100644 index 00000000..c9106a03 --- /dev/null +++ b/.pending/bugfixes/gaiacli/4190-Fix-redelegatio @@ -0,0 +1 @@ +#4190 Fix redelegations-from by using the correct params and query endpoint. diff --git a/.pending/bugfixes/gaiacli/4219-Empty-mnemonic- b/.pending/bugfixes/gaiacli/4219-Empty-mnemonic- new file mode 100644 index 00000000..c0509645 --- /dev/null +++ b/.pending/bugfixes/gaiacli/4219-Empty-mnemonic- @@ -0,0 +1 @@ +#4219 Return an error when an empty mnemonic is provided during key recovery. diff --git a/.pending/bugfixes/gaiacli/4345-Improved-NanoX-detection b/.pending/bugfixes/gaiacli/4345-Improved-NanoX-detection new file mode 100644 index 00000000..98c5fa5f --- /dev/null +++ b/.pending/bugfixes/gaiacli/4345-Improved-NanoX-detection @@ -0,0 +1 @@ +#4345 Improved Ledger Nano X detection diff --git a/.pending/bugfixes/gaiad/4113-Fix-incorrect-G b/.pending/bugfixes/gaiad/4113-Fix-incorrect-G new file mode 100644 index 00000000..2a5d1680 --- /dev/null +++ b/.pending/bugfixes/gaiad/4113-Fix-incorrect-G @@ -0,0 +1 @@ +#4113 Fix incorrect `$GOBIN` in `Install Go` \ No newline at end of file diff --git a/.pending/improvements/gaiacli/3426-remove-redundant-account-check b/.pending/improvements/gaiacli/3426-remove-redundant-account-check new file mode 100644 index 00000000..adf96e76 --- /dev/null +++ b/.pending/improvements/gaiacli/3426-remove-redundant-account-check @@ -0,0 +1 @@ +#4068 Remove redundant account check on `gaiacli` diff --git a/.pending/improvements/gaiacli/4227-Support-for-Ledger-App-1.5 b/.pending/improvements/gaiacli/4227-Support-for-Ledger-App-1.5 new file mode 100644 index 00000000..97bc1e81 --- /dev/null +++ b/.pending/improvements/gaiacli/4227-Support-for-Ledger-App-1.5 @@ -0,0 +1 @@ +#4227 Support for Ledger App v1.5 diff --git a/.pending/improvements/gaiad/4042-Add-description b/.pending/improvements/gaiad/4042-Add-description new file mode 100644 index 00000000..03ed9cc6 --- /dev/null +++ b/.pending/improvements/gaiad/4042-Add-description @@ -0,0 +1 @@ +#4042 Update docs and scripts to include the correct `GO111MODULE=on` environment variable. diff --git a/.pending/improvements/gaiad/4062-Remove-cmd-gaia b/.pending/improvements/gaiad/4062-Remove-cmd-gaia new file mode 100644 index 00000000..b0d5e7c9 --- /dev/null +++ b/.pending/improvements/gaiad/4062-Remove-cmd-gaia @@ -0,0 +1 @@ +#4066 Fix 'ExportGenesisFile() incorrectly overwrites genesis' diff --git a/.pending/improvements/gaiad/4064-Remove-dep-and- b/.pending/improvements/gaiad/4064-Remove-dep-and- new file mode 100644 index 00000000..2a219ea2 --- /dev/null +++ b/.pending/improvements/gaiad/4064-Remove-dep-and- @@ -0,0 +1 @@ +#4064 Remove `dep` and `vendor` from `doc` and `version`. diff --git a/.pending/improvements/gaiad/4080-add-missing-inv b/.pending/improvements/gaiad/4080-add-missing-inv new file mode 100644 index 00000000..48c8172d --- /dev/null +++ b/.pending/improvements/gaiad/4080-add-missing-inv @@ -0,0 +1 @@ +#4080 add missing invariants during simulations \ No newline at end of file diff --git a/.pending/improvements/gaiad/4343-Upgrade-toolcha b/.pending/improvements/gaiad/4343-Upgrade-toolcha new file mode 100644 index 00000000..484d522c --- /dev/null +++ b/.pending/improvements/gaiad/4343-Upgrade-toolcha @@ -0,0 +1 @@ +#4343 Upgrade toolchain to Go 1.12.5. diff --git a/.pending/improvements/rest/2007-Return-200-status-code-on-empty-results b/.pending/improvements/rest/2007-Return-200-status-code-on-empty-results new file mode 100644 index 00000000..af200220 --- /dev/null +++ b/.pending/improvements/rest/2007-Return-200-status-code-on-empty-results @@ -0,0 +1 @@ +#2007 Return 200 status code on empty results diff --git a/.pending/improvements/rest/4123-Fix-typo-url-er b/.pending/improvements/rest/4123-Fix-typo-url-er new file mode 100644 index 00000000..8f25a8f0 --- /dev/null +++ b/.pending/improvements/rest/4123-Fix-typo-url-er @@ -0,0 +1 @@ +#4123 Fix typo, url error and outdated command description of doc clients. \ No newline at end of file diff --git a/.pending/improvements/rest/4129-Translate-doc-c b/.pending/improvements/rest/4129-Translate-doc-c new file mode 100644 index 00000000..ab271021 --- /dev/null +++ b/.pending/improvements/rest/4129-Translate-doc-c @@ -0,0 +1 @@ +#4129 Translate doc clients to chinese. \ No newline at end of file diff --git a/.pending/improvements/rest/4141-Fix-txs-encode- b/.pending/improvements/rest/4141-Fix-txs-encode- new file mode 100644 index 00000000..0c19a162 --- /dev/null +++ b/.pending/improvements/rest/4141-Fix-txs-encode- @@ -0,0 +1 @@ +#4141 Fix /txs/encode endpoint \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..c123dbf6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +# Simple usage with a mounted data directory: +# > docker build -t gaia . +# > docker run -it -p 46657:46657 -p 46656:46656 -v ~/.gaiad:/root/.gaiad -v ~/.gaiacli:/root/.gaiacli gaia gaiad init +# > docker run -it -p 46657:46657 -p 46656:46656 -v ~/.gaiad:/root/.gaiad -v ~/.gaiacli:/root/.gaiacli gaia gaiad start +FROM golang:alpine AS build-env + +# Set up dependencies +ENV PACKAGES curl make git libc-dev bash gcc linux-headers eudev-dev python + +# Set working directory for the build +WORKDIR /go/src/github.com/cosmos/gaia + +# Add source files +COPY . . + +# Install minimum necessary dependencies, build Cosmos SDK, remove packages +RUN apk add --no-cache $PACKAGES && \ + make tools && \ + make install + +# Final image +FROM alpine:edge + +# Install ca-certificates +RUN apk add --update ca-certificates +WORKDIR /root + +# Copy over binaries from the build-env +COPY --from=build-env /go/bin/gaiad /usr/bin/gaiad +COPY --from=build-env /go/bin/gaiacli /usr/bin/gaiacli + +# Run gaiad by default, omit entrypoint to ease using container with gaiacli +CMD ["gaiad"] diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..1d376431 --- /dev/null +++ b/Makefile @@ -0,0 +1,166 @@ +#!/usr/bin/make -f + +PACKAGES_SIMTEST=$(shell go list ./... | grep '/simulation') +VERSION := $(shell echo $(shell git describe --tags) | sed 's/^v//') +COMMIT := $(shell git log -1 --format='%H') +LEDGER_ENABLED ?= true +GOBIN ?= $(GOPATH)/bin + +export GO111MODULE = on + +# process build tags + +build_tags = netgo +ifeq ($(LEDGER_ENABLED),true) + ifeq ($(OS),Windows_NT) + GCCEXE = $(shell where gcc.exe 2> NUL) + ifeq ($(GCCEXE),) + $(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false) + else + build_tags += ledger + endif + else + UNAME_S = $(shell uname -s) + ifeq ($(UNAME_S),OpenBSD) + $(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988)) + else + GCC = $(shell command -v gcc 2> /dev/null) + ifeq ($(GCC),) + $(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false) + else + build_tags += ledger + endif + endif + endif +endif + +ifeq ($(WITH_CLEVELDB),yes) + build_tags += gcc +endif +build_tags += $(BUILD_TAGS) +build_tags := $(strip $(build_tags)) + +whitespace := +whitespace += $(whitespace) +comma := , +build_tags_comma_sep := $(subst $(whitespace),$(comma),$(build_tags)) + +# process linker flags + +ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=gaia \ + -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ + -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) \ + -X "github.com/cosmos/cosmos-sdk/version.BuildTags=$(build_tags_comma_sep)" + +ifeq ($(WITH_CLEVELDB),yes) + ldflags += -X github.com/cosmos/cosmos-sdk/types.DBBackend=cleveldb +endif +ldflags += $(LDFLAGS) +ldflags := $(strip $(ldflags)) + +BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)' + +# The below include contains the tools target. +include contrib/devtools/Makefile + +all: install lint check + +build: go.sum +ifeq ($(OS),Windows_NT) + go build -mod=readonly $(BUILD_FLAGS) -o build/gaiad.exe ./cmd/gaiad + go build -mod=readonly $(BUILD_FLAGS) -o build/gaiacli.exe ./cmd/gaiacli +else + go build -mod=readonly $(BUILD_FLAGS) -o build/gaiad ./cmd/gaiad + go build -mod=readonly $(BUILD_FLAGS) -o build/gaiacli ./cmd/gaiacli +endif + +build-linux: go.sum + LEDGER_ENABLED=false GOOS=linux GOARCH=amd64 $(MAKE) build + +install: go.sum check-ledger + go install -mod=readonly $(BUILD_FLAGS) ./cmd/gaiad + go install -mod=readonly $(BUILD_FLAGS) ./cmd/gaiacli + +install-debug: go.sum + go install -mod=readonly $(BUILD_FLAGS) ./cmd/gaiadebug + + +######################################## +### Tools & dependencies + +go-mod-cache: go.sum + @echo "--> Download go modules to local cache" + @go mod download + +go.sum: go.mod + @echo "--> Ensure dependencies have not been modified" + @go mod verify + +draw-deps: + @# requires brew install graphviz or apt-get install graphviz + go get github.com/RobotsAndPencils/goviz + @goviz -i ./cmd/gaiad -d 2 | dot -Tpng -o dependency-graph.png + +clean: + rm -rf snapcraft-local.yaml build/ + +distclean: clean + rm -rf vendor/ + +######################################## +### Testing + + +check: check-unit +check-unit: + @VERSION=$(VERSION) go test -mod=readonly -race -tags='ledger test_ledger_mock' ./... + +check-race: + @VERSION=$(VERSION) go test -mod=readonly -tags='ledger test_ledger_mock' -race ./... + +check-cover: + @go test -mod=readonly -timeout 30m -race -coverprofile=coverage.txt -covermode=atomic -tags='ledger test_ledger_mock' ./... + +check-build: build + @go test -mod=readonly -p 4 `go list ./cli_test/...` -tags=cli_test + +check-all: check-unit check-race check-cover check-build + +lint: ci-lint +ci-lint: + golangci-lint run + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs gofmt -d -s + go mod verify + +format: + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/lcd/statik/statik.go" | xargs gofmt -w -s + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/lcd/statik/statik.go" | xargs misspell -w + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/lcd/statik/statik.go" | xargs goimports -w -local github.com/cosmos/cosmos-sdk + +benchmark: + @go test -mod=readonly -bench=. ./... + + +######################################## +### Local validator nodes using docker and docker-compose + +build-docker-gaiadnode: + $(MAKE) -C networks/local + +# Run a 4-node testnet locally +localnet-start: localnet-stop + @if ! [ -f build/node0/gaiad/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/gaiad:Z tendermint/gaiadnode testnet --v 4 -o . --starting-ip-address 192.168.10.2 ; fi + docker-compose up -d + +# Stop testnet +localnet-stop: + docker-compose down + + +# include simulations +include sims.mk + +.PHONY: all build-linux install install-debug \ + go-mod-cache draw-deps clean \ + check check-all check-build check-cover check-ledger check-unit check-race + diff --git a/app/app.go b/app/app.go new file mode 100644 index 00000000..1cd5e797 --- /dev/null +++ b/app/app.go @@ -0,0 +1,240 @@ +package app + +import ( + "io" + "os" + + bam "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/genaccounts" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/crisis" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/genutil" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" + + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" +) + +const appName = "GaiaApp" + +var ( + // default home directories for gaiacli + DefaultCLIHome = os.ExpandEnv("$HOME/.gaiacli") + + // default home directories for gaiad + DefaultNodeHome = os.ExpandEnv("$HOME/.gaiad") + + // The ModuleBasicManager is in charge of setting up basic, + // non-dependant module elements, such as codec registration + // and genesis verification. + ModuleBasics sdk.ModuleBasicManager +) + +func init() { + ModuleBasics = sdk.NewModuleBasicManager( + genaccounts.AppModuleBasic{}, + genutil.AppModuleBasic{}, + auth.AppModuleBasic{}, + bank.AppModuleBasic{}, + staking.AppModuleBasic{}, + mint.AppModuleBasic{}, + distr.AppModuleBasic{}, + gov.AppModuleBasic{}, + params.AppModuleBasic{}, + crisis.AppModuleBasic{}, + slashing.AppModuleBasic{}, + ) +} + +// custom tx codec +func MakeCodec() *codec.Codec { + var cdc = codec.New() + ModuleBasics.RegisterCodec(cdc) + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + return cdc +} + +// Extended ABCI application +type GaiaApp struct { + *bam.BaseApp + cdc *codec.Codec + + invCheckPeriod uint + + // keys to access the substores + keyMain *sdk.KVStoreKey + keyAccount *sdk.KVStoreKey + keyStaking *sdk.KVStoreKey + tkeyStaking *sdk.TransientStoreKey + keySlashing *sdk.KVStoreKey + keyMint *sdk.KVStoreKey + keyDistr *sdk.KVStoreKey + tkeyDistr *sdk.TransientStoreKey + keyGov *sdk.KVStoreKey + keyFeeCollection *sdk.KVStoreKey + keyParams *sdk.KVStoreKey + tkeyParams *sdk.TransientStoreKey + + // keepers + accountKeeper auth.AccountKeeper + feeCollectionKeeper auth.FeeCollectionKeeper + bankKeeper bank.Keeper + stakingKeeper staking.Keeper + slashingKeeper slashing.Keeper + mintKeeper mint.Keeper + distrKeeper distr.Keeper + govKeeper gov.Keeper + crisisKeeper crisis.Keeper + paramsKeeper params.Keeper + + // the module manager + mm *sdk.ModuleManager +} + +// NewGaiaApp returns a reference to an initialized GaiaApp. +func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, + invCheckPeriod uint, baseAppOptions ...func(*bam.BaseApp)) *GaiaApp { + + cdc := MakeCodec() + + bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOptions...) + bApp.SetCommitMultiStoreTracer(traceStore) + bApp.SetAppVersion(version.Version) + + var app = &GaiaApp{ + BaseApp: bApp, + cdc: cdc, + invCheckPeriod: invCheckPeriod, + keyMain: sdk.NewKVStoreKey(bam.MainStoreKey), + keyAccount: sdk.NewKVStoreKey(auth.StoreKey), + keyStaking: sdk.NewKVStoreKey(staking.StoreKey), + tkeyStaking: sdk.NewTransientStoreKey(staking.TStoreKey), + keyMint: sdk.NewKVStoreKey(mint.StoreKey), + keyDistr: sdk.NewKVStoreKey(distr.StoreKey), + tkeyDistr: sdk.NewTransientStoreKey(distr.TStoreKey), + keySlashing: sdk.NewKVStoreKey(slashing.StoreKey), + keyGov: sdk.NewKVStoreKey(gov.StoreKey), + keyFeeCollection: sdk.NewKVStoreKey(auth.FeeStoreKey), + keyParams: sdk.NewKVStoreKey(params.StoreKey), + tkeyParams: sdk.NewTransientStoreKey(params.TStoreKey), + } + + // init params keeper and subspaces + app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams, params.DefaultCodespace) + authSubspace := app.paramsKeeper.Subspace(auth.DefaultParamspace) + bankSubspace := app.paramsKeeper.Subspace(bank.DefaultParamspace) + stakingSubspace := app.paramsKeeper.Subspace(staking.DefaultParamspace) + mintSubspace := app.paramsKeeper.Subspace(mint.DefaultParamspace) + distrSubspace := app.paramsKeeper.Subspace(distr.DefaultParamspace) + slashingSubspace := app.paramsKeeper.Subspace(slashing.DefaultParamspace) + govSubspace := app.paramsKeeper.Subspace(gov.DefaultParamspace) + crisisSubspace := app.paramsKeeper.Subspace(crisis.DefaultParamspace) + + // add keepers + app.accountKeeper = auth.NewAccountKeeper(app.cdc, app.keyAccount, authSubspace, auth.ProtoBaseAccount) + app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper, bankSubspace, bank.DefaultCodespace) + app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.keyFeeCollection) + stakingKeeper := staking.NewKeeper(app.cdc, app.keyStaking, app.tkeyStaking, app.bankKeeper, + stakingSubspace, staking.DefaultCodespace) + app.mintKeeper = mint.NewKeeper(app.cdc, app.keyMint, mintSubspace, &stakingKeeper, app.feeCollectionKeeper) + app.distrKeeper = distr.NewKeeper(app.cdc, app.keyDistr, distrSubspace, app.bankKeeper, &stakingKeeper, + app.feeCollectionKeeper, distr.DefaultCodespace) + app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, &stakingKeeper, + slashingSubspace, slashing.DefaultCodespace) + app.crisisKeeper = crisis.NewKeeper(crisisSubspace, invCheckPeriod, app.distrKeeper, + app.bankKeeper, app.feeCollectionKeeper) + + // register the proposal types + govRouter := gov.NewRouter() + govRouter.AddRoute(gov.RouterKey, gov.ProposalHandler). + AddRoute(params.RouterKey, params.NewParamChangeProposalHandler(app.paramsKeeper)) + app.govKeeper = gov.NewKeeper(app.cdc, app.keyGov, app.paramsKeeper, govSubspace, + app.bankKeeper, &stakingKeeper, gov.DefaultCodespace, govRouter) + + // register the staking hooks + // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks + app.stakingKeeper = *stakingKeeper.SetHooks( + staking.NewMultiStakingHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks())) + + app.mm = sdk.NewModuleManager( + genaccounts.NewAppModule(app.accountKeeper), + genutil.NewAppModule(app.accountKeeper, app.stakingKeeper, app.BaseApp.DeliverTx), + auth.NewAppModule(app.accountKeeper, app.feeCollectionKeeper), + bank.NewAppModule(app.bankKeeper, app.accountKeeper), + crisis.NewAppModule(app.crisisKeeper, app.Logger()), + distr.NewAppModule(app.distrKeeper), + gov.NewAppModule(app.govKeeper), + mint.NewAppModule(app.mintKeeper), + slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper), + staking.NewAppModule(app.stakingKeeper, app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper), + ) + + // During begin block slashing happens after distr.BeginBlocker so that + // there is nothing left over in the validator fee pool, so as to keep the + // CanWithdrawInvariant invariant. + app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName) + + app.mm.SetOrderEndBlockers(gov.ModuleName, staking.ModuleName) + + // genutils must occur after staking so that pools are properly + // initialized with tokens from genesis accounts. + app.mm.SetOrderInitGenesis(genaccounts.ModuleName, distr.ModuleName, + staking.ModuleName, auth.ModuleName, bank.ModuleName, slashing.ModuleName, + gov.ModuleName, mint.ModuleName, crisis.ModuleName, genutil.ModuleName) + + app.mm.RegisterInvariants(&app.crisisKeeper) + app.mm.RegisterRoutes(app.Router(), app.QueryRouter()) + + // initialize stores + app.MountStores(app.keyMain, app.keyAccount, app.keyStaking, app.keyMint, + app.keyDistr, app.keySlashing, app.keyGov, app.keyFeeCollection, + app.keyParams, app.tkeyParams, app.tkeyStaking, app.tkeyDistr) + + // initialize BaseApp + app.SetInitChainer(app.InitChainer) + app.SetBeginBlocker(app.BeginBlocker) + app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper, auth.DefaultSigVerificationGasConsumer)) + app.SetEndBlocker(app.EndBlocker) + + if loadLatest { + err := app.LoadLatestVersion(app.keyMain) + if err != nil { + cmn.Exit(err.Error()) + } + } + return app +} + +// application updates every begin block +func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + return app.mm.BeginBlock(ctx, req) +} + +// application updates every end block +func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + return app.mm.EndBlock(ctx, req) +} + +// application update at chain initialization +func (app *GaiaApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + var genesisState GenesisState + app.cdc.MustUnmarshalJSON(req.AppStateBytes, &genesisState) + return app.mm.InitGenesis(ctx, genesisState) +} + +// load a particular height +func (app *GaiaApp) LoadHeight(height int64) error { + return app.LoadVersion(height, app.keyMain) +} diff --git a/app/app_test.go b/app/app_test.go new file mode 100644 index 00000000..1e880cd9 --- /dev/null +++ b/app/app_test.go @@ -0,0 +1,44 @@ +package app + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/codec" + + abci "github.com/tendermint/tendermint/abci/types" +) + +func TestGaiadExport(t *testing.T) { + db := db.NewMemDB() + gapp := NewGaiaApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0) + setGenesis(gapp) + + // 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, 0) + _, _, err := newGapp.ExportAppStateAndValidators(false, []string{}) + require.NoError(t, err, "ExportAppStateAndValidators should not have an error") +} + +func setGenesis(gapp *GaiaApp) error { + + genesisState := NewDefaultGenesisState() + stateBytes, err := codec.MarshalJSONIndent(gapp.cdc, genesisState) + if err != nil { + return err + } + + // Initialize the chain + gapp.InitChain( + abci.RequestInitChain{ + Validators: []abci.ValidatorUpdate{}, + AppStateBytes: stateBytes, + }, + ) + gapp.Commit() + return nil +} diff --git a/app/benchmarks/txsize_test.go b/app/benchmarks/txsize_test.go new file mode 100644 index 00000000..f1d658c7 --- /dev/null +++ b/app/benchmarks/txsize_test.go @@ -0,0 +1,41 @@ +package app + +import ( + "fmt" + + "github.com/tendermint/tendermint/crypto/secp256k1" + + "github.com/cosmos/gaia/app" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" +) + +// This will fail half the time with the second output being 173 +// This is due to secp256k1 signatures not being constant size. +// nolint: vet +func ExampleTxSendSize() { + cdc := app.MakeCodec() + var gas uint64 = 1 + + priv1 := secp256k1.GenPrivKeySecp256k1([]byte{0}) + addr1 := sdk.AccAddress(priv1.PubKey().Address()) + priv2 := secp256k1.GenPrivKeySecp256k1([]byte{1}) + addr2 := sdk.AccAddress(priv2.PubKey().Address()) + coins := sdk.Coins{sdk.NewCoin("denom", sdk.NewInt(10))} + msg1 := bank.MsgMultiSend{ + Inputs: []bank.Input{bank.NewInput(addr1, coins)}, + Outputs: []bank.Output{bank.NewOutput(addr2, coins)}, + } + fee := auth.NewStdFee(gas, coins) + signBytes := auth.StdSignBytes("example-chain-ID", + 1, 1, fee, []sdk.Msg{msg1}, "") + sig, _ := priv1.Sign(signBytes) + sigs := []auth.StdSignature{{nil, sig}} + tx := auth.NewStdTx([]sdk.Msg{msg1}, fee, sigs, "") + fmt.Println(len(cdc.MustMarshalBinaryBare([]sdk.Msg{msg1}))) + fmt.Println(len(cdc.MustMarshalBinaryBare(tx))) + // output: 80 + // 169 +} diff --git a/app/export.go b/app/export.go new file mode 100644 index 00000000..b9fc3366 --- /dev/null +++ b/app/export.go @@ -0,0 +1,165 @@ +package app + +import ( + "encoding/json" + "log" + + abci "github.com/tendermint/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +// export the state of gaia for a genesis file +func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList []string, +) (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { + + // as if they could withdraw from the start of the next block + ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) + + if forZeroHeight { + app.prepForZeroHeightGenesis(ctx, jailWhiteList) + } + + genState := app.mm.ExportGenesis(ctx) + appState, err = codec.MarshalJSONIndent(app.cdc, genState) + if err != nil { + return nil, nil, err + } + validators = staking.WriteValidators(ctx, app.stakingKeeper) + return appState, validators, nil +} + +// prepare for fresh start at zero height +// NOTE zero height genesis is a temporary feature which will be deprecated +// in favour of export at a block height +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. */ + app.crisisKeeper.AssertInvariants(ctx, app.Logger()) + + /* Handle fee distribution state. */ + + // withdraw all validator commission + app.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { + _, _ = app.distrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) + return false + }) + + // withdraw all delegator rewards + dels := app.stakingKeeper.GetAllDelegations(ctx) + for _, delegation := range dels { + _, _ = app.distrKeeper.WithdrawDelegationRewards(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress) + } + + // clear validator slash events + app.distrKeeper.DeleteAllValidatorSlashEvents(ctx) + + // clear validator historical rewards + app.distrKeeper.DeleteAllValidatorHistoricalRewards(ctx) + + // set context height to zero + height := ctx.BlockHeight() + ctx = ctx.WithBlockHeight(0) + + // reinitialize all validators + app.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { + + // donate any unwithdrawn outstanding reward fraction tokens to the community pool + scraps := app.distrKeeper.GetValidatorOutstandingRewards(ctx, val.GetOperator()) + feePool := app.distrKeeper.GetFeePool(ctx) + feePool.CommunityPool = feePool.CommunityPool.Add(scraps) + app.distrKeeper.SetFeePool(ctx, feePool) + + app.distrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()) + return false + }) + + // reinitialize all delegations + for _, del := range dels { + app.distrKeeper.Hooks().BeforeDelegationCreated(ctx, del.DelegatorAddress, del.ValidatorAddress) + app.distrKeeper.Hooks().AfterDelegationModified(ctx, del.DelegatorAddress, del.ValidatorAddress) + } + + // reset context height + ctx = ctx.WithBlockHeight(height) + + /* Handle staking state. */ + + // iterate through redelegations, reset creation height + app.stakingKeeper.IterateRedelegations(ctx, func(_ int64, red staking.Redelegation) (stop bool) { + for i := range red.Entries { + red.Entries[i].CreationHeight = 0 + } + app.stakingKeeper.SetRedelegation(ctx, red) + return false + }) + + // iterate through unbonding delegations, reset creation height + app.stakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd staking.UnbondingDelegation) (stop bool) { + for i := range ubd.Entries { + ubd.Entries[i].CreationHeight = 0 + } + app.stakingKeeper.SetUnbondingDelegation(ctx, ubd) + return false + }) + + // Iterate through validators by power descending, reset bond heights, and + // update bond intra-tx counters. + store := ctx.KVStore(app.keyStaking) + iter := sdk.KVStoreReversePrefixIterator(store, staking.ValidatorsKey) + counter := int16(0) + + var valConsAddrs []sdk.ConsAddress + for ; iter.Valid(); iter.Next() { + addr := sdk.ValAddress(iter.Key()[1:]) + validator, found := app.stakingKeeper.GetValidator(ctx, addr) + if !found { + panic("expected validator, not found") + } + + validator.UnbondingHeight = 0 + valConsAddrs = append(valConsAddrs, validator.ConsAddress()) + if applyWhiteList && !whiteListMap[addr.String()] { + validator.Jailed = true + } + + app.stakingKeeper.SetValidator(ctx, validator) + counter++ + } + + iter.Close() + + _ = app.stakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx) + + /* Handle slashing state. */ + + // reset start height on signing infos + app.slashingKeeper.IterateValidatorSigningInfos( + ctx, + func(addr sdk.ConsAddress, info slashing.ValidatorSigningInfo) (stop bool) { + info.StartHeight = 0 + app.slashingKeeper.SetValidatorSigningInfo(ctx, addr, info) + return false + }, + ) +} diff --git a/app/genesis.go b/app/genesis.go new file mode 100644 index 00000000..d4e99dd4 --- /dev/null +++ b/app/genesis.go @@ -0,0 +1,19 @@ +package app + +import ( + "encoding/json" +) + +// The genesis state of the blockchain is represented here as a map of raw json +// messages key'd by a identifier string. +// The identifier is used to determine which module genesis information belongs +// to so it may be appropriately routed during init chain. +// Within this application default genesis information is retrieved from +// the ModuleBasicManager which populates json from each BasicModule +// object provided to it during init. +type GenesisState map[string]json.RawMessage + +// NewDefaultGenesisState generates the default state for gaia. +func NewDefaultGenesisState() GenesisState { + return ModuleBasics.DefaultGenesis() +} diff --git a/app/sim_test.go b/app/sim_test.go new file mode 100644 index 00000000..be5a8ef3 --- /dev/null +++ b/app/sim_test.go @@ -0,0 +1,618 @@ +package app + +import ( + "encoding/json" + "flag" + "fmt" + "io" + "io/ioutil" + "math/rand" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/secp256k1" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/genaccounts" + 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" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation" + "github.com/cosmos/cosmos-sdk/x/gov" + govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation" + "github.com/cosmos/cosmos-sdk/x/mint" + paramsim "github.com/cosmos/cosmos-sdk/x/params/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/cosmos/cosmos-sdk/x/slashing" + slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingsim "github.com/cosmos/cosmos-sdk/x/staking/simulation" +) + +var ( + genesisFile string + seed int64 + numBlocks int + blockSize int + enabled bool + verbose bool + lean bool + commit bool + period int +) + +func init() { + flag.StringVar(&genesisFile, "SimulationGenesis", "", "custom simulation genesis file") + flag.Int64Var(&seed, "SimulationSeed", 42, "simulation random seed") + flag.IntVar(&numBlocks, "SimulationNumBlocks", 500, "number of blocks") + flag.IntVar(&blockSize, "SimulationBlockSize", 200, "operations per block") + flag.BoolVar(&enabled, "SimulationEnabled", false, "enable the simulation") + flag.BoolVar(&verbose, "SimulationVerbose", false, "verbose log output") + flag.BoolVar(&lean, "SimulationLean", false, "lean simulation log output") + flag.BoolVar(&commit, "SimulationCommit", false, "have the simulation commit") + flag.IntVar(&period, "SimulationPeriod", 1, "run slow invariants only once every period assertions") +} + +// helper function for populating input for SimulateFromSeed +func getSimulateFromSeedInput(tb testing.TB, w io.Writer, app *GaiaApp) ( + testing.TB, io.Writer, *baseapp.BaseApp, simulation.AppStateFn, int64, + simulation.WeightedOperations, sdk.Invariants, int, int, bool, bool) { + + return tb, w, app.BaseApp, appStateFn, seed, + testAndRunTxs(app), invariants(app), numBlocks, blockSize, commit, lean +} + +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) + accounts := genaccounts.GetGenesisStateFromAppState(cdc, appState).Accounts + + var newAccs []simulation.Account + for _, acc := range 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 +} + +// TODO refactor out random initialization code to the modules +func appStateRandomizedFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time, +) (json.RawMessage, []simulation.Account, string) { + + var genesisAccounts []genaccounts.GenesisAccount + genesisState := NewDefaultGenesisState() + cdc := MakeCodec() + + amount := int64(r.Intn(1e12)) + numInitiallyBonded := int64(r.Intn(250)) + numAccs := int64(len(accs)) + if numInitiallyBonded > numAccs { + numInitiallyBonded = numAccs + } + fmt.Printf("Selected randomly generated parameters for simulated genesis:\n"+ + "\t{amount of stake per account: %v, initially bonded validators: %v}\n", + amount, numInitiallyBonded) + + // randomly generate some genesis accounts + for i, acc := range accs { + coins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(amount))} + bacc := auth.NewBaseAccountWithAddress(acc.Address) + bacc.SetCoins(coins) + + var gacc genaccounts.GenesisAccount + + // Only consider making a vesting account once the initial bonded validator + // set is exhausted due to needing to track DelegatedVesting. + if int64(i) > numInitiallyBonded && r.Intn(100) < 50 { + var ( + vacc auth.VestingAccount + endTime int64 + ) + + startTime := genesisTimestamp.Unix() + + // Allow for some vesting accounts to vest very quickly while others very + // slowly. + if r.Intn(100) < 50 { + endTime = int64(simulation.RandIntBetween(r, int(startTime), int(startTime+(60*60*24*30)))) + } else { + endTime = int64(simulation.RandIntBetween(r, int(startTime), int(startTime+(60*60*12)))) + } + + if startTime == endTime { + endTime++ + } + + if r.Intn(100) < 50 { + vacc = auth.NewContinuousVestingAccount(&bacc, startTime, endTime) + } else { + vacc = auth.NewDelayedVestingAccount(&bacc, endTime) + } + + var err error + gacc, err = genaccounts.NewGenesisAccountI(vacc) + if err != nil { + panic(err) + } + } else { + gacc = genaccounts.NewGenesisAccount(&bacc) + } + + genesisAccounts = append(genesisAccounts, gacc) + } + + genaccsGenesis := genaccounts.NewGenesisState(genesisAccounts) + genesisState[genaccounts.ModuleName] = cdc.MustMarshalJSON(genaccsGenesis) + + authGenesis := auth.NewGenesisState( + nil, + auth.NewParams( + simulation.ModuleParamSimulator["MaxMemoCharacters"](r).(uint64), + simulation.ModuleParamSimulator["TxSigLimit"](r).(uint64), + simulation.ModuleParamSimulator["TxSizeCostPerByte"](r).(uint64), + simulation.ModuleParamSimulator["SigVerifyCostED25519"](r).(uint64), + simulation.ModuleParamSimulator["SigVerifyCostSecp256k1"](r).(uint64), + ), + ) + fmt.Printf("Selected randomly generated auth parameters:\n\t%+v\n", authGenesis) + genesisState[auth.ModuleName] = cdc.MustMarshalJSON(authGenesis) + + bankGenesis := bank.NewGenesisState(r.Int63n(2) == 0) + genesisState[bank.ModuleName] = cdc.MustMarshalJSON(bankGenesis) + fmt.Printf("Selected randomly generated bank parameters:\n\t%+v\n", bankGenesis) + + // Random genesis states + vp := simulation.ModuleParamSimulator["VotingParams/VotingPeriod"](r).(time.Duration) + govGenesis := gov.NewGenesisState( + uint64(r.Intn(100)), + gov.NewDepositParams( + simulation.ModuleParamSimulator["DepositParams/MinDeposit"](r).(sdk.Coins), + vp, + ), + gov.NewVotingParams(vp), + gov.NewTallyParams( + simulation.ModuleParamSimulator["TallyParams/Quorum"](r).(sdk.Dec), + simulation.ModuleParamSimulator["TallyParams/Threshold"](r).(sdk.Dec), + simulation.ModuleParamSimulator["TallyParams/Veto"](r).(sdk.Dec), + ), + ) + genesisState[gov.ModuleName] = cdc.MustMarshalJSON(govGenesis) + fmt.Printf("Selected randomly generated governance parameters:\n\t%+v\n", govGenesis) + + stakingGenesis := staking.NewGenesisState( + staking.InitialPool(), + staking.NewParams( + simulation.ModuleParamSimulator["UnbondingTime"](r).(time.Duration), + simulation.ModuleParamSimulator["MaxValidators"](r).(uint16), + 7, + sdk.DefaultBondDenom, + ), + nil, + nil, + ) + fmt.Printf("Selected randomly generated staking parameters:\n\t%+v\n", stakingGenesis) + + slashingParams := slashing.NewParams( + stakingGenesis.Params.UnbondingTime, + simulation.ModuleParamSimulator["SignedBlocksWindow"](r).(int64), + simulation.ModuleParamSimulator["MinSignedPerWindow"](r).(sdk.Dec), + simulation.ModuleParamSimulator["DowntimeJailDuration"](r).(time.Duration), + simulation.ModuleParamSimulator["SlashFractionDoubleSign"](r).(sdk.Dec), + simulation.ModuleParamSimulator["SlashFractionDowntime"](r).(sdk.Dec), + ) + slashingGenesis := slashing.NewGenesisState(slashingParams, nil, nil) + genesisState[slashing.ModuleName] = cdc.MustMarshalJSON(slashingGenesis) + fmt.Printf("Selected randomly generated slashing parameters:\n\t%+v\n", slashingGenesis) + + mintGenesis := mint.NewGenesisState( + mint.InitialMinter( + sdk.NewDecWithPrec(int64(r.Intn(99)), 2)), + mint.NewParams( + sdk.DefaultBondDenom, + simulation.ModuleParamSimulator["InflationRateChange"](r).(sdk.Dec), + simulation.ModuleParamSimulator["InflationMax"](r).(sdk.Dec), + simulation.ModuleParamSimulator["InflationMin"](r).(sdk.Dec), + simulation.ModuleParamSimulator["GoalBonded"](r).(sdk.Dec), + uint64(60*60*8766/5), + ), + ) + genesisState[mint.ModuleName] = cdc.MustMarshalJSON(mintGenesis) + fmt.Printf("Selected randomly generated minting parameters:\n\t%+v\n", mintGenesis) + + var validators []staking.Validator + var delegations []staking.Delegation + + valAddrs := make([]sdk.ValAddress, numInitiallyBonded) + for i := 0; i < int(numInitiallyBonded); i++ { + valAddr := sdk.ValAddress(accs[i].Address) + valAddrs[i] = valAddr + + validator := staking.NewValidator(valAddr, accs[i].PubKey, staking.Description{}) + validator.Tokens = sdk.NewInt(amount) + validator.DelegatorShares = sdk.NewDec(amount) + delegation := staking.Delegation{accs[i].Address, valAddr, sdk.NewDec(amount)} + validators = append(validators, validator) + delegations = append(delegations, delegation) + } + + stakingGenesis.Pool.NotBondedTokens = sdk.NewInt((amount * numAccs) + (numInitiallyBonded * amount)) + stakingGenesis.Validators = validators + stakingGenesis.Delegations = delegations + genesisState[staking.ModuleName] = cdc.MustMarshalJSON(stakingGenesis) + + // TODO make use NewGenesisState + distrGenesis := distr.GenesisState{ + FeePool: distr.InitialFeePool(), + CommunityTax: sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(int64(r.Intn(30)), 2)), + BaseProposerReward: sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(int64(r.Intn(30)), 2)), + BonusProposerReward: sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(int64(r.Intn(30)), 2)), + } + genesisState[distr.ModuleName] = cdc.MustMarshalJSON(distrGenesis) + fmt.Printf("Selected randomly generated distribution parameters:\n\t%+v\n", distrGenesis) + + // Marshal genesis + appState, err := MakeCodec().MarshalJSON(genesisState) + if err != nil { + panic(err) + } + + 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 testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation { + return []simulation.WeightedOperation{ + {5, authsim.SimulateDeductFee(app.accountKeeper, app.feeCollectionKeeper)}, + {100, banksim.SimulateMsgSend(app.accountKeeper, app.bankKeeper)}, + {10, banksim.SimulateSingleInputMsgMultiSend(app.accountKeeper, app.bankKeeper)}, + {50, distrsim.SimulateMsgSetWithdrawAddress(app.accountKeeper, app.distrKeeper)}, + {50, distrsim.SimulateMsgWithdrawDelegatorReward(app.accountKeeper, app.distrKeeper)}, + {50, distrsim.SimulateMsgWithdrawValidatorCommission(app.accountKeeper, app.distrKeeper)}, + {5, govsim.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, govsim.SimulateTextProposalContent)}, + {5, govsim.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, paramsim.SimulateParamChangeProposalContent)}, + {100, govsim.SimulateMsgDeposit(app.govKeeper)}, + {100, stakingsim.SimulateMsgCreateValidator(app.accountKeeper, app.stakingKeeper)}, + {5, stakingsim.SimulateMsgEditValidator(app.stakingKeeper)}, + {100, stakingsim.SimulateMsgDelegate(app.accountKeeper, app.stakingKeeper)}, + {100, stakingsim.SimulateMsgUndelegate(app.accountKeeper, app.stakingKeeper)}, + {100, stakingsim.SimulateMsgBeginRedelegate(app.accountKeeper, app.stakingKeeper)}, + {100, slashingsim.SimulateMsgUnjail(app.slashingKeeper)}, + } +} + +func invariants(app *GaiaApp) []sdk.Invariant { + return simulation.PeriodicInvariants(app.crisisKeeper.Invariants(), period, 0) +} + +// Pass this in as an option to use a dbStoreAdapter instead of an IAVLStore for simulation speed. +func fauxMerkleModeOpt(bapp *baseapp.BaseApp) { + bapp.SetFauxMerkleMode() +} + +// Profile with: +// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/gaia/app -bench ^BenchmarkFullGaiaSimulation$ -SimulationCommit=true -cpuprofile cpu.out +func BenchmarkFullGaiaSimulation(b *testing.B) { + // Setup Gaia application + logger := log.NewNopLogger() + + var db dbm.DB + dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") + db, _ = sdk.NewLevelDB("Simulation", dir) + defer func() { + db.Close() + os.RemoveAll(dir) + }() + app := NewGaiaApp(logger, db, nil, true, 0) + + // Run randomized simulation + // TODO parameterize numbers, save for a later PR + _, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(b, os.Stdout, app)) + if err != nil { + fmt.Println(err) + b.Fail() + } + if commit { + fmt.Println("GoLevelDB Stats") + fmt.Println(db.Stats()["leveldb.stats"]) + fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) + } +} + +func TestFullGaiaSimulation(t *testing.T) { + if !enabled { + t.Skip("Skipping Gaia simulation") + } + + // Setup Gaia application + var logger log.Logger + if verbose { + logger = log.TestingLogger() + } else { + logger = log.NewNopLogger() + } + var db dbm.DB + dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") + db, _ = sdk.NewLevelDB("Simulation", dir) + defer func() { + db.Close() + os.RemoveAll(dir) + }() + app := NewGaiaApp(logger, db, nil, true, 0, fauxMerkleModeOpt) + require.Equal(t, "GaiaApp", app.Name()) + + // Run randomized simulation + _, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(t, os.Stdout, app)) + if commit { + // for memdb: + // fmt.Println("Database Size", db.Stats()["database.size"]) + fmt.Println("GoLevelDB Stats") + fmt.Println(db.Stats()["leveldb.stats"]) + fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) + } + require.Nil(t, err) +} + +func TestGaiaImportExport(t *testing.T) { + if !enabled { + t.Skip("Skipping Gaia import/export simulation") + } + + // Setup Gaia application + var logger log.Logger + if verbose { + logger = log.TestingLogger() + } else { + logger = log.NewNopLogger() + } + var db dbm.DB + dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") + db, _ = sdk.NewLevelDB("Simulation", dir) + defer func() { + db.Close() + os.RemoveAll(dir) + }() + app := NewGaiaApp(logger, db, nil, true, 0, fauxMerkleModeOpt) + require.Equal(t, "GaiaApp", app.Name()) + + // Run randomized simulation + _, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(t, os.Stdout, app)) + + if commit { + // for memdb: + // fmt.Println("Database Size", db.Stats()["database.size"]) + fmt.Println("GoLevelDB Stats") + fmt.Println(db.Stats()["leveldb.stats"]) + fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) + } + require.Nil(t, err) + + fmt.Printf("Exporting genesis...\n") + + appState, _, err := app.ExportAppStateAndValidators(false, []string{}) + require.NoError(t, err) + fmt.Printf("Importing genesis...\n") + + newDir, _ := ioutil.TempDir("", "goleveldb-gaia-sim-2") + newDB, _ := sdk.NewLevelDB("Simulation-2", dir) + defer func() { + newDB.Close() + os.RemoveAll(newDir) + }() + newApp := NewGaiaApp(log.NewNopLogger(), newDB, nil, true, 0, fauxMerkleModeOpt) + require.Equal(t, "GaiaApp", newApp.Name()) + var genesisState GenesisState + err = app.cdc.UnmarshalJSON(appState, &genesisState) + if err != nil { + panic(err) + } + ctxB := newApp.NewContext(true, abci.Header{}) + newApp.mm.InitGenesis(ctxB, genesisState) + + fmt.Printf("Comparing stores...\n") + ctxA := app.NewContext(true, abci.Header{}) + type StoreKeysPrefixes struct { + A sdk.StoreKey + B sdk.StoreKey + Prefixes [][]byte + } + storeKeysPrefixes := []StoreKeysPrefixes{ + {app.keyMain, newApp.keyMain, [][]byte{}}, + {app.keyAccount, newApp.keyAccount, [][]byte{}}, + {app.keyStaking, newApp.keyStaking, [][]byte{staking.UnbondingQueueKey, + staking.RedelegationQueueKey, staking.ValidatorQueueKey}}, // ordering may change but it doesn't matter + {app.keySlashing, newApp.keySlashing, [][]byte{}}, + {app.keyMint, newApp.keyMint, [][]byte{}}, + {app.keyDistr, newApp.keyDistr, [][]byte{}}, + {app.keyFeeCollection, newApp.keyFeeCollection, [][]byte{}}, + {app.keyParams, newApp.keyParams, [][]byte{}}, + {app.keyGov, newApp.keyGov, [][]byte{}}, + } + for _, storeKeysPrefix := range storeKeysPrefixes { + storeKeyA := storeKeysPrefix.A + storeKeyB := storeKeysPrefix.B + prefixes := storeKeysPrefix.Prefixes + storeA := ctxA.KVStore(storeKeyA) + storeB := ctxB.KVStore(storeKeyB) + kvA, kvB, count, equal := sdk.DiffKVStores(storeA, storeB, prefixes) + fmt.Printf("Compared %d key/value pairs between %s and %s\n", count, storeKeyA, storeKeyB) + require.True(t, equal, + "unequal stores: %s / %s:\nstore A %X => %X\nstore B %X => %X", + storeKeyA, storeKeyB, kvA.Key, kvA.Value, kvB.Key, kvB.Value, + ) + } + +} + +func TestGaiaSimulationAfterImport(t *testing.T) { + if !enabled { + t.Skip("Skipping Gaia simulation after import") + } + + // Setup Gaia application + var logger log.Logger + if verbose { + logger = log.TestingLogger() + } else { + logger = log.NewNopLogger() + } + dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") + db, _ := sdk.NewLevelDB("Simulation", dir) + defer func() { + db.Close() + os.RemoveAll(dir) + }() + app := NewGaiaApp(logger, db, nil, true, 0, fauxMerkleModeOpt) + require.Equal(t, "GaiaApp", app.Name()) + + // Run randomized simulation + stopEarly, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(t, os.Stdout, app)) + + if commit { + // for memdb: + // fmt.Println("Database Size", db.Stats()["database.size"]) + fmt.Println("GoLevelDB Stats") + fmt.Println(db.Stats()["leveldb.stats"]) + fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) + } + require.Nil(t, err) + + if stopEarly { + // we can't export or import a zero-validator genesis + fmt.Printf("We can't export or import a zero-validator genesis, exiting test...\n") + return + } + + fmt.Printf("Exporting genesis...\n") + + appState, _, err := app.ExportAppStateAndValidators(true, []string{}) + if err != nil { + panic(err) + } + + fmt.Printf("Importing genesis...\n") + + newDir, _ := ioutil.TempDir("", "goleveldb-gaia-sim-2") + newDB, _ := sdk.NewLevelDB("Simulation-2", dir) + defer func() { + newDB.Close() + os.RemoveAll(newDir) + }() + newApp := NewGaiaApp(log.NewNopLogger(), newDB, nil, true, 0, fauxMerkleModeOpt) + require.Equal(t, "GaiaApp", newApp.Name()) + newApp.InitChain(abci.RequestInitChain{ + AppStateBytes: appState, + }) + + // Run randomized simulation on imported app + _, err = simulation.SimulateFromSeed(getSimulateFromSeedInput(t, os.Stdout, newApp)) + require.Nil(t, err) + +} + +// TODO: Make another test for the fuzzer itself, which just has noOp txs +// and doesn't depend on gaia +func TestAppStateDeterminism(t *testing.T) { + if !enabled { + t.Skip("Skipping Gaia simulation") + } + + numSeeds := 3 + numTimesToRunPerSeed := 5 + appHashList := make([]json.RawMessage, numTimesToRunPerSeed) + + for i := 0; i < numSeeds; i++ { + seed := rand.Int63() + for j := 0; j < numTimesToRunPerSeed; j++ { + logger := log.NewNopLogger() + db := dbm.NewMemDB() + app := NewGaiaApp(logger, db, nil, true, 0) + + // Run randomized simulation + simulation.SimulateFromSeed( + t, os.Stdout, app.BaseApp, appStateFn, seed, + testAndRunTxs(app), + []sdk.Invariant{}, + 50, + 100, + true, + false, + ) + appHash := app.LastCommitID().Hash + appHashList[j] = appHash + } + for k := 1; k < numTimesToRunPerSeed; k++ { + require.Equal(t, appHashList[0], appHashList[k], "appHash list: %v", appHashList) + } + } +} + +func BenchmarkInvariants(b *testing.B) { + // 1. Setup a simulated Gaia application + logger := log.NewNopLogger() + dir, _ := ioutil.TempDir("", "goleveldb-gaia-invariant-bench") + db, _ := sdk.NewLevelDB("simulation", dir) + + defer func() { + db.Close() + os.RemoveAll(dir) + }() + + app := NewGaiaApp(logger, db, nil, true, 0) + + // 2. Run parameterized simulation (w/o invariants) + _, err := simulation.SimulateFromSeed( + b, ioutil.Discard, app.BaseApp, appStateFn, seed, testAndRunTxs(app), + []sdk.Invariant{}, numBlocks, blockSize, commit, lean, + ) + if err != nil { + fmt.Println(err) + b.FailNow() + } + + ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight() + 1}) + + // 3. Benchmark each invariant separately + // + // NOTE: We use the crisis keeper as it has all the invariants registered with + // their respective metadata which makes it useful for testing/benchmarking. + for _, cr := range app.crisisKeeper.Routes() { + b.Run(fmt.Sprintf("%s/%s", cr.ModuleName, cr.Route), func(b *testing.B) { + if err := cr.Invar(ctx); err != nil { + fmt.Println(err) + b.FailNow() + } + }) + } +} diff --git a/app/test_util.go b/app/test_util.go new file mode 100644 index 00000000..4f04c402 --- /dev/null +++ b/app/test_util.go @@ -0,0 +1,23 @@ +package app + +import ( + "io" + + "github.com/tendermint/tendermint/libs/log" + + dbm "github.com/tendermint/tendermint/libs/db" + + bam "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +// used for debugging by gaia/cmd/gaiadebug +// NOTE to not use this function with non-test code +func NewGaiaAppUNSAFE(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, + invCheckPeriod uint, baseAppOptions ...func(*bam.BaseApp), +) (gapp *GaiaApp, keyMain, keyStaking *sdk.KVStoreKey, stakingKeeper staking.Keeper) { + + gapp = NewGaiaApp(logger, db, traceStore, loadLatest, invCheckPeriod, baseAppOptions...) + return gapp, gapp.keyMain, gapp.keyStaking, gapp.stakingKeeper +} diff --git a/cli_test/README.md b/cli_test/README.md new file mode 100644 index 00000000..857150ca --- /dev/null +++ b/cli_test/README.md @@ -0,0 +1,51 @@ +# Gaia CLI Integration tests + +The gaia cli integration tests live in this folder. You can run the full suite by running: + +```bash +$ go test -mod=readonly -p 4 `go list ./cmd/gaia/cli_test/...` -tags=cli_test +# OR! +$ make test_cli +``` +> NOTE: While the full suite runs in parallel, some of the tests can take up to a minute to complete + +### Test Structure + +This integration suite [uses a thin wrapper](https://godoc.org/github.com/cosmos/cosmos-sdk/tests) over the [`os/exec`](https://golang.org/pkg/os/exec/) package. This allows the integration test to run against built binaries (both `gaiad` and `gaiacli` are used) while being written in golang. This allows tests to take advantage of the various golang code we have for operations like marshal/unmarshal, crypto, etc... + +> NOTE: The tests will use whatever `gaiad` or `gaiacli` binaries are available in your `$PATH`. You can check which binary will be run by the suite by running `which gaiad` or `which gaiacli`. If you have your `$GOPATH` properly setup they should be in `$GOPATH/bin/gaia*`. This will ensure that your test uses the latest binary you have built + +Tests generally follow this structure: + +```go +func TestMyNewCommand(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + // Your test code goes here... + + f.Cleanup() +} +``` + +This boilerplate above: +- Ensures the tests run in parallel. Because the tests are calling out to `os/exec` for many operations these tests can take a long time to run. +- Creates `.gaiad` and `.gaiacli` folders in a new temp folder. +- Uses `gaiacli` to create 2 accounts for use in testing: `foo` and `bar` +- Creates a genesis file with coins (`1000footoken,1000feetoken,150stake`) controlled by the `foo` key +- Generates an initial bonding transaction (`gentx`) to make the `foo` key a validator at genesis +- Starts `gaiad` and stops it once the test exits +- Cleans up test state on a successful run + +### Notes when adding/running tests + +- Because the tests run against a built binary, you should make sure you build every time the code changes and you want to test again, otherwise you will be testing against an older version. If you are adding new tests this can easily lead to confusing test results. +- The [`test_helpers.go`](./test_helpers.go) file is organized according to the format of `gaiacli` and `gaiad` commands. There are comments with section headers describing the different areas. Helper functions to call CLI functionality are generally named after the command (e.g. `gaiacli query staking validator` would be `QueryStakingValidator`). Try to keep functions grouped by their position in the command tree. +- Test state that is needed by `tx` and `query` commands (`home`, `chain_id`, etc...) is stored on the `Fixtures` object. This makes constructing your new tests almost trivial. +- Sometimes if you exit a test early there can be still running `gaiad` and `gaiacli` processes that will interrupt subsequent runs. Still running `gaiacli` processes will block access to the keybase while still running `gaiad` processes will block ports and prevent new tests from spinning up. You can ensure new tests spin up clean by running `pkill -9 gaiad && pkill -9 gaiacli` before each test run. +- Most `query` and `tx` commands take a variadic `flags` argument. This pattern allows for the creation of a general function which is easily modified by adding flags. See the `TxSend` function and its use for a good example. +- `Tx*` functions follow a general pattern and return `(success bool, stdout string, stderr string)`. This allows for easy testing of multiple different flag configurations. See `TestGaiaCLICreateValidator` or `TestGaiaCLISubmitProposal` for a good example of the pattern. diff --git a/cli_test/cli_test.go b/cli_test/cli_test.go new file mode 100644 index 00000000..3da984e5 --- /dev/null +++ b/cli_test/cli_test.go @@ -0,0 +1,1251 @@ +// +build cli_test + +package clitest + +import ( + "encoding/base64" + "errors" + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/tendermint/tendermint/crypto/ed25519" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/gaia/app" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/tests" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/genaccounts" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/mint" +) + +func TestGaiaCLIKeysAddMultisig(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // key names order does not matter + f.KeysAdd("msig1", "--multisig-threshold=2", + fmt.Sprintf("--multisig=%s,%s", keyBar, keyBaz)) + f.KeysAdd("msig2", "--multisig-threshold=2", + fmt.Sprintf("--multisig=%s,%s", keyBaz, keyBar)) + require.Equal(t, f.KeysShow("msig1").Address, f.KeysShow("msig2").Address) + + f.KeysAdd("msig3", "--multisig-threshold=2", + fmt.Sprintf("--multisig=%s,%s", keyBar, keyBaz), + "--nosort") + f.KeysAdd("msig4", "--multisig-threshold=2", + fmt.Sprintf("--multisig=%s,%s", keyBaz, keyBar), + "--nosort") + require.NotEqual(t, f.KeysShow("msig3").Address, f.KeysShow("msig4").Address) + + // Cleanup testing directories + f.Cleanup() +} + +func TestGaiaCLIKeysAddRecover(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + exitSuccess, _, _ := f.KeysAddRecover("empty-mnemonic", "") + require.False(t, exitSuccess) + + exitSuccess, _, _ = f.KeysAddRecover("test-recover", "dentist task convince chimney quality leave banana trade firm crawl eternal easily") + require.True(t, exitSuccess) + require.Equal(t, "cosmos1qcfdf69js922qrdr4yaww3ax7gjml6pdds46f4", f.KeyAddress("test-recover").String()) + + // Cleanup testing directories + f.Cleanup() +} + +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()) + + // Cleanup testing directories + f.Cleanup() +} + +func TestGaiaCLIMinimumFees(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server with minimum fees + minGasPrice, _ := sdk.NewDecFromStr("0.000006") + fees := fmt.Sprintf( + "--minimum-gas-prices=%s,%s", + sdk.NewDecCoinFromDec(feeDenom, minGasPrice), + sdk.NewDecCoinFromDec(fee2Denom, minGasPrice), + ) + proc := f.GDStart(fees) + defer proc.Stop(false) + + barAddr := f.KeyAddress(keyBar) + + // Send a transaction that will get rejected + success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10), "-y") + require.False(f.T, success) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure tx w/ correct fees pass + txFees := fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)) + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10), txFees, "-y") + require.True(f.T, success) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure tx w/ improper fees fails + txFees = fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 1)) + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 10), txFees, "-y") + require.False(f.T, success) + + // Cleanup testing directories + f.Cleanup() +} + +func TestGaiaCLIGasPrices(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server with minimum fees + minGasPrice, _ := sdk.NewDecFromStr("0.000006") + proc := f.GDStart(fmt.Sprintf("--minimum-gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, minGasPrice))) + defer proc.Stop(false) + + barAddr := f.KeyAddress(keyBar) + + // insufficient gas prices (tx fails) + badGasPrice, _ := sdk.NewDecFromStr("0.000003") + success, _, _ := f.TxSend( + keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 50), + fmt.Sprintf("--gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, badGasPrice)), "-y") + require.False(t, success) + + // wait for a block confirmation + tests.WaitForNextNBlocksTM(1, f.Port) + + // sufficient gas prices (tx passes) + success, _, _ = f.TxSend( + keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 50), + fmt.Sprintf("--gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, minGasPrice)), "-y") + require.True(t, success) + + // wait for a block confirmation + tests.WaitForNextNBlocksTM(1, f.Port) + + f.Cleanup() +} + +func TestGaiaCLIFeesDeduction(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server with minimum fees + minGasPrice, _ := sdk.NewDecFromStr("0.000006") + proc := f.GDStart(fmt.Sprintf("--minimum-gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, minGasPrice))) + defer proc.Stop(false) + + // Save key addresses for later use + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + fooAcc := f.QueryAccount(fooAddr) + fooAmt := fooAcc.GetCoins().AmountOf(fooDenom) + + // test simulation + success, _, _ := f.TxSend( + keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 1000), + fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)), "--dry-run") + require.True(t, success) + + // Wait for a block + tests.WaitForNextNBlocksTM(1, f.Port) + + // ensure state didn't change + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, fooAmt.Int64(), fooAcc.GetCoins().AmountOf(fooDenom).Int64()) + + // insufficient funds (coins + fees) tx fails + largeCoins := sdk.TokensFromTendermintPower(10000000) + success, _, _ = f.TxSend( + keyFoo, barAddr, sdk.NewCoin(fooDenom, largeCoins), + fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)), "-y") + require.False(t, success) + + // Wait for a block + tests.WaitForNextNBlocksTM(1, f.Port) + + // ensure state didn't change + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, fooAmt.Int64(), fooAcc.GetCoins().AmountOf(fooDenom).Int64()) + + // test success (transfer = coins + fees) + success, _, _ = f.TxSend( + keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 500), + fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)), "-y") + require.True(t, success) + + f.Cleanup() +} + +func TestGaiaCLISend(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + // Save key addresses for later use + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + fooAcc := f.QueryAccount(fooAddr) + startTokens := sdk.TokensFromTendermintPower(50) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) + + // Send some tokens from one account to the other + sendTokens := sdk.TokensFromTendermintPower(10) + f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure account balances match expected + barAcc := f.QueryAccount(barAddr) + require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom)) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom)) + + // Test --dry-run + success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--dry-run") + require.True(t, success) + + // Test --generate-only + success, stdout, stderr := f.TxSend( + fooAddr.String(), barAddr, sdk.NewCoin(denom, sendTokens), "--generate-only=true", + ) + require.Empty(t, stderr) + require.True(t, success) + msg := unmarshalStdTx(f.T, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Len(t, msg.Msgs, 1) + require.Len(t, msg.GetSignatures(), 0) + + // Check state didn't change + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom)) + + // test autosequencing + f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure account balances match expected + barAcc = f.QueryAccount(barAddr) + require.Equal(t, sendTokens.MulRaw(2), barAcc.GetCoins().AmountOf(denom)) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens.Sub(sendTokens.MulRaw(2)), fooAcc.GetCoins().AmountOf(denom)) + + // test memo + f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--memo='testmemo'", "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure account balances match expected + barAcc = f.QueryAccount(barAddr) + require.Equal(t, sendTokens.MulRaw(3), barAcc.GetCoins().AmountOf(denom)) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens.Sub(sendTokens.MulRaw(3)), fooAcc.GetCoins().AmountOf(denom)) + + f.Cleanup() +} + +func TestGaiaCLIConfirmTx(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + // Save key addresses for later use + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + fooAcc := f.QueryAccount(fooAddr) + startTokens := sdk.TokensFromTendermintPower(50) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) + + // send some tokens from one account to the other + sendTokens := sdk.TokensFromTendermintPower(10) + f.txSendWithConfirm(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "Y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // ensure account balances match expected + barAcc := f.QueryAccount(barAddr) + require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom)) + + // send some tokens from one account to the other (cancelling confirmation) + f.txSendWithConfirm(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "n") + tests.WaitForNextNBlocksTM(1, f.Port) + + // ensure account balances match expected + barAcc = f.QueryAccount(barAddr) + require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom)) + + // Cleanup testing directories + f.Cleanup() +} + +func TestGaiaCLIGasAuto(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + fooAcc := f.QueryAccount(fooAddr) + startTokens := sdk.TokensFromTendermintPower(50) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) + + // Test failure with auto gas disabled and very little gas set by hand + sendTokens := sdk.TokensFromTendermintPower(10) + success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=10", "-y") + require.False(t, success) + + // Check state didn't change + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) + + // Test failure with negative gas + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=-100", "-y") + require.False(t, success) + + // Check state didn't change + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) + + // Test failure with 0 gas + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=0", "-y") + require.False(t, success) + + // Check state didn't change + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) + + // Enable auto gas + success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=auto", "-y") + require.NotEmpty(t, stderr) + require.True(t, success) + cdc := app.MakeCodec() + sendResp := sdk.TxResponse{} + err := cdc.UnmarshalJSON([]byte(stdout), &sendResp) + require.Nil(t, err) + require.True(t, sendResp.GasWanted >= sendResp.GasUsed) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Check state has changed accordingly + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom)) + + f.Cleanup() +} + +func TestGaiaCLICreateValidator(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + barAddr := f.KeyAddress(keyBar) + barVal := sdk.ValAddress(barAddr) + + consPubKey := sdk.MustBech32ifyConsPub(ed25519.GenPrivKey().PubKey()) + + sendTokens := sdk.TokensFromTendermintPower(10) + f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + barAcc := f.QueryAccount(barAddr) + require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom)) + + // Generate a create validator transaction and ensure correctness + success, stdout, stderr := f.TxStakingCreateValidator(barAddr.String(), consPubKey, sdk.NewInt64Coin(denom, 2), "--generate-only") + + require.True(f.T, success) + require.Empty(f.T, stderr) + msg := unmarshalStdTx(f.T, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 0, len(msg.GetSignatures())) + + // Test --dry-run + newValTokens := sdk.TokensFromTendermintPower(2) + success, _, _ = f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewCoin(denom, newValTokens), "--dry-run") + require.True(t, success) + + // Create the validator + f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewCoin(denom, newValTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure funds were deducted properly + barAcc = f.QueryAccount(barAddr) + require.Equal(t, sendTokens.Sub(newValTokens), barAcc.GetCoins().AmountOf(denom)) + + // Ensure that validator state is as expected + validator := f.QueryStakingValidator(barVal) + require.Equal(t, validator.OperatorAddress, barVal) + require.True(sdk.IntEq(t, newValTokens, validator.Tokens)) + + // Query delegations to the validator + validatorDelegations := f.QueryStakingDelegationsTo(barVal) + require.Len(t, validatorDelegations, 1) + require.NotZero(t, validatorDelegations[0].Shares) + + // unbond a single share + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(1)) + success = f.TxStakingUnbond(keyBar, unbondAmt.String(), barVal, "-y") + require.True(t, success) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure bonded staking is correct + remainingTokens := newValTokens.Sub(unbondAmt.Amount) + validator = f.QueryStakingValidator(barVal) + require.Equal(t, remainingTokens, validator.Tokens) + + // Get unbonding delegations from the validator + validatorUbds := f.QueryStakingUnbondingDelegationsFrom(barVal) + require.Len(t, validatorUbds, 1) + require.Len(t, validatorUbds[0].Entries, 1) + require.Equal(t, remainingTokens.String(), validatorUbds[0].Entries[0].Balance.String()) + + f.Cleanup() +} + +func TestGaiaCLIQueryRewards(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + cdc := app.MakeCodec() + + genesisState := f.GenesisState() + inflationMin := sdk.MustNewDecFromStr("10000.0") + var mintData mint.GenesisState + cdc.UnmarshalJSON(genesisState[mint.ModuleName], &mintData) + mintData.Minter.Inflation = inflationMin + mintData.Params.InflationMin = inflationMin + mintData.Params.InflationMax = sdk.MustNewDecFromStr("15000.0") + mintDataBz, err := cdc.MarshalJSON(mintData) + require.NoError(t, err) + genesisState[mint.ModuleName] = mintDataBz + + genFile := filepath.Join(f.GaiadHome, "config", "genesis.json") + genDoc, err := tmtypes.GenesisDocFromFile(genFile) + require.NoError(t, err) + genDoc.AppState, err = cdc.MarshalJSON(genesisState) + require.NoError(t, genDoc.SaveAs(genFile)) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + fooAddr := f.KeyAddress(keyFoo) + rewards := f.QueryRewards(fooAddr) + require.Equal(t, 1, len(rewards.Rewards)) + + f.Cleanup() +} + +func TestGaiaCLISubmitProposal(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + f.QueryGovParamDeposit() + f.QueryGovParamVoting() + f.QueryGovParamTallying() + + fooAddr := f.KeyAddress(keyFoo) + + fooAcc := f.QueryAccount(fooAddr) + startTokens := sdk.TokensFromTendermintPower(50) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(sdk.DefaultBondDenom)) + + proposalsQuery := f.QueryGovProposals() + require.Empty(t, proposalsQuery) + + // Test submit generate only for submit proposal + proposalTokens := sdk.TokensFromTendermintPower(5) + success, stdout, stderr := f.TxGovSubmitProposal( + fooAddr.String(), "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens), "--generate-only", "-y") + require.True(t, success) + require.Empty(t, stderr) + msg := unmarshalStdTx(t, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 0, len(msg.GetSignatures())) + + // Test --dry-run + success, _, _ = f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens), "--dry-run") + require.True(t, success) + + // Create the proposal + f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure transaction tags can be queried + searchResult := f.QueryTxs(1, 50, "action:submit_proposal", fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, searchResult.Txs, 1) + + // Ensure deposit was deducted + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens.Sub(proposalTokens), fooAcc.GetCoins().AmountOf(denom)) + + // Ensure propsal is directly queryable + proposal1 := f.QueryGovProposal(1) + require.Equal(t, uint64(1), proposal1.ProposalID) + require.Equal(t, gov.StatusDepositPeriod, proposal1.Status) + + // Ensure query proposals returns properly + proposalsQuery = f.QueryGovProposals() + require.Equal(t, uint64(1), proposalsQuery[0].ProposalID) + + // Query the deposits on the proposal + deposit := f.QueryGovDeposit(1, fooAddr) + require.Equal(t, proposalTokens, deposit.Amount.AmountOf(denom)) + + // Test deposit generate only + depositTokens := sdk.TokensFromTendermintPower(10) + success, stdout, stderr = f.TxGovDeposit(1, fooAddr.String(), sdk.NewCoin(denom, depositTokens), "--generate-only") + require.True(t, success) + require.Empty(t, stderr) + msg = unmarshalStdTx(t, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 0, len(msg.GetSignatures())) + + // Run the deposit transaction + f.TxGovDeposit(1, keyFoo, sdk.NewCoin(denom, depositTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // test query deposit + deposits := f.QueryGovDeposits(1) + require.Len(t, deposits, 1) + require.Equal(t, proposalTokens.Add(depositTokens), deposits[0].Amount.AmountOf(denom)) + + // Ensure querying the deposit returns the proper amount + deposit = f.QueryGovDeposit(1, fooAddr) + require.Equal(t, proposalTokens.Add(depositTokens), deposit.Amount.AmountOf(denom)) + + // Ensure tags are set on the transaction + searchResult = f.QueryTxs(1, 50, "action:deposit", fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, searchResult.Txs, 1) + + // Ensure account has expected amount of funds + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens.Sub(proposalTokens.Add(depositTokens)), fooAcc.GetCoins().AmountOf(denom)) + + // Fetch the proposal and ensure it is now in the voting period + proposal1 = f.QueryGovProposal(1) + require.Equal(t, uint64(1), proposal1.ProposalID) + require.Equal(t, gov.StatusVotingPeriod, proposal1.Status) + + // Test vote generate only + success, stdout, stderr = f.TxGovVote(1, gov.OptionYes, fooAddr.String(), "--generate-only") + require.True(t, success) + require.Empty(t, stderr) + msg = unmarshalStdTx(t, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 0, len(msg.GetSignatures())) + + // Vote on the proposal + f.TxGovVote(1, gov.OptionYes, keyFoo, "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Query the vote + vote := f.QueryGovVote(1, fooAddr) + require.Equal(t, uint64(1), vote.ProposalID) + require.Equal(t, gov.OptionYes, vote.Option) + + // Query the votes + votes := f.QueryGovVotes(1) + require.Len(t, votes, 1) + require.Equal(t, uint64(1), votes[0].ProposalID) + require.Equal(t, gov.OptionYes, votes[0].Option) + + // Ensure tags are applied to voting transaction properly + searchResult = f.QueryTxs(1, 50, "action:vote", fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, searchResult.Txs, 1) + + // Ensure no proposals in deposit period + proposalsQuery = f.QueryGovProposals("--status=DepositPeriod") + require.Empty(t, proposalsQuery) + + // Ensure the proposal returns as in the voting period + proposalsQuery = f.QueryGovProposals("--status=VotingPeriod") + require.Equal(t, uint64(1), proposalsQuery[0].ProposalID) + + // submit a second test proposal + f.TxGovSubmitProposal(keyFoo, "Text", "Apples", "test", sdk.NewCoin(denom, proposalTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Test limit on proposals query + proposalsQuery = f.QueryGovProposals("--limit=1") + require.Equal(t, uint64(2), proposalsQuery[0].ProposalID) + + f.Cleanup() +} + +func TestGaiaCLISubmitParamChangeProposal(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + proc := f.GDStart() + defer proc.Stop(false) + + fooAddr := f.KeyAddress(keyFoo) + fooAcc := f.QueryAccount(fooAddr) + startTokens := sdk.TokensFromTendermintPower(50) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(sdk.DefaultBondDenom)) + + // write proposal to file + proposalTokens := sdk.TokensFromTendermintPower(5) + proposal := fmt.Sprintf(`{ + "title": "Param Change", + "description": "Update max validators", + "changes": [ + { + "subspace": "staking", + "key": "MaxValidators", + "value": 105 + } + ], + "deposit": [ + { + "denom": "stake", + "amount": "%s" + } + ] +} +`, proposalTokens.String()) + + proposalFile := WriteToNewTempFile(t, proposal) + + // create the param change proposal + f.TxGovSubmitParamChangeProposal(keyFoo, proposalFile.Name(), sdk.NewCoin(denom, proposalTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // ensure transaction tags can be queried + txsPage := f.QueryTxs(1, 50, "action:submit_proposal", fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage.Txs, 1) + + // ensure deposit was deducted + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens.Sub(proposalTokens).String(), fooAcc.GetCoins().AmountOf(sdk.DefaultBondDenom).String()) + + // ensure proposal is directly queryable + proposal1 := f.QueryGovProposal(1) + require.Equal(t, uint64(1), proposal1.ProposalID) + require.Equal(t, gov.StatusDepositPeriod, proposal1.Status) + + // ensure correct query proposals result + proposalsQuery := f.QueryGovProposals() + require.Equal(t, uint64(1), proposalsQuery[0].ProposalID) + + // ensure the correct deposit amount on the proposal + deposit := f.QueryGovDeposit(1, fooAddr) + require.Equal(t, proposalTokens, deposit.Amount.AmountOf(denom)) + + // Cleanup testing directories + f.Cleanup() +} + +func TestGaiaCLIQueryTxPagination(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + accFoo := f.QueryAccount(fooAddr) + seq := accFoo.GetSequence() + + for i := 1; i <= 30; i++ { + success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, int64(i)), fmt.Sprintf("--sequence=%d", seq), "-y") + require.True(t, success) + seq++ + } + + // perPage = 15, 2 pages + txsPage1 := f.QueryTxs(1, 15, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage1.Txs, 15) + require.Equal(t, txsPage1.Count, 15) + txsPage2 := f.QueryTxs(2, 15, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage2.Txs, 15) + require.NotEqual(t, txsPage1.Txs, txsPage2.Txs) + txsPage3 := f.QueryTxs(3, 15, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage3.Txs, 15) + require.Equal(t, txsPage2.Txs, txsPage3.Txs) + + // perPage = 16, 2 pages + txsPage1 = f.QueryTxs(1, 16, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage1.Txs, 16) + txsPage2 = f.QueryTxs(2, 16, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage2.Txs, 14) + require.NotEqual(t, txsPage1.Txs, txsPage2.Txs) + + // perPage = 50 + txsPageFull := f.QueryTxs(1, 50, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPageFull.Txs, 30) + require.Equal(t, txsPageFull.Txs, append(txsPage1.Txs, txsPage2.Txs...)) + + // perPage = 0 + f.QueryTxsInvalid(errors.New("ERROR: page must greater than 0"), 0, 50, fmt.Sprintf("sender:%s", fooAddr)) + + // limit = 0 + f.QueryTxsInvalid(errors.New("ERROR: limit must greater than 0"), 1, 0, fmt.Sprintf("sender:%s", fooAddr)) + + // Cleanup testing directories + f.Cleanup() +} + +func TestGaiaCLIValidateSignatures(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + // generate sendTx with default gas + success, stdout, stderr := f.TxSend(fooAddr.String(), barAddr, sdk.NewInt64Coin(denom, 10), "--generate-only") + require.True(t, success) + require.Empty(t, stderr) + + // write unsigned tx to file + unsignedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(unsignedTxFile.Name()) + + // validate we can successfully sign + success, stdout, stderr = f.TxSign(keyFoo, unsignedTxFile.Name()) + require.True(t, success) + require.Empty(t, stderr) + stdTx := unmarshalStdTx(t, stdout) + require.Equal(t, len(stdTx.Msgs), 1) + require.Equal(t, 1, len(stdTx.GetSignatures())) + require.Equal(t, fooAddr.String(), stdTx.GetSigners()[0].String()) + + // write signed tx to file + signedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(signedTxFile.Name()) + + // validate signatures + success, _, _ = f.TxSign(keyFoo, signedTxFile.Name(), "--validate-signatures") + require.True(t, success) + + // modify the transaction + stdTx.Memo = "MODIFIED-ORIGINAL-TX-BAD" + bz := marshalStdTx(t, stdTx) + modSignedTxFile := WriteToNewTempFile(t, string(bz)) + defer os.Remove(modSignedTxFile.Name()) + + // validate signature validation failure due to different transaction sig bytes + success, _, _ = f.TxSign(keyFoo, modSignedTxFile.Name(), "--validate-signatures") + require.False(t, success) + + f.Cleanup() +} + +func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + // Test generate sendTx with default gas + sendTokens := sdk.TokensFromTendermintPower(10) + success, stdout, stderr := f.TxSend(fooAddr.String(), barAddr, sdk.NewCoin(denom, sendTokens), "--generate-only") + require.True(t, success) + require.Empty(t, stderr) + msg := unmarshalStdTx(t, stdout) + require.Equal(t, msg.Fee.Gas, uint64(client.DefaultGasLimit)) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 0, len(msg.GetSignatures())) + + // Test generate sendTx with --gas=$amount + success, stdout, stderr = f.TxSend(fooAddr.String(), barAddr, sdk.NewCoin(denom, sendTokens), "--gas=100", "--generate-only") + require.True(t, success) + require.Empty(t, stderr) + msg = unmarshalStdTx(t, stdout) + require.Equal(t, msg.Fee.Gas, uint64(100)) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 0, len(msg.GetSignatures())) + + // Test generate sendTx, estimate gas + success, stdout, stderr = f.TxSend(fooAddr.String(), barAddr, sdk.NewCoin(denom, sendTokens), "--generate-only") + require.True(t, success) + require.Empty(t, stderr) + msg = unmarshalStdTx(t, stdout) + require.True(t, msg.Fee.Gas > 0) + require.Equal(t, len(msg.Msgs), 1) + + // Write the output to disk + unsignedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(unsignedTxFile.Name()) + + // Test sign --validate-signatures + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--validate-signatures") + require.False(t, success) + require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n\n", fooAddr.String()), stdout) + + // Test sign + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name()) + require.True(t, success) + msg = unmarshalStdTx(t, stdout) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 1, len(msg.GetSignatures())) + require.Equal(t, fooAddr.String(), msg.GetSigners()[0].String()) + + // Write the output to disk + signedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(signedTxFile.Name()) + + // Test sign --validate-signatures + success, stdout, _ = f.TxSign(keyFoo, signedTxFile.Name(), "--validate-signatures") + require.True(t, success) + require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n 0: %v\t\t\t[OK]\n\n", fooAddr.String(), + fooAddr.String()), stdout) + + // Ensure foo has right amount of funds + fooAcc := f.QueryAccount(fooAddr) + startTokens := sdk.TokensFromTendermintPower(50) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) + + // Test broadcast + success, stdout, _ = f.TxBroadcast(signedTxFile.Name()) + require.True(t, success) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure account state + barAcc := f.QueryAccount(barAddr) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom)) + require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom)) + + f.Cleanup() +} + +func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server with minimum fees + proc := f.GDStart() + defer proc.Stop(false) + + fooBarBazAddr := f.KeyAddress(keyFooBarBaz) + barAddr := f.KeyAddress(keyBar) + + // Send some tokens from one account to the other + success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10), "-y") + require.True(t, success) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Test generate sendTx with multisig + success, stdout, _ := f.TxSend(fooBarBazAddr.String(), barAddr, sdk.NewInt64Coin(denom, 5), "--generate-only") + require.True(t, success) + + // Write the output to disk + unsignedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(unsignedTxFile.Name()) + + // Sign with foo's key + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String(), "-y") + require.True(t, success) + + // Write the output to disk + fooSignatureFile := WriteToNewTempFile(t, stdout) + defer os.Remove(fooSignatureFile.Name()) + + // Multisign, not enough signatures + success, stdout, _ = f.TxMultisign(unsignedTxFile.Name(), keyFooBarBaz, []string{fooSignatureFile.Name()}) + require.True(t, success) + + // Write the output to disk + signedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(signedTxFile.Name()) + + // Validate the multisignature + success, _, _ = f.TxSign(keyFooBarBaz, signedTxFile.Name(), "--validate-signatures") + require.False(t, success) + + // Broadcast the transaction + success, _, _ = f.TxBroadcast(signedTxFile.Name()) + require.False(t, success) + + // Cleanup testing directories + f.Cleanup() +} + +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) + keyAddr := f.KeyAddress(keyFoo) + + sendTokens := sdk.TokensFromTendermintPower(10) + success, stdout, stderr := f.TxSend(keyAddr.String(), 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) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server with minimum fees + proc := f.GDStart() + defer proc.Stop(false) + + fooBarBazAddr := f.KeyAddress(keyFooBarBaz) + barAddr := f.KeyAddress(keyBar) + + // Send some tokens from one account to the other + success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10), "-y") + require.True(t, success) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure account balances match expected + fooBarBazAcc := f.QueryAccount(fooBarBazAddr) + require.Equal(t, int64(10), fooBarBazAcc.GetCoins().AmountOf(denom).Int64()) + + // Test generate sendTx with multisig + success, stdout, _ := f.TxSend(fooBarBazAddr.String(), barAddr, sdk.NewInt64Coin(denom, 5), "--generate-only") + require.True(t, success) + + // Write the output to disk + unsignedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(unsignedTxFile.Name()) + + // Sign with foo's key + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String()) + require.True(t, success) + + // Write the output to disk + fooSignatureFile := WriteToNewTempFile(t, stdout) + defer os.Remove(fooSignatureFile.Name()) + + // Sign with baz's key + success, stdout, _ = f.TxSign(keyBaz, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String()) + require.True(t, success) + + // Write the output to disk + bazSignatureFile := WriteToNewTempFile(t, stdout) + defer os.Remove(bazSignatureFile.Name()) + + // Multisign, keys in different order + success, stdout, _ = f.TxMultisign(unsignedTxFile.Name(), keyFooBarBaz, []string{ + bazSignatureFile.Name(), fooSignatureFile.Name()}) + require.True(t, success) + + // Write the output to disk + signedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(signedTxFile.Name()) + + // Validate the multisignature + success, _, _ = f.TxSign(keyFooBarBaz, signedTxFile.Name(), "--validate-signatures") + require.True(t, success) + + // Broadcast the transaction + success, _, _ = f.TxBroadcast(signedTxFile.Name()) + require.True(t, success) + + // Cleanup testing directories + f.Cleanup() +} + +func TestGaiaCLIMultisign(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server with minimum fees + proc := f.GDStart() + defer proc.Stop(false) + + fooBarBazAddr := f.KeyAddress(keyFooBarBaz) + bazAddr := f.KeyAddress(keyBaz) + + // Send some tokens from one account to the other + success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10), "-y") + require.True(t, success) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure account balances match expected + fooBarBazAcc := f.QueryAccount(fooBarBazAddr) + require.Equal(t, int64(10), fooBarBazAcc.GetCoins().AmountOf(denom).Int64()) + + // Test generate sendTx with multisig + success, stdout, stderr := f.TxSend(fooBarBazAddr.String(), bazAddr, sdk.NewInt64Coin(denom, 10), "--generate-only") + require.True(t, success) + require.Empty(t, stderr) + + // Write the output to disk + unsignedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(unsignedTxFile.Name()) + + // Sign with foo's key + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String(), "-y") + require.True(t, success) + + // Write the output to disk + fooSignatureFile := WriteToNewTempFile(t, stdout) + defer os.Remove(fooSignatureFile.Name()) + + // Sign with bar's key + success, stdout, _ = f.TxSign(keyBar, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String(), "-y") + require.True(t, success) + + // Write the output to disk + barSignatureFile := WriteToNewTempFile(t, stdout) + defer os.Remove(barSignatureFile.Name()) + + // Multisign + success, stdout, _ = f.TxMultisign(unsignedTxFile.Name(), keyFooBarBaz, []string{ + fooSignatureFile.Name(), barSignatureFile.Name()}) + require.True(t, success) + + // Write the output to disk + signedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(signedTxFile.Name()) + + // Validate the multisignature + success, _, _ = f.TxSign(keyFooBarBaz, signedTxFile.Name(), "--validate-signatures", "-y") + require.True(t, success) + + // Broadcast the transaction + success, _, _ = f.TxBroadcast(signedTxFile.Name()) + require.True(t, success) + + // Cleanup testing directories + f.Cleanup() +} + +func TestGaiaCLIConfig(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + node := fmt.Sprintf("%s:%s", f.RPCAddr, f.Port) + + // Set available configuration options + f.CLIConfig("broadcast-mode", "block") + f.CLIConfig("node", node) + f.CLIConfig("output", "text") + f.CLIConfig("trust-node", "true") + f.CLIConfig("chain-id", f.ChainID) + f.CLIConfig("trace", "false") + f.CLIConfig("indent", "true") + + config, err := ioutil.ReadFile(path.Join(f.GaiacliHome, "config", "config.toml")) + require.NoError(t, err) + expectedConfig := fmt.Sprintf(`broadcast-mode = "block" +chain-id = "%s" +indent = true +node = "%s" +output = "text" +trace = false +trust-node = true +`, f.ChainID, node) + require.Equal(t, expectedConfig, string(config)) + + f.Cleanup() +} + +func TestGaiadCollectGentxs(t *testing.T) { + t.Parallel() + var customMaxBytes, customMaxGas int64 = 99999999, 1234567 + f := NewFixtures(t) + + // Initialise temporary directories + gentxDir, err := ioutil.TempDir("", "") + gentxDoc := filepath.Join(gentxDir, "gentx.json") + require.NoError(t, err) + + // Reset testing path + f.UnsafeResetAll() + + // Initialize keys + f.KeysAdd(keyFoo) + + // Configure json output + f.CLIConfig("output", "json") + + // Run init + f.GDInit(keyFoo) + + // Customise genesis.json + + genFile := f.GenesisFile() + genDoc, err := tmtypes.GenesisDocFromFile(genFile) + require.NoError(t, err) + genDoc.ConsensusParams.Block.MaxBytes = customMaxBytes + genDoc.ConsensusParams.Block.MaxGas = customMaxGas + genDoc.SaveAs(genFile) + + // Add account to genesis.json + f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins) + + // Write gentx file + f.GenTx(keyFoo, fmt.Sprintf("--output-document=%s", gentxDoc)) + + // Collect gentxs from a custom directory + f.CollectGenTxs(fmt.Sprintf("--gentx-dir=%s", gentxDir)) + + genDoc, err = tmtypes.GenesisDocFromFile(genFile) + require.NoError(t, err) + require.Equal(t, genDoc.ConsensusParams.Block.MaxBytes, customMaxBytes) + require.Equal(t, genDoc.ConsensusParams.Block.MaxGas, customMaxGas) + + 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() + + cdc := app.MakeCodec() + accounts := genaccounts.GetGenesisStateFromAppState(cdc, genesisState).Accounts + + require.Equal(t, accounts[0].Address, f.KeyAddress(keyFoo)) + require.Equal(t, accounts[1].Address, f.KeyAddress(keyBar)) + require.True(t, accounts[0].Coins.IsEqual(startCoins)) + require.True(t, accounts[1].Coins.IsEqual(bazCoins)) + + // Cleanup testing directories + f.Cleanup() +} + +func TestSlashingGetParams(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + params := f.QuerySlashingParams() + require.Equal(t, time.Duration(120000000000), params.MaxEvidenceAge) + require.Equal(t, int64(100), params.SignedBlocksWindow) + 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) + + // Cleanup testing directories + f.Cleanup() +} + +func TestValidateGenesis(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + f.ValidateGenesis() + + // Cleanup testing directories + f.Cleanup() +} diff --git a/cli_test/doc.go b/cli_test/doc.go new file mode 100644 index 00000000..bcf9c5e4 --- /dev/null +++ b/cli_test/doc.go @@ -0,0 +1,3 @@ +package clitest + +// package clitest runs integration tests which make use of CLI commands. diff --git a/cli_test/test_helpers.go b/cli_test/test_helpers.go new file mode 100644 index 00000000..b72f50c5 --- /dev/null +++ b/cli_test/test_helpers.go @@ -0,0 +1,731 @@ +package clitest + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/client" + + "github.com/stretchr/testify/require" + + cmn "github.com/tendermint/tendermint/libs/common" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/gaia/app" + + clientkeys "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/tests" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +const ( + denom = "stake" + keyFoo = "foo" + keyBar = "bar" + fooDenom = "footoken" + feeDenom = "feetoken" + fee2Denom = "fee2token" + keyBaz = "baz" + keyVesting = "vesting" + keyFooBarBaz = "foobarbaz" +) + +var ( + startCoins = sdk.Coins{ + sdk.NewCoin(feeDenom, sdk.TokensFromTendermintPower(1000000)), + sdk.NewCoin(fee2Denom, sdk.TokensFromTendermintPower(1000000)), + sdk.NewCoin(fooDenom, sdk.TokensFromTendermintPower(1000)), + sdk.NewCoin(denom, sdk.TokensFromTendermintPower(150)), + } + + vestingCoins = sdk.Coins{ + sdk.NewCoin(feeDenom, sdk.TokensFromTendermintPower(500000)), + } +) + +//___________________________________________________________________________________ +// Fixtures + +// Fixtures is used to setup the testing environment +type Fixtures struct { + BuildDir string + RootDir string + GaiadBinary string + GaiacliBinary string + ChainID string + RPCAddr string + Port string + GaiadHome string + GaiacliHome string + P2PAddr string + T *testing.T +} + +// NewFixtures creates a new instance of Fixtures with many vars set +func NewFixtures(t *testing.T) *Fixtures { + tmpDir, err := ioutil.TempDir("", "gaia_integration_"+t.Name()+"_") + require.NoError(t, err) + + servAddr, port, err := server.FreeTCPAddr() + require.NoError(t, err) + + p2pAddr, _, err := server.FreeTCPAddr() + require.NoError(t, err) + + buildDir := os.Getenv("BUILDDIR") + if buildDir == "" { + buildDir, err = filepath.Abs("../../../build/") + require.NoError(t, err) + } + + return &Fixtures{ + T: t, + BuildDir: buildDir, + RootDir: tmpDir, + GaiadBinary: filepath.Join(buildDir, "gaiad"), + GaiacliBinary: filepath.Join(buildDir, "gaiacli"), + GaiadHome: filepath.Join(tmpDir, ".gaiad"), + GaiacliHome: filepath.Join(tmpDir, ".gaiacli"), + RPCAddr: servAddr, + P2PAddr: p2pAddr, + Port: port, + } +} + +// GenesisFile returns the path of the genesis file +func (f Fixtures) GenesisFile() string { + return filepath.Join(f.GaiadHome, "config", "genesis.json") +} + +// GenesisFile returns the application's genesis state +func (f Fixtures) GenesisState() app.GenesisState { + cdc := codec.New() + genDoc, err := tmtypes.GenesisDocFromFile(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 and initializes a chain +// with 1 validator. +func InitFixtures(t *testing.T) (f *Fixtures) { + f = NewFixtures(t) + + // reset test state + f.UnsafeResetAll() + + // ensure keystore has foo and bar keys + f.KeysDelete(keyFoo) + f.KeysDelete(keyBar) + f.KeysDelete(keyBar) + f.KeysDelete(keyFooBarBaz) + f.KeysAdd(keyFoo) + f.KeysAdd(keyBar) + f.KeysAdd(keyBaz) + f.KeysAdd(keyVesting) + f.KeysAdd(keyFooBarBaz, "--multisig-threshold=2", fmt.Sprintf( + "--multisig=%s,%s,%s", keyFoo, keyBar, keyBaz)) + + // ensure that CLI output is in JSON format + f.CLIConfig("output", "json") + + // NOTE: GDInit sets the ChainID + f.GDInit(keyFoo) + + f.CLIConfig("chain-id", f.ChainID) + f.CLIConfig("broadcast-mode", "block") + + // start an account with tokens + 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.CollectGenTxs() + + return +} + +// Cleanup is meant to be run at the end of a test to clean up an remaining test state +func (f *Fixtures) Cleanup(dirs ...string) { + clean := append(dirs, f.RootDir) + for _, d := range clean { + require.NoError(f.T, os.RemoveAll(d)) + } +} + +// Flags returns the flags necessary for making most CLI calls +func (f *Fixtures) Flags() string { + return fmt.Sprintf("--home=%s --node=%s", f.GaiacliHome, f.RPCAddr) +} + +//___________________________________________________________________________________ +// gaiad + +// UnsafeResetAll is gaiad unsafe-reset-all +func (f *Fixtures) UnsafeResetAll(flags ...string) { + cmd := fmt.Sprintf("%s --home=%s unsafe-reset-all", f.GaiadBinary, f.GaiadHome) + executeWrite(f.T, addFlags(cmd, flags)) + err := os.RemoveAll(filepath.Join(f.GaiadHome, "config", "gentx")) + require.NoError(f.T, err) +} + +// GDInit is gaiad init +// NOTE: GDInit sets the ChainID for the Fixtures instance +func (f *Fixtures) GDInit(moniker string, flags ...string) { + cmd := fmt.Sprintf("%s init -o --home=%s %s", f.GaiadBinary, f.GaiadHome, moniker) + _, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + + var chainID string + var initRes map[string]json.RawMessage + + err := json.Unmarshal([]byte(stderr), &initRes) + require.NoError(f.T, err) + + err = json.Unmarshal(initRes["chain_id"], &chainID) + require.NoError(f.T, err) + + f.ChainID = chainID +} + +// AddGenesisAccount is gaiad add-genesis-account +func (f *Fixtures) AddGenesisAccount(address sdk.AccAddress, coins sdk.Coins, flags ...string) { + cmd := fmt.Sprintf("%s add-genesis-account %s %s --home=%s", f.GaiadBinary, address, coins, f.GaiadHome) + executeWriteCheckErr(f.T, addFlags(cmd, flags)) +} + +// GenTx is gaiad gentx +func (f *Fixtures) GenTx(name string, flags ...string) { + cmd := fmt.Sprintf("%s gentx --name=%s --home=%s --home-client=%s", f.GaiadBinary, name, f.GaiadHome, f.GaiacliHome) + executeWriteCheckErr(f.T, addFlags(cmd, flags), client.DefaultKeyPass) +} + +// CollectGenTxs is gaiad collect-gentxs +func (f *Fixtures) CollectGenTxs(flags ...string) { + cmd := fmt.Sprintf("%s collect-gentxs --home=%s", f.GaiadBinary, f.GaiadHome) + executeWriteCheckErr(f.T, addFlags(cmd, flags), client.DefaultKeyPass) +} + +// GDStart runs gaiad start with the appropriate flags and returns a process +func (f *Fixtures) GDStart(flags ...string) *tests.Process { + cmd := fmt.Sprintf("%s start --home=%s --rpc.laddr=%v --p2p.laddr=%v", f.GaiadBinary, f.GaiadHome, f.RPCAddr, f.P2PAddr) + proc := tests.GoExecuteTWithStdout(f.T, addFlags(cmd, flags)) + tests.WaitForTMStart(f.Port) + tests.WaitForNextNBlocksTM(1, f.Port) + return proc +} + +// GDTendermint returns the results of gaiad tendermint [query] +func (f *Fixtures) GDTendermint(query string) string { + cmd := fmt.Sprintf("%s tendermint %s --home=%s", f.GaiadBinary, query, f.GaiadHome) + 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("%s validate-genesis --home=%s", f.GaiadBinary, f.GaiadHome) + executeWriteCheckErr(f.T, cmd) +} + +//___________________________________________________________________________________ +// gaiacli keys + +// KeysDelete is gaiacli keys delete +func (f *Fixtures) KeysDelete(name string, flags ...string) { + cmd := fmt.Sprintf("%s keys delete --home=%s %s", f.GaiacliBinary, f.GaiacliHome, name) + executeWrite(f.T, addFlags(cmd, append(append(flags, "-y"), "-f"))) +} + +// KeysAdd is gaiacli keys add +func (f *Fixtures) KeysAdd(name string, flags ...string) { + cmd := fmt.Sprintf("%s keys add --home=%s %s", f.GaiacliBinary, f.GaiacliHome, name) + executeWriteCheckErr(f.T, addFlags(cmd, flags), client.DefaultKeyPass) +} + +// KeysAddRecover prepares gaiacli keys add --recover +func (f *Fixtures) KeysAddRecover(name, mnemonic string, flags ...string) (exitSuccess bool, stdout, stderr string) { + cmd := fmt.Sprintf("%s keys add --home=%s --recover %s", f.GaiacliBinary, f.GaiacliHome, name) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.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("%s keys add --home=%s --recover %s --account %d --index %d", f.GaiacliBinary, f.GaiacliHome, name, account, index) + executeWriteCheckErr(f.T, addFlags(cmd, flags), client.DefaultKeyPass, mnemonic) +} + +// KeysShow is gaiacli keys show +func (f *Fixtures) KeysShow(name string, flags ...string) keys.KeyOutput { + cmd := fmt.Sprintf("%s keys show --home=%s %s", f.GaiacliBinary, f.GaiacliHome, name) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var ko keys.KeyOutput + err := clientkeys.UnmarshalJSON([]byte(out), &ko) + require.NoError(f.T, err) + return ko +} + +// KeyAddress returns the SDK account address from the key +func (f *Fixtures) KeyAddress(name string) sdk.AccAddress { + ko := f.KeysShow(name) + accAddr, err := sdk.AccAddressFromBech32(ko.Address) + require.NoError(f.T, err) + return accAddr +} + +//___________________________________________________________________________________ +// gaiacli config + +// CLIConfig is gaiacli config +func (f *Fixtures) CLIConfig(key, value string, flags ...string) { + cmd := fmt.Sprintf("%s config --home=%s %s %s", f.GaiacliBinary, f.GaiacliHome, key, value) + executeWriteCheckErr(f.T, addFlags(cmd, flags)) +} + +//___________________________________________________________________________________ +// gaiacli tx send/sign/broadcast + +// TxSend is gaiacli tx send +func (f *Fixtures) TxSend(from string, to sdk.AccAddress, amount sdk.Coin, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx send %s %s %s %v", f.GaiacliBinary, from, to, amount, f.Flags()) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) +} + +func (f *Fixtures) txSendWithConfirm( + from string, to sdk.AccAddress, amount sdk.Coin, confirm string, flags ...string, +) (bool, string, string) { + + cmd := fmt.Sprintf("%s tx send %s %s %s %v", f.GaiacliBinary, from, to, amount, f.Flags()) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), confirm, client.DefaultKeyPass) +} + +// TxSign is gaiacli tx sign +func (f *Fixtures) TxSign(signer, fileName string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx sign %v --from=%s %v", f.GaiacliBinary, f.Flags(), signer, fileName) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) +} + +// TxBroadcast is gaiacli tx broadcast +func (f *Fixtures) TxBroadcast(fileName string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx broadcast %v %v", f.GaiacliBinary, f.Flags(), fileName) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) +} + +// TxEncode is gaiacli tx encode +func (f *Fixtures) TxEncode(fileName string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx encode %v %v", f.GaiacliBinary, f.Flags(), fileName) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) +} + +// TxMultisign is gaiacli tx multisign +func (f *Fixtures) TxMultisign(fileName, name string, signaturesFiles []string, + flags ...string) (bool, string, string) { + + cmd := fmt.Sprintf("%s tx multisign %v %s %s %s", f.GaiacliBinary, f.Flags(), + fileName, name, strings.Join(signaturesFiles, " "), + ) + return executeWriteRetStdStreams(f.T, cmd) +} + +//___________________________________________________________________________________ +// gaiacli tx staking + +// TxStakingCreateValidator is gaiacli tx staking create-validator +func (f *Fixtures) TxStakingCreateValidator(from, consPubKey string, amount sdk.Coin, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx staking create-validator %v --from=%s --pubkey=%s", f.GaiacliBinary, f.Flags(), from, consPubKey) + 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(" --min-self-delegation=%v", "1") + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) +} + +// TxStakingUnbond is gaiacli tx staking unbond +func (f *Fixtures) TxStakingUnbond(from, shares string, validator sdk.ValAddress, flags ...string) bool { + cmd := fmt.Sprintf("%s tx staking unbond %s %v --from=%s %v", f.GaiacliBinary, validator, shares, from, f.Flags()) + return executeWrite(f.T, addFlags(cmd, flags), client.DefaultKeyPass) +} + +//___________________________________________________________________________________ +// gaiacli tx gov + +// TxGovSubmitProposal is gaiacli tx gov submit-proposal +func (f *Fixtures) TxGovSubmitProposal(from, typ, title, description string, deposit sdk.Coin, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx gov submit-proposal %v --from=%s --type=%s", f.GaiacliBinary, f.Flags(), from, typ) + cmd += fmt.Sprintf(" --title=%s --description=%s --deposit=%s", title, description, deposit) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) +} + +// TxGovDeposit is gaiacli tx gov deposit +func (f *Fixtures) TxGovDeposit(proposalID int, from string, amount sdk.Coin, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx gov deposit %d %s --from=%s %v", f.GaiacliBinary, proposalID, amount, from, f.Flags()) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) +} + +// TxGovVote is gaiacli tx gov vote +func (f *Fixtures) TxGovVote(proposalID int, option gov.VoteOption, from string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx gov vote %d %s --from=%s %v", f.GaiacliBinary, proposalID, option, from, f.Flags()) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) +} + +// TxGovSubmitParamChangeProposal executes a CLI parameter change proposal +// submission. +func (f *Fixtures) TxGovSubmitParamChangeProposal( + from, proposalPath string, deposit sdk.Coin, flags ...string, +) (bool, string, string) { + + cmd := fmt.Sprintf( + "%s tx gov submit-proposal param-change %s --from=%s %v", + f.GaiacliBinary, proposalPath, from, f.Flags(), + ) + + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) +} + +//___________________________________________________________________________________ +// gaiacli query account + +// QueryAccount is gaiacli query account +func (f *Fixtures) QueryAccount(address sdk.AccAddress, flags ...string) auth.BaseAccount { + cmd := fmt.Sprintf("%s query account %s %v", f.GaiacliBinary, address, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var initRes map[string]json.RawMessage + err := json.Unmarshal([]byte(out), &initRes) + require.NoError(f.T, err, "out %v, err %v", out, err) + value := initRes["value"] + var acc auth.BaseAccount + cdc := codec.New() + codec.RegisterCrypto(cdc) + err = cdc.UnmarshalJSON(value, &acc) + require.NoError(f.T, err, "value %v, err %v", string(value), err) + return acc +} + +//___________________________________________________________________________________ +// gaiacli query txs + +// QueryTxs is gaiacli query txs +func (f *Fixtures) QueryTxs(page, limit int, tags ...string) *sdk.SearchTxsResult { + cmd := fmt.Sprintf("%s query txs --page=%d --limit=%d --tags='%s' %v", f.GaiacliBinary, page, limit, queryTags(tags), f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var result sdk.SearchTxsResult + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &result) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return &result +} + +// QueryTxsInvalid query txs with wrong parameters and compare expected error +func (f *Fixtures) QueryTxsInvalid(expectedErr error, page, limit int, tags ...string) { + cmd := fmt.Sprintf("%s query txs --page=%d --limit=%d --tags='%s' %v", f.GaiacliBinary, page, limit, queryTags(tags), f.Flags()) + _, err := tests.ExecuteT(f.T, cmd, "") + require.EqualError(f.T, expectedErr, err) +} + +//___________________________________________________________________________________ +// gaiacli query staking + +// QueryStakingValidator is gaiacli query staking validator +func (f *Fixtures) QueryStakingValidator(valAddr sdk.ValAddress, flags ...string) staking.Validator { + cmd := fmt.Sprintf("%s query staking validator %s %v", f.GaiacliBinary, valAddr, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var validator staking.Validator + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &validator) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return validator +} + +// QueryStakingUnbondingDelegationsFrom is gaiacli query staking unbonding-delegations-from +func (f *Fixtures) QueryStakingUnbondingDelegationsFrom(valAddr sdk.ValAddress, flags ...string) []staking.UnbondingDelegation { + cmd := fmt.Sprintf("%s query staking unbonding-delegations-from %s %v", f.GaiacliBinary, valAddr, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var ubds []staking.UnbondingDelegation + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &ubds) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return ubds +} + +// QueryStakingDelegationsTo is gaiacli query staking delegations-to +func (f *Fixtures) QueryStakingDelegationsTo(valAddr sdk.ValAddress, flags ...string) []staking.Delegation { + cmd := fmt.Sprintf("%s query staking delegations-to %s %v", f.GaiacliBinary, valAddr, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var delegations []staking.Delegation + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &delegations) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return delegations +} + +// QueryStakingPool is gaiacli query staking pool +func (f *Fixtures) QueryStakingPool(flags ...string) staking.Pool { + cmd := fmt.Sprintf("%s query staking pool %v", f.GaiacliBinary, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var pool staking.Pool + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &pool) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return pool +} + +// QueryStakingParameters is gaiacli query staking parameters +func (f *Fixtures) QueryStakingParameters(flags ...string) staking.Params { + cmd := fmt.Sprintf("%s query staking params %v", f.GaiacliBinary, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var params staking.Params + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), ¶ms) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return params +} + +//___________________________________________________________________________________ +// gaiacli query gov + +// QueryGovParamDeposit is gaiacli query gov param deposit +func (f *Fixtures) QueryGovParamDeposit() gov.DepositParams { + cmd := fmt.Sprintf("%s query gov param deposit %s", f.GaiacliBinary, f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var depositParam gov.DepositParams + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &depositParam) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return depositParam +} + +// QueryGovParamVoting is gaiacli query gov param voting +func (f *Fixtures) QueryGovParamVoting() gov.VotingParams { + cmd := fmt.Sprintf("%s query gov param voting %s", f.GaiacliBinary, f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var votingParam gov.VotingParams + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &votingParam) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return votingParam +} + +// QueryGovParamTallying is gaiacli query gov param tallying +func (f *Fixtures) QueryGovParamTallying() gov.TallyParams { + cmd := fmt.Sprintf("%s query gov param tallying %s", f.GaiacliBinary, f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var tallyingParam gov.TallyParams + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &tallyingParam) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return tallyingParam +} + +// QueryGovProposals is gaiacli query gov proposals +func (f *Fixtures) QueryGovProposals(flags ...string) gov.Proposals { + cmd := fmt.Sprintf("%s query gov proposals %v", f.GaiacliBinary, f.Flags()) + stdout, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + if strings.Contains(stderr, "No matching proposals found") { + return gov.Proposals{} + } + require.Empty(f.T, stderr) + var out gov.Proposals + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(stdout), &out) + require.NoError(f.T, err) + return out +} + +// QueryGovProposal is gaiacli query gov proposal +func (f *Fixtures) QueryGovProposal(proposalID int, flags ...string) gov.Proposal { + cmd := fmt.Sprintf("%s query gov proposal %d %v", f.GaiacliBinary, proposalID, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var proposal gov.Proposal + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &proposal) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return proposal +} + +// QueryGovVote is gaiacli query gov vote +func (f *Fixtures) QueryGovVote(proposalID int, voter sdk.AccAddress, flags ...string) gov.Vote { + cmd := fmt.Sprintf("%s query gov vote %d %s %v", f.GaiacliBinary, proposalID, voter, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var vote gov.Vote + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &vote) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return vote +} + +// QueryGovVotes is gaiacli query gov votes +func (f *Fixtures) QueryGovVotes(proposalID int, flags ...string) []gov.Vote { + cmd := fmt.Sprintf("%s query gov votes %d %v", f.GaiacliBinary, proposalID, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var votes []gov.Vote + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &votes) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return votes +} + +// QueryGovDeposit is gaiacli query gov deposit +func (f *Fixtures) QueryGovDeposit(proposalID int, depositor sdk.AccAddress, flags ...string) gov.Deposit { + cmd := fmt.Sprintf("%s query gov deposit %d %s %v", f.GaiacliBinary, proposalID, depositor, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var deposit gov.Deposit + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &deposit) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return deposit +} + +// QueryGovDeposits is gaiacli query gov deposits +func (f *Fixtures) QueryGovDeposits(propsalID int, flags ...string) []gov.Deposit { + cmd := fmt.Sprintf("%s query gov deposits %d %v", f.GaiacliBinary, propsalID, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var deposits []gov.Deposit + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &deposits) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return deposits +} + +//___________________________________________________________________________________ +// query slashing + +// QuerySigningInfo returns the signing info for a validator +func (f *Fixtures) QuerySigningInfo(val string) slashing.ValidatorSigningInfo { + cmd := fmt.Sprintf("%s query slashing signing-info %s %s", f.GaiacliBinary, 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 +func (f *Fixtures) QuerySlashingParams() slashing.Params { + cmd := fmt.Sprintf("%s query slashing params %s", f.GaiacliBinary, f.Flags()) + res, errStr := tests.ExecuteT(f.T, cmd, "") + require.Empty(f.T, errStr) + cdc := app.MakeCodec() + var params slashing.Params + err := cdc.UnmarshalJSON([]byte(res), ¶ms) + require.NoError(f.T, err) + return params +} + +//___________________________________________________________________________________ +// query distribution + +// QuerySigningInfo returns the signing info for a validator +func (f *Fixtures) QueryRewards(delAddr sdk.AccAddress, flags ...string) distribution.QueryDelegatorTotalRewardsResponse { + cmd := fmt.Sprintf("%s query distr rewards %s %s", f.GaiacliBinary, delAddr, f.Flags()) + res, errStr := tests.ExecuteT(f.T, cmd, "") + require.Empty(f.T, errStr) + cdc := app.MakeCodec() + var rewards distribution.QueryDelegatorTotalRewardsResponse + err := cdc.UnmarshalJSON([]byte(res), &rewards) + require.NoError(f.T, err) + return rewards +} + +//___________________________________________________________________________________ +// executors + +func executeWriteCheckErr(t *testing.T, cmdStr string, writes ...string) { + require.True(t, executeWrite(t, cmdStr, writes...)) +} + +func executeWrite(t *testing.T, cmdStr string, writes ...string) (exitSuccess bool) { + exitSuccess, _, _ = executeWriteRetStdStreams(t, cmdStr, writes...) + return +} + +func executeWriteRetStdStreams(t *testing.T, cmdStr string, writes ...string) (bool, string, string) { + proc := tests.GoExecuteT(t, cmdStr) + + // Enables use of interactive commands + for _, write := range writes { + _, err := proc.StdinPipe.Write([]byte(write + "\n")) + require.NoError(t, err) + } + + // Read both stdout and stderr from the process + stdout, stderr, err := proc.ReadAll() + if err != nil { + fmt.Println("Err on proc.ReadAll()", err, cmdStr) + } + + // Log output. + if len(stdout) > 0 { + t.Log("Stdout:", cmn.Green(string(stdout))) + } + if len(stderr) > 0 { + t.Log("Stderr:", cmn.Red(string(stderr))) + } + + // Wait for process to exit + proc.Wait() + + // Return succes, stdout, stderr + return proc.ExitState.Success(), string(stdout), string(stderr) +} + +//___________________________________________________________________________________ +// utils + +func addFlags(cmd string, flags []string) string { + for _, f := range flags { + cmd += " " + f + } + return strings.TrimSpace(cmd) +} + +func queryTags(tags []string) (out string) { + for _, tag := range tags { + out += tag + "&" + } + return strings.TrimSuffix(out, "&") +} + +// 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_") + require.Nil(t, err) + _, err = fp.WriteString(s) + require.Nil(t, err) + return fp +} + +func marshalStdTx(t *testing.T, stdTx auth.StdTx) []byte { + cdc := app.MakeCodec() + bz, err := cdc.MarshalBinaryBare(stdTx) + require.NoError(t, err) + return bz +} + +func unmarshalStdTx(t *testing.T, s string) (stdTx auth.StdTx) { + cdc := app.MakeCodec() + require.Nil(t, cdc.UnmarshalJSON([]byte(s), &stdTx)) + return +} diff --git a/cmd/gaiacli/main.go b/cmd/gaiacli/main.go new file mode 100644 index 00000000..866b4034 --- /dev/null +++ b/cmd/gaiacli/main.go @@ -0,0 +1,212 @@ +package main + +import ( + "fmt" + "net/http" + "os" + "path" + + "github.com/cosmos/gaia/app" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/lcd" + "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/client/tx" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + at "github.com/cosmos/cosmos-sdk/x/auth" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" + bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" + bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" + crisisclient "github.com/cosmos/cosmos-sdk/x/crisis/client" + distcmd "github.com/cosmos/cosmos-sdk/x/distribution" + distClient "github.com/cosmos/cosmos-sdk/x/distribution/client" + dist "github.com/cosmos/cosmos-sdk/x/distribution/client/rest" + gv "github.com/cosmos/cosmos-sdk/x/gov" + govClient "github.com/cosmos/cosmos-sdk/x/gov/client" + gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest" + "github.com/cosmos/cosmos-sdk/x/mint" + mintclient "github.com/cosmos/cosmos-sdk/x/mint/client" + mintrest "github.com/cosmos/cosmos-sdk/x/mint/client/rest" + paramcli "github.com/cosmos/cosmos-sdk/x/params/client/cli" + paramsrest "github.com/cosmos/cosmos-sdk/x/params/client/rest" + sl "github.com/cosmos/cosmos-sdk/x/slashing" + slashingclient "github.com/cosmos/cosmos-sdk/x/slashing/client" + slashing "github.com/cosmos/cosmos-sdk/x/slashing/client/rest" + st "github.com/cosmos/cosmos-sdk/x/staking" + stakingclient "github.com/cosmos/cosmos-sdk/x/staking/client" + staking "github.com/cosmos/cosmos-sdk/x/staking/client/rest" + + "github.com/rakyll/statik/fs" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + amino "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/libs/cli" + + _ "github.com/cosmos/cosmos-sdk/client/lcd/statik" +) + +func main() { + // Configure cobra to sort commands + cobra.EnableCommandSorting = false + + // Instantiate the codec for the command line application + cdc := app.MakeCodec() + + // Read in the configuration file for the sdk + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + config.Seal() + + // TODO: setup keybase, viper object, etc. to be passed into + // the below functions and eliminate global vars, like we do + // with the cdc + + // Module clients hold cli commnads (tx,query) and lcd routes + // TODO: Make the lcd command take a list of ModuleClient + mc := []sdk.ModuleClient{ + govClient.NewModuleClient(gv.StoreKey, cdc, paramcli.GetCmdSubmitProposal(cdc)), + distClient.NewModuleClient(distcmd.StoreKey, cdc), + stakingclient.NewModuleClient(st.StoreKey, cdc), + mintclient.NewModuleClient(mint.StoreKey, cdc), + slashingclient.NewModuleClient(sl.StoreKey, cdc), + crisisclient.NewModuleClient(sl.StoreKey, cdc), + } + + rootCmd := &cobra.Command{ + Use: "gaiacli", + Short: "Command line interface for interacting with gaiad", + } + + // Add --chain-id to persistent flags and mark it required + rootCmd.PersistentFlags().String(client.FlagChainID, "", "Chain ID of tendermint node") + rootCmd.PersistentPreRunE = func(_ *cobra.Command, _ []string) error { + return initConfig(rootCmd) + } + + // Construct Root Command + rootCmd.AddCommand( + rpc.StatusCommand(), + client.ConfigCmd(app.DefaultCLIHome), + queryCmd(cdc, mc), + txCmd(cdc, mc), + client.LineBreak, + lcd.ServeCommand(cdc, registerRoutes), + client.LineBreak, + keys.Commands(), + client.LineBreak, + version.VersionCmd, + client.NewCompletionCmd(rootCmd, true), + ) + + // Add flags and prefix all env exposed with GA + executor := cli.PrepareMainCmd(rootCmd, "GA", app.DefaultCLIHome) + + err := executor.Execute() + if err != nil { + fmt.Printf("Failed executing CLI command: %s, exiting...\n", err) + os.Exit(1) + } +} + +func queryCmd(cdc *amino.Codec, mc []sdk.ModuleClient) *cobra.Command { + queryCmd := &cobra.Command{ + Use: "query", + Aliases: []string{"q"}, + Short: "Querying subcommands", + } + + queryCmd.AddCommand( + rpc.ValidatorCommand(cdc), + rpc.BlockCommand(), + tx.SearchTxCmd(cdc), + tx.QueryTxCmd(cdc), + client.LineBreak, + authcmd.GetAccountCmd(at.StoreKey, cdc), + ) + + for _, m := range mc { + mQueryCmd := m.GetQueryCmd() + if mQueryCmd != nil { + queryCmd.AddCommand(mQueryCmd) + } + } + + return queryCmd +} + +func txCmd(cdc *amino.Codec, mc []sdk.ModuleClient) *cobra.Command { + txCmd := &cobra.Command{ + Use: "tx", + Short: "Transactions subcommands", + } + + txCmd.AddCommand( + bankcmd.SendTxCmd(cdc), + client.LineBreak, + authcmd.GetSignCommand(cdc), + authcmd.GetMultiSignCommand(cdc), + tx.GetBroadcastCommand(cdc), + tx.GetEncodeCommand(cdc), + client.LineBreak, + ) + + for _, m := range mc { + txCmd.AddCommand(m.GetTxCmd()) + } + + return txCmd +} + +// registerRoutes registers the routes from the different modules for the LCD. +// NOTE: details on the routes added for each module are in the module documentation +// NOTE: If making updates here you also need to update the test helper in client/lcd/test_helper.go +func registerRoutes(rs *lcd.RestServer) { + registerSwaggerUI(rs) + rpc.RegisterRoutes(rs.CliCtx, rs.Mux) + tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) + auth.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, at.StoreKey) + 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) + slashing.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) + gov.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, paramsrest.ProposalRESTHandler(rs.CliCtx, rs.Cdc)) + mintrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) +} + +func registerSwaggerUI(rs *lcd.RestServer) { + statikFS, err := fs.New() + if err != nil { + panic(err) + } + staticServer := http.FileServer(statikFS) + rs.Mux.PathPrefix("/swagger-ui/").Handler(http.StripPrefix("/swagger-ui/", staticServer)) +} + +func initConfig(cmd *cobra.Command) error { + home, err := cmd.PersistentFlags().GetString(cli.HomeFlag) + if err != nil { + return err + } + + cfgFile := path.Join(home, "config", "config.toml") + if _, err := os.Stat(cfgFile); err == nil { + viper.SetConfigFile(cfgFile) + + if err := viper.ReadInConfig(); err != nil { + return err + } + } + if err := viper.BindPFlag(client.FlagChainID, cmd.PersistentFlags().Lookup(client.FlagChainID)); err != nil { + return err + } + if err := viper.BindPFlag(cli.EncodingFlag, cmd.PersistentFlags().Lookup(cli.EncodingFlag)); err != nil { + return err + } + return viper.BindPFlag(cli.OutputFlag, cmd.PersistentFlags().Lookup(cli.OutputFlag)) +} diff --git a/cmd/gaiad/main.go b/cmd/gaiad/main.go new file mode 100644 index 00000000..4985428b --- /dev/null +++ b/cmd/gaiad/main.go @@ -0,0 +1,94 @@ +package main + +import ( + "encoding/json" + "io" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/cli" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/gaia/app" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/genaccounts" + genaccscli "github.com/cosmos/cosmos-sdk/x/auth/genaccounts/client/cli" + genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" +) + +// gaiad custom flags +const flagInvCheckPeriod = "inv-check-period" + +var invCheckPeriod uint + +func main() { + cdc := app.MakeCodec() + + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + config.Seal() + + ctx := server.NewDefaultContext() + cobra.EnableCommandSorting = false + rootCmd := &cobra.Command{ + Use: "gaiad", + Short: "Gaia Daemon (server)", + PersistentPreRunE: server.PersistentPreRunEFn(ctx), + } + + rootCmd.AddCommand(genutilcli.InitCmd(ctx, cdc, app.ModuleBasics, app.DefaultNodeHome)) + rootCmd.AddCommand(genutilcli.CollectGenTxsCmd(ctx, cdc, genaccounts.AppModuleBasic{}, app.DefaultNodeHome)) + rootCmd.AddCommand(genutilcli.GenTxCmd(ctx, cdc, app.ModuleBasics, genaccounts.AppModuleBasic{}, app.DefaultNodeHome, app.DefaultCLIHome)) + rootCmd.AddCommand(genutilcli.ValidateGenesisCmd(ctx, cdc, app.ModuleBasics)) + rootCmd.AddCommand(genaccscli.AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome)) + rootCmd.AddCommand(client.NewCompletionCmd(rootCmd, true)) + rootCmd.AddCommand(testnetCmd(ctx, cdc, app.ModuleBasics, genaccounts.AppModuleBasic{})) + rootCmd.AddCommand(replayCmd()) + + server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators) + + // prepare and add flags + executor := cli.PrepareBaseCmd(rootCmd, "GA", app.DefaultNodeHome) + rootCmd.PersistentFlags().UintVar(&invCheckPeriod, flagInvCheckPeriod, + 0, "Assert registered invariants every N blocks") + err := executor.Execute() + if err != nil { + panic(err) + } +} + +func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application { + return app.NewGaiaApp( + logger, db, traceStore, true, invCheckPeriod, + baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))), + baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)), + baseapp.SetHaltHeight(uint64(viper.GetInt(server.FlagHaltHeight))), + ) +} + +func exportAppStateAndTMValidators( + logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailWhiteList []string, +) (json.RawMessage, []tmtypes.GenesisValidator, error) { + + if height != -1 { + gApp := app.NewGaiaApp(logger, db, traceStore, false, uint(1)) + err := gApp.LoadHeight(height) + if err != nil { + return nil, nil, err + } + return gApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) + } + gApp := app.NewGaiaApp(logger, db, traceStore, true, uint(1)) + return gApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) +} diff --git a/cmd/gaiad/replay.go b/cmd/gaiad/replay.go new file mode 100644 index 00000000..209ec29c --- /dev/null +++ b/cmd/gaiad/replay.go @@ -0,0 +1,191 @@ +package main + +import ( + "fmt" + "io" + "os" + "path/filepath" + "time" + + cpm "github.com/otiai10/copy" + "github.com/spf13/cobra" + + abci "github.com/tendermint/tendermint/abci/types" + bcm "github.com/tendermint/tendermint/blockchain" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/proxy" + tmsm "github.com/tendermint/tendermint/state" + tm "github.com/tendermint/tendermint/types" + + "github.com/cosmos/gaia/app" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func replayCmd() *cobra.Command { + return &cobra.Command{ + Use: "replay ", + Short: "Replay gaia transactions", + RunE: func(_ *cobra.Command, args []string) error { + return replayTxs(args[0]) + }, + Args: cobra.ExactArgs(1), + } +} + +func replayTxs(rootDir string) error { + + if false { + // Copy the rootDir to a new directory, to preserve the old one. + fmt.Fprintln(os.Stderr, "Copying rootdir over") + oldRootDir := rootDir + rootDir = oldRootDir + "_replay" + if cmn.FileExists(rootDir) { + cmn.Exit(fmt.Sprintf("temporary copy dir %v already exists", rootDir)) + } + if err := cpm.Copy(oldRootDir, rootDir); err != nil { + return err + } + } + + configDir := filepath.Join(rootDir, "config") + dataDir := filepath.Join(rootDir, "data") + ctx := server.NewDefaultContext() + + // App DB + // appDB := dbm.NewMemDB() + fmt.Fprintln(os.Stderr, "Opening app database") + appDB, err := sdk.NewLevelDB("application", dataDir) + if err != nil { + return err + } + + // TM DB + // tmDB := dbm.NewMemDB() + fmt.Fprintln(os.Stderr, "Opening tendermint state database") + tmDB, err := sdk.NewLevelDB("state", dataDir) + if err != nil { + return err + } + + // Blockchain DB + fmt.Fprintln(os.Stderr, "Opening blockstore database") + bcDB, err := sdk.NewLevelDB("blockstore", dataDir) + if err != nil { + return err + } + + // TraceStore + var traceStoreWriter io.Writer + var traceStoreDir = filepath.Join(dataDir, "trace.log") + traceStoreWriter, err = os.OpenFile( + traceStoreDir, + os.O_WRONLY|os.O_APPEND|os.O_CREATE, + 0666, + ) + if err != nil { + return err + } + + // Application + fmt.Fprintln(os.Stderr, "Creating application") + myapp := app.NewGaiaApp( + ctx.Logger, appDB, traceStoreWriter, true, uint(1), + baseapp.SetPruning(store.PruneEverything), // nothing + ) + + // Genesis + var genDocPath = filepath.Join(configDir, "genesis.json") + genDoc, err := tm.GenesisDocFromFile(genDocPath) + if err != nil { + return err + } + genState, err := tmsm.MakeGenesisState(genDoc) + if err != nil { + return err + } + // tmsm.SaveState(tmDB, genState) + + cc := proxy.NewLocalClientCreator(myapp) + proxyApp := proxy.NewAppConns(cc) + err = proxyApp.Start() + if err != nil { + return err + } + defer func() { + _ = proxyApp.Stop() + }() + + state := tmsm.LoadState(tmDB) + if state.LastBlockHeight == 0 { + // Send InitChain msg + fmt.Fprintln(os.Stderr, "Sending InitChain msg") + validators := tm.TM2PB.ValidatorUpdates(genState.Validators) + csParams := tm.TM2PB.ConsensusParams(genDoc.ConsensusParams) + req := abci.RequestInitChain{ + Time: genDoc.GenesisTime, + ChainId: genDoc.ChainID, + ConsensusParams: csParams, + Validators: validators, + AppStateBytes: genDoc.AppState, + } + res, err := proxyApp.Consensus().InitChainSync(req) + if err != nil { + return err + } + newValidatorz, err := tm.PB2TM.ValidatorUpdates(res.Validators) + if err != nil { + return err + } + newValidators := tm.NewValidatorSet(newValidatorz) + + // Take the genesis state. + state = genState + state.Validators = newValidators + state.NextValidators = newValidators + } + + // Create executor + fmt.Fprintln(os.Stderr, "Creating block executor") + blockExec := tmsm.NewBlockExecutor(tmDB, ctx.Logger, proxyApp.Consensus(), + tmsm.MockMempool{}, tmsm.MockEvidencePool{}) + + // Create block store + fmt.Fprintln(os.Stderr, "Creating block store") + blockStore := bcm.NewBlockStore(bcDB) + + tz := []time.Duration{0, 0, 0} + for i := int(state.LastBlockHeight) + 1; ; i++ { + fmt.Fprintln(os.Stderr, "Running block ", i) + t1 := time.Now() + + // Apply block + fmt.Printf("loading and applying block %d\n", i) + blockmeta := blockStore.LoadBlockMeta(int64(i)) + if blockmeta == nil { + fmt.Printf("Couldn't find block meta %d... done?\n", i) + return nil + } + block := blockStore.LoadBlock(int64(i)) + if block == nil { + return fmt.Errorf("couldn't find block %d", i) + } + + t2 := time.Now() + + state, err = blockExec.ApplyBlock(state, blockmeta.BlockID, block) + if err != nil { + return err + } + + t3 := time.Now() + tz[0] += t2.Sub(t1) + tz[1] += t3.Sub(t2) + + fmt.Fprintf(os.Stderr, "new app hash: %X\n", state.AppHash) + fmt.Fprintln(os.Stderr, tz) + } +} diff --git a/cmd/gaiad/testnet.go b/cmd/gaiad/testnet.go new file mode 100644 index 00000000..4c182fbb --- /dev/null +++ b/cmd/gaiad/testnet.go @@ -0,0 +1,376 @@ +package main + +// DONTCOVER + +import ( + "encoding/json" + "fmt" + "net" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + tmconfig "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" + srvconfig "github.com/cosmos/cosmos-sdk/server/config" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authtx "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" + "github.com/cosmos/cosmos-sdk/x/auth/genaccounts" + "github.com/cosmos/cosmos-sdk/x/genutil" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +var ( + flagNodeDirPrefix = "node-dir-prefix" + flagNumValidators = "v" + flagOutputDir = "output-dir" + flagNodeDaemonHome = "node-daemon-home" + flagNodeCLIHome = "node-cli-home" + flagStartingIPAddress = "starting-ip-address" +) + +// get cmd to initialize all files for tendermint testnet and application +func testnetCmd(ctx *server.Context, cdc *codec.Codec, + mbm sdk.ModuleBasicManager, genAccIterator genutil.GenesisAccountsIterator) *cobra.Command { + + cmd := &cobra.Command{ + Use: "testnet", + Short: "Initialize files for a Gaiad testnet", + Long: `testnet will create "v" number of directories and populate each with +necessary files (private validator, genesis, config, etc.). + +Note, strict routability for addresses is turned off in the config file. + +Example: + gaiad testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2 + `, + RunE: func(_ *cobra.Command, _ []string) error { + config := ctx.Config + + outputDir := viper.GetString(flagOutputDir) + chainID := viper.GetString(client.FlagChainID) + minGasPrices := viper.GetString(server.FlagMinGasPrices) + nodeDirPrefix := viper.GetString(flagNodeDirPrefix) + nodeDaemonHome := viper.GetString(flagNodeDaemonHome) + nodeCLIHome := viper.GetString(flagNodeCLIHome) + startingIPAddress := viper.GetString(flagStartingIPAddress) + numValidators := viper.GetInt(flagNumValidators) + + return InitTestnet(config, cdc, mbm, genAccIterator, outputDir, chainID, minGasPrices, + nodeDirPrefix, nodeDaemonHome, nodeCLIHome, startingIPAddress, numValidators) + }, + } + + cmd.Flags().Int(flagNumValidators, 4, + "Number of validators to initialize the testnet with") + cmd.Flags().StringP(flagOutputDir, "o", "./mytestnet", + "Directory to store initialization data for the testnet") + cmd.Flags().String(flagNodeDirPrefix, "node", + "Prefix the directory name for each node with (node results in node0, node1, ...)") + cmd.Flags().String(flagNodeDaemonHome, "gaiad", + "Home directory of the node's daemon configuration") + cmd.Flags().String(flagNodeCLIHome, "gaiacli", + "Home directory of the node's cli configuration") + cmd.Flags().String(flagStartingIPAddress, "192.168.0.1", + "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)") + cmd.Flags().String( + client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") + cmd.Flags().String( + server.FlagMinGasPrices, fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom), + "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)") + return cmd +} + +const nodeDirPerm = 0755 + +// Initialize the testnet +func InitTestnet(config *tmconfig.Config, cdc *codec.Codec, mbm sdk.ModuleBasicManager, + genAccIterator genutil.GenesisAccountsIterator, + outputDir, chainID, minGasPrices, nodeDirPrefix, nodeDaemonHome, + nodeCLIHome, startingIPAddress string, numValidators int) error { + + if chainID == "" { + chainID = "chain-" + cmn.RandStr(6) + } + + monikers := make([]string, numValidators) + nodeIDs := make([]string, numValidators) + valPubKeys := make([]crypto.PubKey, numValidators) + + gaiaConfig := srvconfig.DefaultConfig() + gaiaConfig.MinGasPrices = minGasPrices + + var ( + accs []genaccounts.GenesisAccount + genFiles []string + ) + + // generate private keys, node IDs, and initial transactions + for i := 0; i < numValidators; i++ { + nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i) + nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome) + clientDir := filepath.Join(outputDir, nodeDirName, nodeCLIHome) + gentxsDir := filepath.Join(outputDir, "gentxs") + + config.SetRoot(nodeDir) + + err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm) + if err != nil { + _ = os.RemoveAll(outputDir) + return err + } + + err = os.MkdirAll(clientDir, nodeDirPerm) + if err != nil { + _ = os.RemoveAll(outputDir) + return err + } + + monikers = append(monikers, nodeDirName) + config.Moniker = nodeDirName + + ip, err := getIP(i, startingIPAddress) + if err != nil { + _ = os.RemoveAll(outputDir) + return err + } + + nodeIDs[i], valPubKeys[i], err = genutil.InitializeNodeValidatorFiles(config) + if err != nil { + _ = os.RemoveAll(outputDir) + return err + } + + memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip) + genFiles = append(genFiles, config.GenesisFile()) + + buf := client.BufferStdin() + prompt := fmt.Sprintf( + "Password for account '%s' (default %s):", nodeDirName, client.DefaultKeyPass, + ) + + keyPass, err := client.GetPassword(prompt, buf) + if err != nil && keyPass != "" { + // An error was returned that either failed to read the password from + // STDIN or the given password is not empty but failed to meet minimum + // length requirements. + return err + } + + if keyPass == "" { + keyPass = client.DefaultKeyPass + } + + addr, secret, err := server.GenerateSaveCoinKey(clientDir, nodeDirName, keyPass, true) + if err != nil { + _ = os.RemoveAll(outputDir) + return err + } + + info := map[string]string{"secret": secret} + + cliPrint, err := json.Marshal(info) + if err != nil { + return err + } + + // save private key seed words + err = writeFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, cliPrint) + if err != nil { + return err + } + + accTokens := sdk.TokensFromTendermintPower(1000) + accStakingTokens := sdk.TokensFromTendermintPower(500) + accs = append(accs, genaccounts.GenesisAccount{ + Address: addr, + Coins: sdk.Coins{ + sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), accTokens), + sdk.NewCoin(sdk.DefaultBondDenom, accStakingTokens), + }, + }) + + valTokens := sdk.TokensFromTendermintPower(100) + msg := staking.NewMsgCreateValidator( + sdk.ValAddress(addr), + valPubKeys[i], + sdk.NewCoin(sdk.DefaultBondDenom, valTokens), + staking.NewDescription(nodeDirName, "", "", ""), + 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) + txBldr := authtx.NewTxBuilderFromCLI().WithChainID(chainID).WithMemo(memo).WithKeybase(kb) + + signedTx, err := txBldr.SignStdTx(nodeDirName, client.DefaultKeyPass, tx, false) + if err != nil { + _ = os.RemoveAll(outputDir) + return err + } + + txBytes, err := cdc.MarshalJSON(signedTx) + if err != nil { + _ = os.RemoveAll(outputDir) + return err + } + + // gather gentxs folder + err = writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBytes) + if err != nil { + _ = os.RemoveAll(outputDir) + return err + } + + // TODO: Rename config file to server.toml as it's not particular to Gaia + // (REF: https://github.com/cosmos/cosmos-sdk/issues/4125). + gaiaConfigFilePath := filepath.Join(nodeDir, "config/gaiad.toml") + srvconfig.WriteConfigFile(gaiaConfigFilePath, gaiaConfig) + } + + if err := initGenFiles(cdc, mbm, chainID, accs, genFiles, numValidators); err != nil { + return err + } + + err := collectGenFiles( + cdc, config, chainID, monikers, nodeIDs, valPubKeys, numValidators, + outputDir, nodeDirPrefix, nodeDaemonHome, genAccIterator, + ) + if err != nil { + return err + } + + fmt.Printf("Successfully initialized %d node directories\n", numValidators) + return nil +} + +func initGenFiles(cdc *codec.Codec, mbm sdk.ModuleBasicManager, chainID string, + accs []genaccounts.GenesisAccount, genFiles []string, numValidators int) error { + + appGenState := mbm.DefaultGenesis() + + // set the accounts in the genesis state + appGenState = genaccounts.SetGenesisStateInAppState(cdc, appGenState, + genaccounts.NewGenesisState(accs)) + + appGenStateJSON, err := codec.MarshalJSONIndent(cdc, appGenState) + if err != nil { + return err + } + + genDoc := types.GenesisDoc{ + ChainID: chainID, + AppState: appGenStateJSON, + Validators: nil, + } + + // generate empty genesis files for each validator and save + for i := 0; i < numValidators; i++ { + if err := genDoc.SaveAs(genFiles[i]); err != nil { + return err + } + } + return nil +} + +func collectGenFiles( + cdc *codec.Codec, config *tmconfig.Config, chainID string, + monikers, nodeIDs []string, valPubKeys []crypto.PubKey, + numValidators int, outputDir, nodeDirPrefix, nodeDaemonHome string, + genAccIterator genutil.GenesisAccountsIterator) error { + + var appState json.RawMessage + genTime := tmtime.Now() + + for i := 0; i < numValidators; i++ { + nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i) + nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome) + gentxsDir := filepath.Join(outputDir, "gentxs") + moniker := monikers[i] + config.Moniker = nodeDirName + + config.SetRoot(nodeDir) + + nodeID, valPubKey := nodeIDs[i], valPubKeys[i] + initCfg := genutil.NewInitConfig(chainID, gentxsDir, moniker, nodeID, valPubKey) + + genDoc, err := types.GenesisDocFromFile(config.GenesisFile()) + if err != nil { + return err + } + + nodeAppState, err := genutil.GenAppStateFromConfig(cdc, config, initCfg, *genDoc, genAccIterator) + if err != nil { + return err + } + + if appState == nil { + // set the canonical application state (they should not differ) + appState = nodeAppState + } + + genFile := config.GenesisFile() + + // overwrite each validator's genesis file to have a canonical genesis time + err = genutil.ExportGenesisFileWithTime(genFile, chainID, nil, appState, genTime) + if err != nil { + return err + } + } + + return nil +} + +func getIP(i int, startingIPAddr string) (ip string, err error) { + if len(startingIPAddr) == 0 { + ip, err = server.ExternalIP() + if err != nil { + return "", err + } + return ip, nil + } + return calculateIP(startingIPAddr, i) +} + +func calculateIP(ip string, i int) (string, error) { + ipv4 := net.ParseIP(ip).To4() + if ipv4 == nil { + return "", fmt.Errorf("%v: non ipv4 address", ip) + } + + for j := 0; j < i; j++ { + ipv4[3]++ + } + + return ipv4.String(), nil +} + +func writeFile(name string, dir string, contents []byte) error { + writePath := filepath.Join(dir) + file := filepath.Join(writePath, name) + + err := cmn.EnsureDir(writePath, 0700) + if err != nil { + return err + } + + err = cmn.WriteFile(file, contents, 0600) + if err != nil { + return err + } + + return nil +} diff --git a/cmd/gaiadebug/README.md b/cmd/gaiadebug/README.md new file mode 100644 index 00000000..c2f0b8bc --- /dev/null +++ b/cmd/gaiadebug/README.md @@ -0,0 +1,35 @@ +# Gaiadebug + +Simple tool for simple debugging. + +We try to accept both hex and base64 formats and provide a useful response. + +Note we often encode bytes as hex in the logs, but as base64 in the JSON. + +## Pubkeys + +The following give the same result: + +``` +gaiadebug pubkey TZTQnfqOsi89SeoXVnIw+tnFJnr4X8qVC0U8AsEmFk4= +gaiadebug pubkey 4D94D09DFA8EB22F3D49EA17567230FAD9C5267AF85FCA950B453C02C126164E +``` + +## Txs + +Pass in a hex/base64 tx and get back the full JSON + +``` +gaiadebug tx +``` + +## Hack + +This is a command with boilerplate for using Go as a scripting language to hack +on an existing Gaia state. + +Currently we have an example for the state of gaia-6001 after it +[crashed](https://github.com/cosmos/cosmos-sdk/blob/master/cmd/gaia/testnets/STATUS.md#june-13-2018-230-est---published-postmortem-of-gaia-6001-failure). +If you run `gaiadebug hack $HOME/.gaiad` on that +state, it will do a binary search on the state history to find when the state +invariant was violated. diff --git a/cmd/gaiadebug/hack.go b/cmd/gaiadebug/hack.go new file mode 100644 index 00000000..89afc4de --- /dev/null +++ b/cmd/gaiadebug/hack.go @@ -0,0 +1,103 @@ +package main + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "os" + "path" + + "github.com/cosmos/cosmos-sdk/store" + + "github.com/cosmos/cosmos-sdk/baseapp" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/ed25519" + + "github.com/tendermint/tendermint/libs/log" + + sdk "github.com/cosmos/cosmos-sdk/types" + + gaia "github.com/cosmos/gaia/app" +) + +func runHackCmd(cmd *cobra.Command, args []string) error { + + if len(args) != 1 { + return fmt.Errorf("Expected 1 arg") + } + + // ".gaiad" + dataDir := args[0] + dataDir = path.Join(dataDir, "data") + + // load the app + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + db, err := sdk.NewLevelDB("gaia", dataDir) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + app, keyMain, keyStaking, stakingKeeper := gaia.NewGaiaAppUNSAFE( + logger, db, nil, false, 0, baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning")))) + + // print some info + id := app.LastCommitID() + lastBlockHeight := app.LastBlockHeight() + fmt.Println("ID", id) + fmt.Println("LastBlockHeight", lastBlockHeight) + + //---------------------------------------------------- + // XXX: start hacking! + //---------------------------------------------------- + // eg. gaia-6001 testnet bug + // We paniced when iterating through the "bypower" keys. + // The following powerKey was there, but the corresponding "trouble" validator did not exist. + // So here we do a binary search on the past states to find when the powerKey first showed up ... + + // operator of the validator the bonds, gets jailed, later unbonds, and then later is still found in the bypower store + trouble := hexToBytes("D3DC0FF59F7C3B548B7AFA365561B87FD0208AF8") + // this is his "bypower" key + powerKey := hexToBytes("05303030303030303030303033FFFFFFFFFFFF4C0C0000FFFED3DC0FF59F7C3B548B7AFA365561B87FD0208AF8") + + topHeight := lastBlockHeight + bottomHeight := int64(0) + checkHeight := topHeight + for { + // load the given version of the state + err = app.LoadVersion(checkHeight, keyMain) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + ctx := app.NewContext(true, abci.Header{}) + + // check for the powerkey and the validator from the store + store := ctx.KVStore(keyStaking) + res := store.Get(powerKey) + val, _ := stakingKeeper.GetValidator(ctx, trouble) + fmt.Println("checking height", checkHeight, res, val) + if res == nil { + bottomHeight = checkHeight + } else { + topHeight = checkHeight + } + checkHeight = (topHeight + bottomHeight) / 2 + } +} + +func base64ToPub(b64 string) ed25519.PubKeyEd25519 { + data, _ := base64.StdEncoding.DecodeString(b64) + var pubKey ed25519.PubKeyEd25519 + copy(pubKey[:], data) + return pubKey + +} + +func hexToBytes(h string) []byte { + trouble, _ := hex.DecodeString(h) + return trouble + +} diff --git a/cmd/gaiadebug/main.go b/cmd/gaiadebug/main.go new file mode 100644 index 00000000..44fc8734 --- /dev/null +++ b/cmd/gaiadebug/main.go @@ -0,0 +1,257 @@ +package main + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "strconv" + "strings" + + "github.com/spf13/cobra" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + + gaia "github.com/cosmos/gaia/app" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +func init() { + + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + config.Seal() + + rootCmd.AddCommand(txCmd) + rootCmd.AddCommand(pubkeyCmd) + rootCmd.AddCommand(addrCmd) + rootCmd.AddCommand(hackCmd) + rootCmd.AddCommand(rawBytesCmd) +} + +var rootCmd = &cobra.Command{ + Use: "gaiadebug", + Short: "Gaia debug tool", + SilenceUsage: true, +} + +var txCmd = &cobra.Command{ + Use: "tx", + Short: "Decode a gaia tx from hex or base64", + RunE: runTxCmd, +} + +var pubkeyCmd = &cobra.Command{ + Use: "pubkey", + Short: "Decode a pubkey from hex, base64, or bech32", + RunE: runPubKeyCmd, +} + +var addrCmd = &cobra.Command{ + Use: "addr", + Short: "Convert an address between hex and bech32", + RunE: runAddrCmd, +} + +var hackCmd = &cobra.Command{ + Use: "hack", + Short: "Boilerplate to Hack on an existing state by scripting some Go...", + RunE: runHackCmd, +} + +var rawBytesCmd = &cobra.Command{ + Use: "raw-bytes", + Short: "Convert raw bytes output (eg. [10 21 13 255]) to hex", + RunE: runRawBytesCmd, +} + +func runRawBytesCmd(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("Expected single arg") + } + stringBytes := args[0] + stringBytes = strings.Trim(stringBytes, "[") + stringBytes = strings.Trim(stringBytes, "]") + spl := strings.Split(stringBytes, " ") + + byteArray := []byte{} + for _, s := range spl { + b, err := strconv.Atoi(s) + if err != nil { + return err + } + byteArray = append(byteArray, byte(b)) + } + fmt.Printf("%X\n", byteArray) + return nil +} + +func runPubKeyCmd(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("Expected single arg") + } + + pubkeyString := args[0] + var pubKeyI crypto.PubKey + + // try hex, then base64, then bech32 + pubkeyBytes, err := hex.DecodeString(pubkeyString) + if err != nil { + var err2 error + pubkeyBytes, err2 = base64.StdEncoding.DecodeString(pubkeyString) + if err2 != nil { + var err3 error + pubKeyI, err3 = sdk.GetAccPubKeyBech32(pubkeyString) + if err3 != nil { + var err4 error + pubKeyI, err4 = sdk.GetValPubKeyBech32(pubkeyString) + + if err4 != nil { + var err5 error + pubKeyI, err5 = sdk.GetConsPubKeyBech32(pubkeyString) + if err5 != nil { + return fmt.Errorf(`Expected hex, base64, or bech32. Got errors: + hex: %v, + base64: %v + bech32 Acc: %v + bech32 Val: %v + bech32 Cons: %v`, + err, err2, err3, err4, err5) + } + + } + } + + } + } + + var pubKey ed25519.PubKeyEd25519 + if pubKeyI == nil { + copy(pubKey[:], pubkeyBytes) + } else { + pubKey = pubKeyI.(ed25519.PubKeyEd25519) + pubkeyBytes = pubKey[:] + } + + cdc := gaia.MakeCodec() + pubKeyJSONBytes, err := cdc.MarshalJSON(pubKey) + if err != nil { + return err + } + accPub, err := sdk.Bech32ifyAccPub(pubKey) + if err != nil { + return err + } + valPub, err := sdk.Bech32ifyValPub(pubKey) + if err != nil { + return err + } + + consenusPub, err := sdk.Bech32ifyConsPub(pubKey) + if err != nil { + return err + } + fmt.Println("Address:", pubKey.Address()) + fmt.Printf("Hex: %X\n", pubkeyBytes) + fmt.Println("JSON (base64):", string(pubKeyJSONBytes)) + fmt.Println("Bech32 Acc:", accPub) + fmt.Println("Bech32 Validator Operator:", valPub) + fmt.Println("Bech32 Validator Consensus:", consenusPub) + return nil +} + +func runAddrCmd(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("Expected single arg") + } + + addrString := args[0] + var addr []byte + + // try hex, then bech32 + var err error + addr, err = hex.DecodeString(addrString) + if err != nil { + var err2 error + addr, err2 = sdk.AccAddressFromBech32(addrString) + if err2 != nil { + var err3 error + addr, err3 = sdk.ValAddressFromBech32(addrString) + + if err3 != nil { + return fmt.Errorf(`Expected hex or bech32. Got errors: + hex: %v, + bech32 acc: %v + bech32 val: %v + `, err, err2, err3) + + } + } + } + + accAddr := sdk.AccAddress(addr) + valAddr := sdk.ValAddress(addr) + + fmt.Println("Address:", addr) + fmt.Printf("Address (hex): %X\n", addr) + fmt.Printf("Bech32 Acc: %s\n", accAddr) + fmt.Printf("Bech32 Val: %s\n", valAddr) + return nil +} + +func runTxCmd(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("Expected single arg") + } + + txString := args[0] + + // try hex, then base64 + txBytes, err := hex.DecodeString(txString) + if err != nil { + var err2 error + txBytes, err2 = base64.StdEncoding.DecodeString(txString) + if err2 != nil { + return fmt.Errorf(`Expected hex or base64. Got errors: + hex: %v, + base64: %v + `, err, err2) + } + } + + var tx = auth.StdTx{} + cdc := gaia.MakeCodec() + + err = cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) + if err != nil { + return err + } + + bz, err := cdc.MarshalJSON(tx) + if err != nil { + return err + } + + buf := bytes.NewBuffer([]byte{}) + err = json.Indent(buf, bz, "", " ") + if err != nil { + return err + } + + fmt.Println(buf.String()) + return nil +} + +func main() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } + os.Exit(0) +} diff --git a/contrib/devtools/Makefile b/contrib/devtools/Makefile new file mode 100644 index 00000000..08c9b32c --- /dev/null +++ b/contrib/devtools/Makefile @@ -0,0 +1,79 @@ +### +# Find OS and Go environment +# GO contains the Go binary +# FS contains the OS file separator +### +ifeq ($(OS),Windows_NT) + GO := $(shell where go.exe 2> NUL) + FS := \\ +else + GO := $(shell command -v go 2> /dev/null) + FS := / +endif + +ifeq ($(GO),) + $(error could not find go. Is it in PATH? $(GO)) +endif + +GOPATH ?= $(shell $(GO) env GOPATH) +GITHUBDIR := $(GOPATH)$(FS)src$(FS)github.com +GOLANGCI_LINT_VERSION := v1.16.0 +GOLANGCI_LINT_HASHSUM := ac897cadc180bf0c1a4bf27776c410debad27205b22856b861d41d39d06509cf + +### +# Functions +### + +go_get = $(if $(findstring Windows_NT,$(OS)),\ +IF NOT EXIST $(GITHUBDIR)$(FS)$(1)$(FS) ( mkdir $(GITHUBDIR)$(FS)$(1) ) else (cd .) &\ +IF NOT EXIST $(GITHUBDIR)$(FS)$(1)$(FS)$(2)$(FS) ( cd $(GITHUBDIR)$(FS)$(1) && git clone https://github.com/$(1)/$(2) ) else (cd .) &\ +,\ +mkdir -p $(GITHUBDIR)$(FS)$(1) &&\ +(test ! -d $(GITHUBDIR)$(FS)$(1)$(FS)$(2) && cd $(GITHUBDIR)$(FS)$(1) && git clone https://github.com/$(1)/$(2)) || true &&\ +)\ +cd $(GITHUBDIR)$(FS)$(1)$(FS)$(2) && git fetch origin && git checkout -q $(3) + +go_install = $(call go_get,$(1),$(2),$(3)) && cd $(GITHUBDIR)$(FS)$(1)$(FS)$(2) && $(GO) install + +mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) +mkfile_dir := $(shell cd $(shell dirname $(mkfile_path)); pwd) + +### +# tools +### + +TOOLS_DESTDIR ?= $(GOPATH)/bin + +GOLANGCI_LINT = $(TOOLS_DESTDIR)/golangci-lint +STATIK = $(TOOLS_DESTDIR)/statik +GOIMPORTS = $(TOOLS_DESTDIR)/goimports +CLOG = $(TOOLS_DESTDIR)/clog +RUNSIM = $(TOOLS_DESTDIR)/runsim + +all: tools + +tools: tools-stamp +tools-stamp: $(STATIK) $(GOIMPORTS) $(CLOG) $(GOLANGCI_LINT) $(RUNSIM) + touch $@ + +$(GOLANGCI_LINT): $(mkfile_dir)/install-golangci-lint.sh + bash $(mkfile_dir)/install-golangci-lint.sh $(TOOLS_DESTDIR) $(GOLANGCI_LINT_VERSION) $(GOLANGCI_LINT_HASHSUM) + +$(STATIK): + $(call go_install,rakyll,statik,v0.1.5) + +$(GOIMPORTS): + go get golang.org/x/tools/cmd/goimports@v0.0.0-20190114222345-bf090417da8b + +$(CLOG): + $(call go_install,alessio,clog,1) + +$(RUNSIM): + go get github.com/cosmos/cosmos-sdk/contrib/runsim@v0.28.2-0.20190517070908-8ff9b25facc5 + go install github.com/cosmos/cosmos-sdk/contrib/runsim + +tools-clean: + rm -f $(STATIK) $(GOIMPORTS) $(CLOG) $(GOLANGCI_LINT) + rm -f tools-stamp + +.PHONY: all tools tools-clean diff --git a/contrib/devtools/install-golangci-lint.sh b/contrib/devtools/install-golangci-lint.sh new file mode 100644 index 00000000..b9571382 --- /dev/null +++ b/contrib/devtools/install-golangci-lint.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -euo pipefail + +f_sha256() { + local l_file + l_file=$1 + python -sBc "import hashlib;print(hashlib.sha256(open('$l_file','rb').read()).hexdigest())" +} + +installer="$(mktemp)" +trap "rm -f ${installer}" EXIT + +GOBIN="${1}" +VERSION="${2}" +HASHSUM="${3}" +CURL="$(which curl)" + +echo "Downloading golangci-lint ${VERSION} installer ..." >&2 +"${CURL}" -sfL "https://raw.githubusercontent.com/golangci/golangci-lint/${VERSION}/install.sh" > "${installer}" + +echo "Checking hashsum ..." >&2 +[ "${HASHSUM}" = "$(f_sha256 ${installer})" ] +chmod +x "${installer}" + +echo "Launching installer ..." >&2 +exec "${installer}" -d -b "${GOBIN}" "${VERSION}" diff --git a/contrib/gitian-build.sh b/contrib/gitian-build.sh new file mode 100755 index 00000000..22e6d513 --- /dev/null +++ b/contrib/gitian-build.sh @@ -0,0 +1,201 @@ +#!/bin/bash + +# symbol prefixes: +# g_ -> global +# l_ - local variable +# f_ -> function + +set -euo pipefail + +GITIAN_CACHE_DIRNAME='.gitian-builder-cache' +GO_DEBIAN_RELEASE='1.12.5-1' +GO_TARBALL="golang-debian-${GO_DEBIAN_RELEASE}.tar.gz" +GO_TARBALL_URL="https://salsa.debian.org/go-team/compiler/golang/-/archive/debian/${GO_DEBIAN_RELEASE}/${GO_TARBALL}" + +# Defaults + +DEFAULT_SIGN_COMMAND='gpg --detach-sign' +DEFAULT_GAIA_SIGS=${GAIA_SIGS:-'gaia.sigs'} +DEFAULT_GITIAN_REPO='https://github.com/devrandom/gitian-builder' +DEFAULT_GBUILD_FLAGS='' +DEFAULT_SIGS_REPO='https://github.com/cosmos/gaia.sigs' + +# Overrides + +SIGN_COMMAND=${SIGN_COMMAND:-${DEFAULT_SIGN_COMMAND}} +GITIAN_REPO=${GITIAN_REPO:-${DEFAULT_GITIAN_REPO}} +GBUILD_FLAGS=${GBUILD_FLAGS:-${DEFAULT_GBUILD_FLAGS}} + +# Globals + +g_workdir='' +g_gitian_cache='' +g_cached_gitian='' +g_cached_go_tarball='' +g_sign_identity='' +g_sigs_dir='' +g_flag_commit='' + + +f_help() { + cat >&2 <&2 + mkdir "${l_builddir}/inputs/" + cp -v "${g_cached_go_tarball}" "${l_builddir}/inputs/" + done +} + +f_build() { + local l_descriptor + + l_descriptor=$1 + + bin/gbuild --commit cosmos-sdk="$g_commit" ${GBUILD_FLAGS} "$l_descriptor" + libexec/stop-target || f_echo_stderr "warning: couldn't stop target" +} + +f_sign_verify() { + local l_descriptor + + l_descriptor=$1 + + bin/gsign -p "${SIGN_COMMAND}" -s "${g_sign_identity}" --destination="${g_sigs_dir}" --release=${g_release} ${l_descriptor} + bin/gverify --destination="${g_sigs_dir}" --release="${g_release}" ${l_descriptor} +} + +f_commit_sig() { + local l_release_name + + l_release_name=$1 + + pushd "${g_sigs_dir}" + git add . || echo "git add failed" >&2 + git commit -m "Add ${l_release_name} reproducible build" || echo "git commit failed" >&2 + popd +} + +f_prep_docker_image() { + pushd $1 + bin/make-base-vm --docker --suite bionic --arch amd64 + popd +} + +f_ensure_cache() { + g_gitian_cache="${g_workdir}/${GITIAN_CACHE_DIRNAME}" + [ -d "${g_gitian_cache}" ] || mkdir "${g_gitian_cache}" + + g_cached_go_tarball="${g_gitian_cache}/${GO_TARBALL}" + if [ ! -f "${g_cached_go_tarball}" ]; then + f_echo_stderr "${g_cached_go_tarball}: cache miss, caching..." + curl -L "${GO_TARBALL_URL}" --output "${g_cached_go_tarball}" + fi + + g_cached_gitian="${g_gitian_cache}/gitian-builder" + if [ ! -d "${g_cached_gitian}" ]; then + f_echo_stderr "${g_cached_gitian}: cache miss, caching..." + git clone ${GITIAN_REPO} "${g_cached_gitian}" + fi +} + +f_demangle_platforms() { + case "${1}" in + all) + printf '%s' 'darwin linux windows' ;; + linux|darwin|windows) + printf '%s' "${1}" ;; + *) + echo "invalid platform -- ${1}" + exit 1 + esac +} + +f_echo_stderr() { + echo $@ >&2 +} + + +while getopts ":cs:h" opt; do + case "${opt}" in + h) f_help ; exit 0 ;; + c) g_flag_commit=y ;; + s) g_sign_identity="${OPTARG}" ;; + esac +done + +shift "$((OPTIND-1))" + +g_platforms=$(f_demangle_platforms "${1}") +g_workdir="$(pwd)" +g_commit="$(git rev-parse HEAD)" +g_sigs_dir=${GAIA_SIGS:-"${g_workdir}/${DEFAULT_GAIA_SIGS}"} + +f_ensure_cache + +f_prep_docker_image "${g_cached_gitian}" + +f_prep_build "${g_platforms}" + +export USE_DOCKER=1 +for g_os in ${g_platforms}; do + g_release="$(git describe --tags --abbrev=9 | sed 's/^v//')-${g_os}" + g_descriptor="${g_workdir}/cmd/gaia/contrib/gitian-descriptors/gitian-${g_os}.yml" + [ -f ${g_descriptor} ] + g_builddir="$(f_builddir ${g_os})" + + pushd "${g_builddir}" + f_build "${g_descriptor}" + if [ -n "${g_sign_identity}" ]; then + f_sign_verify "${g_descriptor}" + fi + popd + + if [ -n "${g_sign_identity}" -a -n "${g_flag_commit}" ]; then + [ -d "${g_sigs_dir}/.git/" ] && f_commit_sig ${g_release} || f_echo_stderr "couldn't commit, ${g_sigs_dir} is not a git clone" + fi +done + +exit 0 diff --git a/contrib/gitian-descriptors/gitian-darwin.yml b/contrib/gitian-descriptors/gitian-darwin.yml new file mode 100644 index 00000000..7e78c5a5 --- /dev/null +++ b/contrib/gitian-descriptors/gitian-darwin.yml @@ -0,0 +1,116 @@ +--- +name: "gaia-darwin" +enable_cache: true +distro: "ubuntu" +suites: +- "bionic" +architectures: +- "amd64" +packages: +- "bsdmainutils" +- "build-essential" +- "ca-certificates" +- "curl" +- "debhelper" +- "dpkg-dev" +- "devscripts" +- "fakeroot" +- "git" +- "golang-any" +- "xxd" +- "quilt" +remotes: +- "url": "https://github.com/cosmos/gaia.git" + "dir": "gaia" +files: +- "golang-debian-1.12.5-1.tar.gz" +script: | + set -e -o pipefail + + GO_SRC_RELEASE=golang-debian-1.12.5-1 + GO_SRC_TARBALL="${GO_SRC_RELEASE}.tar.gz" + # Compile go and configure the environment + export TAR_OPTIONS="--mtime="$REFERENCE_DATE\\\ $REFERENCE_TIME"" + export BUILD_DIR=`pwd` + tar xf "${GO_SRC_TARBALL}" + rm -f "${GO_SRC_TARBALL}" + [ -d "${GO_SRC_RELEASE}/" ] + mv "${GO_SRC_RELEASE}/" go/ + pushd go/ + QUILT_PATCHES=debian/patches quilt push -a + fakeroot debian/rules build RUN_TESTS=false GOCACHE=/tmp/go-cache + popd + + export GOOS=darwin + export GOROOT=${BUILD_DIR}/go + export GOPATH=${BUILD_DIR}/gopath + mkdir -p ${GOPATH}/bin + + export PATH_orig=${PATH} + export PATH=$GOPATH/bin:$GOROOT/bin:$PATH + + export ARCHS='386 amd64' + export GO111MODULE=on + + # Make release tarball + pushd gaia + VERSION=$(git describe --tags | sed 's/^v//') + COMMIT=$(git log -1 --format='%H') + DISTNAME=gaia-${VERSION} + git archive --format tar.gz --prefix ${DISTNAME}/ -o ${DISTNAME}.tar.gz HEAD + SOURCEDIST=`pwd`/`echo gaia-*.tar.gz` + popd + + # Correct tar file order + mkdir -p temp + pushd temp + tar xf $SOURCEDIST + rm $SOURCEDIST + find gaia-* | sort | tar --no-recursion --mode='u+rw,go+r-w,a+X' --owner=0 --group=0 -c -T - | gzip -9n > $SOURCEDIST + popd + + # Prepare GOPATH and install deps + distsrc=${GOPATH}/src/github.com/cosmos/gaia + mkdir -p ${distsrc} + pushd ${distsrc} + tar --strip-components=1 -xf $SOURCEDIST + go mod download + popd + + # Configure LDFLAGS for reproducible builds + LDFLAGS="-extldflags=-static -buildid=${VERSION} -s -w \ + -X github.com/cosmos/cosmos-sdk/version.Name=gaia \ + -X github.com/cosmos/cosmos-sdk/version.Version=${VERSION} \ + -X github.com/cosmos/cosmos-sdk/version.Commit=${COMMIT} \ + -X github.com/cosmos/cosmos-sdk/version.BuildTags=netgo,ledger" + + # Extract release tarball and build + for arch in ${ARCHS}; do + INSTALLPATH=`pwd`/installed/${DISTNAME}-${arch} + mkdir -p ${INSTALLPATH} + + # Build gaia tool suite + pushd ${distsrc} + for prog in gaiacli gaiad; do + GOARCH=${arch} GOROOT_FINAL=${GOROOT} go build -a \ + -gcflags=all=-trimpath=${GOPATH} \ + -asmflags=all=-trimpath=${GOPATH} \ + -mod=readonly -tags "netgo ledger" \ + -ldflags="${LDFLAGS}" \ + -o ${INSTALLPATH}/${prog} ./cmd/gaia/cmd/${prog} + + done + popd # ${distsrc} + + pushd ${INSTALLPATH} + find -type f | sort | tar \ + --no-recursion --mode='u+rw,go+r-w,a+X' \ + --numeric-owner --sort=name \ + --owner=0 --group=0 -c -T - | gzip -9n > ${OUTDIR}/${DISTNAME}-darwin-${arch}.tar.gz + popd # installed + done + + rm -rf ${distsrc} + + mkdir -p $OUTDIR/src + mv $SOURCEDIST $OUTDIR/src diff --git a/contrib/gitian-descriptors/gitian-linux.yml b/contrib/gitian-descriptors/gitian-linux.yml new file mode 100644 index 00000000..1605e8ee --- /dev/null +++ b/contrib/gitian-descriptors/gitian-linux.yml @@ -0,0 +1,115 @@ +--- +name: "gaia-linux" +enable_cache: true +distro: "ubuntu" +suites: +- "bionic" +architectures: +- "amd64" +packages: +- "bsdmainutils" +- "build-essential" +- "ca-certificates" +- "curl" +- "debhelper" +- "dpkg-dev" +- "devscripts" +- "fakeroot" +- "git" +- "golang-any" +- "xxd" +- "quilt" +remotes: +- "url": "https://github.com/cosmos/gaia.git" + "dir": "gaia" +files: +- "golang-debian-1.12.5-1.tar.gz" +script: | + set -e -o pipefail + + GO_SRC_RELEASE=golang-debian-1.12.5-1 + GO_SRC_TARBALL="${GO_SRC_RELEASE}.tar.gz" + # Compile go and configure the environment + export TAR_OPTIONS="--mtime="$REFERENCE_DATE\\\ $REFERENCE_TIME"" + export BUILD_DIR=`pwd` + tar xf "${GO_SRC_TARBALL}" + rm -f "${GO_SRC_TARBALL}" + [ -d "${GO_SRC_RELEASE}/" ] + mv "${GO_SRC_RELEASE}/" go/ + pushd go/ + QUILT_PATCHES=debian/patches quilt push -a + fakeroot debian/rules build RUN_TESTS=false GOCACHE=/tmp/go-cache + popd + + export GOROOT=${BUILD_DIR}/go + export GOPATH=${BUILD_DIR}/gopath + mkdir -p ${GOPATH}/bin + + export PATH_orig=${PATH} + export PATH=$GOPATH/bin:$GOROOT/bin:$PATH + + export ARCHS='386 amd64 arm arm64' + export GO111MODULE=on + + # Make release tarball + pushd gaia + VERSION=$(git describe --tags | sed 's/^v//') + COMMIT=$(git log -1 --format='%H') + DISTNAME=gaia-${VERSION} + git archive --format tar.gz --prefix ${DISTNAME}/ -o ${DISTNAME}.tar.gz HEAD + SOURCEDIST=`pwd`/`echo gaia-*.tar.gz` + popd + + # Correct tar file order + mkdir -p temp + pushd temp + tar xf $SOURCEDIST + rm $SOURCEDIST + find gaia-* | sort | tar --no-recursion --mode='u+rw,go+r-w,a+X' --owner=0 --group=0 -c -T - | gzip -9n > $SOURCEDIST + popd + + # Prepare GOPATH and install deps + distsrc=${GOPATH}/src/github.com/cosmos/gaia + mkdir -p ${distsrc} + pushd ${distsrc} + tar --strip-components=1 -xf $SOURCEDIST + go mod download + popd + + # Configure LDFLAGS for reproducible builds + LDFLAGS="-extldflags=-static -buildid=${VERSION} -s -w \ + -X github.com/cosmos/cosmos-sdk/version.Name=gaia \ + -X github.com/cosmos/cosmos-sdk/version.Version=${VERSION} \ + -X github.com/cosmos/cosmos-sdk/version.Commit=${COMMIT} \ + -X github.com/cosmos/cosmos-sdk/version.BuildTags=netgo,ledger" + + # Extract release tarball and build + for arch in ${ARCHS}; do + INSTALLPATH=`pwd`/installed/${DISTNAME}-${arch} + mkdir -p ${INSTALLPATH} + + # Build gaia tool suite + pushd ${distsrc} + for prog in gaiacli gaiad; do + GOARCH=${arch} GOROOT_FINAL=${GOROOT} go build -a \ + -gcflags=all=-trimpath=${GOPATH} \ + -asmflags=all=-trimpath=${GOPATH} \ + -mod=readonly -tags "netgo ledger" \ + -ldflags="${LDFLAGS}" \ + -o ${INSTALLPATH}/${prog} ./cmd/gaia/cmd/${prog} + + done + popd # ${distsrc} + + pushd ${INSTALLPATH} + find -type f | sort | tar \ + --no-recursion --mode='u+rw,go+r-w,a+X' \ + --numeric-owner --sort=name \ + --owner=0 --group=0 -c -T - | gzip -9n > ${OUTDIR}/${DISTNAME}-linux-${arch}.tar.gz + popd # installed + done + + rm -rf ${distsrc} + + mkdir -p $OUTDIR/src + mv $SOURCEDIST $OUTDIR/src diff --git a/contrib/gitian-descriptors/gitian-windows.yml b/contrib/gitian-descriptors/gitian-windows.yml new file mode 100644 index 00000000..d5433f64 --- /dev/null +++ b/contrib/gitian-descriptors/gitian-windows.yml @@ -0,0 +1,116 @@ +--- +name: "gaia-windows" +enable_cache: true +distro: "ubuntu" +suites: +- "bionic" +architectures: +- "amd64" +packages: +- "bsdmainutils" +- "build-essential" +- "ca-certificates" +- "curl" +- "debhelper" +- "dpkg-dev" +- "devscripts" +- "fakeroot" +- "git" +- "golang-any" +- "xxd" +- "quilt" +remotes: +- "url": "https://github.com/cosmos/gaia.git" + "dir": "gaia" +files: +- "golang-debian-1.12.5-1.tar.gz" +script: | + set -e -o pipefail + + GO_SRC_RELEASE=golang-debian-1.12.5-1 + GO_SRC_TARBALL="${GO_SRC_RELEASE}.tar.gz" + # Compile go and configure the environment + export TAR_OPTIONS="--mtime="$REFERENCE_DATE\\\ $REFERENCE_TIME"" + export BUILD_DIR=`pwd` + tar xf "${GO_SRC_TARBALL}" + rm -f "${GO_SRC_TARBALL}" + [ -d "${GO_SRC_RELEASE}/" ] + mv "${GO_SRC_RELEASE}/" go/ + pushd go/ + QUILT_PATCHES=debian/patches quilt push -a + fakeroot debian/rules build RUN_TESTS=false GOCACHE=/tmp/go-cache + popd + + export GOROOT=${BUILD_DIR}/go + export GOPATH=${BUILD_DIR}/gopath + mkdir -p ${GOPATH}/bin + + export PATH_orig=${PATH} + export PATH=$GOPATH/bin:$GOROOT/bin:$PATH + + export ARCHS='386 amd64' + export GO111MODULE=on + + # Make release tarball + pushd gaia + VERSION=$(git describe --tags | sed 's/^v//') + COMMIT=$(git log -1 --format='%H') + DISTNAME=gaia-${VERSION} + git archive --format tar.gz --prefix ${DISTNAME}/ -o ${DISTNAME}.tar.gz HEAD + SOURCEDIST=`pwd`/`echo gaia-*.tar.gz` + popd + + # Correct tar file order + mkdir -p temp + pushd temp + tar xf $SOURCEDIST + rm $SOURCEDIST + find gaia-* | sort | tar --no-recursion --mode='u+rw,go+r-w,a+X' --owner=0 --group=0 -c -T - | gzip -9n > $SOURCEDIST + popd + + # Prepare GOPATH and install deps + distsrc=${GOPATH}/src/github.com/cosmos/gaia + mkdir -p ${distsrc} + pushd ${distsrc} + tar --strip-components=1 -xf $SOURCEDIST + go mod download + popd + + # Configure LDFLAGS for reproducible builds + LDFLAGS="-extldflags=-static -buildid=${VERSION} -s -w \ + -X github.com/cosmos/cosmos-sdk/version.Name=gaia \ + -X github.com/cosmos/cosmos-sdk/version.Version=${VERSION} \ + -X github.com/cosmos/cosmos-sdk/version.Commit=${COMMIT} \ + -X github.com/cosmos/cosmos-sdk/version.BuildTags=netgo,ledger" + + # Extract release tarball and build + for arch in ${ARCHS}; do + INSTALLPATH=`pwd`/installed/${DISTNAME}-${arch} + mkdir -p ${INSTALLPATH} + + # Build gaia tool suite + pushd ${distsrc} + for prog in gaiacli gaiad; do + exe=${prog}.exe + GOARCH=${arch} GOROOT_FINAL=${GOROOT} go build -a \ + -gcflags=all=-trimpath=${GOPATH} \ + -asmflags=all=-trimpath=${GOPATH} \ + -mod=readonly -tags "netgo ledger" \ + -ldflags="${LDFLAGS}" \ + -o ${INSTALLPATH}/${exe} ./cmd/gaia/cmd/${prog} + + done + popd # ${distsrc} + + pushd ${INSTALLPATH} + find -type f | sort | tar \ + --no-recursion --mode='u+rw,go+r-w,a+X' \ + --numeric-owner --sort=name \ + --owner=0 --group=0 -c -T - | gzip -9n > ${OUTDIR}/${DISTNAME}-windows-${arch}.tar.gz + popd # installed + done + + rm -rf ${distsrc} + + mkdir -p $OUTDIR/src + mv $SOURCEDIST $OUTDIR/src diff --git a/contrib/gitian-keys/README.md b/contrib/gitian-keys/README.md new file mode 100644 index 00000000..64501433 --- /dev/null +++ b/contrib/gitian-keys/README.md @@ -0,0 +1,29 @@ +## PGP keys of Gitian builders and Gaia Developers + +The file `keys.txt` contains fingerprints of the public keys of Gitian builders +and active developers. + +The associated keys are mainly used to sign git commits or the build results +of Gitian builds. + +The most recent version of each pgp key can be found on most PGP key servers. + +Fetch the latest version from the key server to see if any key was revoked in +the meantime. +To fetch the latest version of all pgp keys in your gpg homedir, + +```sh +gpg --refresh-keys +``` + +To fetch keys of Gitian builders and active core developers, feed the list of +fingerprints of the primary keys into gpg: + +```sh +while read fingerprint keyholder_name; \ +do gpg --keyserver hkp://subset.pool.sks-keyservers.net \ +--recv-keys ${fingerprint}; done < ./keys.txt +``` + +Add your key to the list if you are a Gaia core developer or you have +provided Gitian signatures for two major or minor releases of Gaia. diff --git a/contrib/gitian-keys/keys.txt b/contrib/gitian-keys/keys.txt new file mode 100644 index 00000000..1d9cf6ec --- /dev/null +++ b/contrib/gitian-keys/keys.txt @@ -0,0 +1,2 @@ +04160004A8276E40BB9890FBE8A48AE5311D765A Alessio Treglia +237396563D09DCD65B122AE7EC1904F1389EF7E5 Karoly Albert Szabo diff --git a/contrib/localnet-blocks-test.sh b/contrib/localnet-blocks-test.sh new file mode 100755 index 00000000..53df090f --- /dev/null +++ b/contrib/localnet-blocks-test.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +CNT=0 +ITER=$1 +SLEEP=$2 +NUMBLOCKS=$3 +NODEADDR=$4 + +if [ -z "$1" ]; then + echo "Need to input number of iterations to run..." + exit 1 +fi + +if [ -z "$2" ]; then + echo "Need to input number of seconds to sleep between iterations" + exit 1 +fi + +if [ -z "$3" ]; then + echo "Need to input block height to declare completion..." + exit 1 +fi + +if [ -z "$4" ]; then + echo "Need to input node address to poll..." + exit 1 +fi + +while [ ${CNT} -lt $ITER ]; do + var=$(curl -s $NODEADDR:26657/status | jq -r '.result.sync_info.latest_block_height') + echo "Number of Blocks: ${var}" + if [ ! -z ${var} ] && [ ${var} -gt ${NUMBLOCKS} ]; then + echo "Number of blocks reached, exiting success..." + exit 0 + fi + let CNT=CNT+1 + sleep $SLEEP +done + +echo "Timeout reached, exiting failure..." +exit 1 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..a0001416 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,68 @@ +version: '3' + +services: + gaiadnode0: + container_name: gaiadnode0 + image: "tendermint/gaiadnode" + ports: + - "26656-26657:26656-26657" + environment: + - ID=0 + - LOG=${LOG:-gaiad.log} + volumes: + - ./build:/gaiad:Z + networks: + localnet: + ipv4_address: 192.168.10.2 + + gaiadnode1: + container_name: gaiadnode1 + image: "tendermint/gaiadnode" + ports: + - "26659-26660:26656-26657" + environment: + - ID=1 + - LOG=${LOG:-gaiad.log} + volumes: + - ./build:/gaiad:Z + networks: + localnet: + ipv4_address: 192.168.10.3 + + gaiadnode2: + container_name: gaiadnode2 + image: "tendermint/gaiadnode" + environment: + - ID=2 + - LOG=${LOG:-gaiad.log} + ports: + - "26661-26662:26656-26657" + volumes: + - ./build:/gaiad:Z + networks: + localnet: + ipv4_address: 192.168.10.4 + + gaiadnode3: + container_name: gaiadnode3 + image: "tendermint/gaiadnode" + environment: + - ID=3 + - LOG=${LOG:-gaiad.log} + ports: + - "26663-26664:26656-26657" + volumes: + - ./build:/gaiad:Z + networks: + localnet: + ipv4_address: 192.168.10.5 + +networks: + localnet: + driver: bridge + ipam: + driver: default + config: + - + subnet: 192.168.10.0/16 + diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..963d418c --- /dev/null +++ b/go.mod @@ -0,0 +1,41 @@ +module github.com/cosmos/gaia + +go 1.12 + +require ( + github.com/btcsuite/btcd v0.0.0-20190427004231-96897255fd17 // indirect + github.com/cosmos/cosmos-sdk v0.28.2-0.20190517070908-8ff9b25facc5 + github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d // indirect + github.com/gogo/protobuf v1.2.1 // indirect + github.com/google/gofuzz v1.0.0 // indirect + github.com/gorilla/mux v1.7.2 // indirect + github.com/kr/pretty v0.1.0 // indirect + github.com/magiconair/properties v1.8.1 // indirect + github.com/mattn/go-isatty v0.0.7 // indirect + github.com/onsi/ginkgo v1.8.0 // indirect + github.com/onsi/gomega v1.5.0 // indirect + github.com/otiai10/copy v1.0.1 + github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776 // indirect + github.com/pelletier/go-toml v1.4.0 // indirect + github.com/prometheus/client_golang v0.9.3 // indirect + github.com/prometheus/procfs v0.0.0-20190516194456-169873baca24 // indirect + github.com/rakyll/statik v0.1.6 + github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect + github.com/spf13/afero v1.2.2 // indirect + github.com/spf13/cobra v0.0.3 + github.com/spf13/viper v1.3.2 + github.com/stretchr/testify v1.3.0 + github.com/syndtr/goleveldb v1.0.0 // indirect + github.com/tendermint/go-amino v0.15.0 + github.com/tendermint/tendermint v0.31.5 + golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f // indirect + golang.org/x/exp v0.0.0-20190121172915-509febef88a4 // indirect + golang.org/x/net v0.0.0-20190514140710-3ec191127204 // indirect + golang.org/x/sys v0.0.0-20190516110030-61b9204099cb // indirect + golang.org/x/text v0.3.2 // indirect + google.golang.org/appengine v1.4.0 // indirect + google.golang.org/grpc v1.19.1 // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect +) + +replace golang.org/x/crypto => github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5 diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..d9e59a9f --- /dev/null +++ b/go.sum @@ -0,0 +1,300 @@ +bou.ke/monkey v1.0.1 h1:zEMLInw9xvNakzUUPjfS4Ds6jYPqCFx3m7bRmG5NH2U= +bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d h1:1aAija9gr0Hyv4KfQcRcwlmFIrhkDmIj2dz5bkg/s/8= +github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d/go.mod h1:icNx/6QdFblhsEjZehARqbNumymUT/ydwlLojFdv7Sk= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d h1:xG8Pj6Y6J760xwETNmMzmlt38QSwz0BLp1cZ09g27uw= +github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= +github.com/btcsuite/btcd v0.0.0-20190427004231-96897255fd17 h1:m0N5Vg5nP3zEz8TREZpwX3gt4Biw3/8fbIf4A3hO96g= +github.com/btcsuite/btcd v0.0.0-20190427004231-96897255fd17/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a h1:RQMUrEILyYJEoAT34XS/kLu40vC0+po/UfxrBBA4qZE= +github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cosmos/cosmos-sdk v0.28.2-0.20190517070908-8ff9b25facc5 h1:wumWgV5uM0DQONxXHlQ99rDkfemKuVhWhGf6EQR/WJc= +github.com/cosmos/cosmos-sdk v0.28.2-0.20190517070908-8ff9b25facc5/go.mod h1:IiSWVVfXb8IFmXk0/C8oZMugjsN0sV1Oj1QAbtNkkHQ= +github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 h1:Iwin12wRQtyZhH6FV3ykFcdGNlYEzoeR0jN8Vn+JWsI= +github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= +github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU= +github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= +github.com/cosmos/ledger-cosmos-go v0.10.3 h1:Qhi5yTR5Pg1CaTpd00pxlGwNl4sFRdtK1J96OTjeFFc= +github.com/cosmos/ledger-cosmos-go v0.10.3/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= +github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI= +github.com/cosmos/ledger-go v0.9.2/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= +github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= +github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= +github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-isatty v0.0.6 h1:SrwhHcpV4nWrMGdNcC2kXpMfcBVYGDuTArqyhocJgvA= +github.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/otiai10/copy v0.0.0-20180813032824-7e9a647135a1/go.mod h1:pXzZSDlN+HPzSdyIBnKNN9ptD9Hx7iZMWIJPTwo4FPE= +github.com/otiai10/copy v1.0.1 h1:gtBjD8aq4nychvRZ2CyJvFWAw0aja+VHazDdruZKGZA= +github.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95 h1:+OLn68pqasWca0z5ryit9KGfp3sUsW4Lqg32iRMJyzs= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776 h1:o59bHXu8Ejas8Kq6pjoVJQ9/neN66SM8AKh6wI42BBs= +github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776/go.mod h1:3HNVkVOU7vZeFXocWuvtcS0XSFLcf2XUSDHkq9t1jU4= +github.com/otiai10/mint v1.2.3 h1:PsrRBmrxR68kyNu6YlqYHbNlItc5vOkuS6LBEsNttVA= +github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw= +github.com/otiai10/mint v1.2.4 h1:DxYL0itZyPaR5Z9HILdxSoHx+gNs6Yx+neOGS3IVUk0= +github.com/otiai10/mint v1.2.4/go.mod h1:d+b7n/0R3tdyUYYylALXpWQ/kTN+QobSq/4SRGBkR3M= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190227231451-bbced9601137 h1:3l8oligPtjd4JuM+OZ+U8sjtwFGJs98cdWsqs6QZRWs= +github.com/prometheus/procfs v0.0.0-20190227231451-bbced9601137/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190516194456-169873baca24 h1:Z/gi7FYIpZYoytiiKRT/p+DkwTalgdJ46WIfk+Feq2I= +github.com/prometheus/procfs v0.0.0-20190516194456-169873baca24/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rakyll/statik v0.1.4/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= +github.com/rakyll/statik v0.1.6 h1:uICcfUXpgqtw2VopbIncslhAmE5hwc4g20TEyEENBNs= +github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= +github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 h1:nkcn14uNmFEuGCb2mBZbBb24RdNRL08b/wb+xBOYpuk= +github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= +github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.1 h1:qgMbHoJbPbw579P+1zVY+6n4nIFuIchaIjzZ/I/Yq8M= +github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.0.3/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= +github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/syndtr/goleveldb v0.0.0-20180708030551-c4c61651e9e3 h1:sAlSBRDl4psFR3ysKXRSE8ss6Mt90+ma1zRTroTNBJA= +github.com/syndtr/goleveldb v0.0.0-20180708030551-c4c61651e9e3/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s= +github.com/tendermint/btcd v0.1.1/go.mod h1:DC6/m53jtQzr/NFmMNEu0rxf18/ktVoVtMrnDD5pN+U= +github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5 h1:u8i49c+BxloX3XQ55cvzFNXplizZP/q00i+IlttUjAU= +github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk= +github.com/tendermint/go-amino v0.15.0 h1:TC4e66P59W7ML9+bxio17CPKnxW3nKIRAYskntMAoRk= +github.com/tendermint/go-amino v0.15.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tendermint/iavl v0.12.2 h1:Ls5p5VINCM1HRT9g5Vvs2zmDOCU/CCIvIHzd/pZ8P0E= +github.com/tendermint/iavl v0.12.2/go.mod h1:EoKMMv++tDOL5qKKVnoIqtVPshRrEPeJ0WsgDOLAauM= +github.com/tendermint/tendermint v0.31.5 h1:vTet8tCq3B9/J9Yo11dNZ8pOB7NtSy++bVSfkP4KzR4= +github.com/tendermint/tendermint v0.31.5/go.mod h1:ymcPyWblXCplCPQjbOYbrF1fWnpslATMVqiGgWbZrlc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8= +github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190514140710-3ec191127204 h1:4yG6GqBtw9C+UrLp6s2wtSniayy/Vd/3F7ffLE427XI= +golang.org/x/net v0.0.0-20190514140710-3ec191127204/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190516110030-61b9204099cb h1:k07iPOt0d6nEnwXF+kHB+iEg+WSuKe/SOQuFM2QoD+E= +golang.org/x/sys v0.0.0-20190516110030-61b9204099cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b h1:qMK98NmNCRVDIYFycQ5yVRkvgDUFfdP8Ip4KqmDEB7g= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190516172635-bb713bdc0e52 h1:LHc/6x2dMeCKkSsrVgo4DY+Z566T1OeoMwLtdfoy8LE= +google.golang.org/genproto v0.0.0-20190516172635-bb713bdc0e52/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/lcd_test/helpers_test.go b/lcd_test/helpers_test.go new file mode 100644 index 00000000..1783da75 --- /dev/null +++ b/lcd_test/helpers_test.go @@ -0,0 +1,1558 @@ +package lcd_test + +import ( + "bytes" + "fmt" + "io/ioutil" + "net" + "net/http" + "os" + "path/filepath" + "regexp" + "sort" + "strings" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + + gapp "github.com/cosmos/gaia/app" + + "github.com/cosmos/cosmos-sdk/client" + clientkeys "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/lcd" + "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/client/tx" + clienttx "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys" + crkeys "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/tests" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/rest" + "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" + "github.com/cosmos/cosmos-sdk/x/auth/genaccounts" + bankrest "github.com/cosmos/cosmos-sdk/x/bank/client/rest" + "github.com/cosmos/cosmos-sdk/x/crisis" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + distrrest "github.com/cosmos/cosmos-sdk/x/distribution/client/rest" + "github.com/cosmos/cosmos-sdk/x/genutil" + "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" + "github.com/cosmos/cosmos-sdk/x/mint" + mintrest "github.com/cosmos/cosmos-sdk/x/mint/client/rest" + paramsrest "github.com/cosmos/cosmos-sdk/x/params/client/rest" + paramscutils "github.com/cosmos/cosmos-sdk/x/params/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" + stakingrest "github.com/cosmos/cosmos-sdk/x/staking/client/rest" + + abci "github.com/tendermint/tendermint/abci/types" + tmcfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" + "github.com/tendermint/tendermint/libs/cli" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + nm "github.com/tendermint/tendermint/node" + "github.com/tendermint/tendermint/p2p" + pvm "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/proxy" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + tmrpc "github.com/tendermint/tendermint/rpc/lib/server" + tmtypes "github.com/tendermint/tendermint/types" +) + +var cdc = codec.New() + +func init() { + codec.RegisterCrypto(cdc) +} + +// makePathname creates a unique pathname for each test. It will panic if it +// cannot get the current working directory. +func makePathname() string { + p, err := os.Getwd() + if err != nil { + panic(err) + } + + sep := string(filepath.Separator) + return strings.Replace(p, sep, "_", -1) +} + +// GetConfig returns a Tendermint config for the test cases. +func GetConfig() *tmcfg.Config { + pathname := makePathname() + config := tmcfg.ResetTestRoot(pathname) + + tmAddr, _, err := server.FreeTCPAddr() + if err != nil { + panic(err) + } + + rcpAddr, _, err := server.FreeTCPAddr() + if err != nil { + panic(err) + } + + grpcAddr, _, err := server.FreeTCPAddr() + if err != nil { + panic(err) + } + + config.P2P.ListenAddress = tmAddr + config.RPC.ListenAddress = rcpAddr + config.RPC.GRPCListenAddress = grpcAddr + + return config +} + +// CreateAddr adds an address to the key store and returns an address and seed. +// It also requires that the key could be created. +func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (sdk.AccAddress, string) { + var ( + err error + info crkeys.Info + seed string + ) + + info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1) + require.NoError(t, err) + + return sdk.AccAddress(info.GetPubKey().Address()), seed +} + +// CreateAddr adds multiple address to the key store and returns the addresses and associated seeds in lexographical order by address. +// It also requires that the keys could be created. +func CreateAddrs(t *testing.T, kb crkeys.Keybase, numAddrs int) (addrs []sdk.AccAddress, seeds, names, passwords []string) { + var ( + err error + info crkeys.Info + seed string + ) + + addrSeeds := AddrSeedSlice{} + + for i := 0; i < numAddrs; i++ { + name := fmt.Sprintf("test%d", i) + password := "1234567890" + info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1) + require.NoError(t, err) + addrSeeds = append(addrSeeds, AddrSeed{Address: sdk.AccAddress(info.GetPubKey().Address()), Seed: seed, Name: name, Password: password}) + } + + sort.Sort(addrSeeds) + + for i := range addrSeeds { + addrs = append(addrs, addrSeeds[i].Address) + seeds = append(seeds, addrSeeds[i].Seed) + names = append(names, addrSeeds[i].Name) + passwords = append(passwords, addrSeeds[i].Password) + } + + return addrs, seeds, names, passwords +} + +// 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 +} + +// AddrSeedSlice implements `Interface` in sort package. +type AddrSeedSlice []AddrSeed + +func (b AddrSeedSlice) Len() int { + return len(b) +} + +// Less sorts lexicographically by Address +func (b AddrSeedSlice) Less(i, j int) bool { + // bytes package already implements Comparable for []byte. + switch bytes.Compare(b[i].Address.Bytes(), b[j].Address.Bytes()) { + case -1: + return true + case 0, 1: + return false + default: + panic("not fail-able with `bytes.Comparable` bounded [-1, 1].") + } +} + +func (b AddrSeedSlice) Swap(i, j int) { + b[j], b[i] = b[i], b[j] +} + +// 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 +// InitializeTestLCD starts Tendermint and the LCD in process, listening on +// their respective sockets where nValidators is the total number of validators +// and initAddrs are the accounts to initialize with some stake tokens. It +// returns a cleanup function, a set of validator public keys, and a port. +func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress, minting bool) ( + cleanup func(), valConsPubKeys []crypto.PubKey, valOperAddrs []sdk.ValAddress, port string) { + + if nValidators < 1 { + panic("InitializeTestLCD must use at least one validator") + } + + config := GetConfig() + config.Consensus.TimeoutCommit = 100 + config.Consensus.SkipTimeoutCommit = false + config.TxIndex.IndexAllTags = true + + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + logger = log.NewFilter(logger, log.AllowError()) + + privVal := pvm.LoadOrGenFilePV(config.PrivValidatorKeyFile(), + config.PrivValidatorStateFile()) + privVal.Reset() + + db := dbm.NewMemDB() + app := gapp.NewGaiaApp(logger, db, nil, true, 0) + cdc = gapp.MakeCodec() + + genesisFile := config.GenesisFile() + genDoc, err := tmtypes.GenesisDocFromFile(genesisFile) + require.Nil(t, err) + genDoc.Validators = nil + require.NoError(t, genDoc.SaveAs(genesisFile)) + + // append any additional (non-proposing) validators + var genTxs []auth.StdTx + var accs []genaccounts.GenesisAccount + for i := 0; i < nValidators; i++ { + operPrivKey := secp256k1.GenPrivKey() + operAddr := operPrivKey.PubKey().Address() + pubKey := privVal.GetPubKey() + + power := int64(100) + if i > 0 { + pubKey = ed25519.GenPrivKey().PubKey() + power = 1 + } + startTokens := sdk.TokensFromTendermintPower(power) + + msg := staking.NewMsgCreateValidator( + sdk.ValAddress(operAddr), + pubKey, + sdk.NewCoin(sdk.DefaultBondDenom, startTokens), + staking.NewDescription(fmt.Sprintf("validator-%d", i+1), "", "", ""), + staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), + sdk.OneInt(), + ) + stdSignMsg := txbuilder.StdSignMsg{ + ChainID: genDoc.ChainID, + Msgs: []sdk.Msg{msg}, + } + sig, err := operPrivKey.Sign(stdSignMsg.Bytes()) + require.Nil(t, err) + + tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{{Signature: sig, PubKey: operPrivKey.PubKey()}}, "") + genTxs = append(genTxs, tx) + + valConsPubKeys = append(valConsPubKeys, pubKey) + valOperAddrs = append(valOperAddrs, sdk.ValAddress(operAddr)) + + accAuth := auth.NewBaseAccountWithAddress(sdk.AccAddress(operAddr)) + accTokens := sdk.TokensFromTendermintPower(150) + accAuth.Coins = sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, accTokens)} + accs = append(accs, genaccounts.NewGenesisAccount(&accAuth)) + } + + genesisState := gapp.NewDefaultGenesisState() + genDoc.AppState, err = cdc.MarshalJSON(genesisState) + require.NoError(t, err) + genesisState, err = genutil.SetGenTxsInAppGenesisState(cdc, genesisState, genTxs) + require.NoError(t, err) + + // add some tokens to init accounts + stakingDataBz := genesisState[staking.ModuleName] + var stakingData staking.GenesisState + cdc.MustUnmarshalJSON(stakingDataBz, &stakingData) + for _, addr := range initAddrs { + accAuth := auth.NewBaseAccountWithAddress(addr) + accTokens := sdk.TokensFromTendermintPower(100) + accAuth.Coins = sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, accTokens)} + acc := genaccounts.NewGenesisAccount(&accAuth) + accs = append(accs, acc) + } + + // now add the account tokens to the non-bonded pool + for _, acc := range accs { + accTokens := acc.Coins.AmountOf(sdk.DefaultBondDenom) + stakingData.Pool.NotBondedTokens = stakingData.Pool.NotBondedTokens.Add(accTokens) + } + stakingDataBz = cdc.MustMarshalJSON(stakingData) + genesisState[staking.ModuleName] = stakingDataBz + + genaccountsData := genaccounts.NewGenesisState(accs) + genaccountsDataBz := cdc.MustMarshalJSON(genaccountsData) + genesisState[genaccounts.ModuleName] = genaccountsDataBz + + // mint genesis (none set within genesisState) + mintData := mint.DefaultGenesisState() + inflationMin := sdk.ZeroDec() + if minting { + inflationMin = sdk.MustNewDecFromStr("10000.0") + mintData.Params.InflationMax = sdk.MustNewDecFromStr("15000.0") + } else { + mintData.Params.InflationMax = inflationMin + } + mintData.Minter.Inflation = inflationMin + mintData.Params.InflationMin = inflationMin + mintDataBz := cdc.MustMarshalJSON(mintData) + genesisState[mint.ModuleName] = mintDataBz + + // initialize crisis data + crisisDataBz := genesisState[crisis.ModuleName] + var crisisData crisis.GenesisState + cdc.MustUnmarshalJSON(crisisDataBz, &crisisData) + crisisData.ConstantFee = sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000) + crisisDataBz = cdc.MustMarshalJSON(crisisData) + genesisState[crisis.ModuleName] = crisisDataBz + + // double check inflation is set according to the minting boolean flag + if minting { + require.Equal(t, sdk.MustNewDecFromStr("15000.0"), mintData.Params.InflationMax) + require.Equal(t, sdk.MustNewDecFromStr("10000.0"), mintData.Minter.Inflation) + require.Equal(t, sdk.MustNewDecFromStr("10000.0"), mintData.Params.InflationMin) + } else { + require.Equal(t, sdk.ZeroDec(), mintData.Params.InflationMax) + require.Equal(t, sdk.ZeroDec(), mintData.Minter.Inflation) + require.Equal(t, sdk.ZeroDec(), mintData.Params.InflationMin) + } + + appState, err := codec.MarshalJSONIndent(cdc, genesisState) + require.NoError(t, err) + genDoc.AppState = appState + + listenAddr, port, err := server.FreeTCPAddr() + require.NoError(t, err) + + // NOTE: Need to set this so LCD knows the tendermint node address! + viper.Set(client.FlagNode, config.RPC.ListenAddress) + viper.Set(client.FlagChainID, genDoc.ChainID) + // TODO Set to false once the upstream Tendermint proof verification issue is fixed. + viper.Set(client.FlagTrustNode, true) + + node := startTM(t, config, logger, genDoc, privVal, app) + require.NoError(t, err) + + tests.WaitForNextHeightTM(tests.ExtractPortFromAddress(config.RPC.ListenAddress)) + lcd, err := startLCD(logger, listenAddr, cdc, t) + require.NoError(t, err) + + tests.WaitForLCDStart(port) + tests.WaitForHeight(1, port) + + cleanup = func() { + logger.Debug("cleaning up LCD initialization") + node.Stop() //nolint:errcheck + node.Wait() + lcd.Close() + os.RemoveAll(config.RootDir) + } + + return cleanup, valConsPubKeys, valOperAddrs, port +} + +// startTM creates and starts an in-process Tendermint node with memDB and +// in-process ABCI application. It returns the new node or any error that +// occurred. +// +// TODO: Clean up the WAL dir or enable it to be not persistent! +func startTM( + t *testing.T, tmcfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc, + privVal tmtypes.PrivValidator, app abci.Application, +) *nm.Node { + + genDocProvider := func() (*tmtypes.GenesisDoc, error) { return genDoc, nil } + dbProvider := func(*nm.DBContext) (dbm.DB, error) { return dbm.NewMemDB(), nil } + nodeKey, err := p2p.LoadOrGenNodeKey(tmcfg.NodeKeyFile()) + require.NoError(t, err) + + node, err := nm.NewNode( + tmcfg, + privVal, + nodeKey, + proxy.NewLocalClientCreator(app), + genDocProvider, + dbProvider, + nm.DefaultMetricsProvider(tmcfg.Instrumentation), + logger.With("module", "node"), + ) + require.NoError(t, err) + + err = node.Start() + require.NoError(t, err) + + tests.WaitForRPC(tmcfg.RPC.ListenAddress) + logger.Info("Tendermint running!") + + return node +} + +// startLCD starts the LCD. +func startLCD(logger log.Logger, listenAddr string, cdc *codec.Codec, t *testing.T) (net.Listener, error) { + rs := lcd.NewRestServer(cdc) + registerRoutes(rs) + listener, err := tmrpc.Listen(listenAddr, tmrpc.DefaultConfig()) + if err != nil { + return nil, err + } + go tmrpc.StartHTTPServer(listener, rs.Mux, logger, tmrpc.DefaultConfig()) //nolint:errcheck + return listener, nil +} + +// TODO generalize this with the module basic manager +// NOTE: If making updates here also update cmd/gaia/cmd/gaiacli/main.go +func registerRoutes(rs *lcd.RestServer) { + rpc.RegisterRoutes(rs.CliCtx, rs.Mux) + tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) + authrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, auth.StoreKey) + bankrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) + distrrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, distr.StoreKey) + stakingrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) + slashingrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) + govrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, paramsrest.ProposalRESTHandler(rs.CliCtx, rs.Cdc)) + mintrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) +} + +// Request makes a test LCD test request. It returns a response object and a +// stringified response body. +func Request(t *testing.T, port, method, path string, payload []byte) (*http.Response, string) { + var ( + err error + res *http.Response + ) + url := fmt.Sprintf("http://localhost:%v%v", port, path) + fmt.Printf("REQUEST %s %s\n", method, url) + + req, err := http.NewRequest(method, url, bytes.NewBuffer(payload)) + require.Nil(t, err) + + res, err = http.DefaultClient.Do(req) + require.Nil(t, err) + + output, err := ioutil.ReadAll(res.Body) + res.Body.Close() + require.Nil(t, err) + + return res, string(output) +} + +// ---------------------------------------------------------------------- +// ICS 0 - Tendermint +// ---------------------------------------------------------------------- +// GET /node_info The properties of the connected node +func getNodeInfo(t *testing.T, port string) p2p.DefaultNodeInfo { + res, body := Request(t, port, "GET", "/node_info", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var nodeInfo p2p.DefaultNodeInfo + err := cdc.UnmarshalJSON([]byte(body), &nodeInfo) + require.Nil(t, err, "Couldn't parse node info") + + require.NotEqual(t, p2p.DefaultNodeInfo{}, nodeInfo, "res: %v", res) + return nodeInfo +} + +// GET /syncing Syncing state of node +func getSyncStatus(t *testing.T, port string, syncing bool) { + res, body := Request(t, port, "GET", "/syncing", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + if syncing { + require.Equal(t, "true", body) + return + } + require.Equal(t, "false", body) +} + +// GET /blocks/latest Get the latest block +// GET /blocks/{height} Get a block at a certain height +func getBlock(t *testing.T, port string, height int, expectFail bool) ctypes.ResultBlock { + var url string + if height > 0 { + url = fmt.Sprintf("/blocks/%d", height) + } else { + url = "/blocks/latest" + } + var resultBlock ctypes.ResultBlock + + res, body := Request(t, port, "GET", url, nil) + if expectFail { + require.Equal(t, http.StatusNotFound, res.StatusCode, body) + return resultBlock + } + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err := cdc.UnmarshalJSON([]byte(body), &resultBlock) + require.Nil(t, err, "Couldn't parse block") + + require.NotEqual(t, ctypes.ResultBlock{}, resultBlock) + return resultBlock +} + +// GET /validatorsets/{height} Get a validator set a certain height +// GET /validatorsets/latest Get the latest validator set +func getValidatorSets(t *testing.T, port string, height int, expectFail bool) rpc.ResultValidatorsOutput { + var url string + if height > 0 { + url = fmt.Sprintf("/validatorsets/%d", height) + } else { + url = "/validatorsets/latest" + } + var resultVals rpc.ResultValidatorsOutput + + res, body := Request(t, port, "GET", url, nil) + + if expectFail { + require.Equal(t, http.StatusNotFound, res.StatusCode, body) + return resultVals + } + + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err := cdc.UnmarshalJSON([]byte(body), &resultVals) + require.Nil(t, err, "Couldn't parse validatorset") + + require.NotEqual(t, rpc.ResultValidatorsOutput{}, resultVals) + return resultVals +} + +// GET /txs/{hash} get tx by hash +func getTransaction(t *testing.T, port string, hash string) sdk.TxResponse { + var tx sdk.TxResponse + res, body := getTransactionRequest(t, port, hash) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err := cdc.UnmarshalJSON([]byte(body), &tx) + require.NoError(t, err) + return tx +} + +func getTransactionRequest(t *testing.T, port, hash string) (*http.Response, string) { + return Request(t, port, "GET", fmt.Sprintf("/txs/%s", hash), nil) +} + +// POST /txs broadcast txs + +// GET /txs search transactions +func getTransactions(t *testing.T, port string, tags ...string) *sdk.SearchTxsResult { + var txs []sdk.TxResponse + result := sdk.NewSearchTxsResult(0, 0, 1, 30, txs) + if len(tags) == 0 { + return &result + } + queryStr := strings.Join(tags, "&") + res, body := Request(t, port, "GET", fmt.Sprintf("/txs?%s", queryStr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err := cdc.UnmarshalJSON([]byte(body), &result) + require.NoError(t, err) + return &result +} + +// ---------------------------------------------------------------------- +// ICS 1 - Keys +// ---------------------------------------------------------------------- +// GET /keys List of accounts stored locally +func getKeys(t *testing.T, port string) []keys.KeyOutput { + res, body := Request(t, port, "GET", "/keys", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var m []keys.KeyOutput + err := cdc.UnmarshalJSON([]byte(body), &m) + require.Nil(t, err) + return m +} + +// POST /keys Create a new account locally +func doKeysPost(t *testing.T, port, name, password, mnemonic string, account int, index int) keys.KeyOutput { + pk := clientkeys.NewAddNewKey(name, password, mnemonic, account, index) + req, err := cdc.MarshalJSON(pk) + require.NoError(t, err) + + res, body := Request(t, port, "POST", "/keys", req) + + require.Equal(t, http.StatusOK, res.StatusCode, body) + var resp keys.KeyOutput + err = cdc.UnmarshalJSON([]byte(body), &resp) + require.Nil(t, err, body) + return resp +} + +// GET /keys/seed Create a new seed to create a new account defaultValidFor +func getKeysSeed(t *testing.T, port string) string { + res, body := Request(t, port, "GET", "/keys/seed", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + reg, err := regexp.Compile(`([a-z]+ ){12}`) + require.Nil(t, err) + match := reg.MatchString(body) + require.True(t, match, "Returned seed has wrong format", body) + return body +} + +// POST /keys/{name}/recove Recover a account from a seed +func doRecoverKey(t *testing.T, port, recoverName, recoverPassword, mnemonic string, account uint32, index uint32) { + pk := clientkeys.NewRecoverKey(recoverPassword, mnemonic, int(account), int(index)) + 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) + var resp keys.KeyOutput + err = codec.Cdc.UnmarshalJSON([]byte(body), &resp) + require.Nil(t, err, body) + + addr1Bech32 := resp.Address + _, err = sdk.AccAddressFromBech32(addr1Bech32) + require.NoError(t, err, "Failed to return a correct bech32 address") +} + +// GET /keys/{name} Get a certain locally stored account +func getKey(t *testing.T, port, name string) keys.KeyOutput { + res, body := Request(t, port, "GET", fmt.Sprintf("/keys/%s", name), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var resp keys.KeyOutput + err := cdc.UnmarshalJSON([]byte(body), &resp) + require.Nil(t, err) + return resp +} + +// PUT /keys/{name} Update the password for this account in the KMS +func updateKey(t *testing.T, port, name, oldPassword, newPassword string, fail bool) { + kr := clientkeys.NewUpdateKeyReq(oldPassword, newPassword) + req, err := cdc.MarshalJSON(kr) + require.NoError(t, err) + keyEndpoint := fmt.Sprintf("/keys/%s", name) + res, body := Request(t, port, "PUT", keyEndpoint, req) + if fail { + require.Equal(t, http.StatusUnauthorized, res.StatusCode, body) + return + } + require.Equal(t, http.StatusOK, res.StatusCode, body) +} + +// DELETE /keys/{name} Remove an account +func deleteKey(t *testing.T, port, name, password string) { + dk := clientkeys.NewDeleteKeyReq(password) + req, err := cdc.MarshalJSON(dk) + require.NoError(t, err) + keyEndpoint := fmt.Sprintf("/keys/%s", name) + res, body := Request(t, port, "DELETE", keyEndpoint, req) + require.Equal(t, http.StatusOK, res.StatusCode, body) +} + +// GET /auth/accounts/{address} Get the account information on blockchain +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) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var acc auth.Account + err := cdc.UnmarshalJSON([]byte(body), &acc) + require.Nil(t, err) + return acc +} + +// ---------------------------------------------------------------------- +// ICS 20 - Tokens +// ---------------------------------------------------------------------- + +// POST /tx/broadcast Send a signed Tx +func doBroadcast(t *testing.T, port string, tx auth.StdTx) (*http.Response, string) { + txReq := clienttx.BroadcastReq{Tx: tx, Mode: "block"} + + req, err := cdc.MarshalJSON(txReq) + require.Nil(t, err) + + return Request(t, port, "POST", "/txs", req) +} + +// doTransfer performs a balance transfer with auto gas calculation. It also signs +// the tx and broadcasts it. +func doTransfer( + t *testing.T, port, seed, name, memo, pwd string, addr sdk.AccAddress, fees sdk.Coins, +) (sdk.AccAddress, sdk.TxResponse) { + + resp, body, recvAddr := doTransferWithGas( + t, port, seed, name, memo, pwd, addr, "", 1.0, false, true, fees, + ) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + var txResp sdk.TxResponse + err := cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return recvAddr, txResp +} + +// doTransferWithGas performs a balance transfer with a specified gas value. The +// broadcast parameter determines if the tx should only be generated or also +// signed and broadcasted. The sending account's number and sequence are +// determined prior to generating the tx. +func doTransferWithGas( + t *testing.T, port, seed, name, memo, pwd string, addr sdk.AccAddress, + gas string, gasAdjustment float64, simulate, broadcast bool, fees sdk.Coins, +) (resp *http.Response, body string, receiveAddr sdk.AccAddress) { + + // create receive address + kb := crkeys.NewInMemory() + + receiveInfo, _, err := kb.CreateMnemonic( + "receive_address", crkeys.English, client.DefaultKeyPass, crkeys.SigningAlgo("secp256k1"), + ) + require.Nil(t, err) + + receiveAddr = sdk.AccAddress(receiveInfo.GetPubKey().Address()) + acc := getAccount(t, port, addr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + chainID := viper.GetString(client.FlagChainID) + + from := addr.String() + baseReq := rest.NewBaseReq( + from, memo, chainID, gas, fmt.Sprintf("%f", gasAdjustment), accnum, sequence, fees, nil, simulate, + ) + + sr := bankrest.SendReq{ + Amount: sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 1)}, + BaseReq: baseReq, + } + + req, err := cdc.MarshalJSON(sr) + require.NoError(t, err) + + // generate tx + resp, body = Request(t, port, "POST", fmt.Sprintf("/bank/accounts/%s/transfers", receiveAddr), req) + if !broadcast { + return resp, body, receiveAddr + } + + // sign and broadcast + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, gasAdjustment, simulate) + return resp, body, receiveAddr +} + +// doTransferWithGasAccAuto is similar to doTransferWithGas except that it +// automatically determines the account's number and sequence when generating the +// tx. +func doTransferWithGasAccAuto( + t *testing.T, port, seed, name, memo, pwd string, addr sdk.AccAddress, + gas string, gasAdjustment float64, simulate, broadcast bool, fees sdk.Coins, +) (resp *http.Response, body string, receiveAddr sdk.AccAddress) { + + // create receive address + kb := crkeys.NewInMemory() + acc := getAccount(t, port, addr) + + receiveInfo, _, err := kb.CreateMnemonic( + "receive_address", crkeys.English, client.DefaultKeyPass, crkeys.SigningAlgo("secp256k1"), + ) + require.Nil(t, err) + + receiveAddr = sdk.AccAddress(receiveInfo.GetPubKey().Address()) + chainID := viper.GetString(client.FlagChainID) + + from := addr.String() + baseReq := rest.NewBaseReq( + from, memo, chainID, gas, fmt.Sprintf("%f", gasAdjustment), 0, 0, fees, nil, simulate, + ) + + sr := bankrest.SendReq{ + Amount: sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 1)}, + BaseReq: baseReq, + } + + req, err := cdc.MarshalJSON(sr) + require.NoError(t, err) + + resp, body = Request(t, port, "POST", fmt.Sprintf("/bank/accounts/%s/transfers", receiveAddr), req) + if !broadcast { + return resp, body, receiveAddr + } + + // sign and broadcast + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, gasAdjustment, simulate) + return resp, body, receiveAddr +} + +// signAndBroadcastGenTx accepts a successfully generated unsigned tx, signs it, +// and broadcasts it. +func signAndBroadcastGenTx( + t *testing.T, port, name, pwd, genTx string, acc auth.Account, gasAdjustment float64, simulate bool, +) (resp *http.Response, body string) { + + chainID := viper.GetString(client.FlagChainID) + + var tx auth.StdTx + err := cdc.UnmarshalJSON([]byte(genTx), &tx) + require.Nil(t, err) + + txbldr := txbuilder.NewTxBuilder( + utils.GetTxEncoder(cdc), + acc.GetAccountNumber(), + acc.GetSequence(), + tx.Fee.Gas, + gasAdjustment, + simulate, + chainID, + tx.Memo, + tx.Fee.Amount, + nil, + ) + + signedTx, err := txbldr.SignStdTx(name, pwd, tx, false) + require.NoError(t, err) + + return doBroadcast(t, port, signedTx) +} + +// ---------------------------------------------------------------------- +// ICS 21 - Stake +// ---------------------------------------------------------------------- + +// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation +func doDelegate( + t *testing.T, port, name, pwd string, delAddr sdk.AccAddress, + valAddr sdk.ValAddress, amount sdk.Int, fees sdk.Coins, +) sdk.TxResponse { + + acc := getAccount(t, port, delAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + chainID := viper.GetString(client.FlagChainID) + from := acc.GetAddress().String() + + baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) + msg := stakingrest.DelegateRequest{ + BaseReq: baseReq, + DelegatorAddress: delAddr, + ValidatorAddress: valAddr, + Amount: sdk.NewCoin(sdk.DefaultBondDenom, amount), + } + + req, err := cdc.MarshalJSON(msg) + require.NoError(t, err) + + resp, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/delegations", delAddr.String()), req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + // sign and broadcast + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + var txResp sdk.TxResponse + err = cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp +} + +// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation +func doUndelegate( + t *testing.T, port, name, pwd string, delAddr sdk.AccAddress, + valAddr sdk.ValAddress, amount sdk.Int, fees sdk.Coins, +) sdk.TxResponse { + + acc := getAccount(t, port, delAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + chainID := viper.GetString(client.FlagChainID) + from := acc.GetAddress().String() + + baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) + msg := stakingrest.UndelegateRequest{ + BaseReq: baseReq, + DelegatorAddress: delAddr, + ValidatorAddress: valAddr, + Amount: sdk.NewCoin(sdk.DefaultBondDenom, amount), + } + + req, err := cdc.MarshalJSON(msg) + require.NoError(t, err) + + resp, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/unbonding_delegations", delAddr), req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + var txResp sdk.TxResponse + err = cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp +} + +// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation +func doBeginRedelegation( + t *testing.T, port, name, pwd string, delAddr sdk.AccAddress, valSrcAddr, + valDstAddr sdk.ValAddress, amount sdk.Int, fees sdk.Coins, +) sdk.TxResponse { + + acc := getAccount(t, port, delAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + chainID := viper.GetString(client.FlagChainID) + from := acc.GetAddress().String() + + baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) + msg := stakingrest.RedelegateRequest{ + BaseReq: baseReq, + DelegatorAddress: delAddr, + ValidatorSrcAddress: valSrcAddr, + ValidatorDstAddress: valDstAddr, + Amount: sdk.NewCoin(sdk.DefaultBondDenom, amount), + } + + req, err := cdc.MarshalJSON(msg) + require.NoError(t, err) + + resp, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/redelegations", delAddr), req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + var txResp sdk.TxResponse + err = cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp +} + +// GET /staking/delegators/{delegatorAddr}/delegations Get all delegations from a delegator +func getDelegatorDelegations(t *testing.T, port string, delegatorAddr sdk.AccAddress) staking.DelegationResponses { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/delegations", delegatorAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var dels staking.DelegationResponses + + err := cdc.UnmarshalJSON([]byte(body), &dels) + require.Nil(t, err) + + return dels +} + +// GET /staking/delegators/{delegatorAddr}/unbonding_delegations Get all unbonding delegations from a delegator +func getDelegatorUnbondingDelegations(t *testing.T, port string, delegatorAddr sdk.AccAddress) []staking.UnbondingDelegation { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/unbonding_delegations", delegatorAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var ubds []staking.UnbondingDelegation + + err := cdc.UnmarshalJSON([]byte(body), &ubds) + require.Nil(t, err) + + return ubds +} + +// GET /staking/redelegations?delegator=0xdeadbeef&validator_from=0xdeadbeef&validator_to=0xdeadbeef& Get redelegations filters by params passed in +func getRedelegations(t *testing.T, port string, delegatorAddr sdk.AccAddress, srcValidatorAddr sdk.ValAddress, dstValidatorAddr sdk.ValAddress) staking.RedelegationResponses { + var res *http.Response + var body string + endpoint := "/staking/redelegations?" + if !delegatorAddr.Empty() { + endpoint += fmt.Sprintf("delegator=%s&", delegatorAddr) + } + if !srcValidatorAddr.Empty() { + endpoint += fmt.Sprintf("validator_from=%s&", srcValidatorAddr) + } + if !dstValidatorAddr.Empty() { + endpoint += fmt.Sprintf("validator_to=%s&", dstValidatorAddr) + } + + res, body = Request(t, port, "GET", endpoint, nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var redels staking.RedelegationResponses + err := cdc.UnmarshalJSON([]byte(body), &redels) + require.Nil(t, err) + + return redels +} + +// GET /staking/delegators/{delegatorAddr}/validators Query all validators that a delegator is bonded to +func getDelegatorValidators(t *testing.T, port string, delegatorAddr sdk.AccAddress) []staking.Validator { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/validators", delegatorAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var bondedValidators []staking.Validator + + err := cdc.UnmarshalJSON([]byte(body), &bondedValidators) + require.Nil(t, err) + + return bondedValidators +} + +// GET /staking/delegators/{delegatorAddr}/validators/{validatorAddr} Query a validator that a delegator is bonded to +func getDelegatorValidator(t *testing.T, port string, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) staking.Validator { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/validators/%s", delegatorAddr, validatorAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var bondedValidator staking.Validator + err := cdc.UnmarshalJSON([]byte(body), &bondedValidator) + require.Nil(t, err) + + return bondedValidator +} + +// 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) []sdk.TxResponse { + var res *http.Response + var body string + + if len(query) > 0 { + res, body = Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/txs?type=%s", delegatorAddr, query), nil) + } else { + res, body = Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/txs", delegatorAddr), nil) + } + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var txs []sdk.TxResponse + + err := cdc.UnmarshalJSON([]byte(body), &txs) + require.Nil(t, err) + + return txs +} + +// GET /staking/delegators/{delegatorAddr}/delegations/{validatorAddr} Query the current delegation between a delegator and a validator +func getDelegation(t *testing.T, port string, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) staking.DelegationResp { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/delegations/%s", delegatorAddr, validatorAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var bond staking.DelegationResp + err := cdc.UnmarshalJSON([]byte(body), &bond) + require.Nil(t, err) + + return bond +} + +// GET /staking/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr} Query all unbonding delegations between a delegator and a validator +func getUnbondingDelegation(t *testing.T, port string, delegatorAddr sdk.AccAddress, + validatorAddr sdk.ValAddress) staking.UnbondingDelegation { + + res, body := Request(t, port, "GET", + fmt.Sprintf("/staking/delegators/%s/unbonding_delegations/%s", + delegatorAddr, validatorAddr), nil) + + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var unbond staking.UnbondingDelegation + err := cdc.UnmarshalJSON([]byte(body), &unbond) + require.Nil(t, err) + + return unbond +} + +// GET /staking/validators Get all validator candidates +func getValidators(t *testing.T, port string) []staking.Validator { + res, body := Request(t, port, "GET", "/staking/validators", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var validators []staking.Validator + err := cdc.UnmarshalJSON([]byte(body), &validators) + require.Nil(t, err) + + return validators +} + +// GET /staking/validators/{validatorAddr} Query the information from a single validator +func getValidator(t *testing.T, port string, validatorAddr sdk.ValAddress) staking.Validator { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/validators/%s", validatorAddr.String()), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var validator staking.Validator + err := cdc.UnmarshalJSON([]byte(body), &validator) + require.Nil(t, err) + + return validator +} + +// GET /staking/validators/{validatorAddr}/delegations Get all delegations from a validator +func getValidatorDelegations(t *testing.T, port string, validatorAddr sdk.ValAddress) []staking.Delegation { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/validators/%s/delegations", validatorAddr.String()), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var delegations []staking.Delegation + err := cdc.UnmarshalJSON([]byte(body), &delegations) + require.Nil(t, err) + + return delegations +} + +// GET /staking/validators/{validatorAddr}/unbonding_delegations Get all unbonding delegations from a validator +func getValidatorUnbondingDelegations(t *testing.T, port string, validatorAddr sdk.ValAddress) []staking.UnbondingDelegation { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/validators/%s/unbonding_delegations", validatorAddr.String()), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var ubds []staking.UnbondingDelegation + err := cdc.UnmarshalJSON([]byte(body), &ubds) + require.Nil(t, err) + + return ubds +} + +// GET /staking/pool Get the current state of the staking pool +func getStakingPool(t *testing.T, port string) staking.Pool { + res, body := Request(t, port, "GET", "/staking/pool", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NotNil(t, body) + var pool staking.Pool + err := cdc.UnmarshalJSON([]byte(body), &pool) + require.Nil(t, err) + return pool +} + +// GET /staking/parameters Get the current staking parameter values +func getStakingParams(t *testing.T, port string) staking.Params { + res, body := Request(t, port, "GET", "/staking/parameters", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var params staking.Params + err := cdc.UnmarshalJSON([]byte(body), ¶ms) + require.Nil(t, err) + return params +} + +// ---------------------------------------------------------------------- +// ICS 22 - Gov +// ---------------------------------------------------------------------- +// POST /gov/proposals Submit a proposal +func doSubmitProposal( + t *testing.T, port, seed, name, pwd string, proposerAddr sdk.AccAddress, + amount sdk.Int, fees sdk.Coins, +) sdk.TxResponse { + + acc := getAccount(t, port, proposerAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + chainID := viper.GetString(client.FlagChainID) + from := acc.GetAddress().String() + + baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) + pr := govrest.PostProposalReq{ + Title: "Test", + Description: "test", + ProposalType: "Text", + Proposer: proposerAddr, + InitialDeposit: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, amount)}, + BaseReq: baseReq, + } + + req, err := cdc.MarshalJSON(pr) + require.NoError(t, err) + + // submitproposal + resp, body := Request(t, port, "POST", "/gov/proposals", req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + var txResp sdk.TxResponse + err = cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp +} + +func doSubmitParamChangeProposal( + t *testing.T, port, seed, name, pwd string, proposerAddr sdk.AccAddress, + amount sdk.Int, fees sdk.Coins, +) sdk.TxResponse { + + acc := getAccount(t, port, proposerAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + chainID := viper.GetString(client.FlagChainID) + from := acc.GetAddress().String() + + baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) + pr := paramscutils.ParamChangeProposalReq{ + BaseReq: baseReq, + Title: "Test", + Description: "test", + Proposer: proposerAddr, + Deposit: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, amount)}, + Changes: paramscutils.ParamChangesJSON{ + paramscutils.NewParamChangeJSON("staking", "MaxValidators", "", []byte(`105`)), + }, + } + + req, err := cdc.MarshalJSON(pr) + require.NoError(t, err) + + resp, body := Request(t, port, "POST", "/gov/proposals/param_change", req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + var txResp sdk.TxResponse + err = cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp +} + +// GET /gov/proposals Query proposals +func getProposalsAll(t *testing.T, port string) []gov.Proposal { + res, body := Request(t, port, "GET", "/gov/proposals", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var proposals []gov.Proposal + err := cdc.UnmarshalJSON([]byte(body), &proposals) + require.Nil(t, err) + return proposals +} + +// GET /gov/proposals?depositor=%s Query proposals +func getProposalsFilterDepositor(t *testing.T, port string, depositorAddr sdk.AccAddress) []gov.Proposal { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals?depositor=%s", depositorAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var proposals []gov.Proposal + err := cdc.UnmarshalJSON([]byte(body), &proposals) + require.Nil(t, err) + return proposals +} + +// GET /gov/proposals?voter=%s Query proposals +func getProposalsFilterVoter(t *testing.T, port string, voterAddr sdk.AccAddress) []gov.Proposal { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals?voter=%s", voterAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var proposals []gov.Proposal + err := cdc.UnmarshalJSON([]byte(body), &proposals) + require.Nil(t, err) + return proposals +} + +// GET /gov/proposals?depositor=%s&voter=%s Query proposals +func getProposalsFilterVoterDepositor(t *testing.T, port string, voterAddr, depositorAddr sdk.AccAddress) []gov.Proposal { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals?depositor=%s&voter=%s", depositorAddr, voterAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var proposals []gov.Proposal + err := cdc.UnmarshalJSON([]byte(body), &proposals) + require.Nil(t, err) + return proposals +} + +// GET /gov/proposals?status=%s Query proposals +func getProposalsFilterStatus(t *testing.T, port string, status gov.ProposalStatus) []gov.Proposal { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals?status=%s", status), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var proposals []gov.Proposal + err := cdc.UnmarshalJSON([]byte(body), &proposals) + require.Nil(t, err) + return proposals +} + +// POST /gov/proposals/{proposalId}/deposits Deposit tokens to a proposal +func doDeposit( + t *testing.T, port, seed, name, pwd string, proposerAddr sdk.AccAddress, + proposalID uint64, amount sdk.Int, fees sdk.Coins, +) sdk.TxResponse { + + acc := getAccount(t, port, proposerAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + chainID := viper.GetString(client.FlagChainID) + from := acc.GetAddress().String() + + baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) + dr := govrest.DepositReq{ + Depositor: proposerAddr, + Amount: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, amount)}, + BaseReq: baseReq, + } + + req, err := cdc.MarshalJSON(dr) + require.NoError(t, err) + + resp, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + var txResp sdk.TxResponse + err = cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp +} + +// GET /gov/proposals/{proposalId}/deposits Query deposits +func getDeposits(t *testing.T, port string, proposalID uint64) []gov.Deposit { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var deposits []gov.Deposit + err := cdc.UnmarshalJSON([]byte(body), &deposits) + require.Nil(t, err) + return deposits +} + +// GET /gov/proposals/{proposalId}/tally Get a proposal's tally result at the current time +func getTally(t *testing.T, port string, proposalID uint64) gov.TallyResult { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/tally", proposalID), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var tally gov.TallyResult + err := cdc.UnmarshalJSON([]byte(body), &tally) + require.Nil(t, err) + return tally +} + +// POST /gov/proposals/{proposalId}/votes Vote a proposal +func doVote( + t *testing.T, port, seed, name, pwd string, proposerAddr sdk.AccAddress, + proposalID uint64, option string, fees sdk.Coins, +) sdk.TxResponse { + + // get the account to get the sequence + acc := getAccount(t, port, proposerAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + chainID := viper.GetString(client.FlagChainID) + from := acc.GetAddress().String() + + baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) + vr := govrest.VoteReq{ + Voter: proposerAddr, + Option: option, + BaseReq: baseReq, + } + + req, err := cdc.MarshalJSON(vr) + require.NoError(t, err) + + resp, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/votes", proposalID), req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + var txResp sdk.TxResponse + err = cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp +} + +// GET /gov/proposals/{proposalId}/votes Query voters +func getVotes(t *testing.T, port string, proposalID uint64) []gov.Vote { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/votes", proposalID), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var votes []gov.Vote + err := cdc.UnmarshalJSON([]byte(body), &votes) + require.Nil(t, err) + return votes +} + +// GET /gov/proposals/{proposalId} Query a proposal +func getProposal(t *testing.T, port string, proposalID uint64) gov.Proposal { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d", proposalID), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var proposal gov.Proposal + err := cdc.UnmarshalJSON([]byte(body), &proposal) + require.Nil(t, err) + return proposal +} + +// GET /gov/proposals/{proposalId}/deposits/{depositor} Query deposit +func getDeposit(t *testing.T, port string, proposalID uint64, depositorAddr sdk.AccAddress) gov.Deposit { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/deposits/%s", proposalID, depositorAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var deposit gov.Deposit + err := cdc.UnmarshalJSON([]byte(body), &deposit) + require.Nil(t, err) + return deposit +} + +// GET /gov/proposals/{proposalId}/votes/{voter} Query vote +func getVote(t *testing.T, port string, proposalID uint64, voterAddr sdk.AccAddress) gov.Vote { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/votes/%s", proposalID, voterAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var vote gov.Vote + err := cdc.UnmarshalJSON([]byte(body), &vote) + require.Nil(t, err) + return vote +} + +// GET /gov/proposals/{proposalId}/proposer +func getProposer(t *testing.T, port string, proposalID uint64) gcutils.Proposer { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/proposer", proposalID), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var proposer gcutils.Proposer + err := cdc.UnmarshalJSON([]byte(body), &proposer) + + require.Nil(t, err) + return proposer +} + +// GET /gov/parameters/deposit Query governance deposit parameters +func getDepositParam(t *testing.T, port string) gov.DepositParams { + res, body := Request(t, port, "GET", "/gov/parameters/deposit", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var depositParams gov.DepositParams + err := cdc.UnmarshalJSON([]byte(body), &depositParams) + require.Nil(t, err) + return depositParams +} + +// GET /gov/parameters/tallying Query governance tally parameters +func getTallyingParam(t *testing.T, port string) gov.TallyParams { + res, body := Request(t, port, "GET", "/gov/parameters/tallying", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var tallyParams gov.TallyParams + err := cdc.UnmarshalJSON([]byte(body), &tallyParams) + require.Nil(t, err) + return tallyParams +} + +// GET /gov/parameters/voting Query governance voting parameters +func getVotingParam(t *testing.T, port string) gov.VotingParams { + res, body := Request(t, port, "GET", "/gov/parameters/voting", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var votingParams gov.VotingParams + err := cdc.UnmarshalJSON([]byte(body), &votingParams) + require.Nil(t, err) + return votingParams +} + +// ---------------------------------------------------------------------- +// ICS 23 - Slashing +// ---------------------------------------------------------------------- +// GET /slashing/validators/{validatorPubKey}/signing_info Get sign info of given validator +func getSigningInfo(t *testing.T, port string, validatorPubKey string) slashing.ValidatorSigningInfo { + res, body := Request(t, port, "GET", fmt.Sprintf("/slashing/validators/%s/signing_info", validatorPubKey), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var signingInfo slashing.ValidatorSigningInfo + err := cdc.UnmarshalJSON([]byte(body), &signingInfo) + require.Nil(t, err) + + return signingInfo +} + +// ---------------------------------------------------------------------- +// ICS 23 - SlashingList +// ---------------------------------------------------------------------- +// GET /slashing/signing_infos Get sign info of all validators with pagination +func getSigningInfoList(t *testing.T, port string) []slashing.ValidatorSigningInfo { + res, body := Request(t, port, "GET", "/slashing/signing_infos?page=1&limit=1", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var signingInfo []slashing.ValidatorSigningInfo + err := cdc.UnmarshalJSON([]byte(body), &signingInfo) + require.Nil(t, err) + + return signingInfo +} + +// TODO: Test this functionality, it is not currently in any of the tests +// POST /slashing/validators/{validatorAddr}/unjail Unjail a jailed validator +func doUnjail( + t *testing.T, port, seed, name, pwd string, valAddr sdk.ValAddress, fees sdk.Coins, +) sdk.TxResponse { + + acc := getAccount(t, port, sdk.AccAddress(valAddr.Bytes())) + from := acc.GetAddress().String() + chainID := viper.GetString(client.FlagChainID) + + baseReq := rest.NewBaseReq(from, "", chainID, "", "", 1, 1, fees, nil, false) + ur := slashingrest.UnjailReq{ + BaseReq: baseReq, + } + req, err := cdc.MarshalJSON(ur) + require.NoError(t, err) + + resp, body := Request(t, port, "POST", fmt.Sprintf("/slashing/validators/%s/unjail", valAddr.String()), req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + var txResp sdk.TxResponse + err = cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp +} + +type unjailReq struct { + 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, pwd string, delegatorAddr sdk.AccAddress, fees sdk.Coins, +) 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) + from := acc.GetAddress().String() + + baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) + wr := struct { + BaseReq rest.BaseReq `json:"base_req"` + }{BaseReq: baseReq} + + req := cdc.MustMarshalJSON(wr) + + resp, body := Request(t, port, "POST", fmt.Sprintf("/distribution/delegators/%s/rewards", delegatorAddr), req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + var txResp sdk.TxResponse + err := cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp +} + +func mustParseDecCoins(dcstring string) sdk.DecCoins { + dcoins, err := sdk.ParseDecCoins(dcstring) + if err != nil { + panic(err) + } + return dcoins +} diff --git a/lcd_test/lcd_test.go b/lcd_test/lcd_test.go new file mode 100644 index 00000000..8a0ff0bf --- /dev/null +++ b/lcd_test/lcd_test.go @@ -0,0 +1,1104 @@ +package lcd_test + +import ( + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + "os" + "regexp" + "strings" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/x/mint" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" + "github.com/cosmos/cosmos-sdk/tests" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + dclcommon "github.com/cosmos/cosmos-sdk/x/distribution/client/common" + distrrest "github.com/cosmos/cosmos-sdk/x/distribution/client/rest" + disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +const ( + name1 = "test1" + memo = "LCD test tx" + pw = client.DefaultKeyPass +) + +var fees = sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 5)} + +func init() { + mintkey.BcryptSecurityParameter = 1 + version.Version = os.Getenv("VERSION") +} + +func TestVersion(t *testing.T) { + // skip the test if the VERSION environment variable has not been set + if version.Version == "" { + t.SkipNow() + } + + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}, true) + defer cleanup() + + // node info + res, body := Request(t, port, "GET", "/version", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + reg, err := regexp.Compile(`\d+\.\d+\.\d+.*`) + require.Nil(t, err) + match := reg.MatchString(body) + require.True(t, match, body, body) + + // node info + res, body = Request(t, port, "GET", "/node_version", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + reg, err = regexp.Compile(`\d+\.\d+\.\d+.*`) + require.Nil(t, err) + match = reg.MatchString(body) + require.True(t, match, body) +} + +func TestNodeStatus(t *testing.T) { + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}, true) + defer cleanup() + getNodeInfo(t, port) + getSyncStatus(t, port, false) +} + +func TestBlock(t *testing.T) { + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}, true) + defer cleanup() + getBlock(t, port, -1, false) + getBlock(t, port, 2, false) + getBlock(t, port, 100000000, true) +} + +func TestValidators(t *testing.T) { + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}, true) + defer cleanup() + resultVals := getValidatorSets(t, port, -1, false) + require.Contains(t, resultVals.Validators[0].Address.String(), "cosmosvalcons") + require.Contains(t, resultVals.Validators[0].PubKey, "cosmosvalconspub") + getValidatorSets(t, port, 2, false) + getValidatorSets(t, port, 10000000, true) +} + +func TestCoinSend(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, seed := CreateAddr(t, name1, pw, kb) + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() + + bz, err := hex.DecodeString("8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6") + require.NoError(t, err) + someFakeAddr := sdk.AccAddress(bz) + + // query empty + res, body := Request(t, port, "GET", fmt.Sprintf("/auth/accounts/%s", someFakeAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + acc := getAccount(t, port, addr) + initialBalance := acc.GetCoins() + + // create TX + receiveAddr, resultTx := doTransfer(t, port, seed, name1, memo, pw, addr, fees) + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx was committed + require.Equal(t, uint32(0), resultTx.Code) + + // query sender + acc = getAccount(t, port, addr) + coins := acc.GetCoins() + expectedBalance := initialBalance[0].Sub(fees[0]) + + require.Equal(t, sdk.DefaultBondDenom, coins[0].Denom) + require.Equal(t, expectedBalance.Amount.SubRaw(1), coins[0].Amount) + expectedBalance = coins[0] + + // query receiver + acc2 := getAccount(t, port, receiveAddr) + coins2 := acc2.GetCoins() + require.Equal(t, sdk.DefaultBondDenom, coins2[0].Denom) + require.Equal(t, int64(1), coins2[0].Amount.Int64()) + + // test failure with too little gas + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, "100", 0, false, true, fees) + require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) + require.Nil(t, err) + + // test failure with negative gas + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, "-200", 0, false, false, fees) + require.Equal(t, http.StatusBadRequest, res.StatusCode, body) + + // test failure with negative adjustment + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, "10000", -0.1, true, false, fees) + require.Equal(t, http.StatusBadRequest, res.StatusCode, body) + + // test failure with 0 gas + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, "0", 0, false, true, fees) + require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) + + // test failure with wrong adjustment + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, client.GasFlagAuto, 0.1, false, true, fees) + + require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) + + // run simulation and test success with estimated gas + res, body, _ = doTransferWithGas( + t, port, seed, name1, memo, pw, addr, "10000", 1.0, true, false, fees, + ) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var gasEstResp rest.GasEstimateResponse + require.Nil(t, cdc.UnmarshalJSON([]byte(body), &gasEstResp)) + require.NotZero(t, gasEstResp.GasEstimate) + + acc = getAccount(t, port, addr) + require.Equal(t, expectedBalance.Amount, acc.GetCoins().AmountOf(sdk.DefaultBondDenom)) + + // run successful tx + gas := fmt.Sprintf("%d", gasEstResp.GasEstimate) + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, gas, 1.0, false, true, fees) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err = cdc.UnmarshalJSON([]byte(body), &resultTx) + require.Nil(t, err) + + tests.WaitForHeight(resultTx.Height+1, port) + require.Equal(t, uint32(0), resultTx.Code) + + acc = getAccount(t, port, addr) + expectedBalance = expectedBalance.Sub(fees[0]) + require.Equal(t, expectedBalance.Amount.SubRaw(1), acc.GetCoins().AmountOf(sdk.DefaultBondDenom)) +} + +func TestCoinSendAccAuto(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, seed := CreateAddr(t, name1, pw, kb) + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() + + acc := getAccount(t, port, addr) + initialBalance := acc.GetCoins() + + // send a transfer tx without specifying account number and sequence + res, body, _ := doTransferWithGasAccAuto( + t, port, seed, name1, memo, pw, addr, "200000", 1.0, false, true, fees, + ) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + // query sender + acc = getAccount(t, port, addr) + coins := acc.GetCoins() + expectedBalance := initialBalance[0].Sub(fees[0]) + + require.Equal(t, sdk.DefaultBondDenom, coins[0].Denom) + require.Equal(t, expectedBalance.Amount.SubRaw(1), coins[0].Amount) +} + +func TestCoinMultiSendGenerateOnly(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, seed := CreateAddr(t, name1, pw, kb) + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() + + // generate only + res, body, _ := doTransferWithGas(t, port, seed, "", memo, "", addr, "200000", 1, false, false, fees) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var stdTx auth.StdTx + require.Nil(t, cdc.UnmarshalJSON([]byte(body), &stdTx)) + require.Equal(t, len(stdTx.Msgs), 1) + require.Equal(t, stdTx.GetMsgs()[0].Route(), bank.RouterKey) + require.Equal(t, stdTx.GetMsgs()[0].GetSigners(), []sdk.AccAddress{addr}) + require.Equal(t, 0, len(stdTx.Signatures)) + require.Equal(t, memo, stdTx.Memo) + require.NotZero(t, stdTx.Fee.Gas) + require.IsType(t, stdTx.GetMsgs()[0], bank.MsgSend{}) + require.Equal(t, addr, stdTx.GetMsgs()[0].(bank.MsgSend).FromAddress) +} + +func TestCoinSendGenerateSignAndBroadcast(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, seed := CreateAddr(t, name1, pw, kb) + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + + defer cleanup() + acc := getAccount(t, port, addr) + + // simulate tx + res, body, _ := doTransferWithGas( + t, port, seed, name1, memo, "", addr, client.GasFlagAuto, 1.0, true, false, fees, + ) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var gasEstResp rest.GasEstimateResponse + require.Nil(t, cdc.UnmarshalJSON([]byte(body), &gasEstResp)) + require.NotZero(t, gasEstResp.GasEstimate) + + // generate tx + gas := fmt.Sprintf("%d", gasEstResp.GasEstimate) + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, "", addr, gas, 1, false, false, fees) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var tx auth.StdTx + require.Nil(t, cdc.UnmarshalJSON([]byte(body), &tx)) + require.Equal(t, len(tx.Msgs), 1) + require.Equal(t, tx.Msgs[0].Route(), bank.RouterKey) + require.Equal(t, tx.Msgs[0].GetSigners(), []sdk.AccAddress{addr}) + require.Equal(t, 0, len(tx.Signatures)) + require.Equal(t, memo, tx.Memo) + require.NotZero(t, tx.Fee.Gas) + + gasEstimate := int64(tx.Fee.Gas) + _, body = signAndBroadcastGenTx(t, port, name1, pw, body, acc, 1.0, false) + + // check if tx was committed + var txResp sdk.TxResponse + require.Nil(t, cdc.UnmarshalJSON([]byte(body), &txResp)) + require.Equal(t, uint32(0), txResp.Code) + require.Equal(t, gasEstimate, txResp.GasWanted) +} + +func TestEncodeTx(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, seed := CreateAddr(t, name1, pw, kb) + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() + + res, body, _ := doTransferWithGas(t, port, seed, name1, memo, "", addr, "2", 1, false, false, fees) + var tx auth.StdTx + require.Nil(t, cdc.UnmarshalJSON([]byte(body), &tx)) + + encodedJSON, _ := cdc.MarshalJSON(tx) + res, body = Request(t, port, "POST", "/txs/encode", encodedJSON) + + // Make sure it came back ok, and that we can decode it back to the transaction + // 200 response. + require.Equal(t, http.StatusOK, res.StatusCode, body) + encodeResp := struct { + Tx string `json:"tx"` + }{} + + require.Nil(t, cdc.UnmarshalJSON([]byte(body), &encodeResp)) + + // verify that the base64 decodes + decodedBytes, err := base64.StdEncoding.DecodeString(encodeResp.Tx) + require.Nil(t, err) + + // check that the transaction decodes as expected + var decodedTx auth.StdTx + require.Nil(t, cdc.UnmarshalBinaryLengthPrefixed(decodedBytes, &decodedTx)) + require.Equal(t, memo, decodedTx.Memo) +} + +func TestTxs(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, seed := CreateAddr(t, name1, pw, kb) + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() + + var emptyTxs []sdk.TxResponse + txResult := getTransactions(t, port) + require.Equal(t, emptyTxs, txResult.Txs) + + // query empty + txResult = getTransactions(t, port, fmt.Sprintf("sender=%s", addr.String())) + require.Equal(t, emptyTxs, txResult.Txs) + + // also tests url decoding + txResult = getTransactions(t, port, fmt.Sprintf("sender=%s", addr.String())) + require.Equal(t, emptyTxs, txResult.Txs) + + txResult = getTransactions(t, port, fmt.Sprintf("action=submit%%20proposal&sender=%s", addr.String())) + require.Equal(t, emptyTxs, txResult.Txs) + + // create tx + receiveAddr, resultTx := doTransfer(t, port, seed, name1, memo, pw, addr, fees) + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx is queryable + tx := getTransaction(t, port, resultTx.TxHash) + require.Equal(t, resultTx.TxHash, tx.TxHash) + + // query sender + txResult = getTransactions(t, port, fmt.Sprintf("sender=%s", addr.String())) + require.Len(t, txResult.Txs, 1) + require.Equal(t, resultTx.Height, txResult.Txs[0].Height) + + // query recipient + txResult = getTransactions(t, port, fmt.Sprintf("recipient=%s", receiveAddr.String())) + require.Len(t, txResult.Txs, 1) + require.Equal(t, resultTx.Height, txResult.Txs[0].Height) + + // query transaction that doesn't exist + validTxHash := "9ADBECAAD8DACBEC3F4F535704E7CF715C765BDCEDBEF086AFEAD31BA664FB0B" + res, body := getTransactionRequest(t, port, validTxHash) + require.True(t, strings.Contains(body, validTxHash)) + require.Equal(t, http.StatusNotFound, res.StatusCode) + + // bad query string + res, body = getTransactionRequest(t, port, "badtxhash") + require.True(t, strings.Contains(body, "encoding/hex")) + require.Equal(t, http.StatusInternalServerError, res.StatusCode) +} + +func TestPoolParamsQuery(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, _ := CreateAddr(t, name1, pw, kb) + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() + + defaultParams := staking.DefaultParams() + + params := getStakingParams(t, port) + require.True(t, defaultParams.Equal(params)) + + pool := getStakingPool(t, port) + + initialPool := staking.InitialPool() + tokens := sdk.TokensFromTendermintPower(100) + freeTokens := sdk.TokensFromTendermintPower(50) + initialPool.NotBondedTokens = initialPool.NotBondedTokens.Add(tokens) + initialPool.BondedTokens = initialPool.BondedTokens.Add(tokens) // Delegate tx on GaiaAppGenState + initialPool.NotBondedTokens = initialPool.NotBondedTokens.Add(freeTokens) + + require.Equal(t, initialPool.BondedTokens, pool.BondedTokens) + + //TODO include this test once REST for distribution is online, need to include distribution tokens from inflation + // for this equality to make sense + //require.Equal(t, initialPool.NotBondedTokens, pool.NotBondedTokens) +} + +func TestValidatorsQuery(t *testing.T) { + cleanup, valPubKeys, operAddrs, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}, true) + defer cleanup() + + require.Equal(t, 1, len(valPubKeys)) + require.Equal(t, 1, len(operAddrs)) + + validators := getValidators(t, port) + require.Equal(t, 1, len(validators), fmt.Sprintf("%+v", validators)) + + // make sure all the validators were found (order unknown because sorted by operator addr) + foundVal := false + + if validators[0].ConsPubKey == valPubKeys[0] { + foundVal = true + } + + require.True(t, foundVal, "pk %v, operator %v", operAddrs[0], validators[0].OperatorAddress) +} + +func TestValidatorQuery(t *testing.T) { + cleanup, valPubKeys, operAddrs, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}, true) + defer cleanup() + require.Equal(t, 1, len(valPubKeys)) + require.Equal(t, 1, len(operAddrs)) + + validator := getValidator(t, port, operAddrs[0]) + require.Equal(t, validator.OperatorAddress, operAddrs[0], "The returned validator does not hold the correct data") +} + +func TestBonding(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, _ := CreateAddr(t, name1, pw, kb) + + cleanup, valPubKeys, operAddrs, port := InitializeTestLCD(t, 2, []sdk.AccAddress{addr}, false) + tests.WaitForHeight(1, port) + defer cleanup() + + require.Equal(t, 2, len(valPubKeys)) + require.Equal(t, 2, len(operAddrs)) + + amt := sdk.TokensFromTendermintPower(60) + amtDec := amt.ToDec() + validator := getValidator(t, port, operAddrs[0]) + + acc := getAccount(t, port, addr) + initialBalance := acc.GetCoins() + + // create bond TX + delTokens := sdk.TokensFromTendermintPower(60) + resultTx := doDelegate(t, port, name1, pw, addr, operAddrs[0], delTokens, fees) + tests.WaitForHeight(resultTx.Height+1, port) + + require.Equal(t, uint32(0), resultTx.Code) + + // query tx + txResult := getTransactions(t, port, + fmt.Sprintf("action=delegate&sender=%s", addr), + fmt.Sprintf("destination-validator=%s", operAddrs[0]), + ) + require.Len(t, txResult.Txs, 1) + require.Equal(t, resultTx.Height, txResult.Txs[0].Height) + + // verify balance + acc = getAccount(t, port, addr) + coins := acc.GetCoins() + expectedBalance := initialBalance[0].Sub(fees[0]) + require.Equal(t, expectedBalance.Amount.Sub(delTokens), coins.AmountOf(sdk.DefaultBondDenom)) + expectedBalance = coins[0] + + // query delegation + bond := getDelegation(t, port, addr, operAddrs[0]) + require.Equal(t, amtDec, bond.Shares) + + delegatorDels := getDelegatorDelegations(t, port, addr) + require.Len(t, delegatorDels, 1) + require.Equal(t, amtDec, delegatorDels[0].Shares) + + // query all delegations to validator + bonds := getValidatorDelegations(t, port, operAddrs[0]) + require.Len(t, bonds, 2) + + bondedValidators := getDelegatorValidators(t, port, addr) + require.Len(t, bondedValidators, 1) + require.Equal(t, operAddrs[0], bondedValidators[0].OperatorAddress) + require.Equal(t, validator.DelegatorShares.Add(amtDec).String(), bondedValidators[0].DelegatorShares.String()) + + bondedValidator := getDelegatorValidator(t, port, addr, operAddrs[0]) + require.Equal(t, operAddrs[0], bondedValidator.OperatorAddress) + + // testing unbonding + unbondingTokens := sdk.TokensFromTendermintPower(30) + resultTx = doUndelegate(t, port, name1, pw, addr, operAddrs[0], unbondingTokens, fees) + tests.WaitForHeight(resultTx.Height+1, port) + + require.Equal(t, uint32(0), resultTx.Code) + + // sender should have not received any coins as the unbonding has only just begun + acc = getAccount(t, port, addr) + coins = acc.GetCoins() + expectedBalance = expectedBalance.Sub(fees[0]) + require.True(t, + expectedBalance.Amount.LT(coins.AmountOf(sdk.DefaultBondDenom)) || + expectedBalance.Amount.Equal(coins.AmountOf(sdk.DefaultBondDenom)), + "should get tokens back from automatic withdrawal after an unbonding delegation", + ) + expectedBalance = coins[0] + + // query tx + txResult = getTransactions(t, port, + fmt.Sprintf("action=begin_unbonding&sender=%s", addr), + fmt.Sprintf("source-validator=%s", operAddrs[0]), + ) + require.Len(t, txResult.Txs, 1) + require.Equal(t, resultTx.Height, txResult.Txs[0].Height) + + ubd := getUnbondingDelegation(t, port, addr, operAddrs[0]) + require.Len(t, ubd.Entries, 1) + require.Equal(t, delTokens.QuoRaw(2), ubd.Entries[0].Balance) + + // test redelegation + rdTokens := sdk.TokensFromTendermintPower(30) + resultTx = doBeginRedelegation(t, port, name1, pw, addr, operAddrs[0], operAddrs[1], rdTokens, fees) + require.Equal(t, uint32(0), resultTx.Code) + tests.WaitForHeight(resultTx.Height+1, port) + + // query delegations, unbondings and redelegations from validator and delegator + delegatorDels = getDelegatorDelegations(t, port, addr) + require.Len(t, delegatorDels, 1) + require.Equal(t, operAddrs[1], delegatorDels[0].ValidatorAddress) + + // TODO uncomment once all validators actually sign in the lcd tests + //validator2 := getValidator(t, port, operAddrs[1]) + //delTokensAfterRedelegation := validator2.ShareTokens(delegatorDels[0].GetShares()) + //require.Equal(t, rdTokens.ToDec(), delTokensAfterRedelegation) + + // verify balance after paying fees + acc = getAccount(t, port, addr) + expectedBalance = expectedBalance.Sub(fees[0]) + require.True(t, + expectedBalance.Amount.LT(coins.AmountOf(sdk.DefaultBondDenom)) || + expectedBalance.Amount.Equal(coins.AmountOf(sdk.DefaultBondDenom)), + "should get tokens back from automatic withdrawal after an unbonding delegation", + ) + + // query tx + txResult = getTransactions(t, port, + fmt.Sprintf("action=begin_redelegate&sender=%s", addr), + fmt.Sprintf("source-validator=%s", operAddrs[0]), + fmt.Sprintf("destination-validator=%s", operAddrs[1]), + ) + require.Len(t, txResult.Txs, 1) + require.Equal(t, resultTx.Height, txResult.Txs[0].Height) + + redelegation := getRedelegations(t, port, addr, operAddrs[0], operAddrs[1]) + require.Len(t, redelegation, 1) + require.Len(t, redelegation[0].Entries, 1) + + delegatorUbds := getDelegatorUnbondingDelegations(t, port, addr) + require.Len(t, delegatorUbds, 1) + require.Len(t, delegatorUbds[0].Entries, 1) + require.Equal(t, rdTokens, delegatorUbds[0].Entries[0].Balance) + + delegatorReds := getRedelegations(t, port, addr, nil, nil) + require.Len(t, delegatorReds, 1) + require.Len(t, delegatorReds[0].Entries, 1) + + validatorUbds := getValidatorUnbondingDelegations(t, port, operAddrs[0]) + require.Len(t, validatorUbds, 1) + require.Len(t, validatorUbds[0].Entries, 1) + require.Equal(t, rdTokens, validatorUbds[0].Entries[0].Balance) + + validatorReds := getRedelegations(t, port, nil, operAddrs[0], nil) + require.Len(t, validatorReds, 1) + require.Len(t, validatorReds[0].Entries, 1) + + // TODO Undonding status not currently implemented + // require.Equal(t, sdk.Unbonding, bondedValidators[0].Status) + + // query txs + txs := getBondingTxs(t, port, addr, "") + require.Len(t, txs, 3, "All Txs found") + + txs = getBondingTxs(t, port, addr, "bond") + require.Len(t, txs, 1, "All bonding txs found") + + txs = getBondingTxs(t, port, addr, "unbond") + require.Len(t, txs, 1, "All unbonding txs found") + + txs = getBondingTxs(t, port, addr, "redelegate") + require.Len(t, txs, 1, "All redelegation txs found") +} + +func TestSubmitProposal(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, seed := CreateAddr(t, name1, pw, kb) + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() + + acc := getAccount(t, port, addr) + initialBalance := acc.GetCoins() + + // create SubmitProposal TX + proposalTokens := sdk.TokensFromTendermintPower(5) + resultTx := doSubmitProposal(t, port, seed, name1, pw, addr, proposalTokens, fees) + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx was committed + require.Equal(t, uint32(0), resultTx.Code) + + var proposalID uint64 + bz, err := hex.DecodeString(resultTx.Data) + require.NoError(t, err) + cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID) + + // verify balance + acc = getAccount(t, port, addr) + expectedBalance := initialBalance[0].Sub(fees[0]) + require.Equal(t, expectedBalance.Amount.Sub(proposalTokens), acc.GetCoins().AmountOf(sdk.DefaultBondDenom)) + + // query proposal + proposal := getProposal(t, port, proposalID) + require.Equal(t, "Test", proposal.GetTitle()) + + proposer := getProposer(t, port, proposalID) + require.Equal(t, addr.String(), proposer.Proposer) + require.Equal(t, proposalID, proposer.ProposalID) +} + +func TestSubmitParamChangeProposal(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, seed := CreateAddr(t, name1, pw, kb) + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() + + acc := getAccount(t, port, addr) + initialBalance := acc.GetCoins() + + // create proposal tx + proposalTokens := sdk.TokensFromTendermintPower(5) + resultTx := doSubmitParamChangeProposal(t, port, seed, name1, pw, addr, proposalTokens, fees) + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx was committed + require.Equal(t, uint32(0), resultTx.Code) + + var proposalID uint64 + bz, err := hex.DecodeString(resultTx.Data) + require.NoError(t, err) + cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID) + + // verify balance + acc = getAccount(t, port, addr) + expectedBalance := initialBalance[0].Sub(fees[0]) + require.Equal(t, expectedBalance.Amount.Sub(proposalTokens), acc.GetCoins().AmountOf(sdk.DefaultBondDenom)) + + // query proposal + proposal := getProposal(t, port, proposalID) + require.Equal(t, "Test", proposal.GetTitle()) + + proposer := getProposer(t, port, proposalID) + require.Equal(t, addr.String(), proposer.Proposer) + require.Equal(t, proposalID, proposer.ProposalID) +} + +func TestDeposit(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, seed := CreateAddr(t, name1, pw, kb) + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() + + acc := getAccount(t, port, addr) + initialBalance := acc.GetCoins() + + // create SubmitProposal TX + proposalTokens := sdk.TokensFromTendermintPower(5) + resultTx := doSubmitProposal(t, port, seed, name1, pw, addr, proposalTokens, fees) + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx was committed + require.Equal(t, uint32(0), resultTx.Code) + + var proposalID uint64 + bz, err := hex.DecodeString(resultTx.Data) + require.NoError(t, err) + cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID) + + // verify balance + acc = getAccount(t, port, addr) + coins := acc.GetCoins() + expectedBalance := initialBalance[0].Sub(fees[0]) + require.Equal(t, expectedBalance.Amount.Sub(proposalTokens), coins.AmountOf(sdk.DefaultBondDenom)) + expectedBalance = coins[0] + + // query proposal + proposal := getProposal(t, port, proposalID) + require.Equal(t, "Test", proposal.GetTitle()) + + // create SubmitProposal TX + depositTokens := sdk.TokensFromTendermintPower(5) + resultTx = doDeposit(t, port, seed, name1, pw, addr, proposalID, depositTokens, fees) + tests.WaitForHeight(resultTx.Height+1, port) + + // verify balance after deposit and fee + acc = getAccount(t, port, addr) + expectedBalance = expectedBalance.Sub(fees[0]) + require.Equal(t, expectedBalance.Amount.Sub(depositTokens), acc.GetCoins().AmountOf(sdk.DefaultBondDenom)) + + // query tx + txResult := getTransactions(t, port, fmt.Sprintf("action=deposit&sender=%s", addr)) + require.Len(t, txResult.Txs, 1) + require.Equal(t, resultTx.Height, txResult.Txs[0].Height) + + // query proposal + totalCoins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(10))} + proposal = getProposal(t, port, proposalID) + require.True(t, proposal.TotalDeposit.IsEqual(totalCoins)) + + // query deposit + deposit := getDeposit(t, port, proposalID, addr) + require.True(t, deposit.Amount.IsEqual(totalCoins)) +} + +func TestVote(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, seed := CreateAddr(t, name1, pw, kb) + cleanup, _, operAddrs, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() + + acc := getAccount(t, port, addr) + initialBalance := acc.GetCoins() + + // create SubmitProposal TX + proposalTokens := sdk.TokensFromTendermintPower(10) + resultTx := doSubmitProposal(t, port, seed, name1, pw, addr, proposalTokens, fees) + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx was committed + require.Equal(t, uint32(0), resultTx.Code) + + var proposalID uint64 + bz, err := hex.DecodeString(resultTx.Data) + require.NoError(t, err) + cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID) + + // verify balance + acc = getAccount(t, port, addr) + coins := acc.GetCoins() + expectedBalance := initialBalance[0].Sub(fees[0]) + require.Equal(t, expectedBalance.Amount.Sub(proposalTokens), coins.AmountOf(sdk.DefaultBondDenom)) + expectedBalance = coins[0] + + // query proposal + proposal := getProposal(t, port, proposalID) + require.Equal(t, "Test", proposal.GetTitle()) + require.Equal(t, gov.StatusVotingPeriod, proposal.Status) + + // vote + resultTx = doVote(t, port, seed, name1, pw, addr, proposalID, "Yes", fees) + tests.WaitForHeight(resultTx.Height+1, port) + + // verify balance after vote and fee + acc = getAccount(t, port, addr) + coins = acc.GetCoins() + expectedBalance = expectedBalance.Sub(fees[0]) + require.Equal(t, expectedBalance.Amount, coins.AmountOf(sdk.DefaultBondDenom)) + expectedBalance = coins[0] + + // query tx + txResult := getTransactions(t, port, fmt.Sprintf("action=vote&sender=%s", addr)) + require.Len(t, txResult.Txs, 1) + require.Equal(t, resultTx.Height, txResult.Txs[0].Height) + + vote := getVote(t, port, proposalID, addr) + require.Equal(t, proposalID, vote.ProposalID) + require.Equal(t, gov.OptionYes, vote.Option) + + tally := getTally(t, port, proposalID) + require.Equal(t, sdk.ZeroInt(), tally.Yes, "tally should be 0 as the address is not bonded") + + // create bond TX + delTokens := sdk.TokensFromTendermintPower(60) + resultTx = doDelegate(t, port, name1, pw, addr, operAddrs[0], delTokens, fees) + tests.WaitForHeight(resultTx.Height+1, port) + + // verify balance + acc = getAccount(t, port, addr) + coins = acc.GetCoins() + expectedBalance = expectedBalance.Sub(fees[0]) + require.Equal(t, expectedBalance.Amount.Sub(delTokens), coins.AmountOf(sdk.DefaultBondDenom)) + expectedBalance = coins[0] + + tally = getTally(t, port, proposalID) + require.Equal(t, delTokens, tally.Yes, "tally should be equal to the amount delegated") + + // change vote option + resultTx = doVote(t, port, seed, name1, pw, addr, proposalID, "No", fees) + tests.WaitForHeight(resultTx.Height+1, port) + + // verify balance + acc = getAccount(t, port, addr) + expectedBalance = expectedBalance.Sub(fees[0]) + require.Equal(t, expectedBalance.Amount, acc.GetCoins().AmountOf(sdk.DefaultBondDenom)) + + tally = getTally(t, port, proposalID) + require.Equal(t, sdk.ZeroInt(), tally.Yes, "tally should be 0 the user changed the option") + require.Equal(t, delTokens, tally.No, "tally should be equal to the amount delegated") +} + +func TestUnjail(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, _ := CreateAddr(t, name1, pw, kb) + cleanup, valPubKeys, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() + + // NOTE: any less than this and it fails + tests.WaitForHeight(3, port) + pkString, _ := sdk.Bech32ifyConsPub(valPubKeys[0]) + signingInfo := getSigningInfo(t, port, pkString) + tests.WaitForHeight(4, port) + require.Equal(t, true, signingInfo.IndexOffset > 0) + require.Equal(t, time.Unix(0, 0).UTC(), signingInfo.JailedUntil) + require.Equal(t, true, signingInfo.MissedBlocksCounter == 0) + signingInfoList := getSigningInfoList(t, port) + require.NotZero(t, len(signingInfoList)) +} + +func TestProposalsQuery(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addrs, seeds, names, passwords := CreateAddrs(t, kb, 2) + + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addrs[0], addrs[1]}, true) + defer cleanup() + + depositParam := getDepositParam(t, port) + halfMinDeposit := depositParam.MinDeposit.AmountOf(sdk.DefaultBondDenom).QuoRaw(2) + getVotingParam(t, port) + getTallyingParam(t, port) + + // Addr1 proposes (and deposits) proposals #1 and #2 + resultTx := doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], halfMinDeposit, fees) + var proposalID1 uint64 + bz, err := hex.DecodeString(resultTx.Data) + require.NoError(t, err) + cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID1) + tests.WaitForHeight(resultTx.Height+1, port) + + resultTx = doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], halfMinDeposit, fees) + var proposalID2 uint64 + bz, err = hex.DecodeString(resultTx.Data) + require.NoError(t, err) + cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID2) + tests.WaitForHeight(resultTx.Height+1, port) + + // Addr2 proposes (and deposits) proposals #3 + resultTx = doSubmitProposal(t, port, seeds[1], names[1], passwords[1], addrs[1], halfMinDeposit, fees) + var proposalID3 uint64 + bz, err = hex.DecodeString(resultTx.Data) + require.NoError(t, err) + cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID3) + tests.WaitForHeight(resultTx.Height+1, port) + + // Addr2 deposits on proposals #2 & #3 + resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID2, halfMinDeposit, fees) + tests.WaitForHeight(resultTx.Height+1, port) + + resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID3, halfMinDeposit, fees) + tests.WaitForHeight(resultTx.Height+1, port) + + // check deposits match proposal and individual deposits + deposits := getDeposits(t, port, proposalID1) + require.Len(t, deposits, 1) + deposit := getDeposit(t, port, proposalID1, addrs[0]) + require.Equal(t, deposit, deposits[0]) + + deposits = getDeposits(t, port, proposalID2) + require.Len(t, deposits, 2) + deposit = getDeposit(t, port, proposalID2, addrs[0]) + require.True(t, deposit.Equals(deposits[0])) + deposit = getDeposit(t, port, proposalID2, addrs[1]) + require.True(t, deposit.Equals(deposits[1])) + + deposits = getDeposits(t, port, proposalID3) + require.Len(t, deposits, 1) + deposit = getDeposit(t, port, proposalID3, addrs[1]) + require.Equal(t, deposit, deposits[0]) + + // increasing the amount of the deposit should update the existing one + depositTokens := sdk.TokensFromTendermintPower(1) + resultTx = doDeposit(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID1, depositTokens, fees) + tests.WaitForHeight(resultTx.Height+1, port) + + deposits = getDeposits(t, port, proposalID1) + require.Len(t, deposits, 1) + + // Only proposals #1 should be in Deposit Period + proposals := getProposalsFilterStatus(t, port, gov.StatusDepositPeriod) + require.Len(t, proposals, 1) + require.Equal(t, proposalID1, proposals[0].ProposalID) + + // Only proposals #2 and #3 should be in Voting Period + proposals = getProposalsFilterStatus(t, port, gov.StatusVotingPeriod) + require.Len(t, proposals, 2) + require.Equal(t, proposalID2, proposals[0].ProposalID) + require.Equal(t, proposalID3, proposals[1].ProposalID) + + // Addr1 votes on proposals #2 & #3 + resultTx = doVote(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID2, "Yes", fees) + tests.WaitForHeight(resultTx.Height+1, port) + resultTx = doVote(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID3, "Yes", fees) + tests.WaitForHeight(resultTx.Height+1, port) + + // Addr2 votes on proposal #3 + resultTx = doVote(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID3, "Yes", fees) + tests.WaitForHeight(resultTx.Height+1, port) + + // Test query all proposals + proposals = getProposalsAll(t, port) + require.Equal(t, proposalID1, (proposals[0]).ProposalID) + require.Equal(t, proposalID2, (proposals[1]).ProposalID) + require.Equal(t, proposalID3, (proposals[2]).ProposalID) + + // Test query deposited by addr1 + proposals = getProposalsFilterDepositor(t, port, addrs[0]) + require.Equal(t, proposalID1, (proposals[0]).ProposalID) + + // Test query deposited by addr2 + proposals = getProposalsFilterDepositor(t, port, addrs[1]) + require.Equal(t, proposalID2, (proposals[0]).ProposalID) + require.Equal(t, proposalID3, (proposals[1]).ProposalID) + + // Test query voted by addr1 + proposals = getProposalsFilterVoter(t, port, addrs[0]) + require.Equal(t, proposalID2, (proposals[0]).ProposalID) + require.Equal(t, proposalID3, (proposals[1]).ProposalID) + + // Test query voted by addr2 + proposals = getProposalsFilterVoter(t, port, addrs[1]) + require.Equal(t, proposalID3, (proposals[0]).ProposalID) + + // Test query voted and deposited by addr1 + proposals = getProposalsFilterVoterDepositor(t, port, addrs[0], addrs[0]) + require.Equal(t, proposalID2, (proposals[0]).ProposalID) + + // Test query votes on Proposal 2 + votes := getVotes(t, port, proposalID2) + require.Len(t, votes, 1) + require.Equal(t, addrs[0], votes[0].Voter) + + // Test query votes on Proposal 3 + votes = getVotes(t, port, proposalID3) + require.Len(t, votes, 2) + require.True(t, addrs[0].String() == votes[0].Voter.String() || addrs[0].String() == votes[1].Voter.String()) + require.True(t, addrs[1].String() == votes[0].Voter.String() || addrs[1].String() == votes[1].Voter.String()) +} + +func TestSlashingGetParams(t *testing.T) { + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}, true) + defer cleanup() + + res, body := Request(t, port, "GET", "/slashing/parameters", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var params slashing.Params + err := cdc.UnmarshalJSON([]byte(body), ¶ms) + require.NoError(t, err) +} + +func TestDistributionGetParams(t *testing.T) { + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}, true) + defer cleanup() + + res, body := Request(t, port, "GET", "/distribution/parameters", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &dclcommon.PrettyParams{})) +} + +func TestDistributionFlow(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, seed := CreateAddr(t, name1, pw, kb) + cleanup, _, valAddrs, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() + + valAddr := valAddrs[0] + operAddr := sdk.AccAddress(valAddr) + + var rewards sdk.DecCoins + res, body := Request(t, port, "GET", fmt.Sprintf("/distribution/validators/%s/outstanding_rewards", valAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards)) + + var valDistInfo distrrest.ValidatorDistInfo + res, body = Request(t, port, "GET", "/distribution/validators/"+valAddr.String(), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &valDistInfo)) + require.Equal(t, valDistInfo.OperatorAddress.String(), sdk.AccAddress(valAddr).String()) + + // Delegate some coins + delTokens := sdk.TokensFromTendermintPower(60) + resultTx := doDelegate(t, port, name1, pw, addr, valAddr, delTokens, fees) + tests.WaitForHeight(resultTx.Height+1, port) + require.Equal(t, uint32(0), resultTx.Code) + + // send some coins + _, resultTx = doTransfer(t, port, seed, name1, memo, pw, addr, fees) + tests.WaitForHeight(resultTx.Height+5, port) + require.Equal(t, uint32(0), resultTx.Code) + + // Query outstanding rewards changed + res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/validators/%s/outstanding_rewards", valAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards)) + + // Query validator distribution info + res, body = Request(t, port, "GET", "/distribution/validators/"+valAddr.String(), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &valDistInfo)) + + // Query validator's rewards + res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/validators/%s/rewards", valAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards)) + + // Query self-delegation + res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/delegators/%s/rewards/%s", operAddr, valAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards)) + + // Query delegation + res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/delegators/%s/rewards/%s", addr, valAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards)) + + // Query delegator's rewards total + var delRewards disttypes.QueryDelegatorTotalRewardsResponse + res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/delegators/%s/rewards", operAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, json.Unmarshal([]byte(body), &delRewards)) + + // Query delegator's withdrawal address + var withdrawAddr string + res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/delegators/%s/withdraw_address", operAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &withdrawAddr)) + require.Equal(t, operAddr.String(), withdrawAddr) + + // Withdraw delegator's rewards + resultTx = doWithdrawDelegatorAllRewards(t, port, seed, name1, pw, addr, fees) + require.Equal(t, uint32(0), resultTx.Code) +} + +func TestMintingQueries(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, _ := CreateAddr(t, name1, pw, kb) + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() + + res, body := Request(t, port, "GET", "/minting/parameters", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var params mint.Params + require.NoError(t, cdc.UnmarshalJSON([]byte(body), ¶ms)) + + res, body = Request(t, port, "GET", "/minting/inflation", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var inflation sdk.Dec + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &inflation)) + + res, body = Request(t, port, "GET", "/minting/annual-provisions", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var annualProvisions sdk.Dec + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &annualProvisions)) +} + +func TestAccountBalanceQuery(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, _ := CreateAddr(t, name1, pw, kb) + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() + + bz, err := hex.DecodeString("8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6") + require.NoError(t, err) + someFakeAddr := sdk.AccAddress(bz) + + // empty account + res, body := Request(t, port, "GET", fmt.Sprintf("/auth/accounts/%s", someFakeAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.Contains(t, body, `"type":"auth/Account"`) + + // empty account balance + res, body = Request(t, port, "GET", fmt.Sprintf("/bank/balances/%s", someFakeAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.Contains(t, body, "[]") + +} diff --git a/sims.mk b/sims.mk new file mode 100644 index 00000000..4a8d7f96 --- /dev/null +++ b/sims.mk @@ -0,0 +1,61 @@ +#!/usr/bin/make -f + +######################################## +### Simulations + +SIMAPP = github.com/cosmos/gaia/app + +sim-gaia-nondeterminism: + @echo "Running nondeterminism test..." + @go test -mod=readonly $(SIMAPP) -run TestAppStateDeterminism -SimulationEnabled=true -v -timeout 10m + +sim-gaia-custom-genesis-fast: + @echo "Running custom genesis simulation..." + @echo "By default, ${HOME}/.gaiad/config/genesis.json will be used." + @go test -mod=readonly github.com/cosmos/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 + +sim-gaia-fast: + @echo "Running quick Gaia simulation. This may take several minutes..." + @go test -mod=readonly github.com/cosmos/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=100 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=99 -SimulationPeriod=5 -v -timeout 24h + +sim-gaia-import-export: runsim + @echo "Running Gaia import/export simulation. This may take several minutes..." + $(GOPATH)/bin/runsim 25 5 TestGaiaImportExport + +sim-gaia-simulation-after-import: runsim + @echo "Running Gaia simulation-after-import. This may take several minutes..." + $(GOPATH)/bin/runsim 25 5 TestGaiaSimulationAfterImport + +sim-gaia-custom-genesis-multi-seed: runsim + @echo "Running multi-seed custom genesis simulation..." + @echo "By default, ${HOME}/.gaiad/config/genesis.json will be used." + $(GOPATH)/bin/runsim -g ${HOME}/.gaiad/config/genesis.json 400 5 TestFullGaiaSimulation + +sim-gaia-multi-seed: runsim + @echo "Running multi-seed Gaia simulation. This may take awhile!" + $(GOPATH)/bin/runsim 400 5 TestFullGaiaSimulation + +sim-benchmark-invariants: + @echo "Running simulation invariant benchmarks..." + @go test -mod=readonly github.com/cosmos/gaia/app -benchmem -bench=BenchmarkInvariants -run=^$ \ + -SimulationEnabled=true -SimulationNumBlocks=1000 -SimulationBlockSize=200 \ + -SimulationCommit=true -SimulationSeed=57 -v -timeout 24h + +SIM_NUM_BLOCKS ?= 500 +SIM_BLOCK_SIZE ?= 200 +SIM_COMMIT ?= true +sim-gaia-benchmark: + @echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!" + @go test -mod=readonly -benchmem -run=^$$ github.com/cosmos/gaia/app -bench ^BenchmarkFullGaiaSimulation$$ \ + -SimulationEnabled=true -SimulationNumBlocks=$(SIM_NUM_BLOCKS) -SimulationBlockSize=$(SIM_BLOCK_SIZE) -SimulationCommit=$(SIM_COMMIT) -timeout 24h + +sim-gaia-profile: + @echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!" + @go test -mod=readonly -benchmem -run=^$$ github.com/cosmos/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 + + +.PHONY: runsim sim-gaia-nondeterminism sim-gaia-custom-genesis-fast sim-gaia-fast sim-gaia-import-export \ + sim-gaia-simulation-after-import sim-gaia-custom-genesis-multi-seed sim-gaia-multi-seed \ + sim-benchmark-invariants sim-gaia-benchmark sim-gaia-profile