commit
09688d03f4
|
@ -3,7 +3,7 @@ version: 2
|
||||||
defaults: &linux_defaults
|
defaults: &linux_defaults
|
||||||
working_directory: /go/src/github.com/cosmos/cosmos-sdk
|
working_directory: /go/src/github.com/cosmos/cosmos-sdk
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.11.4
|
- image: circleci/golang:1.11.5
|
||||||
environment:
|
environment:
|
||||||
GOBIN: /tmp/workspace/bin
|
GOBIN: /tmp/workspace/bin
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ macos_config: &macos_defaults
|
||||||
xcode: "10.1.0"
|
xcode: "10.1.0"
|
||||||
working_directory: /Users/distiller/project/src/github.com/cosmos/cosmos-sdk
|
working_directory: /Users/distiller/project/src/github.com/cosmos/cosmos-sdk
|
||||||
environment:
|
environment:
|
||||||
GO_VERSION: "1.11.4"
|
GO_VERSION: "1.11.5"
|
||||||
|
|
||||||
set_macos_env: &macos_env
|
set_macos_env: &macos_env
|
||||||
run:
|
run:
|
||||||
|
@ -82,6 +82,7 @@ jobs:
|
||||||
name: Get metalinter
|
name: Get metalinter
|
||||||
command: |
|
command: |
|
||||||
export PATH="$GOBIN:$PATH"
|
export PATH="$GOBIN:$PATH"
|
||||||
|
make devtools-clean
|
||||||
make devtools
|
make devtools
|
||||||
- run:
|
- run:
|
||||||
name: Lint source
|
name: Lint source
|
||||||
|
@ -171,7 +172,7 @@ jobs:
|
||||||
name: Test multi-seed Gaia simulation long
|
name: Test multi-seed Gaia simulation long
|
||||||
command: |
|
command: |
|
||||||
export PATH="$GOBIN:$PATH"
|
export PATH="$GOBIN:$PATH"
|
||||||
scripts/multisim.sh 800 50 TestFullGaiaSimulation
|
scripts/multisim.sh 500 50 TestFullGaiaSimulation
|
||||||
|
|
||||||
test_sim_gaia_multi_seed:
|
test_sim_gaia_multi_seed:
|
||||||
<<: *linux_defaults
|
<<: *linux_defaults
|
||||||
|
@ -200,11 +201,10 @@ jobs:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
command: |
|
command: |
|
||||||
export PATH="$GOBIN:$PATH"
|
export PATH="$GOBIN:$PATH"
|
||||||
make install
|
|
||||||
export VERSION="$(git describe --tags --long | sed 's/v\(.*\)/\1/')"
|
export VERSION="$(git describe --tags --long | sed 's/v\(.*\)/\1/')"
|
||||||
for pkg in $(go list github.com/cosmos/cosmos-sdk/... | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test | grep -v '/simulation' | circleci tests split --split-by=timings); do
|
for pkg in $(go list ./... | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test | grep -v '/simulation' | circleci tests split --split-by=timings); do
|
||||||
id=$(basename "$pkg")
|
id=$(echo "$pkg" | sed 's|[/.]|_|g')
|
||||||
GOCACHE=off go test -timeout 8m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"
|
GOCACHE=off go test -timeout 8m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic -tags='ledger test_ledger_mock' "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"
|
||||||
done
|
done
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: /tmp/workspace
|
root: /tmp/workspace
|
||||||
|
@ -226,10 +226,20 @@ jobs:
|
||||||
command: |
|
command: |
|
||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
|
echo "--> Concatenating profiles:"
|
||||||
|
ls /tmp/workspace/profiles/
|
||||||
echo "mode: atomic" > coverage.txt
|
echo "mode: atomic" > coverage.txt
|
||||||
for prof in $(ls /tmp/workspace/profiles/); do
|
for prof in $(ls /tmp/workspace/profiles/); do
|
||||||
tail -n +2 /tmp/workspace/profiles/"$prof" >> coverage.txt
|
tail -n +2 /tmp/workspace/profiles/"$prof" >> coverage.txt
|
||||||
done
|
done
|
||||||
|
- run:
|
||||||
|
name: filter out DONTCOVER
|
||||||
|
command: |
|
||||||
|
excludelist="$(find ./ -type f -name '*.go' | xargs grep -l 'DONTCOVER' | xargs realpath --relative-to=$GOPATH/src)"
|
||||||
|
for filename in ${excludelist}; do
|
||||||
|
echo "Excluding ${filename} ..."
|
||||||
|
sed -i "\%${filename}:%d" coverage.txt
|
||||||
|
done
|
||||||
- run:
|
- run:
|
||||||
name: upload
|
name: upload
|
||||||
command: bash <(curl -s https://codecov.io/bash) -f coverage.txt
|
command: bash <(curl -s https://codecov.io/bash) -f coverage.txt
|
||||||
|
@ -243,7 +253,7 @@ jobs:
|
||||||
GOPATH: /home/circleci/.go_workspace/
|
GOPATH: /home/circleci/.go_workspace/
|
||||||
GOOS: linux
|
GOOS: linux
|
||||||
GOARCH: amd64
|
GOARCH: amd64
|
||||||
GO_VERSION: "1.11.4"
|
GO_VERSION: "1.11.5"
|
||||||
parallelism: 1
|
parallelism: 1
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
@ -380,9 +390,7 @@ workflows:
|
||||||
- test_cover:
|
- test_cover:
|
||||||
requires:
|
requires:
|
||||||
- setup_dependencies
|
- setup_dependencies
|
||||||
- localnet:
|
- localnet
|
||||||
requires:
|
|
||||||
- setup_dependencies
|
|
||||||
- upload_coverage:
|
- upload_coverage:
|
||||||
requires:
|
requires:
|
||||||
- test_cover
|
- test_cover
|
||||||
|
|
|
@ -4,4 +4,6 @@
|
||||||
* @ebuchman @rigelrozanski @cwgoes
|
* @ebuchman @rigelrozanski @cwgoes
|
||||||
|
|
||||||
# Precious documentation
|
# Precious documentation
|
||||||
/docs/ @zramsay @jolesbi
|
/docs/README.md @zramsay
|
||||||
|
/docs/DOCS_README.md @zramsay
|
||||||
|
/docs/.vuepress/ @zramsay
|
||||||
|
|
122
CHANGELOG.md
122
CHANGELOG.md
|
@ -1,5 +1,127 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.31.0
|
||||||
|
|
||||||
|
BREAKING CHANGES
|
||||||
|
|
||||||
|
* Gaia REST API (`gaiacli advanced rest-server`)
|
||||||
|
* [\#3284](https://github.com/cosmos/cosmos-sdk/issues/3284) Rename the `name`
|
||||||
|
field to `from` in the `base_req` body.
|
||||||
|
* [\#3485](https://github.com/cosmos/cosmos-sdk/pull/3485) Error responses are now JSON objects.
|
||||||
|
* [\#3477][distribution] endpoint changed "all_delegation_rewards" -> "delegator_total_rewards"
|
||||||
|
|
||||||
|
* Gaia CLI (`gaiacli`)
|
||||||
|
- [#3399](https://github.com/cosmos/cosmos-sdk/pull/3399) Add `gaiad validate-genesis` command to facilitate checking of genesis files
|
||||||
|
- [\#1894](https://github.com/cosmos/cosmos-sdk/issues/1894) `version` prints out short info by default. Add `--long` flag. Proper handling of `--format` flag introduced.
|
||||||
|
- [\#3465](https://github.com/cosmos/cosmos-sdk/issues/3465) `gaiacli rest-server` switched back to insecure mode by default:
|
||||||
|
- `--insecure` flag is removed.
|
||||||
|
- `--tls` is now used to enable secure layer.
|
||||||
|
- [\#3451](https://github.com/cosmos/cosmos-sdk/pull/3451) `gaiacli` now returns transactions in plain text including tags.
|
||||||
|
- [\#3497](https://github.com/cosmos/cosmos-sdk/issues/3497) `gaiad init` now takes moniker as required arguments, not as parameter.
|
||||||
|
* [\#3501](https://github.com/cosmos/cosmos-sdk/issues/3501) Change validator
|
||||||
|
address Bech32 encoding to consensus address in `tendermint-validator-set`.
|
||||||
|
|
||||||
|
* Gaia
|
||||||
|
* [\#3457](https://github.com/cosmos/cosmos-sdk/issues/3457) Changed governance tally validatorGovInfo to use sdk.Int power instead of sdk.Dec
|
||||||
|
* [\#3495](https://github.com/cosmos/cosmos-sdk/issues/3495) Added Validator Minimum Self Delegation
|
||||||
|
* Reintroduce OR semantics for tx fees
|
||||||
|
|
||||||
|
* SDK
|
||||||
|
* [\#2513](https://github.com/cosmos/cosmos-sdk/issues/2513) Tendermint updates are adjusted by 10^-6 relative to staking tokens,
|
||||||
|
* [\#3487](https://github.com/cosmos/cosmos-sdk/pull/3487) Move HTTP/REST utilities out of client/utils into a new dedicated client/rest package.
|
||||||
|
* [\#3490](https://github.com/cosmos/cosmos-sdk/issues/3490) ReadRESTReq() returns bool to avoid callers to write error responses twice.
|
||||||
|
* [\#3502](https://github.com/cosmos/cosmos-sdk/pull/3502) Fixes issue when comparing genesis states
|
||||||
|
* [\#3514](https://github.com/cosmos/cosmos-sdk/pull/3514) Various clean ups:
|
||||||
|
- Replace all GetKeyBase\* functions family in favor of NewKeyBaseFromDir and NewKeyBaseFromHomeFlag.
|
||||||
|
- Remove Get prefix from all TxBuilder's getters.
|
||||||
|
* [\#3522](https://github.com/cosmos/cosmos-sdk/pull/3522) Get rid of double negatives: Coins.IsNotNegative() -> Coins.IsAnyNegative().
|
||||||
|
* [\#3561](https://github.com/cosmos/cosmos-sdk/issues/3561) Don't unnecessarily store denominations in staking
|
||||||
|
|
||||||
|
|
||||||
|
FEATURES
|
||||||
|
|
||||||
|
* Gaia REST API
|
||||||
|
* [\#2358](https://github.com/cosmos/cosmos-sdk/issues/2358) Add distribution module REST interface
|
||||||
|
|
||||||
|
* Gaia CLI (`gaiacli`)
|
||||||
|
* [\#3429](https://github.com/cosmos/cosmos-sdk/issues/3429) Support querying
|
||||||
|
for all delegator distribution rewards.
|
||||||
|
* [\#3449](https://github.com/cosmos/cosmos-sdk/issues/3449) Proof verification now works with absence proofs
|
||||||
|
* [\#3484](https://github.com/cosmos/cosmos-sdk/issues/3484) Add support
|
||||||
|
vesting accounts to the add-genesis-account command.
|
||||||
|
|
||||||
|
* Gaia
|
||||||
|
- [\#3397](https://github.com/cosmos/cosmos-sdk/pull/3397) Implement genesis file sanitization to avoid failures at chain init.
|
||||||
|
* [\#3428](https://github.com/cosmos/cosmos-sdk/issues/3428) Run the simulation from a particular genesis state loaded from a file
|
||||||
|
|
||||||
|
* SDK
|
||||||
|
* [\#3270](https://github.com/cosmos/cosmos-sdk/issues/3270) [x/staking] limit number of ongoing unbonding delegations /redelegations per pair/trio
|
||||||
|
* [\#3477][distribution] new query endpoint "delegator_validators"
|
||||||
|
* [\#3514](https://github.com/cosmos/cosmos-sdk/pull/3514) Provided a lazy loading implementation of Keybase that locks the underlying
|
||||||
|
storage only for the time needed to perform the required operation. Also added Keybase reference to TxBuilder struct.
|
||||||
|
* [types] [\#2580](https://github.com/cosmos/cosmos-sdk/issues/2580) Addresses now Bech32 empty addresses to an empty string
|
||||||
|
|
||||||
|
|
||||||
|
IMPROVEMENTS
|
||||||
|
|
||||||
|
* Gaia REST API
|
||||||
|
* [\#3284](https://github.com/cosmos/cosmos-sdk/issues/3284) Update Gaia Lite
|
||||||
|
REST service to support the following:
|
||||||
|
* Automatic account number and sequence population when fields are omitted
|
||||||
|
* Generate only functionality no longer requires access to a local Keybase
|
||||||
|
* `from` field in the `base_req` body can be a Keybase name or account address
|
||||||
|
* [\#3423](https://github.com/cosmos/cosmos-sdk/issues/3423) Allow simulation
|
||||||
|
(auto gas) to work with generate only.
|
||||||
|
* [\#3514](https://github.com/cosmos/cosmos-sdk/pull/3514) REST server calls to keybase does not lock the underlying storage anymore.
|
||||||
|
* [\#3523](https://github.com/cosmos/cosmos-sdk/pull/3523) Added `/tx/encode` endpoint to serialize a JSON tx to base64-encoded Amino.
|
||||||
|
|
||||||
|
* Gaia CLI (`gaiacli`)
|
||||||
|
* [\#3476](https://github.com/cosmos/cosmos-sdk/issues/3476) New `withdraw-all-rewards` command to withdraw all delegations rewards for delegators.
|
||||||
|
* [\#3497](https://github.com/cosmos/cosmos-sdk/issues/3497) `gaiad gentx` supports `--ip` and `--node-id` flags to override defaults.
|
||||||
|
* [\#3518](https://github.com/cosmos/cosmos-sdk/issues/3518) Fix flow in
|
||||||
|
`keys add` to show the mnemonic by default.
|
||||||
|
* [\#3517](https://github.com/cosmos/cosmos-sdk/pull/3517) Increased test coverage
|
||||||
|
* [\#3523](https://github.com/cosmos/cosmos-sdk/pull/3523) Added `tx encode` command to serialize a JSON tx to base64-encoded Amino.
|
||||||
|
|
||||||
|
* Gaia
|
||||||
|
* [\#3418](https://github.com/cosmos/cosmos-sdk/issues/3418) Add vesting account
|
||||||
|
genesis validation checks to `GaiaValidateGenesisState`.
|
||||||
|
* [\#3420](https://github.com/cosmos/cosmos-sdk/issues/3420) Added maximum length to governance proposal descriptions and titles
|
||||||
|
* [\#3256](https://github.com/cosmos/cosmos-sdk/issues/3256) Add gas consumption
|
||||||
|
for tx size in the ante handler.
|
||||||
|
* [\#3454](https://github.com/cosmos/cosmos-sdk/pull/3454) Add `--jail-whitelist` to `gaiad export` to enable testing of complex exports
|
||||||
|
* [\#3424](https://github.com/cosmos/cosmos-sdk/issues/3424) Allow generation of gentxs with empty memo field.
|
||||||
|
* [\#3507](https://github.com/cosmos/cosmos-sdk/issues/3507) General cleanup, removal of unnecessary struct fields, undelegation bugfix, and comment clarification in x/staking and x/slashing
|
||||||
|
|
||||||
|
* SDK
|
||||||
|
* [\#2605] x/params add subkey accessing
|
||||||
|
* [\#2986](https://github.com/cosmos/cosmos-sdk/pull/2986) Store Refactor
|
||||||
|
* [\#3435](https://github.com/cosmos/cosmos-sdk/issues/3435) Test that store implementations do not allow nil values
|
||||||
|
* [\#2509](https://github.com/cosmos/cosmos-sdk/issues/2509) Sanitize all usage of Dec.RoundInt64()
|
||||||
|
* [\#556](https://github.com/cosmos/cosmos-sdk/issues/556) Increase `BaseApp`
|
||||||
|
test coverage.
|
||||||
|
* [\#3357](https://github.com/cosmos/cosmos-sdk/issues/3357) develop state-transitions.md for staking spec, missing states added to `state.md`
|
||||||
|
* [\#3552](https://github.com/cosmos/cosmos-sdk/pull/3552) Validate bit length when
|
||||||
|
deserializing `Int` types.
|
||||||
|
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
* Gaia CLI (`gaiacli`)
|
||||||
|
- [\#3417](https://github.com/cosmos/cosmos-sdk/pull/3417) Fix `q slashing signing-info` panic by ensuring safety of user input and properly returning not found error
|
||||||
|
- [\#3345](https://github.com/cosmos/cosmos-sdk/issues/3345) Upgrade ledger-cosmos-go dependency to v0.9.3 to pull
|
||||||
|
https://github.com/ZondaX/ledger-cosmos-go/commit/ed9aa39ce8df31bad1448c72d3d226bf2cb1a8d1 in order to fix a derivation path issue that causes `gaiacli keys add --recover`
|
||||||
|
to malfunction.
|
||||||
|
- [\#3419](https://github.com/cosmos/cosmos-sdk/pull/3419) Fix `q distr slashes` panic
|
||||||
|
- [\#3453](https://github.com/cosmos/cosmos-sdk/pull/3453) The `rest-server` command didn't respect persistent flags such as `--chain-id` and `--trust-node` if they were
|
||||||
|
passed on the command line.
|
||||||
|
- [\#3441](https://github.com/cosmos/cosmos-sdk/pull/3431) Improved resource management and connection handling (ledger devices). Fixes issue with DER vs BER signatures.
|
||||||
|
|
||||||
|
* Gaia
|
||||||
|
* [\#3486](https://github.com/cosmos/cosmos-sdk/pull/3486) Use AmountOf in
|
||||||
|
vesting accounts instead of zipping/aligning denominations.
|
||||||
|
|
||||||
|
|
||||||
## 0.30.0
|
## 0.30.0
|
||||||
|
|
||||||
BREAKING CHANGES
|
BREAKING CHANGES
|
||||||
|
|
|
@ -1,22 +1,6 @@
|
||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:7736fc6da04620727f8f3aa2ced8d77be8e074a302820937aa5993848c769b27"
|
|
||||||
name = "github.com/ZondaX/hid-go"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "48b08affede2cea076a3cf13b2e3f72ed262b743"
|
|
||||||
version = "v0.4.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:1ba351898f7efc68c7c9ff3145b920e478f716b077fdaaf06b967c5d883fa988"
|
|
||||||
name = "github.com/ZondaX/ledger-go"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "c3225ab10c2f53397d4aa419a588466493572b22"
|
|
||||||
version = "v0.4.0"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:09a7f74eb6bb3c0f14d8926610c87f569c5cff68e978d30e9a3540aeb626fdf0"
|
digest = "1:09a7f74eb6bb3c0f14d8926610c87f569c5cff68e978d30e9a3540aeb626fdf0"
|
||||||
|
@ -42,12 +26,11 @@
|
||||||
version = "v0.1.0"
|
version = "v0.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
|
||||||
digest = "1:093bf93a65962e8191e3e8cd8fc6c363f83d43caca9739c906531ba7210a9904"
|
digest = "1:093bf93a65962e8191e3e8cd8fc6c363f83d43caca9739c906531ba7210a9904"
|
||||||
name = "github.com/btcsuite/btcd"
|
name = "github.com/btcsuite/btcd"
|
||||||
packages = ["btcec"]
|
packages = ["btcec"]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "7d2daa5bfef28c5e282571bc06416516936115ee"
|
revision = "ed77733ec07dfc8a513741138419b8d9d3de9d2d"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:386de157f7d19259a7f9c81f26ce011223ce0f090353c1152ffdf730d7d10ac2"
|
digest = "1:386de157f7d19259a7f9c81f26ce011223ce0f090353c1152ffdf730d7d10ac2"
|
||||||
|
@ -71,6 +54,14 @@
|
||||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||||
version = "v1.1.0"
|
version = "v1.1.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:fed20bf7f0da387c96d4cfc140a95572e5aba4bb984beb7de910e090ae39849b"
|
||||||
|
name = "github.com/ethereum/go-ethereum"
|
||||||
|
packages = ["crypto/secp256k1"]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "7fa3509e2eaf1a4ebc12344590e5699406690f15"
|
||||||
|
version = "v1.8.22"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd"
|
digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd"
|
||||||
name = "github.com/fsnotify/fsnotify"
|
name = "github.com/fsnotify/fsnotify"
|
||||||
|
@ -149,20 +140,12 @@
|
||||||
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
|
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:c79fb010be38a59d657c48c6ba1d003a8aa651fa56b579d959d74573b7dff8e1"
|
digest = "1:ca59b1175189b3f0e9f1793d2c350114be36eaabbe5b9f554b35edee1de50aea"
|
||||||
name = "github.com/gorilla/context"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
|
|
||||||
version = "v1.1.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:e73f5b0152105f18bc131fba127d9949305c8693f8a762588a82a48f61756f5f"
|
|
||||||
name = "github.com/gorilla/mux"
|
name = "github.com/gorilla/mux"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
|
revision = "a7962380ca08b5a188038c69871b8d3fbdf31e89"
|
||||||
version = "v1.6.2"
|
version = "v1.7.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d"
|
digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d"
|
||||||
|
@ -296,11 +279,10 @@
|
||||||
name = "github.com/prometheus/client_model"
|
name = "github.com/prometheus/client_model"
|
||||||
packages = ["go"]
|
packages = ["go"]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "5c3871d89910bfb32f5fcab2aa4b9ec68e65a99f"
|
revision = "fd36f4220a901265f90734c3183c5f0c91daa0b8"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
digest = "1:35cf6bdf68db765988baa9c4f10cc5d7dda1126a54bd62e252dbcd0b1fc8da90"
|
||||||
digest = "1:db712fde5d12d6cdbdf14b777f0c230f4ff5ab0be8e35b239fc319953ed577a4"
|
|
||||||
name = "github.com/prometheus/common"
|
name = "github.com/prometheus/common"
|
||||||
packages = [
|
packages = [
|
||||||
"expfmt",
|
"expfmt",
|
||||||
|
@ -308,11 +290,12 @@
|
||||||
"model",
|
"model",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "4724e9255275ce38f7179b2478abeae4e28c904f"
|
revision = "cfeb6f9992ffa54aaa4f2170ade4067ee478b250"
|
||||||
|
version = "v0.2.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:d39e7c7677b161c2dd4c635a2ac196460608c7d8ba5337cc8cae5825a2681f8f"
|
digest = "1:c65f369bae3dff3a0382e38f3fe4f62cdfecba59cb6429ee323b75afdd4f3ba3"
|
||||||
name = "github.com/prometheus/procfs"
|
name = "github.com/prometheus/procfs"
|
||||||
packages = [
|
packages = [
|
||||||
".",
|
".",
|
||||||
|
@ -321,7 +304,7 @@
|
||||||
"xfs",
|
"xfs",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "1dc9a6cbc91aacc3e8b2d63db4d2e957a5394ac4"
|
revision = "de1b801bf34b80cd00f14087dc5a994bfe0296bc"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:ea0700160aca4ef099f4e06686a665a87691f4248dddd40796925eda2e46bd64"
|
digest = "1:ea0700160aca4ef099f4e06686a665a87691f4248dddd40796925eda2e46bd64"
|
||||||
|
@ -347,15 +330,15 @@
|
||||||
version = "v1.6.0"
|
version = "v1.6.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:6a4a11ba764a56d2758899ec6f3848d24698d48442ebce85ee7a3f63284526cd"
|
digest = "1:3e39bafd6c2f4bf3c76c3bfd16a2e09e016510ad5db90dc02b88e2f565d6d595"
|
||||||
name = "github.com/spf13/afero"
|
name = "github.com/spf13/afero"
|
||||||
packages = [
|
packages = [
|
||||||
".",
|
".",
|
||||||
"mem",
|
"mem",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "d40851caa0d747393da1ffb28f7f9d8b4eeffebd"
|
revision = "f4711e4db9e9a1d3887343acb72b2bbfc2f686f5"
|
||||||
version = "v1.1.2"
|
version = "v1.2.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc"
|
digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc"
|
||||||
|
@ -453,7 +436,7 @@
|
||||||
version = "v0.12.0"
|
version = "v0.12.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:22a0fe58c626dd09549eb9451688fab5a2c8bef04d478c907f747d6151d431fd"
|
digest = "1:89f6fe8d02b427996828fbf43720ed1297a2e92c930b98dd302767b5ad796579"
|
||||||
name = "github.com/tendermint/tendermint"
|
name = "github.com/tendermint/tendermint"
|
||||||
packages = [
|
packages = [
|
||||||
"abci/client",
|
"abci/client",
|
||||||
|
@ -519,15 +502,31 @@
|
||||||
"version",
|
"version",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "v0.29.0"
|
revision = "v0.30.0-rc0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:a7485b2a69f996923f9d3406a9a853fd8eb31818515e985a830d71f88f6a925b"
|
digest = "1:b73f5e117bc7c6e8fc47128f20db48a873324ad5cfeeebfc505e85c58682b5e4"
|
||||||
|
name = "github.com/zondax/hid"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "T"
|
||||||
|
revision = "302fd402163c34626286195dfa9adac758334acc"
|
||||||
|
version = "v0.9.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:fca24169988a61ea725d1326de30910d8049fe68bcbc194d28803f9a76dda380"
|
||||||
name = "github.com/zondax/ledger-cosmos-go"
|
name = "github.com/zondax/ledger-cosmos-go"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "d4aed6d929a703bb555a2d79fe9c470afe61f648"
|
revision = "69fdb8ce5e5b9d9c3b22b9248e117b231d4f06dd"
|
||||||
version = "v0.9.2"
|
version = "v0.9.7"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:f8e4c0b959174a1fa5946b12f1f2ac7ea5651bef20a9e4a8dac55dbffcaa6cd6"
|
||||||
|
name = "github.com/zondax/ledger-go"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "69c15f1333a9b6866e5f66096561c7d138894bc5"
|
||||||
|
version = "v0.8.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:6f6dc6060c4e9ba73cf28aa88f12a69a030d3d19d518ef8e931879eaa099628d"
|
digest = "1:6f6dc6060c4e9ba73cf28aa88f12a69a030d3d19d518ef8e931879eaa099628d"
|
||||||
|
@ -612,7 +611,7 @@
|
||||||
revision = "383e8b2c3b9e36c4076b235b32537292176bae20"
|
revision = "383e8b2c3b9e36c4076b235b32537292176bae20"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:9edd250a3c46675d0679d87540b30c9ed253b19bd1fd1af08f4f5fb3c79fc487"
|
digest = "1:9ab5a33d8cb5c120602a34d2e985ce17956a4e8c2edce7e6961568f95e40c09a"
|
||||||
name = "google.golang.org/grpc"
|
name = "google.golang.org/grpc"
|
||||||
packages = [
|
packages = [
|
||||||
".",
|
".",
|
||||||
|
@ -648,8 +647,8 @@
|
||||||
"tap",
|
"tap",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "df014850f6dee74ba2fc94874043a9f3f75fbfd8"
|
revision = "a02b0774206b209466313a0b525d2c738fe407eb"
|
||||||
version = "v1.17.0"
|
version = "v1.18.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96"
|
digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96"
|
||||||
|
@ -680,7 +679,7 @@
|
||||||
"github.com/spf13/viper",
|
"github.com/spf13/viper",
|
||||||
"github.com/stretchr/testify/assert",
|
"github.com/stretchr/testify/assert",
|
||||||
"github.com/stretchr/testify/require",
|
"github.com/stretchr/testify/require",
|
||||||
"github.com/syndtr/goleveldb/leveldb/opt",
|
"github.com/tendermint/btcd/btcec",
|
||||||
"github.com/tendermint/go-amino",
|
"github.com/tendermint/go-amino",
|
||||||
"github.com/tendermint/iavl",
|
"github.com/tendermint/iavl",
|
||||||
"github.com/tendermint/tendermint/abci/server",
|
"github.com/tendermint/tendermint/abci/server",
|
||||||
|
|
11
Gopkg.toml
11
Gopkg.toml
|
@ -40,14 +40,18 @@
|
||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "github.com/tendermint/tendermint"
|
name = "github.com/tendermint/tendermint"
|
||||||
revision = "v0.29.0"
|
revision = "v0.30.0-rc0"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/zondax/ledger-cosmos-go"
|
name = "github.com/zondax/ledger-cosmos-go"
|
||||||
version = "=v0.9.2"
|
version = "=v0.9.7"
|
||||||
|
|
||||||
## deps without releases:
|
## deps without releases:
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/btcsuite/btcd"
|
||||||
|
revision = "ed77733ec07dfc8a513741138419b8d9d3de9d2d"
|
||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "golang.org/x/crypto"
|
name = "golang.org/x/crypto"
|
||||||
source = "https://github.com/tendermint/crypto"
|
source = "https://github.com/tendermint/crypto"
|
||||||
|
@ -84,3 +88,6 @@
|
||||||
[prune]
|
[prune]
|
||||||
go-tests = true
|
go-tests = true
|
||||||
unused-packages = true
|
unused-packages = true
|
||||||
|
[[prune.project]]
|
||||||
|
name = "github.com/zondax/hid"
|
||||||
|
unused-packages = false
|
||||||
|
|
52
Makefile
52
Makefile
|
@ -1,6 +1,6 @@
|
||||||
PACKAGES_NOSIMULATION=$(shell go list ./... | grep -v '/simulation')
|
PACKAGES_NOSIMULATION=$(shell go list ./... | grep -v '/simulation')
|
||||||
PACKAGES_SIMTEST=$(shell go list ./... | grep '/simulation')
|
PACKAGES_SIMTEST=$(shell go list ./... | grep '/simulation')
|
||||||
VERSION := $(subst v,,$(shell git describe --tags --long))
|
VERSION := $(shell echo $(shell git describe --tags) | sed 's/^v//')
|
||||||
COMMIT := $(shell git log -1 --format='%H')
|
COMMIT := $(shell git log -1 --format='%H')
|
||||||
BUILD_TAGS = netgo
|
BUILD_TAGS = netgo
|
||||||
CAT := $(if $(filter $(OS),Windows_NT),type,cat)
|
CAT := $(if $(filter $(OS),Windows_NT),type,cat)
|
||||||
|
@ -15,7 +15,7 @@ GOTOOLS = \
|
||||||
github.com/rakyll/statik
|
github.com/rakyll/statik
|
||||||
GOBIN ?= $(GOPATH)/bin
|
GOBIN ?= $(GOPATH)/bin
|
||||||
|
|
||||||
all: devtools get_vendor_deps install test_lint test
|
all: devtools vendor-deps install test_lint test
|
||||||
|
|
||||||
# The below include contains the tools target.
|
# The below include contains the tools target.
|
||||||
include scripts/Makefile
|
include scripts/Makefile
|
||||||
|
@ -99,6 +99,7 @@ update_dev_tools:
|
||||||
go get -u github.com/tendermint/lint/golint
|
go get -u github.com/tendermint/lint/golint
|
||||||
|
|
||||||
devtools: devtools-stamp
|
devtools: devtools-stamp
|
||||||
|
devtools-clean: tools-clean
|
||||||
devtools-stamp: tools
|
devtools-stamp: tools
|
||||||
@echo "--> Downloading linters (this may take awhile)"
|
@echo "--> Downloading linters (this may take awhile)"
|
||||||
$(GOPATH)/src/github.com/alecthomas/gometalinter/scripts/install.sh -b $(GOBIN)
|
$(GOPATH)/src/github.com/alecthomas/gometalinter/scripts/install.sh -b $(GOBIN)
|
||||||
|
@ -122,7 +123,10 @@ draw_deps: tools
|
||||||
@goviz -i github.com/cosmos/cosmos-sdk/cmd/gaia/cmd/gaiad -d 2 | dot -Tpng -o dependency-graph.png
|
@goviz -i github.com/cosmos/cosmos-sdk/cmd/gaia/cmd/gaiad -d 2 | dot -Tpng -o dependency-graph.png
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f devtools-stamp vendor-deps
|
rm -f devtools-stamp vendor-deps snapcraft-local.yaml
|
||||||
|
|
||||||
|
distclean: clean
|
||||||
|
rm -rf vendor/
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
### Documentation
|
### Documentation
|
||||||
|
@ -140,8 +144,14 @@ test: test_unit
|
||||||
test_cli:
|
test_cli:
|
||||||
@go test -p 4 `go list github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test` -tags=cli_test
|
@go test -p 4 `go list github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test` -tags=cli_test
|
||||||
|
|
||||||
|
test_ledger:
|
||||||
|
# First test with mock
|
||||||
|
@go test `go list github.com/cosmos/cosmos-sdk/crypto` -tags='cgo ledger test_ledger_mock'
|
||||||
|
# Now test with a real device
|
||||||
|
@go test -v `go list github.com/cosmos/cosmos-sdk/crypto` -tags='cgo ledger'
|
||||||
|
|
||||||
test_unit:
|
test_unit:
|
||||||
@VERSION=$(VERSION) go test $(PACKAGES_NOSIMULATION)
|
@VERSION=$(VERSION) go test $(PACKAGES_NOSIMULATION) -tags='test_ledger_mock'
|
||||||
|
|
||||||
test_race:
|
test_race:
|
||||||
@VERSION=$(VERSION) go test -race $(PACKAGES_NOSIMULATION)
|
@VERSION=$(VERSION) go test -race $(PACKAGES_NOSIMULATION)
|
||||||
|
@ -150,9 +160,15 @@ test_sim_gaia_nondeterminism:
|
||||||
@echo "Running nondeterminism test..."
|
@echo "Running nondeterminism test..."
|
||||||
@go test ./cmd/gaia/app -run TestAppStateDeterminism -SimulationEnabled=true -v -timeout 10m
|
@go test ./cmd/gaia/app -run TestAppStateDeterminism -SimulationEnabled=true -v -timeout 10m
|
||||||
|
|
||||||
|
test_sim_gaia_custom_genesis_fast:
|
||||||
|
@echo "Running custom genesis simulation..."
|
||||||
|
@echo "By default, ${HOME}/.gaiad/config/genesis.json will be used."
|
||||||
|
@go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationGenesis=${HOME}/.gaiad/config/genesis.json \
|
||||||
|
-SimulationEnabled=true -SimulationNumBlocks=100 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=99 -SimulationPeriod=5 -v -timeout 24h
|
||||||
|
|
||||||
test_sim_gaia_fast:
|
test_sim_gaia_fast:
|
||||||
@echo "Running quick Gaia simulation. This may take several minutes..."
|
@echo "Running quick Gaia simulation. This may take several minutes..."
|
||||||
@go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=99 -v -timeout 24h
|
@go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=100 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=99 -SimulationPeriod=5 -v -timeout 24h
|
||||||
|
|
||||||
test_sim_gaia_import_export:
|
test_sim_gaia_import_export:
|
||||||
@echo "Running Gaia import/export simulation. This may take several minutes..."
|
@echo "Running Gaia import/export simulation. This may take several minutes..."
|
||||||
|
@ -162,6 +178,11 @@ test_sim_gaia_simulation_after_import:
|
||||||
@echo "Running Gaia simulation-after-import. This may take several minutes..."
|
@echo "Running Gaia simulation-after-import. This may take several minutes..."
|
||||||
@bash scripts/multisim.sh 50 5 TestGaiaSimulationAfterImport
|
@bash scripts/multisim.sh 50 5 TestGaiaSimulationAfterImport
|
||||||
|
|
||||||
|
test_sim_gaia_custom_genesis_multi_seed:
|
||||||
|
@echo "Running multi-seed custom genesis simulation..."
|
||||||
|
@echo "By default, ${HOME}/.gaiad/config/genesis.json will be used."
|
||||||
|
@bash scripts/multisim.sh 400 5 TestFullGaiaSimulation ${HOME}/.gaiad/config/genesis.json
|
||||||
|
|
||||||
test_sim_gaia_multi_seed:
|
test_sim_gaia_multi_seed:
|
||||||
@echo "Running multi-seed Gaia simulation. This may take awhile!"
|
@echo "Running multi-seed Gaia simulation. This may take awhile!"
|
||||||
@bash scripts/multisim.sh 400 5 TestFullGaiaSimulation
|
@bash scripts/multisim.sh 400 5 TestFullGaiaSimulation
|
||||||
|
@ -171,14 +192,16 @@ SIM_BLOCK_SIZE ?= 200
|
||||||
SIM_COMMIT ?= true
|
SIM_COMMIT ?= true
|
||||||
test_sim_gaia_benchmark:
|
test_sim_gaia_benchmark:
|
||||||
@echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
|
@echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
|
||||||
@go test -benchmem -run=^$$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$$ -SimulationEnabled=true -SimulationNumBlocks=$(SIM_NUM_BLOCKS) -SimulationBlockSize=$(SIM_BLOCK_SIZE) -SimulationCommit=$(SIM_COMMIT) -timeout 24h
|
@go test -benchmem -run=^$$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$$ \
|
||||||
|
-SimulationEnabled=true -SimulationNumBlocks=$(SIM_NUM_BLOCKS) -SimulationBlockSize=$(SIM_BLOCK_SIZE) -SimulationCommit=$(SIM_COMMIT) -timeout 24h
|
||||||
|
|
||||||
test_sim_gaia_profile:
|
test_sim_gaia_profile:
|
||||||
@echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
|
@echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
|
||||||
@go test -benchmem -run=^$$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$$ -SimulationEnabled=true -SimulationNumBlocks=$(SIM_NUM_BLOCKS) -SimulationBlockSize=$(SIM_BLOCK_SIZE) -SimulationCommit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out
|
@go test -benchmem -run=^$$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$$ \
|
||||||
|
-SimulationEnabled=true -SimulationNumBlocks=$(SIM_NUM_BLOCKS) -SimulationBlockSize=$(SIM_BLOCK_SIZE) -SimulationCommit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out
|
||||||
|
|
||||||
test_cover:
|
test_cover:
|
||||||
@export VERSION=$(VERSION); bash tests/test_cover.sh
|
@export VERSION=$(VERSION); bash -x tests/test_cover.sh
|
||||||
|
|
||||||
test_lint:
|
test_lint:
|
||||||
gometalinter --config=tools/gometalinter.json ./...
|
gometalinter --config=tools/gometalinter.json ./...
|
||||||
|
@ -235,12 +258,21 @@ localnet-start: localnet-stop
|
||||||
localnet-stop:
|
localnet-stop:
|
||||||
docker-compose down
|
docker-compose down
|
||||||
|
|
||||||
|
|
||||||
|
########################################
|
||||||
|
### Packaging
|
||||||
|
|
||||||
|
snapcraft-local.yaml: snapcraft-local.yaml.in
|
||||||
|
sed "s/@VERSION@/${VERSION}/g" < $< > $@
|
||||||
|
|
||||||
# To avoid unintended conflicts with file names, always add to .PHONY
|
# To avoid unintended conflicts with file names, always add to .PHONY
|
||||||
# unless there is a reason not to.
|
# unless there is a reason not to.
|
||||||
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
|
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
|
||||||
.PHONY: build install install_debug dist \
|
.PHONY: build install install_debug dist clean distclean \
|
||||||
check_tools check_dev_tools get_vendor_deps draw_deps test test_cli test_unit \
|
check_tools check_dev_tools get_vendor_deps draw_deps test test_cli test_unit \
|
||||||
test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update \
|
test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update \
|
||||||
build-linux build-docker-gaiadnode localnet-start localnet-stop \
|
build-linux build-docker-gaiadnode localnet-start localnet-stop \
|
||||||
format check-ledger test_sim_gaia_nondeterminism test_sim_modules test_sim_gaia_fast \
|
format check-ledger test_sim_gaia_nondeterminism test_sim_modules test_sim_gaia_fast \
|
||||||
test_sim_gaia_multi_seed test_sim_gaia_import_export update_tools update_dev_tools
|
test_sim_gaia_custom_genesis_fast test_sim_gaia_custom_genesis_multi_seed \
|
||||||
|
test_sim_gaia_multi_seed test_sim_gaia_import_export update_tools update_dev_tools \
|
||||||
|
devtools-clean
|
||||||
|
|
19
PENDING.md
19
PENDING.md
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
BREAKING CHANGES
|
BREAKING CHANGES
|
||||||
|
|
||||||
* Gaia REST API (`gaiacli advanced rest-server`)
|
* Gaia REST API
|
||||||
|
|
||||||
* Gaia CLI (`gaiacli`)
|
* Gaia CLI
|
||||||
|
|
||||||
* Gaia
|
* Gaia
|
||||||
|
|
||||||
|
@ -12,12 +12,11 @@ BREAKING CHANGES
|
||||||
|
|
||||||
* Tendermint
|
* Tendermint
|
||||||
|
|
||||||
|
|
||||||
FEATURES
|
FEATURES
|
||||||
|
|
||||||
* Gaia REST API (`gaiacli advanced rest-server`)
|
* Gaia REST API
|
||||||
|
|
||||||
* Gaia CLI (`gaiacli`)
|
* Gaia CLI
|
||||||
|
|
||||||
* Gaia
|
* Gaia
|
||||||
|
|
||||||
|
@ -28,9 +27,9 @@ FEATURES
|
||||||
|
|
||||||
IMPROVEMENTS
|
IMPROVEMENTS
|
||||||
|
|
||||||
* Gaia REST API (`gaiacli advanced rest-server`)
|
* Gaia REST API
|
||||||
|
|
||||||
* Gaia CLI (`gaiacli`)
|
* Gaia CLI
|
||||||
|
|
||||||
* Gaia
|
* Gaia
|
||||||
|
|
||||||
|
@ -41,12 +40,12 @@ IMPROVEMENTS
|
||||||
|
|
||||||
BUG FIXES
|
BUG FIXES
|
||||||
|
|
||||||
* Gaia REST API (`gaiacli advanced rest-server`)
|
* Gaia REST API
|
||||||
|
|
||||||
* Gaia CLI (`gaiacli`)
|
* Gaia CLI
|
||||||
|
|
||||||
* Gaia
|
* Gaia
|
||||||
|
|
||||||
* SDK
|
* SDK
|
||||||
|
|
||||||
* Tendermint
|
* Tendermint
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
[](https://github.com/cosmos/cosmos-sdk/releases/latest)
|
[](https://github.com/cosmos/cosmos-sdk/releases/latest)
|
||||||
[](https://circleci.com/gh/cosmos/cosmos-sdk/tree/master)
|
[](https://circleci.com/gh/cosmos/cosmos-sdk/tree/master)
|
||||||
|
[](https://build.snapcraft.io/user/cosmos/cosmos-sdk)
|
||||||
[](https://codecov.io/gh/cosmos/cosmos-sdk)
|
[](https://codecov.io/gh/cosmos/cosmos-sdk)
|
||||||
[](https://goreportcard.com/report/github.com/cosmos/cosmos-sdk)
|
[](https://goreportcard.com/report/github.com/cosmos/cosmos-sdk)
|
||||||
[](https://github.com/cosmos/cosmos-sdk/blob/master/LICENSE)
|
[](https://github.com/cosmos/cosmos-sdk/blob/master/LICENSE)
|
||||||
|
@ -17,7 +18,7 @@ It is being used to build `Gaia`, the first implementation of the Cosmos Hub.
|
||||||
**WARNING**: The SDK has mostly stabilized, but we are still making some
|
**WARNING**: The SDK has mostly stabilized, but we are still making some
|
||||||
breaking changes.
|
breaking changes.
|
||||||
|
|
||||||
**Note**: Requires [Go 1.11.4+](https://golang.org/dl/)
|
**Note**: Requires [Go 1.11.5+](https://golang.org/dl/)
|
||||||
|
|
||||||
## Cosmos Hub Public Testnet
|
## Cosmos Hub Public Testnet
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,13 @@ package baseapp
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"reflect"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||||
|
@ -41,7 +43,7 @@ const (
|
||||||
// BaseApp reflects the ABCI application implementation.
|
// BaseApp reflects the ABCI application implementation.
|
||||||
type BaseApp struct {
|
type BaseApp struct {
|
||||||
// initialized on creation
|
// initialized on creation
|
||||||
Logger log.Logger
|
logger log.Logger
|
||||||
name string // application name from abci.Info
|
name string // application name from abci.Info
|
||||||
db dbm.DB // common DB backend
|
db dbm.DB // common DB backend
|
||||||
cms sdk.CommitMultiStore // Main (uncached) state
|
cms sdk.CommitMultiStore // Main (uncached) state
|
||||||
|
@ -50,19 +52,18 @@ type BaseApp struct {
|
||||||
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
|
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
|
||||||
|
|
||||||
// set upon LoadVersion or LoadLatestVersion.
|
// set upon LoadVersion or LoadLatestVersion.
|
||||||
mainKey *sdk.KVStoreKey // Main KVStore in cms
|
baseKey *sdk.KVStoreKey // Main KVStore in cms
|
||||||
|
|
||||||
// may be nil
|
anteHandler sdk.AnteHandler // ante handler for fee and auth
|
||||||
anteHandler sdk.AnteHandler // ante handler for fee and auth
|
initChainer sdk.InitChainer // initialize state with validators and state blob
|
||||||
initChainer sdk.InitChainer // initialize state with validators and state blob
|
beginBlocker sdk.BeginBlocker // logic to run before any txs
|
||||||
beginBlocker sdk.BeginBlocker // logic to run before any txs
|
endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes
|
||||||
endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes
|
addrPeerFilter sdk.PeerFilter // filter peers by address and port
|
||||||
addrPeerFilter sdk.PeerFilter // filter peers by address and port
|
idPeerFilter sdk.PeerFilter // filter peers by node ID
|
||||||
pubkeyPeerFilter sdk.PeerFilter // filter peers by public key
|
fauxMerkleMode bool // if true, IAVL MountStores uses MountStoresDB for simulation speed.
|
||||||
fauxMerkleMode bool // if true, IAVL MountStores uses MountStoresDB for simulation speed.
|
|
||||||
|
|
||||||
//--------------------
|
// --------------------
|
||||||
// Volatile
|
// Volatile state
|
||||||
// checkState is set on initialization and reset on Commit.
|
// checkState is set on initialization and reset on Commit.
|
||||||
// deliverState is set in InitChain and BeginBlock and cleared on Commit.
|
// deliverState is set in InitChain and BeginBlock and cleared on Commit.
|
||||||
// See methods setCheckState and setDeliverState.
|
// See methods setCheckState and setDeliverState.
|
||||||
|
@ -71,27 +72,30 @@ type BaseApp struct {
|
||||||
voteInfos []abci.VoteInfo // absent validators from begin block
|
voteInfos []abci.VoteInfo // absent validators from begin block
|
||||||
|
|
||||||
// consensus params
|
// consensus params
|
||||||
// TODO move this in the future to baseapp param store on main store.
|
// TODO: Move this in the future to baseapp param store on main store.
|
||||||
consensusParams *abci.ConsensusParams
|
consensusParams *abci.ConsensusParams
|
||||||
|
|
||||||
// The minimum gas prices a validator is willing to accept for processing a
|
// The minimum gas prices a validator is willing to accept for processing a
|
||||||
// transaction. This is mainly used for DoS and spam prevention.
|
// transaction. This is mainly used for DoS and spam prevention.
|
||||||
minGasPrices sdk.DecCoins
|
minGasPrices sdk.DecCoins
|
||||||
|
|
||||||
// flag for sealing
|
// flag for sealing options and parameters to a BaseApp
|
||||||
sealed bool
|
sealed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ abci.Application = (*BaseApp)(nil)
|
var _ abci.Application = (*BaseApp)(nil)
|
||||||
|
|
||||||
// NewBaseApp returns a reference to an initialized BaseApp.
|
// NewBaseApp returns a reference to an initialized BaseApp. It accepts a
|
||||||
|
// variadic number of option functions, which act on the BaseApp to set
|
||||||
|
// configuration choices.
|
||||||
//
|
//
|
||||||
// NOTE: The db is used to store the version number for now.
|
// NOTE: The db is used to store the version number for now.
|
||||||
// Accepts a user-defined txDecoder
|
func NewBaseApp(
|
||||||
// Accepts variable number of option functions, which act on the BaseApp to set configuration choices
|
name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp),
|
||||||
func NewBaseApp(name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp)) *BaseApp {
|
) *BaseApp {
|
||||||
|
|
||||||
app := &BaseApp{
|
app := &BaseApp{
|
||||||
Logger: logger,
|
logger: logger,
|
||||||
name: name,
|
name: name,
|
||||||
db: db,
|
db: db,
|
||||||
cms: store.NewCommitMultiStore(db),
|
cms: store.NewCommitMultiStore(db),
|
||||||
|
@ -103,112 +107,120 @@ func NewBaseApp(name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecod
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(app)
|
option(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseApp Name
|
// Name returns the name of the BaseApp.
|
||||||
func (app *BaseApp) Name() string {
|
func (app *BaseApp) Name() string {
|
||||||
return app.name
|
return app.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logger returns the logger of the BaseApp.
|
||||||
|
func (app *BaseApp) Logger() log.Logger {
|
||||||
|
return app.logger
|
||||||
|
}
|
||||||
|
|
||||||
// SetCommitMultiStoreTracer sets the store tracer on the BaseApp's underlying
|
// SetCommitMultiStoreTracer sets the store tracer on the BaseApp's underlying
|
||||||
// CommitMultiStore.
|
// CommitMultiStore.
|
||||||
func (app *BaseApp) SetCommitMultiStoreTracer(w io.Writer) {
|
func (app *BaseApp) SetCommitMultiStoreTracer(w io.Writer) {
|
||||||
app.cms.WithTracer(w)
|
app.cms.SetTracer(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mount IAVL or DB stores to the provided keys in the BaseApp multistore
|
// MountStores mounts all IAVL or DB stores to the provided keys in the BaseApp
|
||||||
func (app *BaseApp) MountStores(keys ...*sdk.KVStoreKey) {
|
// multistore.
|
||||||
|
func (app *BaseApp) MountStores(keys ...sdk.StoreKey) {
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
if !app.fauxMerkleMode {
|
switch key.(type) {
|
||||||
app.MountStore(key, sdk.StoreTypeIAVL)
|
case *sdk.KVStoreKey:
|
||||||
} else {
|
if !app.fauxMerkleMode {
|
||||||
// StoreTypeDB doesn't do anything upon commit, and it doesn't
|
app.MountStore(key, sdk.StoreTypeIAVL)
|
||||||
// retain history, but it's useful for faster simulation.
|
} else {
|
||||||
app.MountStore(key, sdk.StoreTypeDB)
|
// StoreTypeDB doesn't do anything upon commit, and it doesn't
|
||||||
|
// retain history, but it's useful for faster simulation.
|
||||||
|
app.MountStore(key, sdk.StoreTypeDB)
|
||||||
|
}
|
||||||
|
case *sdk.TransientStoreKey:
|
||||||
|
app.MountStore(key, sdk.StoreTypeTransient)
|
||||||
|
default:
|
||||||
|
panic("Unrecognized store key type " + reflect.TypeOf(key).Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mount stores to the provided keys in the BaseApp multistore
|
// MountStoreWithDB mounts a store to the provided key in the BaseApp
|
||||||
func (app *BaseApp) MountStoresTransient(keys ...*sdk.TransientStoreKey) {
|
// multistore, using a specified DB.
|
||||||
for _, key := range keys {
|
|
||||||
app.MountStore(key, sdk.StoreTypeTransient)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mount a store to the provided key in the BaseApp multistore, using a specified DB
|
|
||||||
func (app *BaseApp) MountStoreWithDB(key sdk.StoreKey, typ sdk.StoreType, db dbm.DB) {
|
func (app *BaseApp) MountStoreWithDB(key sdk.StoreKey, typ sdk.StoreType, db dbm.DB) {
|
||||||
app.cms.MountStoreWithDB(key, typ, db)
|
app.cms.MountStoreWithDB(key, typ, db)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mount a store to the provided key in the BaseApp multistore, using the default DB
|
// MountStore mounts a store to the provided key in the BaseApp multistore,
|
||||||
|
// using the default DB.
|
||||||
func (app *BaseApp) MountStore(key sdk.StoreKey, typ sdk.StoreType) {
|
func (app *BaseApp) MountStore(key sdk.StoreKey, typ sdk.StoreType) {
|
||||||
app.cms.MountStoreWithDB(key, typ, nil)
|
app.cms.MountStoreWithDB(key, typ, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// load latest application version
|
// LoadLatestVersion loads the latest application version. It will panic if
|
||||||
// panics if called more than once on a running baseapp
|
// called more than once on a running BaseApp.
|
||||||
func (app *BaseApp) LoadLatestVersion(mainKey *sdk.KVStoreKey) error {
|
func (app *BaseApp) LoadLatestVersion(baseKey *sdk.KVStoreKey) error {
|
||||||
err := app.cms.LoadLatestVersion()
|
err := app.cms.LoadLatestVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return app.initFromMainStore(mainKey)
|
return app.initFromMainStore(baseKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// load application version
|
// LoadVersion loads the BaseApp application version. It will panic if called
|
||||||
// panics if called more than once on a running baseapp
|
// more than once on a running baseapp.
|
||||||
func (app *BaseApp) LoadVersion(version int64, mainKey *sdk.KVStoreKey) error {
|
func (app *BaseApp) LoadVersion(version int64, baseKey *sdk.KVStoreKey) error {
|
||||||
err := app.cms.LoadVersion(version)
|
err := app.cms.LoadVersion(version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return app.initFromMainStore(mainKey)
|
return app.initFromMainStore(baseKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the last CommitID of the multistore
|
// LastCommitID returns the last CommitID of the multistore.
|
||||||
func (app *BaseApp) LastCommitID() sdk.CommitID {
|
func (app *BaseApp) LastCommitID() sdk.CommitID {
|
||||||
return app.cms.LastCommitID()
|
return app.cms.LastCommitID()
|
||||||
}
|
}
|
||||||
|
|
||||||
// the last committed block height
|
// LastBlockHeight returns the last committed block height.
|
||||||
func (app *BaseApp) LastBlockHeight() int64 {
|
func (app *BaseApp) LastBlockHeight() int64 {
|
||||||
return app.cms.LastCommitID().Version
|
return app.cms.LastCommitID().Version
|
||||||
}
|
}
|
||||||
|
|
||||||
// initializes the remaining logic from app.cms
|
// initializes the remaining logic from app.cms
|
||||||
func (app *BaseApp) initFromMainStore(mainKey *sdk.KVStoreKey) error {
|
func (app *BaseApp) initFromMainStore(baseKey *sdk.KVStoreKey) error {
|
||||||
|
mainStore := app.cms.GetKVStore(baseKey)
|
||||||
// main store should exist.
|
|
||||||
mainStore := app.cms.GetKVStore(mainKey)
|
|
||||||
if mainStore == nil {
|
if mainStore == nil {
|
||||||
return errors.New("baseapp expects MultiStore with 'main' KVStore")
|
return errors.New("baseapp expects MultiStore with 'main' KVStore")
|
||||||
}
|
}
|
||||||
|
|
||||||
// memoize mainKey
|
// memoize baseKey
|
||||||
if app.mainKey != nil {
|
if app.baseKey != nil {
|
||||||
panic("app.mainKey expected to be nil; duplicate init?")
|
panic("app.baseKey expected to be nil; duplicate init?")
|
||||||
}
|
}
|
||||||
app.mainKey = mainKey
|
app.baseKey = baseKey
|
||||||
|
|
||||||
// load consensus params from the main store
|
// Load the consensus params from the main store. If the consensus params are
|
||||||
|
// nil, it will be saved later during InitChain.
|
||||||
|
//
|
||||||
|
// TODO: assert that InitChain hasn't yet been called.
|
||||||
consensusParamsBz := mainStore.Get(mainConsensusParamsKey)
|
consensusParamsBz := mainStore.Get(mainConsensusParamsKey)
|
||||||
if consensusParamsBz != nil {
|
if consensusParamsBz != nil {
|
||||||
var consensusParams = &abci.ConsensusParams{}
|
var consensusParams = &abci.ConsensusParams{}
|
||||||
|
|
||||||
err := proto.Unmarshal(consensusParamsBz, consensusParams)
|
err := proto.Unmarshal(consensusParamsBz, consensusParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.setConsensusParams(consensusParams)
|
app.setConsensusParams(consensusParams)
|
||||||
} else {
|
|
||||||
// It will get saved later during InitChain.
|
|
||||||
// TODO assert that InitChain hasn't yet been called.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Needed for `gaiad export`, which inits from store but never calls initchain
|
// needed for `gaiad export`, which inits from store but never calls initchain
|
||||||
app.setCheckState(abci.Header{})
|
app.setCheckState(abci.Header{})
|
||||||
|
|
||||||
app.Seal()
|
app.Seal()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -218,42 +230,45 @@ func (app *BaseApp) setMinGasPrices(gasPrices sdk.DecCoins) {
|
||||||
app.minGasPrices = gasPrices
|
app.minGasPrices = gasPrices
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContext returns a new Context with the correct store, the given header, and nil txBytes.
|
// Router returns the router of the BaseApp.
|
||||||
func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context {
|
func (app *BaseApp) Router() Router {
|
||||||
if isCheckTx {
|
if app.sealed {
|
||||||
return sdk.NewContext(app.checkState.ms, header, true, app.Logger).
|
// We cannot return a router when the app is sealed because we can't have
|
||||||
WithMinGasPrices(app.minGasPrices)
|
// any routes modified which would cause unexpected routing behavior.
|
||||||
|
panic("Router() on sealed BaseApp")
|
||||||
}
|
}
|
||||||
|
return app.router
|
||||||
return sdk.NewContext(app.deliverState.ms, header, false, app.Logger)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type state struct {
|
// QueryRouter returns the QueryRouter of a BaseApp.
|
||||||
ms sdk.CacheMultiStore
|
func (app *BaseApp) QueryRouter() QueryRouter { return app.queryRouter }
|
||||||
ctx sdk.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *state) CacheMultiStore() sdk.CacheMultiStore {
|
// Seal seals a BaseApp. It prohibits any further modifications to a BaseApp.
|
||||||
return st.ms.CacheMultiStore()
|
func (app *BaseApp) Seal() { app.sealed = true }
|
||||||
}
|
|
||||||
|
|
||||||
func (st *state) Context() sdk.Context {
|
// IsSealed returns true if the BaseApp is sealed and false otherwise.
|
||||||
return st.ctx
|
func (app *BaseApp) IsSealed() bool { return app.sealed }
|
||||||
}
|
|
||||||
|
|
||||||
|
// setCheckState sets checkState with the cached multistore and
|
||||||
|
// the context wrapping it.
|
||||||
|
// It is called by InitChain() and Commit()
|
||||||
func (app *BaseApp) setCheckState(header abci.Header) {
|
func (app *BaseApp) setCheckState(header abci.Header) {
|
||||||
ms := app.cms.CacheMultiStore()
|
ms := app.cms.CacheMultiStore()
|
||||||
app.checkState = &state{
|
app.checkState = &state{
|
||||||
ms: ms,
|
ms: ms,
|
||||||
ctx: sdk.NewContext(ms, header, true, app.Logger).WithMinGasPrices(app.minGasPrices),
|
ctx: sdk.NewContext(ms, header, true, app.logger).WithMinGasPrices(app.minGasPrices),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setCheckState sets checkState with the cached multistore and
|
||||||
|
// the context wrapping it.
|
||||||
|
// It is called by InitChain() and BeginBlock(),
|
||||||
|
// and deliverState is set nil on Commit().
|
||||||
func (app *BaseApp) setDeliverState(header abci.Header) {
|
func (app *BaseApp) setDeliverState(header abci.Header) {
|
||||||
ms := app.cms.CacheMultiStore()
|
ms := app.cms.CacheMultiStore()
|
||||||
app.deliverState = &state{
|
app.deliverState = &state{
|
||||||
ms: ms,
|
ms: ms,
|
||||||
ctx: sdk.NewContext(ms, header, false, app.Logger),
|
ctx: sdk.NewContext(ms, header, false, app.logger),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,7 +283,7 @@ func (app *BaseApp) storeConsensusParams(consensusParams *abci.ConsensusParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
mainStore := app.cms.GetKVStore(app.mainKey)
|
mainStore := app.cms.GetKVStore(app.baseKey)
|
||||||
mainStore.Set(mainConsensusParamsKey, consensusParamsBz)
|
mainStore.Set(mainConsensusParamsKey, consensusParamsBz)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,11 +295,10 @@ func (app *BaseApp) getMaximumBlockGas() (maxGas uint64) {
|
||||||
return uint64(app.consensusParams.BlockSize.MaxGas)
|
return uint64(app.consensusParams.BlockSize.MaxGas)
|
||||||
}
|
}
|
||||||
|
|
||||||
//______________________________________________________________________________
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// ABCI
|
// ABCI
|
||||||
|
|
||||||
// Implements ABCI
|
// Info implements the ABCI interface.
|
||||||
func (app *BaseApp) Info(req abci.RequestInfo) abci.ResponseInfo {
|
func (app *BaseApp) Info(req abci.RequestInfo) abci.ResponseInfo {
|
||||||
lastCommitID := app.cms.LastCommitID()
|
lastCommitID := app.cms.LastCommitID()
|
||||||
|
|
||||||
|
@ -295,23 +309,23 @@ func (app *BaseApp) Info(req abci.RequestInfo) abci.ResponseInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements ABCI
|
// SetOption implements the ABCI interface.
|
||||||
func (app *BaseApp) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOption) {
|
func (app *BaseApp) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOption) {
|
||||||
// TODO: Implement
|
// TODO: Implement!
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements ABCI
|
// InitChain implements the ABCI interface. It runs the initialization logic
|
||||||
// InitChain runs the initialization logic directly on the CommitMultiStore.
|
// directly on the CommitMultiStore.
|
||||||
func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) {
|
func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) {
|
||||||
|
|
||||||
// Stash the consensus params in the cms main store and memoize.
|
// stash the consensus params in the cms main store and memoize
|
||||||
if req.ConsensusParams != nil {
|
if req.ConsensusParams != nil {
|
||||||
app.setConsensusParams(req.ConsensusParams)
|
app.setConsensusParams(req.ConsensusParams)
|
||||||
app.storeConsensusParams(req.ConsensusParams)
|
app.storeConsensusParams(req.ConsensusParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the deliver state and check state with ChainID and run initChain
|
// initialize the deliver state and check state with ChainID and run initChain
|
||||||
app.setDeliverState(abci.Header{ChainID: req.ChainId})
|
app.setDeliverState(abci.Header{ChainID: req.ChainId})
|
||||||
app.setCheckState(abci.Header{ChainID: req.ChainId})
|
app.setCheckState(abci.Header{ChainID: req.ChainId})
|
||||||
|
|
||||||
|
@ -325,12 +339,12 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC
|
||||||
|
|
||||||
res = app.initChainer(app.deliverState.ctx, req)
|
res = app.initChainer(app.deliverState.ctx, req)
|
||||||
|
|
||||||
// NOTE: we don't commit, but BeginBlock for block 1
|
// NOTE: We don't commit, but BeginBlock for block 1 starts from this
|
||||||
// starts from this deliverState
|
// deliverState.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter peers by address / port
|
// FilterPeerByAddrPort filters peers by address/port.
|
||||||
func (app *BaseApp) FilterPeerByAddrPort(info string) abci.ResponseQuery {
|
func (app *BaseApp) FilterPeerByAddrPort(info string) abci.ResponseQuery {
|
||||||
if app.addrPeerFilter != nil {
|
if app.addrPeerFilter != nil {
|
||||||
return app.addrPeerFilter(info)
|
return app.addrPeerFilter(info)
|
||||||
|
@ -338,15 +352,16 @@ func (app *BaseApp) FilterPeerByAddrPort(info string) abci.ResponseQuery {
|
||||||
return abci.ResponseQuery{}
|
return abci.ResponseQuery{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter peers by public key
|
// FilterPeerByIDfilters peers by node ID.
|
||||||
func (app *BaseApp) FilterPeerByPubKey(info string) abci.ResponseQuery {
|
func (app *BaseApp) FilterPeerByID(info string) abci.ResponseQuery {
|
||||||
if app.pubkeyPeerFilter != nil {
|
if app.idPeerFilter != nil {
|
||||||
return app.pubkeyPeerFilter(info)
|
return app.idPeerFilter(info)
|
||||||
}
|
}
|
||||||
return abci.ResponseQuery{}
|
return abci.ResponseQuery{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Splits a string path using the delimter '/'. i.e. "this/is/funny" becomes []string{"this", "is", "funny"}
|
// Splits a string path using the delimiter '/'.
|
||||||
|
// e.g. "this/is/funny" becomes []string{"this", "is", "funny"}
|
||||||
func splitPath(requestPath string) (path []string) {
|
func splitPath(requestPath string) (path []string) {
|
||||||
path = strings.Split(requestPath, "/")
|
path = strings.Split(requestPath, "/")
|
||||||
// first element is empty string
|
// first element is empty string
|
||||||
|
@ -356,22 +371,26 @@ func splitPath(requestPath string) (path []string) {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements ABCI.
|
// Query implements the ABCI interface. It delegates to CommitMultiStore if it
|
||||||
// Delegates to CommitMultiStore if it implements Queryable
|
// implements Queryable.
|
||||||
func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
|
func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
|
||||||
path := splitPath(req.Path)
|
path := splitPath(req.Path)
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
msg := "no query path provided"
|
msg := "no query path provided"
|
||||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch path[0] {
|
switch path[0] {
|
||||||
// "/app" prefix for special application queries
|
// "/app" prefix for special application queries
|
||||||
case "app":
|
case "app":
|
||||||
return handleQueryApp(app, path, req)
|
return handleQueryApp(app, path, req)
|
||||||
|
|
||||||
case "store":
|
case "store":
|
||||||
return handleQueryStore(app, path, req)
|
return handleQueryStore(app, path, req)
|
||||||
|
|
||||||
case "p2p":
|
case "p2p":
|
||||||
return handleQueryP2P(app, path, req)
|
return handleQueryP2P(app, path, req)
|
||||||
|
|
||||||
case "custom":
|
case "custom":
|
||||||
return handleQueryCustom(app, path, req)
|
return handleQueryCustom(app, path, req)
|
||||||
}
|
}
|
||||||
|
@ -383,6 +402,7 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
|
||||||
func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
|
func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
|
||||||
if len(path) >= 2 {
|
if len(path) >= 2 {
|
||||||
var result sdk.Result
|
var result sdk.Result
|
||||||
|
|
||||||
switch path[1] {
|
switch path[1] {
|
||||||
case "simulate":
|
case "simulate":
|
||||||
txBytes := req.Data
|
txBytes := req.Data
|
||||||
|
@ -390,19 +410,20 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abc
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result = err.Result()
|
result = err.Result()
|
||||||
} else {
|
} else {
|
||||||
result = app.Simulate(tx)
|
result = app.Simulate(txBytes, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "version":
|
case "version":
|
||||||
return abci.ResponseQuery{
|
return abci.ResponseQuery{
|
||||||
Code: uint32(sdk.CodeOK),
|
Code: uint32(sdk.CodeOK),
|
||||||
Codespace: string(sdk.CodespaceRoot),
|
Codespace: string(sdk.CodespaceRoot),
|
||||||
Value: []byte(version.GetVersion()),
|
Value: []byte(version.Version),
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
result = sdk.ErrUnknownRequest(fmt.Sprintf("Unknown query: %s", path)).Result()
|
result = sdk.ErrUnknownRequest(fmt.Sprintf("Unknown query: %s", path)).Result()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode with json
|
|
||||||
value := codec.Cdc.MustMarshalBinaryLengthPrefixed(result)
|
value := codec.Cdc.MustMarshalBinaryLengthPrefixed(result)
|
||||||
return abci.ResponseQuery{
|
return abci.ResponseQuery{
|
||||||
Code: uint32(sdk.CodeOK),
|
Code: uint32(sdk.CodeOK),
|
||||||
|
@ -410,6 +431,7 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abc
|
||||||
Value: value,
|
Value: value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := "Expected second parameter to be either simulate or version, neither was present"
|
msg := "Expected second parameter to be either simulate or version, neither was present"
|
||||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||||
}
|
}
|
||||||
|
@ -421,51 +443,57 @@ func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) (res a
|
||||||
msg := "multistore doesn't support queries"
|
msg := "multistore doesn't support queries"
|
||||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Path = "/" + strings.Join(path[1:], "/")
|
req.Path = "/" + strings.Join(path[1:], "/")
|
||||||
return queryable.Query(req)
|
return queryable.Query(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint: unparam
|
func handleQueryP2P(app *BaseApp, path []string, _ abci.RequestQuery) (res abci.ResponseQuery) {
|
||||||
func handleQueryP2P(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
|
|
||||||
// "/p2p" prefix for p2p queries
|
// "/p2p" prefix for p2p queries
|
||||||
if len(path) >= 4 {
|
if len(path) >= 4 {
|
||||||
if path[1] == "filter" {
|
cmd, typ, arg := path[1], path[2], path[3]
|
||||||
if path[2] == "addr" {
|
switch cmd {
|
||||||
return app.FilterPeerByAddrPort(path[3])
|
case "filter":
|
||||||
|
switch typ {
|
||||||
|
case "addr":
|
||||||
|
return app.FilterPeerByAddrPort(arg)
|
||||||
|
case "id":
|
||||||
|
return app.FilterPeerByID(arg)
|
||||||
}
|
}
|
||||||
if path[2] == "pubkey" {
|
default:
|
||||||
// TODO: this should be changed to `id`
|
|
||||||
// NOTE: this changed in tendermint and we didn't notice...
|
|
||||||
return app.FilterPeerByPubKey(path[3])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
msg := "Expected second parameter to be filter"
|
msg := "Expected second parameter to be filter"
|
||||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := "Expected path is p2p filter <addr|pubkey> <parameter>"
|
msg := "Expected path is p2p filter <addr|id> <parameter>"
|
||||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
|
func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
|
||||||
// path[0] should be "custom" because "/custom" prefix is required for keeper queries.
|
// path[0] should be "custom" because "/custom" prefix is required for keeper
|
||||||
// the queryRouter routes using path[1]. For example, in the path "custom/gov/proposal", queryRouter routes using "gov"
|
// queries.
|
||||||
|
//
|
||||||
|
// The queryRouter routes using path[1]. For example, in the path
|
||||||
|
// "custom/gov/proposal", queryRouter routes using "gov".
|
||||||
if len(path) < 2 || path[1] == "" {
|
if len(path) < 2 || path[1] == "" {
|
||||||
return sdk.ErrUnknownRequest("No route for custom query specified").QueryResult()
|
return sdk.ErrUnknownRequest("No route for custom query specified").QueryResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
querier := app.queryRouter.Route(path[1])
|
querier := app.queryRouter.Route(path[1])
|
||||||
if querier == nil {
|
if querier == nil {
|
||||||
return sdk.ErrUnknownRequest(fmt.Sprintf("no custom querier found for route %s", path[1])).QueryResult()
|
return sdk.ErrUnknownRequest(fmt.Sprintf("no custom querier found for route %s", path[1])).QueryResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache wrap the commit-multistore for safety.
|
// cache wrap the commit-multistore for safety
|
||||||
ctx := sdk.NewContext(
|
ctx := sdk.NewContext(
|
||||||
app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger,
|
app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.logger,
|
||||||
).WithMinGasPrices(app.minGasPrices)
|
).WithMinGasPrices(app.minGasPrices)
|
||||||
|
|
||||||
// Passes the rest of the path as an argument to the querier.
|
// Passes the rest of the path as an argument to the querier.
|
||||||
// For example, in the path "custom/gov/proposal/test", the gov querier gets []string{"proposal", "test"} as the path
|
//
|
||||||
|
// For example, in the path "custom/gov/proposal/test", the gov querier gets
|
||||||
|
// []string{"proposal", "test"} as the path.
|
||||||
resBytes, err := querier(ctx, path[2:], req)
|
resBytes, err := querier(ctx, path[2:], req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return abci.ResponseQuery{
|
return abci.ResponseQuery{
|
||||||
|
@ -474,6 +502,7 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res
|
||||||
Log: err.ABCILog(),
|
Log: err.ABCILog(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return abci.ResponseQuery{
|
return abci.ResponseQuery{
|
||||||
Code: uint32(sdk.CodeOK),
|
Code: uint32(sdk.CodeOK),
|
||||||
Value: resBytes,
|
Value: resBytes,
|
||||||
|
@ -483,8 +512,7 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res
|
||||||
// BeginBlock implements the ABCI application interface.
|
// BeginBlock implements the ABCI application interface.
|
||||||
func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) {
|
func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) {
|
||||||
if app.cms.TracingEnabled() {
|
if app.cms.TracingEnabled() {
|
||||||
app.cms.ResetTraceContext()
|
app.cms.SetTracingContext(sdk.TraceContext(
|
||||||
app.cms.WithTracingContext(sdk.TraceContext(
|
|
||||||
map[string]interface{}{"blockHeight": req.Header.Height},
|
map[string]interface{}{"blockHeight": req.Header.Height},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -517,20 +545,20 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the signed validators for addition to context in deliverTx
|
// set the signed validators for addition to context in deliverTx
|
||||||
// TODO: communicate this result to the address to pubkey map in slashing
|
|
||||||
app.voteInfos = req.LastCommitInfo.GetVotes()
|
app.voteInfos = req.LastCommitInfo.GetVotes()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckTx implements ABCI
|
// CheckTx implements the ABCI interface. It runs the "basic checks" to see
|
||||||
// CheckTx runs the "basic checks" to see whether or not a transaction can possibly be executed,
|
// whether or not a transaction can possibly be executed, first decoding, then
|
||||||
// first decoding, then the ante handler (which checks signatures/fees/ValidateBasic),
|
// the ante handler (which checks signatures/fees/ValidateBasic), then finally
|
||||||
// then finally the route match to see whether a handler exists. CheckTx does not run the actual
|
// the route match to see whether a handler exists.
|
||||||
// Msg handler function(s).
|
//
|
||||||
|
// NOTE:CheckTx does not run the actual Msg handler function(s).
|
||||||
func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
|
func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
|
||||||
// Decode the Tx.
|
|
||||||
var result sdk.Result
|
var result sdk.Result
|
||||||
var tx, err = app.txDecoder(txBytes)
|
|
||||||
|
tx, err := app.txDecoder(txBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result = err.Result()
|
result = err.Result()
|
||||||
} else {
|
} else {
|
||||||
|
@ -547,22 +575,17 @@ func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements ABCI
|
// DeliverTx implements the ABCI interface.
|
||||||
func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
|
func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
|
||||||
|
|
||||||
// Decode the Tx.
|
|
||||||
var tx, err = app.txDecoder(txBytes)
|
|
||||||
var result sdk.Result
|
var result sdk.Result
|
||||||
|
|
||||||
|
tx, err := app.txDecoder(txBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result = err.Result()
|
result = err.Result()
|
||||||
} else {
|
} else {
|
||||||
result = app.runTx(runTxModeDeliver, txBytes, tx)
|
result = app.runTx(runTxModeDeliver, txBytes, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Even though the Result.Code is not OK, there are still effects,
|
|
||||||
// namely fee deductions and sequence incrementing.
|
|
||||||
|
|
||||||
// Tell the blockchain engine (i.e. Tendermint).
|
|
||||||
return abci.ResponseDeliverTx{
|
return abci.ResponseDeliverTx{
|
||||||
Code: uint32(result.Code),
|
Code: uint32(result.Code),
|
||||||
Codespace: string(result.Codespace),
|
Codespace: string(result.Codespace),
|
||||||
|
@ -574,11 +597,10 @@ func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic validator for msgs
|
// validateBasicTxMsgs executes basic validator calls for messages.
|
||||||
func validateBasicTxMsgs(msgs []sdk.Msg) sdk.Error {
|
func validateBasicTxMsgs(msgs []sdk.Msg) sdk.Error {
|
||||||
if msgs == nil || len(msgs) == 0 {
|
if msgs == nil || len(msgs) == 0 {
|
||||||
// TODO: probably shouldn't be ErrInternal. Maybe new ErrInvalidMessage, or ?
|
return sdk.ErrUnknownRequest("Tx.GetMsgs() must return at least one message in list")
|
||||||
return sdk.ErrInternal("Tx.GetMsgs() must return at least one message in list")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, msg := range msgs {
|
for _, msg := range msgs {
|
||||||
|
@ -598,22 +620,25 @@ func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) (ctx sdk.Con
|
||||||
WithTxBytes(txBytes).
|
WithTxBytes(txBytes).
|
||||||
WithVoteInfos(app.voteInfos).
|
WithVoteInfos(app.voteInfos).
|
||||||
WithConsensusParams(app.consensusParams)
|
WithConsensusParams(app.consensusParams)
|
||||||
|
|
||||||
if mode == runTxModeSimulate {
|
if mode == runTxModeSimulate {
|
||||||
ctx, _ = ctx.CacheContext()
|
ctx, _ = ctx.CacheContext()
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterates through msgs and executes them
|
// runMsgs iterates through all the messages and executes them.
|
||||||
func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (result sdk.Result) {
|
func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (result sdk.Result) {
|
||||||
// accumulate results
|
|
||||||
logs := make([]string, 0, len(msgs))
|
logs := make([]string, 0, len(msgs))
|
||||||
|
|
||||||
var data []byte // NOTE: we just append them all (?!)
|
var data []byte // NOTE: we just append them all (?!)
|
||||||
var tags sdk.Tags // also just append them all
|
var tags sdk.Tags // also just append them all
|
||||||
var code sdk.CodeType
|
var code sdk.CodeType
|
||||||
var codespace sdk.CodespaceType
|
var codespace sdk.CodespaceType
|
||||||
|
|
||||||
for msgIdx, msg := range msgs {
|
for msgIdx, msg := range msgs {
|
||||||
// Match route.
|
// match message route
|
||||||
msgRoute := msg.Route()
|
msgRoute := msg.Route()
|
||||||
handler := app.router.Route(msgRoute)
|
handler := app.router.Route(msgRoute)
|
||||||
if handler == nil {
|
if handler == nil {
|
||||||
|
@ -621,20 +646,20 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re
|
||||||
}
|
}
|
||||||
|
|
||||||
var msgResult sdk.Result
|
var msgResult sdk.Result
|
||||||
// Skip actual execution for CheckTx
|
|
||||||
|
// skip actual execution for CheckTx mode
|
||||||
if mode != runTxModeCheck {
|
if mode != runTxModeCheck {
|
||||||
msgResult = handler(ctx, msg)
|
msgResult = handler(ctx, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: GasWanted is determined by ante handler and
|
// NOTE: GasWanted is determined by ante handler and GasUsed by the GasMeter.
|
||||||
// GasUsed by the GasMeter
|
|
||||||
|
|
||||||
// Append Data and Tags
|
// Result.Data must be length prefixed in order to separate each result
|
||||||
data = append(data, msgResult.Data...)
|
data = append(data, msgResult.Data...)
|
||||||
tags = append(tags, sdk.MakeTag(sdk.TagAction, []byte(msg.Type())))
|
tags = append(tags, sdk.MakeTag(sdk.TagAction, msg.Type()))
|
||||||
tags = append(tags, msgResult.Tags...)
|
tags = append(tags, msgResult.Tags...)
|
||||||
|
|
||||||
// Stop execution and return on first failed message.
|
// stop execution and return on first failed message
|
||||||
if !msgResult.IsOK() {
|
if !msgResult.IsOK() {
|
||||||
logs = append(logs, fmt.Sprintf("Msg %d failed: %s", msgIdx, msgResult.Log))
|
logs = append(logs, fmt.Sprintf("Msg %d failed: %s", msgIdx, msgResult.Log))
|
||||||
code = msgResult.Code
|
code = msgResult.Code
|
||||||
|
@ -642,19 +667,17 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct usable logs in multi-message transactions.
|
// construct usable logs in multi-message transactions
|
||||||
logs = append(logs, fmt.Sprintf("Msg %d: %s", msgIdx, msgResult.Log))
|
logs = append(logs, fmt.Sprintf("Msg %d: %s", msgIdx, msgResult.Log))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the final gas values.
|
|
||||||
result = sdk.Result{
|
result = sdk.Result{
|
||||||
Code: code,
|
Code: code,
|
||||||
Codespace: codespace,
|
Codespace: codespace,
|
||||||
Data: data,
|
Data: data,
|
||||||
Log: strings.Join(logs, "\n"),
|
Log: strings.Join(logs, "\n"),
|
||||||
GasUsed: ctx.GasMeter().GasConsumed(),
|
GasUsed: ctx.GasMeter().GasConsumed(),
|
||||||
// TODO: FeeAmount/FeeDenom
|
Tags: tags,
|
||||||
Tags: tags,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
@ -679,7 +702,7 @@ func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (
|
||||||
// TODO: https://github.com/cosmos/cosmos-sdk/issues/2824
|
// TODO: https://github.com/cosmos/cosmos-sdk/issues/2824
|
||||||
msCache := ms.CacheMultiStore()
|
msCache := ms.CacheMultiStore()
|
||||||
if msCache.TracingEnabled() {
|
if msCache.TracingEnabled() {
|
||||||
msCache = msCache.WithTracingContext(
|
msCache = msCache.SetTracingContext(
|
||||||
sdk.TraceContext(
|
sdk.TraceContext(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"txHash": fmt.Sprintf("%X", tmhash.Sum(txBytes)),
|
"txHash": fmt.Sprintf("%X", tmhash.Sum(txBytes)),
|
||||||
|
@ -719,7 +742,10 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
switch rType := r.(type) {
|
switch rType := r.(type) {
|
||||||
case sdk.ErrorOutOfGas:
|
case sdk.ErrorOutOfGas:
|
||||||
log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor)
|
log := fmt.Sprintf(
|
||||||
|
"out of gas in location: %v; gasWanted: %d, gasUsed: %d",
|
||||||
|
rType.Descriptor, gasWanted, ctx.GasMeter().GasConsumed(),
|
||||||
|
)
|
||||||
result = sdk.ErrOutOfGas(log).Result()
|
result = sdk.ErrOutOfGas(log).Result()
|
||||||
default:
|
default:
|
||||||
log := fmt.Sprintf("recovered: %v\nstack:\n%v", r, string(debug.Stack()))
|
log := fmt.Sprintf("recovered: %v\nstack:\n%v", r, string(debug.Stack()))
|
||||||
|
@ -731,11 +757,11 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
||||||
result.GasUsed = ctx.GasMeter().GasConsumed()
|
result.GasUsed = ctx.GasMeter().GasConsumed()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// If BlockGasMeter() panics it will be caught by the above recover and
|
// If BlockGasMeter() panics it will be caught by the above recover and will
|
||||||
// return an error - in any case BlockGasMeter will consume gas past
|
// return an error - in any case BlockGasMeter will consume gas past the limit.
|
||||||
// the limit.
|
//
|
||||||
// NOTE: this must exist in a separate defer function for the
|
// NOTE: This must exist in a separate defer function for the above recovery
|
||||||
// above recovery to recover from this one
|
// to recover from this one.
|
||||||
defer func() {
|
defer func() {
|
||||||
if mode == runTxModeDeliver {
|
if mode == runTxModeDeliver {
|
||||||
ctx.BlockGasMeter().ConsumeGas(
|
ctx.BlockGasMeter().ConsumeGas(
|
||||||
|
@ -744,7 +770,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
||||||
)
|
)
|
||||||
|
|
||||||
if ctx.BlockGasMeter().GasConsumed() < startingGas {
|
if ctx.BlockGasMeter().GasConsumed() < startingGas {
|
||||||
panic(sdk.ErrorGasOverflow{"tx gas summation"})
|
panic(sdk.ErrorGasOverflow{Descriptor: "tx gas summation"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -754,7 +780,6 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
||||||
return err.Result()
|
return err.Result()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the ante handler if one is defined.
|
|
||||||
if app.anteHandler != nil {
|
if app.anteHandler != nil {
|
||||||
var anteCtx sdk.Context
|
var anteCtx sdk.Context
|
||||||
var msCache sdk.CacheMultiStore
|
var msCache sdk.CacheMultiStore
|
||||||
|
@ -780,12 +805,13 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
||||||
ctx = newCtx.WithMultiStore(ms)
|
ctx = newCtx.WithMultiStore(ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gasWanted = result.GasWanted
|
||||||
|
|
||||||
if abort {
|
if abort {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
msCache.Write()
|
msCache.Write()
|
||||||
gasWanted = result.GasWanted
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode == runTxModeCheck {
|
if mode == runTxModeCheck {
|
||||||
|
@ -810,10 +836,10 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// EndBlock implements the ABCI application interface.
|
// EndBlock implements the ABCI interface.
|
||||||
func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) {
|
func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) {
|
||||||
if app.deliverState.ms.TracingEnabled() {
|
if app.deliverState.ms.TracingEnabled() {
|
||||||
app.deliverState.ms = app.deliverState.ms.ResetTraceContext().(sdk.CacheMultiStore)
|
app.deliverState.ms = app.deliverState.ms.SetTracingContext(nil).(sdk.CacheMultiStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
if app.endBlocker != nil {
|
if app.endBlocker != nil {
|
||||||
|
@ -823,27 +849,41 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements ABCI
|
// Commit implements the ABCI interface.
|
||||||
func (app *BaseApp) Commit() (res abci.ResponseCommit) {
|
func (app *BaseApp) Commit() (res abci.ResponseCommit) {
|
||||||
header := app.deliverState.ctx.BlockHeader()
|
header := app.deliverState.ctx.BlockHeader()
|
||||||
|
|
||||||
// Write the Deliver state and commit the MultiStore
|
// write the Deliver state and commit the MultiStore
|
||||||
app.deliverState.ms.Write()
|
app.deliverState.ms.Write()
|
||||||
commitID := app.cms.Commit()
|
commitID := app.cms.Commit()
|
||||||
// TODO: this is missing a module identifier and dumps byte array
|
app.logger.Debug("Commit synced", "commit", fmt.Sprintf("%X", commitID))
|
||||||
app.Logger.Debug("Commit synced",
|
|
||||||
"commit", fmt.Sprintf("%X", commitID),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reset the Check state to the latest committed
|
// Reset the Check state to the latest committed.
|
||||||
|
//
|
||||||
// NOTE: safe because Tendermint holds a lock on the mempool for Commit.
|
// NOTE: safe because Tendermint holds a lock on the mempool for Commit.
|
||||||
// Use the header from this latest block.
|
// Use the header from this latest block.
|
||||||
app.setCheckState(header)
|
app.setCheckState(header)
|
||||||
|
|
||||||
// Empty the Deliver state
|
// empty/reset the deliver state
|
||||||
app.deliverState = nil
|
app.deliverState = nil
|
||||||
|
|
||||||
return abci.ResponseCommit{
|
return abci.ResponseCommit{
|
||||||
Data: commitID.Hash,
|
Data: commitID.Hash,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// State
|
||||||
|
|
||||||
|
type state struct {
|
||||||
|
ms sdk.CacheMultiStore
|
||||||
|
ctx sdk.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *state) CacheMultiStore() sdk.CacheMultiStore {
|
||||||
|
return st.ms.CacheMultiStore()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *state) Context() sdk.Context {
|
||||||
|
return st.ctx
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/store"
|
store "github.com/cosmos/cosmos-sdk/store/types"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -21,14 +21,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// make some cap keys
|
|
||||||
capKey1 = sdk.NewKVStoreKey("key1")
|
capKey1 = sdk.NewKVStoreKey("key1")
|
||||||
capKey2 = sdk.NewKVStoreKey("key2")
|
capKey2 = sdk.NewKVStoreKey("key2")
|
||||||
)
|
)
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------------
|
|
||||||
// Helpers for setup. Most tests should be able to use setupBaseApp
|
|
||||||
|
|
||||||
func defaultLogger() log.Logger {
|
func defaultLogger() log.Logger {
|
||||||
return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
|
return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
|
||||||
}
|
}
|
||||||
|
@ -70,9 +66,6 @@ func setupBaseApp(t *testing.T, options ...func(*BaseApp)) *BaseApp {
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------------
|
|
||||||
// test mounting and loading stores
|
|
||||||
|
|
||||||
func TestMountStores(t *testing.T) {
|
func TestMountStores(t *testing.T) {
|
||||||
app := setupBaseApp(t)
|
app := setupBaseApp(t)
|
||||||
|
|
||||||
|
@ -137,6 +130,41 @@ func TestLoadVersion(t *testing.T) {
|
||||||
testLoadVersionHelper(t, app, int64(2), commitID2)
|
testLoadVersionHelper(t, app, int64(2), commitID2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadVersionInvalid(t *testing.T) {
|
||||||
|
logger := log.NewNopLogger()
|
||||||
|
pruningOpt := SetPruning(store.PruneSyncable)
|
||||||
|
db := dbm.NewMemDB()
|
||||||
|
name := t.Name()
|
||||||
|
app := NewBaseApp(name, logger, db, nil, pruningOpt)
|
||||||
|
|
||||||
|
capKey := sdk.NewKVStoreKey(MainStoreKey)
|
||||||
|
app.MountStores(capKey)
|
||||||
|
err := app.LoadLatestVersion(capKey)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// require error when loading an invalid version
|
||||||
|
err = app.LoadVersion(-1, capKey)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
header := abci.Header{Height: 1}
|
||||||
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
||||||
|
res := app.Commit()
|
||||||
|
commitID1 := sdk.CommitID{1, res.Data}
|
||||||
|
|
||||||
|
// create a new app with the stores mounted under the same cap key
|
||||||
|
app = NewBaseApp(name, logger, db, nil, pruningOpt)
|
||||||
|
app.MountStores(capKey)
|
||||||
|
|
||||||
|
// require we can load the latest version
|
||||||
|
err = app.LoadVersion(1, capKey)
|
||||||
|
require.Nil(t, err)
|
||||||
|
testLoadVersionHelper(t, app, int64(1), commitID1)
|
||||||
|
|
||||||
|
// require error when loading an invalid version
|
||||||
|
err = app.LoadVersion(2, capKey)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, expectedID sdk.CommitID) {
|
func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, expectedID sdk.CommitID) {
|
||||||
lastHeight := app.LastBlockHeight()
|
lastHeight := app.LastBlockHeight()
|
||||||
lastID := app.LastCommitID()
|
lastID := app.LastCommitID()
|
||||||
|
@ -157,39 +185,21 @@ func testChangeNameHelper(name string) func(*BaseApp) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that the app hash is static
|
|
||||||
// TODO: https://github.com/cosmos/cosmos-sdk/issues/520
|
|
||||||
/*func TestStaticAppHash(t *testing.T) {
|
|
||||||
app := newBaseApp(t.Name())
|
|
||||||
|
|
||||||
// make a cap key and mount the store
|
|
||||||
capKey := sdk.NewKVStoreKey(MainStoreKey)
|
|
||||||
app.MountStores(capKey)
|
|
||||||
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
// execute some blocks
|
|
||||||
header := abci.Header{Height: 1}
|
|
||||||
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
||||||
res := app.Commit()
|
|
||||||
commitID1 := sdk.CommitID{1, res.Data}
|
|
||||||
|
|
||||||
header = abci.Header{Height: 2}
|
|
||||||
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
||||||
res = app.Commit()
|
|
||||||
commitID2 := sdk.CommitID{2, res.Data}
|
|
||||||
|
|
||||||
require.Equal(t, commitID1.Hash, commitID2.Hash)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------------
|
|
||||||
// test some basic abci/baseapp functionality
|
|
||||||
|
|
||||||
// Test that txs can be unmarshalled and read and that
|
// Test that txs can be unmarshalled and read and that
|
||||||
// correct error codes are returned when not
|
// correct error codes are returned when not
|
||||||
func TestTxDecoder(t *testing.T) {
|
func TestTxDecoder(t *testing.T) {
|
||||||
// TODO
|
codec := codec.New()
|
||||||
|
registerTestCodec(codec)
|
||||||
|
|
||||||
|
app := newBaseApp(t.Name())
|
||||||
|
tx := newTxCounter(1, 0)
|
||||||
|
txBytes := codec.MustMarshalBinaryLengthPrefixed(tx)
|
||||||
|
|
||||||
|
dTx, err := app.txDecoder(txBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cTx := dTx.(txTest)
|
||||||
|
require.Equal(t, tx.Counter, cTx.Counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that Info returns the latest committed state.
|
// Test that Info returns the latest committed state.
|
||||||
|
@ -210,8 +220,46 @@ func TestInfo(t *testing.T) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------------
|
func TestBaseAppOptionSeal(t *testing.T) {
|
||||||
// InitChain, BeginBlock, EndBlock
|
app := setupBaseApp(t)
|
||||||
|
|
||||||
|
require.Panics(t, func() {
|
||||||
|
app.SetName("")
|
||||||
|
})
|
||||||
|
require.Panics(t, func() {
|
||||||
|
app.SetDB(nil)
|
||||||
|
})
|
||||||
|
require.Panics(t, func() {
|
||||||
|
app.SetCMS(nil)
|
||||||
|
})
|
||||||
|
require.Panics(t, func() {
|
||||||
|
app.SetInitChainer(nil)
|
||||||
|
})
|
||||||
|
require.Panics(t, func() {
|
||||||
|
app.SetBeginBlocker(nil)
|
||||||
|
})
|
||||||
|
require.Panics(t, func() {
|
||||||
|
app.SetEndBlocker(nil)
|
||||||
|
})
|
||||||
|
require.Panics(t, func() {
|
||||||
|
app.SetAnteHandler(nil)
|
||||||
|
})
|
||||||
|
require.Panics(t, func() {
|
||||||
|
app.SetAddrPeerFilter(nil)
|
||||||
|
})
|
||||||
|
require.Panics(t, func() {
|
||||||
|
app.SetIDPeerFilter(nil)
|
||||||
|
})
|
||||||
|
require.Panics(t, func() {
|
||||||
|
app.SetFauxMerkleMode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetMinGasPrices(t *testing.T) {
|
||||||
|
minGasPrices := sdk.DecCoins{sdk.NewDecCoin("stake", 5000)}
|
||||||
|
app := newBaseApp(t.Name(), SetMinGasPrices(minGasPrices.String()))
|
||||||
|
require.Equal(t, minGasPrices, app.minGasPrices)
|
||||||
|
}
|
||||||
|
|
||||||
func TestInitChainer(t *testing.T) {
|
func TestInitChainer(t *testing.T) {
|
||||||
name := t.Name()
|
name := t.Name()
|
||||||
|
@ -280,11 +328,6 @@ func TestInitChainer(t *testing.T) {
|
||||||
require.Equal(t, value, res.Value)
|
require.Equal(t, value, res.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------------
|
|
||||||
// Mock tx, msgs, and mapper for the baseapp tests.
|
|
||||||
// Self-contained, just uses counters.
|
|
||||||
// We don't care about signatures, coins, accounts, etc. in the baseapp.
|
|
||||||
|
|
||||||
// Simple tx with a list of Msgs.
|
// Simple tx with a list of Msgs.
|
||||||
type txTest struct {
|
type txTest struct {
|
||||||
Msgs []sdk.Msg
|
Msgs []sdk.Msg
|
||||||
|
@ -417,9 +460,6 @@ func handlerMsgCounter(t *testing.T, capKey *sdk.KVStoreKey, deliverKey []byte)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------
|
|
||||||
// simple int mapper
|
|
||||||
|
|
||||||
func i2b(i int64) []byte {
|
func i2b(i int64) []byte {
|
||||||
return []byte{byte(i)}
|
return []byte{byte(i)}
|
||||||
}
|
}
|
||||||
|
@ -655,20 +695,20 @@ func TestSimulateTx(t *testing.T) {
|
||||||
app.BeginBlock(abci.RequestBeginBlock{})
|
app.BeginBlock(abci.RequestBeginBlock{})
|
||||||
|
|
||||||
tx := newTxCounter(count, count)
|
tx := newTxCounter(count, count)
|
||||||
|
txBytes, err := cdc.MarshalBinaryLengthPrefixed(tx)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
// simulate a message, check gas reported
|
// simulate a message, check gas reported
|
||||||
result := app.Simulate(tx)
|
result := app.Simulate(txBytes, tx)
|
||||||
require.True(t, result.IsOK(), result.Log)
|
require.True(t, result.IsOK(), result.Log)
|
||||||
require.Equal(t, gasConsumed, result.GasUsed)
|
require.Equal(t, gasConsumed, result.GasUsed)
|
||||||
|
|
||||||
// simulate again, same result
|
// simulate again, same result
|
||||||
result = app.Simulate(tx)
|
result = app.Simulate(txBytes, tx)
|
||||||
require.True(t, result.IsOK(), result.Log)
|
require.True(t, result.IsOK(), result.Log)
|
||||||
require.Equal(t, gasConsumed, result.GasUsed)
|
require.Equal(t, gasConsumed, result.GasUsed)
|
||||||
|
|
||||||
// simulate by calling Query with encoded tx
|
// simulate by calling Query with encoded tx
|
||||||
txBytes, err := cdc.MarshalBinaryLengthPrefixed(tx)
|
|
||||||
require.Nil(t, err)
|
|
||||||
query := abci.RequestQuery{
|
query := abci.RequestQuery{
|
||||||
Path: "/app/simulate",
|
Path: "/app/simulate",
|
||||||
Data: txBytes,
|
Data: txBytes,
|
||||||
|
@ -686,10 +726,6 @@ func TestSimulateTx(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------------------------------------------------------------------------
|
|
||||||
// Tx failure cases
|
|
||||||
// TODO: add more
|
|
||||||
|
|
||||||
func TestRunInvalidTransaction(t *testing.T) {
|
func TestRunInvalidTransaction(t *testing.T) {
|
||||||
anteOpt := func(bapp *BaseApp) {
|
anteOpt := func(bapp *BaseApp) {
|
||||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||||
|
@ -707,7 +743,7 @@ func TestRunInvalidTransaction(t *testing.T) {
|
||||||
{
|
{
|
||||||
emptyTx := &txTest{}
|
emptyTx := &txTest{}
|
||||||
err := app.Deliver(emptyTx)
|
err := app.Deliver(emptyTx)
|
||||||
require.EqualValues(t, sdk.CodeInternal, err.Code)
|
require.EqualValues(t, sdk.CodeUnknownRequest, err.Code)
|
||||||
require.EqualValues(t, sdk.CodespaceRoot, err.Codespace)
|
require.EqualValues(t, sdk.CodespaceRoot, err.Codespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -777,11 +813,6 @@ func TestTxGasLimits(t *testing.T) {
|
||||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||||
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted))
|
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted))
|
||||||
|
|
||||||
// NOTE/TODO/XXX:
|
|
||||||
// AnteHandlers must have their own defer/recover in order
|
|
||||||
// for the BaseApp to know how much gas was used used!
|
|
||||||
// This is because the GasMeter is created in the AnteHandler,
|
|
||||||
// but if it panics the context won't be set properly in runTx's recover ...
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
switch rType := r.(type) {
|
switch rType := r.(type) {
|
||||||
|
@ -866,11 +897,6 @@ func TestMaxBlockGasLimits(t *testing.T) {
|
||||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||||
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted))
|
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted))
|
||||||
|
|
||||||
// NOTE/TODO/XXX:
|
|
||||||
// AnteHandlers must have their own defer/recover in order
|
|
||||||
// for the BaseApp to know how much gas was used used!
|
|
||||||
// This is because the GasMeter is created in the AnteHandler,
|
|
||||||
// but if it panics the context won't be set properly in runTx's recover ...
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
switch rType := r.(type) {
|
switch rType := r.(type) {
|
||||||
|
@ -1031,3 +1057,162 @@ func TestBaseAppAnteHandler(t *testing.T) {
|
||||||
app.EndBlock(abci.RequestEndBlock{})
|
app.EndBlock(abci.RequestEndBlock{})
|
||||||
app.Commit()
|
app.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGasConsumptionBadTx(t *testing.T) {
|
||||||
|
gasWanted := uint64(5)
|
||||||
|
anteOpt := func(bapp *BaseApp) {
|
||||||
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||||
|
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasWanted))
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
switch rType := r.(type) {
|
||||||
|
case sdk.ErrorOutOfGas:
|
||||||
|
log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor)
|
||||||
|
res = sdk.ErrOutOfGas(log).Result()
|
||||||
|
res.GasWanted = gasWanted
|
||||||
|
res.GasUsed = newCtx.GasMeter().GasConsumed()
|
||||||
|
default:
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
txTest := tx.(txTest)
|
||||||
|
newCtx.GasMeter().ConsumeGas(uint64(txTest.Counter), "counter-ante")
|
||||||
|
if txTest.FailOnAnte {
|
||||||
|
return newCtx, sdk.ErrInternal("ante handler failure").Result(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
res = sdk.Result{
|
||||||
|
GasWanted: gasWanted,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
routerOpt := func(bapp *BaseApp) {
|
||||||
|
bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||||
|
count := msg.(msgCounter).Counter
|
||||||
|
ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler")
|
||||||
|
return sdk.Result{}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cdc := codec.New()
|
||||||
|
registerTestCodec(cdc)
|
||||||
|
|
||||||
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||||
|
app.InitChain(abci.RequestInitChain{
|
||||||
|
ConsensusParams: &abci.ConsensusParams{
|
||||||
|
BlockSize: &abci.BlockSizeParams{
|
||||||
|
MaxGas: 9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
app.InitChain(abci.RequestInitChain{})
|
||||||
|
app.BeginBlock(abci.RequestBeginBlock{})
|
||||||
|
|
||||||
|
tx := newTxCounter(5, 0)
|
||||||
|
tx.setFailOnAnte(true)
|
||||||
|
txBytes, err := cdc.MarshalBinaryLengthPrefixed(tx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
res := app.DeliverTx(txBytes)
|
||||||
|
require.False(t, res.IsOK(), fmt.Sprintf("%v", res))
|
||||||
|
|
||||||
|
// require next tx to fail due to black gas limit
|
||||||
|
tx = newTxCounter(5, 0)
|
||||||
|
txBytes, err = cdc.MarshalBinaryLengthPrefixed(tx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
res = app.DeliverTx(txBytes)
|
||||||
|
require.False(t, res.IsOK(), fmt.Sprintf("%v", res))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that we can only query from the latest committed state.
|
||||||
|
func TestQuery(t *testing.T) {
|
||||||
|
key, value := []byte("hello"), []byte("goodbye")
|
||||||
|
anteOpt := func(bapp *BaseApp) {
|
||||||
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||||
|
store := ctx.KVStore(capKey1)
|
||||||
|
store.Set(key, value)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
routerOpt := func(bapp *BaseApp) {
|
||||||
|
bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||||
|
store := ctx.KVStore(capKey1)
|
||||||
|
store.Set(key, value)
|
||||||
|
return sdk.Result{}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||||
|
|
||||||
|
app.InitChain(abci.RequestInitChain{})
|
||||||
|
|
||||||
|
// NOTE: "/store/key1" tells us KVStore
|
||||||
|
// and the final "/key" says to use the data as the
|
||||||
|
// key in the given KVStore ...
|
||||||
|
query := abci.RequestQuery{
|
||||||
|
Path: "/store/key1/key",
|
||||||
|
Data: key,
|
||||||
|
}
|
||||||
|
tx := newTxCounter(0, 0)
|
||||||
|
|
||||||
|
// query is empty before we do anything
|
||||||
|
res := app.Query(query)
|
||||||
|
require.Equal(t, 0, len(res.Value))
|
||||||
|
|
||||||
|
// query is still empty after a CheckTx
|
||||||
|
resTx := app.Check(tx)
|
||||||
|
require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx))
|
||||||
|
res = app.Query(query)
|
||||||
|
require.Equal(t, 0, len(res.Value))
|
||||||
|
|
||||||
|
// query is still empty after a DeliverTx before we commit
|
||||||
|
app.BeginBlock(abci.RequestBeginBlock{})
|
||||||
|
resTx = app.Deliver(tx)
|
||||||
|
require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx))
|
||||||
|
res = app.Query(query)
|
||||||
|
require.Equal(t, 0, len(res.Value))
|
||||||
|
|
||||||
|
// query returns correct value after Commit
|
||||||
|
app.Commit()
|
||||||
|
res = app.Query(query)
|
||||||
|
require.Equal(t, value, res.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test p2p filter queries
|
||||||
|
func TestP2PQuery(t *testing.T) {
|
||||||
|
addrPeerFilterOpt := func(bapp *BaseApp) {
|
||||||
|
bapp.SetAddrPeerFilter(func(addrport string) abci.ResponseQuery {
|
||||||
|
require.Equal(t, "1.1.1.1:8000", addrport)
|
||||||
|
return abci.ResponseQuery{Code: uint32(3)}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
idPeerFilterOpt := func(bapp *BaseApp) {
|
||||||
|
bapp.SetIDPeerFilter(func(id string) abci.ResponseQuery {
|
||||||
|
require.Equal(t, "testid", id)
|
||||||
|
return abci.ResponseQuery{Code: uint32(4)}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
app := setupBaseApp(t, addrPeerFilterOpt, idPeerFilterOpt)
|
||||||
|
|
||||||
|
addrQuery := abci.RequestQuery{
|
||||||
|
Path: "/p2p/filter/addr/1.1.1.1:8000",
|
||||||
|
}
|
||||||
|
res := app.Query(addrQuery)
|
||||||
|
require.Equal(t, uint32(3), res.Code)
|
||||||
|
|
||||||
|
idQuery := abci.RequestQuery{
|
||||||
|
Path: "/p2p/filter/id/testid",
|
||||||
|
}
|
||||||
|
res = app.Query(idQuery)
|
||||||
|
require.Equal(t, uint32(4), res.Code)
|
||||||
|
}
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
package baseapp
|
package baseapp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/tendermint/tendermint/abci/server"
|
"regexp"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var isAlphaNumeric = regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString
|
||||||
|
|
||||||
// nolint - Mostly for testing
|
// nolint - Mostly for testing
|
||||||
func (app *BaseApp) Check(tx sdk.Tx) (result sdk.Result) {
|
func (app *BaseApp) Check(tx sdk.Tx) (result sdk.Result) {
|
||||||
return app.runTx(runTxModeCheck, nil, tx)
|
return app.runTx(runTxModeCheck, nil, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint - full tx execution
|
// nolint - full tx execution
|
||||||
func (app *BaseApp) Simulate(tx sdk.Tx) (result sdk.Result) {
|
func (app *BaseApp) Simulate(txBytes []byte, tx sdk.Tx) (result sdk.Result) {
|
||||||
return app.runTx(runTxModeSimulate, nil, tx)
|
return app.runTx(runTxModeSimulate, txBytes, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint
|
// nolint
|
||||||
|
@ -23,27 +25,13 @@ func (app *BaseApp) Deliver(tx sdk.Tx) (result sdk.Result) {
|
||||||
return app.runTx(runTxModeDeliver, nil, tx)
|
return app.runTx(runTxModeDeliver, nil, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunForever BasecoinApp execution and cleanup
|
// Context with current {check, deliver}State of the app
|
||||||
func RunForever(app abci.Application) {
|
// used by tests
|
||||||
|
func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context {
|
||||||
// Start the ABCI server
|
if isCheckTx {
|
||||||
srv, err := server.NewServer("0.0.0.0:26658", "socket", app)
|
return sdk.NewContext(app.checkState.ms, header, true, app.logger).
|
||||||
if err != nil {
|
WithMinGasPrices(app.minGasPrices)
|
||||||
cmn.Exit(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = srv.Start()
|
|
||||||
if err != nil {
|
|
||||||
cmn.Exit(err.Error())
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait forever
|
return sdk.NewContext(app.deliverState.ms, header, false, app.logger)
|
||||||
cmn.TrapSignal(func() {
|
|
||||||
// Cleanup
|
|
||||||
err := srv.Stop()
|
|
||||||
if err != nil {
|
|
||||||
cmn.Exit(err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ func SetPruning(opts sdk.PruningOptions) func(*BaseApp) {
|
||||||
return func(bap *BaseApp) { bap.cms.SetPruning(opts) }
|
return func(bap *BaseApp) { bap.cms.SetPruning(opts) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMinimumGasPrices returns an option that sets the minimum gas prices on the app.
|
// SetMinGasPrices returns an option that sets the minimum gas prices on the app.
|
||||||
func SetMinGasPrices(gasPricesStr string) func(*BaseApp) {
|
func SetMinGasPrices(gasPricesStr string) func(*BaseApp) {
|
||||||
gasPrices, err := sdk.ParseDecCoins(gasPricesStr)
|
gasPrices, err := sdk.ParseDecCoins(gasPricesStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -84,11 +84,11 @@ func (app *BaseApp) SetAddrPeerFilter(pf sdk.PeerFilter) {
|
||||||
app.addrPeerFilter = pf
|
app.addrPeerFilter = pf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BaseApp) SetPubKeyPeerFilter(pf sdk.PeerFilter) {
|
func (app *BaseApp) SetIDPeerFilter(pf sdk.PeerFilter) {
|
||||||
if app.sealed {
|
if app.sealed {
|
||||||
panic("SetPubKeyPeerFilter() on sealed BaseApp")
|
panic("SetIDPeerFilter() on sealed BaseApp")
|
||||||
}
|
}
|
||||||
app.pubkeyPeerFilter = pf
|
app.idPeerFilter = pf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BaseApp) SetFauxMerkleMode() {
|
func (app *BaseApp) SetFauxMerkleMode() {
|
||||||
|
@ -97,25 +97,3 @@ func (app *BaseApp) SetFauxMerkleMode() {
|
||||||
}
|
}
|
||||||
app.fauxMerkleMode = true
|
app.fauxMerkleMode = true
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------
|
|
||||||
// TODO: move these out of this file?
|
|
||||||
|
|
||||||
func (app *BaseApp) Router() Router {
|
|
||||||
if app.sealed {
|
|
||||||
panic("Router() on sealed BaseApp")
|
|
||||||
}
|
|
||||||
return app.router
|
|
||||||
}
|
|
||||||
|
|
||||||
func (app *BaseApp) QueryRouter() QueryRouter {
|
|
||||||
return app.queryRouter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (app *BaseApp) Seal() { app.sealed = true }
|
|
||||||
func (app *BaseApp) IsSealed() bool { return app.sealed }
|
|
||||||
func (app *BaseApp) enforceSeal() {
|
|
||||||
if !app.sealed {
|
|
||||||
panic("enforceSeal() on BaseApp but not sealed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
package baseapp
|
|
||||||
|
|
||||||
/*
|
|
||||||
XXX Make this work with MultiStore.
|
|
||||||
XXX It will require some interfaces updates in store/types.go.
|
|
||||||
|
|
||||||
if len(reqQuery.Data) == 0 {
|
|
||||||
resQuery.Log = "Query cannot be zero length"
|
|
||||||
resQuery.Code = abci.CodeType_EncodingError
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the query response height to current
|
|
||||||
tree := app.state.Committed()
|
|
||||||
|
|
||||||
height := reqQuery.Height
|
|
||||||
if height == 0 {
|
|
||||||
// TODO: once the rpc actually passes in non-zero
|
|
||||||
// heights we can use to query right after a tx
|
|
||||||
// we must retrun most recent, even if apphash
|
|
||||||
// is not yet in the blockchain
|
|
||||||
|
|
||||||
withProof := app.CommittedHeight() - 1
|
|
||||||
if tree.Tree.VersionExists(withProof) {
|
|
||||||
height = withProof
|
|
||||||
} else {
|
|
||||||
height = app.CommittedHeight()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resQuery.Height = height
|
|
||||||
|
|
||||||
switch reqQuery.Path {
|
|
||||||
case "/store", "/key": // Get by key
|
|
||||||
key := reqQuery.Data // Data holds the key bytes
|
|
||||||
resQuery.Key = key
|
|
||||||
if reqQuery.Prove {
|
|
||||||
value, proof, err := tree.GetVersionedWithProof(key, height)
|
|
||||||
if err != nil {
|
|
||||||
resQuery.Log = err.Error()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
resQuery.Value = value
|
|
||||||
resQuery.Proof = proof.Bytes()
|
|
||||||
} else {
|
|
||||||
value := tree.Get(key)
|
|
||||||
resQuery.Value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
resQuery.Code = abci.CodeType_UnknownRequest
|
|
||||||
resQuery.Log = cmn.Fmt("Unexpected Query path: %v", reqQuery.Path)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
*/
|
|
|
@ -1,97 +0,0 @@
|
||||||
package baseapp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test that we can only query from the latest committed state.
|
|
||||||
func TestQuery(t *testing.T) {
|
|
||||||
key, value := []byte("hello"), []byte("goodbye")
|
|
||||||
anteOpt := func(bapp *BaseApp) {
|
|
||||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
|
||||||
store := ctx.KVStore(capKey1)
|
|
||||||
store.Set(key, value)
|
|
||||||
return
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
routerOpt := func(bapp *BaseApp) {
|
|
||||||
bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
|
||||||
store := ctx.KVStore(capKey1)
|
|
||||||
store.Set(key, value)
|
|
||||||
return sdk.Result{}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
|
||||||
|
|
||||||
app.InitChain(abci.RequestInitChain{})
|
|
||||||
|
|
||||||
// NOTE: "/store/key1" tells us KVStore
|
|
||||||
// and the final "/key" says to use the data as the
|
|
||||||
// key in the given KVStore ...
|
|
||||||
query := abci.RequestQuery{
|
|
||||||
Path: "/store/key1/key",
|
|
||||||
Data: key,
|
|
||||||
}
|
|
||||||
tx := newTxCounter(0, 0)
|
|
||||||
|
|
||||||
// query is empty before we do anything
|
|
||||||
res := app.Query(query)
|
|
||||||
require.Equal(t, 0, len(res.Value))
|
|
||||||
|
|
||||||
// query is still empty after a CheckTx
|
|
||||||
resTx := app.Check(tx)
|
|
||||||
require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx))
|
|
||||||
res = app.Query(query)
|
|
||||||
require.Equal(t, 0, len(res.Value))
|
|
||||||
|
|
||||||
// query is still empty after a DeliverTx before we commit
|
|
||||||
app.BeginBlock(abci.RequestBeginBlock{})
|
|
||||||
resTx = app.Deliver(tx)
|
|
||||||
require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx))
|
|
||||||
res = app.Query(query)
|
|
||||||
require.Equal(t, 0, len(res.Value))
|
|
||||||
|
|
||||||
// query returns correct value after Commit
|
|
||||||
app.Commit()
|
|
||||||
res = app.Query(query)
|
|
||||||
require.Equal(t, value, res.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test p2p filter queries
|
|
||||||
func TestP2PQuery(t *testing.T) {
|
|
||||||
addrPeerFilterOpt := func(bapp *BaseApp) {
|
|
||||||
bapp.SetAddrPeerFilter(func(addrport string) abci.ResponseQuery {
|
|
||||||
require.Equal(t, "1.1.1.1:8000", addrport)
|
|
||||||
return abci.ResponseQuery{Code: uint32(3)}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pubkeyPeerFilterOpt := func(bapp *BaseApp) {
|
|
||||||
bapp.SetPubKeyPeerFilter(func(pubkey string) abci.ResponseQuery {
|
|
||||||
require.Equal(t, "testpubkey", pubkey)
|
|
||||||
return abci.ResponseQuery{Code: uint32(4)}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
app := setupBaseApp(t, addrPeerFilterOpt, pubkeyPeerFilterOpt)
|
|
||||||
|
|
||||||
addrQuery := abci.RequestQuery{
|
|
||||||
Path: "/p2p/filter/addr/1.1.1.1:8000",
|
|
||||||
}
|
|
||||||
res := app.Query(addrQuery)
|
|
||||||
require.Equal(t, uint32(3), res.Code)
|
|
||||||
|
|
||||||
pubkeyQuery := abci.RequestQuery{
|
|
||||||
Path: "/p2p/filter/pubkey/testpubkey",
|
|
||||||
}
|
|
||||||
res = app.Query(pubkeyQuery)
|
|
||||||
require.Equal(t, uint32(4), res.Code)
|
|
||||||
}
|
|
|
@ -1,6 +1,8 @@
|
||||||
package baseapp
|
package baseapp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,32 +12,34 @@ type QueryRouter interface {
|
||||||
Route(path string) (h sdk.Querier)
|
Route(path string) (h sdk.Querier)
|
||||||
}
|
}
|
||||||
|
|
||||||
type queryrouter struct {
|
type queryRouter struct {
|
||||||
routes map[string]sdk.Querier
|
routes map[string]sdk.Querier
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint
|
// NewQueryRouter returns a reference to a new queryRouter.
|
||||||
// NewRouter - create new router
|
//
|
||||||
// TODO either make Function unexported or make return type (router) Exported
|
// TODO: Either make the function private or make return type (queryRouter) public.
|
||||||
func NewQueryRouter() *queryrouter {
|
func NewQueryRouter() *queryRouter { // nolint: golint
|
||||||
return &queryrouter{
|
return &queryRouter{
|
||||||
routes: map[string]sdk.Querier{},
|
routes: map[string]sdk.Querier{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRoute - Adds an sdk.Querier to the route provided. Panics on duplicate
|
// AddRoute adds a query path to the router with a given Querier. It will panic
|
||||||
func (rtr *queryrouter) AddRoute(r string, q sdk.Querier) QueryRouter {
|
// if a duplicate route is given. The route must be alphanumeric.
|
||||||
if !isAlphaNumeric(r) {
|
func (qrt *queryRouter) AddRoute(path string, q sdk.Querier) QueryRouter {
|
||||||
|
if !isAlphaNumeric(path) {
|
||||||
panic("route expressions can only contain alphanumeric characters")
|
panic("route expressions can only contain alphanumeric characters")
|
||||||
}
|
}
|
||||||
if rtr.routes[r] != nil {
|
if qrt.routes[path] != nil {
|
||||||
panic("route has already been initialized")
|
panic(fmt.Sprintf("route %s has already been initialized", path))
|
||||||
}
|
}
|
||||||
rtr.routes[r] = q
|
|
||||||
return rtr
|
qrt.routes[path] = q
|
||||||
|
return qrt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the sdk.Querier for a certain route path
|
// Route returns the Querier for a given query route path.
|
||||||
func (rtr *queryrouter) Route(path string) (h sdk.Querier) {
|
func (qrt *queryRouter) Route(path string) sdk.Querier {
|
||||||
return rtr.routes[path]
|
return qrt.routes[path]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package baseapp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testQuerier = func(_ sdk.Context, _ []string, _ abci.RequestQuery) (res []byte, err sdk.Error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryRouter(t *testing.T) {
|
||||||
|
qr := NewQueryRouter()
|
||||||
|
|
||||||
|
// require panic on invalid route
|
||||||
|
require.Panics(t, func() {
|
||||||
|
qr.AddRoute("*", testQuerier)
|
||||||
|
})
|
||||||
|
|
||||||
|
qr.AddRoute("testRoute", testQuerier)
|
||||||
|
q := qr.Route("testRoute")
|
||||||
|
require.NotNil(t, q)
|
||||||
|
|
||||||
|
// require panic on duplicate route
|
||||||
|
require.Panics(t, func() {
|
||||||
|
qr.AddRoute("testRoute", testQuerier)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package baseapp
|
package baseapp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"fmt"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
@ -12,44 +12,36 @@ type Router interface {
|
||||||
Route(path string) (h sdk.Handler)
|
Route(path string) (h sdk.Handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// map a transaction type to a handler and an initgenesis function
|
|
||||||
type route struct {
|
|
||||||
r string
|
|
||||||
h sdk.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
type router struct {
|
type router struct {
|
||||||
routes []route
|
routes map[string]sdk.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint
|
// NewRouter returns a reference to a new router.
|
||||||
// NewRouter - create new router
|
//
|
||||||
// TODO either make Function unexported or make return type (router) Exported
|
// TODO: Either make the function private or make return type (router) public.
|
||||||
func NewRouter() *router {
|
func NewRouter() *router { // nolint: golint
|
||||||
return &router{
|
return &router{
|
||||||
routes: make([]route, 0),
|
routes: make(map[string]sdk.Handler),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var isAlphaNumeric = regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString
|
// AddRoute adds a route path to the router with a given handler. The route must
|
||||||
|
// be alphanumeric.
|
||||||
// AddRoute - TODO add description
|
func (rtr *router) AddRoute(path string, h sdk.Handler) Router {
|
||||||
func (rtr *router) AddRoute(r string, h sdk.Handler) Router {
|
if !isAlphaNumeric(path) {
|
||||||
if !isAlphaNumeric(r) {
|
|
||||||
panic("route expressions can only contain alphanumeric characters")
|
panic("route expressions can only contain alphanumeric characters")
|
||||||
}
|
}
|
||||||
rtr.routes = append(rtr.routes, route{r, h})
|
if rtr.routes[path] != nil {
|
||||||
|
panic(fmt.Sprintf("route %s has already been initialized", path))
|
||||||
|
}
|
||||||
|
|
||||||
|
rtr.routes[path] = h
|
||||||
return rtr
|
return rtr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route - TODO add description
|
// Route returns a handler for a given route path.
|
||||||
// TODO handle expressive matches.
|
//
|
||||||
func (rtr *router) Route(path string) (h sdk.Handler) {
|
// TODO: Handle expressive matches.
|
||||||
for _, route := range rtr.routes {
|
func (rtr *router) Route(path string) sdk.Handler {
|
||||||
if route.r == path {
|
return rtr.routes[path]
|
||||||
return route.h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package baseapp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testHandler = func(_ sdk.Context, _ sdk.Msg) sdk.Result {
|
||||||
|
return sdk.Result{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouter(t *testing.T) {
|
||||||
|
rtr := NewRouter()
|
||||||
|
|
||||||
|
// require panic on invalid route
|
||||||
|
require.Panics(t, func() {
|
||||||
|
rtr.AddRoute("*", testHandler)
|
||||||
|
})
|
||||||
|
|
||||||
|
rtr.AddRoute("testRoute", testHandler)
|
||||||
|
h := rtr.Route("testRoute")
|
||||||
|
require.NotNil(t, h)
|
||||||
|
|
||||||
|
// require panic on duplicate route
|
||||||
|
require.Panics(t, func() {
|
||||||
|
rtr.AddRoute("testRoute", testHandler)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"chain_id": "foo_bar_chain",
|
|
||||||
"app_options": {
|
|
||||||
"accounts": [{
|
|
||||||
"pub_key": {
|
|
||||||
"type": "ed25519",
|
|
||||||
"data": "6880db93598e283a67c4d88fc67a8858aa2de70f713fe94a5109e29c137100c2"
|
|
||||||
},
|
|
||||||
"coins": [
|
|
||||||
{
|
|
||||||
"denom": "blank",
|
|
||||||
"amount": 12345
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"denom": "ETH",
|
|
||||||
"amount": 654321
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}],
|
|
||||||
"plugin_options": ["plugin1/key1", "value1", "plugin1/key2", "value2"]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
{
|
|
||||||
"chain_id": "addr_accounts_chain",
|
|
||||||
"app_options": {
|
|
||||||
"accounts": [{
|
|
||||||
"name": "alice",
|
|
||||||
"pub_key": {
|
|
||||||
"type": "ed25519",
|
|
||||||
"data": "DBD9A46C45868F0A37C92B53113C09B048FBD87B5FBC2F8B199052973B8FAA36"
|
|
||||||
},
|
|
||||||
"coins": [
|
|
||||||
{
|
|
||||||
"denom": "one",
|
|
||||||
"amount": 111
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
"name": "bob",
|
|
||||||
"address": "C471FB670E44D219EE6DF2FC284BE38793ACBCE1",
|
|
||||||
"coins": [
|
|
||||||
{
|
|
||||||
"denom": "two",
|
|
||||||
"amount": 222
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
"name": "sam",
|
|
||||||
"pub_key": {
|
|
||||||
"type": "secp256k1",
|
|
||||||
"data": "02AA8342F63CCCCE6DDB128525BA048CE0B2993DA3B4308746E1F216361A87651E"
|
|
||||||
},
|
|
||||||
"coins": [
|
|
||||||
{
|
|
||||||
"denom": "four",
|
|
||||||
"amount": 444
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
{
|
|
||||||
"chain_id": "addr_accounts_chain",
|
|
||||||
"app_options": {
|
|
||||||
"accounts": [{
|
|
||||||
"name": "alice",
|
|
||||||
"pub_key": {
|
|
||||||
"type": "ed25519",
|
|
||||||
"data": "DBD9A46C45868F0A37C92B53113C09B048FBD87B5FBC2F8B199052973B8FAA36"
|
|
||||||
},
|
|
||||||
"coins": [
|
|
||||||
{
|
|
||||||
"denom": "one",
|
|
||||||
"amount": 111
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
"name": "bob",
|
|
||||||
"address": "C471FB670E44D219EE6DF2FC284BE38793ACBCE1",
|
|
||||||
"coins": [
|
|
||||||
{
|
|
||||||
"denom": "two",
|
|
||||||
"amount": 222
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
"name": "carl",
|
|
||||||
"address": "1234ABCDD18E8EFE3FFC4B0506BF9BF8E5B0D9E9",
|
|
||||||
"pub_key": {
|
|
||||||
"type": "ed25519",
|
|
||||||
"data": "177C0AC45E86257F0708DC085D592AB22AAEECD1D26381B757F7C96135921858"
|
|
||||||
},
|
|
||||||
"coins": [
|
|
||||||
{
|
|
||||||
"denom": "three",
|
|
||||||
"amount": 333
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
"name": "sam",
|
|
||||||
"pub_key": {
|
|
||||||
"type": "secp256k1",
|
|
||||||
"data": "02AA8342F63CCCCE6DDB128525BA048CE0B2993DA3B4308746E1F216361A87651E"
|
|
||||||
},
|
|
||||||
"coins": [
|
|
||||||
{
|
|
||||||
"denom": "four",
|
|
||||||
"amount": 444
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"chain_id": "foo_bar_chain"
|
|
||||||
}
|
|
|
@ -9,8 +9,6 @@ import (
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/libs/cli"
|
"github.com/tendermint/tendermint/libs/cli"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
|
||||||
|
|
||||||
"github.com/pelletier/go-toml"
|
"github.com/pelletier/go-toml"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -32,7 +30,7 @@ func init() {
|
||||||
|
|
||||||
// ConfigCmd returns a CLI command to interactively create a
|
// ConfigCmd returns a CLI command to interactively create a
|
||||||
// Gaia CLI config file.
|
// Gaia CLI config file.
|
||||||
func ConfigCmd() *cobra.Command {
|
func ConfigCmd(defaultCLIHome string) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "config <key> [value]",
|
Use: "config <key> [value]",
|
||||||
Short: "Create or query a Gaia CLI configuration file",
|
Short: "Create or query a Gaia CLI configuration file",
|
||||||
|
@ -40,7 +38,7 @@ func ConfigCmd() *cobra.Command {
|
||||||
Args: cobra.RangeArgs(0, 2),
|
Args: cobra.RangeArgs(0, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().String(cli.HomeFlag, app.DefaultCLIHome,
|
cmd.Flags().String(cli.HomeFlag, defaultCLIHome,
|
||||||
"set client's home directory for configuration")
|
"set client's home directory for configuration")
|
||||||
cmd.Flags().Bool(flagGet, false,
|
cmd.Flags().Bool(flagGet, false,
|
||||||
"print configuration value or its default if unset")
|
"print configuration value or its default if unset")
|
||||||
|
|
|
@ -2,169 +2,79 @@ package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
|
||||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: This should get deleted eventually, and perhaps
|
|
||||||
// ctypes.ResultBroadcastTx be stripped of unused fields, and
|
|
||||||
// ctypes.ResultBroadcastTxCommit returned for tendermint RPC BroadcastTxSync.
|
|
||||||
//
|
|
||||||
// The motivation is that we want a unified type to return, and the better
|
|
||||||
// option is the one that can hold CheckTx/DeliverTx responses optionally.
|
|
||||||
func resultBroadcastTxToCommit(res *ctypes.ResultBroadcastTx) *ctypes.ResultBroadcastTxCommit {
|
|
||||||
return &ctypes.ResultBroadcastTxCommit{
|
|
||||||
Hash: res.Hash,
|
|
||||||
// NOTE: other fields are unused for async.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BroadcastTx broadcasts a transactions either synchronously or asynchronously
|
// BroadcastTx broadcasts a transactions either synchronously or asynchronously
|
||||||
// based on the context parameters. The result of the broadcast is parsed into
|
// based on the context parameters. The result of the broadcast is parsed into
|
||||||
// an intermediate structure which is logged if the context has a logger
|
// an intermediate structure which is logged if the context has a logger
|
||||||
// defined.
|
// defined.
|
||||||
func (ctx CLIContext) BroadcastTx(txBytes []byte) (*ctypes.ResultBroadcastTxCommit, error) {
|
func (ctx CLIContext) BroadcastTx(txBytes []byte) (res sdk.TxResponse, err error) {
|
||||||
if ctx.Async {
|
if ctx.Async {
|
||||||
res, err := ctx.broadcastTxAsync(txBytes)
|
if res, err = ctx.BroadcastTxAsync(txBytes); err != nil {
|
||||||
if err != nil {
|
return
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
return
|
||||||
resCommit := resultBroadcastTxToCommit(res)
|
|
||||||
return resCommit, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.broadcastTxCommit(txBytes)
|
if res, err = ctx.BroadcastTxAndAwaitCommit(txBytes); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// BroadcastTxAndAwaitCommit broadcasts transaction bytes to a Tendermint node
|
// BroadcastTxAndAwaitCommit broadcasts transaction bytes to a Tendermint node
|
||||||
// and waits for a commit.
|
// and waits for a commit.
|
||||||
func (ctx CLIContext) BroadcastTxAndAwaitCommit(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) {
|
func (ctx CLIContext) BroadcastTxAndAwaitCommit(tx []byte) (sdk.TxResponse, error) {
|
||||||
node, err := ctx.GetNode()
|
node, err := ctx.GetNode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return sdk.TxResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := node.BroadcastTxCommit(tx)
|
res, err := node.BroadcastTxCommit(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return sdk.NewResponseFormatBroadcastTxCommit(res), err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !res.CheckTx.IsOK() {
|
if !res.CheckTx.IsOK() {
|
||||||
return res, errors.Errorf(res.CheckTx.Log)
|
return sdk.NewResponseFormatBroadcastTxCommit(res), fmt.Errorf(res.CheckTx.Log)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !res.DeliverTx.IsOK() {
|
if !res.DeliverTx.IsOK() {
|
||||||
return res, errors.Errorf(res.DeliverTx.Log)
|
return sdk.NewResponseFormatBroadcastTxCommit(res), fmt.Errorf(res.DeliverTx.Log)
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, err
|
return sdk.NewResponseFormatBroadcastTxCommit(res), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// BroadcastTxSync broadcasts transaction bytes to a Tendermint node
|
// BroadcastTxSync broadcasts transaction bytes to a Tendermint node synchronously.
|
||||||
// synchronously.
|
func (ctx CLIContext) BroadcastTxSync(tx []byte) (sdk.TxResponse, error) {
|
||||||
func (ctx CLIContext) BroadcastTxSync(tx []byte) (*ctypes.ResultBroadcastTx, error) {
|
|
||||||
node, err := ctx.GetNode()
|
node, err := ctx.GetNode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return sdk.TxResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := node.BroadcastTxSync(tx)
|
res, err := node.BroadcastTxSync(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return sdk.NewResponseFormatBroadcastTx(res), err
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, err
|
return sdk.NewResponseFormatBroadcastTx(res), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// BroadcastTxAsync broadcasts transaction bytes to a Tendermint node
|
// BroadcastTxAsync broadcasts transaction bytes to a Tendermint node asynchronously.
|
||||||
// asynchronously.
|
func (ctx CLIContext) BroadcastTxAsync(tx []byte) (sdk.TxResponse, error) {
|
||||||
func (ctx CLIContext) BroadcastTxAsync(tx []byte) (*ctypes.ResultBroadcastTx, error) {
|
|
||||||
node, err := ctx.GetNode()
|
node, err := ctx.GetNode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return sdk.TxResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := node.BroadcastTxAsync(tx)
|
res, err := node.BroadcastTxAsync(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return sdk.NewResponseFormatBroadcastTx(res), err
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, err
|
return sdk.NewResponseFormatBroadcastTx(res), err
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx CLIContext) broadcastTxAsync(txBytes []byte) (*ctypes.ResultBroadcastTx, error) {
|
|
||||||
res, err := ctx.BroadcastTxAsync(txBytes)
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.Output != nil {
|
|
||||||
if ctx.OutputFormat == "json" {
|
|
||||||
type toJSON struct {
|
|
||||||
TxHash string
|
|
||||||
}
|
|
||||||
|
|
||||||
resJSON := toJSON{res.Hash.String()}
|
|
||||||
bz, err := ctx.Codec.MarshalJSON(resJSON)
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Output.Write(bz)
|
|
||||||
io.WriteString(ctx.Output, "\n")
|
|
||||||
} else {
|
|
||||||
io.WriteString(ctx.Output, fmt.Sprintf("async tx sent (tx hash: %s)\n", res.Hash))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx CLIContext) broadcastTxCommit(txBytes []byte) (*ctypes.ResultBroadcastTxCommit, error) {
|
|
||||||
res, err := ctx.BroadcastTxAndAwaitCommit(txBytes)
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.OutputFormat == "json" {
|
|
||||||
// Since JSON is intended for automated scripts, always include response in
|
|
||||||
// JSON mode.
|
|
||||||
type toJSON struct {
|
|
||||||
Height int64
|
|
||||||
TxHash string
|
|
||||||
Response abci.ResponseDeliverTx
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.Output != nil {
|
|
||||||
resJSON := toJSON{res.Height, res.Hash.String(), res.DeliverTx}
|
|
||||||
bz, err := ctx.Codec.MarshalJSON(resJSON)
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Output.Write(bz)
|
|
||||||
io.WriteString(ctx.Output, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.Output != nil {
|
|
||||||
resStr := fmt.Sprintf("Committed at block %d (tx hash: %s)\n", res.Height, res.Hash.String())
|
|
||||||
|
|
||||||
if ctx.PrintResponse {
|
|
||||||
resStr = fmt.Sprintf("Committed at block %d (tx hash: %s, response: %+v)\n",
|
|
||||||
res.Height, res.Hash.String(), res.DeliverTx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
io.WriteString(ctx.Output, resStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
cryptokeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -19,13 +21,12 @@ import (
|
||||||
tmliteProxy "github.com/tendermint/tendermint/lite/proxy"
|
tmliteProxy "github.com/tendermint/tendermint/lite/proxy"
|
||||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
cskeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
verifier tmlite.Verifier
|
verifier tmlite.Verifier
|
||||||
|
verifierHome string
|
||||||
)
|
)
|
||||||
|
|
||||||
// CLIContext implements a typical CLI context created in SDK modules for
|
// CLIContext implements a typical CLI context created in SDK modules for
|
||||||
|
@ -34,6 +35,7 @@ type CLIContext struct {
|
||||||
Codec *codec.Codec
|
Codec *codec.Codec
|
||||||
AccDecoder auth.AccountDecoder
|
AccDecoder auth.AccountDecoder
|
||||||
Client rpcclient.Client
|
Client rpcclient.Client
|
||||||
|
Keybase cryptokeys.Keybase
|
||||||
Output io.Writer
|
Output io.Writer
|
||||||
OutputFormat string
|
OutputFormat string
|
||||||
Height int64
|
Height int64
|
||||||
|
@ -45,10 +47,11 @@ type CLIContext struct {
|
||||||
Async bool
|
Async bool
|
||||||
PrintResponse bool
|
PrintResponse bool
|
||||||
Verifier tmlite.Verifier
|
Verifier tmlite.Verifier
|
||||||
|
VerifierHome string
|
||||||
Simulate bool
|
Simulate bool
|
||||||
GenerateOnly bool
|
GenerateOnly bool
|
||||||
fromAddress types.AccAddress
|
FromAddress sdk.AccAddress
|
||||||
fromName string
|
FromName string
|
||||||
Indent bool
|
Indent bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,11 +66,16 @@ func NewCLIContext() CLIContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
from := viper.GetString(client.FlagFrom)
|
from := viper.GetString(client.FlagFrom)
|
||||||
fromAddress, fromName := fromFields(from)
|
fromAddress, fromName, err := GetFromFields(from)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to get from fields: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
// We need to use a single verifier for all contexts
|
// We need to use a single verifier for all contexts
|
||||||
if verifier == nil {
|
if verifier == nil || verifierHome != viper.GetString(cli.HomeFlag) {
|
||||||
verifier = createVerifier()
|
verifier = createVerifier()
|
||||||
|
verifierHome = viper.GetString(cli.HomeFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
return CLIContext{
|
return CLIContext{
|
||||||
|
@ -85,8 +93,8 @@ func NewCLIContext() CLIContext {
|
||||||
Verifier: verifier,
|
Verifier: verifier,
|
||||||
Simulate: viper.GetBool(client.FlagDryRun),
|
Simulate: viper.GetBool(client.FlagDryRun),
|
||||||
GenerateOnly: viper.GetBool(client.FlagGenerateOnly),
|
GenerateOnly: viper.GetBool(client.FlagGenerateOnly),
|
||||||
fromAddress: fromAddress,
|
FromAddress: fromAddress,
|
||||||
fromName: fromName,
|
FromName: fromName,
|
||||||
Indent: viper.GetBool(client.FlagIndentResponse),
|
Indent: viper.GetBool(client.FlagIndentResponse),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,37 +145,6 @@ func createVerifier() tmlite.Verifier {
|
||||||
return verifier
|
return verifier
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromFields(from string) (fromAddr types.AccAddress, fromName string) {
|
|
||||||
if from == "" {
|
|
||||||
return nil, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
keybase, err := keys.GetKeyBase()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("no keybase found")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var info cskeys.Info
|
|
||||||
if addr, err := types.AccAddressFromBech32(from); err == nil {
|
|
||||||
info, err = keybase.GetByAddress(addr)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("could not find key %s\n", from)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
info, err = keybase.Get(from)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("could not find key %s\n", from)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fromAddr = info.GetAddress()
|
|
||||||
fromName = info.GetName()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithCodec returns a copy of the context with an updated codec.
|
// WithCodec returns a copy of the context with an updated codec.
|
||||||
func (ctx CLIContext) WithCodec(cdc *codec.Codec) CLIContext {
|
func (ctx CLIContext) WithCodec(cdc *codec.Codec) CLIContext {
|
||||||
ctx.Codec = cdc
|
ctx.Codec = cdc
|
||||||
|
@ -255,6 +232,19 @@ func (ctx CLIContext) WithSimulation(simulate bool) CLIContext {
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithFromName returns a copy of the context with an updated from account name.
|
||||||
|
func (ctx CLIContext) WithFromName(name string) CLIContext {
|
||||||
|
ctx.FromName = name
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFromAddress returns a copy of the context with an updated from account
|
||||||
|
// address.
|
||||||
|
func (ctx CLIContext) WithFromAddress(addr sdk.AccAddress) CLIContext {
|
||||||
|
ctx.FromAddress = addr
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
// PrintOutput prints output while respecting output and indent flags
|
// PrintOutput prints output while respecting output and indent flags
|
||||||
// NOTE: pass in marshalled structs that have been unmarshaled
|
// NOTE: pass in marshalled structs that have been unmarshaled
|
||||||
// because this function will panic on marshaling errors
|
// because this function will panic on marshaling errors
|
||||||
|
@ -279,3 +269,31 @@ func (ctx CLIContext) PrintOutput(toPrint fmt.Stringer) (err error) {
|
||||||
fmt.Println(string(out))
|
fmt.Println(string(out))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFromFields returns a from account address and Keybase name given either
|
||||||
|
// an address or key name.
|
||||||
|
func GetFromFields(from string) (sdk.AccAddress, string, error) {
|
||||||
|
if from == "" {
|
||||||
|
return nil, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
keybase, err := keys.NewKeyBaseFromHomeFlag()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var info cryptokeys.Info
|
||||||
|
if addr, err := sdk.AccAddressFromBech32(from); err == nil {
|
||||||
|
info, err = keybase.GetByAddress(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info, err = keybase.Get(from)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return info.GetAddress(), info.GetName(), nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pkg/errors"
|
"fmt"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
@ -9,7 +9,7 @@ import (
|
||||||
// ErrInvalidAccount returns a standardized error reflecting that a given
|
// ErrInvalidAccount returns a standardized error reflecting that a given
|
||||||
// account address does not exist.
|
// account address does not exist.
|
||||||
func ErrInvalidAccount(addr sdk.AccAddress) error {
|
func ErrInvalidAccount(addr sdk.AccAddress) error {
|
||||||
return errors.Errorf(`No account with address %s was found in the state.
|
return fmt.Errorf(`No account with address %s was found in the state.
|
||||||
Are you sure there has been a transaction involving it?`, addr)
|
Are you sure there has been a transaction involving it?`, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,6 @@ Are you sure there has been a transaction involving it?`, addr)
|
||||||
// height can't be verified. The reason is that the base checkpoint of the certifier is
|
// height can't be verified. The reason is that the base checkpoint of the certifier is
|
||||||
// newer than the given height
|
// newer than the given height
|
||||||
func ErrVerifyCommit(height int64) error {
|
func ErrVerifyCommit(height int64) error {
|
||||||
return errors.Errorf(`The height of base truststore in gaia-lite is higher than height %d.
|
return fmt.Errorf(`The height of base truststore in gaia-lite is higher than height %d.
|
||||||
Can't verify blockchain proof at this height. Please set --trust-node to true and try again`, height)
|
Can't verify blockchain proof at this height. Please set --trust-node to true and try again`, height)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/store"
|
"github.com/cosmos/cosmos-sdk/store/rootmulti"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetNode returns an RPC client. If the context's client is not defined, an
|
// GetNode returns an RPC client. If the context's client is not defined, an
|
||||||
|
@ -82,13 +82,13 @@ func (ctx CLIContext) GetAccount(address []byte) (auth.Account, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFromAddress returns the from address from the context's name.
|
// GetFromAddress returns the from address from the context's name.
|
||||||
func (ctx CLIContext) GetFromAddress() (sdk.AccAddress, error) {
|
func (ctx CLIContext) GetFromAddress() sdk.AccAddress {
|
||||||
return ctx.fromAddress, nil
|
return ctx.FromAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFromName returns the key name for the current context.
|
// GetFromName returns the key name for the current context.
|
||||||
func (ctx CLIContext) GetFromName() (string, error) {
|
func (ctx CLIContext) GetFromName() string {
|
||||||
return ctx.fromName, nil
|
return ctx.FromName
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountNumber returns the next account number for the given account
|
// GetAccountNumber returns the next account number for the given account
|
||||||
|
@ -116,11 +116,7 @@ func (ctx CLIContext) GetAccountSequence(address []byte) (uint64, error) {
|
||||||
// EnsureAccountExists ensures that an account exists for a given context. An
|
// EnsureAccountExists ensures that an account exists for a given context. An
|
||||||
// error is returned if it does not.
|
// error is returned if it does not.
|
||||||
func (ctx CLIContext) EnsureAccountExists() error {
|
func (ctx CLIContext) EnsureAccountExists() error {
|
||||||
addr, err := ctx.GetFromAddress()
|
addr := ctx.GetFromAddress()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
accountBytes, err := ctx.QueryStore(auth.AddressStoreKey(addr), ctx.AccountStore)
|
accountBytes, err := ctx.QueryStore(auth.AddressStoreKey(addr), ctx.AccountStore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -169,7 +165,7 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro
|
||||||
|
|
||||||
resp := result.Response
|
resp := result.Response
|
||||||
if !resp.IsOK() {
|
if !resp.IsOK() {
|
||||||
return res, errors.Errorf(resp.Log)
|
return res, errors.New(resp.Log)
|
||||||
}
|
}
|
||||||
|
|
||||||
// data from trusted node or subspace query doesn't need verification
|
// data from trusted node or subspace query doesn't need verification
|
||||||
|
@ -211,7 +207,7 @@ func (ctx CLIContext) verifyProof(queryPath string, resp abci.ResponseQuery) err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Instead of reconstructing, stash on CLIContext field?
|
// TODO: Instead of reconstructing, stash on CLIContext field?
|
||||||
prt := store.DefaultProofRuntime()
|
prt := rootmulti.DefaultProofRuntime()
|
||||||
|
|
||||||
// TODO: Better convention for path?
|
// TODO: Better convention for path?
|
||||||
storeName, err := parseQueryStorePath(queryPath)
|
storeName, err := parseQueryStorePath(queryPath)
|
||||||
|
@ -223,6 +219,13 @@ func (ctx CLIContext) verifyProof(queryPath string, resp abci.ResponseQuery) err
|
||||||
kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL)
|
kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL)
|
||||||
kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL)
|
kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL)
|
||||||
|
|
||||||
|
if resp.Value == nil {
|
||||||
|
err = prt.VerifyAbsence(resp.Proof, commit.Header.AppHash, kp.String())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to prove merkle proof")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
err = prt.VerifyValue(resp.Proof, commit.Header.AppHash, kp.String(), resp.Value)
|
err = prt.VerifyValue(resp.Proof, commit.Header.AppHash, kp.String(), resp.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to prove merkle proof")
|
return errors.Wrap(err, "failed to prove merkle proof")
|
||||||
|
@ -251,7 +254,7 @@ func isQueryStoreWithProof(path string) bool {
|
||||||
return false
|
return false
|
||||||
case paths[0] != "store":
|
case paths[0] != "store":
|
||||||
return false
|
return false
|
||||||
case store.RequireProof("/" + paths[2]):
|
case rootmulti.RequireProof("/" + paths[2]):
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// common errors for CLI and REST clients
|
||||||
|
var (
|
||||||
|
ErrInvalidGasAdjustment = errors.New("invalid gas adjustment")
|
||||||
|
ErrInvalidSigner = errors.New("tx intended signer does not match the given signer")
|
||||||
|
)
|
|
@ -11,9 +11,9 @@ import (
|
||||||
|
|
||||||
// nolint
|
// nolint
|
||||||
const (
|
const (
|
||||||
// DefaultGasAdjustment is applied to gas estimates to avoid tx
|
// DefaultGasAdjustment is applied to gas estimates to avoid tx execution
|
||||||
// execution failures due to state changes that might
|
// failures due to state changes that might occur between the tx simulation
|
||||||
// occur between the tx simulation and the actual run.
|
// and the actual run.
|
||||||
DefaultGasAdjustment = 1.0
|
DefaultGasAdjustment = 1.0
|
||||||
DefaultGasLimit = 200000
|
DefaultGasLimit = 200000
|
||||||
GasFlagAuto = "auto"
|
GasFlagAuto = "auto"
|
||||||
|
@ -40,7 +40,7 @@ const (
|
||||||
FlagListenAddr = "laddr"
|
FlagListenAddr = "laddr"
|
||||||
FlagCORS = "cors"
|
FlagCORS = "cors"
|
||||||
FlagMaxOpenConnections = "max-open"
|
FlagMaxOpenConnections = "max-open"
|
||||||
FlagInsecure = "insecure"
|
FlagTLS = "tls"
|
||||||
FlagSSLHosts = "ssl-hosts"
|
FlagSSLHosts = "ssl-hosts"
|
||||||
FlagSSLCertFile = "ssl-certfile"
|
FlagSSLCertFile = "ssl-certfile"
|
||||||
FlagSSLKeyFile = "ssl-keyfile"
|
FlagSSLKeyFile = "ssl-keyfile"
|
||||||
|
@ -103,21 +103,15 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
|
||||||
|
|
||||||
// RegisterRestServerFlags registers the flags required for rest server
|
// RegisterRestServerFlags registers the flags required for rest server
|
||||||
func RegisterRestServerFlags(cmd *cobra.Command) *cobra.Command {
|
func RegisterRestServerFlags(cmd *cobra.Command) *cobra.Command {
|
||||||
|
cmd = GetCommands(cmd)[0]
|
||||||
cmd.Flags().String(FlagListenAddr, "tcp://localhost:1317", "The address for the server to listen on")
|
cmd.Flags().String(FlagListenAddr, "tcp://localhost:1317", "The address for the server to listen on")
|
||||||
cmd.Flags().Bool(FlagInsecure, false, "Do not set up SSL/TLS layer")
|
cmd.Flags().Bool(FlagTLS, false, "Enable SSL/TLS layer")
|
||||||
cmd.Flags().String(FlagSSLHosts, "", "Comma-separated hostnames and IPs to generate a certificate for")
|
cmd.Flags().String(FlagSSLHosts, "", "Comma-separated hostnames and IPs to generate a certificate for")
|
||||||
cmd.Flags().String(FlagSSLCertFile, "", "Path to a SSL certificate file. If not supplied, a self-signed certificate will be generated.")
|
cmd.Flags().String(FlagSSLCertFile, "", "Path to a SSL certificate file. If not supplied, a self-signed certificate will be generated.")
|
||||||
cmd.Flags().String(FlagSSLKeyFile, "", "Path to a key file; ignored if a certificate file is not supplied.")
|
cmd.Flags().String(FlagSSLKeyFile, "", "Path to a key file; ignored if a certificate file is not supplied.")
|
||||||
cmd.Flags().String(FlagCORS, "", "Set the domains that can make CORS requests (* for all)")
|
cmd.Flags().String(FlagCORS, "", "Set the domains that can make CORS requests (* for all)")
|
||||||
cmd.Flags().String(FlagChainID, "", "Chain ID of Tendermint node")
|
|
||||||
cmd.Flags().String(FlagNode, "tcp://localhost:26657", "Address of the node to connect to")
|
|
||||||
cmd.Flags().Int(FlagMaxOpenConnections, 1000, "The number of maximum open connections")
|
cmd.Flags().Int(FlagMaxOpenConnections, 1000, "The number of maximum open connections")
|
||||||
cmd.Flags().Bool(FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)")
|
|
||||||
cmd.Flags().Bool(FlagIndentResponse, false, "Add indent to JSON response")
|
|
||||||
|
|
||||||
viper.BindPFlag(FlagTrustNode, cmd.Flags().Lookup(FlagTrustNode))
|
|
||||||
viper.BindPFlag(FlagChainID, cmd.Flags().Lookup(FlagChainID))
|
|
||||||
viper.BindPFlag(FlagNode, cmd.Flags().Lookup(FlagNode))
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,18 +6,35 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/bgentry/speakeasy"
|
"github.com/bgentry/speakeasy"
|
||||||
"github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MinPassLength is the minimum acceptable password length
|
// MinPassLength is the minimum acceptable password length
|
||||||
const MinPassLength = 8
|
const MinPassLength = 8
|
||||||
|
|
||||||
|
var currentStdin *bufio.Reader
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
currentStdin = bufio.NewReader(os.Stdin)
|
||||||
|
}
|
||||||
|
|
||||||
// BufferStdin is used to allow reading prompts for stdin
|
// BufferStdin is used to allow reading prompts for stdin
|
||||||
// multiple times, when we read from non-tty
|
// multiple times, when we read from non-tty
|
||||||
func BufferStdin() *bufio.Reader {
|
func BufferStdin() *bufio.Reader {
|
||||||
return bufio.NewReader(os.Stdin)
|
return currentStdin
|
||||||
|
}
|
||||||
|
|
||||||
|
// OverrideStdin allows to temporarily override stdin
|
||||||
|
func OverrideStdin(newStdin *bufio.Reader) (cleanUp func()) {
|
||||||
|
prevStdin := currentStdin
|
||||||
|
currentStdin = newStdin
|
||||||
|
cleanUp = func() {
|
||||||
|
currentStdin = prevStdin
|
||||||
|
}
|
||||||
|
return cleanUp
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPassword will prompt for a password one-time (to sign a tx)
|
// GetPassword will prompt for a password one-time (to sign a tx)
|
||||||
|
@ -36,18 +53,12 @@ func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) {
|
||||||
if len(pass) < MinPassLength {
|
if len(pass) < MinPassLength {
|
||||||
// Return the given password to the upstream client so it can handle a
|
// Return the given password to the upstream client so it can handle a
|
||||||
// non-STDIN failure gracefully.
|
// non-STDIN failure gracefully.
|
||||||
return pass, errors.Errorf("password must be at least %d characters", MinPassLength)
|
return pass, fmt.Errorf("password must be at least %d characters", MinPassLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pass, nil
|
return pass, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSeed will request a seed phrase from stdin and trims off
|
|
||||||
// leading/trailing spaces
|
|
||||||
func GetSeed(prompt string, buf *bufio.Reader) (string, error) {
|
|
||||||
return GetString(prompt, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCheckPassword will prompt for a password twice to verify they
|
// GetCheckPassword will prompt for a password twice to verify they
|
||||||
// match (for creating a new password).
|
// match (for creating a new password).
|
||||||
// It enforces the password length. Only parses password once if
|
// It enforces the password length. Only parses password once if
|
||||||
|
|
|
@ -6,18 +6,9 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetKeyBase initializes a keybase based on the given db.
|
|
||||||
// The KeyBase manages all activity requiring access to a key.
|
|
||||||
func GetKeyBase(db dbm.DB) keys.Keybase {
|
|
||||||
keybase := keys.New(
|
|
||||||
db,
|
|
||||||
)
|
|
||||||
return keybase
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockKeyBase generates an in-memory keybase that will be discarded
|
// MockKeyBase generates an in-memory keybase that will be discarded
|
||||||
// useful for --dry-run to generate a seed phrase without
|
// useful for --dry-run to generate a seed phrase without
|
||||||
// storing the key
|
// storing the key
|
||||||
func MockKeyBase() keys.Keybase {
|
func MockKeyBase() keys.Keybase {
|
||||||
return GetKeyBase(dbm.NewMemDB())
|
return keys.New(dbm.NewMemDB())
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,26 +9,26 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/crypto/multisig"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/cosmos/go-bip39"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/tendermint/tendermint/crypto"
|
|
||||||
"github.com/tendermint/tendermint/libs/cli"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/go-bip39"
|
||||||
ccrypto "github.com/cosmos/cosmos-sdk/crypto"
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
"github.com/tendermint/tendermint/crypto/multisig"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
"github.com/tendermint/tendermint/libs/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
flagInteractive = "interactive"
|
flagInteractive = "interactive"
|
||||||
flagBIP44Path = "bip44-path"
|
|
||||||
flagRecover = "recover"
|
flagRecover = "recover"
|
||||||
flagNoBackup = "no-backup"
|
flagNoBackup = "no-backup"
|
||||||
flagDryRun = "dry-run"
|
flagDryRun = "dry-run"
|
||||||
|
@ -38,6 +38,11 @@ const (
|
||||||
flagNoSort = "nosort"
|
flagNoSort = "nosort"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxValidAccountValue = int(0x80000000 - 1)
|
||||||
|
maxValidIndexalue = int(0x80000000 - 1)
|
||||||
|
)
|
||||||
|
|
||||||
func addKeyCommand() *cobra.Command {
|
func addKeyCommand() *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "add <name>",
|
Use: "add <name>",
|
||||||
|
@ -68,7 +73,6 @@ the flag --nosort is set.
|
||||||
cmd.Flags().String(FlagPublicKey, "", "Parse a public key in bech32 format and save it to disk")
|
cmd.Flags().String(FlagPublicKey, "", "Parse a public key in bech32 format and save it to disk")
|
||||||
cmd.Flags().BoolP(flagInteractive, "i", false, "Interactively prompt user for BIP39 passphrase and mnemonic")
|
cmd.Flags().BoolP(flagInteractive, "i", false, "Interactively prompt user for BIP39 passphrase and mnemonic")
|
||||||
cmd.Flags().Bool(client.FlagUseLedger, false, "Store a local reference to a private key on a Ledger device")
|
cmd.Flags().Bool(client.FlagUseLedger, false, "Store a local reference to a private key on a Ledger device")
|
||||||
cmd.Flags().String(flagBIP44Path, "44'/118'/0'/0/0", "BIP44 path from which to derive a private key")
|
|
||||||
cmd.Flags().Bool(flagRecover, false, "Provide seed phrase to recover existing key instead of creating")
|
cmd.Flags().Bool(flagRecover, false, "Provide seed phrase to recover existing key instead of creating")
|
||||||
cmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
|
cmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
|
||||||
cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
|
cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
|
||||||
|
@ -86,7 +90,7 @@ input
|
||||||
output
|
output
|
||||||
- armor encrypted private key (saved to file)
|
- armor encrypted private key (saved to file)
|
||||||
*/
|
*/
|
||||||
func runAddCmd(cmd *cobra.Command, args []string) error {
|
func runAddCmd(_ *cobra.Command, args []string) error {
|
||||||
var kb keys.Keybase
|
var kb keys.Keybase
|
||||||
var err error
|
var err error
|
||||||
var encryptPassword string
|
var encryptPassword string
|
||||||
|
@ -95,24 +99,25 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
interactive := viper.GetBool(flagInteractive)
|
interactive := viper.GetBool(flagInteractive)
|
||||||
|
showMnemonic := !viper.GetBool(flagNoBackup)
|
||||||
|
|
||||||
if viper.GetBool(flagDryRun) {
|
if viper.GetBool(flagDryRun) {
|
||||||
// we throw this away, so don't enforce args,
|
// we throw this away, so don't enforce args,
|
||||||
// we want to get a new random seed phrase quickly
|
// we want to get a new random seed phrase quickly
|
||||||
kb = client.MockKeyBase()
|
kb = client.MockKeyBase()
|
||||||
encryptPassword = "throwing-this-key-away"
|
encryptPassword = app.DefaultKeyPass
|
||||||
} else {
|
} else {
|
||||||
kb, err = GetKeyBaseWithWritePerm()
|
kb, err = NewKeyBaseFromHomeFlag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := kb.Get(name)
|
_, err = kb.Get(name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// account exists, ask for user confirmation
|
// account exists, ask for user confirmation
|
||||||
if response, err := client.GetConfirmation(
|
if response, err2 := client.GetConfirmation(
|
||||||
fmt.Sprintf("override the existing name %s", name), buf); err != nil || !response {
|
fmt.Sprintf("override the existing name %s", name), buf); err2 != nil || !response {
|
||||||
return err
|
return err2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +149,7 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
||||||
if _, err := kb.CreateOffline(name, pk); err != nil {
|
if _, err := kb.CreateOffline(name, pk); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "Key %q saved to disk.", name)
|
fmt.Fprintf(os.Stderr, "Key %q saved to disk.", name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -164,51 +170,37 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
kb.CreateOffline(name, pk)
|
_, err = kb.CreateOffline(name, pk)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
bipFlag := cmd.Flags().Lookup(flagBIP44Path)
|
account := uint32(viper.GetInt(flagAccount))
|
||||||
bip44Params, err := getBIP44ParamsAndPath(bipFlag.Value.String(), bipFlag.Changed || !interactive)
|
index := uint32(viper.GetInt(flagIndex))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're using ledger, only thing we need is the path. So generate key and
|
// If we're using ledger, only thing we need is the path. So generate key and we're done.
|
||||||
// we're done.
|
|
||||||
if viper.GetBool(client.FlagUseLedger) {
|
if viper.GetBool(client.FlagUseLedger) {
|
||||||
account := uint32(viper.GetInt(flagAccount))
|
info, err := kb.CreateLedger(name, keys.Secp256k1, account, index)
|
||||||
index := uint32(viper.GetInt(flagIndex))
|
|
||||||
path := ccrypto.DerivationPath{44, 118, account, 0, index}
|
|
||||||
info, err := kb.CreateLedger(name, path, keys.Secp256k1)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
printCreate(info, "")
|
return printCreate(info, false, "")
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recover key from seed passphrase
|
|
||||||
if viper.GetBool(flagRecover) {
|
|
||||||
seed, err := client.GetSeed(
|
|
||||||
"Enter your recovery seed phrase:", buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info, err := kb.CreateKey(name, seed, encryptPassword)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// print out results without the seed phrase
|
|
||||||
viper.Set(flagNoBackup, true)
|
|
||||||
printCreate(info, "")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get bip39 mnemonic
|
||||||
var mnemonic string
|
var mnemonic string
|
||||||
if interactive {
|
var bip39Passphrase string
|
||||||
mnemonic, err = client.GetString("Enter your bip39 mnemonic, or hit enter to generate one.", buf)
|
|
||||||
|
if interactive || viper.GetBool(flagRecover) {
|
||||||
|
bip39Message := "Enter your bip39 mnemonic"
|
||||||
|
if !viper.GetBool(flagRecover) {
|
||||||
|
bip39Message = "Enter your bip39 mnemonic, or hit enter to generate one."
|
||||||
|
}
|
||||||
|
|
||||||
|
mnemonic, err = client.GetString(bip39Message, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -227,8 +219,12 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get bip39 passphrase
|
if !bip39.IsMnemonicValid(mnemonic) {
|
||||||
var bip39Passphrase string
|
fmt.Fprintf(os.Stderr, "Error: Mnemonic is not valid")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// override bip39 passphrase
|
||||||
if interactive {
|
if interactive {
|
||||||
bip39Passphrase, err = client.GetString(
|
bip39Passphrase, err = client.GetString(
|
||||||
"Enter your bip39 passphrase. This is combined with the mnemonic to derive the seed. "+
|
"Enter your bip39 passphrase. This is combined with the mnemonic to derive the seed. "+
|
||||||
|
@ -250,173 +246,158 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := kb.Derive(name, mnemonic, bip39Passphrase, encryptPassword, *bip44Params)
|
info, err := kb.CreateAccount(name, mnemonic, keys.DefaultBIP39Passphrase, encryptPassword, account, index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printCreate(info, mnemonic)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBIP44ParamsAndPath(path string, flagSet bool) (*hd.BIP44Params, error) {
|
// Recover key from seed passphrase
|
||||||
buf := client.BufferStdin()
|
if viper.GetBool(flagRecover) {
|
||||||
bip44Path := path
|
// Hide mnemonic from output
|
||||||
|
showMnemonic = false
|
||||||
// if it wasn't set in the flag, give it a chance to overide interactively
|
mnemonic = ""
|
||||||
if !flagSet {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
bip44Path, err = client.GetString(fmt.Sprintf("Enter your bip44 path. Default is %s\n", path), buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(bip44Path) == 0 {
|
|
||||||
bip44Path = path
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bip44params, err := hd.NewParamsFromPath(bip44Path)
|
return printCreate(info, showMnemonic, mnemonic)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return bip44params, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func printCreate(info keys.Info, seed string) {
|
func printCreate(info keys.Info, showMnemonic bool, mnemonic string) error {
|
||||||
output := viper.Get(cli.OutputFlag)
|
output := viper.Get(cli.OutputFlag)
|
||||||
|
|
||||||
switch output {
|
switch output {
|
||||||
case "text":
|
case OutputFormatText:
|
||||||
fmt.Fprintln(os.Stderr, "")
|
fmt.Fprintln(os.Stderr)
|
||||||
printKeyInfo(info, Bech32KeyOutput)
|
printKeyInfo(info, Bech32KeyOutput)
|
||||||
|
|
||||||
// print seed unless requested not to.
|
// print mnemonic unless requested not to.
|
||||||
if !viper.GetBool(client.FlagUseLedger) && !viper.GetBool(flagNoBackup) {
|
if showMnemonic {
|
||||||
fmt.Fprintln(os.Stderr, "\n**Important** write this seed phrase in a safe place.")
|
fmt.Fprintln(os.Stderr, "\n**Important** write this mnemonic phrase in a safe place.")
|
||||||
fmt.Fprintln(os.Stderr, "It is the only way to recover your account if you ever forget your password.")
|
fmt.Fprintln(os.Stderr, "It is the only way to recover your account if you ever forget your password.")
|
||||||
fmt.Fprintln(os.Stderr)
|
fmt.Fprintln(os.Stderr, "")
|
||||||
fmt.Fprintln(os.Stderr, seed)
|
fmt.Fprintln(os.Stderr, mnemonic)
|
||||||
}
|
}
|
||||||
case "json":
|
case OutputFormatJSON:
|
||||||
out, err := Bech32KeyOutput(info)
|
out, err := Bech32KeyOutput(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
if !viper.GetBool(flagNoBackup) {
|
|
||||||
out.Seed = seed
|
if showMnemonic {
|
||||||
|
out.Mnemonic = mnemonic
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsonString []byte
|
var jsonString []byte
|
||||||
if viper.GetBool(client.FlagIndentResponse) {
|
if viper.GetBool(client.FlagIndentResponse) {
|
||||||
jsonString, err = cdc.MarshalJSONIndent(out, "", " ")
|
jsonString, err = cdc.MarshalJSONIndent(out, "", " ")
|
||||||
} else {
|
} else {
|
||||||
jsonString, err = cdc.MarshalJSON(out)
|
jsonString, err = cdc.MarshalJSON(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err) // really shouldn't happen...
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintln(os.Stderr, string(jsonString))
|
fmt.Fprintln(os.Stderr, string(jsonString))
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("I can't speak: %s", output))
|
return fmt.Errorf("I can't speak: %s", output)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// function to just a new seed to display in the UI before actually persisting it in the keybase
|
return nil
|
||||||
func getSeed(algo keys.SigningAlgo) string {
|
|
||||||
kb := client.MockKeyBase()
|
|
||||||
pass := "throwing-this-key-away"
|
|
||||||
name := "inmemorykey"
|
|
||||||
_, seed, _ := kb.CreateMnemonic(name, keys.English, pass, algo)
|
|
||||||
return seed
|
|
||||||
}
|
|
||||||
|
|
||||||
func printPrefixed(msg string) {
|
|
||||||
fmt.Fprintln(os.Stderr, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printStep() {
|
|
||||||
printPrefixed("-------------------------------------")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
// REST
|
// REST
|
||||||
|
|
||||||
// new key request REST body
|
// function to just create a new seed to display in the UI before actually persisting it in the keybase
|
||||||
type NewKeyBody struct {
|
func generateMnemonic(algo keys.SigningAlgo) string {
|
||||||
Name string `json:"name"`
|
kb := client.MockKeyBase()
|
||||||
Password string `json:"password"`
|
pass := app.DefaultKeyPass
|
||||||
Seed string `json:"seed"`
|
name := "inmemorykey"
|
||||||
|
_, seed, _ := kb.CreateMnemonic(name, keys.English, pass, algo)
|
||||||
|
return seed
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckAndWriteErrorResponse will check for errors and return
|
||||||
|
// a given error message when corresponding
|
||||||
|
//TODO: Move to utils/rest or similar
|
||||||
|
func CheckAndWriteErrorResponse(w http.ResponseWriter, httpErr int, err error) bool {
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(httpErr)
|
||||||
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// add new key REST handler
|
// add new key REST handler
|
||||||
func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
|
func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var kb keys.Keybase
|
var kb keys.Keybase
|
||||||
var m NewKeyBody
|
var m AddNewKey
|
||||||
|
|
||||||
kb, err := GetKeyBaseWithWritePerm()
|
kb, err := NewKeyBaseFromHomeFlag()
|
||||||
if err != nil {
|
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(body, &m)
|
err = json.Unmarshal(body, &m)
|
||||||
if err != nil {
|
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check parameters
|
||||||
if m.Name == "" {
|
if m.Name == "" {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingName())
|
||||||
err = errMissingName()
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if m.Password == "" {
|
if m.Password == "" {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingPassword())
|
||||||
err = errMissingPassword()
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if already exists
|
mnemonic := m.Mnemonic
|
||||||
infos, err := kb.List()
|
// if mnemonic is empty, generate one
|
||||||
for _, info := range infos {
|
if mnemonic == "" {
|
||||||
if info.GetName() == m.Name {
|
mnemonic = generateMnemonic(keys.Secp256k1)
|
||||||
w.WriteHeader(http.StatusConflict)
|
}
|
||||||
err = errKeyNameConflict(m.Name)
|
if !bip39.IsMnemonicValid(mnemonic) {
|
||||||
w.Write([]byte(err.Error()))
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidMnemonic())
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
if m.Account < 0 || m.Account > maxValidAccountValue {
|
||||||
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidAccountNumber())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Index < 0 || m.Index > maxValidIndexalue {
|
||||||
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidIndexNumber())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = kb.Get(m.Name)
|
||||||
|
if err == nil {
|
||||||
|
CheckAndWriteErrorResponse(w, http.StatusConflict, errKeyNameConflict(m.Name))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// create account
|
// create account
|
||||||
seed := m.Seed
|
account := uint32(m.Account)
|
||||||
if seed == "" {
|
index := uint32(m.Index)
|
||||||
seed = getSeed(keys.Secp256k1)
|
info, err := kb.CreateAccount(m.Name, mnemonic, keys.DefaultBIP39Passphrase, m.Password, account, index)
|
||||||
}
|
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
|
||||||
info, err := kb.CreateKey(m.Name, seed, m.Password)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
keyOutput, err := Bech32KeyOutput(info)
|
keyOutput, err := Bech32KeyOutput(info)
|
||||||
if err != nil {
|
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
keyOutput.Seed = seed
|
keyOutput.Mnemonic = mnemonic
|
||||||
|
|
||||||
PostProcessResponse(w, cdc, keyOutput, indent)
|
PostProcessResponse(w, cdc, keyOutput, indent)
|
||||||
}
|
}
|
||||||
|
@ -426,22 +407,17 @@ func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
|
||||||
func SeedRequestHandler(w http.ResponseWriter, r *http.Request) {
|
func SeedRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
algoType := vars["type"]
|
algoType := vars["type"]
|
||||||
|
|
||||||
// algo type defaults to secp256k1
|
// algo type defaults to secp256k1
|
||||||
if algoType == "" {
|
if algoType == "" {
|
||||||
algoType = "secp256k1"
|
algoType = "secp256k1"
|
||||||
}
|
}
|
||||||
algo := keys.SigningAlgo(algoType)
|
|
||||||
|
|
||||||
seed := getSeed(algo)
|
algo := keys.SigningAlgo(algoType)
|
||||||
|
seed := generateMnemonic(algo)
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Write([]byte(seed))
|
_, _ = w.Write([]byte(seed))
|
||||||
}
|
|
||||||
|
|
||||||
// RecoverKeyBody is recover key request REST body
|
|
||||||
type RecoverKeyBody struct {
|
|
||||||
Password string `json:"password"`
|
|
||||||
Seed string `json:"seed"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecoverRequestHandler performs key recover request
|
// RecoverRequestHandler performs key recover request
|
||||||
|
@ -449,67 +425,66 @@ func RecoverRequestHandler(indent bool) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
name := vars["name"]
|
name := vars["name"]
|
||||||
var m RecoverKeyBody
|
var m RecoverKey
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = cdc.UnmarshalJSON(body, &m)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = cdc.UnmarshalJSON(body, &m)
|
||||||
|
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kb, err := NewKeyBaseFromHomeFlag()
|
||||||
|
CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err)
|
||||||
|
|
||||||
if name == "" {
|
if name == "" {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingName())
|
||||||
err = errMissingName()
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if m.Password == "" {
|
if m.Password == "" {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingPassword())
|
||||||
err = errMissingPassword()
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if m.Seed == "" {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
err = errMissingSeed()
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
kb, err := GetKeyBaseWithWritePerm()
|
mnemonic := m.Mnemonic
|
||||||
if err != nil {
|
if !bip39.IsMnemonicValid(mnemonic) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidMnemonic())
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// check if already exists
|
|
||||||
infos, err := kb.List()
|
|
||||||
for _, info := range infos {
|
|
||||||
if info.GetName() == name {
|
|
||||||
w.WriteHeader(http.StatusConflict)
|
|
||||||
err = errKeyNameConflict(name)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := kb.CreateKey(name, m.Seed, m.Password)
|
if m.Mnemonic == "" {
|
||||||
if err != nil {
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingMnemonic())
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
return
|
||||||
w.Write([]byte(err.Error()))
|
}
|
||||||
|
|
||||||
|
if m.Account < 0 || m.Account > maxValidAccountValue {
|
||||||
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidAccountNumber())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Index < 0 || m.Index > maxValidIndexalue {
|
||||||
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidIndexNumber())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = kb.Get(name)
|
||||||
|
if err == nil {
|
||||||
|
CheckAndWriteErrorResponse(w, http.StatusConflict, errKeyNameConflict(name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
account := uint32(m.Account)
|
||||||
|
index := uint32(m.Index)
|
||||||
|
|
||||||
|
info, err := kb.CreateAccount(name, mnemonic, keys.DefaultBIP39Passphrase, m.Password, account, index)
|
||||||
|
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
keyOutput, err := Bech32KeyOutput(info)
|
keyOutput, err := Bech32KeyOutput(info)
|
||||||
if err != nil {
|
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/tendermint/tendermint/libs/cli"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_runAddCmdBasic(t *testing.T) {
|
||||||
|
cmd := addKeyCommand()
|
||||||
|
assert.NotNil(t, cmd)
|
||||||
|
|
||||||
|
// Missing input (enter password)
|
||||||
|
err := runAddCmd(cmd, []string{"keyname"})
|
||||||
|
assert.EqualError(t, err, "EOF")
|
||||||
|
|
||||||
|
// Prepare a keybase
|
||||||
|
kbHome, kbCleanUp := tests.NewTestCaseDir(t)
|
||||||
|
assert.NotNil(t, kbHome)
|
||||||
|
defer kbCleanUp()
|
||||||
|
viper.Set(cli.HomeFlag, kbHome)
|
||||||
|
|
||||||
|
/// Test Text
|
||||||
|
viper.Set(cli.OutputFlag, OutputFormatText)
|
||||||
|
// Now enter password
|
||||||
|
cleanUp1 := client.OverrideStdin(bufio.NewReader(strings.NewReader("test1234\ntest1234\n")))
|
||||||
|
defer cleanUp1()
|
||||||
|
err = runAddCmd(cmd, []string{"keyname1"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
/// Test Text - Replace? >> FAIL
|
||||||
|
viper.Set(cli.OutputFlag, OutputFormatText)
|
||||||
|
// Now enter password
|
||||||
|
cleanUp2 := client.OverrideStdin(bufio.NewReader(strings.NewReader("test1234\ntest1234\n")))
|
||||||
|
defer cleanUp2()
|
||||||
|
err = runAddCmd(cmd, []string{"keyname1"})
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
/// Test Text - Replace? Answer >> PASS
|
||||||
|
viper.Set(cli.OutputFlag, OutputFormatText)
|
||||||
|
// Now enter password
|
||||||
|
cleanUp3 := client.OverrideStdin(bufio.NewReader(strings.NewReader("y\ntest1234\ntest1234\n")))
|
||||||
|
defer cleanUp3()
|
||||||
|
err = runAddCmd(cmd, []string{"keyname1"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Check JSON
|
||||||
|
viper.Set(cli.OutputFlag, OutputFormatJSON)
|
||||||
|
// Now enter password
|
||||||
|
cleanUp4 := client.OverrideStdin(bufio.NewReader(strings.NewReader("test1234\ntest1234\n")))
|
||||||
|
defer cleanUp4()
|
||||||
|
err = runAddCmd(cmd, []string{"keyname2"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockResponseWriter struct {
|
||||||
|
dataHeaderStatus int
|
||||||
|
dataBody []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MockResponseWriter) Header() http.Header {
|
||||||
|
panic("Unexpected call!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *MockResponseWriter) Write(data []byte) (int, error) {
|
||||||
|
w.dataBody = append(w.dataBody, data...)
|
||||||
|
return len(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *MockResponseWriter) WriteHeader(statusCode int) {
|
||||||
|
w.dataHeaderStatus = statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckAndWriteErrorResponse(t *testing.T) {
|
||||||
|
mockRW := MockResponseWriter{}
|
||||||
|
|
||||||
|
mockRW.WriteHeader(100)
|
||||||
|
assert.Equal(t, 100, mockRW.dataHeaderStatus)
|
||||||
|
|
||||||
|
detected := CheckAndWriteErrorResponse(&mockRW, http.StatusBadRequest, errors.New("some ERROR"))
|
||||||
|
require.True(t, detected)
|
||||||
|
require.Equal(t, http.StatusBadRequest, mockRW.dataHeaderStatus)
|
||||||
|
require.Equal(t, "some ERROR", string(mockRW.dataBody[:]))
|
||||||
|
|
||||||
|
mockRW = MockResponseWriter{}
|
||||||
|
detected = CheckAndWriteErrorResponse(&mockRW, http.StatusBadRequest, nil)
|
||||||
|
require.False(t, detected)
|
||||||
|
require.Equal(t, 0, mockRW.dataHeaderStatus)
|
||||||
|
require.Equal(t, "", string(mockRW.dataBody[:]))
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testCases struct {
|
||||||
|
Keys []KeyOutput
|
||||||
|
Answers []KeyOutput
|
||||||
|
JSON [][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTestCases() testCases {
|
||||||
|
return testCases{
|
||||||
|
[]KeyOutput{
|
||||||
|
{"A", "B", "C", "D", "E"},
|
||||||
|
{"A", "B", "C", "D", ""},
|
||||||
|
{"", "B", "C", "D", ""},
|
||||||
|
{"", "", "", "", ""},
|
||||||
|
},
|
||||||
|
make([]KeyOutput, 4),
|
||||||
|
[][]byte{
|
||||||
|
[]byte(`{"name":"A","type":"B","address":"C","pub_key":"D","mnemonic":"E"}`),
|
||||||
|
[]byte(`{"name":"A","type":"B","address":"C","pub_key":"D"}`),
|
||||||
|
[]byte(`{"name":"","type":"B","address":"C","pub_key":"D"}`),
|
||||||
|
[]byte(`{"name":"","type":"","address":"","pub_key":""}`),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalJSON(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
o KeyOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
data := getTestCases()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []byte
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"basic", args{data.Keys[0]}, []byte(data.JSON[0]), false},
|
||||||
|
{"mnemonic is optional", args{data.Keys[1]}, []byte(data.JSON[1]), false},
|
||||||
|
|
||||||
|
// REVIEW: Are the next results expected??
|
||||||
|
{"empty name", args{data.Keys[2]}, []byte(data.JSON[2]), false},
|
||||||
|
{"empty object", args{data.Keys[3]}, []byte(data.JSON[3]), false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := MarshalJSON(tt.args.o)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("%s\n", got)
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("MarshalJSON() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalJSON(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
bz []byte
|
||||||
|
ptr interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := getTestCases()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"basic", args{data.JSON[0], &data.Answers[0]}, false},
|
||||||
|
{"mnemonic is optional", args{data.JSON[1], &data.Answers[1]}, false},
|
||||||
|
|
||||||
|
// REVIEW: Are the next results expected??
|
||||||
|
{"empty name", args{data.JSON[2], &data.Answers[2]}, false},
|
||||||
|
{"empty object", args{data.JSON[3], &data.Answers[3]}, false},
|
||||||
|
}
|
||||||
|
for idx, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := UnmarshalJSON(tt.args.bz, tt.args.ptr); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm deserialized objects are the same
|
||||||
|
require.Equal(t, data.Keys[idx], data.Answers[idx])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,8 +13,8 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
keyerror "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -49,7 +49,7 @@ gaiacli.
|
||||||
func runDeleteCmd(cmd *cobra.Command, args []string) error {
|
func runDeleteCmd(cmd *cobra.Command, args []string) error {
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
kb, err := GetKeyBaseWithWritePerm()
|
kb, err := NewKeyBaseFromHomeFlag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,17 @@ func runDeleteCmd(cmd *cobra.Command, args []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func confirmDeletion(buf *bufio.Reader) error {
|
||||||
|
answer, err := client.GetConfirmation("Key reference will be deleted. Continue?", buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !answer {
|
||||||
|
return errors.New("aborted")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////
|
////////////////////////
|
||||||
// REST
|
// REST
|
||||||
|
|
||||||
|
@ -110,42 +121,31 @@ func DeleteKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
err := decoder.Decode(&m)
|
err := decoder.Decode(&m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
kb, err = GetKeyBaseWithWritePerm()
|
kb, err = NewKeyBaseFromHomeFlag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = kb.Delete(name, m.Password, false)
|
err = kb.Delete(name, m.Password, false)
|
||||||
if keyerror.IsErrKeyNotFound(err) {
|
if keyerror.IsErrKeyNotFound(err) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
} else if keyerror.IsErrWrongPassword(err) {
|
} else if keyerror.IsErrWrongPassword(err) {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func confirmDeletion(buf *bufio.Reader) error {
|
|
||||||
answer, err := client.GetConfirmation("Key reference will be deleted. Continue?", buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !answer {
|
|
||||||
return errors.New("aborted")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/tendermint/tendermint/libs/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_runDeleteCmd(t *testing.T) {
|
||||||
|
deleteKeyCommand := deleteKeyCommand()
|
||||||
|
|
||||||
|
yesF, _ := deleteKeyCommand.Flags().GetBool(flagYes)
|
||||||
|
forceF, _ := deleteKeyCommand.Flags().GetBool(flagForce)
|
||||||
|
assert.False(t, yesF)
|
||||||
|
assert.False(t, forceF)
|
||||||
|
|
||||||
|
fakeKeyName1 := "runDeleteCmd_Key1"
|
||||||
|
fakeKeyName2 := "runDeleteCmd_Key2"
|
||||||
|
|
||||||
|
// Now add a temporary keybase
|
||||||
|
kbHome, cleanUp := tests.NewTestCaseDir(t)
|
||||||
|
defer cleanUp()
|
||||||
|
viper.Set(cli.HomeFlag, kbHome)
|
||||||
|
|
||||||
|
// Now
|
||||||
|
kb, err := NewKeyBaseFromHomeFlag()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", 0, 0)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", 0, 1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = runDeleteCmd(deleteKeyCommand, []string{"blah"})
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, "Key blah not found", err.Error())
|
||||||
|
|
||||||
|
// User confirmation missing
|
||||||
|
err = runDeleteCmd(deleteKeyCommand, []string{fakeKeyName1})
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, "EOF", err.Error())
|
||||||
|
|
||||||
|
{
|
||||||
|
_, err = kb.Get(fakeKeyName1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Now there is a confirmation
|
||||||
|
cleanUp := client.OverrideStdin(bufio.NewReader(strings.NewReader("y\n")))
|
||||||
|
defer cleanUp()
|
||||||
|
err = runDeleteCmd(deleteKeyCommand, []string{fakeKeyName1})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = kb.Get(fakeKeyName1)
|
||||||
|
require.Error(t, err) // Key1 is gone
|
||||||
|
}
|
||||||
|
|
||||||
|
viper.Set(flagYes, true)
|
||||||
|
_, err = kb.Get(fakeKeyName2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = runDeleteCmd(deleteKeyCommand, []string{fakeKeyName2})
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = kb.Get(fakeKeyName2)
|
||||||
|
require.Error(t, err) // Key2 is gone
|
||||||
|
|
||||||
|
// TODO: Write another case for !keys.Local
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_confirmDeletion(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
buf *bufio.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
answerYes := bufio.NewReader(strings.NewReader("y\n"))
|
||||||
|
answerYes2 := bufio.NewReader(strings.NewReader("Y\n"))
|
||||||
|
answerNo := bufio.NewReader(strings.NewReader("n\n"))
|
||||||
|
answerInvalid := bufio.NewReader(strings.NewReader("245\n"))
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"Y", args{answerYes}, false},
|
||||||
|
{"y", args{answerYes2}, false},
|
||||||
|
{"N", args{answerNo}, true},
|
||||||
|
{"BAD", args{answerInvalid}, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := confirmDeletion(tt.args.buf); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("confirmDeletion() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ package keys
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
func errKeyNameConflict(name string) error {
|
func errKeyNameConflict(name string) error {
|
||||||
return fmt.Errorf("acount with name %s already exists", name)
|
return fmt.Errorf("account with name %s already exists", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func errMissingName() error {
|
func errMissingName() error {
|
||||||
|
@ -14,6 +14,18 @@ func errMissingPassword() error {
|
||||||
return fmt.Errorf("you have to specify a password for the locally stored account")
|
return fmt.Errorf("you have to specify a password for the locally stored account")
|
||||||
}
|
}
|
||||||
|
|
||||||
func errMissingSeed() error {
|
func errMissingMnemonic() error {
|
||||||
return fmt.Errorf("you have to specify seed for key recover")
|
return fmt.Errorf("you have to specify a mnemonic for key recovery")
|
||||||
|
}
|
||||||
|
|
||||||
|
func errInvalidMnemonic() error {
|
||||||
|
return fmt.Errorf("the mnemonic is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
func errInvalidAccountNumber() error {
|
||||||
|
return fmt.Errorf("the account number is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
func errInvalidIndexNumber() error {
|
||||||
|
return fmt.Errorf("the index number is invalid")
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,19 +6,18 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CMD
|
func listKeysCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
// listKeysCmd represents the list command
|
Use: "list",
|
||||||
var listKeysCmd = &cobra.Command{
|
Short: "List all keys",
|
||||||
Use: "list",
|
Long: `Return a list of all public keys stored by this key manager
|
||||||
Short: "List all keys",
|
|
||||||
Long: `Return a list of all public keys stored by this key manager
|
|
||||||
along with their associated name and address.`,
|
along with their associated name and address.`,
|
||||||
RunE: runListCmd,
|
RunE: runListCmd,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runListCmd(cmd *cobra.Command, args []string) error {
|
func runListCmd(cmd *cobra.Command, args []string) error {
|
||||||
kb, err := GetKeyBase()
|
kb, err := NewKeyBaseFromHomeFlag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -36,16 +35,16 @@ func runListCmd(cmd *cobra.Command, args []string) error {
|
||||||
// query key list REST handler
|
// query key list REST handler
|
||||||
func QueryKeysRequestHandler(indent bool) http.HandlerFunc {
|
func QueryKeysRequestHandler(indent bool) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
kb, err := GetKeyBase()
|
kb, err := NewKeyBaseFromHomeFlag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
infos, err := kb.List()
|
infos, err := kb.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// an empty list will be JSONized as null, but we want to keep the empty list
|
// an empty list will be JSONized as null, but we want to keep the empty list
|
||||||
|
@ -56,7 +55,7 @@ func QueryKeysRequestHandler(indent bool) http.HandlerFunc {
|
||||||
keysOutput, err := Bech32KeysOutput(infos)
|
keysOutput, err := Bech32KeysOutput(infos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
PostProcessResponse(w, cdc, keysOutput, indent)
|
PostProcessResponse(w, cdc, keysOutput, indent)
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/tendermint/tendermint/libs/cli"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_runListCmd(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
cmd *cobra.Command
|
||||||
|
args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdBasic := listKeysCmd()
|
||||||
|
|
||||||
|
// Prepare some keybases
|
||||||
|
kbHome1, cleanUp1 := tests.NewTestCaseDir(t)
|
||||||
|
defer cleanUp1()
|
||||||
|
// Do nothing, leave home1 empty
|
||||||
|
|
||||||
|
kbHome2, cleanUp2 := tests.NewTestCaseDir(t)
|
||||||
|
defer cleanUp2()
|
||||||
|
viper.Set(cli.HomeFlag, kbHome2)
|
||||||
|
|
||||||
|
kb, err := NewKeyBaseFromHomeFlag()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = kb.CreateAccount("something", tests.TestMnemonic, "", "", 0, 0)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
testData := []struct {
|
||||||
|
name string
|
||||||
|
kbDir string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"invalid keybase", "/dev/null", args{cmdBasic, []string{}}, true},
|
||||||
|
{"keybase: empty", kbHome1, args{cmdBasic, []string{}}, false},
|
||||||
|
{"keybase: w/key", kbHome2, args{cmdBasic, []string{}}, false},
|
||||||
|
}
|
||||||
|
for _, tt := range testData {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
viper.Set(cli.HomeFlag, tt.kbDir)
|
||||||
|
if err := runListCmd(tt.args.cmd, tt.args.args); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("runListCmd() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,11 +4,10 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
bip39 "github.com/bartekn/go-bip39"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
|
||||||
bip39 "github.com/bartekn/go-bip39"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -45,9 +44,7 @@ func runMnemonicCmd(cmd *cobra.Command, args []string) error {
|
||||||
if len(inputEntropy) < 43 {
|
if len(inputEntropy) < 43 {
|
||||||
return fmt.Errorf("256-bits is 43 characters in Base-64, and 100 in Base-6. You entered %v, and probably want more", len(inputEntropy))
|
return fmt.Errorf("256-bits is 43 characters in Base-64, and 100 in Base-6. You entered %v, and probably want more", len(inputEntropy))
|
||||||
}
|
}
|
||||||
conf, err := client.GetConfirmation(
|
conf, err := client.GetConfirmation(fmt.Sprintf("> Input length: %d", len(inputEntropy)), buf)
|
||||||
fmt.Sprintf("> Input length: %d", len(inputEntropy)),
|
|
||||||
buf)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -58,7 +55,6 @@ func runMnemonicCmd(cmd *cobra.Command, args []string) error {
|
||||||
// hash input entropy to get entropy seed
|
// hash input entropy to get entropy seed
|
||||||
hashedEntropy := sha256.Sum256([]byte(inputEntropy))
|
hashedEntropy := sha256.Sum256([]byte(inputEntropy))
|
||||||
entropySeed = hashedEntropy[:]
|
entropySeed = hashedEntropy[:]
|
||||||
printStep()
|
|
||||||
} else {
|
} else {
|
||||||
// read entropy seed straight from crypto.Rand
|
// read entropy seed straight from crypto.Rand
|
||||||
var err error
|
var err error
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_RunMnemonicCmdNormal(t *testing.T) {
|
||||||
|
cmdBasic := mnemonicKeyCommand()
|
||||||
|
err := runMnemonicCmd(cmdBasic, []string{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_RunMnemonicCmdUser(t *testing.T) {
|
||||||
|
cmdUser := mnemonicKeyCommand()
|
||||||
|
err := cmdUser.Flags().Set(flagUserEntropy, "1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = runMnemonicCmd(cmdUser, []string{})
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, "EOF", err.Error())
|
||||||
|
|
||||||
|
// Try again
|
||||||
|
cleanUp := client.OverrideStdin(bufio.NewReader(strings.NewReader("Hi!\n")))
|
||||||
|
defer cleanUp()
|
||||||
|
err = runMnemonicCmd(cmdUser, []string{})
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t,
|
||||||
|
"256-bits is 43 characters in Base-64, and 100 in Base-6. You entered 3, and probably want more",
|
||||||
|
err.Error())
|
||||||
|
|
||||||
|
// Now provide "good" entropy :)
|
||||||
|
fakeEntropy := strings.Repeat(":)", 40) + "\ny\n" // entropy + accept count
|
||||||
|
cleanUp2 := client.OverrideStdin(bufio.NewReader(strings.NewReader(fakeEntropy)))
|
||||||
|
defer cleanUp2()
|
||||||
|
err = runMnemonicCmd(cmdUser, []string{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Now provide "good" entropy but no answer
|
||||||
|
fakeEntropy = strings.Repeat(":)", 40) + "\n" // entropy + accept count
|
||||||
|
cleanUp3 := client.OverrideStdin(bufio.NewReader(strings.NewReader(fakeEntropy)))
|
||||||
|
defer cleanUp3()
|
||||||
|
err = runMnemonicCmd(cmdUser, []string{})
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// Now provide "good" entropy but say no
|
||||||
|
fakeEntropy = strings.Repeat(":)", 40) + "\nn\n" // entropy + accept count
|
||||||
|
cleanUp4 := client.OverrideStdin(bufio.NewReader(strings.NewReader(fakeEntropy)))
|
||||||
|
defer cleanUp4()
|
||||||
|
err = runMnemonicCmd(cmdUser, []string{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ func Commands() *cobra.Command {
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
mnemonicKeyCommand(),
|
mnemonicKeyCommand(),
|
||||||
addKeyCommand(),
|
addKeyCommand(),
|
||||||
listKeysCmd,
|
listKeysCmd(),
|
||||||
showKeysCmd(),
|
showKeysCmd(),
|
||||||
client.LineBreak,
|
client.LineBreak,
|
||||||
deleteKeyCommand(),
|
deleteKeyCommand(),
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommands(t *testing.T) {
|
||||||
|
rootCommands := Commands()
|
||||||
|
assert.NotNil(t, rootCommands)
|
||||||
|
|
||||||
|
// Commands are registered
|
||||||
|
assert.Equal(t, 7, len(rootCommands.Commands()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterRoutes(t *testing.T) {
|
||||||
|
fakeRouter := mux.Router{}
|
||||||
|
RegisterRoutes(&fakeRouter, false)
|
||||||
|
}
|
|
@ -8,8 +8,9 @@ import (
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
|
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/tendermint/tendermint/crypto/multisig"
|
"github.com/tendermint/tendermint/crypto/multisig"
|
||||||
|
@ -92,7 +93,12 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
|
||||||
isShowAddr := viper.GetBool(FlagAddress)
|
isShowAddr := viper.GetBool(FlagAddress)
|
||||||
isShowPubKey := viper.GetBool(FlagPublicKey)
|
isShowPubKey := viper.GetBool(FlagPublicKey)
|
||||||
isOutputSet := cmd.Flag(cli.OutputFlag).Changed
|
|
||||||
|
isOutputSet := false
|
||||||
|
tmp := cmd.Flag(cli.OutputFlag)
|
||||||
|
if tmp != nil {
|
||||||
|
isOutputSet = tmp.Changed
|
||||||
|
}
|
||||||
|
|
||||||
if isShowAddr && isShowPubKey {
|
if isShowAddr && isShowPubKey {
|
||||||
return errors.New("cannot use both --address and --pubkey at once")
|
return errors.New("cannot use both --address and --pubkey at once")
|
||||||
|
@ -160,25 +166,25 @@ func GetKeyRequestHandler(indent bool) http.HandlerFunc {
|
||||||
bechKeyOut, err := getBechKeyOut(bechPrefix)
|
bechKeyOut, err := getBechKeyOut(bechPrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := GetKeyInfo(name)
|
info, err := GetKeyInfo(name)
|
||||||
if keyerror.IsErrKeyNotFound(err) {
|
if keyerror.IsErrKeyNotFound(err) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
keyOutput, err := bechKeyOut(info)
|
keyOutput, err := bechKeyOut(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/tendermint/tendermint/libs/cli"
|
||||||
|
|
||||||
|
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_multiSigKey_Properties(t *testing.T) {
|
||||||
|
tmpKey1 := secp256k1.GenPrivKeySecp256k1([]byte("mySecret"))
|
||||||
|
|
||||||
|
tmp := multiSigKey{
|
||||||
|
name: "myMultisig",
|
||||||
|
key: tmpKey1.PubKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "myMultisig", tmp.GetName())
|
||||||
|
assert.Equal(t, keys.TypeLocal, tmp.GetType())
|
||||||
|
assert.Equal(t, "015ABFFB09DB738A45745A91E8C401423ECE4016", tmp.GetPubKey().Address().String())
|
||||||
|
assert.Equal(t, "cosmos1q9dtl7cfmdec53t5t2g733qpgglvusqk6xdntl", tmp.GetAddress().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_showKeysCmd(t *testing.T) {
|
||||||
|
cmd := showKeysCmd()
|
||||||
|
assert.NotNil(t, cmd)
|
||||||
|
assert.Equal(t, "false", cmd.Flag(FlagAddress).DefValue)
|
||||||
|
assert.Equal(t, "false", cmd.Flag(FlagPublicKey).DefValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_runShowCmd(t *testing.T) {
|
||||||
|
cmd := showKeysCmd()
|
||||||
|
|
||||||
|
err := runShowCmd(cmd, []string{"invalid"})
|
||||||
|
assert.EqualError(t, err, "Key invalid not found")
|
||||||
|
|
||||||
|
err = runShowCmd(cmd, []string{"invalid1", "invalid2"})
|
||||||
|
assert.EqualError(t, err, "Key invalid1 not found")
|
||||||
|
|
||||||
|
// Prepare a key base
|
||||||
|
// Now add a temporary keybase
|
||||||
|
kbHome, cleanUp := tests.NewTestCaseDir(t)
|
||||||
|
defer cleanUp()
|
||||||
|
viper.Set(cli.HomeFlag, kbHome)
|
||||||
|
|
||||||
|
fakeKeyName1 := "runShowCmd_Key1"
|
||||||
|
fakeKeyName2 := "runShowCmd_Key2"
|
||||||
|
kb, err := NewKeyBaseFromHomeFlag()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", 0, 0)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", 0, 1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Now try single key
|
||||||
|
err = runShowCmd(cmd, []string{fakeKeyName1})
|
||||||
|
assert.EqualError(t, err, "invalid Bech32 prefix encoding provided: ")
|
||||||
|
|
||||||
|
// Now try single key - set bech to acc
|
||||||
|
viper.Set(FlagBechPrefix, "acc")
|
||||||
|
err = runShowCmd(cmd, []string{fakeKeyName1})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Now try multisig key - set bech to acc
|
||||||
|
viper.Set(FlagBechPrefix, "acc")
|
||||||
|
err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2})
|
||||||
|
assert.EqualError(t, err, "threshold must be a positive integer")
|
||||||
|
|
||||||
|
// Now try multisig key - set bech to acc + threshold=2
|
||||||
|
viper.Set(FlagBechPrefix, "acc")
|
||||||
|
viper.Set(flagMultiSigThreshold, 2)
|
||||||
|
err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// TODO: Capture stdout and compare
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_validateMultisigThreshold(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
k int
|
||||||
|
nKeys int
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"zeros", args{0, 0}, true},
|
||||||
|
{"1-0", args{1, 0}, true},
|
||||||
|
{"1-1", args{1, 1}, false},
|
||||||
|
{"1-2", args{1, 1}, false},
|
||||||
|
{"1-2", args{2, 1}, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := validateMultisigThreshold(tt.args.k, tt.args.nKeys); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("validateMultisigThreshold() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_getBechKeyOut(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
bechPrefix string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want bechKeyOutFn
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"empty", args{""}, nil, true},
|
||||||
|
{"wrong", args{"???"}, nil, true},
|
||||||
|
{"acc", args{"acc"}, Bech32KeyOutput, false},
|
||||||
|
{"val", args{"val"}, Bech32ValKeyOutput, false},
|
||||||
|
{"cons", args{"cons"}, Bech32ConsKeyOutput, false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := getBechKeyOut(tt.args.bechPrefix)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("getBechKeyOut() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.wantErr {
|
||||||
|
assert.NotNil(t, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Still not possible to compare functions
|
||||||
|
// Maybe in next release: https://github.com/stretchr/testify/issues/182
|
||||||
|
//if &got != &tt.want {
|
||||||
|
// t.Errorf("getBechKeyOut() = %v, want %v", got, tt.want)
|
||||||
|
//}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
// used for outputting keys.Info over REST
|
||||||
|
type KeyOutput struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
PubKey string `json:"pub_key"`
|
||||||
|
Mnemonic string `json:"mnemonic,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNewKey request a new key
|
||||||
|
type AddNewKey struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Mnemonic string `json:"mnemonic"`
|
||||||
|
Account int `json:"account,string,omitempty"`
|
||||||
|
Index int `json:"index,string,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecoverKeyBody recovers a key
|
||||||
|
type RecoverKey struct {
|
||||||
|
Password string `json:"password"`
|
||||||
|
Mnemonic string `json:"mnemonic"`
|
||||||
|
Account int `json:"account,string,omitempty"`
|
||||||
|
Index int `json:"index,string,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateKeyReq requests updating a key
|
||||||
|
type UpdateKeyReq struct {
|
||||||
|
OldPassword string `json:"old_password"`
|
||||||
|
NewPassword string `json:"new_password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteKeyReq requests deleting a key
|
||||||
|
type DeleteKeyReq struct {
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ func runUpdateCmd(cmd *cobra.Command, args []string) error {
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
buf := client.BufferStdin()
|
buf := client.BufferStdin()
|
||||||
kb, err := GetKeyBaseWithWritePerm()
|
kb, err := NewKeyBaseFromHomeFlag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -73,14 +73,14 @@ func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
err := decoder.Decode(&m)
|
err := decoder.Decode(&m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
kb, err = GetKeyBaseWithWritePerm()
|
kb, err = NewKeyBaseFromHomeFlag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,15 +89,15 @@ func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
err = kb.Update(name, m.OldPassword, getNewpass)
|
err = kb.Update(name, m.OldPassword, getNewpass)
|
||||||
if keyerror.IsErrKeyNotFound(err) {
|
if keyerror.IsErrKeyNotFound(err) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
} else if keyerror.IsErrWrongPassword(err) {
|
} else if keyerror.IsErrWrongPassword(err) {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/tendermint/tendermint/libs/cli"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_updateKeyCommand(t *testing.T) {
|
||||||
|
cmd := updateKeyCommand()
|
||||||
|
assert.NotNil(t, cmd)
|
||||||
|
// No flags or defaults to validate
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_runUpdateCmd(t *testing.T) {
|
||||||
|
fakeKeyName1 := "runUpdateCmd_Key1"
|
||||||
|
fakeKeyName2 := "runUpdateCmd_Key2"
|
||||||
|
|
||||||
|
cmd := updateKeyCommand()
|
||||||
|
|
||||||
|
// fails because it requests a password
|
||||||
|
err := runUpdateCmd(cmd, []string{fakeKeyName1})
|
||||||
|
assert.EqualError(t, err, "EOF")
|
||||||
|
|
||||||
|
cleanUp := client.OverrideStdin(bufio.NewReader(strings.NewReader("pass1234\n")))
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
// try again
|
||||||
|
err = runUpdateCmd(cmd, []string{fakeKeyName1})
|
||||||
|
assert.EqualError(t, err, "Key runUpdateCmd_Key1 not found")
|
||||||
|
|
||||||
|
// Prepare a key base
|
||||||
|
// Now add a temporary keybase
|
||||||
|
kbHome, cleanUp1 := tests.NewTestCaseDir(t)
|
||||||
|
defer cleanUp1()
|
||||||
|
viper.Set(cli.HomeFlag, kbHome)
|
||||||
|
|
||||||
|
kb, err := NewKeyBaseFromHomeFlag()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", 0, 0)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", 0, 1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Try again now that we have keys
|
||||||
|
cleanUp2 := client.OverrideStdin(bufio.NewReader(strings.NewReader("pass1234\nNew1234\nNew1234")))
|
||||||
|
defer cleanUp2()
|
||||||
|
|
||||||
|
// Incorrect key type
|
||||||
|
err = runUpdateCmd(cmd, []string{fakeKeyName1})
|
||||||
|
assert.EqualError(t, err, "locally stored key required. Received: keys.offlineInfo")
|
||||||
|
|
||||||
|
// TODO: Check for other type types?
|
||||||
|
|
||||||
|
}
|
|
@ -6,9 +6,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
|
||||||
"github.com/tendermint/tendermint/libs/cli"
|
"github.com/tendermint/tendermint/libs/cli"
|
||||||
dbm "github.com/tendermint/tendermint/libs/db"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
@ -17,17 +15,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// KeyDBName is the directory under root where we store the keys
|
// KeyDBName is the directory under root where we store the keys
|
||||||
const KeyDBName = "keys"
|
const (
|
||||||
|
KeyDBName = "keys"
|
||||||
// keybase is used to make GetKeyBase a singleton
|
OutputFormatText = "text"
|
||||||
var keybase keys.Keybase
|
OutputFormatJSON = "json"
|
||||||
|
)
|
||||||
|
|
||||||
type bechKeyOutFn func(keyInfo keys.Info) (KeyOutput, error)
|
type bechKeyOutFn func(keyInfo keys.Info) (KeyOutput, error)
|
||||||
|
|
||||||
// GetKeyInfo returns key info for a given name. An error is returned if the
|
// GetKeyInfo returns key info for a given name. An error is returned if the
|
||||||
// keybase cannot be retrieved or getting the info fails.
|
// keybase cannot be retrieved or getting the info fails.
|
||||||
func GetKeyInfo(name string) (keys.Info, error) {
|
func GetKeyInfo(name string) (keys.Info, error) {
|
||||||
keybase, err := GetKeyBase()
|
keybase, err := NewKeyBaseFromHomeFlag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -73,58 +72,22 @@ func ReadPassphraseFromStdin(name string) (string, error) {
|
||||||
return passphrase, nil
|
return passphrase, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO make keybase take a database not load from the directory
|
// NewKeyBaseFromHomeFlag initializes a Keybase based on the configuration.
|
||||||
|
func NewKeyBaseFromHomeFlag() (keys.Keybase, error) {
|
||||||
// GetKeyBase initializes a read-only KeyBase based on the configuration.
|
|
||||||
func GetKeyBase() (keys.Keybase, error) {
|
|
||||||
rootDir := viper.GetString(cli.HomeFlag)
|
rootDir := viper.GetString(cli.HomeFlag)
|
||||||
return GetKeyBaseFromDir(rootDir)
|
return NewKeyBaseFromDir(rootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeyBaseWithWritePerm initialize a keybase based on the configuration with write permissions.
|
// NewKeyBaseFromDir initializes a keybase at a particular dir.
|
||||||
func GetKeyBaseWithWritePerm() (keys.Keybase, error) {
|
func NewKeyBaseFromDir(rootDir string) (keys.Keybase, error) {
|
||||||
rootDir := viper.GetString(cli.HomeFlag)
|
return getLazyKeyBaseFromDir(rootDir)
|
||||||
return GetKeyBaseFromDirWithWritePerm(rootDir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeyBaseFromDirWithWritePerm initializes a keybase at a particular dir with write permissions.
|
// NewInMemoryKeyBase returns a storage-less keybase.
|
||||||
func GetKeyBaseFromDirWithWritePerm(rootDir string) (keys.Keybase, error) {
|
func NewInMemoryKeyBase() keys.Keybase { return keys.NewInMemory() }
|
||||||
return getKeyBaseFromDirWithOpts(rootDir, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKeyBaseFromDir initializes a read-only keybase at a particular dir.
|
func getLazyKeyBaseFromDir(rootDir string) (keys.Keybase, error) {
|
||||||
func GetKeyBaseFromDir(rootDir string) (keys.Keybase, error) {
|
return keys.NewLazyKeybase(KeyDBName, filepath.Join(rootDir, "keys")), nil
|
||||||
// Disabled because of the inability to create a new keys database directory
|
|
||||||
// in the instance of when ReadOnly is set to true.
|
|
||||||
//
|
|
||||||
// ref: syndtr/goleveldb#240
|
|
||||||
// return getKeyBaseFromDirWithOpts(rootDir, &opt.Options{ReadOnly: true})
|
|
||||||
return getKeyBaseFromDirWithOpts(rootDir, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getKeyBaseFromDirWithOpts(rootDir string, o *opt.Options) (keys.Keybase, error) {
|
|
||||||
if keybase == nil {
|
|
||||||
db, err := dbm.NewGoLevelDBWithOpts(KeyDBName, filepath.Join(rootDir, "keys"), o)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
keybase = client.GetKeyBase(db)
|
|
||||||
}
|
|
||||||
return keybase, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// used to set the keybase manually in test
|
|
||||||
func SetKeyBase(kb keys.Keybase) {
|
|
||||||
keybase = kb
|
|
||||||
}
|
|
||||||
|
|
||||||
// used for outputting keys.Info over REST
|
|
||||||
type KeyOutput struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Address string `json:"address"`
|
|
||||||
PubKey string `json:"pub_key"`
|
|
||||||
Seed string `json:"seed,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a list of KeyOutput in bech32 format
|
// create a list of KeyOutput in bech32 format
|
||||||
|
@ -198,7 +161,7 @@ func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch viper.Get(cli.OutputFlag) {
|
switch viper.Get(cli.OutputFlag) {
|
||||||
case "text":
|
case OutputFormatText:
|
||||||
fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n")
|
fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n")
|
||||||
printKeyOutput(ko)
|
printKeyOutput(ko)
|
||||||
case "json":
|
case "json":
|
||||||
|
@ -217,12 +180,12 @@ func printInfos(infos []keys.Info) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
switch viper.Get(cli.OutputFlag) {
|
switch viper.Get(cli.OutputFlag) {
|
||||||
case "text":
|
case OutputFormatText:
|
||||||
fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n")
|
fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n")
|
||||||
for _, ko := range kos {
|
for _, ko := range kos {
|
||||||
printKeyOutput(ko)
|
printKeyOutput(ko)
|
||||||
}
|
}
|
||||||
case "json":
|
case OutputFormatJSON:
|
||||||
out, err := MarshalJSON(kos)
|
out, err := MarshalJSON(kos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -266,12 +229,12 @@ func PostProcessResponse(w http.ResponseWriter, cdc *codec.Codec, response inter
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte(err.Error()))
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case []byte:
|
case []byte:
|
||||||
output = response.([]byte)
|
output = response.([]byte)
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Write(output)
|
_, _ = w.Write(output)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
package keys
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetKeyBaseLocks(t *testing.T) {
|
|
||||||
dir, err := ioutil.TempDir("", "cosmos-sdk-keys")
|
|
||||||
require.Nil(t, err)
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
// Acquire db
|
|
||||||
kb, err := GetKeyBaseFromDirWithWritePerm(dir)
|
|
||||||
require.Nil(t, err)
|
|
||||||
_, _, err = kb.CreateMnemonic("foo", keys.English, "12345678", keys.Secp256k1)
|
|
||||||
require.Nil(t, err)
|
|
||||||
// Reset global variable
|
|
||||||
keybase = nil
|
|
||||||
// Try to acquire another keybase from the same storage
|
|
||||||
_, err = GetKeyBaseFromDirWithWritePerm(dir)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
_, err = GetKeyBaseFromDirWithWritePerm(dir)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
|
|
||||||
// Close the db and try to acquire the lock
|
|
||||||
kb.CloseDB()
|
|
||||||
kb, err = GetKeyBaseFromDirWithWritePerm(dir)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
// Try to acquire another read-only keybase from the same storage
|
|
||||||
_, err = GetKeyBaseFromDir(dir)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
kb.CloseDB()
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -16,7 +16,6 @@ import (
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
keybase "github.com/cosmos/cosmos-sdk/crypto/keys"
|
keybase "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
"github.com/cosmos/cosmos-sdk/server"
|
"github.com/cosmos/cosmos-sdk/server"
|
||||||
|
@ -52,25 +51,9 @@ func NewRestServer(cdc *codec.Codec) *RestServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *RestServer) setKeybase(kb keybase.Keybase) {
|
|
||||||
// If a keybase is passed in, set it and return
|
|
||||||
if kb != nil {
|
|
||||||
rs.KeyBase = kb
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise get the keybase and set it
|
|
||||||
kb, err := keys.GetKeyBase() //XXX
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed to open Keybase: %s, exiting...", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
rs.KeyBase = kb
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts the rest server
|
// Start starts the rest server
|
||||||
func (rs *RestServer) Start(listenAddr string, sslHosts string,
|
func (rs *RestServer) Start(listenAddr string, sslHosts string,
|
||||||
certFile string, keyFile string, maxOpen int, insecure bool) (err error) {
|
certFile string, keyFile string, maxOpen int, secure bool) (err error) {
|
||||||
|
|
||||||
server.TrapSignal(func() {
|
server.TrapSignal(func() {
|
||||||
err := rs.listener.Close()
|
err := rs.listener.Close()
|
||||||
|
@ -84,10 +67,11 @@ func (rs *RestServer) Start(listenAddr string, sslHosts string,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rs.log.Info("Starting Gaia Lite REST service...")
|
rs.log.Info(fmt.Sprintf("Starting Gaia Lite REST service (chain-id: %q)...",
|
||||||
|
viper.GetString(client.FlagChainID)))
|
||||||
|
|
||||||
// launch rest-server in insecure mode
|
// launch rest-server in insecure mode
|
||||||
if insecure {
|
if !secure {
|
||||||
return rpcserver.StartHTTPServer(rs.listener, rs.Mux, rs.log)
|
return rpcserver.StartHTTPServer(rs.listener, rs.Mux, rs.log)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +119,6 @@ func ServeCommand(cdc *codec.Codec, registerRoutesFn func(*RestServer)) *cobra.C
|
||||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
rs := NewRestServer(cdc)
|
rs := NewRestServer(cdc)
|
||||||
|
|
||||||
rs.setKeybase(nil)
|
|
||||||
registerRoutesFn(rs)
|
registerRoutesFn(rs)
|
||||||
|
|
||||||
// Start the rest server and return error if one exists
|
// Start the rest server and return error if one exists
|
||||||
|
@ -145,15 +128,13 @@ func ServeCommand(cdc *codec.Codec, registerRoutesFn func(*RestServer)) *cobra.C
|
||||||
viper.GetString(client.FlagSSLCertFile),
|
viper.GetString(client.FlagSSLCertFile),
|
||||||
viper.GetString(client.FlagSSLKeyFile),
|
viper.GetString(client.FlagSSLKeyFile),
|
||||||
viper.GetInt(client.FlagMaxOpenConnections),
|
viper.GetInt(client.FlagMaxOpenConnections),
|
||||||
viper.GetBool(client.FlagInsecure))
|
viper.GetBool(client.FlagTLS))
|
||||||
|
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
client.RegisterRestServerFlags(cmd)
|
return client.RegisterRestServerFlags(cmd)
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *RestServer) registerSwaggerUI() {
|
func (rs *RestServer) registerSwaggerUI() {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -15,20 +15,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
"github.com/spf13/viper"
|
||||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
|
||||||
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
|
||||||
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||||
"github.com/cosmos/cosmos-sdk/client/rpc"
|
"github.com/cosmos/cosmos-sdk/client/rpc"
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
|
||||||
gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
crkeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
crkeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
|
@ -36,16 +30,24 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/tests"
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
||||||
|
txbuilder "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
||||||
|
bankrest "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
|
||||||
|
distr "github.com/cosmos/cosmos-sdk/x/distribution"
|
||||||
|
distrrest "github.com/cosmos/cosmos-sdk/x/distribution/client/rest"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||||
|
govrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
|
||||||
gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
|
gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||||
|
slashingrest "github.com/cosmos/cosmos-sdk/x/slashing/client/rest"
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||||
|
stakingrest "github.com/cosmos/cosmos-sdk/x/staking/client/rest"
|
||||||
"github.com/spf13/viper"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
tmcfg "github.com/tendermint/tendermint/config"
|
tmcfg "github.com/tendermint/tendermint/config"
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||||
|
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||||
"github.com/tendermint/tendermint/libs/cli"
|
"github.com/tendermint/tendermint/libs/cli"
|
||||||
dbm "github.com/tendermint/tendermint/libs/db"
|
dbm "github.com/tendermint/tendermint/libs/db"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
@ -53,16 +55,9 @@ import (
|
||||||
"github.com/tendermint/tendermint/p2p"
|
"github.com/tendermint/tendermint/p2p"
|
||||||
pvm "github.com/tendermint/tendermint/privval"
|
pvm "github.com/tendermint/tendermint/privval"
|
||||||
"github.com/tendermint/tendermint/proxy"
|
"github.com/tendermint/tendermint/proxy"
|
||||||
|
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
tmrpc "github.com/tendermint/tendermint/rpc/lib/server"
|
tmrpc "github.com/tendermint/tendermint/rpc/lib/server"
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
|
||||||
txbuilder "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
|
||||||
|
|
||||||
authRest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
|
||||||
bankRest "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
|
|
||||||
govRest "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
|
|
||||||
slashingRest "github.com/cosmos/cosmos-sdk/x/slashing/client/rest"
|
|
||||||
stakingRest "github.com/cosmos/cosmos-sdk/x/staking/client/rest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// makePathname creates a unique pathname for each test. It will panic if it
|
// makePathname creates a unique pathname for each test. It will panic if it
|
||||||
|
@ -104,30 +99,6 @@ func GetConfig() *tmcfg.Config {
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeyBase returns the LCD test keybase. It also requires that a directory
|
|
||||||
// could be made and a keybase could be fetched.
|
|
||||||
//
|
|
||||||
// NOTE: memDB cannot be used because the request is expecting to interact with
|
|
||||||
// the default location.
|
|
||||||
func GetKeyBase(t *testing.T) crkeys.Keybase {
|
|
||||||
dir, err := ioutil.TempDir("", "lcd_test")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
viper.Set(cli.HomeFlag, dir)
|
|
||||||
|
|
||||||
keybase, err := keys.GetKeyBaseWithWritePerm()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
return keybase
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTestKeyBase fetches the current testing keybase
|
|
||||||
func GetTestKeyBase(t *testing.T) crkeys.Keybase {
|
|
||||||
keybase, err := keys.GetKeyBaseWithWritePerm()
|
|
||||||
require.NoError(t, err)
|
|
||||||
return keybase
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateAddr adds an address to the key store and returns an address and seed.
|
// CreateAddr adds an address to the key store and returns an address and seed.
|
||||||
// It also requires that the key could be created.
|
// It also requires that the key could be created.
|
||||||
func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (sdk.AccAddress, string) {
|
func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (sdk.AccAddress, string) {
|
||||||
|
@ -143,14 +114,6 @@ func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (sdk.Acc
|
||||||
return sdk.AccAddress(info.GetPubKey().Address()), seed
|
return sdk.AccAddress(info.GetPubKey().Address()), seed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type that combines an Address with the pnemonic of the private key to that address
|
|
||||||
type AddrSeed struct {
|
|
||||||
Address sdk.AccAddress
|
|
||||||
Seed string
|
|
||||||
Name string
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateAddr adds multiple address to the key store and returns the addresses and associated seeds in lexographical order by address.
|
// CreateAddr adds multiple address to the key store and returns the addresses and associated seeds in lexographical order by address.
|
||||||
// It also requires that the keys could be created.
|
// It also requires that the keys could be created.
|
||||||
func CreateAddrs(t *testing.T, kb crkeys.Keybase, numAddrs int) (addrs []sdk.AccAddress, seeds, names, passwords []string) {
|
func CreateAddrs(t *testing.T, kb crkeys.Keybase, numAddrs int) (addrs []sdk.AccAddress, seeds, names, passwords []string) {
|
||||||
|
@ -167,7 +130,7 @@ func CreateAddrs(t *testing.T, kb crkeys.Keybase, numAddrs int) (addrs []sdk.Acc
|
||||||
password := "1234567890"
|
password := "1234567890"
|
||||||
info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1)
|
info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
addrSeeds = append(addrSeeds, AddrSeed{Address: sdk.AccAddress(info.GetPubKey().Address()), Seed: seed, Name: name, Password: password})
|
addrSeeds = append(addrSeeds, rest.AddrSeed{Address: sdk.AccAddress(info.GetPubKey().Address()), Seed: seed, Name: name, Password: password})
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(addrSeeds)
|
sort.Sort(addrSeeds)
|
||||||
|
@ -182,14 +145,14 @@ func CreateAddrs(t *testing.T, kb crkeys.Keybase, numAddrs int) (addrs []sdk.Acc
|
||||||
return addrs, seeds, names, passwords
|
return addrs, seeds, names, passwords
|
||||||
}
|
}
|
||||||
|
|
||||||
// implement `Interface` in sort package.
|
// AddrSeedSlice implements `Interface` in sort package.
|
||||||
type AddrSeedSlice []AddrSeed
|
type AddrSeedSlice []rest.AddrSeed
|
||||||
|
|
||||||
func (b AddrSeedSlice) Len() int {
|
func (b AddrSeedSlice) Len() int {
|
||||||
return len(b)
|
return len(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sorts lexographically by Address
|
// Less sorts lexicographically by Address
|
||||||
func (b AddrSeedSlice) Less(i, j int) bool {
|
func (b AddrSeedSlice) Less(i, j int) bool {
|
||||||
// bytes package already implements Comparable for []byte.
|
// bytes package already implements Comparable for []byte.
|
||||||
switch bytes.Compare(b[i].Address.Bytes(), b[j].Address.Bytes()) {
|
switch bytes.Compare(b[i].Address.Bytes(), b[j].Address.Bytes()) {
|
||||||
|
@ -206,14 +169,27 @@ func (b AddrSeedSlice) Swap(i, j int) {
|
||||||
b[j], b[i] = b[i], b[j]
|
b[j], b[i] = b[i], b[j]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitClientHome initialises client home dir.
|
||||||
|
func InitClientHome(t *testing.T, dir string) string {
|
||||||
|
var err error
|
||||||
|
if dir == "" {
|
||||||
|
dir, err = ioutil.TempDir("", "lcd_test")
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
// TODO: this should be set in NewRestServer
|
||||||
|
// and pass down the CLIContext to achieve
|
||||||
|
// parallelism.
|
||||||
|
viper.Set(cli.HomeFlag, dir)
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Make InitializeTestLCD safe to call in multiple tests at the same time
|
// TODO: Make InitializeTestLCD safe to call in multiple tests at the same time
|
||||||
// InitializeTestLCD starts Tendermint and the LCD in process, listening on
|
// InitializeTestLCD starts Tendermint and the LCD in process, listening on
|
||||||
// their respective sockets where nValidators is the total number of validators
|
// their respective sockets where nValidators is the total number of validators
|
||||||
// and initAddrs are the accounts to initialize with some steak tokens. It
|
// and initAddrs are the accounts to initialize with some steak tokens. It
|
||||||
// returns a cleanup function, a set of validator public keys, and a port.
|
// returns a cleanup function, a set of validator public keys, and a port.
|
||||||
func InitializeTestLCD(
|
func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress, minting bool) (
|
||||||
t *testing.T, nValidators int, initAddrs []sdk.AccAddress,
|
cleanup func(), valConsPubKeys []crypto.PubKey, valOperAddrs []sdk.ValAddress, port string) {
|
||||||
) (cleanup func(), valConsPubKeys []crypto.PubKey, valOperAddrs []sdk.ValAddress, port string) {
|
|
||||||
|
|
||||||
if nValidators < 1 {
|
if nValidators < 1 {
|
||||||
panic("InitializeTestLCD must use at least one validator")
|
panic("InitializeTestLCD must use at least one validator")
|
||||||
|
@ -248,17 +224,21 @@ func InitializeTestLCD(
|
||||||
operPrivKey := secp256k1.GenPrivKey()
|
operPrivKey := secp256k1.GenPrivKey()
|
||||||
operAddr := operPrivKey.PubKey().Address()
|
operAddr := operPrivKey.PubKey().Address()
|
||||||
pubKey := privVal.GetPubKey()
|
pubKey := privVal.GetPubKey()
|
||||||
delegation := 100
|
|
||||||
|
power := int64(100)
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
pubKey = ed25519.GenPrivKey().PubKey()
|
pubKey = ed25519.GenPrivKey().PubKey()
|
||||||
delegation = 1
|
power = 1
|
||||||
}
|
}
|
||||||
|
startTokens := staking.TokensFromTendermintPower(power)
|
||||||
|
|
||||||
msg := staking.NewMsgCreateValidator(
|
msg := staking.NewMsgCreateValidator(
|
||||||
sdk.ValAddress(operAddr),
|
sdk.ValAddress(operAddr),
|
||||||
pubKey,
|
pubKey,
|
||||||
sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(int64(delegation))),
|
sdk.NewCoin(staking.DefaultBondDenom, startTokens),
|
||||||
staking.Description{Moniker: fmt.Sprintf("validator-%d", i+1)},
|
staking.NewDescription(fmt.Sprintf("validator-%d", i+1), "", "", ""),
|
||||||
staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
|
staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
|
||||||
|
sdk.OneInt(),
|
||||||
)
|
)
|
||||||
stdSignMsg := txbuilder.StdSignMsg{
|
stdSignMsg := txbuilder.StdSignMsg{
|
||||||
ChainID: genDoc.ChainID,
|
ChainID: genDoc.ChainID,
|
||||||
|
@ -269,12 +249,14 @@ func InitializeTestLCD(
|
||||||
tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{{Signature: sig, PubKey: operPrivKey.PubKey()}}, "")
|
tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{{Signature: sig, PubKey: operPrivKey.PubKey()}}, "")
|
||||||
txBytes, err := cdc.MarshalJSON(tx)
|
txBytes, err := cdc.MarshalJSON(tx)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
genTxs = append(genTxs, txBytes)
|
genTxs = append(genTxs, txBytes)
|
||||||
valConsPubKeys = append(valConsPubKeys, pubKey)
|
valConsPubKeys = append(valConsPubKeys, pubKey)
|
||||||
valOperAddrs = append(valOperAddrs, sdk.ValAddress(operAddr))
|
valOperAddrs = append(valOperAddrs, sdk.ValAddress(operAddr))
|
||||||
|
|
||||||
accAuth := auth.NewBaseAccountWithAddress(sdk.AccAddress(operAddr))
|
accAuth := auth.NewBaseAccountWithAddress(sdk.AccAddress(operAddr))
|
||||||
accAuth.Coins = sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 150)}
|
accTokens := staking.TokensFromTendermintPower(150)
|
||||||
|
accAuth.Coins = sdk.Coins{sdk.NewCoin(staking.DefaultBondDenom, accTokens)}
|
||||||
accs = append(accs, gapp.NewGenesisAccount(&accAuth))
|
accs = append(accs, gapp.NewGenesisAccount(&accAuth))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,10 +270,34 @@ func InitializeTestLCD(
|
||||||
// add some tokens to init accounts
|
// add some tokens to init accounts
|
||||||
for _, addr := range initAddrs {
|
for _, addr := range initAddrs {
|
||||||
accAuth := auth.NewBaseAccountWithAddress(addr)
|
accAuth := auth.NewBaseAccountWithAddress(addr)
|
||||||
accAuth.Coins = sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 100)}
|
accTokens := staking.TokensFromTendermintPower(100)
|
||||||
|
accAuth.Coins = sdk.Coins{sdk.NewCoin(staking.DefaultBondDenom, accTokens)}
|
||||||
acc := gapp.NewGenesisAccount(&accAuth)
|
acc := gapp.NewGenesisAccount(&accAuth)
|
||||||
genesisState.Accounts = append(genesisState.Accounts, acc)
|
genesisState.Accounts = append(genesisState.Accounts, acc)
|
||||||
genesisState.StakingData.Pool.NotBondedTokens = genesisState.StakingData.Pool.NotBondedTokens.Add(sdk.NewInt(100))
|
genesisState.StakingData.Pool.NotBondedTokens = genesisState.StakingData.Pool.NotBondedTokens.Add(accTokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
inflationMin := sdk.ZeroDec()
|
||||||
|
if minting {
|
||||||
|
inflationMin = sdk.MustNewDecFromStr("10000.0")
|
||||||
|
genesisState.MintData.Params.InflationMax = sdk.MustNewDecFromStr("15000.0")
|
||||||
|
} else {
|
||||||
|
genesisState.MintData.Params.InflationMax = inflationMin
|
||||||
|
}
|
||||||
|
genesisState.MintData.Minter.Inflation = inflationMin
|
||||||
|
genesisState.MintData.Params.InflationMin = inflationMin
|
||||||
|
|
||||||
|
// double check inflation is set according to the minting boolean flag
|
||||||
|
if minting {
|
||||||
|
require.Equal(t, sdk.MustNewDecFromStr("15000.0"),
|
||||||
|
genesisState.MintData.Params.InflationMax)
|
||||||
|
require.Equal(t, sdk.MustNewDecFromStr("10000.0"), genesisState.MintData.Minter.Inflation)
|
||||||
|
require.Equal(t, sdk.MustNewDecFromStr("10000.0"),
|
||||||
|
genesisState.MintData.Params.InflationMin)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, sdk.ZeroDec(), genesisState.MintData.Params.InflationMax)
|
||||||
|
require.Equal(t, sdk.ZeroDec(), genesisState.MintData.Minter.Inflation)
|
||||||
|
require.Equal(t, sdk.ZeroDec(), genesisState.MintData.Params.InflationMin)
|
||||||
}
|
}
|
||||||
|
|
||||||
appState, err := codec.MarshalJSONIndent(cdc, genesisState)
|
appState, err := codec.MarshalJSONIndent(cdc, genesisState)
|
||||||
|
@ -306,9 +312,6 @@ func InitializeTestLCD(
|
||||||
viper.Set(client.FlagChainID, genDoc.ChainID)
|
viper.Set(client.FlagChainID, genDoc.ChainID)
|
||||||
// TODO Set to false once the upstream Tendermint proof verification issue is fixed.
|
// TODO Set to false once the upstream Tendermint proof verification issue is fixed.
|
||||||
viper.Set(client.FlagTrustNode, true)
|
viper.Set(client.FlagTrustNode, true)
|
||||||
dir, err := ioutil.TempDir("", "lcd_test")
|
|
||||||
require.NoError(t, err)
|
|
||||||
viper.Set(cli.HomeFlag, dir)
|
|
||||||
|
|
||||||
node, err := startTM(config, logger, genDoc, privVal, app)
|
node, err := startTM(config, logger, genDoc, privVal, app)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -339,6 +342,7 @@ func startTM(
|
||||||
tmcfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc,
|
tmcfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc,
|
||||||
privVal tmtypes.PrivValidator, app abci.Application,
|
privVal tmtypes.PrivValidator, app abci.Application,
|
||||||
) (*nm.Node, error) {
|
) (*nm.Node, error) {
|
||||||
|
|
||||||
genDocProvider := func() (*tmtypes.GenesisDoc, error) { return genDoc, nil }
|
genDocProvider := func() (*tmtypes.GenesisDoc, error) { return genDoc, nil }
|
||||||
dbProvider := func(*nm.DBContext) (dbm.DB, error) { return dbm.NewMemDB(), nil }
|
dbProvider := func(*nm.DBContext) (dbm.DB, error) { return dbm.NewMemDB(), nil }
|
||||||
nodeKey, err := p2p.LoadOrGenNodeKey(tmcfg.NodeKeyFile())
|
nodeKey, err := p2p.LoadOrGenNodeKey(tmcfg.NodeKeyFile())
|
||||||
|
@ -373,7 +377,6 @@ func startTM(
|
||||||
// startLCD starts the LCD.
|
// startLCD starts the LCD.
|
||||||
func startLCD(logger log.Logger, listenAddr string, cdc *codec.Codec, t *testing.T) (net.Listener, error) {
|
func startLCD(logger log.Logger, listenAddr string, cdc *codec.Codec, t *testing.T) (net.Listener, error) {
|
||||||
rs := NewRestServer(cdc)
|
rs := NewRestServer(cdc)
|
||||||
rs.setKeybase(GetTestKeyBase(t))
|
|
||||||
registerRoutes(rs)
|
registerRoutes(rs)
|
||||||
listener, err := tmrpc.Listen(listenAddr, tmrpc.Config{})
|
listener, err := tmrpc.Listen(listenAddr, tmrpc.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -388,11 +391,12 @@ func registerRoutes(rs *RestServer) {
|
||||||
keys.RegisterRoutes(rs.Mux, rs.CliCtx.Indent)
|
keys.RegisterRoutes(rs.Mux, rs.CliCtx.Indent)
|
||||||
rpc.RegisterRoutes(rs.CliCtx, rs.Mux)
|
rpc.RegisterRoutes(rs.CliCtx, rs.Mux)
|
||||||
tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
|
tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
|
||||||
authRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, auth.StoreKey)
|
authrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, auth.StoreKey)
|
||||||
bankRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
bankrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
||||||
stakingRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
distrrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, distr.StoreKey)
|
||||||
slashingRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
stakingrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
||||||
govRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
|
slashingrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
||||||
|
govrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request makes a test LCD test request. It returns a response object and a
|
// Request makes a test LCD test request. It returns a response object and a
|
||||||
|
@ -498,8 +502,8 @@ func getValidatorSets(t *testing.T, port string, height int, expectFail bool) rp
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /txs/{hash} get tx by hash
|
// GET /txs/{hash} get tx by hash
|
||||||
func getTransaction(t *testing.T, port string, hash string) tx.Info {
|
func getTransaction(t *testing.T, port string, hash string) sdk.TxResponse {
|
||||||
var tx tx.Info
|
var tx sdk.TxResponse
|
||||||
res, body := Request(t, port, "GET", fmt.Sprintf("/txs/%s", hash), nil)
|
res, body := Request(t, port, "GET", fmt.Sprintf("/txs/%s", hash), nil)
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
|
|
||||||
|
@ -511,8 +515,8 @@ func getTransaction(t *testing.T, port string, hash string) tx.Info {
|
||||||
// POST /txs broadcast txs
|
// POST /txs broadcast txs
|
||||||
|
|
||||||
// GET /txs search transactions
|
// GET /txs search transactions
|
||||||
func getTransactions(t *testing.T, port string, tags ...string) []tx.Info {
|
func getTransactions(t *testing.T, port string, tags ...string) []sdk.TxResponse {
|
||||||
var txs []tx.Info
|
var txs []sdk.TxResponse
|
||||||
if len(tags) == 0 {
|
if len(tags) == 0 {
|
||||||
return txs
|
return txs
|
||||||
}
|
}
|
||||||
|
@ -539,10 +543,11 @@ func getKeys(t *testing.T, port string) []keys.KeyOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /keys Create a new account locally
|
// POST /keys Create a new account locally
|
||||||
func doKeysPost(t *testing.T, port, name, password, seed string) keys.KeyOutput {
|
func doKeysPost(t *testing.T, port, name, password, mnemonic string, account int, index int) keys.KeyOutput {
|
||||||
pk := postKeys{name, password, seed}
|
pk := keys.AddNewKey{name, password, mnemonic, account, index}
|
||||||
req, err := cdc.MarshalJSON(pk)
|
req, err := cdc.MarshalJSON(pk)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
res, body := Request(t, port, "POST", "/keys", req)
|
res, body := Request(t, port, "POST", "/keys", req)
|
||||||
|
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
|
@ -552,12 +557,6 @@ func doKeysPost(t *testing.T, port, name, password, seed string) keys.KeyOutput
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
type postKeys struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Seed string `json:"seed"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET /keys/seed Create a new seed to create a new account defaultValidFor
|
// GET /keys/seed Create a new seed to create a new account defaultValidFor
|
||||||
func getKeysSeed(t *testing.T, port string) string {
|
func getKeysSeed(t *testing.T, port string) string {
|
||||||
res, body := Request(t, port, "GET", "/keys/seed", nil)
|
res, body := Request(t, port, "GET", "/keys/seed", nil)
|
||||||
|
@ -569,14 +568,17 @@ func getKeysSeed(t *testing.T, port string) string {
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /keys/{name}/recover Recover a account from a seed
|
// POST /keys/{name}/recove Recover a account from a seed
|
||||||
func doRecoverKey(t *testing.T, port, recoverName, recoverPassword, seed string) {
|
func doRecoverKey(t *testing.T, port, recoverName, recoverPassword, mnemonic string, account uint32, index uint32) {
|
||||||
jsonStr := []byte(fmt.Sprintf(`{"password":"%s", "seed":"%s"}`, recoverPassword, seed))
|
pk := keys.RecoverKey{recoverPassword, mnemonic, int(account), int(index)}
|
||||||
res, body := Request(t, port, "POST", fmt.Sprintf("/keys/%s/recover", recoverName), jsonStr)
|
req, err := cdc.MarshalJSON(pk)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
res, body := Request(t, port, "POST", fmt.Sprintf("/keys/%s/recover", recoverName), req)
|
||||||
|
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
var resp keys.KeyOutput
|
var resp keys.KeyOutput
|
||||||
err := codec.Cdc.UnmarshalJSON([]byte(body), &resp)
|
err = codec.Cdc.UnmarshalJSON([]byte(body), &resp)
|
||||||
require.Nil(t, err, body)
|
require.Nil(t, err, body)
|
||||||
|
|
||||||
addr1Bech32 := resp.Address
|
addr1Bech32 := resp.Address
|
||||||
|
@ -596,7 +598,7 @@ func getKey(t *testing.T, port, name string) keys.KeyOutput {
|
||||||
|
|
||||||
// PUT /keys/{name} Update the password for this account in the KMS
|
// PUT /keys/{name} Update the password for this account in the KMS
|
||||||
func updateKey(t *testing.T, port, name, oldPassword, newPassword string, fail bool) {
|
func updateKey(t *testing.T, port, name, oldPassword, newPassword string, fail bool) {
|
||||||
kr := updateKeyReq{oldPassword, newPassword}
|
kr := keys.UpdateKeyReq{oldPassword, newPassword}
|
||||||
req, err := cdc.MarshalJSON(kr)
|
req, err := cdc.MarshalJSON(kr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
keyEndpoint := fmt.Sprintf("/keys/%s", name)
|
keyEndpoint := fmt.Sprintf("/keys/%s", name)
|
||||||
|
@ -608,14 +610,9 @@ func updateKey(t *testing.T, port, name, oldPassword, newPassword string, fail b
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
type updateKeyReq struct {
|
|
||||||
OldPassword string `json:"old_password"`
|
|
||||||
NewPassword string `json:"new_password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE /keys/{name} Remove an account
|
// DELETE /keys/{name} Remove an account
|
||||||
func deleteKey(t *testing.T, port, name, password string) {
|
func deleteKey(t *testing.T, port, name, password string) {
|
||||||
dk := deleteKeyReq{password}
|
dk := keys.DeleteKeyReq{password}
|
||||||
req, err := cdc.MarshalJSON(dk)
|
req, err := cdc.MarshalJSON(dk)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
keyEndpoint := fmt.Sprintf("/keys/%s", name)
|
keyEndpoint := fmt.Sprintf("/keys/%s", name)
|
||||||
|
@ -623,10 +620,6 @@ func deleteKey(t *testing.T, port, name, password string) {
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
type deleteKeyReq struct {
|
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET /auth/accounts/{address} Get the account information on blockchain
|
// GET /auth/accounts/{address} Get the account information on blockchain
|
||||||
func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account {
|
func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account {
|
||||||
res, body := Request(t, port, "GET", fmt.Sprintf("/auth/accounts/%s", addr.String()), nil)
|
res, body := Request(t, port, "GET", fmt.Sprintf("/auth/accounts/%s", addr.String()), nil)
|
||||||
|
@ -646,7 +639,7 @@ func doSign(t *testing.T, port, name, password, chainID string, accnum, sequence
|
||||||
var signedMsg auth.StdTx
|
var signedMsg auth.StdTx
|
||||||
payload := authrest.SignBody{
|
payload := authrest.SignBody{
|
||||||
Tx: msg,
|
Tx: msg,
|
||||||
BaseReq: utils.NewBaseReq(
|
BaseReq: rest.NewBaseReq(
|
||||||
name, password, "", chainID, "", "", accnum, sequence, nil, nil, false, false,
|
name, password, "", chainID, "", "", accnum, sequence, nil, nil, false, false,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -659,26 +652,21 @@ func doSign(t *testing.T, port, name, password, chainID string, accnum, sequence
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /tx/broadcast Send a signed Tx
|
// POST /tx/broadcast Send a signed Tx
|
||||||
func doBroadcast(t *testing.T, port string, msg auth.StdTx) ctypes.ResultBroadcastTxCommit {
|
func doBroadcast(t *testing.T, port string, msg auth.StdTx) sdk.TxResponse {
|
||||||
tx := broadcastReq{Tx: msg, Return: "block"}
|
tx := rest.BroadcastReq{Tx: msg, Return: "block"}
|
||||||
req, err := cdc.MarshalJSON(tx)
|
req, err := cdc.MarshalJSON(tx)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
res, body := Request(t, port, "POST", "/tx/broadcast", req)
|
res, body := Request(t, port, "POST", "/tx/broadcast", req)
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
var resultTx ctypes.ResultBroadcastTxCommit
|
var resultTx sdk.TxResponse
|
||||||
require.Nil(t, cdc.UnmarshalJSON([]byte(body), &resultTx))
|
require.Nil(t, cdc.UnmarshalJSON([]byte(body), &resultTx))
|
||||||
return resultTx
|
return resultTx
|
||||||
}
|
}
|
||||||
|
|
||||||
type broadcastReq struct {
|
|
||||||
Tx auth.StdTx `json:"tx"`
|
|
||||||
Return string `json:"return"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET /bank/balances/{address} Get the account balances
|
// GET /bank/balances/{address} Get the account balances
|
||||||
|
|
||||||
// POST /bank/accounts/{address}/transfers Send coins (build -> sign -> send)
|
// POST /bank/accounts/{address}/transfers Send coins (build -> sign -> send)
|
||||||
func doTransfer(t *testing.T, port, seed, name, memo, password string, addr sdk.AccAddress, fees sdk.Coins) (receiveAddr sdk.AccAddress, resultTx ctypes.ResultBroadcastTxCommit) {
|
func doTransfer(t *testing.T, port, seed, name, memo, password string, addr sdk.AccAddress, fees sdk.Coins) (receiveAddr sdk.AccAddress, resultTx sdk.TxResponse) {
|
||||||
res, body, receiveAddr := doTransferWithGas(t, port, seed, name, memo, password, addr, "", 1.0, false, false, fees)
|
res, body, receiveAddr := doTransferWithGas(t, port, seed, name, memo, password, addr, "", 1.0, false, false, fees)
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
|
|
||||||
|
@ -688,29 +676,71 @@ func doTransfer(t *testing.T, port, seed, name, memo, password string, addr sdk.
|
||||||
return receiveAddr, resultTx
|
return receiveAddr, resultTx
|
||||||
}
|
}
|
||||||
|
|
||||||
func doTransferWithGas(t *testing.T, port, seed, name, memo, password string, addr sdk.AccAddress, gas string,
|
func doTransferWithGas(
|
||||||
gasAdjustment float64, simulate, generateOnly bool, fees sdk.Coins) (
|
t *testing.T, port, seed, from, memo, password string, addr sdk.AccAddress,
|
||||||
res *http.Response, body string, receiveAddr sdk.AccAddress) {
|
gas string, gasAdjustment float64, simulate, generateOnly bool, fees sdk.Coins,
|
||||||
|
) (res *http.Response, body string, receiveAddr sdk.AccAddress) {
|
||||||
|
|
||||||
// create receive address
|
// create receive address
|
||||||
kb := client.MockKeyBase()
|
kb := client.MockKeyBase()
|
||||||
receiveInfo, _, err := kb.CreateMnemonic("receive_address", cryptoKeys.English, gapp.DefaultKeyPass, cryptoKeys.SigningAlgo("secp256k1"))
|
|
||||||
require.Nil(t, err)
|
|
||||||
receiveAddr = sdk.AccAddress(receiveInfo.GetPubKey().Address())
|
|
||||||
|
|
||||||
|
receiveInfo, _, err := kb.CreateMnemonic(
|
||||||
|
"receive_address", crkeys.English, gapp.DefaultKeyPass, crkeys.SigningAlgo("secp256k1"),
|
||||||
|
)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
receiveAddr = sdk.AccAddress(receiveInfo.GetPubKey().Address())
|
||||||
acc := getAccount(t, port, addr)
|
acc := getAccount(t, port, addr)
|
||||||
accnum := acc.GetAccountNumber()
|
accnum := acc.GetAccountNumber()
|
||||||
sequence := acc.GetSequence()
|
sequence := acc.GetSequence()
|
||||||
chainID := viper.GetString(client.FlagChainID)
|
chainID := viper.GetString(client.FlagChainID)
|
||||||
|
|
||||||
baseReq := utils.NewBaseReq(
|
if generateOnly {
|
||||||
name, password, memo, chainID, gas,
|
// generate only txs do not use a Keybase so the address must be used
|
||||||
|
from = addr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReq := rest.NewBaseReq(
|
||||||
|
from, password, memo, chainID, gas,
|
||||||
fmt.Sprintf("%f", gasAdjustment), accnum, sequence, fees, nil,
|
fmt.Sprintf("%f", gasAdjustment), accnum, sequence, fees, nil,
|
||||||
generateOnly, simulate,
|
generateOnly, simulate,
|
||||||
)
|
)
|
||||||
|
|
||||||
sr := sendReq{
|
sr := rest.SendReq{
|
||||||
Amount: sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 1)},
|
Amount: sdk.Coins{sdk.NewInt64Coin(staking.DefaultBondDenom, 1)},
|
||||||
|
BaseReq: baseReq,
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := cdc.MarshalJSON(sr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
res, body = Request(t, port, "POST", fmt.Sprintf("/bank/accounts/%s/transfers", receiveAddr), req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTransferWithGasAccAuto(
|
||||||
|
t *testing.T, port, seed, from, memo, password string, gas string,
|
||||||
|
gasAdjustment float64, simulate, generateOnly bool, fees sdk.Coins,
|
||||||
|
) (res *http.Response, body string, receiveAddr sdk.AccAddress) {
|
||||||
|
|
||||||
|
// create receive address
|
||||||
|
kb := client.MockKeyBase()
|
||||||
|
|
||||||
|
receiveInfo, _, err := kb.CreateMnemonic(
|
||||||
|
"receive_address", crkeys.English, gapp.DefaultKeyPass, crkeys.SigningAlgo("secp256k1"),
|
||||||
|
)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
receiveAddr = sdk.AccAddress(receiveInfo.GetPubKey().Address())
|
||||||
|
chainID := viper.GetString(client.FlagChainID)
|
||||||
|
|
||||||
|
baseReq := rest.NewBaseReq(
|
||||||
|
from, password, memo, chainID, gas,
|
||||||
|
fmt.Sprintf("%f", gasAdjustment), 0, 0, fees, nil, generateOnly, simulate,
|
||||||
|
)
|
||||||
|
|
||||||
|
sr := rest.SendReq{
|
||||||
|
Amount: sdk.Coins{sdk.NewInt64Coin(staking.DefaultBondDenom, 1)},
|
||||||
BaseReq: baseReq,
|
BaseReq: baseReq,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -722,8 +752,8 @@ func doTransferWithGas(t *testing.T, port, seed, name, memo, password string, ad
|
||||||
}
|
}
|
||||||
|
|
||||||
type sendReq struct {
|
type sendReq struct {
|
||||||
Amount sdk.Coins `json:"amount"`
|
Amount sdk.Coins `json:"amount"`
|
||||||
BaseReq utils.BaseReq `json:"base_req"`
|
BaseReq rest.BaseReq `json:"base_req"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
@ -732,24 +762,25 @@ type sendReq struct {
|
||||||
|
|
||||||
// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation
|
// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation
|
||||||
func doDelegate(t *testing.T, port, name, password string,
|
func doDelegate(t *testing.T, port, name, password string,
|
||||||
delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount int64, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) {
|
delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Int, fees sdk.Coins) (resultTx sdk.TxResponse) {
|
||||||
|
|
||||||
acc := getAccount(t, port, delAddr)
|
acc := getAccount(t, port, delAddr)
|
||||||
accnum := acc.GetAccountNumber()
|
accnum := acc.GetAccountNumber()
|
||||||
sequence := acc.GetSequence()
|
sequence := acc.GetSequence()
|
||||||
chainID := viper.GetString(client.FlagChainID)
|
chainID := viper.GetString(client.FlagChainID)
|
||||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||||
msg := msgDelegationsInput{
|
msg := msgDelegationsInput{
|
||||||
BaseReq: baseReq,
|
BaseReq: baseReq,
|
||||||
DelegatorAddr: delAddr,
|
DelegatorAddr: delAddr,
|
||||||
ValidatorAddr: valAddr,
|
ValidatorAddr: valAddr,
|
||||||
Delegation: sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, amount),
|
Delegation: sdk.NewCoin(staking.DefaultBondDenom, amount),
|
||||||
}
|
}
|
||||||
req, err := cdc.MarshalJSON(msg)
|
req, err := cdc.MarshalJSON(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/delegations", delAddr.String()), req)
|
res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/delegations", delAddr.String()), req)
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
|
|
||||||
var result ctypes.ResultBroadcastTxCommit
|
var result sdk.TxResponse
|
||||||
err = cdc.UnmarshalJSON([]byte(body), &result)
|
err = cdc.UnmarshalJSON([]byte(body), &result)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
@ -757,7 +788,7 @@ func doDelegate(t *testing.T, port, name, password string,
|
||||||
}
|
}
|
||||||
|
|
||||||
type msgDelegationsInput struct {
|
type msgDelegationsInput struct {
|
||||||
BaseReq utils.BaseReq `json:"base_req"`
|
BaseReq rest.BaseReq `json:"base_req"`
|
||||||
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32
|
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32
|
||||||
ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32
|
ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32
|
||||||
Delegation sdk.Coin `json:"delegation"`
|
Delegation sdk.Coin `json:"delegation"`
|
||||||
|
@ -765,18 +796,18 @@ type msgDelegationsInput struct {
|
||||||
|
|
||||||
// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation
|
// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation
|
||||||
func doUndelegate(t *testing.T, port, name, password string,
|
func doUndelegate(t *testing.T, port, name, password string,
|
||||||
delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount int64, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) {
|
delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Int, fees sdk.Coins) (resultTx sdk.TxResponse) {
|
||||||
|
|
||||||
acc := getAccount(t, port, delAddr)
|
acc := getAccount(t, port, delAddr)
|
||||||
accnum := acc.GetAccountNumber()
|
accnum := acc.GetAccountNumber()
|
||||||
sequence := acc.GetSequence()
|
sequence := acc.GetSequence()
|
||||||
chainID := viper.GetString(client.FlagChainID)
|
chainID := viper.GetString(client.FlagChainID)
|
||||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||||
msg := msgUndelegateInput{
|
msg := msgUndelegateInput{
|
||||||
BaseReq: baseReq,
|
BaseReq: baseReq,
|
||||||
DelegatorAddr: delAddr,
|
DelegatorAddr: delAddr,
|
||||||
ValidatorAddr: valAddr,
|
ValidatorAddr: valAddr,
|
||||||
SharesAmount: sdk.NewDec(amount),
|
SharesAmount: sdk.NewDecFromInt(amount),
|
||||||
}
|
}
|
||||||
req, err := cdc.MarshalJSON(msg)
|
req, err := cdc.MarshalJSON(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -784,7 +815,7 @@ func doUndelegate(t *testing.T, port, name, password string,
|
||||||
res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/unbonding_delegations", delAddr), req)
|
res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/unbonding_delegations", delAddr), req)
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
|
|
||||||
var result ctypes.ResultBroadcastTxCommit
|
var result sdk.TxResponse
|
||||||
err = cdc.UnmarshalJSON([]byte(body), &result)
|
err = cdc.UnmarshalJSON([]byte(body), &result)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
@ -792,7 +823,7 @@ func doUndelegate(t *testing.T, port, name, password string,
|
||||||
}
|
}
|
||||||
|
|
||||||
type msgUndelegateInput struct {
|
type msgUndelegateInput struct {
|
||||||
BaseReq utils.BaseReq `json:"base_req"`
|
BaseReq rest.BaseReq `json:"base_req"`
|
||||||
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32
|
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32
|
||||||
ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32
|
ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32
|
||||||
SharesAmount sdk.Dec `json:"shares"`
|
SharesAmount sdk.Dec `json:"shares"`
|
||||||
|
@ -800,21 +831,22 @@ type msgUndelegateInput struct {
|
||||||
|
|
||||||
// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation
|
// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation
|
||||||
func doBeginRedelegation(t *testing.T, port, name, password string,
|
func doBeginRedelegation(t *testing.T, port, name, password string,
|
||||||
delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, amount int64, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) {
|
delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, amount sdk.Int,
|
||||||
|
fees sdk.Coins) (resultTx sdk.TxResponse) {
|
||||||
|
|
||||||
acc := getAccount(t, port, delAddr)
|
acc := getAccount(t, port, delAddr)
|
||||||
accnum := acc.GetAccountNumber()
|
accnum := acc.GetAccountNumber()
|
||||||
sequence := acc.GetSequence()
|
sequence := acc.GetSequence()
|
||||||
|
|
||||||
chainID := viper.GetString(client.FlagChainID)
|
chainID := viper.GetString(client.FlagChainID)
|
||||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||||
|
|
||||||
msg := msgBeginRedelegateInput{
|
msg := rest.MsgBeginRedelegateInput{
|
||||||
BaseReq: baseReq,
|
BaseReq: baseReq,
|
||||||
DelegatorAddr: delAddr,
|
DelegatorAddr: delAddr,
|
||||||
ValidatorSrcAddr: valSrcAddr,
|
ValidatorSrcAddr: valSrcAddr,
|
||||||
ValidatorDstAddr: valDstAddr,
|
ValidatorDstAddr: valDstAddr,
|
||||||
SharesAmount: sdk.NewDec(amount),
|
SharesAmount: sdk.NewDecFromInt(amount),
|
||||||
}
|
}
|
||||||
req, err := cdc.MarshalJSON(msg)
|
req, err := cdc.MarshalJSON(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -822,7 +854,7 @@ func doBeginRedelegation(t *testing.T, port, name, password string,
|
||||||
res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/redelegations", delAddr), req)
|
res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/redelegations", delAddr), req)
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
|
|
||||||
var result ctypes.ResultBroadcastTxCommit
|
var result sdk.TxResponse
|
||||||
err = cdc.UnmarshalJSON([]byte(body), &result)
|
err = cdc.UnmarshalJSON([]byte(body), &result)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
@ -830,7 +862,7 @@ func doBeginRedelegation(t *testing.T, port, name, password string,
|
||||||
}
|
}
|
||||||
|
|
||||||
type msgBeginRedelegateInput struct {
|
type msgBeginRedelegateInput struct {
|
||||||
BaseReq utils.BaseReq `json:"base_req"`
|
BaseReq rest.BaseReq `json:"base_req"`
|
||||||
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32
|
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32
|
||||||
ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` // in bech32
|
ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` // in bech32
|
||||||
ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` // in bech32
|
ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` // in bech32
|
||||||
|
@ -911,7 +943,7 @@ func getDelegatorValidator(t *testing.T, port string, delegatorAddr sdk.AccAddre
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /staking/delegators/{delegatorAddr}/txs Get all staking txs (i.e msgs) from a delegator
|
// GET /staking/delegators/{delegatorAddr}/txs Get all staking txs (i.e msgs) from a delegator
|
||||||
func getBondingTxs(t *testing.T, port string, delegatorAddr sdk.AccAddress, query string) []tx.Info {
|
func getBondingTxs(t *testing.T, port string, delegatorAddr sdk.AccAddress, query string) []sdk.TxResponse {
|
||||||
var res *http.Response
|
var res *http.Response
|
||||||
var body string
|
var body string
|
||||||
|
|
||||||
|
@ -922,7 +954,7 @@ func getBondingTxs(t *testing.T, port string, delegatorAddr sdk.AccAddress, quer
|
||||||
}
|
}
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
|
|
||||||
var txs []tx.Info
|
var txs []sdk.TxResponse
|
||||||
|
|
||||||
err := cdc.UnmarshalJSON([]byte(body), &txs)
|
err := cdc.UnmarshalJSON([]byte(body), &txs)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
@ -1033,19 +1065,21 @@ func getStakingParams(t *testing.T, port string) staking.Params {
|
||||||
// ICS 22 - Gov
|
// ICS 22 - Gov
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// POST /gov/proposals Submit a proposal
|
// POST /gov/proposals Submit a proposal
|
||||||
func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, amount int64, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) {
|
func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress,
|
||||||
|
amount sdk.Int, fees sdk.Coins) (resultTx sdk.TxResponse) {
|
||||||
|
|
||||||
acc := getAccount(t, port, proposerAddr)
|
acc := getAccount(t, port, proposerAddr)
|
||||||
accnum := acc.GetAccountNumber()
|
accnum := acc.GetAccountNumber()
|
||||||
sequence := acc.GetSequence()
|
sequence := acc.GetSequence()
|
||||||
chainID := viper.GetString(client.FlagChainID)
|
chainID := viper.GetString(client.FlagChainID)
|
||||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||||
|
|
||||||
pr := postProposalReq{
|
pr := rest.PostProposalReq{
|
||||||
Title: "Test",
|
Title: "Test",
|
||||||
Description: "test",
|
Description: "test",
|
||||||
ProposalType: "Text",
|
ProposalType: "Text",
|
||||||
Proposer: proposerAddr,
|
Proposer: proposerAddr,
|
||||||
InitialDeposit: sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))},
|
InitialDeposit: sdk.Coins{sdk.NewCoin(staking.DefaultBondDenom, amount)},
|
||||||
BaseReq: baseReq,
|
BaseReq: baseReq,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1056,7 +1090,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA
|
||||||
res, body := Request(t, port, "POST", "/gov/proposals", req)
|
res, body := Request(t, port, "POST", "/gov/proposals", req)
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
|
|
||||||
var results ctypes.ResultBroadcastTxCommit
|
var results sdk.TxResponse
|
||||||
err = cdc.UnmarshalJSON([]byte(body), &results)
|
err = cdc.UnmarshalJSON([]byte(body), &results)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
@ -1064,7 +1098,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA
|
||||||
}
|
}
|
||||||
|
|
||||||
type postProposalReq struct {
|
type postProposalReq struct {
|
||||||
BaseReq utils.BaseReq `json:"base_req"`
|
BaseReq rest.BaseReq `json:"base_req"`
|
||||||
Title string `json:"title"` // Title of the proposal
|
Title string `json:"title"` // Title of the proposal
|
||||||
Description string `json:"description"` // Description of the proposal
|
Description string `json:"description"` // Description of the proposal
|
||||||
ProposalType string `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
|
ProposalType string `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
|
||||||
|
@ -1128,17 +1162,18 @@ func getProposalsFilterStatus(t *testing.T, port string, status gov.ProposalStat
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /gov/proposals/{proposalId}/deposits Deposit tokens to a proposal
|
// POST /gov/proposals/{proposalId}/deposits Deposit tokens to a proposal
|
||||||
func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64, amount int64, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) {
|
func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64,
|
||||||
|
amount sdk.Int, fees sdk.Coins) (resultTx sdk.TxResponse) {
|
||||||
|
|
||||||
acc := getAccount(t, port, proposerAddr)
|
acc := getAccount(t, port, proposerAddr)
|
||||||
accnum := acc.GetAccountNumber()
|
accnum := acc.GetAccountNumber()
|
||||||
sequence := acc.GetSequence()
|
sequence := acc.GetSequence()
|
||||||
chainID := viper.GetString(client.FlagChainID)
|
chainID := viper.GetString(client.FlagChainID)
|
||||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||||
|
|
||||||
dr := depositReq{
|
dr := rest.DepositReq{
|
||||||
Depositor: proposerAddr,
|
Depositor: proposerAddr,
|
||||||
Amount: sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))},
|
Amount: sdk.Coins{sdk.NewCoin(staking.DefaultBondDenom, amount)},
|
||||||
BaseReq: baseReq,
|
BaseReq: baseReq,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1148,7 +1183,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk
|
||||||
res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), req)
|
res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), req)
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
|
|
||||||
var results ctypes.ResultBroadcastTxCommit
|
var results sdk.TxResponse
|
||||||
err = cdc.UnmarshalJSON([]byte(body), &results)
|
err = cdc.UnmarshalJSON([]byte(body), &results)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
@ -1156,7 +1191,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk
|
||||||
}
|
}
|
||||||
|
|
||||||
type depositReq struct {
|
type depositReq struct {
|
||||||
BaseReq utils.BaseReq `json:"base_req"`
|
BaseReq rest.BaseReq `json:"base_req"`
|
||||||
Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor
|
Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor
|
||||||
Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit
|
Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit
|
||||||
}
|
}
|
||||||
|
@ -1182,15 +1217,15 @@ func getTally(t *testing.T, port string, proposalID uint64) gov.TallyResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /gov/proposals/{proposalId}/votes Vote a proposal
|
// POST /gov/proposals/{proposalId}/votes Vote a proposal
|
||||||
func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64, option string, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) {
|
func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64, option string, fees sdk.Coins) (resultTx sdk.TxResponse) {
|
||||||
// get the account to get the sequence
|
// get the account to get the sequence
|
||||||
acc := getAccount(t, port, proposerAddr)
|
acc := getAccount(t, port, proposerAddr)
|
||||||
accnum := acc.GetAccountNumber()
|
accnum := acc.GetAccountNumber()
|
||||||
sequence := acc.GetSequence()
|
sequence := acc.GetSequence()
|
||||||
chainID := viper.GetString(client.FlagChainID)
|
chainID := viper.GetString(client.FlagChainID)
|
||||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||||
|
|
||||||
vr := voteReq{
|
vr := rest.VoteReq{
|
||||||
Voter: proposerAddr,
|
Voter: proposerAddr,
|
||||||
Option: option,
|
Option: option,
|
||||||
BaseReq: baseReq,
|
BaseReq: baseReq,
|
||||||
|
@ -1202,7 +1237,7 @@ func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.Ac
|
||||||
res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/votes", proposalID), req)
|
res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/votes", proposalID), req)
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
|
|
||||||
var results ctypes.ResultBroadcastTxCommit
|
var results sdk.TxResponse
|
||||||
err = cdc.UnmarshalJSON([]byte(body), &results)
|
err = cdc.UnmarshalJSON([]byte(body), &results)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
@ -1210,7 +1245,7 @@ func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.Ac
|
||||||
}
|
}
|
||||||
|
|
||||||
type voteReq struct {
|
type voteReq struct {
|
||||||
BaseReq utils.BaseReq `json:"base_req"`
|
BaseReq rest.BaseReq `json:"base_req"`
|
||||||
Voter sdk.AccAddress `json:"voter"` // address of the voter
|
Voter sdk.AccAddress `json:"voter"` // address of the voter
|
||||||
Option string `json:"option"` // option from OptionSet chosen by the voter
|
Option string `json:"option"` // option from OptionSet chosen by the voter
|
||||||
}
|
}
|
||||||
|
@ -1318,11 +1353,11 @@ func getSigningInfo(t *testing.T, port string, validatorPubKey string) slashing.
|
||||||
// TODO: Test this functionality, it is not currently in any of the tests
|
// TODO: Test this functionality, it is not currently in any of the tests
|
||||||
// POST /slashing/validators/{validatorAddr}/unjail Unjail a jailed validator
|
// POST /slashing/validators/{validatorAddr}/unjail Unjail a jailed validator
|
||||||
func doUnjail(t *testing.T, port, seed, name, password string,
|
func doUnjail(t *testing.T, port, seed, name, password string,
|
||||||
valAddr sdk.ValAddress, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) {
|
valAddr sdk.ValAddress, fees sdk.Coins) (resultTx sdk.TxResponse) {
|
||||||
chainID := viper.GetString(client.FlagChainID)
|
chainID := viper.GetString(client.FlagChainID)
|
||||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", 1, 1, fees, nil, false, false)
|
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", 1, 1, fees, nil, false, false)
|
||||||
|
|
||||||
ur := unjailReq{
|
ur := rest.UnjailReq{
|
||||||
BaseReq: baseReq,
|
BaseReq: baseReq,
|
||||||
}
|
}
|
||||||
req, err := cdc.MarshalJSON(ur)
|
req, err := cdc.MarshalJSON(ur)
|
||||||
|
@ -1330,13 +1365,47 @@ func doUnjail(t *testing.T, port, seed, name, password string,
|
||||||
res, body := Request(t, port, "POST", fmt.Sprintf("/slashing/validators/%s/unjail", valAddr.String()), req)
|
res, body := Request(t, port, "POST", fmt.Sprintf("/slashing/validators/%s/unjail", valAddr.String()), req)
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
|
|
||||||
var results []ctypes.ResultBroadcastTxCommit
|
var results sdk.TxResponse
|
||||||
err = cdc.UnmarshalJSON([]byte(body), &results)
|
err = cdc.UnmarshalJSON([]byte(body), &results)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
return results[0]
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
type unjailReq struct {
|
type unjailReq struct {
|
||||||
BaseReq utils.BaseReq `json:"base_req"`
|
BaseReq rest.BaseReq `json:"base_req"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ICS24 - fee distribution
|
||||||
|
|
||||||
|
// POST /distribution/delegators/{delgatorAddr}/rewards Withdraw delegator rewards
|
||||||
|
func doWithdrawDelegatorAllRewards(t *testing.T, port, seed, name, password string,
|
||||||
|
delegatorAddr sdk.AccAddress, fees sdk.Coins) (resultTx sdk.TxResponse) {
|
||||||
|
// get the account to get the sequence
|
||||||
|
acc := getAccount(t, port, delegatorAddr)
|
||||||
|
accnum := acc.GetAccountNumber()
|
||||||
|
sequence := acc.GetSequence()
|
||||||
|
chainID := viper.GetString(client.FlagChainID)
|
||||||
|
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||||
|
|
||||||
|
wr := struct {
|
||||||
|
BaseReq rest.BaseReq `json:"base_req"`
|
||||||
|
}{BaseReq: baseReq}
|
||||||
|
|
||||||
|
req := cdc.MustMarshalJSON(wr)
|
||||||
|
res, body := Request(t, port, "POST", fmt.Sprintf("/distribution/delegators/%s/rewards", delegatorAddr), req)
|
||||||
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
|
|
||||||
|
var results sdk.TxResponse
|
||||||
|
cdc.MustUnmarshalJSON([]byte(body), &results)
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustParseDecCoins(dcstring string) sdk.DecCoins {
|
||||||
|
dcoins, err := sdk.ParseDecCoins(dcstring)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return dcoins
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,255 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Basic HTTP utilities
|
||||||
|
|
||||||
|
// ErrorResponse defines the attributes of a JSON error response.
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Code int `json:"code,omitempty"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrorResponse creates a new ErrorResponse instance.
|
||||||
|
func NewErrorResponse(code int, msg string) ErrorResponse {
|
||||||
|
return ErrorResponse{Code: code, Message: msg}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteErrorResponse prepares and writes a HTTP error
|
||||||
|
// given a status code and an error message.
|
||||||
|
func WriteErrorResponse(w http.ResponseWriter, status int, err string) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(status)
|
||||||
|
w.Write(codec.Cdc.MustMarshalJSON(NewErrorResponse(0, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteSimulationResponse prepares and writes an HTTP
|
||||||
|
// response for transactions simulations.
|
||||||
|
func WriteSimulationResponse(w http.ResponseWriter, cdc *codec.Codec, gas uint64) {
|
||||||
|
gasEst := GasEstimateResponse{GasEstimate: gas}
|
||||||
|
resp, err := cdc.MarshalJSON(gasEst)
|
||||||
|
if err != nil {
|
||||||
|
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseInt64OrReturnBadRequest converts s to a int64 value.
|
||||||
|
func ParseInt64OrReturnBadRequest(w http.ResponseWriter, s string) (n int64, ok bool) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
n, err = strconv.ParseInt(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("'%s' is not a valid int64", s)
|
||||||
|
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
|
return n, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseUint64OrReturnBadRequest converts s to a uint64 value.
|
||||||
|
func ParseUint64OrReturnBadRequest(w http.ResponseWriter, s string) (n uint64, ok bool) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
n, err = strconv.ParseUint(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("'%s' is not a valid uint64", s)
|
||||||
|
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
|
return n, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a
|
||||||
|
// default value, defaultIfEmpty, if the string is empty.
|
||||||
|
func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEmpty float64) (n float64, ok bool) {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return defaultIfEmpty, true
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
|
return n, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Building / Sending utilities
|
||||||
|
|
||||||
|
// CompleteAndBroadcastTxREST implements a utility function that facilitates
|
||||||
|
// sending a series of messages in a signed tx. In addition, it will handle
|
||||||
|
// tx gas simulation and estimation.
|
||||||
|
//
|
||||||
|
// NOTE: Also see CompleteAndBroadcastTxCLI.
|
||||||
|
func CompleteAndBroadcastTxREST(
|
||||||
|
w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext,
|
||||||
|
baseReq BaseReq, msgs []sdk.Msg, cdc *codec.Codec,
|
||||||
|
) {
|
||||||
|
|
||||||
|
gasAdj, ok := ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
simAndExec, gas, err := client.ParseGas(baseReq.Gas)
|
||||||
|
if err != nil {
|
||||||
|
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// derive the from account address and name from the Keybase
|
||||||
|
fromAddress, fromName, err := context.GetFromFields(baseReq.From)
|
||||||
|
if err != nil {
|
||||||
|
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress)
|
||||||
|
txBldr := authtxb.NewTxBuilder(
|
||||||
|
utils.GetTxEncoder(cdc), baseReq.AccountNumber,
|
||||||
|
baseReq.Sequence, gas, gasAdj, baseReq.Simulate,
|
||||||
|
baseReq.ChainID, baseReq.Memo, baseReq.Fees, baseReq.GasPrices,
|
||||||
|
)
|
||||||
|
|
||||||
|
txBldr, err = utils.PrepareTxBuilder(txBldr, cliCtx)
|
||||||
|
if err != nil {
|
||||||
|
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if baseReq.Simulate || simAndExec {
|
||||||
|
if gasAdj < 0 {
|
||||||
|
WriteErrorResponse(w, http.StatusBadRequest, client.ErrInvalidGasAdjustment.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
txBldr, err = utils.EnrichWithGas(txBldr, cliCtx, msgs)
|
||||||
|
if err != nil {
|
||||||
|
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if baseReq.Simulate {
|
||||||
|
WriteSimulationResponse(w, cdc, txBldr.Gas())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
txBytes, err := txBldr.BuildAndSign(cliCtx.GetFromName(), baseReq.Password, msgs)
|
||||||
|
if keyerror.IsErrKeyNotFound(err) {
|
||||||
|
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
} else if keyerror.IsErrWrongPassword(err) {
|
||||||
|
WriteErrorResponse(w, http.StatusUnauthorized, err.Error())
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := cliCtx.BroadcastTx(txBytes)
|
||||||
|
if err != nil {
|
||||||
|
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
PostProcessResponse(w, cdc, res, cliCtx.Indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostProcessResponse performs post processing for a REST response.
|
||||||
|
func PostProcessResponse(w http.ResponseWriter, cdc *codec.Codec, response interface{}, indent bool) {
|
||||||
|
var output []byte
|
||||||
|
|
||||||
|
switch response.(type) {
|
||||||
|
default:
|
||||||
|
var err error
|
||||||
|
if indent {
|
||||||
|
output, err = cdc.MarshalJSONIndent(response, "", " ")
|
||||||
|
} else {
|
||||||
|
output, err = cdc.MarshalJSON(response)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case []byte:
|
||||||
|
output = response.([]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteGenerateStdTxResponse writes response for the generate only mode.
|
||||||
|
func WriteGenerateStdTxResponse(
|
||||||
|
w http.ResponseWriter, cdc *codec.Codec, cliCtx context.CLIContext, br BaseReq, msgs []sdk.Msg,
|
||||||
|
) {
|
||||||
|
|
||||||
|
gasAdj, ok := ParseFloat64OrReturnBadRequest(w, br.GasAdjustment, client.DefaultGasAdjustment)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
simAndExec, gas, err := client.ParseGas(br.Gas)
|
||||||
|
if err != nil {
|
||||||
|
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
txBldr := authtxb.NewTxBuilder(
|
||||||
|
utils.GetTxEncoder(cdc), br.AccountNumber, br.Sequence, gas, gasAdj,
|
||||||
|
br.Simulate, br.ChainID, br.Memo, br.Fees, br.GasPrices,
|
||||||
|
)
|
||||||
|
|
||||||
|
if simAndExec {
|
||||||
|
if gasAdj < 0 {
|
||||||
|
WriteErrorResponse(w, http.StatusBadRequest, client.ErrInvalidGasAdjustment.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
txBldr, err = utils.EnrichWithGas(txBldr, cliCtx, msgs)
|
||||||
|
if err != nil {
|
||||||
|
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stdMsg, err := txBldr.BuildSignMsg(msgs)
|
||||||
|
if err != nil {
|
||||||
|
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := cdc.MarshalJSON(auth.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo))
|
||||||
|
if err != nil {
|
||||||
|
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(output)
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,187 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GasEstimateResponse defines a response definition for tx gas estimation.
|
||||||
|
type GasEstimateResponse struct {
|
||||||
|
GasEstimate uint64 `json:"gas_estimate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseReq defines a structure that can be embedded in other request structures
|
||||||
|
// that all share common "base" fields.
|
||||||
|
type BaseReq struct {
|
||||||
|
From string `json:"from"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Memo string `json:"memo"`
|
||||||
|
ChainID string `json:"chain_id"`
|
||||||
|
AccountNumber uint64 `json:"account_number"`
|
||||||
|
Sequence uint64 `json:"sequence"`
|
||||||
|
Fees sdk.Coins `json:"fees"`
|
||||||
|
GasPrices sdk.DecCoins `json:"gas_prices"`
|
||||||
|
Gas string `json:"gas"`
|
||||||
|
GasAdjustment string `json:"gas_adjustment"`
|
||||||
|
GenerateOnly bool `json:"generate_only"`
|
||||||
|
Simulate bool `json:"simulate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBaseReq creates a new basic request instance and sanitizes its values
|
||||||
|
func NewBaseReq(
|
||||||
|
from, password, memo, chainID string, gas, gasAdjustment string,
|
||||||
|
accNumber, seq uint64, fees sdk.Coins, gasPrices sdk.DecCoins, genOnly, simulate bool,
|
||||||
|
) BaseReq {
|
||||||
|
|
||||||
|
return BaseReq{
|
||||||
|
From: strings.TrimSpace(from),
|
||||||
|
Password: password,
|
||||||
|
Memo: strings.TrimSpace(memo),
|
||||||
|
ChainID: strings.TrimSpace(chainID),
|
||||||
|
Fees: fees,
|
||||||
|
GasPrices: gasPrices,
|
||||||
|
Gas: strings.TrimSpace(gas),
|
||||||
|
GasAdjustment: strings.TrimSpace(gasAdjustment),
|
||||||
|
AccountNumber: accNumber,
|
||||||
|
Sequence: seq,
|
||||||
|
GenerateOnly: genOnly,
|
||||||
|
Simulate: simulate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitize performs basic sanitization on a BaseReq object.
|
||||||
|
func (br BaseReq) Sanitize() BaseReq {
|
||||||
|
return NewBaseReq(
|
||||||
|
br.From, br.Password, br.Memo, br.ChainID, br.Gas, br.GasAdjustment,
|
||||||
|
br.AccountNumber, br.Sequence, br.Fees, br.GasPrices, br.GenerateOnly, br.Simulate,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateBasic performs basic validation of a BaseReq. If custom validation
|
||||||
|
// logic is needed, the implementing request handler should perform those
|
||||||
|
// checks manually.
|
||||||
|
func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool {
|
||||||
|
if !br.GenerateOnly && !br.Simulate {
|
||||||
|
switch {
|
||||||
|
case len(br.Password) == 0:
|
||||||
|
WriteErrorResponse(w, http.StatusUnauthorized, "password required but not specified")
|
||||||
|
return false
|
||||||
|
|
||||||
|
case len(br.ChainID) == 0:
|
||||||
|
WriteErrorResponse(w, http.StatusUnauthorized, "chain-id required but not specified")
|
||||||
|
return false
|
||||||
|
|
||||||
|
case !br.Fees.IsZero() && !br.GasPrices.IsZero():
|
||||||
|
// both fees and gas prices were provided
|
||||||
|
WriteErrorResponse(w, http.StatusBadRequest, "cannot provide both fees and gas prices")
|
||||||
|
return false
|
||||||
|
|
||||||
|
case !br.Fees.IsValid() && !br.GasPrices.IsValid():
|
||||||
|
// neither fees or gas prices were provided
|
||||||
|
WriteErrorResponse(w, http.StatusPaymentRequired, "invalid fees or gas prices provided")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(br.From) == 0 {
|
||||||
|
WriteErrorResponse(w, http.StatusUnauthorized, "name or address required but not specified")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ReadRESTReq is a simple convenience wrapper that reads the body and
|
||||||
|
unmarshals to the req interface. Returns false if errors occurred.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
type SomeReq struct {
|
||||||
|
BaseReq `json:"base_req"`
|
||||||
|
CustomField string `json:"custom_field"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(SomeReq)
|
||||||
|
if ok := ReadRESTReq(w, r, cdc, req); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req interface{}) bool {
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cdc.UnmarshalJSON(body, req)
|
||||||
|
if err != nil {
|
||||||
|
WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to decode JSON payload: %s", err))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddrSeed combines an Address with the mnemonic of the private key to that address
|
||||||
|
type AddrSeed struct {
|
||||||
|
Address sdk.AccAddress
|
||||||
|
Seed string
|
||||||
|
Name string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendReq requests sending an amount of coins
|
||||||
|
type SendReq struct {
|
||||||
|
Amount sdk.Coins `json:"amount"`
|
||||||
|
BaseReq BaseReq `json:"base_req"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgBeginRedelegateInput request to begin a redelegation
|
||||||
|
type MsgBeginRedelegateInput struct {
|
||||||
|
BaseReq BaseReq `json:"base_req"`
|
||||||
|
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32
|
||||||
|
ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` // in bech32
|
||||||
|
ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` // in bech32
|
||||||
|
SharesAmount sdk.Dec `json:"shares"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostProposalReq requests a proposals
|
||||||
|
type PostProposalReq struct {
|
||||||
|
BaseReq BaseReq `json:"base_req"`
|
||||||
|
Title string `json:"title"` // Title of the proposal
|
||||||
|
Description string `json:"description"` // Description of the proposal
|
||||||
|
ProposalType string `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
|
||||||
|
Proposer sdk.AccAddress `json:"proposer"` // Address of the proposer
|
||||||
|
InitialDeposit sdk.Coins `json:"initial_deposit"` // Coins to add to the proposal's deposit
|
||||||
|
}
|
||||||
|
|
||||||
|
// BroadcastReq requests broadcasting a transaction
|
||||||
|
type BroadcastReq struct {
|
||||||
|
Tx auth.StdTx `json:"tx"`
|
||||||
|
Return string `json:"return"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DepositReq requests a deposit of an amount of coins
|
||||||
|
type DepositReq struct {
|
||||||
|
BaseReq BaseReq `json:"base_req"`
|
||||||
|
Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor
|
||||||
|
Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit
|
||||||
|
}
|
||||||
|
|
||||||
|
// VoteReq requests sending a vote
|
||||||
|
type VoteReq struct {
|
||||||
|
BaseReq BaseReq `json:"base_req"`
|
||||||
|
Voter sdk.AccAddress `json:"voter"` // address of the voter
|
||||||
|
Option string `json:"option"` // option from OptionSet chosen by the voter
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnjailReq request unjailing
|
||||||
|
type UnjailReq struct {
|
||||||
|
BaseReq BaseReq `json:"base_req"`
|
||||||
|
}
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
|
||||||
|
@ -12,8 +14,6 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
tmliteProxy "github.com/tendermint/tendermint/lite/proxy"
|
tmliteProxy "github.com/tendermint/tendermint/lite/proxy"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//BlockCommand returns the verified block data for a given heights
|
//BlockCommand returns the verified block data for a given heights
|
||||||
|
@ -131,7 +131,7 @@ func BlockRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||||
w.Write([]byte(err.Error()))
|
w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
rest.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,6 +150,6 @@ func LatestBlockRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||||
w.Write([]byte(err.Error()))
|
w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
rest.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
|
||||||
"github.com/cosmos/cosmos-sdk/version"
|
"github.com/cosmos/cosmos-sdk/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||||
// cli version REST handler endpoint
|
// cli version REST handler endpoint
|
||||||
func CLIVersionRequestHandler(w http.ResponseWriter, r *http.Request) {
|
func CLIVersionRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Write([]byte(fmt.Sprintf("{\"version\": \"%s\"}", version.GetVersion())))
|
w.Write([]byte(fmt.Sprintf("{\"version\": \"%s\"}", version.Version)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// connected node version REST handler endpoint
|
// connected node version REST handler endpoint
|
||||||
|
@ -34,7 +35,7 @@ func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
version, err := cliCtx.Query("/app/version", nil)
|
version, err := cliCtx.Query("/app/version", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -12,7 +14,6 @@ import (
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// StatusCommand returns the status of the network
|
// StatusCommand returns the status of the network
|
||||||
|
@ -77,7 +78,7 @@ func NodeInfoRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeInfo := status.NodeInfo
|
nodeInfo := status.NodeInfo
|
||||||
utils.PostProcessResponse(w, cdc, nodeInfo, cliCtx.Indent)
|
rest.PostProcessResponse(w, cdc, nodeInfo, cliCtx.Indent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -14,33 +19,59 @@ import (
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO these next two functions feel kinda hacky based on their placement
|
// TODO these next two functions feel kinda hacky based on their placement
|
||||||
|
|
||||||
//ValidatorCommand returns the validator set for a given height
|
//ValidatorCommand returns the validator set for a given height
|
||||||
func ValidatorCommand() *cobra.Command {
|
func ValidatorCommand(cdc *codec.Codec) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "tendermint-validator-set [height]",
|
Use: "tendermint-validator-set [height]",
|
||||||
Short: "Get the full tendermint validator set at given height",
|
Short: "Get the full tendermint validator set at given height",
|
||||||
Args: cobra.MaximumNArgs(1),
|
Args: cobra.MaximumNArgs(1),
|
||||||
RunE: printValidators,
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
var height *int64
|
||||||
|
|
||||||
|
// optional height
|
||||||
|
if len(args) > 0 {
|
||||||
|
h, err := strconv.Atoi(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if h > 0 {
|
||||||
|
tmp := int64(h)
|
||||||
|
height = &tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||||
|
|
||||||
|
result, err := getValidators(cliCtx, height)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cliCtx.PrintOutput(result)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
|
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
|
||||||
viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode))
|
viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode))
|
||||||
cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)")
|
cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)")
|
||||||
viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode))
|
viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode))
|
||||||
|
cmd.Flags().Bool(client.FlagIndentResponse, false, "indent JSON response")
|
||||||
|
viper.BindPFlag(client.FlagIndentResponse, cmd.Flags().Lookup(client.FlagIndentResponse))
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validator output in bech32 format
|
// Validator output in bech32 format
|
||||||
type ValidatorOutput struct {
|
type ValidatorOutput struct {
|
||||||
Address sdk.ValAddress `json:"address"` // in bech32
|
Address sdk.ConsAddress `json:"address"`
|
||||||
PubKey string `json:"pub_key"` // in bech32
|
PubKey string `json:"pub_key"`
|
||||||
ProposerPriority int64 `json:"proposer_priority"`
|
ProposerPriority int64 `json:"proposer_priority"`
|
||||||
VotingPower int64 `json:"voting_power"`
|
VotingPower int64 `json:"voting_power"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validators at a certain height output in bech32 format
|
// Validators at a certain height output in bech32 format
|
||||||
|
@ -49,6 +80,27 @@ type ResultValidatorsOutput struct {
|
||||||
Validators []ValidatorOutput `json:"validators"`
|
Validators []ValidatorOutput `json:"validators"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rvo ResultValidatorsOutput) String() string {
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
b.WriteString(fmt.Sprintf("block height: %d\n", rvo.BlockHeight))
|
||||||
|
|
||||||
|
for _, val := range rvo.Validators {
|
||||||
|
b.WriteString(
|
||||||
|
fmt.Sprintf(`
|
||||||
|
Address: %s
|
||||||
|
Pubkey: %s
|
||||||
|
ProposerPriority: %d
|
||||||
|
VotingPower: %d
|
||||||
|
`,
|
||||||
|
val.Address, val.PubKey, val.ProposerPriority, val.VotingPower,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error) {
|
func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error) {
|
||||||
bechValPubkey, err := sdk.Bech32ifyConsPub(validator.PubKey)
|
bechValPubkey, err := sdk.Bech32ifyConsPub(validator.PubKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -56,33 +108,33 @@ func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error
|
||||||
}
|
}
|
||||||
|
|
||||||
return ValidatorOutput{
|
return ValidatorOutput{
|
||||||
Address: sdk.ValAddress(validator.Address),
|
Address: sdk.ConsAddress(validator.Address),
|
||||||
PubKey: bechValPubkey,
|
PubKey: bechValPubkey,
|
||||||
ProposerPriority: validator.ProposerPriority,
|
ProposerPriority: validator.ProposerPriority,
|
||||||
VotingPower: validator.VotingPower,
|
VotingPower: validator.VotingPower,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getValidators(cliCtx context.CLIContext, height *int64) ([]byte, error) {
|
func getValidators(cliCtx context.CLIContext, height *int64) (ResultValidatorsOutput, error) {
|
||||||
// get the node
|
// get the node
|
||||||
node, err := cliCtx.GetNode()
|
node, err := cliCtx.GetNode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return ResultValidatorsOutput{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
validatorsRes, err := node.Validators(height)
|
validatorsRes, err := node.Validators(height)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return ResultValidatorsOutput{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cliCtx.TrustNode {
|
if !cliCtx.TrustNode {
|
||||||
check, err := cliCtx.Verify(validatorsRes.BlockHeight)
|
check, err := cliCtx.Verify(validatorsRes.BlockHeight)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return ResultValidatorsOutput{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(check.ValidatorsHash, tmtypes.NewValidatorSet(validatorsRes.Validators).Hash()) {
|
if !bytes.Equal(check.ValidatorsHash, tmtypes.NewValidatorSet(validatorsRes.Validators).Hash()) {
|
||||||
return nil, fmt.Errorf("got invalid validatorset")
|
return ResultValidatorsOutput{}, fmt.Errorf("received invalid validatorset")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,40 +146,11 @@ func getValidators(cliCtx context.CLIContext, height *int64) ([]byte, error) {
|
||||||
for i := 0; i < len(validatorsRes.Validators); i++ {
|
for i := 0; i < len(validatorsRes.Validators); i++ {
|
||||||
outputValidatorsRes.Validators[i], err = bech32ValidatorOutput(validatorsRes.Validators[i])
|
outputValidatorsRes.Validators[i], err = bech32ValidatorOutput(validatorsRes.Validators[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return ResultValidatorsOutput{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cliCtx.Indent {
|
return outputValidatorsRes, nil
|
||||||
return cdc.MarshalJSONIndent(outputValidatorsRes, "", " ")
|
|
||||||
}
|
|
||||||
return cdc.MarshalJSON(outputValidatorsRes)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// CMD
|
|
||||||
|
|
||||||
func printValidators(cmd *cobra.Command, args []string) error {
|
|
||||||
var height *int64
|
|
||||||
// optional height
|
|
||||||
if len(args) > 0 {
|
|
||||||
h, err := strconv.Atoi(args[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if h > 0 {
|
|
||||||
tmp := int64(h)
|
|
||||||
height = &tmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := getValidators(context.NewCLIContext(), height)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(string(output))
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// REST
|
// REST
|
||||||
|
@ -157,7 +180,7 @@ func ValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||||
w.Write([]byte(err.Error()))
|
w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
rest.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,6 +200,6 @@ func LatestValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerF
|
||||||
w.Write([]byte(err.Error()))
|
w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
rest.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,11 @@ package tx
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||||
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,12 +33,12 @@ func BroadcastTxRequest(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle
|
||||||
var m BroadcastBody
|
var m BroadcastBody
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = cdc.UnmarshalJSON(body, &m)
|
err = cdc.UnmarshalJSON(body, &m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var res interface{}
|
var res interface{}
|
||||||
|
@ -49,13 +50,13 @@ func BroadcastTxRequest(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle
|
||||||
case flagAsync:
|
case flagAsync:
|
||||||
res, err = cliCtx.BroadcastTxAsync(m.TxBytes)
|
res, err = cliCtx.BroadcastTxAsync(m.TxBytes)
|
||||||
default:
|
default:
|
||||||
utils.WriteErrorResponse(w, http.StatusInternalServerError, "unsupported return type. supported types: block, sync, async")
|
rest.WriteErrorResponse(w, http.StatusInternalServerError, "unsupported return type. supported types: block, sync, async")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
utils.PostProcessResponse(w, cdc, res, cliCtx.Indent)
|
rest.PostProcessResponse(w, cdc, res, cliCtx.Indent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,15 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/libs/common"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
|
||||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
@ -99,26 +96,13 @@ func ValidateTxResult(cliCtx context.CLIContext, res *ctypes.ResultTx) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatTxResult(cdc *codec.Codec, res *ctypes.ResultTx) (Info, error) {
|
func formatTxResult(cdc *codec.Codec, res *ctypes.ResultTx) (sdk.TxResponse, error) {
|
||||||
tx, err := parseTx(cdc, res.Tx)
|
tx, err := parseTx(cdc, res.Tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Info{}, err
|
return sdk.TxResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return Info{
|
return sdk.NewResponseResultTx(res, tx), nil
|
||||||
Hash: res.Hash,
|
|
||||||
Height: res.Height,
|
|
||||||
Tx: tx,
|
|
||||||
Result: res.TxResult,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info is used to prepare info to display
|
|
||||||
type Info struct {
|
|
||||||
Hash common.HexBytes `json:"hash"`
|
|
||||||
Height int64 `json:"height"`
|
|
||||||
Tx sdk.Tx `json:"tx"`
|
|
||||||
Result abci.ResponseDeliverTx `json:"result"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTx(cdc *codec.Codec, txBytes []byte) (sdk.Tx, error) {
|
func parseTx(cdc *codec.Codec, txBytes []byte) (sdk.Tx, error) {
|
||||||
|
@ -142,9 +126,9 @@ func QueryTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.H
|
||||||
|
|
||||||
output, err := queryTx(cdc, cliCtx, hashHexStr)
|
output, err := queryTx(cdc, cliCtx, hashHexStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
rest.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,10 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
@ -105,7 +106,7 @@ $ gaiacli query txs --tags '<tag1>:<value1>&<tag2>:<value2>' --page 1 --limit 30
|
||||||
// SearchTxs performs a search for transactions for a given set of tags via
|
// SearchTxs performs a search for transactions for a given set of tags via
|
||||||
// Tendermint RPC. It returns a slice of Info object containing txs and metadata.
|
// Tendermint RPC. It returns a slice of Info object containing txs and metadata.
|
||||||
// An error is returned if the query fails.
|
// An error is returned if the query fails.
|
||||||
func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string, page, limit int) ([]Info, error) {
|
func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string, page, limit int) ([]sdk.TxResponse, error) {
|
||||||
if len(tags) == 0 {
|
if len(tags) == 0 {
|
||||||
return nil, errors.New("must declare at least one tag to search")
|
return nil, errors.New("must declare at least one tag to search")
|
||||||
}
|
}
|
||||||
|
@ -152,9 +153,9 @@ func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string, page,
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse the indexed txs into an array of Info
|
// parse the indexed txs into an array of Info
|
||||||
func FormatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]Info, error) {
|
func FormatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]sdk.TxResponse, error) {
|
||||||
var err error
|
var err error
|
||||||
out := make([]Info, len(res))
|
out := make([]sdk.TxResponse, len(res))
|
||||||
for i := range res {
|
for i := range res {
|
||||||
out[i], err = formatTxResult(cdc, res[i])
|
out[i], err = formatTxResult(cdc, res[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -172,31 +173,31 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var tags []string
|
var tags []string
|
||||||
var page, limit int
|
var page, limit int
|
||||||
var txs []Info
|
var txs []sdk.TxResponse
|
||||||
err := r.ParseForm()
|
err := r.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.WriteErrorResponse(w, http.StatusBadRequest, sdk.AppendMsgToErr("could not parse query parameters", err.Error()))
|
rest.WriteErrorResponse(w, http.StatusBadRequest, sdk.AppendMsgToErr("could not parse query parameters", err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(r.Form) == 0 {
|
if len(r.Form) == 0 {
|
||||||
utils.PostProcessResponse(w, cdc, txs, cliCtx.Indent)
|
rest.PostProcessResponse(w, cdc, txs, cliCtx.Indent)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tags, page, limit, err = parseHTTPArgs(r)
|
tags, page, limit, err = parseHTTPArgs(r)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
txs, err = SearchTxs(cliCtx, cdc, tags, page, limit)
|
txs, err = SearchTxs(cliCtx, cdc, tags, page, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.PostProcessResponse(w, cdc, txs, cliCtx.Indent)
|
rest.PostProcessResponse(w, cdc, txs, cliCtx.Indent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,305 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
|
||||||
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
|
||||||
)
|
|
||||||
|
|
||||||
//----------------------------------------
|
|
||||||
// Basic HTTP utilities
|
|
||||||
|
|
||||||
// WriteErrorResponse prepares and writes a HTTP error
|
|
||||||
// given a status code and an error message.
|
|
||||||
func WriteErrorResponse(w http.ResponseWriter, status int, err string) {
|
|
||||||
w.WriteHeader(status)
|
|
||||||
w.Write([]byte(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteSimulationResponse prepares and writes an HTTP
|
|
||||||
// response for transactions simulations.
|
|
||||||
func WriteSimulationResponse(w http.ResponseWriter, gas uint64) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte(fmt.Sprintf(`{"gas_estimate":%v}`, gas)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseInt64OrReturnBadRequest converts s to a int64 value.
|
|
||||||
func ParseInt64OrReturnBadRequest(w http.ResponseWriter, s string) (n int64, ok bool) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
n, err = strconv.ParseInt(s, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("'%s' is not a valid int64", s)
|
|
||||||
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
||||||
return n, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseUint64OrReturnBadRequest converts s to a uint64 value.
|
|
||||||
func ParseUint64OrReturnBadRequest(w http.ResponseWriter, s string) (n uint64, ok bool) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
n, err = strconv.ParseUint(s, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("'%s' is not a valid uint64", s)
|
|
||||||
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
||||||
return n, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a
|
|
||||||
// default value, defaultIfEmpty, if the string is empty.
|
|
||||||
func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEmpty float64) (n float64, ok bool) {
|
|
||||||
if len(s) == 0 {
|
|
||||||
return defaultIfEmpty, true
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := strconv.ParseFloat(s, 64)
|
|
||||||
if err != nil {
|
|
||||||
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
||||||
return n, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteGenerateStdTxResponse writes response for the generate_only mode.
|
|
||||||
func WriteGenerateStdTxResponse(w http.ResponseWriter, cdc *codec.Codec, txBldr authtxb.TxBuilder, msgs []sdk.Msg) {
|
|
||||||
stdMsg, err := txBldr.Build(msgs)
|
|
||||||
if err != nil {
|
|
||||||
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := cdc.MarshalJSON(auth.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo))
|
|
||||||
if err != nil {
|
|
||||||
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------
|
|
||||||
// Building / Sending utilities
|
|
||||||
|
|
||||||
// BaseReq defines a structure that can be embedded in other request structures
|
|
||||||
// that all share common "base" fields.
|
|
||||||
type BaseReq struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Memo string `json:"memo"`
|
|
||||||
ChainID string `json:"chain_id"`
|
|
||||||
AccountNumber uint64 `json:"account_number"`
|
|
||||||
Sequence uint64 `json:"sequence"`
|
|
||||||
Fees sdk.Coins `json:"fees"`
|
|
||||||
GasPrices sdk.DecCoins `json:"gas_prices"`
|
|
||||||
Gas string `json:"gas"`
|
|
||||||
GasAdjustment string `json:"gas_adjustment"`
|
|
||||||
GenerateOnly bool `json:"generate_only"`
|
|
||||||
Simulate bool `json:"simulate"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBaseReq creates a new basic request instance and sanitizes its values
|
|
||||||
func NewBaseReq(
|
|
||||||
name, password, memo, chainID string, gas, gasAdjustment string,
|
|
||||||
accNumber, seq uint64, fees sdk.Coins, gasPrices sdk.DecCoins, genOnly, simulate bool,
|
|
||||||
) BaseReq {
|
|
||||||
|
|
||||||
return BaseReq{
|
|
||||||
Name: strings.TrimSpace(name),
|
|
||||||
Password: password,
|
|
||||||
Memo: strings.TrimSpace(memo),
|
|
||||||
ChainID: strings.TrimSpace(chainID),
|
|
||||||
Fees: fees,
|
|
||||||
GasPrices: gasPrices,
|
|
||||||
Gas: strings.TrimSpace(gas),
|
|
||||||
GasAdjustment: strings.TrimSpace(gasAdjustment),
|
|
||||||
AccountNumber: accNumber,
|
|
||||||
Sequence: seq,
|
|
||||||
GenerateOnly: genOnly,
|
|
||||||
Simulate: simulate,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanitize performs basic sanitization on a BaseReq object.
|
|
||||||
func (br BaseReq) Sanitize() BaseReq {
|
|
||||||
return NewBaseReq(
|
|
||||||
br.Name, br.Password, br.Memo, br.ChainID, br.Gas, br.GasAdjustment,
|
|
||||||
br.AccountNumber, br.Sequence, br.Fees, br.GasPrices, br.GenerateOnly, br.Simulate,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateBasic performs basic validation of a BaseReq. If custom validation
|
|
||||||
// logic is needed, the implementing request handler should perform those
|
|
||||||
// checks manually.
|
|
||||||
func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool {
|
|
||||||
if !br.GenerateOnly && !br.Simulate {
|
|
||||||
switch {
|
|
||||||
case len(br.Password) == 0:
|
|
||||||
WriteErrorResponse(w, http.StatusUnauthorized, "password required but not specified")
|
|
||||||
return false
|
|
||||||
|
|
||||||
case len(br.ChainID) == 0:
|
|
||||||
WriteErrorResponse(w, http.StatusUnauthorized, "chain-id required but not specified")
|
|
||||||
return false
|
|
||||||
|
|
||||||
case !br.Fees.IsZero() && !br.GasPrices.IsZero():
|
|
||||||
// both fees and gas prices were provided
|
|
||||||
WriteErrorResponse(w, http.StatusBadRequest, "cannot provide both fees and gas prices")
|
|
||||||
return false
|
|
||||||
|
|
||||||
case !br.Fees.IsValid() && !br.GasPrices.IsValid():
|
|
||||||
// neither fees or gas prices were provided
|
|
||||||
WriteErrorResponse(w, http.StatusPaymentRequired, "invalid fees or gas prices provided")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(br.Name) == 0 {
|
|
||||||
WriteErrorResponse(w, http.StatusUnauthorized, "name required but not specified")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
ReadRESTReq is a simple convenience wrapper that reads the body and
|
|
||||||
unmarshals to the req interface.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
type SomeReq struct {
|
|
||||||
BaseReq `json:"base_req"`
|
|
||||||
CustomField string `json:"custom_field"`
|
|
||||||
}
|
|
||||||
|
|
||||||
req := new(SomeReq)
|
|
||||||
err := ReadRESTReq(w, r, cdc, req)
|
|
||||||
*/
|
|
||||||
func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req interface{}) error {
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cdc.UnmarshalJSON(body, req)
|
|
||||||
if err != nil {
|
|
||||||
WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to decode JSON payload: %s", err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompleteAndBroadcastTxREST implements a utility function that facilitates
|
|
||||||
// sending a series of messages in a signed transaction given a TxBuilder and a
|
|
||||||
// QueryContext. It ensures that the account exists, has a proper number and
|
|
||||||
// sequence set. In addition, it builds and signs a transaction with the
|
|
||||||
// supplied messages. Finally, it broadcasts the signed transaction to a node.
|
|
||||||
//
|
|
||||||
// NOTE: Also see CompleteAndBroadcastTxCli.
|
|
||||||
// NOTE: Also see x/stake/client/rest/tx.go delegationsRequestHandlerFn.
|
|
||||||
func CompleteAndBroadcastTxREST(
|
|
||||||
w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext,
|
|
||||||
baseReq BaseReq, msgs []sdk.Msg, cdc *codec.Codec,
|
|
||||||
) {
|
|
||||||
|
|
||||||
gasAdjustment, ok := ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
simulateAndExecute, gas, err := client.ParseGas(baseReq.Gas)
|
|
||||||
if err != nil {
|
|
||||||
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
txBldr := authtxb.NewTxBuilder(
|
|
||||||
GetTxEncoder(cdc), baseReq.AccountNumber,
|
|
||||||
baseReq.Sequence, gas, gasAdjustment, baseReq.Simulate,
|
|
||||||
baseReq.ChainID, baseReq.Memo, baseReq.Fees, baseReq.GasPrices,
|
|
||||||
)
|
|
||||||
|
|
||||||
if baseReq.Simulate || simulateAndExecute {
|
|
||||||
if gasAdjustment < 0 {
|
|
||||||
WriteErrorResponse(w, http.StatusBadRequest, "gas adjustment must be a positive float")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, msgs)
|
|
||||||
if err != nil {
|
|
||||||
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if baseReq.Simulate {
|
|
||||||
WriteSimulationResponse(w, txBldr.GetGas())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if baseReq.GenerateOnly {
|
|
||||||
WriteGenerateStdTxResponse(w, cdc, txBldr, msgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
txBytes, err := txBldr.BuildAndSign(baseReq.Name, baseReq.Password, msgs)
|
|
||||||
if keyerror.IsErrKeyNotFound(err) {
|
|
||||||
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
} else if keyerror.IsErrWrongPassword(err) {
|
|
||||||
WriteErrorResponse(w, http.StatusUnauthorized, err.Error())
|
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := cliCtx.BroadcastTx(txBytes)
|
|
||||||
if err != nil {
|
|
||||||
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
PostProcessResponse(w, cdc, res, cliCtx.Indent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostProcessResponse performs post process for rest response
|
|
||||||
func PostProcessResponse(w http.ResponseWriter, cdc *codec.Codec, response interface{}, indent bool) {
|
|
||||||
var output []byte
|
|
||||||
switch response.(type) {
|
|
||||||
default:
|
|
||||||
var err error
|
|
||||||
if indent {
|
|
||||||
output, err = cdc.MarshalJSONIndent(response, "", " ")
|
|
||||||
} else {
|
|
||||||
output, err = cdc.MarshalJSON(response)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case []byte:
|
|
||||||
output = response.([]byte)
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.Write(output)
|
|
||||||
}
|
|
|
@ -3,9 +3,9 @@ package utils
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
|
||||||
"github.com/tendermint/go-amino"
|
"github.com/tendermint/go-amino"
|
||||||
|
@ -18,55 +18,73 @@ import (
|
||||||
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompleteAndBroadcastTxCli implements a utility function that facilitates
|
// GasEstimateResponse defines a response definition for tx gas estimation.
|
||||||
|
type GasEstimateResponse struct {
|
||||||
|
GasEstimate uint64 `json:"gas_estimate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gr GasEstimateResponse) String() string {
|
||||||
|
return fmt.Sprintf("gas estimate: %d", gr.GasEstimate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateOrBroadcastMsgs respects CLI flags and outputs a message
|
||||||
|
func GenerateOrBroadcastMsgs(cliCtx context.CLIContext, txBldr authtxb.TxBuilder, msgs []sdk.Msg, offline bool) error {
|
||||||
|
if cliCtx.GenerateOnly {
|
||||||
|
return PrintUnsignedStdTx(txBldr, cliCtx, msgs, offline)
|
||||||
|
}
|
||||||
|
return CompleteAndBroadcastTxCLI(txBldr, cliCtx, msgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompleteAndBroadcastTxCLI implements a utility function that facilitates
|
||||||
// sending a series of messages in a signed transaction given a TxBuilder and a
|
// sending a series of messages in a signed transaction given a TxBuilder and a
|
||||||
// QueryContext. It ensures that the account exists, has a proper number and
|
// QueryContext. It ensures that the account exists, has a proper number and
|
||||||
// sequence set. In addition, it builds and signs a transaction with the
|
// sequence set. In addition, it builds and signs a transaction with the
|
||||||
// supplied messages. Finally, it broadcasts the signed transaction to a node.
|
// supplied messages. Finally, it broadcasts the signed transaction to a node.
|
||||||
//
|
//
|
||||||
// NOTE: Also see CompleteAndBroadcastTxREST.
|
// NOTE: Also see CompleteAndBroadcastTxREST.
|
||||||
func CompleteAndBroadcastTxCli(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error {
|
func CompleteAndBroadcastTxCLI(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error {
|
||||||
txBldr, err := prepareTxBuilder(txBldr, cliCtx)
|
txBldr, err := PrepareTxBuilder(txBldr, cliCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
name, err := cliCtx.GetFromName()
|
fromName := cliCtx.GetFromName()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if txBldr.GetSimulateAndExecute() || cliCtx.Simulate {
|
if txBldr.SimulateAndExecute() || cliCtx.Simulate {
|
||||||
txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs)
|
txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.GetGas())
|
|
||||||
|
gasEst := GasEstimateResponse{GasEstimate: txBldr.Gas()}
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", gasEst.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if cliCtx.Simulate {
|
if cliCtx.Simulate {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
passphrase, err := keys.GetPassphrase(name)
|
passphrase, err := keys.GetPassphrase(fromName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// build and sign the transaction
|
// build and sign the transaction
|
||||||
txBytes, err := txBldr.BuildAndSign(name, passphrase, msgs)
|
txBytes, err := txBldr.BuildAndSign(fromName, passphrase, msgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// broadcast to a Tendermint node
|
// broadcast to a Tendermint node
|
||||||
_, err = cliCtx.BroadcastTx(txBytes)
|
res, err := cliCtx.BroadcastTx(txBytes)
|
||||||
|
cliCtx.PrintOutput(res)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnrichCtxWithGas calculates the gas estimate that would be consumed by the
|
// EnrichWithGas calculates the gas estimate that would be consumed by the
|
||||||
// transaction and set the transaction's respective value accordingly.
|
// transaction and set the transaction's respective value accordingly.
|
||||||
func EnrichCtxWithGas(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, msgs []sdk.Msg) (authtxb.TxBuilder, error) {
|
func EnrichWithGas(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (authtxb.TxBuilder, error) {
|
||||||
_, adjusted, err := simulateMsgs(txBldr, cliCtx, name, msgs)
|
_, adjusted, err := simulateMsgs(txBldr, cliCtx, msgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return txBldr, err
|
return txBldr, err
|
||||||
}
|
}
|
||||||
|
@ -92,7 +110,7 @@ func CalculateGas(queryFunc func(string, common.HexBytes) ([]byte, error), cdc *
|
||||||
|
|
||||||
// PrintUnsignedStdTx builds an unsigned StdTx and prints it to os.Stdout.
|
// PrintUnsignedStdTx builds an unsigned StdTx and prints it to os.Stdout.
|
||||||
// Don't perform online validation or lookups if offline is true.
|
// Don't perform online validation or lookups if offline is true.
|
||||||
func PrintUnsignedStdTx(w io.Writer, txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg, offline bool) (err error) {
|
func PrintUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg, offline bool) (err error) {
|
||||||
var stdTx auth.StdTx
|
var stdTx auth.StdTx
|
||||||
if offline {
|
if offline {
|
||||||
stdTx, err = buildUnsignedStdTxOffline(txBldr, cliCtx, msgs)
|
stdTx, err = buildUnsignedStdTxOffline(txBldr, cliCtx, msgs)
|
||||||
|
@ -104,7 +122,7 @@ func PrintUnsignedStdTx(w io.Writer, txBldr authtxb.TxBuilder, cliCtx context.CL
|
||||||
}
|
}
|
||||||
json, err := cliCtx.Codec.MarshalJSON(stdTx)
|
json, err := cliCtx.Codec.MarshalJSON(stdTx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Fprintf(w, "%s\n", json)
|
fmt.Fprintf(cliCtx.Output, "%s\n", json)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -115,10 +133,7 @@ func PrintUnsignedStdTx(w io.Writer, txBldr authtxb.TxBuilder, cliCtx context.CL
|
||||||
func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, stdTx auth.StdTx, appendSig bool, offline bool) (auth.StdTx, error) {
|
func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, stdTx auth.StdTx, appendSig bool, offline bool) (auth.StdTx, error) {
|
||||||
var signedStdTx auth.StdTx
|
var signedStdTx auth.StdTx
|
||||||
|
|
||||||
keybase, err := keys.GetKeyBase()
|
keybase := txBldr.Keybase()
|
||||||
if err != nil {
|
|
||||||
return signedStdTx, err
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := keybase.Get(name)
|
info, err := keybase.Get(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -129,8 +144,7 @@ func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string,
|
||||||
|
|
||||||
// check whether the address is a signer
|
// check whether the address is a signer
|
||||||
if !isTxSigner(sdk.AccAddress(addr), stdTx.GetSigners()) {
|
if !isTxSigner(sdk.AccAddress(addr), stdTx.GetSigners()) {
|
||||||
return signedStdTx, fmt.Errorf(
|
return signedStdTx, fmt.Errorf("%s: %s", client.ErrInvalidSigner, name)
|
||||||
"The generated transaction's intended signer does not match the given signer: %q", name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !offline {
|
if !offline {
|
||||||
|
@ -158,8 +172,7 @@ func SignStdTxWithSignerAddress(txBldr authtxb.TxBuilder, cliCtx context.CLICont
|
||||||
|
|
||||||
// check whether the address is a signer
|
// check whether the address is a signer
|
||||||
if !isTxSigner(addr, stdTx.GetSigners()) {
|
if !isTxSigner(addr, stdTx.GetSigners()) {
|
||||||
return signedStdTx, fmt.Errorf(
|
return signedStdTx, fmt.Errorf("%s: %s", client.ErrInvalidSigner, name)
|
||||||
"The generated transaction's intended signer does not match the given signer: %q", name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !offline {
|
if !offline {
|
||||||
|
@ -179,7 +192,7 @@ func SignStdTxWithSignerAddress(txBldr authtxb.TxBuilder, cliCtx context.CLICont
|
||||||
|
|
||||||
func populateAccountFromState(txBldr authtxb.TxBuilder, cliCtx context.CLIContext,
|
func populateAccountFromState(txBldr authtxb.TxBuilder, cliCtx context.CLIContext,
|
||||||
addr sdk.AccAddress) (authtxb.TxBuilder, error) {
|
addr sdk.AccAddress) (authtxb.TxBuilder, error) {
|
||||||
if txBldr.GetAccountNumber() == 0 {
|
if txBldr.AccountNumber() == 0 {
|
||||||
accNum, err := cliCtx.GetAccountNumber(addr)
|
accNum, err := cliCtx.GetAccountNumber(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return txBldr, err
|
return txBldr, err
|
||||||
|
@ -187,7 +200,7 @@ func populateAccountFromState(txBldr authtxb.TxBuilder, cliCtx context.CLIContex
|
||||||
txBldr = txBldr.WithAccountNumber(accNum)
|
txBldr = txBldr.WithAccountNumber(accNum)
|
||||||
}
|
}
|
||||||
|
|
||||||
if txBldr.GetSequence() == 0 {
|
if txBldr.Sequence() == 0 {
|
||||||
accSeq, err := cliCtx.GetAccountSequence(addr)
|
accSeq, err := cliCtx.GetAccountSequence(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return txBldr, err
|
return txBldr, err
|
||||||
|
@ -210,12 +223,12 @@ func GetTxEncoder(cdc *codec.Codec) (encoder sdk.TxEncoder) {
|
||||||
|
|
||||||
// nolint
|
// nolint
|
||||||
// SimulateMsgs simulates the transaction and returns the gas estimate and the adjusted value.
|
// SimulateMsgs simulates the transaction and returns the gas estimate and the adjusted value.
|
||||||
func simulateMsgs(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, msgs []sdk.Msg) (estimated, adjusted uint64, err error) {
|
func simulateMsgs(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (estimated, adjusted uint64, err error) {
|
||||||
txBytes, err := txBldr.BuildWithPubKey(name, msgs)
|
txBytes, err := txBldr.BuildTxForSim(msgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, txBldr.GetGasAdjustment())
|
estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, txBldr.GasAdjustment())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,19 +244,17 @@ func parseQueryResponse(cdc *amino.Codec, rawRes []byte) (uint64, error) {
|
||||||
return simulationResult.GasUsed, nil
|
return simulationResult.GasUsed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (authtxb.TxBuilder, error) {
|
// PrepareTxBuilder populates a TxBuilder in preparation for the build of a Tx.
|
||||||
|
func PrepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (authtxb.TxBuilder, error) {
|
||||||
if err := cliCtx.EnsureAccountExists(); err != nil {
|
if err := cliCtx.EnsureAccountExists(); err != nil {
|
||||||
return txBldr, err
|
return txBldr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
from, err := cliCtx.GetFromAddress()
|
from := cliCtx.GetFromAddress()
|
||||||
if err != nil {
|
|
||||||
return txBldr, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: (ref #1903) Allow for user supplied account number without
|
// TODO: (ref #1903) Allow for user supplied account number without
|
||||||
// automatically doing a manual lookup.
|
// automatically doing a manual lookup.
|
||||||
if txBldr.GetAccountNumber() == 0 {
|
if txBldr.AccountNumber() == 0 {
|
||||||
accNum, err := cliCtx.GetAccountNumber(from)
|
accNum, err := cliCtx.GetAccountNumber(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return txBldr, err
|
return txBldr, err
|
||||||
|
@ -253,7 +264,7 @@ func prepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (auth
|
||||||
|
|
||||||
// TODO: (ref #1903) Allow for user supplied account sequence without
|
// TODO: (ref #1903) Allow for user supplied account sequence without
|
||||||
// automatically doing a manual lookup.
|
// automatically doing a manual lookup.
|
||||||
if txBldr.GetSequence() == 0 {
|
if txBldr.Sequence() == 0 {
|
||||||
accSeq, err := cliCtx.GetAccountSequence(from)
|
accSeq, err := cliCtx.GetAccountSequence(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return txBldr, err
|
return txBldr, err
|
||||||
|
@ -266,7 +277,7 @@ func prepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (auth
|
||||||
// buildUnsignedStdTx builds a StdTx as per the parameters passed in the
|
// buildUnsignedStdTx builds a StdTx as per the parameters passed in the
|
||||||
// contexts. Gas is automatically estimated if gas wanted is set to 0.
|
// contexts. Gas is automatically estimated if gas wanted is set to 0.
|
||||||
func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) {
|
func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) {
|
||||||
txBldr, err = prepareTxBuilder(txBldr, cliCtx)
|
txBldr, err = PrepareTxBuilder(txBldr, cliCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -274,23 +285,20 @@ func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msg
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildUnsignedStdTxOffline(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) {
|
func buildUnsignedStdTxOffline(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) {
|
||||||
if txBldr.GetSimulateAndExecute() {
|
if txBldr.SimulateAndExecute() {
|
||||||
var name string
|
txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs)
|
||||||
name, err = cliCtx.GetFromName()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs)
|
fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.Gas())
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.GetGas())
|
|
||||||
}
|
}
|
||||||
stdSignMsg, err := txBldr.Build(msgs)
|
|
||||||
|
stdSignMsg, err := txBldr.BuildSignMsg(msgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return auth.NewStdTx(stdSignMsg.Msgs, stdSignMsg.Fee, nil, stdSignMsg.Memo), nil
|
return auth.NewStdTx(stdSignMsg.Msgs, stdSignMsg.Fee, nil, stdSignMsg.Memo), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,5 +308,6 @@ func isTxSigner(user sdk.AccAddress, signers []sdk.AccAddress) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,11 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b
|
||||||
)
|
)
|
||||||
|
|
||||||
// add handlers
|
// add handlers
|
||||||
app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper)
|
app.bankKeeper = bank.NewBaseKeeper(
|
||||||
|
app.accountKeeper,
|
||||||
|
app.paramsKeeper.Subspace(bank.DefaultParamspace),
|
||||||
|
bank.DefaultCodespace,
|
||||||
|
)
|
||||||
app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(
|
app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(
|
||||||
app.cdc,
|
app.cdc,
|
||||||
app.keyFeeCollection,
|
app.keyFeeCollection,
|
||||||
|
@ -160,11 +164,12 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b
|
||||||
|
|
||||||
// initialize BaseApp
|
// initialize BaseApp
|
||||||
app.MountStores(app.keyMain, app.keyAccount, app.keyStaking, app.keyMint, app.keyDistr,
|
app.MountStores(app.keyMain, app.keyAccount, app.keyStaking, app.keyMint, app.keyDistr,
|
||||||
app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams)
|
app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams,
|
||||||
|
app.tkeyParams, app.tkeyStaking, app.tkeyDistr,
|
||||||
|
)
|
||||||
app.SetInitChainer(app.initChainer)
|
app.SetInitChainer(app.initChainer)
|
||||||
app.SetBeginBlocker(app.BeginBlocker)
|
app.SetBeginBlocker(app.BeginBlocker)
|
||||||
app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper))
|
app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper))
|
||||||
app.MountStoresTransient(app.tkeyParams, app.tkeyStaking, app.tkeyDistr)
|
|
||||||
app.SetEndBlocker(app.EndBlocker)
|
app.SetEndBlocker(app.EndBlocker)
|
||||||
|
|
||||||
if loadLatest {
|
if loadLatest {
|
||||||
|
@ -228,10 +233,7 @@ func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.R
|
||||||
|
|
||||||
// initialize store from a genesis state
|
// initialize store from a genesis state
|
||||||
func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisState) []abci.ValidatorUpdate {
|
func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisState) []abci.ValidatorUpdate {
|
||||||
// sort by account number to maintain consistency
|
genesisState.Sanitize()
|
||||||
sort.Slice(genesisState.Accounts, func(i, j int) bool {
|
|
||||||
return genesisState.Accounts[i].AccountNumber < genesisState.Accounts[j].AccountNumber
|
|
||||||
})
|
|
||||||
|
|
||||||
// load the accounts
|
// load the accounts
|
||||||
for _, gacc := range genesisState.Accounts {
|
for _, gacc := range genesisState.Accounts {
|
||||||
|
@ -251,13 +253,13 @@ func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisSt
|
||||||
|
|
||||||
// initialize module-specific stores
|
// initialize module-specific stores
|
||||||
auth.InitGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper, genesisState.AuthData)
|
auth.InitGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper, genesisState.AuthData)
|
||||||
|
bank.InitGenesis(ctx, app.bankKeeper, genesisState.BankData)
|
||||||
slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakingData)
|
slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakingData)
|
||||||
gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData)
|
gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData)
|
||||||
mint.InitGenesis(ctx, app.mintKeeper, genesisState.MintData)
|
mint.InitGenesis(ctx, app.mintKeeper, genesisState.MintData)
|
||||||
|
|
||||||
// validate genesis state
|
// validate genesis state
|
||||||
err = GaiaValidateGenesisState(genesisState)
|
if err := GaiaValidateGenesisState(genesisState); err != nil {
|
||||||
if err != nil {
|
|
||||||
panic(err) // TODO find a way to do this w/o panics
|
panic(err) // TODO find a way to do this w/o panics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,10 +356,6 @@ func (h StakingHooks) AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAdd
|
||||||
h.dh.AfterValidatorBonded(ctx, consAddr, valAddr)
|
h.dh.AfterValidatorBonded(ctx, consAddr, valAddr)
|
||||||
h.sh.AfterValidatorBonded(ctx, consAddr, valAddr)
|
h.sh.AfterValidatorBonded(ctx, consAddr, valAddr)
|
||||||
}
|
}
|
||||||
func (h StakingHooks) AfterValidatorPowerDidChange(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) {
|
|
||||||
h.dh.AfterValidatorPowerDidChange(ctx, consAddr, valAddr)
|
|
||||||
h.sh.AfterValidatorPowerDidChange(ctx, consAddr, valAddr)
|
|
||||||
}
|
|
||||||
func (h StakingHooks) AfterValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) {
|
func (h StakingHooks) AfterValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) {
|
||||||
h.dh.AfterValidatorBeginUnbonding(ctx, consAddr, valAddr)
|
h.dh.AfterValidatorBeginUnbonding(ctx, consAddr, valAddr)
|
||||||
h.sh.AfterValidatorBeginUnbonding(ctx, consAddr, valAddr)
|
h.sh.AfterValidatorBeginUnbonding(ctx, consAddr, valAddr)
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/tendermint/tendermint/libs/db"
|
"github.com/tendermint/tendermint/libs/db"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
@ -28,6 +30,7 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error {
|
||||||
genesisState := NewGenesisState(
|
genesisState := NewGenesisState(
|
||||||
genaccs,
|
genaccs,
|
||||||
auth.DefaultGenesisState(),
|
auth.DefaultGenesisState(),
|
||||||
|
bank.DefaultGenesisState(),
|
||||||
staking.DefaultGenesisState(),
|
staking.DefaultGenesisState(),
|
||||||
mint.DefaultGenesisState(),
|
mint.DefaultGenesisState(),
|
||||||
distr.DefaultGenesisState(),
|
distr.DefaultGenesisState(),
|
||||||
|
@ -55,6 +58,6 @@ func TestGaiadExport(t *testing.T) {
|
||||||
|
|
||||||
// Making a new app object with the db, so that initchain hasn't been called
|
// Making a new app object with the db, so that initchain hasn't been called
|
||||||
newGapp := NewGaiaApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true)
|
newGapp := NewGaiaApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true)
|
||||||
_, _, err := newGapp.ExportAppStateAndValidators(false)
|
_, _, err := newGapp.ExportAppStateAndValidators(false, []string{})
|
||||||
require.NoError(t, err, "ExportAppStateAndValidators should not have an error")
|
require.NoError(t, err, "ExportAppStateAndValidators should not have an error")
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ func ExampleTxSendSize() {
|
||||||
priv2 := secp256k1.GenPrivKeySecp256k1([]byte{1})
|
priv2 := secp256k1.GenPrivKeySecp256k1([]byte{1})
|
||||||
addr2 := sdk.AccAddress(priv2.PubKey().Address())
|
addr2 := sdk.AccAddress(priv2.PubKey().Address())
|
||||||
coins := sdk.Coins{sdk.NewCoin("denom", sdk.NewInt(10))}
|
coins := sdk.Coins{sdk.NewCoin("denom", sdk.NewInt(10))}
|
||||||
msg1 := bank.MsgSend{
|
msg1 := bank.MsgMultiSend{
|
||||||
Inputs: []bank.Input{bank.NewInput(addr1, coins)},
|
Inputs: []bank.Input{bank.NewInput(addr1, coins)},
|
||||||
Outputs: []bank.Output{bank.NewOutput(addr2, coins)},
|
Outputs: []bank.Output{bank.NewOutput(addr2, coins)},
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
@ -9,6 +10,7 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||||
distr "github.com/cosmos/cosmos-sdk/x/distribution"
|
distr "github.com/cosmos/cosmos-sdk/x/distribution"
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||||
"github.com/cosmos/cosmos-sdk/x/mint"
|
"github.com/cosmos/cosmos-sdk/x/mint"
|
||||||
|
@ -17,14 +19,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// export the state of gaia for a genesis file
|
// export the state of gaia for a genesis file
|
||||||
func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (
|
func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList []string) (
|
||||||
appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) {
|
appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) {
|
||||||
|
|
||||||
// as if they could withdraw from the start of the next block
|
// as if they could withdraw from the start of the next block
|
||||||
ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()})
|
ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()})
|
||||||
|
|
||||||
if forZeroHeight {
|
if forZeroHeight {
|
||||||
app.prepForZeroHeightGenesis(ctx)
|
app.prepForZeroHeightGenesis(ctx, jailWhiteList)
|
||||||
}
|
}
|
||||||
|
|
||||||
// iterate to get the accounts
|
// iterate to get the accounts
|
||||||
|
@ -39,6 +41,7 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (
|
||||||
genState := NewGenesisState(
|
genState := NewGenesisState(
|
||||||
accounts,
|
accounts,
|
||||||
auth.ExportGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper),
|
auth.ExportGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper),
|
||||||
|
bank.ExportGenesis(ctx, app.bankKeeper),
|
||||||
staking.ExportGenesis(ctx, app.stakingKeeper),
|
staking.ExportGenesis(ctx, app.stakingKeeper),
|
||||||
mint.ExportGenesis(ctx, app.mintKeeper),
|
mint.ExportGenesis(ctx, app.mintKeeper),
|
||||||
distr.ExportGenesis(ctx, app.distrKeeper),
|
distr.ExportGenesis(ctx, app.distrKeeper),
|
||||||
|
@ -54,7 +57,23 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare for fresh start at zero height
|
// prepare for fresh start at zero height
|
||||||
func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) {
|
func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string) {
|
||||||
|
applyWhiteList := false
|
||||||
|
|
||||||
|
//Check if there is a whitelist
|
||||||
|
if len(jailWhiteList) > 0 {
|
||||||
|
applyWhiteList = true
|
||||||
|
}
|
||||||
|
|
||||||
|
whiteListMap := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, addr := range jailWhiteList {
|
||||||
|
_, err := sdk.ValAddressFromBech32(addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
whiteListMap[addr] = true
|
||||||
|
}
|
||||||
|
|
||||||
/* Just to be safe, assert the invariants on current state. */
|
/* Just to be safe, assert the invariants on current state. */
|
||||||
app.assertRuntimeInvariantsOnContext(ctx)
|
app.assertRuntimeInvariantsOnContext(ctx)
|
||||||
|
@ -131,9 +150,11 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) {
|
||||||
panic("expected validator, not found")
|
panic("expected validator, not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
validator.BondHeight = 0
|
|
||||||
validator.UnbondingHeight = 0
|
validator.UnbondingHeight = 0
|
||||||
valConsAddrs = append(valConsAddrs, validator.ConsAddress())
|
valConsAddrs = append(valConsAddrs, validator.ConsAddress())
|
||||||
|
if applyWhiteList && !whiteListMap[addr.String()] {
|
||||||
|
validator.Jailed = true
|
||||||
|
}
|
||||||
|
|
||||||
app.stakingKeeper.SetValidator(ctx, validator)
|
app.stakingKeeper.SetValidator(ctx, validator)
|
||||||
counter++
|
counter++
|
||||||
|
@ -141,6 +162,8 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) {
|
||||||
|
|
||||||
iter.Close()
|
iter.Close()
|
||||||
|
|
||||||
|
_ = app.stakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx)
|
||||||
|
|
||||||
/* Handle slashing state. */
|
/* Handle slashing state. */
|
||||||
|
|
||||||
// reset start height on signing infos
|
// reset start height on signing infos
|
||||||
|
|
|
@ -9,6 +9,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||||
|
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
|
||||||
|
@ -20,20 +23,19 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/x/mint"
|
"github.com/cosmos/cosmos-sdk/x/mint"
|
||||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||||
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// bonded tokens given to genesis validators/accounts
|
// bonded tokens given to genesis validators/accounts
|
||||||
freeFermionVal = int64(100)
|
freeFermionsAcc = staking.TokensFromTendermintPower(150)
|
||||||
freeFermionsAcc = sdk.NewInt(150)
|
bondDenom = staking.DefaultBondDenom
|
||||||
bondDenom = stakingTypes.DefaultBondDenom
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// State to Unmarshal
|
// State to Unmarshal
|
||||||
type GenesisState struct {
|
type GenesisState struct {
|
||||||
Accounts []GenesisAccount `json:"accounts"`
|
Accounts []GenesisAccount `json:"accounts"`
|
||||||
AuthData auth.GenesisState `json:"auth"`
|
AuthData auth.GenesisState `json:"auth"`
|
||||||
|
BankData bank.GenesisState `json:"bank"`
|
||||||
StakingData staking.GenesisState `json:"staking"`
|
StakingData staking.GenesisState `json:"staking"`
|
||||||
MintData mint.GenesisState `json:"mint"`
|
MintData mint.GenesisState `json:"mint"`
|
||||||
DistrData distr.GenesisState `json:"distr"`
|
DistrData distr.GenesisState `json:"distr"`
|
||||||
|
@ -43,6 +45,7 @@ type GenesisState struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState,
|
func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState,
|
||||||
|
bankData bank.GenesisState,
|
||||||
stakingData staking.GenesisState, mintData mint.GenesisState,
|
stakingData staking.GenesisState, mintData mint.GenesisState,
|
||||||
distrData distr.GenesisState, govData gov.GenesisState,
|
distrData distr.GenesisState, govData gov.GenesisState,
|
||||||
slashingData slashing.GenesisState) GenesisState {
|
slashingData slashing.GenesisState) GenesisState {
|
||||||
|
@ -50,6 +53,7 @@ func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState,
|
||||||
return GenesisState{
|
return GenesisState{
|
||||||
Accounts: accounts,
|
Accounts: accounts,
|
||||||
AuthData: authData,
|
AuthData: authData,
|
||||||
|
BankData: bankData,
|
||||||
StakingData: stakingData,
|
StakingData: stakingData,
|
||||||
MintData: mintData,
|
MintData: mintData,
|
||||||
DistrData: distrData,
|
DistrData: distrData,
|
||||||
|
@ -58,6 +62,17 @@ func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sanitize sorts accounts and coin sets.
|
||||||
|
func (gs GenesisState) Sanitize() {
|
||||||
|
sort.Slice(gs.Accounts, func(i, j int) bool {
|
||||||
|
return gs.Accounts[i].AccountNumber < gs.Accounts[j].AccountNumber
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, acc := range gs.Accounts {
|
||||||
|
acc.Coins = acc.Coins.Sort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GenesisAccount defines an account initialized at genesis.
|
// GenesisAccount defines an account initialized at genesis.
|
||||||
type GenesisAccount struct {
|
type GenesisAccount struct {
|
||||||
Address sdk.AccAddress `json:"address"`
|
Address sdk.AccAddress `json:"address"`
|
||||||
|
@ -69,8 +84,8 @@ type GenesisAccount struct {
|
||||||
OriginalVesting sdk.Coins `json:"original_vesting"` // total vesting coins upon initialization
|
OriginalVesting sdk.Coins `json:"original_vesting"` // total vesting coins upon initialization
|
||||||
DelegatedFree sdk.Coins `json:"delegated_free"` // delegated vested coins at time of delegation
|
DelegatedFree sdk.Coins `json:"delegated_free"` // delegated vested coins at time of delegation
|
||||||
DelegatedVesting sdk.Coins `json:"delegated_vesting"` // delegated vesting coins at time of delegation
|
DelegatedVesting sdk.Coins `json:"delegated_vesting"` // delegated vesting coins at time of delegation
|
||||||
StartTime int64 `json:"start_time"` // vesting start time
|
StartTime int64 `json:"start_time"` // vesting start time (UNIX Epoch time)
|
||||||
EndTime int64 `json:"end_time"` // vesting end time
|
EndTime int64 `json:"end_time"` // vesting end time (UNIX Epoch time)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount {
|
func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount {
|
||||||
|
@ -190,6 +205,7 @@ func NewDefaultGenesisState() GenesisState {
|
||||||
return GenesisState{
|
return GenesisState{
|
||||||
Accounts: nil,
|
Accounts: nil,
|
||||||
AuthData: auth.DefaultGenesisState(),
|
AuthData: auth.DefaultGenesisState(),
|
||||||
|
BankData: bank.DefaultGenesisState(),
|
||||||
StakingData: staking.DefaultGenesisState(),
|
StakingData: staking.DefaultGenesisState(),
|
||||||
MintData: mint.DefaultGenesisState(),
|
MintData: mint.DefaultGenesisState(),
|
||||||
DistrData: distr.DefaultGenesisState(),
|
DistrData: distr.DefaultGenesisState(),
|
||||||
|
@ -216,6 +232,9 @@ func GaiaValidateGenesisState(genesisState GenesisState) error {
|
||||||
if err := auth.ValidateGenesis(genesisState.AuthData); err != nil {
|
if err := auth.ValidateGenesis(genesisState.AuthData); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := bank.ValidateGenesis(genesisState.BankData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := staking.ValidateGenesis(genesisState.StakingData); err != nil {
|
if err := staking.ValidateGenesis(genesisState.StakingData); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -232,17 +251,38 @@ func GaiaValidateGenesisState(genesisState GenesisState) error {
|
||||||
return slashing.ValidateGenesis(genesisState.SlashingData)
|
return slashing.ValidateGenesis(genesisState.SlashingData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensures that there are no duplicate accounts in the genesis state,
|
// validateGenesisStateAccounts performs validation of genesis accounts. It
|
||||||
|
// ensures that there are no duplicate accounts in the genesis state and any
|
||||||
|
// provided vesting accounts are valid.
|
||||||
func validateGenesisStateAccounts(accs []GenesisAccount) error {
|
func validateGenesisStateAccounts(accs []GenesisAccount) error {
|
||||||
addrMap := make(map[string]bool, len(accs))
|
addrMap := make(map[string]bool, len(accs))
|
||||||
for i := 0; i < len(accs); i++ {
|
for _, acc := range accs {
|
||||||
acc := accs[i]
|
addrStr := acc.Address.String()
|
||||||
strAddr := string(acc.Address)
|
|
||||||
if _, ok := addrMap[strAddr]; ok {
|
// disallow any duplicate accounts
|
||||||
return fmt.Errorf("Duplicate account in genesis state: Address %v", acc.Address)
|
if _, ok := addrMap[addrStr]; ok {
|
||||||
|
return fmt.Errorf("duplicate account found in genesis state; address: %s", addrStr)
|
||||||
}
|
}
|
||||||
addrMap[strAddr] = true
|
|
||||||
|
// validate any vesting fields
|
||||||
|
if !acc.OriginalVesting.IsZero() {
|
||||||
|
if acc.EndTime == 0 {
|
||||||
|
return fmt.Errorf("missing end time for vesting account; address: %s", addrStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if acc.StartTime >= acc.EndTime {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"vesting start time must before end time; address: %s, start: %s, end: %s",
|
||||||
|
addrStr,
|
||||||
|
time.Unix(acc.StartTime, 0).UTC().Format(time.RFC3339),
|
||||||
|
time.Unix(acc.EndTime, 0).UTC().Format(time.RFC3339),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addrMap[addrStr] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,17 +5,16 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||||
|
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||||
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||||
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -104,41 +103,86 @@ func TestGaiaAppGenState(t *testing.T) {
|
||||||
|
|
||||||
func makeMsg(name string, pk crypto.PubKey) auth.StdTx {
|
func makeMsg(name string, pk crypto.PubKey) auth.StdTx {
|
||||||
desc := staking.NewDescription(name, "", "", "")
|
desc := staking.NewDescription(name, "", "", "")
|
||||||
comm := stakingTypes.CommissionMsg{}
|
comm := staking.CommissionMsg{}
|
||||||
msg := staking.NewMsgCreateValidator(sdk.ValAddress(pk.Address()), pk, sdk.NewInt64Coin(bondDenom,
|
msg := staking.NewMsgCreateValidator(sdk.ValAddress(pk.Address()), pk, sdk.NewInt64Coin(bondDenom,
|
||||||
50), desc, comm)
|
50), desc, comm, sdk.OneInt())
|
||||||
return auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, nil, "")
|
return auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, nil, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGaiaGenesisValidation(t *testing.T) {
|
func TestGaiaGenesisValidation(t *testing.T) {
|
||||||
genTxs := make([]auth.StdTx, 2)
|
genTxs := []auth.StdTx{makeMsg("test-0", pk1), makeMsg("test-1", pk2)}
|
||||||
// Test duplicate accounts fails
|
dupGenTxs := []auth.StdTx{makeMsg("test-0", pk1), makeMsg("test-1", pk1)}
|
||||||
genTxs[0] = makeMsg("test-0", pk1)
|
|
||||||
genTxs[1] = makeMsg("test-1", pk1)
|
// require duplicate accounts fails validation
|
||||||
genesisState := makeGenesisState(t, genTxs)
|
genesisState := makeGenesisState(t, dupGenTxs)
|
||||||
err := GaiaValidateGenesisState(genesisState)
|
err := GaiaValidateGenesisState(genesisState)
|
||||||
require.NotNil(t, err)
|
require.Error(t, err)
|
||||||
// Test bonded + jailed validator fails
|
|
||||||
|
// require invalid vesting account fails validation (invalid end time)
|
||||||
genesisState = makeGenesisState(t, genTxs)
|
genesisState = makeGenesisState(t, genTxs)
|
||||||
val1 := stakingTypes.NewValidator(addr1, pk1, stakingTypes.Description{Moniker: "test #2"})
|
genesisState.Accounts[0].OriginalVesting = genesisState.Accounts[0].Coins
|
||||||
|
err = GaiaValidateGenesisState(genesisState)
|
||||||
|
require.Error(t, err)
|
||||||
|
genesisState.Accounts[0].StartTime = 1548888000
|
||||||
|
genesisState.Accounts[0].EndTime = 1548775410
|
||||||
|
err = GaiaValidateGenesisState(genesisState)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// require bonded + jailed validator fails validation
|
||||||
|
genesisState = makeGenesisState(t, genTxs)
|
||||||
|
val1 := staking.NewValidator(addr1, pk1, staking.NewDescription("test #2", "", "", ""))
|
||||||
val1.Jailed = true
|
val1.Jailed = true
|
||||||
val1.Status = sdk.Bonded
|
val1.Status = sdk.Bonded
|
||||||
genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1)
|
genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1)
|
||||||
err = GaiaValidateGenesisState(genesisState)
|
err = GaiaValidateGenesisState(genesisState)
|
||||||
require.NotNil(t, err)
|
require.Error(t, err)
|
||||||
// Test duplicate validator fails
|
|
||||||
|
// require duplicate validator fails validation
|
||||||
val1.Jailed = false
|
val1.Jailed = false
|
||||||
genesisState = makeGenesisState(t, genTxs)
|
genesisState = makeGenesisState(t, genTxs)
|
||||||
val2 := stakingTypes.NewValidator(addr1, pk1, stakingTypes.Description{Moniker: "test #3"})
|
val2 := staking.NewValidator(addr1, pk1, staking.NewDescription("test #3", "", "", ""))
|
||||||
genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1)
|
genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1)
|
||||||
genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val2)
|
genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val2)
|
||||||
err = GaiaValidateGenesisState(genesisState)
|
err = GaiaValidateGenesisState(genesisState)
|
||||||
require.NotNil(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewDefaultGenesisAccount(t *testing.T) {
|
func TestNewDefaultGenesisAccount(t *testing.T) {
|
||||||
addr := secp256k1.GenPrivKeySecp256k1([]byte("")).PubKey().Address()
|
addr := secp256k1.GenPrivKeySecp256k1([]byte("")).PubKey().Address()
|
||||||
acc := NewDefaultGenesisAccount(sdk.AccAddress(addr))
|
acc := NewDefaultGenesisAccount(sdk.AccAddress(addr))
|
||||||
require.Equal(t, sdk.NewInt(1000), acc.Coins.AmountOf("footoken"))
|
require.Equal(t, sdk.NewInt(1000), acc.Coins.AmountOf("footoken"))
|
||||||
require.Equal(t, sdk.NewInt(150), acc.Coins.AmountOf(bondDenom))
|
require.Equal(t, staking.TokensFromTendermintPower(150), acc.Coins.AmountOf(bondDenom))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenesisStateSanitize(t *testing.T) {
|
||||||
|
genesisState := makeGenesisState(t, nil)
|
||||||
|
require.Nil(t, GaiaValidateGenesisState(genesisState))
|
||||||
|
|
||||||
|
addr1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address())
|
||||||
|
authAcc1 := auth.NewBaseAccountWithAddress(addr1)
|
||||||
|
authAcc1.SetCoins(sdk.Coins{
|
||||||
|
sdk.NewInt64Coin("bcoin", 150),
|
||||||
|
sdk.NewInt64Coin("acoin", 150),
|
||||||
|
})
|
||||||
|
authAcc1.SetAccountNumber(1)
|
||||||
|
genAcc1 := NewGenesisAccount(&authAcc1)
|
||||||
|
|
||||||
|
addr2 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address())
|
||||||
|
authAcc2 := auth.NewBaseAccountWithAddress(addr2)
|
||||||
|
authAcc2.SetCoins(sdk.Coins{
|
||||||
|
sdk.NewInt64Coin("acoin", 150),
|
||||||
|
sdk.NewInt64Coin("bcoin", 150),
|
||||||
|
})
|
||||||
|
genAcc2 := NewGenesisAccount(&authAcc2)
|
||||||
|
|
||||||
|
genesisState.Accounts = []GenesisAccount{genAcc1, genAcc2}
|
||||||
|
require.True(t, genesisState.Accounts[0].AccountNumber > genesisState.Accounts[1].AccountNumber)
|
||||||
|
require.Equal(t, genesisState.Accounts[0].Coins[0].Denom, "bcoin")
|
||||||
|
require.Equal(t, genesisState.Accounts[0].Coins[1].Denom, "acoin")
|
||||||
|
require.Equal(t, genesisState.Accounts[1].Address, addr2)
|
||||||
|
genesisState.Sanitize()
|
||||||
|
require.False(t, genesisState.Accounts[0].AccountNumber > genesisState.Accounts[1].AccountNumber)
|
||||||
|
require.Equal(t, genesisState.Accounts[1].Address, addr1)
|
||||||
|
require.Equal(t, genesisState.Accounts[1].Coins[0].Denom, "acoin")
|
||||||
|
require.Equal(t, genesisState.Accounts[1].Coins[1].Denom, "bcoin")
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,5 +38,5 @@ func (app *GaiaApp) assertRuntimeInvariantsOnContext(ctx sdk.Context) {
|
||||||
}
|
}
|
||||||
end := time.Now()
|
end := time.Now()
|
||||||
diff := end.Sub(start)
|
diff := end.Sub(start)
|
||||||
app.BaseApp.Logger.With("module", "invariants").Info("Asserted all invariants", "duration", diff)
|
app.BaseApp.Logger().With("module", "invariants").Info("Asserted all invariants", "duration", diff)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,16 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||||
dbm "github.com/tendermint/tendermint/libs/db"
|
dbm "github.com/tendermint/tendermint/libs/db"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
authsim "github.com/cosmos/cosmos-sdk/x/auth/simulation"
|
authsim "github.com/cosmos/cosmos-sdk/x/auth/simulation"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||||
banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation"
|
banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation"
|
||||||
distr "github.com/cosmos/cosmos-sdk/x/distribution"
|
distr "github.com/cosmos/cosmos-sdk/x/distribution"
|
||||||
distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation"
|
distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation"
|
||||||
|
@ -31,20 +34,21 @@ import (
|
||||||
slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation"
|
slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation"
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||||
stakingsim "github.com/cosmos/cosmos-sdk/x/staking/simulation"
|
stakingsim "github.com/cosmos/cosmos-sdk/x/staking/simulation"
|
||||||
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
seed int64
|
genesisFile string
|
||||||
numBlocks int
|
seed int64
|
||||||
blockSize int
|
numBlocks int
|
||||||
enabled bool
|
blockSize int
|
||||||
verbose bool
|
enabled bool
|
||||||
commit bool
|
verbose bool
|
||||||
period int
|
commit bool
|
||||||
|
period int
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
flag.StringVar(&genesisFile, "SimulationGenesis", "", "Custom simulation genesis file")
|
||||||
flag.Int64Var(&seed, "SimulationSeed", 42, "Simulation random seed")
|
flag.Int64Var(&seed, "SimulationSeed", 42, "Simulation random seed")
|
||||||
flag.IntVar(&numBlocks, "SimulationNumBlocks", 500, "Number of blocks")
|
flag.IntVar(&numBlocks, "SimulationNumBlocks", 500, "Number of blocks")
|
||||||
flag.IntVar(&blockSize, "SimulationBlockSize", 200, "Operations per block")
|
flag.IntVar(&blockSize, "SimulationBlockSize", 200, "Operations per block")
|
||||||
|
@ -54,10 +58,34 @@ func init() {
|
||||||
flag.IntVar(&period, "SimulationPeriod", 1, "Run slow invariants only once every period assertions")
|
flag.IntVar(&period, "SimulationPeriod", 1, "Run slow invariants only once every period assertions")
|
||||||
}
|
}
|
||||||
|
|
||||||
func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) json.RawMessage {
|
func appStateFromGenesisFileFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) (json.RawMessage, []simulation.Account, string) {
|
||||||
|
var genesis tmtypes.GenesisDoc
|
||||||
|
cdc := MakeCodec()
|
||||||
|
bytes, err := ioutil.ReadFile(genesisFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
cdc.MustUnmarshalJSON(bytes, &genesis)
|
||||||
|
var appState GenesisState
|
||||||
|
cdc.MustUnmarshalJSON(genesis.AppState, &appState)
|
||||||
|
var newAccs []simulation.Account
|
||||||
|
for _, acc := range appState.Accounts {
|
||||||
|
// Pick a random private key, since we don't know the actual key
|
||||||
|
// This should be fine as it's only used for mock Tendermint validators
|
||||||
|
// and these keys are never actually used to sign by mock Tendermint.
|
||||||
|
privkeySeed := make([]byte, 15)
|
||||||
|
r.Read(privkeySeed)
|
||||||
|
privKey := secp256k1.GenPrivKeySecp256k1(privkeySeed)
|
||||||
|
newAccs = append(newAccs, simulation.Account{privKey, privKey.PubKey(), acc.Address})
|
||||||
|
}
|
||||||
|
return genesis.AppState, newAccs, genesis.ChainID
|
||||||
|
}
|
||||||
|
|
||||||
|
func appStateRandomizedFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) (json.RawMessage, []simulation.Account, string) {
|
||||||
|
|
||||||
var genesisAccounts []GenesisAccount
|
var genesisAccounts []GenesisAccount
|
||||||
|
|
||||||
amount := int64(r.Intn(1e6))
|
amount := int64(r.Intn(1e12))
|
||||||
numInitiallyBonded := int64(r.Intn(250))
|
numInitiallyBonded := int64(r.Intn(250))
|
||||||
numAccs := int64(len(accs))
|
numAccs := int64(len(accs))
|
||||||
if numInitiallyBonded > numAccs {
|
if numInitiallyBonded > numAccs {
|
||||||
|
@ -69,7 +97,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
|
||||||
|
|
||||||
// randomly generate some genesis accounts
|
// randomly generate some genesis accounts
|
||||||
for i, acc := range accs {
|
for i, acc := range accs {
|
||||||
coins := sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))}
|
coins := sdk.Coins{sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(amount))}
|
||||||
bacc := auth.NewBaseAccountWithAddress(acc.Address)
|
bacc := auth.NewBaseAccountWithAddress(acc.Address)
|
||||||
bacc.SetCoins(coins)
|
bacc.SetCoins(coins)
|
||||||
|
|
||||||
|
@ -109,21 +137,24 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
|
||||||
|
|
||||||
authGenesis := auth.GenesisState{
|
authGenesis := auth.GenesisState{
|
||||||
Params: auth.Params{
|
Params: auth.Params{
|
||||||
MemoCostPerByte: uint64(r.Intn(10) + 1),
|
MaxMemoCharacters: uint64(randIntBetween(r, 100, 200)),
|
||||||
MaxMemoCharacters: uint64(r.Intn(200-100) + 100),
|
|
||||||
TxSigLimit: uint64(r.Intn(7) + 1),
|
TxSigLimit: uint64(r.Intn(7) + 1),
|
||||||
SigVerifyCostED25519: uint64(r.Intn(1000-500) + 500),
|
TxSizeCostPerByte: uint64(randIntBetween(r, 5, 15)),
|
||||||
SigVerifyCostSecp256k1: uint64(r.Intn(1000-500) + 500),
|
SigVerifyCostED25519: uint64(randIntBetween(r, 500, 1000)),
|
||||||
|
SigVerifyCostSecp256k1: uint64(randIntBetween(r, 500, 1000)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fmt.Printf("Selected randomly generated auth parameters:\n\t%+v\n", authGenesis)
|
fmt.Printf("Selected randomly generated auth parameters:\n\t%+v\n", authGenesis)
|
||||||
|
|
||||||
|
bankGenesis := bank.NewGenesisState(r.Int63n(2) == 0)
|
||||||
|
fmt.Printf("Selected randomly generated bank parameters:\n\t%+v\n", bankGenesis)
|
||||||
|
|
||||||
// Random genesis states
|
// Random genesis states
|
||||||
vp := time.Duration(r.Intn(2*172800)) * time.Second
|
vp := time.Duration(r.Intn(2*172800)) * time.Second
|
||||||
govGenesis := gov.GenesisState{
|
govGenesis := gov.GenesisState{
|
||||||
StartingProposalID: uint64(r.Intn(100)),
|
StartingProposalID: uint64(r.Intn(100)),
|
||||||
DepositParams: gov.DepositParams{
|
DepositParams: gov.DepositParams{
|
||||||
MinDeposit: sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, int64(r.Intn(1e3)))},
|
MinDeposit: sdk.Coins{sdk.NewInt64Coin(staking.DefaultBondDenom, int64(r.Intn(1e3)))},
|
||||||
MaxDepositPeriod: vp,
|
MaxDepositPeriod: vp,
|
||||||
},
|
},
|
||||||
VotingParams: gov.VotingParams{
|
VotingParams: gov.VotingParams{
|
||||||
|
@ -142,7 +173,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
|
||||||
Params: staking.Params{
|
Params: staking.Params{
|
||||||
UnbondingTime: time.Duration(randIntBetween(r, 60, 60*60*24*3*2)) * time.Second,
|
UnbondingTime: time.Duration(randIntBetween(r, 60, 60*60*24*3*2)) * time.Second,
|
||||||
MaxValidators: uint16(r.Intn(250)),
|
MaxValidators: uint16(r.Intn(250)),
|
||||||
BondDenom: stakingTypes.DefaultBondDenom,
|
BondDenom: staking.DefaultBondDenom,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fmt.Printf("Selected randomly generated staking parameters:\n\t%+v\n", stakingGenesis)
|
fmt.Printf("Selected randomly generated staking parameters:\n\t%+v\n", stakingGenesis)
|
||||||
|
@ -163,7 +194,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
|
||||||
Minter: mint.InitialMinter(
|
Minter: mint.InitialMinter(
|
||||||
sdk.NewDecWithPrec(int64(r.Intn(99)), 2)),
|
sdk.NewDecWithPrec(int64(r.Intn(99)), 2)),
|
||||||
Params: mint.NewParams(
|
Params: mint.NewParams(
|
||||||
stakingTypes.DefaultBondDenom,
|
staking.DefaultBondDenom,
|
||||||
sdk.NewDecWithPrec(int64(r.Intn(99)), 2),
|
sdk.NewDecWithPrec(int64(r.Intn(99)), 2),
|
||||||
sdk.NewDecWithPrec(20, 2),
|
sdk.NewDecWithPrec(20, 2),
|
||||||
sdk.NewDecWithPrec(7, 2),
|
sdk.NewDecWithPrec(7, 2),
|
||||||
|
@ -203,6 +234,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
|
||||||
genesis := GenesisState{
|
genesis := GenesisState{
|
||||||
Accounts: genesisAccounts,
|
Accounts: genesisAccounts,
|
||||||
AuthData: authGenesis,
|
AuthData: authGenesis,
|
||||||
|
BankData: bankGenesis,
|
||||||
StakingData: stakingGenesis,
|
StakingData: stakingGenesis,
|
||||||
MintData: mintGenesis,
|
MintData: mintGenesis,
|
||||||
DistrData: distrGenesis,
|
DistrData: distrGenesis,
|
||||||
|
@ -216,7 +248,14 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return appState
|
return appState, accs, "simulation"
|
||||||
|
}
|
||||||
|
|
||||||
|
func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) (json.RawMessage, []simulation.Account, string) {
|
||||||
|
if genesisFile != "" {
|
||||||
|
return appStateFromGenesisFileFn(r, accs, genesisTimestamp)
|
||||||
|
}
|
||||||
|
return appStateRandomizedFn(r, accs, genesisTimestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func randIntBetween(r *rand.Rand, min, max int) int {
|
func randIntBetween(r *rand.Rand, min, max int) int {
|
||||||
|
@ -226,7 +265,8 @@ func randIntBetween(r *rand.Rand, min, max int) int {
|
||||||
func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation {
|
func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation {
|
||||||
return []simulation.WeightedOperation{
|
return []simulation.WeightedOperation{
|
||||||
{5, authsim.SimulateDeductFee(app.accountKeeper, app.feeCollectionKeeper)},
|
{5, authsim.SimulateDeductFee(app.accountKeeper, app.feeCollectionKeeper)},
|
||||||
{100, banksim.SingleInputSendMsg(app.accountKeeper, app.bankKeeper)},
|
{100, banksim.SendMsg(app.accountKeeper, app.bankKeeper)},
|
||||||
|
{10, banksim.SingleInputMsgMultiSend(app.accountKeeper, app.bankKeeper)},
|
||||||
{50, distrsim.SimulateMsgSetWithdrawAddress(app.accountKeeper, app.distrKeeper)},
|
{50, distrsim.SimulateMsgSetWithdrawAddress(app.accountKeeper, app.distrKeeper)},
|
||||||
{50, distrsim.SimulateMsgWithdrawDelegatorReward(app.accountKeeper, app.distrKeeper)},
|
{50, distrsim.SimulateMsgWithdrawDelegatorReward(app.accountKeeper, app.distrKeeper)},
|
||||||
{50, distrsim.SimulateMsgWithdrawValidatorCommission(app.accountKeeper, app.distrKeeper)},
|
{50, distrsim.SimulateMsgWithdrawValidatorCommission(app.accountKeeper, app.distrKeeper)},
|
||||||
|
@ -261,8 +301,8 @@ func fauxMerkleModeOpt(bapp *baseapp.BaseApp) {
|
||||||
// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$ -SimulationCommit=true -cpuprofile cpu.out
|
// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$ -SimulationCommit=true -cpuprofile cpu.out
|
||||||
func BenchmarkFullGaiaSimulation(b *testing.B) {
|
func BenchmarkFullGaiaSimulation(b *testing.B) {
|
||||||
// Setup Gaia application
|
// Setup Gaia application
|
||||||
var logger log.Logger
|
logger := log.NewNopLogger()
|
||||||
logger = log.NewNopLogger()
|
|
||||||
var db dbm.DB
|
var db dbm.DB
|
||||||
dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim")
|
dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim")
|
||||||
db, _ = dbm.NewGoLevelDB("Simulation", dir)
|
db, _ = dbm.NewGoLevelDB("Simulation", dir)
|
||||||
|
@ -376,11 +416,8 @@ func TestGaiaImportExport(t *testing.T) {
|
||||||
|
|
||||||
fmt.Printf("Exporting genesis...\n")
|
fmt.Printf("Exporting genesis...\n")
|
||||||
|
|
||||||
appState, _, err := app.ExportAppStateAndValidators(false)
|
appState, _, err := app.ExportAppStateAndValidators(false, []string{})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Importing genesis...\n")
|
fmt.Printf("Importing genesis...\n")
|
||||||
|
|
||||||
newDir, _ := ioutil.TempDir("", "goleveldb-gaia-sim-2")
|
newDir, _ := ioutil.TempDir("", "goleveldb-gaia-sim-2")
|
||||||
|
@ -480,7 +517,7 @@ func TestGaiaSimulationAfterImport(t *testing.T) {
|
||||||
|
|
||||||
fmt.Printf("Exporting genesis...\n")
|
fmt.Printf("Exporting genesis...\n")
|
||||||
|
|
||||||
appState, _, err := app.ExportAppStateAndValidators(true)
|
appState, _, err := app.ExportAppStateAndValidators(true, []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package clitest
|
package clitest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -14,15 +16,13 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||||
"github.com/cosmos/cosmos-sdk/tests"
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||||
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGaiaCLIKeysAddMultisig(t *testing.T) {
|
func TestGaiaCLIKeysAddMultisig(t *testing.T) {
|
||||||
|
@ -45,6 +45,31 @@ func TestGaiaCLIKeysAddMultisig(t *testing.T) {
|
||||||
require.NotEqual(t, f.KeysShow("msig3").Address, f.KeysShow("msig4").Address)
|
require.NotEqual(t, f.KeysShow("msig3").Address, f.KeysShow("msig4").Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGaiaCLIKeysAddRecover(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
f := InitFixtures(t)
|
||||||
|
|
||||||
|
f.KeysAddRecover("test-recover", "dentist task convince chimney quality leave banana trade firm crawl eternal easily")
|
||||||
|
require.Equal(t, "cosmos1qcfdf69js922qrdr4yaww3ax7gjml6pdds46f4", f.KeyAddress("test-recover").String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGaiaCLIKeysAddRecoverHDPath(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
f := InitFixtures(t)
|
||||||
|
|
||||||
|
f.KeysAddRecoverHDPath("test-recoverHD1", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 0, 0)
|
||||||
|
require.Equal(t, "cosmos1qcfdf69js922qrdr4yaww3ax7gjml6pdds46f4", f.KeyAddress("test-recoverHD1").String())
|
||||||
|
|
||||||
|
f.KeysAddRecoverHDPath("test-recoverH2", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 1, 5)
|
||||||
|
require.Equal(t, "cosmos1pdfav2cjhry9k79nu6r8kgknnjtq6a7rykmafy", f.KeyAddress("test-recoverH2").String())
|
||||||
|
|
||||||
|
f.KeysAddRecoverHDPath("test-recoverH3", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 1, 17)
|
||||||
|
require.Equal(t, "cosmos1909k354n6wl8ujzu6kmh49w4d02ax7qvlkv4sn", f.KeyAddress("test-recoverH3").String())
|
||||||
|
|
||||||
|
f.KeysAddRecoverHDPath("test-recoverH4", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 2, 17)
|
||||||
|
require.Equal(t, "cosmos1v9plmhvyhgxk3th9ydacm7j4z357s3nhtwsjat", f.KeyAddress("test-recoverH4").String())
|
||||||
|
}
|
||||||
|
|
||||||
func TestGaiaCLIMinimumFees(t *testing.T) {
|
func TestGaiaCLIMinimumFees(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
f := InitFixtures(t)
|
f := InitFixtures(t)
|
||||||
|
@ -67,13 +92,13 @@ func TestGaiaCLIMinimumFees(t *testing.T) {
|
||||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||||
|
|
||||||
// Ensure tx w/ correct fees pass
|
// Ensure tx w/ correct fees pass
|
||||||
txFees := fmt.Sprintf("--fees=%s,%s", sdk.NewInt64Coin(feeDenom, 2), sdk.NewInt64Coin(fee2Denom, 2))
|
txFees := fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2))
|
||||||
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10), txFees)
|
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10), txFees)
|
||||||
require.True(f.T, success)
|
require.True(f.T, success)
|
||||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||||
|
|
||||||
// Ensure tx w/ improper fees fails
|
// Ensure tx w/ improper fees fails
|
||||||
txFees = fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 5))
|
txFees = fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 1))
|
||||||
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 10), txFees)
|
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 10), txFees)
|
||||||
require.False(f.T, success)
|
require.False(f.T, success)
|
||||||
|
|
||||||
|
@ -144,8 +169,9 @@ func TestGaiaCLIFeesDeduction(t *testing.T) {
|
||||||
require.Equal(t, fooAmt.Int64(), fooAcc.GetCoins().AmountOf(fooDenom).Int64())
|
require.Equal(t, fooAmt.Int64(), fooAcc.GetCoins().AmountOf(fooDenom).Int64())
|
||||||
|
|
||||||
// insufficient funds (coins + fees) tx fails
|
// insufficient funds (coins + fees) tx fails
|
||||||
|
largeCoins := staking.TokensFromTendermintPower(10000000)
|
||||||
success, _, _ = f.TxSend(
|
success, _, _ = f.TxSend(
|
||||||
keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 10000000),
|
keyFoo, barAddr, sdk.NewCoin(fooDenom, largeCoins),
|
||||||
fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)))
|
fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)))
|
||||||
require.False(t, success)
|
require.False(t, success)
|
||||||
|
|
||||||
|
@ -178,45 +204,47 @@ func TestGaiaCLISend(t *testing.T) {
|
||||||
barAddr := f.KeyAddress(keyBar)
|
barAddr := f.KeyAddress(keyBar)
|
||||||
|
|
||||||
fooAcc := f.QueryAccount(fooAddr)
|
fooAcc := f.QueryAccount(fooAddr)
|
||||||
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64())
|
startTokens := staking.TokensFromTendermintPower(50)
|
||||||
|
require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom))
|
||||||
|
|
||||||
// Send some tokens from one account to the other
|
// Send some tokens from one account to the other
|
||||||
f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10))
|
sendTokens := staking.TokensFromTendermintPower(10)
|
||||||
|
f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens))
|
||||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||||
|
|
||||||
// Ensure account balances match expected
|
// Ensure account balances match expected
|
||||||
barAcc := f.QueryAccount(barAddr)
|
barAcc := f.QueryAccount(barAddr)
|
||||||
require.Equal(t, int64(10), barAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom))
|
||||||
fooAcc = f.QueryAccount(fooAddr)
|
fooAcc = f.QueryAccount(fooAddr)
|
||||||
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom))
|
||||||
|
|
||||||
// Test --dry-run
|
// Test --dry-run
|
||||||
success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--dry-run")
|
success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--dry-run")
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Check state didn't change
|
// Check state didn't change
|
||||||
fooAcc = f.QueryAccount(fooAddr)
|
fooAcc = f.QueryAccount(fooAddr)
|
||||||
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom))
|
||||||
|
|
||||||
// test autosequencing
|
// test autosequencing
|
||||||
f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10))
|
f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens))
|
||||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||||
|
|
||||||
// Ensure account balances match expected
|
// Ensure account balances match expected
|
||||||
barAcc = f.QueryAccount(barAddr)
|
barAcc = f.QueryAccount(barAddr)
|
||||||
require.Equal(t, int64(20), barAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, sendTokens.MulRaw(2), barAcc.GetCoins().AmountOf(denom))
|
||||||
fooAcc = f.QueryAccount(fooAddr)
|
fooAcc = f.QueryAccount(fooAddr)
|
||||||
require.Equal(t, int64(30), fooAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, startTokens.Sub(sendTokens.MulRaw(2)), fooAcc.GetCoins().AmountOf(denom))
|
||||||
|
|
||||||
// test memo
|
// test memo
|
||||||
f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--memo='testmemo'")
|
f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--memo='testmemo'")
|
||||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||||
|
|
||||||
// Ensure account balances match expected
|
// Ensure account balances match expected
|
||||||
barAcc = f.QueryAccount(barAddr)
|
barAcc = f.QueryAccount(barAddr)
|
||||||
require.Equal(t, int64(30), barAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, sendTokens.MulRaw(3), barAcc.GetCoins().AmountOf(denom))
|
||||||
fooAcc = f.QueryAccount(fooAddr)
|
fooAcc = f.QueryAccount(fooAddr)
|
||||||
require.Equal(t, int64(20), fooAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, startTokens.Sub(sendTokens.MulRaw(3)), fooAcc.GetCoins().AmountOf(denom))
|
||||||
|
|
||||||
f.Cleanup()
|
f.Cleanup()
|
||||||
}
|
}
|
||||||
|
@ -233,50 +261,48 @@ func TestGaiaCLIGasAuto(t *testing.T) {
|
||||||
barAddr := f.KeyAddress(keyBar)
|
barAddr := f.KeyAddress(keyBar)
|
||||||
|
|
||||||
fooAcc := f.QueryAccount(fooAddr)
|
fooAcc := f.QueryAccount(fooAddr)
|
||||||
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64())
|
startTokens := staking.TokensFromTendermintPower(50)
|
||||||
|
require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom))
|
||||||
|
|
||||||
// Test failure with auto gas disabled and very little gas set by hand
|
// Test failure with auto gas disabled and very little gas set by hand
|
||||||
success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=10")
|
sendTokens := staking.TokensFromTendermintPower(10)
|
||||||
|
success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=10")
|
||||||
require.False(t, success)
|
require.False(t, success)
|
||||||
|
|
||||||
// Check state didn't change
|
// Check state didn't change
|
||||||
fooAcc = f.QueryAccount(fooAddr)
|
fooAcc = f.QueryAccount(fooAddr)
|
||||||
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom))
|
||||||
|
|
||||||
// Test failure with negative gas
|
// Test failure with negative gas
|
||||||
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=-100")
|
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=-100")
|
||||||
require.False(t, success)
|
require.False(t, success)
|
||||||
|
|
||||||
// Check state didn't change
|
// Check state didn't change
|
||||||
fooAcc = f.QueryAccount(fooAddr)
|
fooAcc = f.QueryAccount(fooAddr)
|
||||||
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom))
|
||||||
|
|
||||||
// Test failure with 0 gas
|
// Test failure with 0 gas
|
||||||
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=0")
|
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=0")
|
||||||
require.False(t, success)
|
require.False(t, success)
|
||||||
|
|
||||||
// Check state didn't change
|
// Check state didn't change
|
||||||
fooAcc = f.QueryAccount(fooAddr)
|
fooAcc = f.QueryAccount(fooAddr)
|
||||||
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom))
|
||||||
|
|
||||||
// Enable auto gas
|
// Enable auto gas
|
||||||
success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=auto")
|
success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=auto")
|
||||||
require.NotEmpty(t, stderr)
|
require.NotEmpty(t, stderr)
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
cdc := app.MakeCodec()
|
cdc := app.MakeCodec()
|
||||||
sendResp := struct {
|
sendResp := sdk.TxResponse{}
|
||||||
Height int64
|
|
||||||
TxHash string
|
|
||||||
Response abci.ResponseDeliverTx
|
|
||||||
}{}
|
|
||||||
err := cdc.UnmarshalJSON([]byte(stdout), &sendResp)
|
err := cdc.UnmarshalJSON([]byte(stdout), &sendResp)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Equal(t, sendResp.Response.GasWanted, sendResp.Response.GasUsed)
|
require.True(t, sendResp.GasWanted >= sendResp.GasUsed)
|
||||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||||
|
|
||||||
// Check state has changed accordingly
|
// Check state has changed accordingly
|
||||||
fooAcc = f.QueryAccount(fooAddr)
|
fooAcc = f.QueryAccount(fooAddr)
|
||||||
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom))
|
||||||
|
|
||||||
f.Cleanup()
|
f.Cleanup()
|
||||||
}
|
}
|
||||||
|
@ -289,23 +315,17 @@ func TestGaiaCLICreateValidator(t *testing.T) {
|
||||||
proc := f.GDStart()
|
proc := f.GDStart()
|
||||||
defer proc.Stop(false)
|
defer proc.Stop(false)
|
||||||
|
|
||||||
fooAddr := f.KeyAddress(keyFoo)
|
|
||||||
barAddr := f.KeyAddress(keyBar)
|
barAddr := f.KeyAddress(keyBar)
|
||||||
barVal := sdk.ValAddress(barAddr)
|
barVal := sdk.ValAddress(barAddr)
|
||||||
|
|
||||||
consPubKey := sdk.MustBech32ifyConsPub(ed25519.GenPrivKey().PubKey())
|
consPubKey := sdk.MustBech32ifyConsPub(ed25519.GenPrivKey().PubKey())
|
||||||
|
|
||||||
f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10))
|
sendTokens := staking.TokensFromTendermintPower(10)
|
||||||
|
f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens))
|
||||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||||
|
|
||||||
barAcc := f.QueryAccount(barAddr)
|
barAcc := f.QueryAccount(barAddr)
|
||||||
require.Equal(t, int64(10), barAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom))
|
||||||
fooAcc := f.QueryAccount(fooAddr)
|
|
||||||
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64())
|
|
||||||
|
|
||||||
defaultParams := staking.DefaultParams()
|
|
||||||
initialPool := staking.InitialPool()
|
|
||||||
initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewInt(101)) // Delegate tx on GaiaAppGenState
|
|
||||||
|
|
||||||
// Generate a create validator transaction and ensure correctness
|
// Generate a create validator transaction and ensure correctness
|
||||||
success, stdout, stderr := f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewInt64Coin(denom, 2), "--generate-only")
|
success, stdout, stderr := f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewInt64Coin(denom, 2), "--generate-only")
|
||||||
|
@ -318,21 +338,22 @@ func TestGaiaCLICreateValidator(t *testing.T) {
|
||||||
require.Equal(t, 0, len(msg.GetSignatures()))
|
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||||
|
|
||||||
// Test --dry-run
|
// Test --dry-run
|
||||||
success, _, _ = f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewInt64Coin(denom, 2), "--dry-run")
|
newValTokens := staking.TokensFromTendermintPower(2)
|
||||||
|
success, _, _ = f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewCoin(denom, newValTokens), "--dry-run")
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Create the validator
|
// Create the validator
|
||||||
f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewInt64Coin(denom, 2))
|
f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewCoin(denom, newValTokens))
|
||||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||||
|
|
||||||
// Ensure funds were deducted properly
|
// Ensure funds were deducted properly
|
||||||
barAcc = f.QueryAccount(barAddr)
|
barAcc = f.QueryAccount(barAddr)
|
||||||
require.Equal(t, int64(8), barAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, sendTokens.Sub(newValTokens), barAcc.GetCoins().AmountOf(denom))
|
||||||
|
|
||||||
// Ensure that validator state is as expected
|
// Ensure that validator state is as expected
|
||||||
validator := f.QueryStakingValidator(barVal)
|
validator := f.QueryStakingValidator(barVal)
|
||||||
require.Equal(t, validator.OperatorAddr, barVal)
|
require.Equal(t, validator.OperatorAddr, barVal)
|
||||||
require.True(sdk.IntEq(t, sdk.NewInt(2), validator.Tokens))
|
require.True(sdk.IntEq(t, newValTokens, validator.Tokens))
|
||||||
|
|
||||||
// Query delegations to the validator
|
// Query delegations to the validator
|
||||||
validatorDelegations := f.QueryStakingDelegationsTo(barVal)
|
validatorDelegations := f.QueryStakingDelegationsTo(barVal)
|
||||||
|
@ -340,27 +361,21 @@ func TestGaiaCLICreateValidator(t *testing.T) {
|
||||||
require.NotZero(t, validatorDelegations[0].Shares)
|
require.NotZero(t, validatorDelegations[0].Shares)
|
||||||
|
|
||||||
// unbond a single share
|
// unbond a single share
|
||||||
success = f.TxStakingUnbond(keyBar, "1", barVal)
|
unbondTokens := staking.TokensFromTendermintPower(1)
|
||||||
|
success = f.TxStakingUnbond(keyBar, unbondTokens.String(), barVal)
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||||
|
|
||||||
// Ensure bonded staking is correct
|
// Ensure bonded staking is correct
|
||||||
|
remainingTokens := newValTokens.Sub(unbondTokens)
|
||||||
validator = f.QueryStakingValidator(barVal)
|
validator = f.QueryStakingValidator(barVal)
|
||||||
require.Equal(t, "1", validator.Tokens.String())
|
require.Equal(t, remainingTokens, validator.Tokens)
|
||||||
|
|
||||||
// Get unbonding delegations from the validator
|
// Get unbonding delegations from the validator
|
||||||
validatorUbds := f.QueryStakingUnbondingDelegationsFrom(barVal)
|
validatorUbds := f.QueryStakingUnbondingDelegationsFrom(barVal)
|
||||||
require.Len(t, validatorUbds, 1)
|
require.Len(t, validatorUbds, 1)
|
||||||
require.Len(t, validatorUbds[0].Entries, 1)
|
require.Len(t, validatorUbds[0].Entries, 1)
|
||||||
require.Equal(t, "1", validatorUbds[0].Entries[0].Balance.Amount.String())
|
require.Equal(t, remainingTokens.String(), validatorUbds[0].Entries[0].Balance.String())
|
||||||
|
|
||||||
// Query staking parameters
|
|
||||||
params := f.QueryStakingParameters()
|
|
||||||
require.True(t, defaultParams.Equal(params))
|
|
||||||
|
|
||||||
// Query staking pool
|
|
||||||
pool := f.QueryStakingPool()
|
|
||||||
require.Equal(t, initialPool.BondedTokens, pool.BondedTokens)
|
|
||||||
|
|
||||||
f.Cleanup()
|
f.Cleanup()
|
||||||
}
|
}
|
||||||
|
@ -380,14 +395,16 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
||||||
fooAddr := f.KeyAddress(keyFoo)
|
fooAddr := f.KeyAddress(keyFoo)
|
||||||
|
|
||||||
fooAcc := f.QueryAccount(fooAddr)
|
fooAcc := f.QueryAccount(fooAddr)
|
||||||
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(stakingTypes.DefaultBondDenom).Int64())
|
startTokens := staking.TokensFromTendermintPower(50)
|
||||||
|
require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(staking.DefaultBondDenom))
|
||||||
|
|
||||||
proposalsQuery := f.QueryGovProposals()
|
proposalsQuery := f.QueryGovProposals()
|
||||||
require.Empty(t, proposalsQuery)
|
require.Empty(t, proposalsQuery)
|
||||||
|
|
||||||
// Test submit generate only for submit proposal
|
// Test submit generate only for submit proposal
|
||||||
|
proposalTokens := staking.TokensFromTendermintPower(5)
|
||||||
success, stdout, stderr := f.TxGovSubmitProposal(
|
success, stdout, stderr := f.TxGovSubmitProposal(
|
||||||
keyFoo, "Text", "Test", "test", sdk.NewInt64Coin(denom, 5), "--generate-only")
|
keyFoo, "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens), "--generate-only")
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
require.Empty(t, stderr)
|
require.Empty(t, stderr)
|
||||||
msg := unmarshalStdTx(t, stdout)
|
msg := unmarshalStdTx(t, stdout)
|
||||||
|
@ -396,11 +413,11 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
||||||
require.Equal(t, 0, len(msg.GetSignatures()))
|
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||||
|
|
||||||
// Test --dry-run
|
// Test --dry-run
|
||||||
success, _, _ = f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewInt64Coin(denom, 5), "--dry-run")
|
success, _, _ = f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens), "--dry-run")
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Create the proposal
|
// Create the proposal
|
||||||
f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewInt64Coin(denom, 5))
|
f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens))
|
||||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||||
|
|
||||||
// Ensure transaction tags can be queried
|
// Ensure transaction tags can be queried
|
||||||
|
@ -409,7 +426,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
||||||
|
|
||||||
// Ensure deposit was deducted
|
// Ensure deposit was deducted
|
||||||
fooAcc = f.QueryAccount(fooAddr)
|
fooAcc = f.QueryAccount(fooAddr)
|
||||||
require.Equal(t, int64(45), fooAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, startTokens.Sub(proposalTokens), fooAcc.GetCoins().AmountOf(denom))
|
||||||
|
|
||||||
// Ensure propsal is directly queryable
|
// Ensure propsal is directly queryable
|
||||||
proposal1 := f.QueryGovProposal(1)
|
proposal1 := f.QueryGovProposal(1)
|
||||||
|
@ -422,10 +439,11 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
||||||
|
|
||||||
// Query the deposits on the proposal
|
// Query the deposits on the proposal
|
||||||
deposit := f.QueryGovDeposit(1, fooAddr)
|
deposit := f.QueryGovDeposit(1, fooAddr)
|
||||||
require.Equal(t, int64(5), deposit.Amount.AmountOf(denom).Int64())
|
require.Equal(t, proposalTokens, deposit.Amount.AmountOf(denom))
|
||||||
|
|
||||||
// Test deposit generate only
|
// Test deposit generate only
|
||||||
success, stdout, stderr = f.TxGovDeposit(1, keyFoo, sdk.NewInt64Coin(denom, 10), "--generate-only")
|
depositTokens := staking.TokensFromTendermintPower(10)
|
||||||
|
success, stdout, stderr = f.TxGovDeposit(1, keyFoo, sdk.NewCoin(denom, depositTokens), "--generate-only")
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
require.Empty(t, stderr)
|
require.Empty(t, stderr)
|
||||||
msg = unmarshalStdTx(t, stdout)
|
msg = unmarshalStdTx(t, stdout)
|
||||||
|
@ -434,17 +452,17 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
||||||
require.Equal(t, 0, len(msg.GetSignatures()))
|
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||||
|
|
||||||
// Run the deposit transaction
|
// Run the deposit transaction
|
||||||
f.TxGovDeposit(1, keyFoo, sdk.NewInt64Coin(denom, 10))
|
f.TxGovDeposit(1, keyFoo, sdk.NewCoin(denom, depositTokens))
|
||||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||||
|
|
||||||
// test query deposit
|
// test query deposit
|
||||||
deposits := f.QueryGovDeposits(1)
|
deposits := f.QueryGovDeposits(1)
|
||||||
require.Len(t, deposits, 1)
|
require.Len(t, deposits, 1)
|
||||||
require.Equal(t, int64(15), deposits[0].Amount.AmountOf(denom).Int64())
|
require.Equal(t, proposalTokens.Add(depositTokens), deposits[0].Amount.AmountOf(denom))
|
||||||
|
|
||||||
// Ensure querying the deposit returns the proper amount
|
// Ensure querying the deposit returns the proper amount
|
||||||
deposit = f.QueryGovDeposit(1, fooAddr)
|
deposit = f.QueryGovDeposit(1, fooAddr)
|
||||||
require.Equal(t, int64(15), deposit.Amount.AmountOf(denom).Int64())
|
require.Equal(t, proposalTokens.Add(depositTokens), deposit.Amount.AmountOf(denom))
|
||||||
|
|
||||||
// Ensure tags are set on the transaction
|
// Ensure tags are set on the transaction
|
||||||
txs = f.QueryTxs(1, 50, "action:deposit", fmt.Sprintf("depositor:%s", fooAddr))
|
txs = f.QueryTxs(1, 50, "action:deposit", fmt.Sprintf("depositor:%s", fooAddr))
|
||||||
|
@ -452,7 +470,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
||||||
|
|
||||||
// Ensure account has expected amount of funds
|
// Ensure account has expected amount of funds
|
||||||
fooAcc = f.QueryAccount(fooAddr)
|
fooAcc = f.QueryAccount(fooAddr)
|
||||||
require.Equal(t, int64(35), fooAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, startTokens.Sub(proposalTokens.Add(depositTokens)), fooAcc.GetCoins().AmountOf(denom))
|
||||||
|
|
||||||
// Fetch the proposal and ensure it is now in the voting period
|
// Fetch the proposal and ensure it is now in the voting period
|
||||||
proposal1 = f.QueryGovProposal(1)
|
proposal1 = f.QueryGovProposal(1)
|
||||||
|
@ -496,7 +514,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
||||||
require.Equal(t, uint64(1), proposalsQuery[0].GetProposalID())
|
require.Equal(t, uint64(1), proposalsQuery[0].GetProposalID())
|
||||||
|
|
||||||
// submit a second test proposal
|
// submit a second test proposal
|
||||||
f.TxGovSubmitProposal(keyFoo, "Text", "Apples", "test", sdk.NewInt64Coin(denom, 5))
|
f.TxGovSubmitProposal(keyFoo, "Text", "Apples", "test", sdk.NewCoin(denom, proposalTokens))
|
||||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||||
|
|
||||||
// Test limit on proposals query
|
// Test limit on proposals query
|
||||||
|
@ -517,12 +535,13 @@ func TestGaiaCLIQueryTxPagination(t *testing.T) {
|
||||||
fooAddr := f.KeyAddress(keyFoo)
|
fooAddr := f.KeyAddress(keyFoo)
|
||||||
barAddr := f.KeyAddress(keyBar)
|
barAddr := f.KeyAddress(keyBar)
|
||||||
|
|
||||||
|
accFoo := f.QueryAccount(fooAddr)
|
||||||
|
seq := accFoo.GetSequence()
|
||||||
|
|
||||||
for i := 1; i <= 30; i++ {
|
for i := 1; i <= 30; i++ {
|
||||||
success := executeWrite(t, fmt.Sprintf(
|
success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, int64(i)), fmt.Sprintf("--sequence=%d", seq))
|
||||||
"gaiacli tx send %s --amount=%dfootoken --to=%s --from=foo",
|
|
||||||
f.Flags(), i, barAddr), app.DefaultKeyPass)
|
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
seq++
|
||||||
}
|
}
|
||||||
|
|
||||||
// perPage = 15, 2 pages
|
// perPage = 15, 2 pages
|
||||||
|
@ -571,7 +590,7 @@ func TestGaiaCLIValidateSignatures(t *testing.T) {
|
||||||
require.Empty(t, stderr)
|
require.Empty(t, stderr)
|
||||||
|
|
||||||
// write unsigned tx to file
|
// write unsigned tx to file
|
||||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
unsignedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(unsignedTxFile.Name())
|
defer os.Remove(unsignedTxFile.Name())
|
||||||
|
|
||||||
// validate we can successfully sign
|
// validate we can successfully sign
|
||||||
|
@ -584,7 +603,7 @@ func TestGaiaCLIValidateSignatures(t *testing.T) {
|
||||||
require.Equal(t, fooAddr.String(), stdTx.GetSigners()[0].String())
|
require.Equal(t, fooAddr.String(), stdTx.GetSigners()[0].String())
|
||||||
|
|
||||||
// write signed tx to file
|
// write signed tx to file
|
||||||
signedTxFile := writeToNewTempFile(t, stdout)
|
signedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(signedTxFile.Name())
|
defer os.Remove(signedTxFile.Name())
|
||||||
|
|
||||||
// validate signatures
|
// validate signatures
|
||||||
|
@ -594,7 +613,7 @@ func TestGaiaCLIValidateSignatures(t *testing.T) {
|
||||||
// modify the transaction
|
// modify the transaction
|
||||||
stdTx.Memo = "MODIFIED-ORIGINAL-TX-BAD"
|
stdTx.Memo = "MODIFIED-ORIGINAL-TX-BAD"
|
||||||
bz := marshalStdTx(t, stdTx)
|
bz := marshalStdTx(t, stdTx)
|
||||||
modSignedTxFile := writeToNewTempFile(t, string(bz))
|
modSignedTxFile := WriteToNewTempFile(t, string(bz))
|
||||||
defer os.Remove(modSignedTxFile.Name())
|
defer os.Remove(modSignedTxFile.Name())
|
||||||
|
|
||||||
// validate signature validation failure due to different transaction sig bytes
|
// validate signature validation failure due to different transaction sig bytes
|
||||||
|
@ -616,7 +635,8 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
||||||
barAddr := f.KeyAddress(keyBar)
|
barAddr := f.KeyAddress(keyBar)
|
||||||
|
|
||||||
// Test generate sendTx with default gas
|
// Test generate sendTx with default gas
|
||||||
success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--generate-only")
|
sendTokens := staking.TokensFromTendermintPower(10)
|
||||||
|
success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--generate-only")
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
require.Empty(t, stderr)
|
require.Empty(t, stderr)
|
||||||
msg := unmarshalStdTx(t, stdout)
|
msg := unmarshalStdTx(t, stdout)
|
||||||
|
@ -625,7 +645,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
||||||
require.Equal(t, 0, len(msg.GetSignatures()))
|
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||||
|
|
||||||
// Test generate sendTx with --gas=$amount
|
// Test generate sendTx with --gas=$amount
|
||||||
success, stdout, stderr = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=100", "--generate-only")
|
success, stdout, stderr = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=100", "--generate-only")
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
require.Empty(t, stderr)
|
require.Empty(t, stderr)
|
||||||
msg = unmarshalStdTx(t, stdout)
|
msg = unmarshalStdTx(t, stdout)
|
||||||
|
@ -634,7 +654,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
||||||
require.Equal(t, 0, len(msg.GetSignatures()))
|
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||||
|
|
||||||
// Test generate sendTx, estimate gas
|
// Test generate sendTx, estimate gas
|
||||||
success, stdout, stderr = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=auto", "--generate-only")
|
success, stdout, stderr = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=auto", "--generate-only")
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
require.NotEmpty(t, stderr)
|
require.NotEmpty(t, stderr)
|
||||||
msg = unmarshalStdTx(t, stdout)
|
msg = unmarshalStdTx(t, stdout)
|
||||||
|
@ -642,7 +662,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
||||||
require.Equal(t, len(msg.Msgs), 1)
|
require.Equal(t, len(msg.Msgs), 1)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
unsignedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(unsignedTxFile.Name())
|
defer os.Remove(unsignedTxFile.Name())
|
||||||
|
|
||||||
// Test sign --validate-signatures
|
// Test sign --validate-signatures
|
||||||
|
@ -659,7 +679,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
||||||
require.Equal(t, fooAddr.String(), msg.GetSigners()[0].String())
|
require.Equal(t, fooAddr.String(), msg.GetSigners()[0].String())
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
signedTxFile := writeToNewTempFile(t, stdout)
|
signedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(signedTxFile.Name())
|
defer os.Remove(signedTxFile.Name())
|
||||||
|
|
||||||
// Test sign --validate-signatures
|
// Test sign --validate-signatures
|
||||||
|
@ -670,27 +690,26 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
||||||
|
|
||||||
// Ensure foo has right amount of funds
|
// Ensure foo has right amount of funds
|
||||||
fooAcc := f.QueryAccount(fooAddr)
|
fooAcc := f.QueryAccount(fooAddr)
|
||||||
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64())
|
startTokens := staking.TokensFromTendermintPower(50)
|
||||||
|
require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom))
|
||||||
|
|
||||||
// Test broadcast
|
// Test broadcast
|
||||||
success, stdout, _ = f.TxBroadcast(signedTxFile.Name())
|
success, stdout, _ = f.TxBroadcast(signedTxFile.Name())
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
var result struct {
|
var result sdk.TxResponse
|
||||||
Response abci.ResponseDeliverTx
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal the response and ensure that gas was properly used
|
// Unmarshal the response and ensure that gas was properly used
|
||||||
require.Nil(t, app.MakeCodec().UnmarshalJSON([]byte(stdout), &result))
|
require.Nil(t, app.MakeCodec().UnmarshalJSON([]byte(stdout), &result))
|
||||||
require.Equal(t, msg.Fee.Gas, uint64(result.Response.GasUsed))
|
require.Equal(t, msg.Fee.Gas, uint64(result.GasUsed))
|
||||||
require.Equal(t, msg.Fee.Gas, uint64(result.Response.GasWanted))
|
require.Equal(t, msg.Fee.Gas, uint64(result.GasWanted))
|
||||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||||
|
|
||||||
// Ensure account state
|
// Ensure account state
|
||||||
barAcc := f.QueryAccount(barAddr)
|
barAcc := f.QueryAccount(barAddr)
|
||||||
fooAcc = f.QueryAccount(fooAddr)
|
fooAcc = f.QueryAccount(fooAddr)
|
||||||
require.Equal(t, int64(10), barAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom))
|
||||||
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64())
|
require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom))
|
||||||
|
|
||||||
f.Cleanup()
|
f.Cleanup()
|
||||||
}
|
}
|
||||||
|
@ -716,7 +735,7 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
unsignedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(unsignedTxFile.Name())
|
defer os.Remove(unsignedTxFile.Name())
|
||||||
|
|
||||||
// Sign with foo's key
|
// Sign with foo's key
|
||||||
|
@ -724,7 +743,7 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
fooSignatureFile := writeToNewTempFile(t, stdout)
|
fooSignatureFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(fooSignatureFile.Name())
|
defer os.Remove(fooSignatureFile.Name())
|
||||||
|
|
||||||
// Multisign, not enough signatures
|
// Multisign, not enough signatures
|
||||||
|
@ -732,7 +751,7 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
signedTxFile := writeToNewTempFile(t, stdout)
|
signedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(signedTxFile.Name())
|
defer os.Remove(signedTxFile.Name())
|
||||||
|
|
||||||
// Validate the multisignature
|
// Validate the multisignature
|
||||||
|
@ -744,6 +763,42 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
|
||||||
require.False(t, success)
|
require.False(t, success)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGaiaCLIEncode(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
f := InitFixtures(t)
|
||||||
|
|
||||||
|
// start gaiad server
|
||||||
|
proc := f.GDStart()
|
||||||
|
defer proc.Stop(false)
|
||||||
|
|
||||||
|
cdc := app.MakeCodec()
|
||||||
|
|
||||||
|
// Build a testing transaction and write it to disk
|
||||||
|
barAddr := f.KeyAddress(keyBar)
|
||||||
|
sendTokens := staking.TokensFromTendermintPower(10)
|
||||||
|
success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--generate-only", "--memo", "deadbeef")
|
||||||
|
require.True(t, success)
|
||||||
|
require.Empty(t, stderr)
|
||||||
|
|
||||||
|
// Write it to disk
|
||||||
|
jsonTxFile := WriteToNewTempFile(t, stdout)
|
||||||
|
defer os.Remove(jsonTxFile.Name())
|
||||||
|
|
||||||
|
// Run the encode command, and trim the extras from the stdout capture
|
||||||
|
success, base64Encoded, _ := f.TxEncode(jsonTxFile.Name())
|
||||||
|
require.True(t, success)
|
||||||
|
trimmedBase64 := strings.Trim(base64Encoded, "\"\n")
|
||||||
|
|
||||||
|
// Decode the base64
|
||||||
|
decodedBytes, err := base64.StdEncoding.DecodeString(trimmedBase64)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Check that the transaction decodes as epxceted
|
||||||
|
var decodedTx auth.StdTx
|
||||||
|
require.Nil(t, cdc.UnmarshalBinaryLengthPrefixed(decodedBytes, &decodedTx))
|
||||||
|
require.Equal(t, "deadbeef", decodedTx.Memo)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
f := InitFixtures(t)
|
f := InitFixtures(t)
|
||||||
|
@ -769,7 +824,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
unsignedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(unsignedTxFile.Name())
|
defer os.Remove(unsignedTxFile.Name())
|
||||||
|
|
||||||
// Sign with foo's key
|
// Sign with foo's key
|
||||||
|
@ -777,7 +832,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
fooSignatureFile := writeToNewTempFile(t, stdout)
|
fooSignatureFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(fooSignatureFile.Name())
|
defer os.Remove(fooSignatureFile.Name())
|
||||||
|
|
||||||
// Sign with baz's key
|
// Sign with baz's key
|
||||||
|
@ -785,7 +840,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
bazSignatureFile := writeToNewTempFile(t, stdout)
|
bazSignatureFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(bazSignatureFile.Name())
|
defer os.Remove(bazSignatureFile.Name())
|
||||||
|
|
||||||
// Multisign, keys in different order
|
// Multisign, keys in different order
|
||||||
|
@ -794,7 +849,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
signedTxFile := writeToNewTempFile(t, stdout)
|
signedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(signedTxFile.Name())
|
defer os.Remove(signedTxFile.Name())
|
||||||
|
|
||||||
// Validate the multisignature
|
// Validate the multisignature
|
||||||
|
@ -832,7 +887,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
|
||||||
require.Empty(t, stderr)
|
require.Empty(t, stderr)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
unsignedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(unsignedTxFile.Name())
|
defer os.Remove(unsignedTxFile.Name())
|
||||||
|
|
||||||
// Sign with foo's key
|
// Sign with foo's key
|
||||||
|
@ -840,7 +895,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
fooSignatureFile := writeToNewTempFile(t, stdout)
|
fooSignatureFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(fooSignatureFile.Name())
|
defer os.Remove(fooSignatureFile.Name())
|
||||||
|
|
||||||
// Sign with bar's key
|
// Sign with bar's key
|
||||||
|
@ -848,7 +903,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
barSignatureFile := writeToNewTempFile(t, stdout)
|
barSignatureFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(barSignatureFile.Name())
|
defer os.Remove(barSignatureFile.Name())
|
||||||
|
|
||||||
// Multisign
|
// Multisign
|
||||||
|
@ -857,7 +912,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
signedTxFile := writeToNewTempFile(t, stdout)
|
signedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(signedTxFile.Name())
|
defer os.Remove(signedTxFile.Name())
|
||||||
|
|
||||||
// Validate the multisignature
|
// Validate the multisignature
|
||||||
|
@ -909,10 +964,7 @@ func TestGaiadCollectGentxs(t *testing.T) {
|
||||||
f.UnsafeResetAll()
|
f.UnsafeResetAll()
|
||||||
|
|
||||||
// Initialize keys
|
// Initialize keys
|
||||||
f.KeysDelete(keyFoo)
|
|
||||||
f.KeysDelete(keyBar)
|
|
||||||
f.KeysAdd(keyFoo)
|
f.KeysAdd(keyFoo)
|
||||||
f.KeysAdd(keyBar)
|
|
||||||
|
|
||||||
// Configure json output
|
// Configure json output
|
||||||
f.CLIConfig("output", "json")
|
f.CLIConfig("output", "json")
|
||||||
|
@ -932,6 +984,42 @@ func TestGaiadCollectGentxs(t *testing.T) {
|
||||||
f.Cleanup(gentxDir)
|
f.Cleanup(gentxDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGaiadAddGenesisAccount(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
f := NewFixtures(t)
|
||||||
|
|
||||||
|
// Reset testing path
|
||||||
|
f.UnsafeResetAll()
|
||||||
|
|
||||||
|
// Initialize keys
|
||||||
|
f.KeysDelete(keyFoo)
|
||||||
|
f.KeysDelete(keyBar)
|
||||||
|
f.KeysDelete(keyBaz)
|
||||||
|
f.KeysAdd(keyFoo)
|
||||||
|
f.KeysAdd(keyBar)
|
||||||
|
f.KeysAdd(keyBaz)
|
||||||
|
|
||||||
|
// Configure json output
|
||||||
|
f.CLIConfig("output", "json")
|
||||||
|
|
||||||
|
// Run init
|
||||||
|
f.GDInit(keyFoo)
|
||||||
|
|
||||||
|
// Add account to genesis.json
|
||||||
|
bazCoins := sdk.Coins{
|
||||||
|
sdk.NewInt64Coin("acoin", 1000000),
|
||||||
|
sdk.NewInt64Coin("bcoin", 1000000),
|
||||||
|
}
|
||||||
|
|
||||||
|
f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins)
|
||||||
|
f.AddGenesisAccount(f.KeyAddress(keyBar), bazCoins)
|
||||||
|
genesisState := f.GenesisState()
|
||||||
|
require.Equal(t, genesisState.Accounts[0].Address, f.KeyAddress(keyFoo))
|
||||||
|
require.Equal(t, genesisState.Accounts[1].Address, f.KeyAddress(keyBar))
|
||||||
|
require.True(t, genesisState.Accounts[0].Coins.IsEqual(startCoins))
|
||||||
|
require.True(t, genesisState.Accounts[1].Coins.IsEqual(bazCoins))
|
||||||
|
}
|
||||||
|
|
||||||
func TestSlashingGetParams(t *testing.T) {
|
func TestSlashingGetParams(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
f := InitFixtures(t)
|
f := InitFixtures(t)
|
||||||
|
@ -944,4 +1032,19 @@ func TestSlashingGetParams(t *testing.T) {
|
||||||
require.Equal(t, time.Duration(120000000000), params.MaxEvidenceAge)
|
require.Equal(t, time.Duration(120000000000), params.MaxEvidenceAge)
|
||||||
require.Equal(t, int64(100), params.SignedBlocksWindow)
|
require.Equal(t, int64(100), params.SignedBlocksWindow)
|
||||||
require.Equal(t, sdk.NewDecWithPrec(5, 1), params.MinSignedPerWindow)
|
require.Equal(t, sdk.NewDecWithPrec(5, 1), params.MinSignedPerWindow)
|
||||||
|
|
||||||
|
sinfo := f.QuerySigningInfo(f.GDTendermint("show-validator"))
|
||||||
|
require.Equal(t, int64(0), sinfo.StartHeight)
|
||||||
|
require.False(t, sinfo.Tombstoned)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateGenesis(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
f := InitFixtures(t)
|
||||||
|
|
||||||
|
// start gaiad server
|
||||||
|
proc := f.GDStart()
|
||||||
|
defer proc.Stop(false)
|
||||||
|
|
||||||
|
f.ValidateGenesis()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,15 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
|
||||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||||
|
appInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init"
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
"github.com/cosmos/cosmos-sdk/server"
|
"github.com/cosmos/cosmos-sdk/server"
|
||||||
"github.com/cosmos/cosmos-sdk/tests"
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
|
@ -34,15 +35,22 @@ const (
|
||||||
feeDenom = "feetoken"
|
feeDenom = "feetoken"
|
||||||
fee2Denom = "fee2token"
|
fee2Denom = "fee2token"
|
||||||
keyBaz = "baz"
|
keyBaz = "baz"
|
||||||
|
keyVesting = "vesting"
|
||||||
keyFooBarBaz = "foobarbaz"
|
keyFooBarBaz = "foobarbaz"
|
||||||
)
|
)
|
||||||
|
|
||||||
var startCoins = sdk.Coins{
|
var (
|
||||||
sdk.NewInt64Coin(feeDenom, 1000000),
|
startCoins = sdk.Coins{
|
||||||
sdk.NewInt64Coin(fee2Denom, 1000000),
|
sdk.NewCoin(feeDenom, staking.TokensFromTendermintPower(1000000)),
|
||||||
sdk.NewInt64Coin(fooDenom, 1000),
|
sdk.NewCoin(fee2Denom, staking.TokensFromTendermintPower(1000000)),
|
||||||
sdk.NewInt64Coin(denom, 150),
|
sdk.NewCoin(fooDenom, staking.TokensFromTendermintPower(1000)),
|
||||||
}
|
sdk.NewCoin(denom, staking.TokensFromTendermintPower(150)),
|
||||||
|
}
|
||||||
|
|
||||||
|
vestingCoins = sdk.Coins{
|
||||||
|
sdk.NewCoin(feeDenom, staking.TokensFromTendermintPower(500000)),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
//___________________________________________________________________________________
|
//___________________________________________________________________________________
|
||||||
// Fixtures
|
// Fixtures
|
||||||
|
@ -76,6 +84,22 @@ func NewFixtures(t *testing.T) *Fixtures {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenesisFile returns the path of the genesis file
|
||||||
|
func (f Fixtures) GenesisFile() string {
|
||||||
|
return filepath.Join(f.GDHome, "config", "genesis.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenesisFile returns the application's genesis state
|
||||||
|
func (f Fixtures) GenesisState() app.GenesisState {
|
||||||
|
cdc := codec.New()
|
||||||
|
genDoc, err := appInit.LoadGenesisDoc(cdc, f.GenesisFile())
|
||||||
|
require.NoError(f.T, err)
|
||||||
|
|
||||||
|
var appState app.GenesisState
|
||||||
|
require.NoError(f.T, cdc.UnmarshalJSON(genDoc.AppState, &appState))
|
||||||
|
return appState
|
||||||
|
}
|
||||||
|
|
||||||
// InitFixtures is called at the beginning of a test
|
// InitFixtures is called at the beginning of a test
|
||||||
// and initializes a chain with 1 validator
|
// and initializes a chain with 1 validator
|
||||||
func InitFixtures(t *testing.T) (f *Fixtures) {
|
func InitFixtures(t *testing.T) (f *Fixtures) {
|
||||||
|
@ -92,6 +116,7 @@ func InitFixtures(t *testing.T) (f *Fixtures) {
|
||||||
f.KeysAdd(keyFoo)
|
f.KeysAdd(keyFoo)
|
||||||
f.KeysAdd(keyBar)
|
f.KeysAdd(keyBar)
|
||||||
f.KeysAdd(keyBaz)
|
f.KeysAdd(keyBaz)
|
||||||
|
f.KeysAdd(keyVesting)
|
||||||
f.KeysAdd(keyFooBarBaz, "--multisig-threshold=2", fmt.Sprintf(
|
f.KeysAdd(keyFooBarBaz, "--multisig-threshold=2", fmt.Sprintf(
|
||||||
"--multisig=%s,%s,%s", keyFoo, keyBar, keyBaz))
|
"--multisig=%s,%s,%s", keyFoo, keyBar, keyBaz))
|
||||||
|
|
||||||
|
@ -104,6 +129,12 @@ func InitFixtures(t *testing.T) (f *Fixtures) {
|
||||||
|
|
||||||
// Start an account with tokens
|
// Start an account with tokens
|
||||||
f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins)
|
f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins)
|
||||||
|
f.AddGenesisAccount(
|
||||||
|
f.KeyAddress(keyVesting), startCoins,
|
||||||
|
fmt.Sprintf("--vesting-amount=%s", vestingCoins),
|
||||||
|
fmt.Sprintf("--vesting-start-time=%d", time.Now().UTC().UnixNano()),
|
||||||
|
fmt.Sprintf("--vesting-end-time=%d", time.Now().Add(60*time.Second).UTC().UnixNano()),
|
||||||
|
)
|
||||||
f.GenTx(keyFoo)
|
f.GenTx(keyFoo)
|
||||||
f.CollectGenTxs()
|
f.CollectGenTxs()
|
||||||
return
|
return
|
||||||
|
@ -137,7 +168,7 @@ func (f *Fixtures) UnsafeResetAll(flags ...string) {
|
||||||
// GDInit is gaiad init
|
// GDInit is gaiad init
|
||||||
// NOTE: GDInit sets the ChainID for the Fixtures instance
|
// NOTE: GDInit sets the ChainID for the Fixtures instance
|
||||||
func (f *Fixtures) GDInit(moniker string, flags ...string) {
|
func (f *Fixtures) GDInit(moniker string, flags ...string) {
|
||||||
cmd := fmt.Sprintf("gaiad init -o --moniker=%s --home=%s", moniker, f.GDHome)
|
cmd := fmt.Sprintf("gaiad init -o --home=%s %s", f.GDHome, moniker)
|
||||||
_, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
_, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
||||||
|
|
||||||
var chainID string
|
var chainID string
|
||||||
|
@ -179,6 +210,21 @@ func (f *Fixtures) GDStart(flags ...string) *tests.Process {
|
||||||
return proc
|
return proc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GDTendermint returns the results of gaiad tendermint [query]
|
||||||
|
func (f *Fixtures) GDTendermint(query string) string {
|
||||||
|
cmd := fmt.Sprintf("gaiad tendermint %s --home=%s", query, f.GDHome)
|
||||||
|
success, stdout, stderr := executeWriteRetStdStreams(f.T, cmd)
|
||||||
|
require.Empty(f.T, stderr)
|
||||||
|
require.True(f.T, success)
|
||||||
|
return strings.TrimSpace(stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateGenesis runs gaiad validate-genesis
|
||||||
|
func (f *Fixtures) ValidateGenesis() {
|
||||||
|
cmd := fmt.Sprintf("gaiad validate-genesis --home=%s", f.GDHome)
|
||||||
|
executeWriteCheckErr(f.T, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
//___________________________________________________________________________________
|
//___________________________________________________________________________________
|
||||||
// gaiacli keys
|
// gaiacli keys
|
||||||
|
|
||||||
|
@ -194,6 +240,18 @@ func (f *Fixtures) KeysAdd(name string, flags ...string) {
|
||||||
executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KeysAddRecover prepares gaiacli keys add --recover
|
||||||
|
func (f *Fixtures) KeysAddRecover(name, mnemonic string, flags ...string) {
|
||||||
|
cmd := fmt.Sprintf("gaiacli keys add --home=%s --recover %s", f.GCLIHome, name)
|
||||||
|
executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass, mnemonic)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeysAddRecoverHDPath prepares gaiacli keys add --recover --account --index
|
||||||
|
func (f *Fixtures) KeysAddRecoverHDPath(name, mnemonic string, account uint32, index uint32, flags ...string) {
|
||||||
|
cmd := fmt.Sprintf("gaiacli keys add --home=%s --recover %s --account %d --index %d", f.GCLIHome, name, account, index)
|
||||||
|
executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass, mnemonic)
|
||||||
|
}
|
||||||
|
|
||||||
// KeysShow is gaiacli keys show
|
// KeysShow is gaiacli keys show
|
||||||
func (f *Fixtures) KeysShow(name string, flags ...string) keys.KeyOutput {
|
func (f *Fixtures) KeysShow(name string, flags ...string) keys.KeyOutput {
|
||||||
cmd := fmt.Sprintf("gaiacli keys show --home=%s %s", f.GCLIHome, name)
|
cmd := fmt.Sprintf("gaiacli keys show --home=%s %s", f.GCLIHome, name)
|
||||||
|
@ -226,7 +284,7 @@ func (f *Fixtures) CLIConfig(key, value string, flags ...string) {
|
||||||
|
|
||||||
// TxSend is gaiacli tx send
|
// TxSend is gaiacli tx send
|
||||||
func (f *Fixtures) TxSend(from string, to sdk.AccAddress, amount sdk.Coin, flags ...string) (bool, string, string) {
|
func (f *Fixtures) TxSend(from string, to sdk.AccAddress, amount sdk.Coin, flags ...string) (bool, string, string) {
|
||||||
cmd := fmt.Sprintf("gaiacli tx send %v --amount=%s --to=%s --from=%s", f.Flags(), amount, to, from)
|
cmd := fmt.Sprintf("gaiacli tx send %s %s %v --from=%s", to, amount, f.Flags(), from)
|
||||||
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,12 +294,18 @@ func (f *Fixtures) TxSign(signer, fileName string, flags ...string) (bool, strin
|
||||||
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TxBroadcast is gaiacli tx sign
|
// TxBroadcast is gaiacli tx broadcast
|
||||||
func (f *Fixtures) TxBroadcast(fileName string, flags ...string) (bool, string, string) {
|
func (f *Fixtures) TxBroadcast(fileName string, flags ...string) (bool, string, string) {
|
||||||
cmd := fmt.Sprintf("gaiacli tx broadcast %v %v", f.Flags(), fileName)
|
cmd := fmt.Sprintf("gaiacli tx broadcast %v %v", f.Flags(), fileName)
|
||||||
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TxEncode is gaiacli tx encode
|
||||||
|
func (f *Fixtures) TxEncode(fileName string, flags ...string) (bool, string, string) {
|
||||||
|
cmd := fmt.Sprintf("gaiacli tx encode %v %v", f.Flags(), fileName)
|
||||||
|
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
||||||
|
}
|
||||||
|
|
||||||
// TxMultisign is gaiacli tx multisign
|
// TxMultisign is gaiacli tx multisign
|
||||||
func (f *Fixtures) TxMultisign(fileName, name string, signaturesFiles []string,
|
func (f *Fixtures) TxMultisign(fileName, name string, signaturesFiles []string,
|
||||||
flags ...string) (bool, string, string) {
|
flags ...string) (bool, string, string) {
|
||||||
|
@ -260,12 +324,13 @@ func (f *Fixtures) TxStakingCreateValidator(from, consPubKey string, amount sdk.
|
||||||
cmd := fmt.Sprintf("gaiacli tx staking create-validator %v --from=%s --pubkey=%s", f.Flags(), from, consPubKey)
|
cmd := fmt.Sprintf("gaiacli tx staking create-validator %v --from=%s --pubkey=%s", f.Flags(), from, consPubKey)
|
||||||
cmd += fmt.Sprintf(" --amount=%v --moniker=%v --commission-rate=%v", amount, from, "0.05")
|
cmd += fmt.Sprintf(" --amount=%v --moniker=%v --commission-rate=%v", amount, from, "0.05")
|
||||||
cmd += fmt.Sprintf(" --commission-max-rate=%v --commission-max-change-rate=%v", "0.20", "0.10")
|
cmd += fmt.Sprintf(" --commission-max-rate=%v --commission-max-change-rate=%v", "0.20", "0.10")
|
||||||
|
cmd += fmt.Sprintf(" --min-self-delegation=%v", "1")
|
||||||
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TxStakingUnbond is gaiacli tx staking unbond
|
// TxStakingUnbond is gaiacli tx staking unbond
|
||||||
func (f *Fixtures) TxStakingUnbond(from, shares string, validator sdk.ValAddress, flags ...string) bool {
|
func (f *Fixtures) TxStakingUnbond(from, shares string, validator sdk.ValAddress, flags ...string) bool {
|
||||||
cmd := fmt.Sprintf("gaiacli tx staking unbond %v --from=%s --validator=%s --shares-amount=%v", f.Flags(), from, validator, shares)
|
cmd := fmt.Sprintf("gaiacli tx staking unbond %s %v --from=%s %v", validator, shares, from, f.Flags())
|
||||||
return executeWrite(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
return executeWrite(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,10 +379,10 @@ func (f *Fixtures) QueryAccount(address sdk.AccAddress, flags ...string) auth.Ba
|
||||||
// gaiacli query txs
|
// gaiacli query txs
|
||||||
|
|
||||||
// QueryTxs is gaiacli query txs
|
// QueryTxs is gaiacli query txs
|
||||||
func (f *Fixtures) QueryTxs(page, limit int, tags ...string) []tx.Info {
|
func (f *Fixtures) QueryTxs(page, limit int, tags ...string) []sdk.TxResponse {
|
||||||
cmd := fmt.Sprintf("gaiacli query txs --page=%d --limit=%d --tags='%s' %v", page, limit, queryTags(tags), f.Flags())
|
cmd := fmt.Sprintf("gaiacli query txs --page=%d --limit=%d --tags='%s' %v", page, limit, queryTags(tags), f.Flags())
|
||||||
out, _ := tests.ExecuteT(f.T, cmd, "")
|
out, _ := tests.ExecuteT(f.T, cmd, "")
|
||||||
var txs []tx.Info
|
var txs []sdk.TxResponse
|
||||||
cdc := app.MakeCodec()
|
cdc := app.MakeCodec()
|
||||||
err := cdc.UnmarshalJSON([]byte(out), &txs)
|
err := cdc.UnmarshalJSON([]byte(out), &txs)
|
||||||
require.NoError(f.T, err, "out %v\n, err %v", out, err)
|
require.NoError(f.T, err, "out %v\n, err %v", out, err)
|
||||||
|
@ -498,6 +563,18 @@ func (f *Fixtures) QueryGovDeposits(propsalID int, flags ...string) []gov.Deposi
|
||||||
//___________________________________________________________________________________
|
//___________________________________________________________________________________
|
||||||
// query slashing
|
// query slashing
|
||||||
|
|
||||||
|
// QuerySigningInfo returns the signing info for a validator
|
||||||
|
func (f *Fixtures) QuerySigningInfo(val string) slashing.ValidatorSigningInfo {
|
||||||
|
cmd := fmt.Sprintf("gaiacli query slashing signing-info %s %s", val, f.Flags())
|
||||||
|
res, errStr := tests.ExecuteT(f.T, cmd, "")
|
||||||
|
require.Empty(f.T, errStr)
|
||||||
|
cdc := app.MakeCodec()
|
||||||
|
var sinfo slashing.ValidatorSigningInfo
|
||||||
|
err := cdc.UnmarshalJSON([]byte(res), &sinfo)
|
||||||
|
require.NoError(f.T, err)
|
||||||
|
return sinfo
|
||||||
|
}
|
||||||
|
|
||||||
// QuerySlashingParams is gaiacli query slashing params
|
// QuerySlashingParams is gaiacli query slashing params
|
||||||
func (f *Fixtures) QuerySlashingParams() slashing.Params {
|
func (f *Fixtures) QuerySlashingParams() slashing.Params {
|
||||||
cmd := fmt.Sprintf("gaiacli query slashing params %s", f.Flags())
|
cmd := fmt.Sprintf("gaiacli query slashing params %s", f.Flags())
|
||||||
|
@ -569,7 +646,8 @@ func queryTags(tags []string) (out string) {
|
||||||
return strings.TrimSuffix(out, "&")
|
return strings.TrimSuffix(out, "&")
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeToNewTempFile(t *testing.T, s string) *os.File {
|
// Write the given string to a new temporary file
|
||||||
|
func WriteToNewTempFile(t *testing.T, s string) *os.File {
|
||||||
fp, err := ioutil.TempFile(os.TempDir(), "cosmos_cli_test_")
|
fp, err := ioutil.TempFile(os.TempDir(), "cosmos_cli_test_")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
_, err = fp.WriteString(s)
|
_, err = fp.WriteString(s)
|
||||||
|
|
|
@ -25,7 +25,7 @@ import (
|
||||||
at "github.com/cosmos/cosmos-sdk/x/auth"
|
at "github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
||||||
bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
|
bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
|
||||||
dist "github.com/cosmos/cosmos-sdk/x/distribution"
|
dist "github.com/cosmos/cosmos-sdk/x/distribution/client/rest"
|
||||||
gv "github.com/cosmos/cosmos-sdk/x/gov"
|
gv "github.com/cosmos/cosmos-sdk/x/gov"
|
||||||
gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
|
gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
|
||||||
sl "github.com/cosmos/cosmos-sdk/x/slashing"
|
sl "github.com/cosmos/cosmos-sdk/x/slashing"
|
||||||
|
@ -35,6 +35,7 @@ import (
|
||||||
|
|
||||||
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
||||||
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
|
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
|
||||||
|
distcmd "github.com/cosmos/cosmos-sdk/x/distribution"
|
||||||
distClient "github.com/cosmos/cosmos-sdk/x/distribution/client"
|
distClient "github.com/cosmos/cosmos-sdk/x/distribution/client"
|
||||||
govClient "github.com/cosmos/cosmos-sdk/x/gov/client"
|
govClient "github.com/cosmos/cosmos-sdk/x/gov/client"
|
||||||
slashingClient "github.com/cosmos/cosmos-sdk/x/slashing/client"
|
slashingClient "github.com/cosmos/cosmos-sdk/x/slashing/client"
|
||||||
|
@ -65,7 +66,7 @@ func main() {
|
||||||
// TODO: Make the lcd command take a list of ModuleClient
|
// TODO: Make the lcd command take a list of ModuleClient
|
||||||
mc := []sdk.ModuleClients{
|
mc := []sdk.ModuleClients{
|
||||||
govClient.NewModuleClient(gv.StoreKey, cdc),
|
govClient.NewModuleClient(gv.StoreKey, cdc),
|
||||||
distClient.NewModuleClient(dist.StoreKey, cdc),
|
distClient.NewModuleClient(distcmd.StoreKey, cdc),
|
||||||
stakingClient.NewModuleClient(st.StoreKey, cdc),
|
stakingClient.NewModuleClient(st.StoreKey, cdc),
|
||||||
slashingClient.NewModuleClient(sl.StoreKey, cdc),
|
slashingClient.NewModuleClient(sl.StoreKey, cdc),
|
||||||
}
|
}
|
||||||
|
@ -84,7 +85,7 @@ func main() {
|
||||||
// Construct Root Command
|
// Construct Root Command
|
||||||
rootCmd.AddCommand(
|
rootCmd.AddCommand(
|
||||||
rpc.StatusCommand(),
|
rpc.StatusCommand(),
|
||||||
client.ConfigCmd(),
|
client.ConfigCmd(app.DefaultCLIHome),
|
||||||
queryCmd(cdc, mc),
|
queryCmd(cdc, mc),
|
||||||
txCmd(cdc, mc),
|
txCmd(cdc, mc),
|
||||||
client.LineBreak,
|
client.LineBreak,
|
||||||
|
@ -114,7 +115,7 @@ func queryCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
queryCmd.AddCommand(
|
queryCmd.AddCommand(
|
||||||
rpc.ValidatorCommand(),
|
rpc.ValidatorCommand(cdc),
|
||||||
rpc.BlockCommand(),
|
rpc.BlockCommand(),
|
||||||
tx.SearchTxCmd(cdc),
|
tx.SearchTxCmd(cdc),
|
||||||
tx.QueryTxCmd(cdc),
|
tx.QueryTxCmd(cdc),
|
||||||
|
@ -140,7 +141,8 @@ func txCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command {
|
||||||
client.LineBreak,
|
client.LineBreak,
|
||||||
authcmd.GetSignCommand(cdc),
|
authcmd.GetSignCommand(cdc),
|
||||||
authcmd.GetMultiSignCommand(cdc),
|
authcmd.GetMultiSignCommand(cdc),
|
||||||
bankcmd.GetBroadcastCommand(cdc),
|
authcmd.GetBroadcastCommand(cdc),
|
||||||
|
authcmd.GetEncodeCommand(cdc),
|
||||||
client.LineBreak,
|
client.LineBreak,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -161,6 +163,7 @@ func registerRoutes(rs *lcd.RestServer) {
|
||||||
tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
|
tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
|
||||||
auth.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, at.StoreKey)
|
auth.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, at.StoreKey)
|
||||||
bank.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
bank.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
||||||
|
dist.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, distcmd.StoreKey)
|
||||||
staking.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
staking.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
||||||
slashing.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
slashing.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
||||||
gov.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
|
gov.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
|
||||||
|
|
|
@ -43,6 +43,7 @@ func main() {
|
||||||
rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc))
|
rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc))
|
||||||
rootCmd.AddCommand(gaiaInit.GenTxCmd(ctx, cdc))
|
rootCmd.AddCommand(gaiaInit.GenTxCmd(ctx, cdc))
|
||||||
rootCmd.AddCommand(gaiaInit.AddGenesisAccountCmd(ctx, cdc))
|
rootCmd.AddCommand(gaiaInit.AddGenesisAccountCmd(ctx, cdc))
|
||||||
|
rootCmd.AddCommand(gaiaInit.ValidateGenesisCmd(ctx, cdc))
|
||||||
rootCmd.AddCommand(client.NewCompletionCmd(rootCmd, true))
|
rootCmd.AddCommand(client.NewCompletionCmd(rootCmd, true))
|
||||||
|
|
||||||
server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators)
|
server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators)
|
||||||
|
@ -59,13 +60,13 @@ func main() {
|
||||||
func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application {
|
func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application {
|
||||||
return app.NewGaiaApp(
|
return app.NewGaiaApp(
|
||||||
logger, db, traceStore, true,
|
logger, db, traceStore, true,
|
||||||
baseapp.SetPruning(store.NewPruningOptions(viper.GetString("pruning"))),
|
baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))),
|
||||||
baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)),
|
baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportAppStateAndTMValidators(
|
func exportAppStateAndTMValidators(
|
||||||
logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool,
|
logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailWhiteList []string,
|
||||||
) (json.RawMessage, []tmtypes.GenesisValidator, error) {
|
) (json.RawMessage, []tmtypes.GenesisValidator, error) {
|
||||||
if height != -1 {
|
if height != -1 {
|
||||||
gApp := app.NewGaiaApp(logger, db, traceStore, false)
|
gApp := app.NewGaiaApp(logger, db, traceStore, false)
|
||||||
|
@ -73,8 +74,8 @@ func exportAppStateAndTMValidators(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return gApp.ExportAppStateAndValidators(forZeroHeight)
|
return gApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
|
||||||
}
|
}
|
||||||
gApp := app.NewGaiaApp(logger, db, traceStore, true)
|
gApp := app.NewGaiaApp(logger, db, traceStore, true)
|
||||||
return gApp.ExportAppStateAndValidators(forZeroHeight)
|
return gApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ func runHackCmd(cmd *cobra.Command, args []string) error {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
app := NewGaiaApp(logger, db, baseapp.SetPruning(store.NewPruningOptions(viper.GetString("pruning"))))
|
app := NewGaiaApp(logger, db, baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))))
|
||||||
|
|
||||||
// print some info
|
// print some info
|
||||||
id := app.LastCommitID()
|
id := app.LastCommitID()
|
||||||
|
@ -178,7 +178,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp
|
||||||
)
|
)
|
||||||
|
|
||||||
// add handlers
|
// add handlers
|
||||||
app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper)
|
app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper, app.paramsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace)
|
||||||
app.stakingKeeper = staking.NewKeeper(app.cdc, app.keyStaking, app.tkeyStaking, app.bankKeeper, app.paramsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace)
|
app.stakingKeeper = staking.NewKeeper(app.cdc, app.keyStaking, app.tkeyStaking, app.bankKeeper, app.paramsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace)
|
||||||
app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakingKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), slashing.DefaultCodespace)
|
app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakingKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), slashing.DefaultCodespace)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package init
|
package init
|
||||||
|
|
||||||
|
// DONTCOVER
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -44,7 +46,7 @@ func CollectGenTxsCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
genDoc, err := loadGenesisDoc(cdc, config.GenesisFile())
|
genDoc, err := LoadGenesisDoc(cdc, config.GenesisFile())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package init
|
package init
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -17,7 +16,7 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddGenesisAccountCmd returns add-genesis-account cobra Command
|
// AddGenesisAccountCmd returns add-genesis-account cobra Command.
|
||||||
func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
|
func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]",
|
Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]",
|
||||||
|
@ -29,27 +28,37 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command
|
||||||
|
|
||||||
addr, err := sdk.AccAddressFromBech32(args[0])
|
addr, err := sdk.AccAddressFromBech32(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
kb, err := keys.GetKeyBaseFromDir(viper.GetString(flagClientHome))
|
kb, err := keys.NewKeyBaseFromDir(viper.GetString(flagClientHome))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := kb.Get(args[0])
|
info, err := kb.Get(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
addr = info.GetAddress()
|
addr = info.GetAddress()
|
||||||
}
|
}
|
||||||
|
|
||||||
coins, err := sdk.ParseCoins(args[1])
|
coins, err := sdk.ParseCoins(args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
coins.Sort()
|
|
||||||
|
vestingStart := viper.GetInt64(flagVestingStart)
|
||||||
|
vestingEnd := viper.GetInt64(flagVestingEnd)
|
||||||
|
vestingAmt, err := sdk.ParseCoins(viper.GetString(flagVestingAmt))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
genFile := config.GenesisFile()
|
genFile := config.GenesisFile()
|
||||||
if !common.FileExists(genFile) {
|
if !common.FileExists(genFile) {
|
||||||
return fmt.Errorf("%s does not exist, run `gaiad init` first", genFile)
|
return fmt.Errorf("%s does not exist, run `gaiad init` first", genFile)
|
||||||
}
|
}
|
||||||
genDoc, err := loadGenesisDoc(cdc, genFile)
|
|
||||||
|
genDoc, err := LoadGenesisDoc(cdc, genFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -59,7 +68,12 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
appStateJSON, err := addGenesisAccount(cdc, appState, addr, coins)
|
appState, err = addGenesisAccount(cdc, appState, addr, coins, vestingAmt, vestingStart, vestingEnd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
appStateJSON, err := cdc.MarshalJSON(appState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -70,18 +84,58 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command
|
||||||
|
|
||||||
cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory")
|
cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory")
|
||||||
cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory")
|
cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory")
|
||||||
|
cmd.Flags().String(flagVestingAmt, "", "amount of coins for vesting accounts")
|
||||||
|
cmd.Flags().Uint64(flagVestingStart, 0, "schedule start time (unix epoch) for vesting accounts")
|
||||||
|
cmd.Flags().Uint64(flagVestingEnd, 0, "schedule end time (unix epoch) for vesting accounts")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func addGenesisAccount(cdc *codec.Codec, appState app.GenesisState, addr sdk.AccAddress, coins sdk.Coins) (json.RawMessage, error) {
|
func addGenesisAccount(
|
||||||
|
cdc *codec.Codec, appState app.GenesisState, addr sdk.AccAddress,
|
||||||
|
coins, vestingAmt sdk.Coins, vestingStart, vestingEnd int64,
|
||||||
|
) (app.GenesisState, error) {
|
||||||
|
|
||||||
for _, stateAcc := range appState.Accounts {
|
for _, stateAcc := range appState.Accounts {
|
||||||
if stateAcc.Address.Equals(addr) {
|
if stateAcc.Address.Equals(addr) {
|
||||||
return nil, fmt.Errorf("the application state already contains account %v", addr)
|
return appState, fmt.Errorf("the application state already contains account %v", addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
acc := auth.NewBaseAccountWithAddress(addr)
|
acc := auth.NewBaseAccountWithAddress(addr)
|
||||||
acc.Coins = coins
|
acc.Coins = coins
|
||||||
appState.Accounts = append(appState.Accounts, app.NewGenesisAccount(&acc))
|
|
||||||
return cdc.MarshalJSON(appState)
|
if !vestingAmt.IsZero() {
|
||||||
|
var vacc auth.VestingAccount
|
||||||
|
|
||||||
|
bvacc := &auth.BaseVestingAccount{
|
||||||
|
BaseAccount: &acc,
|
||||||
|
OriginalVesting: vestingAmt,
|
||||||
|
EndTime: vestingEnd,
|
||||||
|
}
|
||||||
|
|
||||||
|
if bvacc.OriginalVesting.IsAllGT(acc.Coins) {
|
||||||
|
return appState, fmt.Errorf("vesting amount cannot be greater than total amount")
|
||||||
|
}
|
||||||
|
if vestingStart >= vestingEnd {
|
||||||
|
return appState, fmt.Errorf("vesting start time must before end time")
|
||||||
|
}
|
||||||
|
|
||||||
|
if vestingStart != 0 {
|
||||||
|
vacc = &auth.ContinuousVestingAccount{
|
||||||
|
BaseVestingAccount: bvacc,
|
||||||
|
StartTime: vestingStart,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vacc = &auth.DelayedVestingAccount{
|
||||||
|
BaseVestingAccount: bvacc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appState.Accounts = append(appState.Accounts, app.NewGenesisAccountI(vacc))
|
||||||
|
} else {
|
||||||
|
appState.Accounts = append(appState.Accounts, app.NewGenesisAccount(&acc))
|
||||||
|
}
|
||||||
|
|
||||||
|
return appState, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,12 @@ func TestAddGenesisAccount(t *testing.T) {
|
||||||
cdc := codec.New()
|
cdc := codec.New()
|
||||||
addr1 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
|
addr1 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
|
||||||
type args struct {
|
type args struct {
|
||||||
appState app.GenesisState
|
appState app.GenesisState
|
||||||
addr sdk.AccAddress
|
addr sdk.AccAddress
|
||||||
coins sdk.Coins
|
coins sdk.Coins
|
||||||
|
vestingAmt sdk.Coins
|
||||||
|
vestingStart int64
|
||||||
|
vestingEnd int64
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -30,16 +33,55 @@ func TestAddGenesisAccount(t *testing.T) {
|
||||||
app.GenesisState{},
|
app.GenesisState{},
|
||||||
addr1,
|
addr1,
|
||||||
sdk.Coins{},
|
sdk.Coins{},
|
||||||
|
sdk.Coins{},
|
||||||
|
0,
|
||||||
|
0,
|
||||||
},
|
},
|
||||||
false},
|
false,
|
||||||
{"dup account", args{
|
},
|
||||||
app.GenesisState{Accounts: []app.GenesisAccount{{Address: addr1}}},
|
{
|
||||||
addr1,
|
"dup account",
|
||||||
sdk.Coins{}}, true},
|
args{
|
||||||
|
app.GenesisState{Accounts: []app.GenesisAccount{{Address: addr1}}},
|
||||||
|
addr1,
|
||||||
|
sdk.Coins{},
|
||||||
|
sdk.Coins{},
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid vesting amount",
|
||||||
|
args{
|
||||||
|
app.GenesisState{},
|
||||||
|
addr1,
|
||||||
|
sdk.Coins{sdk.NewInt64Coin("stake", 50)},
|
||||||
|
sdk.Coins{sdk.NewInt64Coin("stake", 100)},
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid vesting times",
|
||||||
|
args{
|
||||||
|
app.GenesisState{},
|
||||||
|
addr1,
|
||||||
|
sdk.Coins{sdk.NewInt64Coin("stake", 50)},
|
||||||
|
sdk.Coins{sdk.NewInt64Coin("stake", 50)},
|
||||||
|
1654668078,
|
||||||
|
1554668078,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
_, err := addGenesisAccount(cdc, tt.args.appState, tt.args.addr, tt.args.coins)
|
_, err := addGenesisAccount(
|
||||||
|
cdc, tt.args.appState, tt.args.addr, tt.args.coins,
|
||||||
|
tt.args.vestingAmt, tt.args.vestingStart, tt.args.vestingEnd,
|
||||||
|
)
|
||||||
require.Equal(t, tt.wantErr, (err != nil))
|
require.Equal(t, tt.wantErr, (err != nil))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package init
|
package init
|
||||||
|
|
||||||
|
// DONTCOVER
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -26,15 +28,17 @@ import (
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/staking/client/cli"
|
||||||
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var (
|
||||||
defaultAmount = "100" + stakingTypes.DefaultBondDenom
|
defaultTokens = staking.TokensFromTendermintPower(100)
|
||||||
|
defaultAmount = defaultTokens.String() + staking.DefaultBondDenom
|
||||||
defaultCommissionRate = "0.1"
|
defaultCommissionRate = "0.1"
|
||||||
defaultCommissionMaxRate = "0.2"
|
defaultCommissionMaxRate = "0.2"
|
||||||
defaultCommissionMaxChangeRate = "0.01"
|
defaultCommissionMaxChangeRate = "0.01"
|
||||||
|
defaultMinSelfDelegation = "1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenTxCmd builds the gaiad gentx command.
|
// GenTxCmd builds the gaiad gentx command.
|
||||||
|
@ -52,7 +56,8 @@ following delegation and commission default parameters:
|
||||||
commission rate: %s
|
commission rate: %s
|
||||||
commission max rate: %s
|
commission max rate: %s
|
||||||
commission max change rate: %s
|
commission max change rate: %s
|
||||||
`, defaultAmount, defaultCommissionRate, defaultCommissionMaxRate, defaultCommissionMaxChangeRate),
|
minimum self delegation: %s
|
||||||
|
`, defaultAmount, defaultCommissionRate, defaultCommissionMaxRate, defaultCommissionMaxChangeRate, defaultMinSelfDelegation),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
config := ctx.Config
|
config := ctx.Config
|
||||||
|
@ -61,12 +66,19 @@ following delegation and commission default parameters:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ip, err := server.ExternalIP()
|
|
||||||
if err != nil {
|
// Read --nodeID, if empty take it from priv_validator.json
|
||||||
return err
|
if nodeIDString := viper.GetString(cli.FlagNodeID); nodeIDString != "" {
|
||||||
|
nodeID = nodeIDString
|
||||||
}
|
}
|
||||||
|
|
||||||
genDoc, err := loadGenesisDoc(cdc, config.GenesisFile())
|
ip := viper.GetString(cli.FlagIP)
|
||||||
|
if ip == "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "couldn't retrieve an external IP; "+
|
||||||
|
"the tx's memo field will be unset")
|
||||||
|
}
|
||||||
|
|
||||||
|
genDoc, err := LoadGenesisDoc(cdc, config.GenesisFile())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -76,7 +88,7 @@ following delegation and commission default parameters:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
kb, err := keys.GetKeyBaseFromDir(viper.GetString(flagClientHome))
|
kb, err := keys.NewKeyBaseFromDir(viper.GetString(flagClientHome))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -120,7 +132,8 @@ following delegation and commission default parameters:
|
||||||
|
|
||||||
// write the unsigned transaction to the buffer
|
// write the unsigned transaction to the buffer
|
||||||
w := bytes.NewBuffer([]byte{})
|
w := bytes.NewBuffer([]byte{})
|
||||||
if err := utils.PrintUnsignedStdTx(w, txBldr, cliCtx, []sdk.Msg{msg}, true); err != nil {
|
cliCtx = cliCtx.WithOutput(w)
|
||||||
|
if err = utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,12 +167,17 @@ following delegation and commission default parameters:
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ip, _ := server.ExternalIP()
|
||||||
|
|
||||||
cmd.Flags().String(tmcli.HomeFlag, app.DefaultNodeHome, "node's home directory")
|
cmd.Flags().String(tmcli.HomeFlag, app.DefaultNodeHome, "node's home directory")
|
||||||
cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory")
|
cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory")
|
||||||
cmd.Flags().String(client.FlagName, "", "name of private key with which to sign the gentx")
|
cmd.Flags().String(client.FlagName, "", "name of private key with which to sign the gentx")
|
||||||
cmd.Flags().String(client.FlagOutputDocument, "",
|
cmd.Flags().String(client.FlagOutputDocument, "",
|
||||||
"write the genesis transaction JSON document to the given file instead of the default location")
|
"write the genesis transaction JSON document to the given file instead of the default location")
|
||||||
|
cmd.Flags().String(cli.FlagIP, ip, "The node's public IP")
|
||||||
|
cmd.Flags().String(cli.FlagNodeID, "", "The node's NodeID")
|
||||||
cmd.Flags().AddFlagSet(cli.FsCommissionCreate)
|
cmd.Flags().AddFlagSet(cli.FsCommissionCreate)
|
||||||
|
cmd.Flags().AddFlagSet(cli.FsMinSelfDelegation)
|
||||||
cmd.Flags().AddFlagSet(cli.FsAmount)
|
cmd.Flags().AddFlagSet(cli.FsAmount)
|
||||||
cmd.Flags().AddFlagSet(cli.FsPk)
|
cmd.Flags().AddFlagSet(cli.FsPk)
|
||||||
cmd.MarkFlagRequired(client.FlagName)
|
cmd.MarkFlagRequired(client.FlagName)
|
||||||
|
@ -202,7 +220,7 @@ func prepareFlagsForTxCreateValidator(config *cfg.Config, nodeID, ip, chainID st
|
||||||
viper.Set(cli.FlagNodeID, nodeID) // --node-id
|
viper.Set(cli.FlagNodeID, nodeID) // --node-id
|
||||||
viper.Set(cli.FlagIP, ip) // --ip
|
viper.Set(cli.FlagIP, ip) // --ip
|
||||||
viper.Set(cli.FlagPubKey, sdk.MustBech32ifyConsPub(valPubKey)) // --pubkey
|
viper.Set(cli.FlagPubKey, sdk.MustBech32ifyConsPub(valPubKey)) // --pubkey
|
||||||
viper.Set(cli.FlagGenesisFormat, true) // --genesis-format
|
viper.Set(client.FlagGenerateOnly, true) // --genesis-format
|
||||||
viper.Set(cli.FlagMoniker, config.Moniker) // --moniker
|
viper.Set(cli.FlagMoniker, config.Moniker) // --moniker
|
||||||
if config.Moniker == "" {
|
if config.Moniker == "" {
|
||||||
viper.Set(cli.FlagMoniker, viper.GetString(client.FlagName))
|
viper.Set(cli.FlagMoniker, viper.GetString(client.FlagName))
|
||||||
|
@ -219,6 +237,9 @@ func prepareFlagsForTxCreateValidator(config *cfg.Config, nodeID, ip, chainID st
|
||||||
if viper.GetString(cli.FlagCommissionMaxChangeRate) == "" {
|
if viper.GetString(cli.FlagCommissionMaxChangeRate) == "" {
|
||||||
viper.Set(cli.FlagCommissionMaxChangeRate, defaultCommissionMaxChangeRate)
|
viper.Set(cli.FlagCommissionMaxChangeRate, defaultCommissionMaxChangeRate)
|
||||||
}
|
}
|
||||||
|
if viper.GetString(cli.FlagMinSelfDelegation) == "" {
|
||||||
|
viper.Set(cli.FlagMinSelfDelegation, defaultMinSelfDelegation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeOutputFilepath(rootDir, nodeID string) (string, error) {
|
func makeOutputFilepath(rootDir, nodeID string) (string, error) {
|
||||||
|
|
|
@ -19,9 +19,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
flagOverwrite = "overwrite"
|
flagOverwrite = "overwrite"
|
||||||
flagClientHome = "home-client"
|
flagClientHome = "home-client"
|
||||||
flagMoniker = "moniker"
|
flagVestingStart = "vesting-start-time"
|
||||||
|
flagVestingEnd = "vesting-end-time"
|
||||||
|
flagVestingAmt = "vesting-amount"
|
||||||
)
|
)
|
||||||
|
|
||||||
type printInfo struct {
|
type printInfo struct {
|
||||||
|
@ -32,25 +34,25 @@ type printInfo struct {
|
||||||
AppMessage json.RawMessage `json:"app_message"`
|
AppMessage json.RawMessage `json:"app_message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint: errcheck
|
|
||||||
func displayInfo(cdc *codec.Codec, info printInfo) error {
|
func displayInfo(cdc *codec.Codec, info printInfo) error {
|
||||||
out, err := codec.MarshalJSONIndent(cdc, info)
|
out, err := codec.MarshalJSONIndent(cdc, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", string(out))
|
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", string(out)) // nolint: errcheck
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// get cmd to initialize all files for tendermint and application
|
// InitCmd returns a command that initializes all files needed for Tendermint
|
||||||
// nolint
|
// and the respective application.
|
||||||
func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
|
func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { // nolint: golint
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "init",
|
Use: "init [moniker]",
|
||||||
Short: "Initialize private validator, p2p, genesis, and application configuration files",
|
Short: "Initialize private validator, p2p, genesis, and application configuration files",
|
||||||
Long: `Initialize validators's and node's configuration files.`,
|
Long: `Initialize validators's and node's configuration files.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: func(_ *cobra.Command, _ []string) error {
|
RunE: func(_ *cobra.Command, args []string) error {
|
||||||
config := ctx.Config
|
config := ctx.Config
|
||||||
config.SetRoot(viper.GetString(cli.HomeFlag))
|
config.SetRoot(viper.GetString(cli.HomeFlag))
|
||||||
|
|
||||||
|
@ -64,7 +66,7 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Moniker = viper.GetString(flagMoniker)
|
config.Moniker = args[0]
|
||||||
|
|
||||||
var appState json.RawMessage
|
var appState json.RawMessage
|
||||||
genFile := config.GenesisFile()
|
genFile := config.GenesisFile()
|
||||||
|
@ -81,7 +83,6 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
|
||||||
toPrint := newPrintInfo(config.Moniker, chainID, nodeID, "", appState)
|
toPrint := newPrintInfo(config.Moniker, chainID, nodeID, "", appState)
|
||||||
|
|
||||||
cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config)
|
cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config)
|
||||||
|
|
||||||
return displayInfo(cdc, toPrint)
|
return displayInfo(cdc, toPrint)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -89,8 +90,6 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
|
||||||
cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory")
|
cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory")
|
||||||
cmd.Flags().BoolP(flagOverwrite, "o", false, "overwrite the genesis.json file")
|
cmd.Flags().BoolP(flagOverwrite, "o", false, "overwrite the genesis.json file")
|
||||||
cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
|
cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
|
||||||
cmd.Flags().String(flagMoniker, "", "set the validator's moniker")
|
|
||||||
cmd.MarkFlagRequired(flagMoniker)
|
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,20 +8,16 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/libs/cli"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||||
|
"github.com/cosmos/cosmos-sdk/server"
|
||||||
|
"github.com/cosmos/cosmos-sdk/server/mock"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
abciServer "github.com/tendermint/tendermint/abci/server"
|
abciServer "github.com/tendermint/tendermint/abci/server"
|
||||||
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
|
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
|
||||||
|
"github.com/tendermint/tendermint/libs/cli"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/server"
|
|
||||||
"github.com/cosmos/cosmos-sdk/server/mock"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInitCmd(t *testing.T) {
|
func TestInitCmd(t *testing.T) {
|
||||||
|
@ -36,10 +32,7 @@ func TestInitCmd(t *testing.T) {
|
||||||
cdc := app.MakeCodec()
|
cdc := app.MakeCodec()
|
||||||
cmd := InitCmd(ctx, cdc)
|
cmd := InitCmd(ctx, cdc)
|
||||||
|
|
||||||
viper.Set(flagMoniker, "gaianode-test")
|
require.NoError(t, cmd.RunE(nil, []string{"gaianode-test"}))
|
||||||
|
|
||||||
err = cmd.RunE(nil, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupClientHome(t *testing.T) func() {
|
func setupClientHome(t *testing.T) func() {
|
||||||
|
@ -64,11 +57,9 @@ func TestEmptyState(t *testing.T) {
|
||||||
|
|
||||||
ctx := server.NewContext(cfg, logger)
|
ctx := server.NewContext(cfg, logger)
|
||||||
cdc := app.MakeCodec()
|
cdc := app.MakeCodec()
|
||||||
viper.Set(flagMoniker, "gaianode-test")
|
|
||||||
|
|
||||||
cmd := InitCmd(ctx, cdc)
|
cmd := InitCmd(ctx, cdc)
|
||||||
err = cmd.RunE(nil, nil)
|
require.NoError(t, cmd.RunE(nil, []string{"gaianode-test"}))
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
old := os.Stdout
|
old := os.Stdout
|
||||||
r, w, _ := os.Pipe()
|
r, w, _ := os.Pipe()
|
||||||
|
@ -88,7 +79,6 @@ func TestEmptyState(t *testing.T) {
|
||||||
w.Close()
|
w.Close()
|
||||||
os.Stdout = old
|
os.Stdout = old
|
||||||
out := <-outC
|
out := <-outC
|
||||||
require.Contains(t, out, "WARNING: State is not initialized")
|
|
||||||
require.Contains(t, out, "genesis_time")
|
require.Contains(t, out, "genesis_time")
|
||||||
require.Contains(t, out, "chain_id")
|
require.Contains(t, out, "chain_id")
|
||||||
require.Contains(t, out, "consensus_params")
|
require.Contains(t, out, "consensus_params")
|
||||||
|
@ -103,7 +93,6 @@ func TestStartStandAlone(t *testing.T) {
|
||||||
os.RemoveAll(home)
|
os.RemoveAll(home)
|
||||||
}()
|
}()
|
||||||
viper.Set(cli.HomeFlag, home)
|
viper.Set(cli.HomeFlag, home)
|
||||||
viper.Set(client.FlagName, "moniker")
|
|
||||||
defer setupClientHome(t)()
|
defer setupClientHome(t)()
|
||||||
|
|
||||||
logger := log.NewNopLogger()
|
logger := log.NewNopLogger()
|
||||||
|
@ -112,8 +101,7 @@ func TestStartStandAlone(t *testing.T) {
|
||||||
ctx := server.NewContext(cfg, logger)
|
ctx := server.NewContext(cfg, logger)
|
||||||
cdc := app.MakeCodec()
|
cdc := app.MakeCodec()
|
||||||
initCmd := InitCmd(ctx, cdc)
|
initCmd := InitCmd(ctx, cdc)
|
||||||
err = initCmd.RunE(nil, nil)
|
require.NoError(t, initCmd.RunE(nil, []string{"gaianode-test"}))
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
app, err := mock.NewApp(home, logger)
|
app, err := mock.NewApp(home, logger)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package init
|
package init
|
||||||
|
|
||||||
|
// DONTCOVER
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -7,6 +9,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
@ -188,23 +192,31 @@ func initTestnet(config *tmconfig.Config, cdc *codec.Codec) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accTokens := staking.TokensFromTendermintPower(1000)
|
||||||
|
accStakingTokens := staking.TokensFromTendermintPower(500)
|
||||||
accs = append(accs, app.GenesisAccount{
|
accs = append(accs, app.GenesisAccount{
|
||||||
Address: addr,
|
Address: addr,
|
||||||
Coins: sdk.Coins{
|
Coins: sdk.Coins{
|
||||||
sdk.NewInt64Coin(fmt.Sprintf("%stoken", nodeDirName), 1000),
|
sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), accTokens),
|
||||||
sdk.NewInt64Coin(stakingtypes.DefaultBondDenom, 500),
|
sdk.NewCoin(stakingtypes.DefaultBondDenom, accStakingTokens),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
valTokens := staking.TokensFromTendermintPower(100)
|
||||||
msg := staking.NewMsgCreateValidator(
|
msg := staking.NewMsgCreateValidator(
|
||||||
sdk.ValAddress(addr),
|
sdk.ValAddress(addr),
|
||||||
valPubKeys[i],
|
valPubKeys[i],
|
||||||
sdk.NewInt64Coin(stakingtypes.DefaultBondDenom, 100),
|
sdk.NewCoin(stakingtypes.DefaultBondDenom, valTokens),
|
||||||
staking.NewDescription(nodeDirName, "", "", ""),
|
staking.NewDescription(nodeDirName, "", "", ""),
|
||||||
staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
|
staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
|
||||||
|
sdk.OneInt(),
|
||||||
)
|
)
|
||||||
|
kb, err := keys.NewKeyBaseFromDir(clientDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo)
|
tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo)
|
||||||
txBldr := authtx.NewTxBuilderFromCLI().WithChainID(chainID).WithMemo(memo)
|
txBldr := authtx.NewTxBuilderFromCLI().WithChainID(chainID).WithMemo(memo).WithKeybase(kb)
|
||||||
|
|
||||||
signedTx, err := txBldr.SignStdTx(nodeDirName, app.DefaultKeyPass, tx, false)
|
signedTx, err := txBldr.SignStdTx(nodeDirName, app.DefaultKeyPass, tx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -295,7 +307,7 @@ func collectGenFiles(
|
||||||
nodeID, valPubKey := nodeIDs[i], valPubKeys[i]
|
nodeID, valPubKey := nodeIDs[i], valPubKeys[i]
|
||||||
initCfg := newInitConfig(chainID, gentxsDir, moniker, nodeID, valPubKey)
|
initCfg := newInitConfig(chainID, gentxsDir, moniker, nodeID, valPubKey)
|
||||||
|
|
||||||
genDoc, err := loadGenesisDoc(cdc, config.GenesisFile())
|
genDoc, err := LoadGenesisDoc(cdc, config.GenesisFile())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,8 @@ func InitializeNodeValidatorFiles(
|
||||||
return nodeID, valPubKey, nil
|
return nodeID, valPubKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadGenesisDoc(cdc *amino.Codec, genFile string) (genDoc types.GenesisDoc, err error) {
|
// LoadGenesisDoc reads and unmarshals GenesisDoc from the given file.
|
||||||
|
func LoadGenesisDoc(cdc *amino.Codec, genFile string) (genDoc types.GenesisDoc, err error) {
|
||||||
genContents, err := ioutil.ReadFile(genFile)
|
genContents, err := ioutil.ReadFile(genFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return genDoc, err
|
return genDoc, err
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package init
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExportGenesisFileWithTime(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
dir, cleanup := tests.NewTestCaseDir(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
fname := filepath.Join(dir, "genesis.json")
|
||||||
|
require.NoError(t, ExportGenesisFileWithTime(fname, "test", nil, json.RawMessage(""), time.Now()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadGenesisDoc(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
dir, cleanup := tests.NewTestCaseDir(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
fname := filepath.Join(dir, "genesis.json")
|
||||||
|
require.NoError(t, ExportGenesisFileWithTime(fname, "test", nil, json.RawMessage(""), time.Now()))
|
||||||
|
|
||||||
|
_, err := LoadGenesisDoc(codec.Cdc, fname)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Non-existing file
|
||||||
|
_, err = LoadGenesisDoc(codec.Cdc, "non-existing-file")
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
malformedFilename := filepath.Join(dir, "malformed")
|
||||||
|
malformedFile, err := os.Create(malformedFilename)
|
||||||
|
require.NoError(t, err)
|
||||||
|
fmt.Fprint(malformedFile, "invalidjson")
|
||||||
|
malformedFile.Close()
|
||||||
|
// Non-existing file
|
||||||
|
_, err = LoadGenesisDoc(codec.Cdc, malformedFilename)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package init
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/server"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/tendermint/tendermint/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validate genesis command takes
|
||||||
|
func ValidateGenesisCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "validate-genesis [file]",
|
||||||
|
Args: cobra.RangeArgs(0, 1),
|
||||||
|
Short: "validates the genesis file at the default location or at the location passed as an arg",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
|
||||||
|
// Load default if passed no args, otherwise load passed file
|
||||||
|
var genesis string
|
||||||
|
if len(args) == 0 {
|
||||||
|
genesis = ctx.Config.GenesisFile()
|
||||||
|
} else {
|
||||||
|
genesis = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint
|
||||||
|
fmt.Fprintf(os.Stderr, "validating genesis file at %s\n", genesis)
|
||||||
|
|
||||||
|
var genDoc types.GenesisDoc
|
||||||
|
if genDoc, err = LoadGenesisDoc(cdc, genesis); err != nil {
|
||||||
|
return fmt.Errorf("Error loading genesis doc from %s: %s", genesis, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
var genstate app.GenesisState
|
||||||
|
if err = cdc.UnmarshalJSON(genDoc.AppState, &genstate); err != nil {
|
||||||
|
return fmt.Errorf("Error unmarshaling genesis doc %s: %s", genesis, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = app.GaiaValidateGenesisState(genstate); err != nil {
|
||||||
|
return fmt.Errorf("Error validating genesis file %s: %s", genesis, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("File at %s is a valid genesis file for gaiad\n", genesis)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ package hd
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -62,19 +63,7 @@ func NewParamsFromPath(path string) (*BIP44Params, error) {
|
||||||
return nil, fmt.Errorf("path length is wrong. Expected 5, got %d", len(spl))
|
return nil, fmt.Errorf("path length is wrong. Expected 5, got %d", len(spl))
|
||||||
}
|
}
|
||||||
|
|
||||||
if spl[0] != "44'" {
|
// Check items can be parsed
|
||||||
return nil, fmt.Errorf("first field in path must be 44', got %v", spl[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isHardened(spl[1]) || !isHardened(spl[2]) {
|
|
||||||
return nil,
|
|
||||||
fmt.Errorf("second and third field in path must be hardened (ie. contain the suffix ', got %v and %v", spl[1], spl[2])
|
|
||||||
}
|
|
||||||
if isHardened(spl[3]) || isHardened(spl[4]) {
|
|
||||||
return nil,
|
|
||||||
fmt.Errorf("fourth and fifth field in path must not be hardened (ie. not contain the suffix ', got %v and %v", spl[3], spl[4])
|
|
||||||
}
|
|
||||||
|
|
||||||
purpose, err := hardenedInt(spl[0])
|
purpose, err := hardenedInt(spl[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -91,15 +80,30 @@ func NewParamsFromPath(path string) (*BIP44Params, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !(change == 0 || change == 1) {
|
|
||||||
return nil, fmt.Errorf("change field can only be 0 or 1")
|
|
||||||
}
|
|
||||||
|
|
||||||
addressIdx, err := hardenedInt(spl[4])
|
addressIdx, err := hardenedInt(spl[4])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Confirm valid values
|
||||||
|
if spl[0] != "44'" {
|
||||||
|
return nil, fmt.Errorf("first field in path must be 44', got %v", spl[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isHardened(spl[1]) || !isHardened(spl[2]) {
|
||||||
|
return nil,
|
||||||
|
fmt.Errorf("second and third field in path must be hardened (ie. contain the suffix ', got %v and %v", spl[1], spl[2])
|
||||||
|
}
|
||||||
|
if isHardened(spl[3]) || isHardened(spl[4]) {
|
||||||
|
return nil,
|
||||||
|
fmt.Errorf("fourth and fifth field in path must not be hardened (ie. not contain the suffix ', got %v and %v", spl[3], spl[4])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(change == 0 || change == 1) {
|
||||||
|
return nil, fmt.Errorf("change field can only be 0 or 1")
|
||||||
|
}
|
||||||
|
|
||||||
return &BIP44Params{
|
return &BIP44Params{
|
||||||
purpose: purpose,
|
purpose: purpose,
|
||||||
coinType: coinType,
|
coinType: coinType,
|
||||||
|
@ -132,7 +136,7 @@ func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params {
|
||||||
return NewParams(44, 118, account, false, addressIdx)
|
return NewParams(44, 118, account, false, addressIdx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the BIP44 fields as an array.
|
// DerivationPath returns the BIP44 fields as an array.
|
||||||
func (p BIP44Params) DerivationPath() []uint32 {
|
func (p BIP44Params) DerivationPath() []uint32 {
|
||||||
change := uint32(0)
|
change := uint32(0)
|
||||||
if p.change {
|
if p.change {
|
||||||
|
@ -251,8 +255,10 @@ func i64(key []byte, data []byte) (IL [32]byte, IR [32]byte) {
|
||||||
mac := hmac.New(sha512.New, key)
|
mac := hmac.New(sha512.New, key)
|
||||||
// sha512 does not err
|
// sha512 does not err
|
||||||
_, _ = mac.Write(data)
|
_, _ = mac.Write(data)
|
||||||
|
|
||||||
I := mac.Sum(nil)
|
I := mac.Sum(nil)
|
||||||
copy(IL[:], I[:32])
|
copy(IL[:], I[:32])
|
||||||
copy(IR[:], I[32:])
|
copy(IR[:], I[32:])
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/cosmos/go-bip39"
|
"github.com/cosmos/go-bip39"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultBIP39Passphrase = ""
|
var defaultBIP39Passphrase = ""
|
||||||
|
@ -21,7 +21,27 @@ func mnemonicToSeed(mnemonic string) []byte {
|
||||||
func ExampleStringifyPathParams() {
|
func ExampleStringifyPathParams() {
|
||||||
path := NewParams(44, 0, 0, false, 0)
|
path := NewParams(44, 0, 0, false, 0)
|
||||||
fmt.Println(path.String())
|
fmt.Println(path.String())
|
||||||
// Output: 44'/0'/0'/0/0
|
path = NewParams(44, 33, 7, true, 9)
|
||||||
|
fmt.Println(path.String())
|
||||||
|
// Output:
|
||||||
|
// 44'/0'/0'/0/0
|
||||||
|
// 44'/33'/7'/1/9
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringifyFundraiserPathParams(t *testing.T) {
|
||||||
|
path := NewFundraiserParams(4, 22)
|
||||||
|
require.Equal(t, "44'/118'/4'/0/22", path.String())
|
||||||
|
|
||||||
|
path = NewFundraiserParams(4, 57)
|
||||||
|
require.Equal(t, "44'/118'/4'/0/57", path.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathToArray(t *testing.T) {
|
||||||
|
path := NewParams(44, 118, 1, false, 4)
|
||||||
|
require.Equal(t, "[44 118 1 0 4]", fmt.Sprintf("%v", path.DerivationPath()))
|
||||||
|
|
||||||
|
path = NewParams(44, 118, 2, true, 15)
|
||||||
|
require.Equal(t, "[44 118 2 1 15]", fmt.Sprintf("%v", path.DerivationPath()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParamsFromPath(t *testing.T) {
|
func TestParamsFromPath(t *testing.T) {
|
||||||
|
@ -60,6 +80,11 @@ func TestParamsFromPath(t *testing.T) {
|
||||||
{"44'/0'/0'/0/0'"}, // fifth field must not have '
|
{"44'/0'/0'/0/0'"}, // fifth field must not have '
|
||||||
{"44'/-1'/0'/0/0"}, // no negatives
|
{"44'/-1'/0'/0/0"}, // no negatives
|
||||||
{"44'/0'/0'/-1/0"}, // no negatives
|
{"44'/0'/0'/-1/0"}, // no negatives
|
||||||
|
{"a'/0'/0'/-1/0"}, // valid values
|
||||||
|
{"0/X/0'/-1/0"}, // valid values
|
||||||
|
{"44'/0'/X/-1/0"}, // valid values
|
||||||
|
{"44'/0'/0'/%/0"}, // valid values
|
||||||
|
{"44'/0'/0'/0/%"}, // valid values
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, c := range badCases {
|
for i, c := range badCases {
|
||||||
|
@ -80,14 +105,39 @@ func ExampleSomeBIP32TestVecs() {
|
||||||
fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)")
|
fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
// cosmos
|
// cosmos
|
||||||
priv, _ := DerivePrivateKeyForPath(master, ch, FullFundraiserPath)
|
priv, err := DerivePrivateKeyForPath(master, ch, FullFundraiserPath)
|
||||||
fmt.Println(hex.EncodeToString(priv[:]))
|
if err != nil {
|
||||||
|
fmt.Println("INVALID")
|
||||||
|
} else {
|
||||||
|
fmt.Println(hex.EncodeToString(priv[:]))
|
||||||
|
}
|
||||||
// bitcoin
|
// bitcoin
|
||||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0")
|
priv, err = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0")
|
||||||
fmt.Println(hex.EncodeToString(priv[:]))
|
if err != nil {
|
||||||
|
fmt.Println("INVALID")
|
||||||
|
} else {
|
||||||
|
fmt.Println(hex.EncodeToString(priv[:]))
|
||||||
|
}
|
||||||
// ether
|
// ether
|
||||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0")
|
priv, err = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0")
|
||||||
fmt.Println(hex.EncodeToString(priv[:]))
|
if err != nil {
|
||||||
|
fmt.Println("INVALID")
|
||||||
|
} else {
|
||||||
|
fmt.Println(hex.EncodeToString(priv[:]))
|
||||||
|
}
|
||||||
|
// INVALID
|
||||||
|
priv, err = DerivePrivateKeyForPath(master, ch, "X/0'/0'/0/0")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("INVALID")
|
||||||
|
} else {
|
||||||
|
fmt.Println(hex.EncodeToString(priv[:]))
|
||||||
|
}
|
||||||
|
priv, err = DerivePrivateKeyForPath(master, ch, "-44/0'/0'/0/0")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("INVALID")
|
||||||
|
} else {
|
||||||
|
fmt.Println(hex.EncodeToString(priv[:]))
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html")
|
fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html")
|
||||||
|
@ -121,6 +171,8 @@ func ExampleSomeBIP32TestVecs() {
|
||||||
// bfcb217c058d8bbafd5e186eae936106ca3e943889b0b4a093ae13822fd3170c
|
// bfcb217c058d8bbafd5e186eae936106ca3e943889b0b4a093ae13822fd3170c
|
||||||
// e77c3de76965ad89997451de97b95bb65ede23a6bf185a55d80363d92ee37c3d
|
// e77c3de76965ad89997451de97b95bb65ede23a6bf185a55d80363d92ee37c3d
|
||||||
// 7fc4d8a8146dea344ba04c593517d3f377fa6cded36cd55aee0a0bb968e651bc
|
// 7fc4d8a8146dea344ba04c593517d3f377fa6cded36cd55aee0a0bb968e651bc
|
||||||
|
// INVALID
|
||||||
|
// INVALID
|
||||||
//
|
//
|
||||||
// keys generated via https://coinomi.com/recovery-phrase-tool.html
|
// keys generated via https://coinomi.com/recovery-phrase-tool.html
|
||||||
//
|
//
|
||||||
|
|
|
@ -4,23 +4,23 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/cosmos/go-bip39"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto"
|
"github.com/cosmos/cosmos-sdk/crypto"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
||||||
"github.com/cosmos/cosmos-sdk/types"
|
"github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/cosmos/go-bip39"
|
||||||
|
|
||||||
tmcrypto "github.com/tendermint/tendermint/crypto"
|
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||||
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||||
dbm "github.com/tendermint/tendermint/libs/db"
|
dbm "github.com/tendermint/tendermint/libs/db"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Keybase = dbKeybase{}
|
var _ Keybase = dbKeybase{}
|
||||||
|
@ -30,6 +30,7 @@ var _ Keybase = dbKeybase{}
|
||||||
// Find a list of all supported languages in the BIP 39 spec (word lists).
|
// Find a list of all supported languages in the BIP 39 spec (word lists).
|
||||||
type Language int
|
type Language int
|
||||||
|
|
||||||
|
//noinspection ALL
|
||||||
const (
|
const (
|
||||||
// English is the default language to create a mnemonic.
|
// English is the default language to create a mnemonic.
|
||||||
// It is the only supported language by this package.
|
// It is the only supported language by this package.
|
||||||
|
@ -54,7 +55,7 @@ const (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// used for deriving seed from mnemonic
|
// used for deriving seed from mnemonic
|
||||||
defaultBIP39Passphrase = ""
|
DefaultBIP39Passphrase = ""
|
||||||
|
|
||||||
// bits of entropy to draw when creating a mnemonic
|
// bits of entropy to draw when creating a mnemonic
|
||||||
defaultEntropySize = 256
|
defaultEntropySize = 256
|
||||||
|
@ -83,6 +84,9 @@ func New(db dbm.DB) Keybase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewInMemory creates a new keybase on top of in-memory storage instance.
|
||||||
|
func NewInMemory() Keybase { return dbKeybase{dbm.NewMemDB()} }
|
||||||
|
|
||||||
// CreateMnemonic generates a new key and persists it to storage, encrypted
|
// CreateMnemonic generates a new key and persists it to storage, encrypted
|
||||||
// using the provided password.
|
// using the provided password.
|
||||||
// It returns the generated mnemonic and the key Info.
|
// It returns the generated mnemonic and the key Info.
|
||||||
|
@ -109,41 +113,15 @@ func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
seed := bip39.NewSeed(mnemonic, defaultBIP39Passphrase)
|
seed := bip39.NewSeed(mnemonic, DefaultBIP39Passphrase)
|
||||||
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
|
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEMPORARY METHOD UNTIL WE FIGURE OUT USER FACING HD DERIVATION API
|
// CreateAccount converts a mnemonic to a private key and persists it, encrypted with the given password.
|
||||||
func (kb dbKeybase) CreateKey(name, mnemonic, passwd string) (info Info, err error) {
|
func (kb dbKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) {
|
||||||
words := strings.Split(mnemonic, " ")
|
hdPath := hd.NewFundraiserParams(account, index)
|
||||||
if len(words) != 12 && len(words) != 24 {
|
return kb.Derive(name, mnemonic, bip39Passwd, encryptPasswd, *hdPath)
|
||||||
err = fmt.Errorf("recovering only works with 12 word (fundraiser) or 24 word mnemonics, got: %v words", len(words))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFundraiserKey converts a mnemonic to a private key and persists it,
|
|
||||||
// encrypted with the given password.
|
|
||||||
// TODO(ismail)
|
|
||||||
func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error) {
|
|
||||||
words := strings.Split(mnemonic, " ")
|
|
||||||
if len(words) != 12 {
|
|
||||||
err = fmt.Errorf("recovering only works with 12 word (fundraiser), got: %v words", len(words))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params) (info Info, err error) {
|
func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params) (info Info, err error) {
|
||||||
|
@ -151,29 +129,32 @@ func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String())
|
|
||||||
|
|
||||||
|
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateLedger creates a new locally-stored reference to a Ledger keypair
|
// CreateLedger creates a new locally-stored reference to a Ledger keypair
|
||||||
// It returns the created key info and an error if the Ledger could not be queried
|
// It returns the created key info and an error if the Ledger could not be queried
|
||||||
func (kb dbKeybase) CreateLedger(name string, path crypto.DerivationPath, algo SigningAlgo) (Info, error) {
|
func (kb dbKeybase) CreateLedger(name string, algo SigningAlgo, account uint32, index uint32) (Info, error) {
|
||||||
if algo != Secp256k1 {
|
if algo != Secp256k1 {
|
||||||
return nil, ErrUnsupportedSigningAlgo
|
return nil, ErrUnsupportedSigningAlgo
|
||||||
}
|
}
|
||||||
priv, err := crypto.NewPrivKeyLedgerSecp256k1(path)
|
|
||||||
|
hdPath := hd.NewFundraiserParams(account, index)
|
||||||
|
priv, err := crypto.NewPrivKeyLedgerSecp256k1(*hdPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pub := priv.PubKey()
|
pub := priv.PubKey()
|
||||||
return kb.writeLedgerKey(pub, path, name), nil
|
|
||||||
|
return kb.writeLedgerKey(name, pub, *hdPath), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOffline creates a new reference to an offline keypair
|
// CreateOffline creates a new reference to an offline keypair
|
||||||
// It returns the created key info
|
// It returns the created key info
|
||||||
func (kb dbKeybase) CreateOffline(name string, pub tmcrypto.PubKey) (Info, error) {
|
func (kb dbKeybase) CreateOffline(name string, pub tmcrypto.PubKey) (Info, error) {
|
||||||
return kb.writeOfflineKey(pub, name), nil
|
return kb.writeOfflineKey(name, pub), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kb *dbKeybase) persistDerivedKey(seed []byte, passwd, name, fullHdPath string) (info Info, err error) {
|
func (kb *dbKeybase) persistDerivedKey(seed []byte, passwd, name, fullHdPath string) (info Info, err error) {
|
||||||
|
@ -187,10 +168,10 @@ func (kb *dbKeybase) persistDerivedKey(seed []byte, passwd, name, fullHdPath str
|
||||||
// if we have a password, use it to encrypt the private key and store it
|
// if we have a password, use it to encrypt the private key and store it
|
||||||
// else store the public key only
|
// else store the public key only
|
||||||
if passwd != "" {
|
if passwd != "" {
|
||||||
info = kb.writeLocalKey(secp256k1.PrivKeySecp256k1(derivedPriv), name, passwd)
|
info = kb.writeLocalKey(name, secp256k1.PrivKeySecp256k1(derivedPriv), passwd)
|
||||||
} else {
|
} else {
|
||||||
pubk := secp256k1.PrivKeySecp256k1(derivedPriv).PubKey()
|
pubk := secp256k1.PrivKeySecp256k1(derivedPriv).PubKey()
|
||||||
info = kb.writeOfflineKey(pubk, name)
|
info = kb.writeOfflineKey(name, pubk)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -362,7 +343,7 @@ func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
kb.writeOfflineKey(pubKey, name)
|
kb.writeOfflineKey(name, pubKey)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,10 +391,10 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
kb.writeLocalKey(key, name, newpass)
|
kb.writeLocalKey(name, key, newpass)
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("locally stored key required")
|
return fmt.Errorf("locally stored key required. Received: %v", reflect.TypeOf(info).String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -422,29 +403,29 @@ func (kb dbKeybase) CloseDB() {
|
||||||
kb.db.Close()
|
kb.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kb dbKeybase) writeLocalKey(priv tmcrypto.PrivKey, name, passphrase string) Info {
|
func (kb dbKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase string) Info {
|
||||||
// encrypt private key using passphrase
|
// encrypt private key using passphrase
|
||||||
privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase)
|
privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase)
|
||||||
// make Info
|
// make Info
|
||||||
pub := priv.PubKey()
|
pub := priv.PubKey()
|
||||||
info := newLocalInfo(name, pub, privArmor)
|
info := newLocalInfo(name, pub, privArmor)
|
||||||
kb.writeInfo(info, name)
|
kb.writeInfo(name, info)
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kb dbKeybase) writeLedgerKey(pub tmcrypto.PubKey, path crypto.DerivationPath, name string) Info {
|
func (kb dbKeybase) writeLedgerKey(name string, pub tmcrypto.PubKey, path hd.BIP44Params) Info {
|
||||||
info := newLedgerInfo(name, pub, path)
|
info := newLedgerInfo(name, pub, path)
|
||||||
kb.writeInfo(info, name)
|
kb.writeInfo(name, info)
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kb dbKeybase) writeOfflineKey(pub tmcrypto.PubKey, name string) Info {
|
func (kb dbKeybase) writeOfflineKey(name string, pub tmcrypto.PubKey) Info {
|
||||||
info := newOfflineInfo(name, pub)
|
info := newOfflineInfo(name, pub)
|
||||||
kb.writeInfo(info, name)
|
kb.writeInfo(name, info)
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kb dbKeybase) writeInfo(info Info, name string) {
|
func (kb dbKeybase) writeInfo(name string, info Info) {
|
||||||
// write the info by key
|
// write the info by key
|
||||||
key := infoKey(name)
|
key := infoKey(name)
|
||||||
kb.db.SetSync(key, writeInfo(info))
|
kb.db.SetSync(key, writeInfo(info))
|
||||||
|
|
|
@ -2,6 +2,7 @@ package keys
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -9,26 +10,84 @@ import (
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||||
|
|
||||||
dbm "github.com/tendermint/tendermint/libs/db"
|
dbm "github.com/tendermint/tendermint/libs/db"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
mintkey.BcryptSecurityParameter = 1
|
mintkey.BcryptSecurityParameter = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestKeybaseOpenClose(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir("", "TestKeybaseOpenClose")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
kb := New(dbm.NewDB("TestKeybaseOpenClose", dbm.LevelDBBackend, dir))
|
||||||
|
kb.CloseDB()
|
||||||
|
|
||||||
|
// The DB has been closed. At the moment, the expected behaviour is to panic
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
_, _ = kb.CreateAccount(
|
||||||
|
"some_account",
|
||||||
|
"key pair crucial catch public canyon evil outer stage ten gym tornado",
|
||||||
|
"", "", 0, 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLanguage(t *testing.T) {
|
||||||
|
kb := New(dbm.NewMemDB())
|
||||||
|
_, _, err := kb.CreateMnemonic("something", Japanese, "no_pass", Secp256k1)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, "unsupported language: only english is supported", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateAccountInvalidMnemonic(t *testing.T) {
|
||||||
|
kb := New(dbm.NewMemDB())
|
||||||
|
_, err := kb.CreateAccount(
|
||||||
|
"some_account",
|
||||||
|
"malarkey pair crucial catch public canyon evil outer stage ten gym tornado",
|
||||||
|
"", "", 0, 1)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, "Invalid mnemonic", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateLedgerUnsupportedAlgo(t *testing.T) {
|
||||||
|
kb := New(dbm.NewMemDB())
|
||||||
|
_, err := kb.CreateLedger("some_account", Ed25519, 0, 1)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, "unsupported signing algo: only secp256k1 is supported", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateLedger(t *testing.T) {
|
||||||
|
kb := New(dbm.NewMemDB())
|
||||||
|
|
||||||
|
// test_cover and test_unit will result in different answers
|
||||||
|
// test_cover does not compile some dependencies so ledger is disabled
|
||||||
|
// test_unit may add a ledger mock
|
||||||
|
// both cases are acceptable
|
||||||
|
ledger, err := kb.CreateLedger("some_account", Secp256k1, 0, 1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error())
|
||||||
|
assert.Nil(t, ledger)
|
||||||
|
} else {
|
||||||
|
// The mock is available, check that the address is correct
|
||||||
|
pubKey := ledger.GetPubKey()
|
||||||
|
addr, err := sdk.Bech32ifyAccPub(pubKey)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "cosmospub1addwnpepqfsdqjr68h7wjg5wacksmqaypasnra232fkgu5sxdlnlu8j22ztxvlqvd65", addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestKeyManagement makes sure we can manipulate these keys well
|
// TestKeyManagement makes sure we can manipulate these keys well
|
||||||
func TestKeyManagement(t *testing.T) {
|
func TestKeyManagement(t *testing.T) {
|
||||||
// make the storage with reasonable defaults
|
// make the storage with reasonable defaults
|
||||||
db := dbm.NewMemDB()
|
db := dbm.NewMemDB()
|
||||||
cstore := New(
|
cstore := New(db)
|
||||||
db,
|
|
||||||
)
|
|
||||||
|
|
||||||
algo := Secp256k1
|
algo := Secp256k1
|
||||||
n1, n2, n3 := "personal", "business", "other"
|
n1, n2, n3 := "personal", "business", "other"
|
||||||
|
@ -112,9 +171,7 @@ func TestKeyManagement(t *testing.T) {
|
||||||
// TestSignVerify does some detailed checks on how we sign and validate
|
// TestSignVerify does some detailed checks on how we sign and validate
|
||||||
// signatures
|
// signatures
|
||||||
func TestSignVerify(t *testing.T) {
|
func TestSignVerify(t *testing.T) {
|
||||||
cstore := New(
|
cstore := New(dbm.NewMemDB())
|
||||||
dbm.NewMemDB(),
|
|
||||||
)
|
|
||||||
algo := Secp256k1
|
algo := Secp256k1
|
||||||
|
|
||||||
n1, n2, n3 := "some dude", "a dudette", "dude-ish"
|
n1, n2, n3 := "some dude", "a dudette", "dude-ish"
|
||||||
|
@ -344,7 +401,7 @@ func TestSeedPhrase(t *testing.T) {
|
||||||
|
|
||||||
// let us re-create it from the mnemonic-phrase
|
// let us re-create it from the mnemonic-phrase
|
||||||
params := *hd.NewFundraiserParams(0, 0)
|
params := *hd.NewFundraiserParams(0, 0)
|
||||||
newInfo, err := cstore.Derive(n2, mnemonic, defaultBIP39Passphrase, p2, params)
|
newInfo, err := cstore.Derive(n2, mnemonic, DefaultBIP39Passphrase, p2, params)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, n2, newInfo.GetName())
|
require.Equal(t, n2, newInfo.GetName())
|
||||||
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
|
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tendermint/tendermint/crypto"
|
||||||
|
dbm "github.com/tendermint/tendermint/libs/db"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Keybase = lazyKeybase{}
|
||||||
|
|
||||||
|
type lazyKeybase struct {
|
||||||
|
name string
|
||||||
|
dir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLazyKeybase(name, dir string) Keybase {
|
||||||
|
return lazyKeybase{name: name, dir: dir}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) List() ([]Info, error) {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).List()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) Get(name string) (Info, error) {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) GetByAddress(address types.AccAddress) (Info, error) {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).GetByAddress(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) Delete(name, passphrase string, skipPass bool) error {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).Delete(name, passphrase, skipPass)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).Sign(name, passphrase, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).CreateMnemonic(name, language, passwd, algo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, account, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error) {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).Derive(name, mnemonic, bip39Passwd, encryptPasswd, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) CreateLedger(name string, algo SigningAlgo, account uint32, index uint32) (info Info, err error) {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).CreateLedger(name, algo, account, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).CreateOffline(name, pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).Update(name, oldpass, getNewpass)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) Import(name string, armor string) (err error) {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).Import(name, armor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) ImportPubKey(name string, armor string) (err error) {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).ImportPubKey(name, armor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) Export(name string) (armor string, err error) {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).Export(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) ExportPubKey(name string) (armor string, err error) {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).ExportPubKey(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) {
|
||||||
|
db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return New(db).ExportPrivateKeyObject(name, passphrase)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) CloseDB() {}
|
|
@ -3,8 +3,6 @@ package keys
|
||||||
import (
|
import (
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
|
|
||||||
ccrypto "github.com/cosmos/cosmos-sdk/crypto"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||||
"github.com/cosmos/cosmos-sdk/types"
|
"github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
@ -23,20 +21,20 @@ type Keybase interface {
|
||||||
// CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic
|
// CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic
|
||||||
// key from that.
|
// key from that.
|
||||||
CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error)
|
CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error)
|
||||||
// CreateKey takes a mnemonic and derives, a password. This method is temporary
|
|
||||||
CreateKey(name, mnemonic, passwd string) (info Info, err error)
|
// CreateAccount creates an account based using the BIP44 path (44'/118'/{account}'/0/{index}
|
||||||
// CreateFundraiserKey takes a mnemonic and derives, a password
|
CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error)
|
||||||
CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error)
|
|
||||||
// Compute a BIP39 seed from th mnemonic and bip39Passwd.
|
// Derive computes a BIP39 seed from th mnemonic and bip39Passwd.
|
||||||
// Derive private key from the seed using the BIP44 params.
|
// Derive private key from the seed using the BIP44 params.
|
||||||
// Encrypt the key to disk using encryptPasswd.
|
// Encrypt the key to disk using encryptPasswd.
|
||||||
// See https://github.com/cosmos/cosmos-sdk/issues/2095
|
// See https://github.com/cosmos/cosmos-sdk/issues/2095
|
||||||
Derive(name, mnemonic, bip39Passwd,
|
Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error)
|
||||||
encryptPasswd string, params hd.BIP44Params) (Info, error)
|
|
||||||
// Create, store, and return a new Ledger key reference
|
|
||||||
CreateLedger(name string, path ccrypto.DerivationPath, algo SigningAlgo) (info Info, err error)
|
|
||||||
|
|
||||||
// Create, store, and return a new offline key reference
|
// CreateLedger creates, stores, and returns a new Ledger key reference
|
||||||
|
CreateLedger(name string, algo SigningAlgo, account uint32, index uint32) (info Info, err error)
|
||||||
|
|
||||||
|
// CreateOffline creates, stores, and returns a new offline key reference
|
||||||
CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error)
|
CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error)
|
||||||
|
|
||||||
// The following operations will *only* work on locally-stored keys
|
// The following operations will *only* work on locally-stored keys
|
||||||
|
@ -46,10 +44,10 @@ type Keybase interface {
|
||||||
Export(name string) (armor string, err error)
|
Export(name string) (armor string, err error)
|
||||||
ExportPubKey(name string) (armor string, err error)
|
ExportPubKey(name string) (armor string, err error)
|
||||||
|
|
||||||
// *only* works on locally-stored keys. Temporary method until we redo the exporting API
|
// ExportPrivateKeyObject *only* works on locally-stored keys. Temporary method until we redo the exporting API
|
||||||
ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error)
|
ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error)
|
||||||
|
|
||||||
// Close closes the database.
|
// CloseDB closes the database.
|
||||||
CloseDB()
|
CloseDB()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,12 +121,12 @@ func (i localInfo) GetAddress() types.AccAddress {
|
||||||
|
|
||||||
// ledgerInfo is the public information about a Ledger key
|
// ledgerInfo is the public information about a Ledger key
|
||||||
type ledgerInfo struct {
|
type ledgerInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
PubKey crypto.PubKey `json:"pubkey"`
|
PubKey crypto.PubKey `json:"pubkey"`
|
||||||
Path ccrypto.DerivationPath `json:"path"`
|
Path hd.BIP44Params `json:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLedgerInfo(name string, pub crypto.PubKey, path ccrypto.DerivationPath) Info {
|
func newLedgerInfo(name string, pub crypto.PubKey, path hd.BIP44Params) Info {
|
||||||
return &ledgerInfo{
|
return &ledgerInfo{
|
||||||
Name: name,
|
Name: name,
|
||||||
PubKey: pub,
|
PubKey: pub,
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
// +build ledger,test_ledger_mock
|
||||||
|
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/btcec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||||
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
|
"github.com/cosmos/go-bip39"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
secp256k1 "github.com/tendermint/btcd/btcec"
|
||||||
|
"github.com/tendermint/tendermint/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// If ledger support (build tag) has been enabled, which implies a CGO dependency,
|
||||||
|
// set the discoverLedger function which is responsible for loading the Ledger
|
||||||
|
// device at runtime or returning an error.
|
||||||
|
func init() {
|
||||||
|
discoverLedger = func() (LedgerSECP256K1, error) {
|
||||||
|
return LedgerSECP256K1Mock{}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type LedgerSECP256K1Mock struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mock LedgerSECP256K1Mock) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mock LedgerSECP256K1Mock) GetPublicKeySECP256K1(derivationPath []uint32) ([]byte, error) {
|
||||||
|
if derivationPath[0] != 44 {
|
||||||
|
return nil, errors.New("Invalid derivation path")
|
||||||
|
}
|
||||||
|
if derivationPath[1] != 118 {
|
||||||
|
return nil, errors.New("Invalid derivation path")
|
||||||
|
}
|
||||||
|
|
||||||
|
seed, err := bip39.NewSeedWithErrorChecking(tests.TestMnemonic, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
path := hd.NewParams(derivationPath[0], derivationPath[1], derivationPath[2], derivationPath[3] != 0, derivationPath[4])
|
||||||
|
masterPriv, ch := hd.ComputeMastersFromSeed(seed)
|
||||||
|
derivedPriv, err := hd.DerivePrivateKeyForPath(masterPriv, ch, path.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, pubkeyObject := secp256k1.PrivKeyFromBytes(secp256k1.S256(), derivedPriv[:])
|
||||||
|
|
||||||
|
return pubkeyObject.SerializeUncompressed(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mock LedgerSECP256K1Mock) SignSECP256K1(derivationPath []uint32, message []byte) ([]byte, error) {
|
||||||
|
path := hd.NewParams(derivationPath[0], derivationPath[1], derivationPath[2], derivationPath[3] != 0, derivationPath[4])
|
||||||
|
seed, err := bip39.NewSeedWithErrorChecking(tests.TestMnemonic, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
masterPriv, ch := hd.ComputeMastersFromSeed(seed)
|
||||||
|
derivedPriv, err := hd.DerivePrivateKeyForPath(masterPriv, ch, path.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
priv, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), derivedPriv[:])
|
||||||
|
|
||||||
|
sig, err := priv.Sign(crypto.Sha256(message))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to return DER as the ledger does
|
||||||
|
sig2 := btcec.Signature{R: sig.R, S: sig.S}
|
||||||
|
return sig2.Serialize(), nil
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
// +build !cgo !ledger
|
||||||
|
// test_ledger_mock
|
||||||
|
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// If ledger support (build tag) has been enabled, which implies a CGO dependency,
|
||||||
|
// set the discoverLedger function which is responsible for loading the Ledger
|
||||||
|
// device at runtime or returning an error.
|
||||||
|
func init() {
|
||||||
|
discoverLedger = func() (LedgerSECP256K1, error) {
|
||||||
|
return nil, errors.New("support for ledger devices is not available in this executable")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,8 @@
|
||||||
// +build cgo,ledger
|
// +build cgo,ledger,!test_ledger_mock
|
||||||
|
|
||||||
package crypto
|
package crypto
|
||||||
|
|
||||||
import (
|
import ledger "github.com/zondax/ledger-cosmos-go"
|
||||||
ledger "github.com/zondax/ledger-cosmos-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// If ledger support (build tag) has been enabled, which implies a CGO dependency,
|
// If ledger support (build tag) has been enabled, which implies a CGO dependency,
|
||||||
// set the discoverLedger function which is responsible for loading the Ledger
|
// set the discoverLedger function which is responsible for loading the Ledger
|
|
@ -2,12 +2,15 @@ package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
tmbtcec "github.com/tendermint/btcd/btcec"
|
||||||
secp256k1 "github.com/btcsuite/btcd/btcec"
|
|
||||||
tmcrypto "github.com/tendermint/tendermint/crypto"
|
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||||
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
|
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -22,12 +25,10 @@ type (
|
||||||
// dependencies when Ledger support is potentially not enabled.
|
// dependencies when Ledger support is potentially not enabled.
|
||||||
discoverLedgerFn func() (LedgerSECP256K1, error)
|
discoverLedgerFn func() (LedgerSECP256K1, error)
|
||||||
|
|
||||||
// DerivationPath represents a Ledger derivation path.
|
|
||||||
DerivationPath []uint32
|
|
||||||
|
|
||||||
// LedgerSECP256K1 reflects an interface a Ledger API must implement for
|
// LedgerSECP256K1 reflects an interface a Ledger API must implement for
|
||||||
// the SECP256K1 scheme.
|
// the SECP256K1 scheme.
|
||||||
LedgerSECP256K1 interface {
|
LedgerSECP256K1 interface {
|
||||||
|
Close() error
|
||||||
GetPublicKeySECP256K1([]uint32) ([]byte, error)
|
GetPublicKeySECP256K1([]uint32) ([]byte, error)
|
||||||
SignSECP256K1([]uint32, []byte) ([]byte, error)
|
SignSECP256K1([]uint32, []byte) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
@ -39,35 +40,25 @@ type (
|
||||||
// go-amino so we can view the address later, even without having the
|
// go-amino so we can view the address later, even without having the
|
||||||
// ledger attached.
|
// ledger attached.
|
||||||
CachedPubKey tmcrypto.PubKey
|
CachedPubKey tmcrypto.PubKey
|
||||||
Path DerivationPath
|
Path hd.BIP44Params
|
||||||
ledger LedgerSECP256K1
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewPrivKeyLedgerSecp256k1 will generate a new key and store the public key
|
// NewPrivKeyLedgerSecp256k1 will generate a new key and store the public key
|
||||||
// for later use.
|
// for later use.
|
||||||
//
|
func NewPrivKeyLedgerSecp256k1(path hd.BIP44Params) (tmcrypto.PrivKey, error) {
|
||||||
// CONTRACT: The ledger device, ledgerDevice, must be loaded and set prior to
|
device, err := getLedgerDevice()
|
||||||
// any creation of a PrivKeyLedgerSecp256k1.
|
|
||||||
func NewPrivKeyLedgerSecp256k1(path DerivationPath) (tmcrypto.PrivKey, error) {
|
|
||||||
if discoverLedger == nil {
|
|
||||||
return nil, errors.New("no Ledger discovery function defined")
|
|
||||||
}
|
|
||||||
|
|
||||||
device, err := discoverLedger()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to create PrivKeyLedgerSecp256k1")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer warnIfErrors(device.Close)
|
||||||
|
|
||||||
pkl := &PrivKeyLedgerSecp256k1{Path: path, ledger: device}
|
pubKey, err := getPubKey(device, path)
|
||||||
|
|
||||||
pubKey, err := pkl.getPubKey()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pkl.CachedPubKey = pubKey
|
return PrivKeyLedgerSecp256k1{pubKey, path}, nil
|
||||||
return pkl, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PubKey returns the cached public key.
|
// PubKey returns the cached public key.
|
||||||
|
@ -75,21 +66,27 @@ func (pkl PrivKeyLedgerSecp256k1) PubKey() tmcrypto.PubKey {
|
||||||
return pkl.CachedPubKey
|
return pkl.CachedPubKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sign returns a secp256k1 signature for the corresponding message
|
||||||
|
func (pkl PrivKeyLedgerSecp256k1) Sign(message []byte) ([]byte, error) {
|
||||||
|
device, err := getLedgerDevice()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer warnIfErrors(device.Close)
|
||||||
|
|
||||||
|
return sign(device, pkl, message)
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateKey allows us to verify the sanity of a public key after loading it
|
// ValidateKey allows us to verify the sanity of a public key after loading it
|
||||||
// from disk.
|
// from disk.
|
||||||
func (pkl PrivKeyLedgerSecp256k1) ValidateKey() error {
|
func (pkl PrivKeyLedgerSecp256k1) ValidateKey() error {
|
||||||
// getPubKey will return an error if the ledger is not
|
device, err := getLedgerDevice()
|
||||||
pub, err := pkl.getPubKey()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer warnIfErrors(device.Close)
|
||||||
|
|
||||||
// verify this matches cached address
|
return validateKey(device, pkl)
|
||||||
if !pub.Equals(pkl.CachedPubKey) {
|
|
||||||
return fmt.Errorf("cached key does not match retrieved key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssertIsPrivKeyInner implements the PrivKey interface. It performs a no-op.
|
// AssertIsPrivKeyInner implements the PrivKey interface. It performs a no-op.
|
||||||
|
@ -104,11 +101,54 @@ func (pkl PrivKeyLedgerSecp256k1) Bytes() []byte {
|
||||||
// Equals implements the PrivKey interface. It makes sure two private keys
|
// Equals implements the PrivKey interface. It makes sure two private keys
|
||||||
// refer to the same public key.
|
// refer to the same public key.
|
||||||
func (pkl PrivKeyLedgerSecp256k1) Equals(other tmcrypto.PrivKey) bool {
|
func (pkl PrivKeyLedgerSecp256k1) Equals(other tmcrypto.PrivKey) bool {
|
||||||
if ledger, ok := other.(*PrivKeyLedgerSecp256k1); ok {
|
if otherKey, ok := other.(PrivKeyLedgerSecp256k1); ok {
|
||||||
return pkl.CachedPubKey.Equals(ledger.CachedPubKey)
|
return pkl.CachedPubKey.Equals(otherKey.CachedPubKey)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// warnIfErrors wraps a function and writes a warning to stderr. This is required
|
||||||
|
// to avoid ignoring errors when defer is used. Using defer may result in linter warnings.
|
||||||
|
func warnIfErrors(f func() error) {
|
||||||
|
if err := f(); err != nil {
|
||||||
|
_, _ = fmt.Fprint(os.Stderr, "received error when closing ledger connection", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertDERtoBER(signatureDER []byte) ([]byte, error) {
|
||||||
|
sigDER, err := btcec.ParseDERSignature(signatureDER[:], btcec.S256())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sigBER := tmbtcec.Signature{R: sigDER.R, S: sigDER.S}
|
||||||
|
return sigBER.Serialize(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLedgerDevice() (LedgerSECP256K1, error) {
|
||||||
|
if discoverLedger == nil {
|
||||||
|
return nil, errors.New("no Ledger discovery function defined")
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
device, err := discoverLedger()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "ledger nano S")
|
||||||
|
}
|
||||||
|
|
||||||
|
return device, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateKey(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1) error {
|
||||||
|
pub, err := getPubKey(device, pkl.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify this matches cached address
|
||||||
|
if !pub.Equals(pkl.CachedPubKey) {
|
||||||
|
return fmt.Errorf("cached key does not match retrieved key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign calls the ledger and stores the PubKey for future use.
|
// Sign calls the ledger and stores the PubKey for future use.
|
||||||
|
@ -116,45 +156,37 @@ func (pkl PrivKeyLedgerSecp256k1) Equals(other tmcrypto.PrivKey) bool {
|
||||||
// Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, returning
|
// Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, returning
|
||||||
// an error, so this should only trigger if the private key is held in memory
|
// an error, so this should only trigger if the private key is held in memory
|
||||||
// for a while before use.
|
// for a while before use.
|
||||||
func (pkl PrivKeyLedgerSecp256k1) Sign(msg []byte) ([]byte, error) {
|
func sign(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte) ([]byte, error) {
|
||||||
sig, err := pkl.signLedgerSecp256k1(msg)
|
err := validateKey(device, pkl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return sig, nil
|
sig, err := device.SignSECP256K1(pkl.Path.DerivationPath(), msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertDERtoBER(sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPubKey reads the pubkey the ledger itself
|
// getPubKey reads the pubkey the ledger itself
|
||||||
// since this involves IO, it may return an error, which is not exposed
|
// since this involves IO, it may return an error, which is not exposed
|
||||||
// in the PubKey interface, so this function allows better error handling
|
// in the PubKey interface, so this function allows better error handling
|
||||||
func (pkl PrivKeyLedgerSecp256k1) getPubKey() (key tmcrypto.PubKey, err error) {
|
func getPubKey(device LedgerSECP256K1, path hd.BIP44Params) (tmcrypto.PubKey, error) {
|
||||||
key, err = pkl.pubkeyLedgerSecp256k1()
|
publicKey, err := device.GetPublicKeySECP256K1(path.DerivationPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return key, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err)
|
return nil, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return key, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pkl PrivKeyLedgerSecp256k1) signLedgerSecp256k1(msg []byte) ([]byte, error) {
|
|
||||||
return pkl.ledger.SignSECP256K1(pkl.Path, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pkl PrivKeyLedgerSecp256k1) pubkeyLedgerSecp256k1() (pub tmcrypto.PubKey, err error) {
|
|
||||||
key, err := pkl.ledger.GetPublicKeySECP256K1(pkl.Path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error fetching public key: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pk tmsecp256k1.PubKeySecp256k1
|
|
||||||
|
|
||||||
// re-serialize in the 33-byte compressed format
|
// re-serialize in the 33-byte compressed format
|
||||||
cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256())
|
cmp, err := btcec.ParsePubKey(publicKey[:], btcec.S256())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error parsing public key: %v", err)
|
return nil, fmt.Errorf("error parsing public key: %v", err)
|
||||||
}
|
}
|
||||||
copy(pk[:], cmp.SerializeCompressed())
|
|
||||||
|
|
||||||
return pk, nil
|
var compressedPublicKey tmsecp256k1.PubKeySecp256k1
|
||||||
|
copy(compressedPublicKey[:], cmp.SerializeCompressed())
|
||||||
|
|
||||||
|
return compressedPublicKey, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,22 +2,127 @@ package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
|
|
||||||
|
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||||
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ledgerEnabledEnv = "TEST_WITH_LEDGER"
|
func TestLedgerErrorHandling(t *testing.T) {
|
||||||
|
// first, try to generate a key, must return an error
|
||||||
|
// (no panic)
|
||||||
|
path := *hd.NewParams(44, 555, 0, false, 0)
|
||||||
|
_, err := NewPrivKeyLedgerSecp256k1(path)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicKey(t *testing.T) {
|
||||||
|
path := *hd.NewFundraiserParams(0, 0)
|
||||||
|
priv, err := NewPrivKeyLedgerSecp256k1(path)
|
||||||
|
require.Nil(t, err, "%s", err)
|
||||||
|
require.NotNil(t, priv)
|
||||||
|
|
||||||
|
pubKeyAddr, err := sdk.Bech32ifyAccPub(priv.PubKey())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "cosmospub1addwnpepqd87l8xhcnrrtzxnkql7k55ph8fr9jarf4hn6udwukfprlalu8lgw0urza0",
|
||||||
|
pubKeyAddr, "Is your device using test mnemonic: %s ?", tests.TestMnemonic)
|
||||||
|
|
||||||
|
require.Equal(t, "5075624b6579536563703235366b317b303334464546394344374334433633353838443342303"+
|
||||||
|
"3464542353238314239443233324342413334443646334437314145453539323131464642464531464538377d",
|
||||||
|
fmt.Sprintf("%x", priv.PubKey()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicKeyHDPath(t *testing.T) {
|
||||||
|
expectedAnswers := []string{
|
||||||
|
"cosmospub1addwnpepqd87l8xhcnrrtzxnkql7k55ph8fr9jarf4hn6udwukfprlalu8lgw0urza0",
|
||||||
|
"cosmospub1addwnpepqfsdqjr68h7wjg5wacksmqaypasnra232fkgu5sxdlnlu8j22ztxvlqvd65",
|
||||||
|
"cosmospub1addwnpepqw3xwqun6q43vtgw6p4qspq7srvxhcmvq4jrx5j5ma6xy3r7k6dtxmrkh3d",
|
||||||
|
"cosmospub1addwnpepqvez9lrp09g8w7gkv42y4yr5p6826cu28ydrhrujv862yf4njmqyyjr4pjs",
|
||||||
|
"cosmospub1addwnpepq06hw3enfrtmq8n67teytcmtnrgcr0yntmyt25kdukfjkerdc7lqg32rcz7",
|
||||||
|
"cosmospub1addwnpepqg3trf2gd0s2940nckrxherwqhgmm6xd5h4pcnrh4x7y35h6yafmcpk5qns",
|
||||||
|
"cosmospub1addwnpepqdm6rjpx6wsref8wjn7ym6ntejet430j4szpngfgc20caz83lu545vuv8hp",
|
||||||
|
"cosmospub1addwnpepqvdhtjzy2wf44dm03jxsketxc07vzqwvt3vawqqtljgsr9s7jvydjmt66ew",
|
||||||
|
"cosmospub1addwnpepqwystfpyxwcava7v3t7ndps5xzu6s553wxcxzmmnxevlzvwrlqpzz695nw9",
|
||||||
|
"cosmospub1addwnpepqw970u6gjqkccg9u3rfj99857wupj2z9fqfzy2w7e5dd7xn7kzzgkgqch0r",
|
||||||
|
}
|
||||||
|
|
||||||
|
const numIters = 10
|
||||||
|
|
||||||
|
privKeys := make([]tmcrypto.PrivKey, numIters)
|
||||||
|
|
||||||
|
// Check with device
|
||||||
|
for i := uint32(0); i < 10; i++ {
|
||||||
|
path := *hd.NewFundraiserParams(0, i)
|
||||||
|
fmt.Printf("Checking keys at %v\n", path)
|
||||||
|
|
||||||
|
priv, err := NewPrivKeyLedgerSecp256k1(path)
|
||||||
|
require.Nil(t, err, "%s", err)
|
||||||
|
require.NotNil(t, priv)
|
||||||
|
|
||||||
|
// Check other methods
|
||||||
|
require.NoError(t, priv.(PrivKeyLedgerSecp256k1).ValidateKey())
|
||||||
|
tmp := priv.(PrivKeyLedgerSecp256k1)
|
||||||
|
(&tmp).AssertIsPrivKeyInner()
|
||||||
|
|
||||||
|
pubKeyAddr, err := sdk.Bech32ifyAccPub(priv.PubKey())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t,
|
||||||
|
expectedAnswers[i], pubKeyAddr,
|
||||||
|
"Is your device using test mnemonic: %s ?", tests.TestMnemonic)
|
||||||
|
|
||||||
|
// Store and restore
|
||||||
|
serializedPk := priv.Bytes()
|
||||||
|
require.NotNil(t, serializedPk)
|
||||||
|
require.Equal(t, 44, len(serializedPk))
|
||||||
|
|
||||||
|
privKeys[i] = priv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check equality
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
for j := 0; j < 10; j++ {
|
||||||
|
require.Equal(t, i == j, privKeys[i].Equals(privKeys[j]))
|
||||||
|
require.Equal(t, i == j, privKeys[j].Equals(privKeys[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFakeTx(accountNumber uint32) []byte {
|
||||||
|
tmp := fmt.Sprintf(
|
||||||
|
`{"account_number":"%d","chain_id":"1234","fee":{"amount":[{"amount":"150","denom":"atom"}],"gas":"5000"},"memo":"memo","msgs":[[""]],"sequence":"6"}`,
|
||||||
|
accountNumber)
|
||||||
|
|
||||||
|
return []byte(tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignaturesHD(t *testing.T) {
|
||||||
|
for account := uint32(0); account < 100; account += 30 {
|
||||||
|
msg := getFakeTx(account)
|
||||||
|
|
||||||
|
path := *hd.NewFundraiserParams(account, account/5)
|
||||||
|
fmt.Printf("Checking signature at %v --- PLEASE REVIEW AND ACCEPT IN THE DEVICE\n", path)
|
||||||
|
|
||||||
|
priv, err := NewPrivKeyLedgerSecp256k1(path)
|
||||||
|
require.Nil(t, err, "%s", err)
|
||||||
|
|
||||||
|
pub := priv.PubKey()
|
||||||
|
sig, err := priv.Sign(msg)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
valid := pub.VerifyBytes(msg, sig)
|
||||||
|
require.True(t, valid, "Is your device using test mnemonic: %s ?", tests.TestMnemonic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRealLedgerSecp256k1(t *testing.T) {
|
func TestRealLedgerSecp256k1(t *testing.T) {
|
||||||
if os.Getenv(ledgerEnabledEnv) == "" {
|
msg := getFakeTx(50)
|
||||||
t.Skip(fmt.Sprintf("Set '%s' to run code on a real ledger", ledgerEnabledEnv))
|
path := *hd.NewFundraiserParams(0, 0)
|
||||||
}
|
|
||||||
msg := []byte("{\"account_number\":\"3\",\"chain_id\":\"1234\",\"fee\":{\"amount\":[{\"amount\":\"150\",\"denom\":\"atom\"}],\"gas\":\"5000\"},\"memo\":\"memo\",\"msgs\":[[\"%s\"]],\"sequence\":\"6\"}")
|
|
||||||
path := DerivationPath{44, 60, 0, 0, 0}
|
|
||||||
|
|
||||||
priv, err := NewPrivKeyLedgerSecp256k1(path)
|
priv, err := NewPrivKeyLedgerSecp256k1(path)
|
||||||
require.Nil(t, err, "%s", err)
|
require.Nil(t, err, "%s", err)
|
||||||
|
|
||||||
|
@ -48,17 +153,3 @@ func TestRealLedgerSecp256k1(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, pub, bpub)
|
require.Equal(t, pub, bpub)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRealLedgerErrorHandling calls. These tests assume
|
|
||||||
// the ledger is not plugged in....
|
|
||||||
func TestRealLedgerErrorHandling(t *testing.T) {
|
|
||||||
if os.Getenv(ledgerEnabledEnv) != "" {
|
|
||||||
t.Skip(fmt.Sprintf("Unset '%s' to run code as if without a real Ledger", ledgerEnabledEnv))
|
|
||||||
}
|
|
||||||
|
|
||||||
// first, try to generate a key, must return an error
|
|
||||||
// (no panic)
|
|
||||||
path := DerivationPath{44, 60, 0, 0, 0}
|
|
||||||
_, err := NewPrivKeyLedgerSecp256k1(path)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue