commit
09688d03f4
|
@ -3,7 +3,7 @@ version: 2
|
|||
defaults: &linux_defaults
|
||||
working_directory: /go/src/github.com/cosmos/cosmos-sdk
|
||||
docker:
|
||||
- image: circleci/golang:1.11.4
|
||||
- image: circleci/golang:1.11.5
|
||||
environment:
|
||||
GOBIN: /tmp/workspace/bin
|
||||
|
||||
|
@ -17,7 +17,7 @@ macos_config: &macos_defaults
|
|||
xcode: "10.1.0"
|
||||
working_directory: /Users/distiller/project/src/github.com/cosmos/cosmos-sdk
|
||||
environment:
|
||||
GO_VERSION: "1.11.4"
|
||||
GO_VERSION: "1.11.5"
|
||||
|
||||
set_macos_env: &macos_env
|
||||
run:
|
||||
|
@ -82,6 +82,7 @@ jobs:
|
|||
name: Get metalinter
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make devtools-clean
|
||||
make devtools
|
||||
- run:
|
||||
name: Lint source
|
||||
|
@ -171,7 +172,7 @@ jobs:
|
|||
name: Test multi-seed Gaia simulation long
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
scripts/multisim.sh 800 50 TestFullGaiaSimulation
|
||||
scripts/multisim.sh 500 50 TestFullGaiaSimulation
|
||||
|
||||
test_sim_gaia_multi_seed:
|
||||
<<: *linux_defaults
|
||||
|
@ -200,11 +201,10 @@ jobs:
|
|||
name: Run tests
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make install
|
||||
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
|
||||
id=$(basename "$pkg")
|
||||
GOCACHE=off go test -timeout 8m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"
|
||||
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=$(echo "$pkg" | sed 's|[/.]|_|g')
|
||||
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
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
|
@ -226,10 +226,20 @@ jobs:
|
|||
command: |
|
||||
set -ex
|
||||
|
||||
echo "--> Concatenating profiles:"
|
||||
ls /tmp/workspace/profiles/
|
||||
echo "mode: atomic" > coverage.txt
|
||||
for prof in $(ls /tmp/workspace/profiles/); do
|
||||
tail -n +2 /tmp/workspace/profiles/"$prof" >> coverage.txt
|
||||
done
|
||||
- run:
|
||||
name: filter out DONTCOVER
|
||||
command: |
|
||||
excludelist="$(find ./ -type f -name '*.go' | xargs grep -l 'DONTCOVER' | xargs realpath --relative-to=$GOPATH/src)"
|
||||
for filename in ${excludelist}; do
|
||||
echo "Excluding ${filename} ..."
|
||||
sed -i "\%${filename}:%d" coverage.txt
|
||||
done
|
||||
- run:
|
||||
name: upload
|
||||
command: bash <(curl -s https://codecov.io/bash) -f coverage.txt
|
||||
|
@ -243,7 +253,7 @@ jobs:
|
|||
GOPATH: /home/circleci/.go_workspace/
|
||||
GOOS: linux
|
||||
GOARCH: amd64
|
||||
GO_VERSION: "1.11.4"
|
||||
GO_VERSION: "1.11.5"
|
||||
parallelism: 1
|
||||
steps:
|
||||
- checkout
|
||||
|
@ -380,9 +390,7 @@ workflows:
|
|||
- test_cover:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- localnet:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- localnet
|
||||
- upload_coverage:
|
||||
requires:
|
||||
- test_cover
|
||||
|
|
|
@ -4,4 +4,6 @@
|
|||
* @ebuchman @rigelrozanski @cwgoes
|
||||
|
||||
# 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
|
||||
|
||||
## 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
|
||||
|
||||
BREAKING CHANGES
|
||||
|
|
|
@ -1,22 +1,6 @@
|
|||
# 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]]
|
||||
branch = "master"
|
||||
digest = "1:09a7f74eb6bb3c0f14d8926610c87f569c5cff68e978d30e9a3540aeb626fdf0"
|
||||
|
@ -42,12 +26,11 @@
|
|||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:093bf93a65962e8191e3e8cd8fc6c363f83d43caca9739c906531ba7210a9904"
|
||||
name = "github.com/btcsuite/btcd"
|
||||
packages = ["btcec"]
|
||||
pruneopts = "UT"
|
||||
revision = "7d2daa5bfef28c5e282571bc06416516936115ee"
|
||||
revision = "ed77733ec07dfc8a513741138419b8d9d3de9d2d"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:386de157f7d19259a7f9c81f26ce011223ce0f090353c1152ffdf730d7d10ac2"
|
||||
|
@ -71,6 +54,14 @@
|
|||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
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]]
|
||||
digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd"
|
||||
name = "github.com/fsnotify/fsnotify"
|
||||
|
@ -149,20 +140,12 @@
|
|||
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c79fb010be38a59d657c48c6ba1d003a8aa651fa56b579d959d74573b7dff8e1"
|
||||
name = "github.com/gorilla/context"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e73f5b0152105f18bc131fba127d9949305c8693f8a762588a82a48f61756f5f"
|
||||
digest = "1:ca59b1175189b3f0e9f1793d2c350114be36eaabbe5b9f554b35edee1de50aea"
|
||||
name = "github.com/gorilla/mux"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
|
||||
version = "v1.6.2"
|
||||
revision = "a7962380ca08b5a188038c69871b8d3fbdf31e89"
|
||||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d"
|
||||
|
@ -296,11 +279,10 @@
|
|||
name = "github.com/prometheus/client_model"
|
||||
packages = ["go"]
|
||||
pruneopts = "UT"
|
||||
revision = "5c3871d89910bfb32f5fcab2aa4b9ec68e65a99f"
|
||||
revision = "fd36f4220a901265f90734c3183c5f0c91daa0b8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:db712fde5d12d6cdbdf14b777f0c230f4ff5ab0be8e35b239fc319953ed577a4"
|
||||
digest = "1:35cf6bdf68db765988baa9c4f10cc5d7dda1126a54bd62e252dbcd0b1fc8da90"
|
||||
name = "github.com/prometheus/common"
|
||||
packages = [
|
||||
"expfmt",
|
||||
|
@ -308,11 +290,12 @@
|
|||
"model",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "4724e9255275ce38f7179b2478abeae4e28c904f"
|
||||
revision = "cfeb6f9992ffa54aaa4f2170ade4067ee478b250"
|
||||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:d39e7c7677b161c2dd4c635a2ac196460608c7d8ba5337cc8cae5825a2681f8f"
|
||||
digest = "1:c65f369bae3dff3a0382e38f3fe4f62cdfecba59cb6429ee323b75afdd4f3ba3"
|
||||
name = "github.com/prometheus/procfs"
|
||||
packages = [
|
||||
".",
|
||||
|
@ -321,7 +304,7 @@
|
|||
"xfs",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "1dc9a6cbc91aacc3e8b2d63db4d2e957a5394ac4"
|
||||
revision = "de1b801bf34b80cd00f14087dc5a994bfe0296bc"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ea0700160aca4ef099f4e06686a665a87691f4248dddd40796925eda2e46bd64"
|
||||
|
@ -347,15 +330,15 @@
|
|||
version = "v1.6.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6a4a11ba764a56d2758899ec6f3848d24698d48442ebce85ee7a3f63284526cd"
|
||||
digest = "1:3e39bafd6c2f4bf3c76c3bfd16a2e09e016510ad5db90dc02b88e2f565d6d595"
|
||||
name = "github.com/spf13/afero"
|
||||
packages = [
|
||||
".",
|
||||
"mem",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "d40851caa0d747393da1ffb28f7f9d8b4eeffebd"
|
||||
version = "v1.1.2"
|
||||
revision = "f4711e4db9e9a1d3887343acb72b2bbfc2f686f5"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc"
|
||||
|
@ -453,7 +436,7 @@
|
|||
version = "v0.12.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:22a0fe58c626dd09549eb9451688fab5a2c8bef04d478c907f747d6151d431fd"
|
||||
digest = "1:89f6fe8d02b427996828fbf43720ed1297a2e92c930b98dd302767b5ad796579"
|
||||
name = "github.com/tendermint/tendermint"
|
||||
packages = [
|
||||
"abci/client",
|
||||
|
@ -519,15 +502,31 @@
|
|||
"version",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "v0.29.0"
|
||||
revision = "v0.30.0-rc0"
|
||||
|
||||
[[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"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "d4aed6d929a703bb555a2d79fe9c470afe61f648"
|
||||
version = "v0.9.2"
|
||||
revision = "69fdb8ce5e5b9d9c3b22b9248e117b231d4f06dd"
|
||||
version = "v0.9.7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f8e4c0b959174a1fa5946b12f1f2ac7ea5651bef20a9e4a8dac55dbffcaa6cd6"
|
||||
name = "github.com/zondax/ledger-go"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "69c15f1333a9b6866e5f66096561c7d138894bc5"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6f6dc6060c4e9ba73cf28aa88f12a69a030d3d19d518ef8e931879eaa099628d"
|
||||
|
@ -612,7 +611,7 @@
|
|||
revision = "383e8b2c3b9e36c4076b235b32537292176bae20"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9edd250a3c46675d0679d87540b30c9ed253b19bd1fd1af08f4f5fb3c79fc487"
|
||||
digest = "1:9ab5a33d8cb5c120602a34d2e985ce17956a4e8c2edce7e6961568f95e40c09a"
|
||||
name = "google.golang.org/grpc"
|
||||
packages = [
|
||||
".",
|
||||
|
@ -648,8 +647,8 @@
|
|||
"tap",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "df014850f6dee74ba2fc94874043a9f3f75fbfd8"
|
||||
version = "v1.17.0"
|
||||
revision = "a02b0774206b209466313a0b525d2c738fe407eb"
|
||||
version = "v1.18.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96"
|
||||
|
@ -680,7 +679,7 @@
|
|||
"github.com/spf13/viper",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"github.com/stretchr/testify/require",
|
||||
"github.com/syndtr/goleveldb/leveldb/opt",
|
||||
"github.com/tendermint/btcd/btcec",
|
||||
"github.com/tendermint/go-amino",
|
||||
"github.com/tendermint/iavl",
|
||||
"github.com/tendermint/tendermint/abci/server",
|
||||
|
|
11
Gopkg.toml
11
Gopkg.toml
|
@ -40,14 +40,18 @@
|
|||
|
||||
[[override]]
|
||||
name = "github.com/tendermint/tendermint"
|
||||
revision = "v0.29.0"
|
||||
revision = "v0.30.0-rc0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/zondax/ledger-cosmos-go"
|
||||
version = "=v0.9.2"
|
||||
version = "=v0.9.7"
|
||||
|
||||
## deps without releases:
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/btcsuite/btcd"
|
||||
revision = "ed77733ec07dfc8a513741138419b8d9d3de9d2d"
|
||||
|
||||
[[override]]
|
||||
name = "golang.org/x/crypto"
|
||||
source = "https://github.com/tendermint/crypto"
|
||||
|
@ -84,3 +88,6 @@
|
|||
[prune]
|
||||
go-tests = 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_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')
|
||||
BUILD_TAGS = netgo
|
||||
CAT := $(if $(filter $(OS),Windows_NT),type,cat)
|
||||
|
@ -15,7 +15,7 @@ GOTOOLS = \
|
|||
github.com/rakyll/statik
|
||||
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.
|
||||
include scripts/Makefile
|
||||
|
@ -99,6 +99,7 @@ update_dev_tools:
|
|||
go get -u github.com/tendermint/lint/golint
|
||||
|
||||
devtools: devtools-stamp
|
||||
devtools-clean: tools-clean
|
||||
devtools-stamp: tools
|
||||
@echo "--> Downloading linters (this may take awhile)"
|
||||
$(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
|
||||
|
||||
clean:
|
||||
rm -f devtools-stamp vendor-deps
|
||||
rm -f devtools-stamp vendor-deps snapcraft-local.yaml
|
||||
|
||||
distclean: clean
|
||||
rm -rf vendor/
|
||||
|
||||
########################################
|
||||
### Documentation
|
||||
|
@ -140,8 +144,14 @@ test: test_unit
|
|||
test_cli:
|
||||
@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:
|
||||
@VERSION=$(VERSION) go test $(PACKAGES_NOSIMULATION)
|
||||
@VERSION=$(VERSION) go test $(PACKAGES_NOSIMULATION) -tags='test_ledger_mock'
|
||||
|
||||
test_race:
|
||||
@VERSION=$(VERSION) go test -race $(PACKAGES_NOSIMULATION)
|
||||
|
@ -150,9 +160,15 @@ test_sim_gaia_nondeterminism:
|
|||
@echo "Running nondeterminism test..."
|
||||
@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:
|
||||
@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:
|
||||
@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..."
|
||||
@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:
|
||||
@echo "Running multi-seed Gaia simulation. This may take awhile!"
|
||||
@bash scripts/multisim.sh 400 5 TestFullGaiaSimulation
|
||||
|
@ -171,14 +192,16 @@ SIM_BLOCK_SIZE ?= 200
|
|||
SIM_COMMIT ?= true
|
||||
test_sim_gaia_benchmark:
|
||||
@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:
|
||||
@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:
|
||||
@export VERSION=$(VERSION); bash tests/test_cover.sh
|
||||
@export VERSION=$(VERSION); bash -x tests/test_cover.sh
|
||||
|
||||
test_lint:
|
||||
gometalinter --config=tools/gometalinter.json ./...
|
||||
|
@ -235,12 +258,21 @@ localnet-start: localnet-stop
|
|||
localnet-stop:
|
||||
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
|
||||
# unless there is a reason not to.
|
||||
# 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 \
|
||||
test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update \
|
||||
build-linux build-docker-gaiadnode localnet-start localnet-stop \
|
||||
format check-ledger test_sim_gaia_nondeterminism test_sim_modules test_sim_gaia_fast \
|
||||
test_sim_gaia_multi_seed 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
|
||||
|
||||
* Gaia REST API (`gaiacli advanced rest-server`)
|
||||
* Gaia REST API
|
||||
|
||||
* Gaia CLI (`gaiacli`)
|
||||
* Gaia CLI
|
||||
|
||||
* Gaia
|
||||
|
||||
|
@ -12,12 +12,11 @@ BREAKING CHANGES
|
|||
|
||||
* Tendermint
|
||||
|
||||
|
||||
FEATURES
|
||||
|
||||
* Gaia REST API (`gaiacli advanced rest-server`)
|
||||
* Gaia REST API
|
||||
|
||||
* Gaia CLI (`gaiacli`)
|
||||
* Gaia CLI
|
||||
|
||||
* Gaia
|
||||
|
||||
|
@ -28,9 +27,9 @@ FEATURES
|
|||
|
||||
IMPROVEMENTS
|
||||
|
||||
* Gaia REST API (`gaiacli advanced rest-server`)
|
||||
* Gaia REST API
|
||||
|
||||
* Gaia CLI (`gaiacli`)
|
||||
* Gaia CLI
|
||||
|
||||
* Gaia
|
||||
|
||||
|
@ -41,12 +40,12 @@ IMPROVEMENTS
|
|||
|
||||
BUG FIXES
|
||||
|
||||
* Gaia REST API (`gaiacli advanced rest-server`)
|
||||
* Gaia REST API
|
||||
|
||||
* Gaia CLI (`gaiacli`)
|
||||
* Gaia CLI
|
||||
|
||||
* Gaia
|
||||
|
||||
* SDK
|
||||
|
||||
* Tendermint
|
||||
* Tendermint
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
[](https://github.com/cosmos/cosmos-sdk/releases/latest)
|
||||
[](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://goreportcard.com/report/github.com/cosmos/cosmos-sdk)
|
||||
[](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
|
||||
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
|
||||
|
||||
|
|
|
@ -3,11 +3,13 @@ package baseapp
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
|
@ -41,7 +43,7 @@ const (
|
|||
// BaseApp reflects the ABCI application implementation.
|
||||
type BaseApp struct {
|
||||
// initialized on creation
|
||||
Logger log.Logger
|
||||
logger log.Logger
|
||||
name string // application name from abci.Info
|
||||
db dbm.DB // common DB backend
|
||||
cms sdk.CommitMultiStore // Main (uncached) state
|
||||
|
@ -50,19 +52,18 @@ type BaseApp struct {
|
|||
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
|
||||
|
||||
// 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
|
||||
initChainer sdk.InitChainer // initialize state with validators and state blob
|
||||
beginBlocker sdk.BeginBlocker // logic to run before any txs
|
||||
endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes
|
||||
addrPeerFilter sdk.PeerFilter // filter peers by address and port
|
||||
pubkeyPeerFilter sdk.PeerFilter // filter peers by public key
|
||||
fauxMerkleMode bool // if true, IAVL MountStores uses MountStoresDB for simulation speed.
|
||||
anteHandler sdk.AnteHandler // ante handler for fee and auth
|
||||
initChainer sdk.InitChainer // initialize state with validators and state blob
|
||||
beginBlocker sdk.BeginBlocker // logic to run before any txs
|
||||
endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes
|
||||
addrPeerFilter sdk.PeerFilter // filter peers by address and port
|
||||
idPeerFilter sdk.PeerFilter // filter peers by node ID
|
||||
fauxMerkleMode bool // if true, IAVL MountStores uses MountStoresDB for simulation speed.
|
||||
|
||||
//--------------------
|
||||
// Volatile
|
||||
// --------------------
|
||||
// Volatile state
|
||||
// checkState is set on initialization and reset on Commit.
|
||||
// deliverState is set in InitChain and BeginBlock and cleared on Commit.
|
||||
// See methods setCheckState and setDeliverState.
|
||||
|
@ -71,27 +72,30 @@ type BaseApp struct {
|
|||
voteInfos []abci.VoteInfo // absent validators from begin block
|
||||
|
||||
// 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
|
||||
|
||||
// The minimum gas prices a validator is willing to accept for processing a
|
||||
// transaction. This is mainly used for DoS and spam prevention.
|
||||
minGasPrices sdk.DecCoins
|
||||
|
||||
// flag for sealing
|
||||
// flag for sealing options and parameters to a BaseApp
|
||||
sealed bool
|
||||
}
|
||||
|
||||
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.
|
||||
// Accepts a user-defined txDecoder
|
||||
// Accepts variable number of option functions, which act on the BaseApp to set configuration choices
|
||||
func NewBaseApp(name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp)) *BaseApp {
|
||||
func NewBaseApp(
|
||||
name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp),
|
||||
) *BaseApp {
|
||||
|
||||
app := &BaseApp{
|
||||
Logger: logger,
|
||||
logger: logger,
|
||||
name: name,
|
||||
db: 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 {
|
||||
option(app)
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
// BaseApp Name
|
||||
// Name returns the name of the BaseApp.
|
||||
func (app *BaseApp) Name() string {
|
||||
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
|
||||
// CommitMultiStore.
|
||||
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
|
||||
func (app *BaseApp) MountStores(keys ...*sdk.KVStoreKey) {
|
||||
// MountStores mounts all IAVL or DB stores to the provided keys in the BaseApp
|
||||
// multistore.
|
||||
func (app *BaseApp) MountStores(keys ...sdk.StoreKey) {
|
||||
for _, key := range keys {
|
||||
if !app.fauxMerkleMode {
|
||||
app.MountStore(key, sdk.StoreTypeIAVL)
|
||||
} else {
|
||||
// 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)
|
||||
switch key.(type) {
|
||||
case *sdk.KVStoreKey:
|
||||
if !app.fauxMerkleMode {
|
||||
app.MountStore(key, sdk.StoreTypeIAVL)
|
||||
} else {
|
||||
// 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
|
||||
func (app *BaseApp) MountStoresTransient(keys ...*sdk.TransientStoreKey) {
|
||||
for _, key := range keys {
|
||||
app.MountStore(key, sdk.StoreTypeTransient)
|
||||
}
|
||||
}
|
||||
|
||||
// Mount a store to the provided key in the BaseApp multistore, using a specified DB
|
||||
// MountStoreWithDB mounts 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) {
|
||||
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) {
|
||||
app.cms.MountStoreWithDB(key, typ, nil)
|
||||
}
|
||||
|
||||
// load latest application version
|
||||
// panics if called more than once on a running baseapp
|
||||
func (app *BaseApp) LoadLatestVersion(mainKey *sdk.KVStoreKey) error {
|
||||
// LoadLatestVersion loads the latest application version. It will panic if
|
||||
// called more than once on a running BaseApp.
|
||||
func (app *BaseApp) LoadLatestVersion(baseKey *sdk.KVStoreKey) error {
|
||||
err := app.cms.LoadLatestVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return app.initFromMainStore(mainKey)
|
||||
return app.initFromMainStore(baseKey)
|
||||
}
|
||||
|
||||
// load application version
|
||||
// panics if called more than once on a running baseapp
|
||||
func (app *BaseApp) LoadVersion(version int64, mainKey *sdk.KVStoreKey) error {
|
||||
// LoadVersion loads the BaseApp application version. It will panic if called
|
||||
// more than once on a running baseapp.
|
||||
func (app *BaseApp) LoadVersion(version int64, baseKey *sdk.KVStoreKey) error {
|
||||
err := app.cms.LoadVersion(version)
|
||||
if err != nil {
|
||||
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 {
|
||||
return app.cms.LastCommitID()
|
||||
}
|
||||
|
||||
// the last committed block height
|
||||
// LastBlockHeight returns the last committed block height.
|
||||
func (app *BaseApp) LastBlockHeight() int64 {
|
||||
return app.cms.LastCommitID().Version
|
||||
}
|
||||
|
||||
// initializes the remaining logic from app.cms
|
||||
func (app *BaseApp) initFromMainStore(mainKey *sdk.KVStoreKey) error {
|
||||
|
||||
// main store should exist.
|
||||
mainStore := app.cms.GetKVStore(mainKey)
|
||||
func (app *BaseApp) initFromMainStore(baseKey *sdk.KVStoreKey) error {
|
||||
mainStore := app.cms.GetKVStore(baseKey)
|
||||
if mainStore == nil {
|
||||
return errors.New("baseapp expects MultiStore with 'main' KVStore")
|
||||
}
|
||||
|
||||
// memoize mainKey
|
||||
if app.mainKey != nil {
|
||||
panic("app.mainKey expected to be nil; duplicate init?")
|
||||
// memoize baseKey
|
||||
if app.baseKey != nil {
|
||||
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)
|
||||
if consensusParamsBz != nil {
|
||||
var consensusParams = &abci.ConsensusParams{}
|
||||
|
||||
err := proto.Unmarshal(consensusParamsBz, consensusParams)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
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.Seal()
|
||||
|
||||
return nil
|
||||
|
@ -218,42 +230,45 @@ func (app *BaseApp) setMinGasPrices(gasPrices sdk.DecCoins) {
|
|||
app.minGasPrices = gasPrices
|
||||
}
|
||||
|
||||
// NewContext returns a new Context with the correct store, the given header, and nil txBytes.
|
||||
func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context {
|
||||
if isCheckTx {
|
||||
return sdk.NewContext(app.checkState.ms, header, true, app.Logger).
|
||||
WithMinGasPrices(app.minGasPrices)
|
||||
// Router returns the router of the BaseApp.
|
||||
func (app *BaseApp) Router() Router {
|
||||
if app.sealed {
|
||||
// We cannot return a router when the app is sealed because we can't have
|
||||
// any routes modified which would cause unexpected routing behavior.
|
||||
panic("Router() on sealed BaseApp")
|
||||
}
|
||||
|
||||
return sdk.NewContext(app.deliverState.ms, header, false, app.Logger)
|
||||
return app.router
|
||||
}
|
||||
|
||||
type state struct {
|
||||
ms sdk.CacheMultiStore
|
||||
ctx sdk.Context
|
||||
}
|
||||
// QueryRouter returns the QueryRouter of a BaseApp.
|
||||
func (app *BaseApp) QueryRouter() QueryRouter { return app.queryRouter }
|
||||
|
||||
func (st *state) CacheMultiStore() sdk.CacheMultiStore {
|
||||
return st.ms.CacheMultiStore()
|
||||
}
|
||||
// Seal seals a BaseApp. It prohibits any further modifications to a BaseApp.
|
||||
func (app *BaseApp) Seal() { app.sealed = true }
|
||||
|
||||
func (st *state) Context() sdk.Context {
|
||||
return st.ctx
|
||||
}
|
||||
// IsSealed returns true if the BaseApp is sealed and false otherwise.
|
||||
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) {
|
||||
ms := app.cms.CacheMultiStore()
|
||||
app.checkState = &state{
|
||||
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) {
|
||||
ms := app.cms.CacheMultiStore()
|
||||
app.deliverState = &state{
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
mainStore := app.cms.GetKVStore(app.mainKey)
|
||||
mainStore := app.cms.GetKVStore(app.baseKey)
|
||||
mainStore.Set(mainConsensusParamsKey, consensusParamsBz)
|
||||
}
|
||||
|
||||
|
@ -280,11 +295,10 @@ func (app *BaseApp) getMaximumBlockGas() (maxGas uint64) {
|
|||
return uint64(app.consensusParams.BlockSize.MaxGas)
|
||||
}
|
||||
|
||||
//______________________________________________________________________________
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ABCI
|
||||
|
||||
// Implements ABCI
|
||||
// Info implements the ABCI interface.
|
||||
func (app *BaseApp) Info(req abci.RequestInfo) abci.ResponseInfo {
|
||||
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) {
|
||||
// TODO: Implement
|
||||
// TODO: Implement!
|
||||
return
|
||||
}
|
||||
|
||||
// Implements ABCI
|
||||
// InitChain runs the initialization logic directly on the CommitMultiStore.
|
||||
// InitChain implements the ABCI interface. It runs the initialization logic
|
||||
// directly on the CommitMultiStore.
|
||||
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 {
|
||||
app.setConsensusParams(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.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)
|
||||
|
||||
// NOTE: we don't commit, but BeginBlock for block 1
|
||||
// starts from this deliverState
|
||||
// NOTE: We don't commit, but BeginBlock for block 1 starts from this
|
||||
// deliverState.
|
||||
return
|
||||
}
|
||||
|
||||
// Filter peers by address / port
|
||||
// FilterPeerByAddrPort filters peers by address/port.
|
||||
func (app *BaseApp) FilterPeerByAddrPort(info string) abci.ResponseQuery {
|
||||
if app.addrPeerFilter != nil {
|
||||
return app.addrPeerFilter(info)
|
||||
|
@ -338,15 +352,16 @@ func (app *BaseApp) FilterPeerByAddrPort(info string) abci.ResponseQuery {
|
|||
return abci.ResponseQuery{}
|
||||
}
|
||||
|
||||
// Filter peers by public key
|
||||
func (app *BaseApp) FilterPeerByPubKey(info string) abci.ResponseQuery {
|
||||
if app.pubkeyPeerFilter != nil {
|
||||
return app.pubkeyPeerFilter(info)
|
||||
// FilterPeerByIDfilters peers by node ID.
|
||||
func (app *BaseApp) FilterPeerByID(info string) abci.ResponseQuery {
|
||||
if app.idPeerFilter != nil {
|
||||
return app.idPeerFilter(info)
|
||||
}
|
||||
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) {
|
||||
path = strings.Split(requestPath, "/")
|
||||
// first element is empty string
|
||||
|
@ -356,22 +371,26 @@ func splitPath(requestPath string) (path []string) {
|
|||
return path
|
||||
}
|
||||
|
||||
// Implements ABCI.
|
||||
// Delegates to CommitMultiStore if it implements Queryable
|
||||
// Query implements the ABCI interface. It delegates to CommitMultiStore if it
|
||||
// implements Queryable.
|
||||
func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
|
||||
path := splitPath(req.Path)
|
||||
if len(path) == 0 {
|
||||
msg := "no query path provided"
|
||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||
}
|
||||
|
||||
switch path[0] {
|
||||
// "/app" prefix for special application queries
|
||||
case "app":
|
||||
return handleQueryApp(app, path, req)
|
||||
|
||||
case "store":
|
||||
return handleQueryStore(app, path, req)
|
||||
|
||||
case "p2p":
|
||||
return handleQueryP2P(app, path, req)
|
||||
|
||||
case "custom":
|
||||
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) {
|
||||
if len(path) >= 2 {
|
||||
var result sdk.Result
|
||||
|
||||
switch path[1] {
|
||||
case "simulate":
|
||||
txBytes := req.Data
|
||||
|
@ -390,19 +410,20 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abc
|
|||
if err != nil {
|
||||
result = err.Result()
|
||||
} else {
|
||||
result = app.Simulate(tx)
|
||||
result = app.Simulate(txBytes, tx)
|
||||
}
|
||||
|
||||
case "version":
|
||||
return abci.ResponseQuery{
|
||||
Code: uint32(sdk.CodeOK),
|
||||
Codespace: string(sdk.CodespaceRoot),
|
||||
Value: []byte(version.GetVersion()),
|
||||
Value: []byte(version.Version),
|
||||
}
|
||||
|
||||
default:
|
||||
result = sdk.ErrUnknownRequest(fmt.Sprintf("Unknown query: %s", path)).Result()
|
||||
}
|
||||
|
||||
// Encode with json
|
||||
value := codec.Cdc.MustMarshalBinaryLengthPrefixed(result)
|
||||
return abci.ResponseQuery{
|
||||
Code: uint32(sdk.CodeOK),
|
||||
|
@ -410,6 +431,7 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abc
|
|||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
msg := "Expected second parameter to be either simulate or version, neither was present"
|
||||
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"
|
||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||
}
|
||||
|
||||
req.Path = "/" + strings.Join(path[1:], "/")
|
||||
return queryable.Query(req)
|
||||
}
|
||||
|
||||
// nolint: unparam
|
||||
func handleQueryP2P(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
|
||||
func handleQueryP2P(app *BaseApp, path []string, _ abci.RequestQuery) (res abci.ResponseQuery) {
|
||||
// "/p2p" prefix for p2p queries
|
||||
if len(path) >= 4 {
|
||||
if path[1] == "filter" {
|
||||
if path[2] == "addr" {
|
||||
return app.FilterPeerByAddrPort(path[3])
|
||||
cmd, typ, arg := path[1], path[2], path[3]
|
||||
switch cmd {
|
||||
case "filter":
|
||||
switch typ {
|
||||
case "addr":
|
||||
return app.FilterPeerByAddrPort(arg)
|
||||
case "id":
|
||||
return app.FilterPeerByID(arg)
|
||||
}
|
||||
if path[2] == "pubkey" {
|
||||
// TODO: this should be changed to `id`
|
||||
// NOTE: this changed in tendermint and we didn't notice...
|
||||
return app.FilterPeerByPubKey(path[3])
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
msg := "Expected second parameter to be filter"
|
||||
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()
|
||||
}
|
||||
|
||||
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.
|
||||
// the queryRouter routes using path[1]. For example, in the path "custom/gov/proposal", queryRouter routes using "gov"
|
||||
// path[0] should be "custom" because "/custom" prefix is required for keeper
|
||||
// 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] == "" {
|
||||
return sdk.ErrUnknownRequest("No route for custom query specified").QueryResult()
|
||||
}
|
||||
|
||||
querier := app.queryRouter.Route(path[1])
|
||||
if querier == nil {
|
||||
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(
|
||||
app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger,
|
||||
app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.logger,
|
||||
).WithMinGasPrices(app.minGasPrices)
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return abci.ResponseQuery{
|
||||
|
@ -474,6 +502,7 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res
|
|||
Log: err.ABCILog(),
|
||||
}
|
||||
}
|
||||
|
||||
return abci.ResponseQuery{
|
||||
Code: uint32(sdk.CodeOK),
|
||||
Value: resBytes,
|
||||
|
@ -483,8 +512,7 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res
|
|||
// BeginBlock implements the ABCI application interface.
|
||||
func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) {
|
||||
if app.cms.TracingEnabled() {
|
||||
app.cms.ResetTraceContext()
|
||||
app.cms.WithTracingContext(sdk.TraceContext(
|
||||
app.cms.SetTracingContext(sdk.TraceContext(
|
||||
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
|
||||
// TODO: communicate this result to the address to pubkey map in slashing
|
||||
app.voteInfos = req.LastCommitInfo.GetVotes()
|
||||
return
|
||||
}
|
||||
|
||||
// CheckTx implements ABCI
|
||||
// CheckTx runs the "basic checks" to see whether or not a transaction can possibly be executed,
|
||||
// first decoding, then the ante handler (which checks signatures/fees/ValidateBasic),
|
||||
// then finally the route match to see whether a handler exists. CheckTx does not run the actual
|
||||
// Msg handler function(s).
|
||||
// CheckTx implements the ABCI interface. It runs the "basic checks" to see
|
||||
// whether or not a transaction can possibly be executed, first decoding, then
|
||||
// the ante handler (which checks signatures/fees/ValidateBasic), then finally
|
||||
// the route match to see whether a handler exists.
|
||||
//
|
||||
// NOTE:CheckTx does not run the actual Msg handler function(s).
|
||||
func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
|
||||
// Decode the Tx.
|
||||
var result sdk.Result
|
||||
var tx, err = app.txDecoder(txBytes)
|
||||
|
||||
tx, err := app.txDecoder(txBytes)
|
||||
if err != nil {
|
||||
result = err.Result()
|
||||
} 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) {
|
||||
|
||||
// Decode the Tx.
|
||||
var tx, err = app.txDecoder(txBytes)
|
||||
var result sdk.Result
|
||||
|
||||
tx, err := app.txDecoder(txBytes)
|
||||
if err != nil {
|
||||
result = err.Result()
|
||||
} else {
|
||||
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{
|
||||
Code: uint32(result.Code),
|
||||
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 {
|
||||
if msgs == nil || len(msgs) == 0 {
|
||||
// TODO: probably shouldn't be ErrInternal. Maybe new ErrInvalidMessage, or ?
|
||||
return sdk.ErrInternal("Tx.GetMsgs() must return at least one message in list")
|
||||
return sdk.ErrUnknownRequest("Tx.GetMsgs() must return at least one message in list")
|
||||
}
|
||||
|
||||
for _, msg := range msgs {
|
||||
|
@ -598,22 +620,25 @@ func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) (ctx sdk.Con
|
|||
WithTxBytes(txBytes).
|
||||
WithVoteInfos(app.voteInfos).
|
||||
WithConsensusParams(app.consensusParams)
|
||||
|
||||
if mode == runTxModeSimulate {
|
||||
ctx, _ = ctx.CacheContext()
|
||||
}
|
||||
|
||||
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) {
|
||||
// accumulate results
|
||||
logs := make([]string, 0, len(msgs))
|
||||
|
||||
var data []byte // NOTE: we just append them all (?!)
|
||||
var tags sdk.Tags // also just append them all
|
||||
var code sdk.CodeType
|
||||
var codespace sdk.CodespaceType
|
||||
|
||||
for msgIdx, msg := range msgs {
|
||||
// Match route.
|
||||
// match message route
|
||||
msgRoute := msg.Route()
|
||||
handler := app.router.Route(msgRoute)
|
||||
if handler == nil {
|
||||
|
@ -621,20 +646,20 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re
|
|||
}
|
||||
|
||||
var msgResult sdk.Result
|
||||
// Skip actual execution for CheckTx
|
||||
|
||||
// skip actual execution for CheckTx mode
|
||||
if mode != runTxModeCheck {
|
||||
msgResult = handler(ctx, msg)
|
||||
}
|
||||
|
||||
// NOTE: GasWanted is determined by ante handler and
|
||||
// GasUsed by the GasMeter
|
||||
// NOTE: GasWanted is determined by ante handler and 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...)
|
||||
tags = append(tags, sdk.MakeTag(sdk.TagAction, []byte(msg.Type())))
|
||||
tags = append(tags, sdk.MakeTag(sdk.TagAction, msg.Type()))
|
||||
tags = append(tags, msgResult.Tags...)
|
||||
|
||||
// Stop execution and return on first failed message.
|
||||
// stop execution and return on first failed message
|
||||
if !msgResult.IsOK() {
|
||||
logs = append(logs, fmt.Sprintf("Msg %d failed: %s", msgIdx, msgResult.Log))
|
||||
code = msgResult.Code
|
||||
|
@ -642,19 +667,17 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re
|
|||
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))
|
||||
}
|
||||
|
||||
// Set the final gas values.
|
||||
result = sdk.Result{
|
||||
Code: code,
|
||||
Codespace: codespace,
|
||||
Data: data,
|
||||
Log: strings.Join(logs, "\n"),
|
||||
GasUsed: ctx.GasMeter().GasConsumed(),
|
||||
// TODO: FeeAmount/FeeDenom
|
||||
Tags: tags,
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -679,7 +702,7 @@ func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (
|
|||
// TODO: https://github.com/cosmos/cosmos-sdk/issues/2824
|
||||
msCache := ms.CacheMultiStore()
|
||||
if msCache.TracingEnabled() {
|
||||
msCache = msCache.WithTracingContext(
|
||||
msCache = msCache.SetTracingContext(
|
||||
sdk.TraceContext(
|
||||
map[string]interface{}{
|
||||
"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 {
|
||||
switch rType := r.(type) {
|
||||
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()
|
||||
default:
|
||||
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()
|
||||
}()
|
||||
|
||||
// If BlockGasMeter() panics it will be caught by the above recover and
|
||||
// return an error - in any case BlockGasMeter will consume gas past
|
||||
// the limit.
|
||||
// NOTE: this must exist in a separate defer function for the
|
||||
// above recovery to recover from this one
|
||||
// If BlockGasMeter() panics it will be caught by the above recover and will
|
||||
// return an error - in any case BlockGasMeter will consume gas past the limit.
|
||||
//
|
||||
// NOTE: This must exist in a separate defer function for the above recovery
|
||||
// to recover from this one.
|
||||
defer func() {
|
||||
if mode == runTxModeDeliver {
|
||||
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 {
|
||||
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()
|
||||
}
|
||||
|
||||
// Execute the ante handler if one is defined.
|
||||
if app.anteHandler != nil {
|
||||
var anteCtx sdk.Context
|
||||
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)
|
||||
}
|
||||
|
||||
gasWanted = result.GasWanted
|
||||
|
||||
if abort {
|
||||
return result
|
||||
}
|
||||
|
||||
msCache.Write()
|
||||
gasWanted = result.GasWanted
|
||||
}
|
||||
|
||||
if mode == runTxModeCheck {
|
||||
|
@ -810,10 +836,10 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
|||
return
|
||||
}
|
||||
|
||||
// EndBlock implements the ABCI application interface.
|
||||
// EndBlock implements the ABCI interface.
|
||||
func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) {
|
||||
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 {
|
||||
|
@ -823,27 +849,41 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc
|
|||
return
|
||||
}
|
||||
|
||||
// Implements ABCI
|
||||
// Commit implements the ABCI interface.
|
||||
func (app *BaseApp) Commit() (res abci.ResponseCommit) {
|
||||
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()
|
||||
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.
|
||||
// Use the header from this latest block.
|
||||
app.setCheckState(header)
|
||||
|
||||
// Empty the Deliver state
|
||||
// empty/reset the deliver state
|
||||
app.deliverState = nil
|
||||
|
||||
return abci.ResponseCommit{
|
||||
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"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
store "github.com/cosmos/cosmos-sdk/store/types"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -21,14 +21,10 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
// make some cap keys
|
||||
capKey1 = sdk.NewKVStoreKey("key1")
|
||||
capKey2 = sdk.NewKVStoreKey("key2")
|
||||
)
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
// Helpers for setup. Most tests should be able to use setupBaseApp
|
||||
|
||||
func defaultLogger() log.Logger {
|
||||
return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
|
||||
}
|
||||
|
@ -70,9 +66,6 @@ func setupBaseApp(t *testing.T, options ...func(*BaseApp)) *BaseApp {
|
|||
return app
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
// test mounting and loading stores
|
||||
|
||||
func TestMountStores(t *testing.T) {
|
||||
app := setupBaseApp(t)
|
||||
|
||||
|
@ -137,6 +130,41 @@ func TestLoadVersion(t *testing.T) {
|
|||
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) {
|
||||
lastHeight := app.LastBlockHeight()
|
||||
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
|
||||
// correct error codes are returned when not
|
||||
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.
|
||||
|
@ -210,8 +220,46 @@ func TestInfo(t *testing.T) {
|
|||
// TODO
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
// InitChain, BeginBlock, EndBlock
|
||||
func TestBaseAppOptionSeal(t *testing.T) {
|
||||
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) {
|
||||
name := t.Name()
|
||||
|
@ -280,11 +328,6 @@ func TestInitChainer(t *testing.T) {
|
|||
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.
|
||||
type txTest struct {
|
||||
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 {
|
||||
return []byte{byte(i)}
|
||||
}
|
||||
|
@ -655,20 +695,20 @@ func TestSimulateTx(t *testing.T) {
|
|||
app.BeginBlock(abci.RequestBeginBlock{})
|
||||
|
||||
tx := newTxCounter(count, count)
|
||||
txBytes, err := cdc.MarshalBinaryLengthPrefixed(tx)
|
||||
require.Nil(t, err)
|
||||
|
||||
// simulate a message, check gas reported
|
||||
result := app.Simulate(tx)
|
||||
result := app.Simulate(txBytes, tx)
|
||||
require.True(t, result.IsOK(), result.Log)
|
||||
require.Equal(t, gasConsumed, result.GasUsed)
|
||||
|
||||
// simulate again, same result
|
||||
result = app.Simulate(tx)
|
||||
result = app.Simulate(txBytes, tx)
|
||||
require.True(t, result.IsOK(), result.Log)
|
||||
require.Equal(t, gasConsumed, result.GasUsed)
|
||||
|
||||
// simulate by calling Query with encoded tx
|
||||
txBytes, err := cdc.MarshalBinaryLengthPrefixed(tx)
|
||||
require.Nil(t, err)
|
||||
query := abci.RequestQuery{
|
||||
Path: "/app/simulate",
|
||||
Data: txBytes,
|
||||
|
@ -686,10 +726,6 @@ func TestSimulateTx(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------------
|
||||
// Tx failure cases
|
||||
// TODO: add more
|
||||
|
||||
func TestRunInvalidTransaction(t *testing.T) {
|
||||
anteOpt := func(bapp *BaseApp) {
|
||||
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{}
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted))
|
||||
|
||||
// NOTE/TODO/XXX:
|
||||
// AnteHandlers must have their own defer/recover in order
|
||||
// for the BaseApp to know how much gas was used used!
|
||||
// This is because the GasMeter is created in the AnteHandler,
|
||||
// but if it panics the context won't be set properly in runTx's recover ...
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
switch rType := r.(type) {
|
||||
|
@ -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) {
|
||||
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted))
|
||||
|
||||
// NOTE/TODO/XXX:
|
||||
// AnteHandlers must have their own defer/recover in order
|
||||
// for the BaseApp to know how much gas was used used!
|
||||
// This is because the GasMeter is created in the AnteHandler,
|
||||
// but if it panics the context won't be set properly in runTx's recover ...
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
switch rType := r.(type) {
|
||||
|
@ -1031,3 +1057,162 @@ func TestBaseAppAnteHandler(t *testing.T) {
|
|||
app.EndBlock(abci.RequestEndBlock{})
|
||||
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
|
||||
|
||||
import (
|
||||
"github.com/tendermint/tendermint/abci/server"
|
||||
"regexp"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
var isAlphaNumeric = regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString
|
||||
|
||||
// nolint - Mostly for testing
|
||||
func (app *BaseApp) Check(tx sdk.Tx) (result sdk.Result) {
|
||||
return app.runTx(runTxModeCheck, nil, tx)
|
||||
}
|
||||
|
||||
// nolint - full tx execution
|
||||
func (app *BaseApp) Simulate(tx sdk.Tx) (result sdk.Result) {
|
||||
return app.runTx(runTxModeSimulate, nil, tx)
|
||||
func (app *BaseApp) Simulate(txBytes []byte, tx sdk.Tx) (result sdk.Result) {
|
||||
return app.runTx(runTxModeSimulate, txBytes, tx)
|
||||
}
|
||||
|
||||
// nolint
|
||||
|
@ -23,27 +25,13 @@ func (app *BaseApp) Deliver(tx sdk.Tx) (result sdk.Result) {
|
|||
return app.runTx(runTxModeDeliver, nil, tx)
|
||||
}
|
||||
|
||||
// RunForever BasecoinApp execution and cleanup
|
||||
func RunForever(app abci.Application) {
|
||||
|
||||
// Start the ABCI server
|
||||
srv, err := server.NewServer("0.0.0.0:26658", "socket", app)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
return
|
||||
}
|
||||
err = srv.Start()
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
return
|
||||
// Context with current {check, deliver}State of the app
|
||||
// used by tests
|
||||
func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context {
|
||||
if isCheckTx {
|
||||
return sdk.NewContext(app.checkState.ms, header, true, app.logger).
|
||||
WithMinGasPrices(app.minGasPrices)
|
||||
}
|
||||
|
||||
// Wait forever
|
||||
cmn.TrapSignal(func() {
|
||||
// Cleanup
|
||||
err := srv.Stop()
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
})
|
||||
return sdk.NewContext(app.deliverState.ms, header, false, app.logger)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ func SetPruning(opts sdk.PruningOptions) func(*BaseApp) {
|
|||
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) {
|
||||
gasPrices, err := sdk.ParseDecCoins(gasPricesStr)
|
||||
if err != nil {
|
||||
|
@ -84,11 +84,11 @@ func (app *BaseApp) SetAddrPeerFilter(pf sdk.PeerFilter) {
|
|||
app.addrPeerFilter = pf
|
||||
}
|
||||
|
||||
func (app *BaseApp) SetPubKeyPeerFilter(pf sdk.PeerFilter) {
|
||||
func (app *BaseApp) SetIDPeerFilter(pf sdk.PeerFilter) {
|
||||
if app.sealed {
|
||||
panic("SetPubKeyPeerFilter() on sealed BaseApp")
|
||||
panic("SetIDPeerFilter() on sealed BaseApp")
|
||||
}
|
||||
app.pubkeyPeerFilter = pf
|
||||
app.idPeerFilter = pf
|
||||
}
|
||||
|
||||
func (app *BaseApp) SetFauxMerkleMode() {
|
||||
|
@ -97,25 +97,3 @@ func (app *BaseApp) SetFauxMerkleMode() {
|
|||
}
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
|
@ -10,32 +12,34 @@ type QueryRouter interface {
|
|||
Route(path string) (h sdk.Querier)
|
||||
}
|
||||
|
||||
type queryrouter struct {
|
||||
type queryRouter struct {
|
||||
routes map[string]sdk.Querier
|
||||
}
|
||||
|
||||
// nolint
|
||||
// NewRouter - create new router
|
||||
// TODO either make Function unexported or make return type (router) Exported
|
||||
func NewQueryRouter() *queryrouter {
|
||||
return &queryrouter{
|
||||
// NewQueryRouter returns a reference to a new queryRouter.
|
||||
//
|
||||
// TODO: Either make the function private or make return type (queryRouter) public.
|
||||
func NewQueryRouter() *queryRouter { // nolint: golint
|
||||
return &queryRouter{
|
||||
routes: map[string]sdk.Querier{},
|
||||
}
|
||||
}
|
||||
|
||||
// AddRoute - Adds an sdk.Querier to the route provided. Panics on duplicate
|
||||
func (rtr *queryrouter) AddRoute(r string, q sdk.Querier) QueryRouter {
|
||||
if !isAlphaNumeric(r) {
|
||||
// AddRoute adds a query path to the router with a given Querier. It will panic
|
||||
// if a duplicate route is given. The route must be alphanumeric.
|
||||
func (qrt *queryRouter) AddRoute(path string, q sdk.Querier) QueryRouter {
|
||||
if !isAlphaNumeric(path) {
|
||||
panic("route expressions can only contain alphanumeric characters")
|
||||
}
|
||||
if rtr.routes[r] != nil {
|
||||
panic("route has already been initialized")
|
||||
if qrt.routes[path] != nil {
|
||||
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
|
||||
func (rtr *queryrouter) Route(path string) (h sdk.Querier) {
|
||||
return rtr.routes[path]
|
||||
// Route returns the Querier for a given query route path.
|
||||
func (qrt *queryRouter) Route(path string) sdk.Querier {
|
||||
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
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
@ -12,44 +12,36 @@ type Router interface {
|
|||
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 {
|
||||
routes []route
|
||||
routes map[string]sdk.Handler
|
||||
}
|
||||
|
||||
// nolint
|
||||
// NewRouter - create new router
|
||||
// TODO either make Function unexported or make return type (router) Exported
|
||||
func NewRouter() *router {
|
||||
// NewRouter returns a reference to a new router.
|
||||
//
|
||||
// TODO: Either make the function private or make return type (router) public.
|
||||
func NewRouter() *router { // nolint: golint
|
||||
return &router{
|
||||
routes: make([]route, 0),
|
||||
routes: make(map[string]sdk.Handler),
|
||||
}
|
||||
}
|
||||
|
||||
var isAlphaNumeric = regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString
|
||||
|
||||
// AddRoute - TODO add description
|
||||
func (rtr *router) AddRoute(r string, h sdk.Handler) Router {
|
||||
if !isAlphaNumeric(r) {
|
||||
// AddRoute adds a route path to the router with a given handler. The route must
|
||||
// be alphanumeric.
|
||||
func (rtr *router) AddRoute(path string, h sdk.Handler) Router {
|
||||
if !isAlphaNumeric(path) {
|
||||
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
|
||||
}
|
||||
|
||||
// Route - TODO add description
|
||||
// TODO handle expressive matches.
|
||||
func (rtr *router) Route(path string) (h sdk.Handler) {
|
||||
for _, route := range rtr.routes {
|
||||
if route.r == path {
|
||||
return route.h
|
||||
}
|
||||
}
|
||||
return nil
|
||||
// Route returns a handler for a given route path.
|
||||
//
|
||||
// TODO: Handle expressive matches.
|
||||
func (rtr *router) Route(path string) sdk.Handler {
|
||||
return rtr.routes[path]
|
||||
}
|
||||
|
|
|
@ -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/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
|
||||
"github.com/pelletier/go-toml"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -32,7 +30,7 @@ func init() {
|
|||
|
||||
// ConfigCmd returns a CLI command to interactively create a
|
||||
// Gaia CLI config file.
|
||||
func ConfigCmd() *cobra.Command {
|
||||
func ConfigCmd(defaultCLIHome string) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "config <key> [value]",
|
||||
Short: "Create or query a Gaia CLI configuration file",
|
||||
|
@ -40,7 +38,7 @@ func ConfigCmd() *cobra.Command {
|
|||
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")
|
||||
cmd.Flags().Bool(flagGet, false,
|
||||
"print configuration value or its default if unset")
|
||||
|
|
|
@ -2,169 +2,79 @@ package context
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/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
|
||||
// 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
|
||||
// defined.
|
||||
func (ctx CLIContext) BroadcastTx(txBytes []byte) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
func (ctx CLIContext) BroadcastTx(txBytes []byte) (res sdk.TxResponse, err error) {
|
||||
if ctx.Async {
|
||||
res, err := ctx.broadcastTxAsync(txBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if res, err = ctx.BroadcastTxAsync(txBytes); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resCommit := resultBroadcastTxToCommit(res)
|
||||
return resCommit, err
|
||||
return
|
||||
}
|
||||
|
||||
return ctx.broadcastTxCommit(txBytes)
|
||||
if res, err = ctx.BroadcastTxAndAwaitCommit(txBytes); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// BroadcastTxAndAwaitCommit broadcasts transaction bytes to a Tendermint node
|
||||
// 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()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return sdk.TxResponse{}, err
|
||||
}
|
||||
|
||||
res, err := node.BroadcastTxCommit(tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
return sdk.NewResponseFormatBroadcastTxCommit(res), err
|
||||
}
|
||||
|
||||
if !res.CheckTx.IsOK() {
|
||||
return res, errors.Errorf(res.CheckTx.Log)
|
||||
return sdk.NewResponseFormatBroadcastTxCommit(res), fmt.Errorf(res.CheckTx.Log)
|
||||
}
|
||||
|
||||
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
|
||||
// synchronously.
|
||||
func (ctx CLIContext) BroadcastTxSync(tx []byte) (*ctypes.ResultBroadcastTx, error) {
|
||||
// BroadcastTxSync broadcasts transaction bytes to a Tendermint node synchronously.
|
||||
func (ctx CLIContext) BroadcastTxSync(tx []byte) (sdk.TxResponse, error) {
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return sdk.TxResponse{}, err
|
||||
}
|
||||
|
||||
res, err := node.BroadcastTxSync(tx)
|
||||
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
|
||||
// asynchronously.
|
||||
func (ctx CLIContext) BroadcastTxAsync(tx []byte) (*ctypes.ResultBroadcastTx, error) {
|
||||
// BroadcastTxAsync broadcasts transaction bytes to a Tendermint node asynchronously.
|
||||
func (ctx CLIContext) BroadcastTxAsync(tx []byte) (sdk.TxResponse, error) {
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return sdk.TxResponse{}, err
|
||||
}
|
||||
|
||||
res, err := node.BroadcastTxAsync(tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
return sdk.NewResponseFormatBroadcastTx(res), err
|
||||
}
|
||||
|
||||
return 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
|
||||
return sdk.NewResponseFormatBroadcastTx(res), err
|
||||
}
|
||||
|
|
|
@ -8,7 +8,9 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
cryptokeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
@ -19,13 +21,12 @@ import (
|
|||
tmliteProxy "github.com/tendermint/tendermint/lite/proxy"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
cskeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
var (
|
||||
verifier tmlite.Verifier
|
||||
verifier tmlite.Verifier
|
||||
verifierHome string
|
||||
)
|
||||
|
||||
// CLIContext implements a typical CLI context created in SDK modules for
|
||||
|
@ -34,6 +35,7 @@ type CLIContext struct {
|
|||
Codec *codec.Codec
|
||||
AccDecoder auth.AccountDecoder
|
||||
Client rpcclient.Client
|
||||
Keybase cryptokeys.Keybase
|
||||
Output io.Writer
|
||||
OutputFormat string
|
||||
Height int64
|
||||
|
@ -45,10 +47,11 @@ type CLIContext struct {
|
|||
Async bool
|
||||
PrintResponse bool
|
||||
Verifier tmlite.Verifier
|
||||
VerifierHome string
|
||||
Simulate bool
|
||||
GenerateOnly bool
|
||||
fromAddress types.AccAddress
|
||||
fromName string
|
||||
FromAddress sdk.AccAddress
|
||||
FromName string
|
||||
Indent bool
|
||||
}
|
||||
|
||||
|
@ -63,11 +66,16 @@ func NewCLIContext() CLIContext {
|
|||
}
|
||||
|
||||
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
|
||||
if verifier == nil {
|
||||
if verifier == nil || verifierHome != viper.GetString(cli.HomeFlag) {
|
||||
verifier = createVerifier()
|
||||
verifierHome = viper.GetString(cli.HomeFlag)
|
||||
}
|
||||
|
||||
return CLIContext{
|
||||
|
@ -85,8 +93,8 @@ func NewCLIContext() CLIContext {
|
|||
Verifier: verifier,
|
||||
Simulate: viper.GetBool(client.FlagDryRun),
|
||||
GenerateOnly: viper.GetBool(client.FlagGenerateOnly),
|
||||
fromAddress: fromAddress,
|
||||
fromName: fromName,
|
||||
FromAddress: fromAddress,
|
||||
FromName: fromName,
|
||||
Indent: viper.GetBool(client.FlagIndentResponse),
|
||||
}
|
||||
}
|
||||
|
@ -137,37 +145,6 @@ func createVerifier() tmlite.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.
|
||||
func (ctx CLIContext) WithCodec(cdc *codec.Codec) CLIContext {
|
||||
ctx.Codec = cdc
|
||||
|
@ -255,6 +232,19 @@ func (ctx CLIContext) WithSimulation(simulate bool) CLIContext {
|
|||
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
|
||||
// NOTE: pass in marshalled structs that have been unmarshaled
|
||||
// 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))
|
||||
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
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
@ -9,7 +9,7 @@ import (
|
|||
// ErrInvalidAccount returns a standardized error reflecting that a given
|
||||
// account address does not exist.
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
// newer than the given height
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
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
|
||||
|
@ -82,13 +82,13 @@ func (ctx CLIContext) GetAccount(address []byte) (auth.Account, error) {
|
|||
}
|
||||
|
||||
// GetFromAddress returns the from address from the context's name.
|
||||
func (ctx CLIContext) GetFromAddress() (sdk.AccAddress, error) {
|
||||
return ctx.fromAddress, nil
|
||||
func (ctx CLIContext) GetFromAddress() sdk.AccAddress {
|
||||
return ctx.FromAddress
|
||||
}
|
||||
|
||||
// GetFromName returns the key name for the current context.
|
||||
func (ctx CLIContext) GetFromName() (string, error) {
|
||||
return ctx.fromName, nil
|
||||
func (ctx CLIContext) GetFromName() string {
|
||||
return ctx.FromName
|
||||
}
|
||||
|
||||
// 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
|
||||
// error is returned if it does not.
|
||||
func (ctx CLIContext) EnsureAccountExists() error {
|
||||
addr, err := ctx.GetFromAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addr := ctx.GetFromAddress()
|
||||
accountBytes, err := ctx.QueryStore(auth.AddressStoreKey(addr), ctx.AccountStore)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -169,7 +165,7 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro
|
|||
|
||||
resp := result.Response
|
||||
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
|
||||
|
@ -211,7 +207,7 @@ func (ctx CLIContext) verifyProof(queryPath string, resp abci.ResponseQuery) err
|
|||
}
|
||||
|
||||
// TODO: Instead of reconstructing, stash on CLIContext field?
|
||||
prt := store.DefaultProofRuntime()
|
||||
prt := rootmulti.DefaultProofRuntime()
|
||||
|
||||
// TODO: Better convention for path?
|
||||
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(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)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to prove merkle proof")
|
||||
|
@ -251,7 +254,7 @@ func isQueryStoreWithProof(path string) bool {
|
|||
return false
|
||||
case paths[0] != "store":
|
||||
return false
|
||||
case store.RequireProof("/" + paths[2]):
|
||||
case rootmulti.RequireProof("/" + paths[2]):
|
||||
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
|
||||
const (
|
||||
// DefaultGasAdjustment is applied to gas estimates to avoid tx
|
||||
// execution failures due to state changes that might
|
||||
// occur between the tx simulation and the actual run.
|
||||
// DefaultGasAdjustment is applied to gas estimates to avoid tx execution
|
||||
// failures due to state changes that might occur between the tx simulation
|
||||
// and the actual run.
|
||||
DefaultGasAdjustment = 1.0
|
||||
DefaultGasLimit = 200000
|
||||
GasFlagAuto = "auto"
|
||||
|
@ -40,7 +40,7 @@ const (
|
|||
FlagListenAddr = "laddr"
|
||||
FlagCORS = "cors"
|
||||
FlagMaxOpenConnections = "max-open"
|
||||
FlagInsecure = "insecure"
|
||||
FlagTLS = "tls"
|
||||
FlagSSLHosts = "ssl-hosts"
|
||||
FlagSSLCertFile = "ssl-certfile"
|
||||
FlagSSLKeyFile = "ssl-keyfile"
|
||||
|
@ -103,21 +103,15 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
|
|||
|
||||
// RegisterRestServerFlags registers the flags required for rest server
|
||||
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().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(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(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().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
|
||||
}
|
||||
|
||||
|
|
|
@ -6,18 +6,35 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/bgentry/speakeasy"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// MinPassLength is the minimum acceptable password length
|
||||
const MinPassLength = 8
|
||||
|
||||
var currentStdin *bufio.Reader
|
||||
|
||||
func init() {
|
||||
currentStdin = bufio.NewReader(os.Stdin)
|
||||
}
|
||||
|
||||
// BufferStdin is used to allow reading prompts for stdin
|
||||
// multiple times, when we read from non-tty
|
||||
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)
|
||||
|
@ -36,18 +53,12 @@ func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) {
|
|||
if len(pass) < MinPassLength {
|
||||
// Return the given password to the upstream client so it can handle a
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
// match (for creating a new password).
|
||||
// It enforces the password length. Only parses password once if
|
||||
|
|
|
@ -6,18 +6,9 @@ import (
|
|||
"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
|
||||
// useful for --dry-run to generate a seed phrase without
|
||||
// storing the key
|
||||
func MockKeyBase() keys.Keybase {
|
||||
return GetKeyBase(dbm.NewMemDB())
|
||||
return keys.New(dbm.NewMemDB())
|
||||
}
|
||||
|
|
|
@ -9,26 +9,26 @@ import (
|
|||
"os"
|
||||
"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/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
ccrypto "github.com/cosmos/cosmos-sdk/crypto"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/go-bip39"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/multisig"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
flagInteractive = "interactive"
|
||||
flagBIP44Path = "bip44-path"
|
||||
flagRecover = "recover"
|
||||
flagNoBackup = "no-backup"
|
||||
flagDryRun = "dry-run"
|
||||
|
@ -38,6 +38,11 @@ const (
|
|||
flagNoSort = "nosort"
|
||||
)
|
||||
|
||||
const (
|
||||
maxValidAccountValue = int(0x80000000 - 1)
|
||||
maxValidIndexalue = int(0x80000000 - 1)
|
||||
)
|
||||
|
||||
func addKeyCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
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().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().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(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")
|
||||
|
@ -86,7 +90,7 @@ input
|
|||
output
|
||||
- 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 err error
|
||||
var encryptPassword string
|
||||
|
@ -95,24 +99,25 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
|||
name := args[0]
|
||||
|
||||
interactive := viper.GetBool(flagInteractive)
|
||||
showMnemonic := !viper.GetBool(flagNoBackup)
|
||||
|
||||
if viper.GetBool(flagDryRun) {
|
||||
// we throw this away, so don't enforce args,
|
||||
// we want to get a new random seed phrase quickly
|
||||
kb = client.MockKeyBase()
|
||||
encryptPassword = "throwing-this-key-away"
|
||||
encryptPassword = app.DefaultKeyPass
|
||||
} else {
|
||||
kb, err = GetKeyBaseWithWritePerm()
|
||||
kb, err = NewKeyBaseFromHomeFlag()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := kb.Get(name)
|
||||
_, err = kb.Get(name)
|
||||
if err == nil {
|
||||
// account exists, ask for user confirmation
|
||||
if response, err := client.GetConfirmation(
|
||||
fmt.Sprintf("override the existing name %s", name), buf); err != nil || !response {
|
||||
return err
|
||||
if response, err2 := client.GetConfirmation(
|
||||
fmt.Sprintf("override the existing name %s", name), buf); err2 != nil || !response {
|
||||
return err2
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,6 +149,7 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
|||
if _, err := kb.CreateOffline(name, pk); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Key %q saved to disk.", name)
|
||||
return nil
|
||||
}
|
||||
|
@ -164,51 +170,37 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kb.CreateOffline(name, pk)
|
||||
_, err = kb.CreateOffline(name, pk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
bipFlag := cmd.Flags().Lookup(flagBIP44Path)
|
||||
bip44Params, err := getBIP44ParamsAndPath(bipFlag.Value.String(), bipFlag.Changed || !interactive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account := uint32(viper.GetInt(flagAccount))
|
||||
index := uint32(viper.GetInt(flagIndex))
|
||||
|
||||
// If we're using ledger, only thing we need is the path. So generate key and
|
||||
// we're done.
|
||||
// If we're using ledger, only thing we need is the path. So generate key and we're done.
|
||||
if viper.GetBool(client.FlagUseLedger) {
|
||||
account := uint32(viper.GetInt(flagAccount))
|
||||
index := uint32(viper.GetInt(flagIndex))
|
||||
path := ccrypto.DerivationPath{44, 118, account, 0, index}
|
||||
info, err := kb.CreateLedger(name, path, keys.Secp256k1)
|
||||
info, err := kb.CreateLedger(name, keys.Secp256k1, account, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printCreate(info, "")
|
||||
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
|
||||
return printCreate(info, false, "")
|
||||
}
|
||||
|
||||
// Get bip39 mnemonic
|
||||
var mnemonic string
|
||||
if interactive {
|
||||
mnemonic, err = client.GetString("Enter your bip39 mnemonic, or hit enter to generate one.", buf)
|
||||
var bip39Passphrase string
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -227,8 +219,12 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
// get bip39 passphrase
|
||||
var bip39Passphrase string
|
||||
if !bip39.IsMnemonicValid(mnemonic) {
|
||||
fmt.Fprintf(os.Stderr, "Error: Mnemonic is not valid")
|
||||
return nil
|
||||
}
|
||||
|
||||
// override bip39 passphrase
|
||||
if interactive {
|
||||
bip39Passphrase, err = client.GetString(
|
||||
"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 {
|
||||
return err
|
||||
}
|
||||
printCreate(info, mnemonic)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBIP44ParamsAndPath(path string, flagSet bool) (*hd.BIP44Params, error) {
|
||||
buf := client.BufferStdin()
|
||||
bip44Path := path
|
||||
|
||||
// if it wasn't set in the flag, give it a chance to overide interactively
|
||||
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
|
||||
}
|
||||
// Recover key from seed passphrase
|
||||
if viper.GetBool(flagRecover) {
|
||||
// Hide mnemonic from output
|
||||
showMnemonic = false
|
||||
mnemonic = ""
|
||||
}
|
||||
|
||||
bip44params, err := hd.NewParamsFromPath(bip44Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bip44params, nil
|
||||
return printCreate(info, showMnemonic, mnemonic)
|
||||
}
|
||||
|
||||
func printCreate(info keys.Info, seed string) {
|
||||
func printCreate(info keys.Info, showMnemonic bool, mnemonic string) error {
|
||||
output := viper.Get(cli.OutputFlag)
|
||||
|
||||
switch output {
|
||||
case "text":
|
||||
fmt.Fprintln(os.Stderr, "")
|
||||
case OutputFormatText:
|
||||
fmt.Fprintln(os.Stderr)
|
||||
printKeyInfo(info, Bech32KeyOutput)
|
||||
|
||||
// print seed unless requested not to.
|
||||
if !viper.GetBool(client.FlagUseLedger) && !viper.GetBool(flagNoBackup) {
|
||||
fmt.Fprintln(os.Stderr, "\n**Important** write this seed phrase in a safe place.")
|
||||
// print mnemonic unless requested not to.
|
||||
if showMnemonic {
|
||||
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)
|
||||
fmt.Fprintln(os.Stderr, seed)
|
||||
fmt.Fprintln(os.Stderr, "")
|
||||
fmt.Fprintln(os.Stderr, mnemonic)
|
||||
}
|
||||
case "json":
|
||||
case OutputFormatJSON:
|
||||
out, err := Bech32KeyOutput(info)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
if !viper.GetBool(flagNoBackup) {
|
||||
out.Seed = seed
|
||||
|
||||
if showMnemonic {
|
||||
out.Mnemonic = mnemonic
|
||||
}
|
||||
|
||||
var jsonString []byte
|
||||
if viper.GetBool(client.FlagIndentResponse) {
|
||||
jsonString, err = cdc.MarshalJSONIndent(out, "", " ")
|
||||
} else {
|
||||
jsonString, err = cdc.MarshalJSON(out)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err) // really shouldn't happen...
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, string(jsonString))
|
||||
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
|
||||
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("-------------------------------------")
|
||||
return nil
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// REST
|
||||
|
||||
// new key request REST body
|
||||
type NewKeyBody struct {
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
Seed string `json:"seed"`
|
||||
// function to just create a new seed to display in the UI before actually persisting it in the keybase
|
||||
func generateMnemonic(algo keys.SigningAlgo) string {
|
||||
kb := client.MockKeyBase()
|
||||
pass := app.DefaultKeyPass
|
||||
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
|
||||
func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var kb keys.Keybase
|
||||
var m NewKeyBody
|
||||
var m AddNewKey
|
||||
|
||||
kb, err := GetKeyBaseWithWritePerm()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
kb, err := NewKeyBaseFromHomeFlag()
|
||||
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &m)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check parameters
|
||||
if m.Name == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
err = errMissingName()
|
||||
w.Write([]byte(err.Error()))
|
||||
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingName())
|
||||
return
|
||||
}
|
||||
if m.Password == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
err = errMissingPassword()
|
||||
w.Write([]byte(err.Error()))
|
||||
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingPassword())
|
||||
return
|
||||
}
|
||||
|
||||
// check if already exists
|
||||
infos, err := kb.List()
|
||||
for _, info := range infos {
|
||||
if info.GetName() == m.Name {
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
err = errKeyNameConflict(m.Name)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
mnemonic := m.Mnemonic
|
||||
// if mnemonic is empty, generate one
|
||||
if mnemonic == "" {
|
||||
mnemonic = generateMnemonic(keys.Secp256k1)
|
||||
}
|
||||
if !bip39.IsMnemonicValid(mnemonic) {
|
||||
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidMnemonic())
|
||||
}
|
||||
|
||||
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
|
||||
seed := m.Seed
|
||||
if seed == "" {
|
||||
seed = getSeed(keys.Secp256k1)
|
||||
}
|
||||
info, err := kb.CreateKey(m.Name, seed, m.Password)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
account := uint32(m.Account)
|
||||
index := uint32(m.Index)
|
||||
info, err := kb.CreateAccount(m.Name, mnemonic, keys.DefaultBIP39Passphrase, m.Password, account, index)
|
||||
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
|
||||
return
|
||||
}
|
||||
|
||||
keyOutput, err := Bech32KeyOutput(info)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
|
||||
return
|
||||
}
|
||||
|
||||
keyOutput.Seed = seed
|
||||
keyOutput.Mnemonic = mnemonic
|
||||
|
||||
PostProcessResponse(w, cdc, keyOutput, indent)
|
||||
}
|
||||
|
@ -426,22 +407,17 @@ func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
|
|||
func SeedRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
algoType := vars["type"]
|
||||
|
||||
// algo type defaults to secp256k1
|
||||
if algoType == "" {
|
||||
algoType = "secp256k1"
|
||||
}
|
||||
algo := keys.SigningAlgo(algoType)
|
||||
|
||||
seed := getSeed(algo)
|
||||
algo := keys.SigningAlgo(algoType)
|
||||
seed := generateMnemonic(algo)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(seed))
|
||||
}
|
||||
|
||||
// RecoverKeyBody is recover key request REST body
|
||||
type RecoverKeyBody struct {
|
||||
Password string `json:"password"`
|
||||
Seed string `json:"seed"`
|
||||
_, _ = w.Write([]byte(seed))
|
||||
}
|
||||
|
||||
// RecoverRequestHandler performs key recover request
|
||||
|
@ -449,67 +425,66 @@ func RecoverRequestHandler(indent bool) http.HandlerFunc {
|
|||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
name := vars["name"]
|
||||
var m RecoverKeyBody
|
||||
var m RecoverKey
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
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()))
|
||||
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
|
||||
return
|
||||
}
|
||||
|
||||
err = cdc.UnmarshalJSON(body, &m)
|
||||
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
|
||||
return
|
||||
}
|
||||
|
||||
kb, err := NewKeyBaseFromHomeFlag()
|
||||
CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err)
|
||||
|
||||
if name == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
err = errMissingName()
|
||||
w.Write([]byte(err.Error()))
|
||||
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingName())
|
||||
return
|
||||
}
|
||||
if m.Password == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
err = errMissingPassword()
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
if m.Seed == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
err = errMissingSeed()
|
||||
w.Write([]byte(err.Error()))
|
||||
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingPassword())
|
||||
return
|
||||
}
|
||||
|
||||
kb, err := GetKeyBaseWithWritePerm()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
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
|
||||
}
|
||||
mnemonic := m.Mnemonic
|
||||
if !bip39.IsMnemonicValid(mnemonic) {
|
||||
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidMnemonic())
|
||||
}
|
||||
|
||||
info, err := kb.CreateKey(name, m.Seed, m.Password)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
if m.Mnemonic == "" {
|
||||
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingMnemonic())
|
||||
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(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
|
||||
}
|
||||
|
||||
keyOutput, err := Bech32KeyOutput(info)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
|
||||
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/cosmos/cosmos-sdk/client"
|
||||
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
keyerror "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -49,7 +49,7 @@ gaiacli.
|
|||
func runDeleteCmd(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
kb, err := GetKeyBaseWithWritePerm()
|
||||
kb, err := NewKeyBaseFromHomeFlag()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -91,6 +91,17 @@ func runDeleteCmd(cmd *cobra.Command, args []string) error {
|
|||
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
|
||||
|
||||
|
@ -110,42 +121,31 @@ func DeleteKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
|
|||
err := decoder.Decode(&m)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
kb, err = GetKeyBaseWithWritePerm()
|
||||
kb, err = NewKeyBaseFromHomeFlag()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
err = kb.Delete(name, m.Password, false)
|
||||
if keyerror.IsErrKeyNotFound(err) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
} else if keyerror.IsErrWrongPassword(err) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
} else if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
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 {
|
||||
|
@ -14,6 +14,18 @@ func errMissingPassword() error {
|
|||
return fmt.Errorf("you have to specify a password for the locally stored account")
|
||||
}
|
||||
|
||||
func errMissingSeed() error {
|
||||
return fmt.Errorf("you have to specify seed for key recover")
|
||||
func errMissingMnemonic() error {
|
||||
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"
|
||||
)
|
||||
|
||||
// CMD
|
||||
|
||||
// listKeysCmd represents the list command
|
||||
var listKeysCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all keys",
|
||||
Long: `Return a list of all public keys stored by this key manager
|
||||
func listKeysCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all keys",
|
||||
Long: `Return a list of all public keys stored by this key manager
|
||||
along with their associated name and address.`,
|
||||
RunE: runListCmd,
|
||||
RunE: runListCmd,
|
||||
}
|
||||
}
|
||||
|
||||
func runListCmd(cmd *cobra.Command, args []string) error {
|
||||
kb, err := GetKeyBase()
|
||||
kb, err := NewKeyBaseFromHomeFlag()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -36,16 +35,16 @@ func runListCmd(cmd *cobra.Command, args []string) error {
|
|||
// query key list REST handler
|
||||
func QueryKeysRequestHandler(indent bool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
kb, err := GetKeyBase()
|
||||
kb, err := NewKeyBaseFromHomeFlag()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
infos, err := kb.List()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
// 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)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
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"
|
||||
"fmt"
|
||||
|
||||
bip39 "github.com/bartekn/go-bip39"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
|
||||
bip39 "github.com/bartekn/go-bip39"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -45,9 +44,7 @@ func runMnemonicCmd(cmd *cobra.Command, args []string) error {
|
|||
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))
|
||||
}
|
||||
conf, err := client.GetConfirmation(
|
||||
fmt.Sprintf("> Input length: %d", len(inputEntropy)),
|
||||
buf)
|
||||
conf, err := client.GetConfirmation(fmt.Sprintf("> Input length: %d", len(inputEntropy)), buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -58,7 +55,6 @@ func runMnemonicCmd(cmd *cobra.Command, args []string) error {
|
|||
// hash input entropy to get entropy seed
|
||||
hashedEntropy := sha256.Sum256([]byte(inputEntropy))
|
||||
entropySeed = hashedEntropy[:]
|
||||
printStep()
|
||||
} else {
|
||||
// read entropy seed straight from crypto.Rand
|
||||
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(
|
||||
mnemonicKeyCommand(),
|
||||
addKeyCommand(),
|
||||
listKeysCmd,
|
||||
listKeysCmd(),
|
||||
showKeysCmd(),
|
||||
client.LineBreak,
|
||||
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"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tendermint/tendermint/crypto/multisig"
|
||||
|
@ -92,7 +93,12 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) {
|
|||
|
||||
isShowAddr := viper.GetBool(FlagAddress)
|
||||
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 {
|
||||
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)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
info, err := GetKeyInfo(name)
|
||||
if keyerror.IsErrKeyNotFound(err) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
} else if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
keyOutput, err := bechKeyOut(info)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
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/cosmos/cosmos-sdk/client"
|
||||
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
|
@ -29,7 +29,7 @@ func runUpdateCmd(cmd *cobra.Command, args []string) error {
|
|||
name := args[0]
|
||||
|
||||
buf := client.BufferStdin()
|
||||
kb, err := GetKeyBaseWithWritePerm()
|
||||
kb, err := NewKeyBaseFromHomeFlag()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -73,14 +73,14 @@ func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
|
|||
err := decoder.Decode(&m)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
kb, err = GetKeyBaseWithWritePerm()
|
||||
kb, err = NewKeyBaseFromHomeFlag()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -89,15 +89,15 @@ func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
|
|||
err = kb.Update(name, m.OldPassword, getNewpass)
|
||||
if keyerror.IsErrKeyNotFound(err) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
} else if keyerror.IsErrWrongPassword(err) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
} else if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
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"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
|
@ -17,17 +15,18 @@ import (
|
|||
)
|
||||
|
||||
// KeyDBName is the directory under root where we store the keys
|
||||
const KeyDBName = "keys"
|
||||
|
||||
// keybase is used to make GetKeyBase a singleton
|
||||
var keybase keys.Keybase
|
||||
const (
|
||||
KeyDBName = "keys"
|
||||
OutputFormatText = "text"
|
||||
OutputFormatJSON = "json"
|
||||
)
|
||||
|
||||
type bechKeyOutFn func(keyInfo keys.Info) (KeyOutput, error)
|
||||
|
||||
// GetKeyInfo returns key info for a given name. An error is returned if the
|
||||
// keybase cannot be retrieved or getting the info fails.
|
||||
func GetKeyInfo(name string) (keys.Info, error) {
|
||||
keybase, err := GetKeyBase()
|
||||
keybase, err := NewKeyBaseFromHomeFlag()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -73,58 +72,22 @@ func ReadPassphraseFromStdin(name string) (string, error) {
|
|||
return passphrase, nil
|
||||
}
|
||||
|
||||
// TODO make keybase take a database not load from the directory
|
||||
|
||||
// GetKeyBase initializes a read-only KeyBase based on the configuration.
|
||||
func GetKeyBase() (keys.Keybase, error) {
|
||||
// NewKeyBaseFromHomeFlag initializes a Keybase based on the configuration.
|
||||
func NewKeyBaseFromHomeFlag() (keys.Keybase, error) {
|
||||
rootDir := viper.GetString(cli.HomeFlag)
|
||||
return GetKeyBaseFromDir(rootDir)
|
||||
return NewKeyBaseFromDir(rootDir)
|
||||
}
|
||||
|
||||
// GetKeyBaseWithWritePerm initialize a keybase based on the configuration with write permissions.
|
||||
func GetKeyBaseWithWritePerm() (keys.Keybase, error) {
|
||||
rootDir := viper.GetString(cli.HomeFlag)
|
||||
return GetKeyBaseFromDirWithWritePerm(rootDir)
|
||||
// NewKeyBaseFromDir initializes a keybase at a particular dir.
|
||||
func NewKeyBaseFromDir(rootDir string) (keys.Keybase, error) {
|
||||
return getLazyKeyBaseFromDir(rootDir)
|
||||
}
|
||||
|
||||
// GetKeyBaseFromDirWithWritePerm initializes a keybase at a particular dir with write permissions.
|
||||
func GetKeyBaseFromDirWithWritePerm(rootDir string) (keys.Keybase, error) {
|
||||
return getKeyBaseFromDirWithOpts(rootDir, nil)
|
||||
}
|
||||
// NewInMemoryKeyBase returns a storage-less keybase.
|
||||
func NewInMemoryKeyBase() keys.Keybase { return keys.NewInMemory() }
|
||||
|
||||
// GetKeyBaseFromDir initializes a read-only keybase at a particular dir.
|
||||
func GetKeyBaseFromDir(rootDir string) (keys.Keybase, error) {
|
||||
// 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"`
|
||||
func getLazyKeyBaseFromDir(rootDir string) (keys.Keybase, error) {
|
||||
return keys.NewLazyKeybase(KeyDBName, filepath.Join(rootDir, "keys")), nil
|
||||
}
|
||||
|
||||
// create a list of KeyOutput in bech32 format
|
||||
|
@ -198,7 +161,7 @@ func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) {
|
|||
}
|
||||
|
||||
switch viper.Get(cli.OutputFlag) {
|
||||
case "text":
|
||||
case OutputFormatText:
|
||||
fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n")
|
||||
printKeyOutput(ko)
|
||||
case "json":
|
||||
|
@ -217,12 +180,12 @@ func printInfos(infos []keys.Info) {
|
|||
panic(err)
|
||||
}
|
||||
switch viper.Get(cli.OutputFlag) {
|
||||
case "text":
|
||||
case OutputFormatText:
|
||||
fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n")
|
||||
for _, ko := range kos {
|
||||
printKeyOutput(ko)
|
||||
}
|
||||
case "json":
|
||||
case OutputFormatJSON:
|
||||
out, err := MarshalJSON(kos)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -266,12 +229,12 @@ func PostProcessResponse(w http.ResponseWriter, cdc *codec.Codec, response inter
|
|||
}
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
case []byte:
|
||||
output = response.([]byte)
|
||||
}
|
||||
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/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
keybase "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"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
|
||||
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() {
|
||||
err := rs.listener.Close()
|
||||
|
@ -84,10 +67,11 @@ func (rs *RestServer) Start(listenAddr string, sslHosts string,
|
|||
if err != nil {
|
||||
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
|
||||
if insecure {
|
||||
if !secure {
|
||||
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) {
|
||||
rs := NewRestServer(cdc)
|
||||
|
||||
rs.setKeybase(nil)
|
||||
registerRoutesFn(rs)
|
||||
|
||||
// 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.FlagSSLKeyFile),
|
||||
viper.GetInt(client.FlagMaxOpenConnections),
|
||||
viper.GetBool(client.FlagInsecure))
|
||||
viper.GetBool(client.FlagTLS))
|
||||
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
client.RegisterRestServerFlags(cmd)
|
||||
|
||||
return cmd
|
||||
return client.RegisterRestServerFlags(cmd)
|
||||
}
|
||||
|
||||
func (rs *RestServer) registerSwaggerUI() {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -15,20 +15,14 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
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/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"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/tx"
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
crkeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
|
@ -36,16 +30,24 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/tests"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"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"
|
||||
"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/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
stakingrest "github.com/cosmos/cosmos-sdk/x/staking/client/rest"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmcfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
@ -53,16 +55,9 @@ import (
|
|||
"github.com/tendermint/tendermint/p2p"
|
||||
pvm "github.com/tendermint/tendermint/privval"
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
tmrpc "github.com/tendermint/tendermint/rpc/lib/server"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
|
||||
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
|
||||
|
@ -104,30 +99,6 @@ func GetConfig() *tmcfg.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.
|
||||
// It also requires that the key could be created.
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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) {
|
||||
|
@ -167,7 +130,7 @@ func CreateAddrs(t *testing.T, kb crkeys.Keybase, numAddrs int) (addrs []sdk.Acc
|
|||
password := "1234567890"
|
||||
info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1)
|
||||
require.NoError(t, err)
|
||||
addrSeeds = append(addrSeeds, AddrSeed{Address: sdk.AccAddress(info.GetPubKey().Address()), Seed: seed, Name: name, Password: password})
|
||||
addrSeeds = append(addrSeeds, rest.AddrSeed{Address: sdk.AccAddress(info.GetPubKey().Address()), Seed: seed, Name: name, Password: password})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// implement `Interface` in sort package.
|
||||
type AddrSeedSlice []AddrSeed
|
||||
// AddrSeedSlice implements `Interface` in sort package.
|
||||
type AddrSeedSlice []rest.AddrSeed
|
||||
|
||||
func (b AddrSeedSlice) Len() int {
|
||||
return len(b)
|
||||
}
|
||||
|
||||
// Sorts lexographically by Address
|
||||
// Less sorts lexicographically by Address
|
||||
func (b AddrSeedSlice) Less(i, j int) bool {
|
||||
// bytes package already implements Comparable for []byte.
|
||||
switch bytes.Compare(b[i].Address.Bytes(), b[j].Address.Bytes()) {
|
||||
|
@ -206,14 +169,27 @@ func (b AddrSeedSlice) Swap(i, j int) {
|
|||
b[j], b[i] = b[i], b[j]
|
||||
}
|
||||
|
||||
// InitClientHome initialises client home dir.
|
||||
func InitClientHome(t *testing.T, dir string) string {
|
||||
var err error
|
||||
if dir == "" {
|
||||
dir, err = ioutil.TempDir("", "lcd_test")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// TODO: this should be set in NewRestServer
|
||||
// and pass down the CLIContext to achieve
|
||||
// parallelism.
|
||||
viper.Set(cli.HomeFlag, dir)
|
||||
return dir
|
||||
}
|
||||
|
||||
// TODO: Make InitializeTestLCD safe to call in multiple tests at the same time
|
||||
// InitializeTestLCD starts Tendermint and the LCD in process, listening on
|
||||
// their respective sockets where nValidators is the total number of validators
|
||||
// and initAddrs are the accounts to initialize with some steak tokens. It
|
||||
// returns a cleanup function, a set of validator public keys, and a port.
|
||||
func InitializeTestLCD(
|
||||
t *testing.T, nValidators int, initAddrs []sdk.AccAddress,
|
||||
) (cleanup func(), valConsPubKeys []crypto.PubKey, valOperAddrs []sdk.ValAddress, port string) {
|
||||
func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress, minting bool) (
|
||||
cleanup func(), valConsPubKeys []crypto.PubKey, valOperAddrs []sdk.ValAddress, port string) {
|
||||
|
||||
if nValidators < 1 {
|
||||
panic("InitializeTestLCD must use at least one validator")
|
||||
|
@ -248,17 +224,21 @@ func InitializeTestLCD(
|
|||
operPrivKey := secp256k1.GenPrivKey()
|
||||
operAddr := operPrivKey.PubKey().Address()
|
||||
pubKey := privVal.GetPubKey()
|
||||
delegation := 100
|
||||
|
||||
power := int64(100)
|
||||
if i > 0 {
|
||||
pubKey = ed25519.GenPrivKey().PubKey()
|
||||
delegation = 1
|
||||
power = 1
|
||||
}
|
||||
startTokens := staking.TokensFromTendermintPower(power)
|
||||
|
||||
msg := staking.NewMsgCreateValidator(
|
||||
sdk.ValAddress(operAddr),
|
||||
pubKey,
|
||||
sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(int64(delegation))),
|
||||
staking.Description{Moniker: fmt.Sprintf("validator-%d", i+1)},
|
||||
sdk.NewCoin(staking.DefaultBondDenom, startTokens),
|
||||
staking.NewDescription(fmt.Sprintf("validator-%d", i+1), "", "", ""),
|
||||
staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
|
||||
sdk.OneInt(),
|
||||
)
|
||||
stdSignMsg := txbuilder.StdSignMsg{
|
||||
ChainID: genDoc.ChainID,
|
||||
|
@ -269,12 +249,14 @@ func InitializeTestLCD(
|
|||
tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{{Signature: sig, PubKey: operPrivKey.PubKey()}}, "")
|
||||
txBytes, err := cdc.MarshalJSON(tx)
|
||||
require.Nil(t, err)
|
||||
|
||||
genTxs = append(genTxs, txBytes)
|
||||
valConsPubKeys = append(valConsPubKeys, pubKey)
|
||||
valOperAddrs = append(valOperAddrs, sdk.ValAddress(operAddr))
|
||||
|
||||
accAuth := auth.NewBaseAccountWithAddress(sdk.AccAddress(operAddr))
|
||||
accAuth.Coins = sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 150)}
|
||||
accTokens := staking.TokensFromTendermintPower(150)
|
||||
accAuth.Coins = sdk.Coins{sdk.NewCoin(staking.DefaultBondDenom, accTokens)}
|
||||
accs = append(accs, gapp.NewGenesisAccount(&accAuth))
|
||||
}
|
||||
|
||||
|
@ -288,10 +270,34 @@ func InitializeTestLCD(
|
|||
// add some tokens to init accounts
|
||||
for _, addr := range initAddrs {
|
||||
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)
|
||||
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)
|
||||
|
@ -306,9 +312,6 @@ func InitializeTestLCD(
|
|||
viper.Set(client.FlagChainID, genDoc.ChainID)
|
||||
// TODO Set to false once the upstream Tendermint proof verification issue is fixed.
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
|
@ -339,6 +342,7 @@ func startTM(
|
|||
tmcfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc,
|
||||
privVal tmtypes.PrivValidator, app abci.Application,
|
||||
) (*nm.Node, error) {
|
||||
|
||||
genDocProvider := func() (*tmtypes.GenesisDoc, error) { return genDoc, nil }
|
||||
dbProvider := func(*nm.DBContext) (dbm.DB, error) { return dbm.NewMemDB(), nil }
|
||||
nodeKey, err := p2p.LoadOrGenNodeKey(tmcfg.NodeKeyFile())
|
||||
|
@ -373,7 +377,6 @@ func startTM(
|
|||
// startLCD starts the LCD.
|
||||
func startLCD(logger log.Logger, listenAddr string, cdc *codec.Codec, t *testing.T) (net.Listener, error) {
|
||||
rs := NewRestServer(cdc)
|
||||
rs.setKeybase(GetTestKeyBase(t))
|
||||
registerRoutes(rs)
|
||||
listener, err := tmrpc.Listen(listenAddr, tmrpc.Config{})
|
||||
if err != nil {
|
||||
|
@ -388,11 +391,12 @@ func registerRoutes(rs *RestServer) {
|
|||
keys.RegisterRoutes(rs.Mux, rs.CliCtx.Indent)
|
||||
rpc.RegisterRoutes(rs.CliCtx, rs.Mux)
|
||||
tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
|
||||
authRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, auth.StoreKey)
|
||||
bankRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
||||
stakingRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
||||
slashingRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
||||
govRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
|
||||
authrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, auth.StoreKey)
|
||||
bankrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
||||
distrrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, distr.StoreKey)
|
||||
stakingrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
||||
slashingrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
||||
govrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
|
||||
}
|
||||
|
||||
// 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
|
||||
func getTransaction(t *testing.T, port string, hash string) tx.Info {
|
||||
var tx tx.Info
|
||||
func getTransaction(t *testing.T, port string, hash string) sdk.TxResponse {
|
||||
var tx sdk.TxResponse
|
||||
res, body := Request(t, port, "GET", fmt.Sprintf("/txs/%s", hash), nil)
|
||||
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
|
||||
|
||||
// GET /txs search transactions
|
||||
func getTransactions(t *testing.T, port string, tags ...string) []tx.Info {
|
||||
var txs []tx.Info
|
||||
func getTransactions(t *testing.T, port string, tags ...string) []sdk.TxResponse {
|
||||
var txs []sdk.TxResponse
|
||||
if len(tags) == 0 {
|
||||
return txs
|
||||
}
|
||||
|
@ -539,10 +543,11 @@ func getKeys(t *testing.T, port string) []keys.KeyOutput {
|
|||
}
|
||||
|
||||
// POST /keys Create a new account locally
|
||||
func doKeysPost(t *testing.T, port, name, password, seed string) keys.KeyOutput {
|
||||
pk := postKeys{name, password, seed}
|
||||
func doKeysPost(t *testing.T, port, name, password, mnemonic string, account int, index int) keys.KeyOutput {
|
||||
pk := keys.AddNewKey{name, password, mnemonic, account, index}
|
||||
req, err := cdc.MarshalJSON(pk)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, body := Request(t, port, "POST", "/keys", req)
|
||||
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
|
@ -552,12 +557,6 @@ func doKeysPost(t *testing.T, port, name, password, seed string) keys.KeyOutput
|
|||
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
|
||||
func getKeysSeed(t *testing.T, port string) string {
|
||||
res, body := Request(t, port, "GET", "/keys/seed", nil)
|
||||
|
@ -569,14 +568,17 @@ func getKeysSeed(t *testing.T, port string) string {
|
|||
return body
|
||||
}
|
||||
|
||||
// POST /keys/{name}/recover Recover a account from a seed
|
||||
func doRecoverKey(t *testing.T, port, recoverName, recoverPassword, seed string) {
|
||||
jsonStr := []byte(fmt.Sprintf(`{"password":"%s", "seed":"%s"}`, recoverPassword, seed))
|
||||
res, body := Request(t, port, "POST", fmt.Sprintf("/keys/%s/recover", recoverName), jsonStr)
|
||||
// POST /keys/{name}/recove Recover a account from a seed
|
||||
func doRecoverKey(t *testing.T, port, recoverName, recoverPassword, mnemonic string, account uint32, index uint32) {
|
||||
pk := keys.RecoverKey{recoverPassword, mnemonic, int(account), int(index)}
|
||||
req, err := cdc.MarshalJSON(pk)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, body := Request(t, port, "POST", fmt.Sprintf("/keys/%s/recover", recoverName), req)
|
||||
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
var resp keys.KeyOutput
|
||||
err := codec.Cdc.UnmarshalJSON([]byte(body), &resp)
|
||||
err = codec.Cdc.UnmarshalJSON([]byte(body), &resp)
|
||||
require.Nil(t, err, body)
|
||||
|
||||
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
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
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)
|
||||
}
|
||||
|
||||
type updateKeyReq struct {
|
||||
OldPassword string `json:"old_password"`
|
||||
NewPassword string `json:"new_password"`
|
||||
}
|
||||
|
||||
// DELETE /keys/{name} Remove an account
|
||||
func deleteKey(t *testing.T, port, name, password string) {
|
||||
dk := deleteKeyReq{password}
|
||||
dk := keys.DeleteKeyReq{password}
|
||||
req, err := cdc.MarshalJSON(dk)
|
||||
require.NoError(t, err)
|
||||
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)
|
||||
}
|
||||
|
||||
type deleteKeyReq struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// GET /auth/accounts/{address} Get the account information on blockchain
|
||||
func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account {
|
||||
res, body := Request(t, port, "GET", fmt.Sprintf("/auth/accounts/%s", addr.String()), nil)
|
||||
|
@ -646,7 +639,7 @@ func doSign(t *testing.T, port, name, password, chainID string, accnum, sequence
|
|||
var signedMsg auth.StdTx
|
||||
payload := authrest.SignBody{
|
||||
Tx: msg,
|
||||
BaseReq: utils.NewBaseReq(
|
||||
BaseReq: rest.NewBaseReq(
|
||||
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
|
||||
func doBroadcast(t *testing.T, port string, msg auth.StdTx) ctypes.ResultBroadcastTxCommit {
|
||||
tx := broadcastReq{Tx: msg, Return: "block"}
|
||||
func doBroadcast(t *testing.T, port string, msg auth.StdTx) sdk.TxResponse {
|
||||
tx := rest.BroadcastReq{Tx: msg, Return: "block"}
|
||||
req, err := cdc.MarshalJSON(tx)
|
||||
require.Nil(t, err)
|
||||
res, body := Request(t, port, "POST", "/tx/broadcast", req)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
var resultTx ctypes.ResultBroadcastTxCommit
|
||||
var resultTx sdk.TxResponse
|
||||
require.Nil(t, cdc.UnmarshalJSON([]byte(body), &resultTx))
|
||||
return resultTx
|
||||
}
|
||||
|
||||
type broadcastReq struct {
|
||||
Tx auth.StdTx `json:"tx"`
|
||||
Return string `json:"return"`
|
||||
}
|
||||
|
||||
// GET /bank/balances/{address} Get the account balances
|
||||
|
||||
// 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)
|
||||
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
|
||||
}
|
||||
|
||||
func doTransferWithGas(t *testing.T, port, seed, name, memo, password string, addr sdk.AccAddress, gas string,
|
||||
gasAdjustment float64, simulate, generateOnly bool, fees sdk.Coins) (
|
||||
res *http.Response, body string, receiveAddr sdk.AccAddress) {
|
||||
func doTransferWithGas(
|
||||
t *testing.T, port, seed, from, memo, password string, addr sdk.AccAddress,
|
||||
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", 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)
|
||||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
|
||||
baseReq := utils.NewBaseReq(
|
||||
name, password, memo, chainID, gas,
|
||||
if generateOnly {
|
||||
// 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,
|
||||
generateOnly, simulate,
|
||||
)
|
||||
|
||||
sr := sendReq{
|
||||
Amount: sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 1)},
|
||||
sr := rest.SendReq{
|
||||
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,
|
||||
}
|
||||
|
||||
|
@ -722,8 +752,8 @@ func doTransferWithGas(t *testing.T, port, seed, name, memo, password string, ad
|
|||
}
|
||||
|
||||
type sendReq struct {
|
||||
Amount sdk.Coins `json:"amount"`
|
||||
BaseReq utils.BaseReq `json:"base_req"`
|
||||
Amount sdk.Coins `json:"amount"`
|
||||
BaseReq rest.BaseReq `json:"base_req"`
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
@ -732,24 +762,25 @@ type sendReq struct {
|
|||
|
||||
// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation
|
||||
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)
|
||||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
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{
|
||||
BaseReq: baseReq,
|
||||
DelegatorAddr: delAddr,
|
||||
ValidatorAddr: valAddr,
|
||||
Delegation: sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, amount),
|
||||
Delegation: sdk.NewCoin(staking.DefaultBondDenom, amount),
|
||||
}
|
||||
req, err := cdc.MarshalJSON(msg)
|
||||
require.NoError(t, err)
|
||||
res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/delegations", delAddr.String()), req)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
|
||||
var result ctypes.ResultBroadcastTxCommit
|
||||
var result sdk.TxResponse
|
||||
err = cdc.UnmarshalJSON([]byte(body), &result)
|
||||
require.Nil(t, err)
|
||||
|
||||
|
@ -757,7 +788,7 @@ func doDelegate(t *testing.T, port, name, password string,
|
|||
}
|
||||
|
||||
type msgDelegationsInput struct {
|
||||
BaseReq utils.BaseReq `json:"base_req"`
|
||||
BaseReq rest.BaseReq `json:"base_req"`
|
||||
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32
|
||||
ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32
|
||||
Delegation sdk.Coin `json:"delegation"`
|
||||
|
@ -765,18 +796,18 @@ type msgDelegationsInput struct {
|
|||
|
||||
// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation
|
||||
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)
|
||||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
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{
|
||||
BaseReq: baseReq,
|
||||
DelegatorAddr: delAddr,
|
||||
ValidatorAddr: valAddr,
|
||||
SharesAmount: sdk.NewDec(amount),
|
||||
SharesAmount: sdk.NewDecFromInt(amount),
|
||||
}
|
||||
req, err := cdc.MarshalJSON(msg)
|
||||
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)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
|
||||
var result ctypes.ResultBroadcastTxCommit
|
||||
var result sdk.TxResponse
|
||||
err = cdc.UnmarshalJSON([]byte(body), &result)
|
||||
require.Nil(t, err)
|
||||
|
||||
|
@ -792,7 +823,7 @@ func doUndelegate(t *testing.T, port, name, password string,
|
|||
}
|
||||
|
||||
type msgUndelegateInput struct {
|
||||
BaseReq utils.BaseReq `json:"base_req"`
|
||||
BaseReq rest.BaseReq `json:"base_req"`
|
||||
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32
|
||||
ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32
|
||||
SharesAmount sdk.Dec `json:"shares"`
|
||||
|
@ -800,21 +831,22 @@ type msgUndelegateInput struct {
|
|||
|
||||
// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation
|
||||
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)
|
||||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
|
||||
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,
|
||||
DelegatorAddr: delAddr,
|
||||
ValidatorSrcAddr: valSrcAddr,
|
||||
ValidatorDstAddr: valDstAddr,
|
||||
SharesAmount: sdk.NewDec(amount),
|
||||
SharesAmount: sdk.NewDecFromInt(amount),
|
||||
}
|
||||
req, err := cdc.MarshalJSON(msg)
|
||||
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)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
|
||||
var result ctypes.ResultBroadcastTxCommit
|
||||
var result sdk.TxResponse
|
||||
err = cdc.UnmarshalJSON([]byte(body), &result)
|
||||
require.Nil(t, err)
|
||||
|
||||
|
@ -830,7 +862,7 @@ func doBeginRedelegation(t *testing.T, port, name, password string,
|
|||
}
|
||||
|
||||
type msgBeginRedelegateInput struct {
|
||||
BaseReq utils.BaseReq `json:"base_req"`
|
||||
BaseReq rest.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
|
||||
|
@ -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
|
||||
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 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)
|
||||
|
||||
var txs []tx.Info
|
||||
var txs []sdk.TxResponse
|
||||
|
||||
err := cdc.UnmarshalJSON([]byte(body), &txs)
|
||||
require.Nil(t, err)
|
||||
|
@ -1033,19 +1065,21 @@ func getStakingParams(t *testing.T, port string) staking.Params {
|
|||
// ICS 22 - Gov
|
||||
// ----------------------------------------------------------------------
|
||||
// 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)
|
||||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
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",
|
||||
Description: "test",
|
||||
ProposalType: "Text",
|
||||
Proposer: proposerAddr,
|
||||
InitialDeposit: sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))},
|
||||
InitialDeposit: sdk.Coins{sdk.NewCoin(staking.DefaultBondDenom, amount)},
|
||||
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)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
|
||||
var results ctypes.ResultBroadcastTxCommit
|
||||
var results sdk.TxResponse
|
||||
err = cdc.UnmarshalJSON([]byte(body), &results)
|
||||
require.Nil(t, err)
|
||||
|
||||
|
@ -1064,7 +1098,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA
|
|||
}
|
||||
|
||||
type postProposalReq struct {
|
||||
BaseReq utils.BaseReq `json:"base_req"`
|
||||
BaseReq rest.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}
|
||||
|
@ -1128,17 +1162,18 @@ func getProposalsFilterStatus(t *testing.T, port string, status gov.ProposalStat
|
|||
}
|
||||
|
||||
// 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)
|
||||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
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,
|
||||
Amount: sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))},
|
||||
Amount: sdk.Coins{sdk.NewCoin(staking.DefaultBondDenom, amount)},
|
||||
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)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
|
||||
var results ctypes.ResultBroadcastTxCommit
|
||||
var results sdk.TxResponse
|
||||
err = cdc.UnmarshalJSON([]byte(body), &results)
|
||||
require.Nil(t, err)
|
||||
|
||||
|
@ -1156,7 +1191,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk
|
|||
}
|
||||
|
||||
type depositReq struct {
|
||||
BaseReq utils.BaseReq `json:"base_req"`
|
||||
BaseReq rest.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
|
||||
}
|
||||
|
@ -1182,15 +1217,15 @@ func getTally(t *testing.T, port string, proposalID uint64) gov.TallyResult {
|
|||
}
|
||||
|
||||
// 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
|
||||
acc := getAccount(t, port, proposerAddr)
|
||||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
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,
|
||||
Option: option,
|
||||
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)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
|
||||
var results ctypes.ResultBroadcastTxCommit
|
||||
var results sdk.TxResponse
|
||||
err = cdc.UnmarshalJSON([]byte(body), &results)
|
||||
require.Nil(t, err)
|
||||
|
||||
|
@ -1210,7 +1245,7 @@ func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.Ac
|
|||
}
|
||||
|
||||
type voteReq struct {
|
||||
BaseReq utils.BaseReq `json:"base_req"`
|
||||
BaseReq rest.BaseReq `json:"base_req"`
|
||||
Voter sdk.AccAddress `json:"voter"` // address of 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
|
||||
// POST /slashing/validators/{validatorAddr}/unjail Unjail a jailed validator
|
||||
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)
|
||||
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,
|
||||
}
|
||||
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)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
|
||||
var results []ctypes.ResultBroadcastTxCommit
|
||||
var results sdk.TxResponse
|
||||
err = cdc.UnmarshalJSON([]byte(body), &results)
|
||||
require.Nil(t, err)
|
||||
|
||||
return results[0]
|
||||
return results
|
||||
}
|
||||
|
||||
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"
|
||||
"strconv"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
|
||||
|
@ -12,8 +14,6 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
tmliteProxy "github.com/tendermint/tendermint/lite/proxy"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
)
|
||||
|
||||
//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()))
|
||||
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()))
|
||||
return
|
||||
}
|
||||
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||
rest.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,11 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
)
|
||||
|
||||
|
@ -26,7 +27,7 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
|||
// cli version REST handler endpoint
|
||||
func CLIVersionRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
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
|
||||
|
@ -34,7 +35,7 @@ func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc {
|
|||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
version, err := cliCtx.Query("/app/version", nil)
|
||||
if err != nil {
|
||||
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
@ -12,7 +14,6 @@ import (
|
|||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
)
|
||||
|
||||
// StatusCommand returns the status of the network
|
||||
|
@ -77,7 +78,7 @@ func NodeInfoRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
|||
}
|
||||
|
||||
nodeInfo := status.NodeInfo
|
||||
utils.PostProcessResponse(w, cdc, nodeInfo, cliCtx.Indent)
|
||||
rest.PostProcessResponse(w, cdc, nodeInfo, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,11 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -14,33 +19,59 @@ import (
|
|||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// TODO these next two functions feel kinda hacky based on their placement
|
||||
|
||||
//ValidatorCommand returns the validator set for a given height
|
||||
func ValidatorCommand() *cobra.Command {
|
||||
func ValidatorCommand(cdc *codec.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "tendermint-validator-set [height]",
|
||||
Short: "Get the full tendermint validator set at given height",
|
||||
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")
|
||||
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)")
|
||||
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
|
||||
}
|
||||
|
||||
// Validator output in bech32 format
|
||||
type ValidatorOutput struct {
|
||||
Address sdk.ValAddress `json:"address"` // in bech32
|
||||
PubKey string `json:"pub_key"` // in bech32
|
||||
ProposerPriority int64 `json:"proposer_priority"`
|
||||
VotingPower int64 `json:"voting_power"`
|
||||
Address sdk.ConsAddress `json:"address"`
|
||||
PubKey string `json:"pub_key"`
|
||||
ProposerPriority int64 `json:"proposer_priority"`
|
||||
VotingPower int64 `json:"voting_power"`
|
||||
}
|
||||
|
||||
// Validators at a certain height output in bech32 format
|
||||
|
@ -49,6 +80,27 @@ type ResultValidatorsOutput struct {
|
|||
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) {
|
||||
bechValPubkey, err := sdk.Bech32ifyConsPub(validator.PubKey)
|
||||
if err != nil {
|
||||
|
@ -56,33 +108,33 @@ func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error
|
|||
}
|
||||
|
||||
return ValidatorOutput{
|
||||
Address: sdk.ValAddress(validator.Address),
|
||||
Address: sdk.ConsAddress(validator.Address),
|
||||
PubKey: bechValPubkey,
|
||||
ProposerPriority: validator.ProposerPriority,
|
||||
VotingPower: validator.VotingPower,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getValidators(cliCtx context.CLIContext, height *int64) ([]byte, error) {
|
||||
func getValidators(cliCtx context.CLIContext, height *int64) (ResultValidatorsOutput, error) {
|
||||
// get the node
|
||||
node, err := cliCtx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return ResultValidatorsOutput{}, err
|
||||
}
|
||||
|
||||
validatorsRes, err := node.Validators(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return ResultValidatorsOutput{}, err
|
||||
}
|
||||
|
||||
if !cliCtx.TrustNode {
|
||||
check, err := cliCtx.Verify(validatorsRes.BlockHeight)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return ResultValidatorsOutput{}, err
|
||||
}
|
||||
|
||||
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++ {
|
||||
outputValidatorsRes.Validators[i], err = bech32ValidatorOutput(validatorsRes.Validators[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return ResultValidatorsOutput{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if cliCtx.Indent {
|
||||
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
|
||||
return outputValidatorsRes, nil
|
||||
}
|
||||
|
||||
// REST
|
||||
|
@ -157,7 +180,7 @@ func ValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
|||
w.Write([]byte(err.Error()))
|
||||
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()))
|
||||
return
|
||||
}
|
||||
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||
rest.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,11 @@ package tx
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
|
@ -32,12 +33,12 @@ func BroadcastTxRequest(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle
|
|||
var m BroadcastBody
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
err = cdc.UnmarshalJSON(body, &m)
|
||||
if err != nil {
|
||||
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
var res interface{}
|
||||
|
@ -49,13 +50,13 @@ func BroadcastTxRequest(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle
|
|||
case flagAsync:
|
||||
res, err = cliCtx.BroadcastTxAsync(m.TxBytes)
|
||||
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
|
||||
}
|
||||
if err != nil {
|
||||
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
utils.PostProcessResponse(w, cdc, res, cliCtx.Indent)
|
||||
rest.PostProcessResponse(w, cdc, res, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,18 +5,15 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/cobra"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"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/client/rest"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
|
@ -99,26 +96,13 @@ func ValidateTxResult(cliCtx context.CLIContext, res *ctypes.ResultTx) error {
|
|||
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)
|
||||
if err != nil {
|
||||
return Info{}, err
|
||||
return sdk.TxResponse{}, err
|
||||
}
|
||||
|
||||
return Info{
|
||||
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"`
|
||||
return sdk.NewResponseResultTx(res, tx), nil
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||
rest.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,10 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||
|
||||
"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"
|
||||
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
|
||||
// Tendermint RPC. It returns a slice of Info object containing txs and metadata.
|
||||
// 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 {
|
||||
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
|
||||
func FormatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]Info, error) {
|
||||
func FormatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]sdk.TxResponse, error) {
|
||||
var err error
|
||||
out := make([]Info, len(res))
|
||||
out := make([]sdk.TxResponse, len(res))
|
||||
for i := range res {
|
||||
out[i], err = formatTxResult(cdc, res[i])
|
||||
if err != nil {
|
||||
|
@ -172,31 +173,31 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.
|
|||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var tags []string
|
||||
var page, limit int
|
||||
var txs []Info
|
||||
var txs []sdk.TxResponse
|
||||
err := r.ParseForm()
|
||||
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
|
||||
}
|
||||
if len(r.Form) == 0 {
|
||||
utils.PostProcessResponse(w, cdc, txs, cliCtx.Indent)
|
||||
rest.PostProcessResponse(w, cdc, txs, cliCtx.Indent)
|
||||
return
|
||||
}
|
||||
|
||||
tags, page, limit, err = parseHTTPArgs(r)
|
||||
|
||||
if err != nil {
|
||||
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
txs, err = SearchTxs(cliCtx, cdc, tags, page, limit)
|
||||
if err != nil {
|
||||
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
|
||||
"github.com/tendermint/go-amino"
|
||||
|
@ -18,55 +18,73 @@ import (
|
|||
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
|
||||
// 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 CompleteAndBroadcastTxREST.
|
||||
func CompleteAndBroadcastTxCli(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error {
|
||||
txBldr, err := prepareTxBuilder(txBldr, cliCtx)
|
||||
func CompleteAndBroadcastTxCLI(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error {
|
||||
txBldr, err := PrepareTxBuilder(txBldr, cliCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name, err := cliCtx.GetFromName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fromName := cliCtx.GetFromName()
|
||||
|
||||
if txBldr.GetSimulateAndExecute() || cliCtx.Simulate {
|
||||
txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs)
|
||||
if txBldr.SimulateAndExecute() || cliCtx.Simulate {
|
||||
txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs)
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
|
||||
passphrase, err := keys.GetPassphrase(name)
|
||||
passphrase, err := keys.GetPassphrase(fromName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// build and sign the transaction
|
||||
txBytes, err := txBldr.BuildAndSign(name, passphrase, msgs)
|
||||
txBytes, err := txBldr.BuildAndSign(fromName, passphrase, msgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// broadcast to a Tendermint node
|
||||
_, err = cliCtx.BroadcastTx(txBytes)
|
||||
res, err := cliCtx.BroadcastTx(txBytes)
|
||||
cliCtx.PrintOutput(res)
|
||||
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.
|
||||
func EnrichCtxWithGas(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, msgs []sdk.Msg) (authtxb.TxBuilder, error) {
|
||||
_, adjusted, err := simulateMsgs(txBldr, cliCtx, name, msgs)
|
||||
func EnrichWithGas(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (authtxb.TxBuilder, error) {
|
||||
_, adjusted, err := simulateMsgs(txBldr, cliCtx, msgs)
|
||||
if err != nil {
|
||||
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.
|
||||
// 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
|
||||
if offline {
|
||||
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)
|
||||
if err == nil {
|
||||
fmt.Fprintf(w, "%s\n", json)
|
||||
fmt.Fprintf(cliCtx.Output, "%s\n", json)
|
||||
}
|
||||
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) {
|
||||
var signedStdTx auth.StdTx
|
||||
|
||||
keybase, err := keys.GetKeyBase()
|
||||
if err != nil {
|
||||
return signedStdTx, err
|
||||
}
|
||||
keybase := txBldr.Keybase()
|
||||
|
||||
info, err := keybase.Get(name)
|
||||
if err != nil {
|
||||
|
@ -129,8 +144,7 @@ func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string,
|
|||
|
||||
// check whether the address is a signer
|
||||
if !isTxSigner(sdk.AccAddress(addr), stdTx.GetSigners()) {
|
||||
return signedStdTx, fmt.Errorf(
|
||||
"The generated transaction's intended signer does not match the given signer: %q", name)
|
||||
return signedStdTx, fmt.Errorf("%s: %s", client.ErrInvalidSigner, name)
|
||||
}
|
||||
|
||||
if !offline {
|
||||
|
@ -158,8 +172,7 @@ func SignStdTxWithSignerAddress(txBldr authtxb.TxBuilder, cliCtx context.CLICont
|
|||
|
||||
// check whether the address is a signer
|
||||
if !isTxSigner(addr, stdTx.GetSigners()) {
|
||||
return signedStdTx, fmt.Errorf(
|
||||
"The generated transaction's intended signer does not match the given signer: %q", name)
|
||||
return signedStdTx, fmt.Errorf("%s: %s", client.ErrInvalidSigner, name)
|
||||
}
|
||||
|
||||
if !offline {
|
||||
|
@ -179,7 +192,7 @@ func SignStdTxWithSignerAddress(txBldr authtxb.TxBuilder, cliCtx context.CLICont
|
|||
|
||||
func populateAccountFromState(txBldr authtxb.TxBuilder, cliCtx context.CLIContext,
|
||||
addr sdk.AccAddress) (authtxb.TxBuilder, error) {
|
||||
if txBldr.GetAccountNumber() == 0 {
|
||||
if txBldr.AccountNumber() == 0 {
|
||||
accNum, err := cliCtx.GetAccountNumber(addr)
|
||||
if err != nil {
|
||||
return txBldr, err
|
||||
|
@ -187,7 +200,7 @@ func populateAccountFromState(txBldr authtxb.TxBuilder, cliCtx context.CLIContex
|
|||
txBldr = txBldr.WithAccountNumber(accNum)
|
||||
}
|
||||
|
||||
if txBldr.GetSequence() == 0 {
|
||||
if txBldr.Sequence() == 0 {
|
||||
accSeq, err := cliCtx.GetAccountSequence(addr)
|
||||
if err != nil {
|
||||
return txBldr, err
|
||||
|
@ -210,12 +223,12 @@ func GetTxEncoder(cdc *codec.Codec) (encoder sdk.TxEncoder) {
|
|||
|
||||
// nolint
|
||||
// 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) {
|
||||
txBytes, err := txBldr.BuildWithPubKey(name, msgs)
|
||||
func simulateMsgs(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (estimated, adjusted uint64, err error) {
|
||||
txBytes, err := txBldr.BuildTxForSim(msgs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, txBldr.GetGasAdjustment())
|
||||
estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, txBldr.GasAdjustment())
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -231,19 +244,17 @@ func parseQueryResponse(cdc *amino.Codec, rawRes []byte) (uint64, error) {
|
|||
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 {
|
||||
return txBldr, err
|
||||
}
|
||||
|
||||
from, err := cliCtx.GetFromAddress()
|
||||
if err != nil {
|
||||
return txBldr, err
|
||||
}
|
||||
from := cliCtx.GetFromAddress()
|
||||
|
||||
// TODO: (ref #1903) Allow for user supplied account number without
|
||||
// automatically doing a manual lookup.
|
||||
if txBldr.GetAccountNumber() == 0 {
|
||||
if txBldr.AccountNumber() == 0 {
|
||||
accNum, err := cliCtx.GetAccountNumber(from)
|
||||
if err != nil {
|
||||
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
|
||||
// automatically doing a manual lookup.
|
||||
if txBldr.GetSequence() == 0 {
|
||||
if txBldr.Sequence() == 0 {
|
||||
accSeq, err := cliCtx.GetAccountSequence(from)
|
||||
if err != nil {
|
||||
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
|
||||
// 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) {
|
||||
txBldr, err = prepareTxBuilder(txBldr, cliCtx)
|
||||
txBldr, err = PrepareTxBuilder(txBldr, cliCtx)
|
||||
if err != nil {
|
||||
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) {
|
||||
if txBldr.GetSimulateAndExecute() {
|
||||
var name string
|
||||
name, err = cliCtx.GetFromName()
|
||||
if txBldr.SimulateAndExecute() {
|
||||
txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.GetGas())
|
||||
fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.Gas())
|
||||
}
|
||||
stdSignMsg, err := txBldr.Build(msgs)
|
||||
|
||||
stdSignMsg, err := txBldr.BuildSignMsg(msgs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
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 false
|
||||
}
|
||||
|
|
|
@ -102,7 +102,11 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b
|
|||
)
|
||||
|
||||
// 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.cdc,
|
||||
app.keyFeeCollection,
|
||||
|
@ -160,11 +164,12 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b
|
|||
|
||||
// initialize BaseApp
|
||||
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.SetBeginBlocker(app.BeginBlocker)
|
||||
app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper))
|
||||
app.MountStoresTransient(app.tkeyParams, app.tkeyStaking, app.tkeyDistr)
|
||||
app.SetEndBlocker(app.EndBlocker)
|
||||
|
||||
if loadLatest {
|
||||
|
@ -228,10 +233,7 @@ func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.R
|
|||
|
||||
// initialize store from a genesis state
|
||||
func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisState) []abci.ValidatorUpdate {
|
||||
// sort by account number to maintain consistency
|
||||
sort.Slice(genesisState.Accounts, func(i, j int) bool {
|
||||
return genesisState.Accounts[i].AccountNumber < genesisState.Accounts[j].AccountNumber
|
||||
})
|
||||
genesisState.Sanitize()
|
||||
|
||||
// load the accounts
|
||||
for _, gacc := range genesisState.Accounts {
|
||||
|
@ -251,13 +253,13 @@ func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisSt
|
|||
|
||||
// initialize module-specific stores
|
||||
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)
|
||||
gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData)
|
||||
mint.InitGenesis(ctx, app.mintKeeper, genesisState.MintData)
|
||||
|
||||
// validate genesis state
|
||||
err = GaiaValidateGenesisState(genesisState)
|
||||
if err != nil {
|
||||
if err := GaiaValidateGenesisState(genesisState); err != nil {
|
||||
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.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) {
|
||||
h.dh.AfterValidatorBeginUnbonding(ctx, consAddr, valAddr)
|
||||
h.sh.AfterValidatorBeginUnbonding(ctx, consAddr, valAddr)
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
@ -28,6 +30,7 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error {
|
|||
genesisState := NewGenesisState(
|
||||
genaccs,
|
||||
auth.DefaultGenesisState(),
|
||||
bank.DefaultGenesisState(),
|
||||
staking.DefaultGenesisState(),
|
||||
mint.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
|
||||
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")
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ func ExampleTxSendSize() {
|
|||
priv2 := secp256k1.GenPrivKeySecp256k1([]byte{1})
|
||||
addr2 := sdk.AccAddress(priv2.PubKey().Address())
|
||||
coins := sdk.Coins{sdk.NewCoin("denom", sdk.NewInt(10))}
|
||||
msg1 := bank.MsgSend{
|
||||
msg1 := bank.MsgMultiSend{
|
||||
Inputs: []bank.Input{bank.NewInput(addr1, coins)},
|
||||
Outputs: []bank.Output{bank.NewOutput(addr2, coins)},
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package app
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
|
@ -9,6 +10,7 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
distr "github.com/cosmos/cosmos-sdk/x/distribution"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
"github.com/cosmos/cosmos-sdk/x/mint"
|
||||
|
@ -17,14 +19,14 @@ import (
|
|||
)
|
||||
|
||||
// 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) {
|
||||
|
||||
// as if they could withdraw from the start of the next block
|
||||
ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()})
|
||||
|
||||
if forZeroHeight {
|
||||
app.prepForZeroHeightGenesis(ctx)
|
||||
app.prepForZeroHeightGenesis(ctx, jailWhiteList)
|
||||
}
|
||||
|
||||
// iterate to get the accounts
|
||||
|
@ -39,6 +41,7 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (
|
|||
genState := NewGenesisState(
|
||||
accounts,
|
||||
auth.ExportGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper),
|
||||
bank.ExportGenesis(ctx, app.bankKeeper),
|
||||
staking.ExportGenesis(ctx, app.stakingKeeper),
|
||||
mint.ExportGenesis(ctx, app.mintKeeper),
|
||||
distr.ExportGenesis(ctx, app.distrKeeper),
|
||||
|
@ -54,7 +57,23 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (
|
|||
}
|
||||
|
||||
// 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. */
|
||||
app.assertRuntimeInvariantsOnContext(ctx)
|
||||
|
@ -131,9 +150,11 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) {
|
|||
panic("expected validator, not found")
|
||||
}
|
||||
|
||||
validator.BondHeight = 0
|
||||
validator.UnbondingHeight = 0
|
||||
valConsAddrs = append(valConsAddrs, validator.ConsAddress())
|
||||
if applyWhiteList && !whiteListMap[addr.String()] {
|
||||
validator.Jailed = true
|
||||
}
|
||||
|
||||
app.stakingKeeper.SetValidator(ctx, validator)
|
||||
counter++
|
||||
|
@ -141,6 +162,8 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) {
|
|||
|
||||
iter.Close()
|
||||
|
||||
_ = app.stakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx)
|
||||
|
||||
/* Handle slashing state. */
|
||||
|
||||
// reset start height on signing infos
|
||||
|
|
|
@ -9,6 +9,9 @@ import (
|
|||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
|
||||
|
@ -20,20 +23,19 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/x/mint"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
var (
|
||||
// bonded tokens given to genesis validators/accounts
|
||||
freeFermionVal = int64(100)
|
||||
freeFermionsAcc = sdk.NewInt(150)
|
||||
bondDenom = stakingTypes.DefaultBondDenom
|
||||
freeFermionsAcc = staking.TokensFromTendermintPower(150)
|
||||
bondDenom = staking.DefaultBondDenom
|
||||
)
|
||||
|
||||
// State to Unmarshal
|
||||
type GenesisState struct {
|
||||
Accounts []GenesisAccount `json:"accounts"`
|
||||
AuthData auth.GenesisState `json:"auth"`
|
||||
BankData bank.GenesisState `json:"bank"`
|
||||
StakingData staking.GenesisState `json:"staking"`
|
||||
MintData mint.GenesisState `json:"mint"`
|
||||
DistrData distr.GenesisState `json:"distr"`
|
||||
|
@ -43,6 +45,7 @@ type GenesisState struct {
|
|||
}
|
||||
|
||||
func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState,
|
||||
bankData bank.GenesisState,
|
||||
stakingData staking.GenesisState, mintData mint.GenesisState,
|
||||
distrData distr.GenesisState, govData gov.GenesisState,
|
||||
slashingData slashing.GenesisState) GenesisState {
|
||||
|
@ -50,6 +53,7 @@ func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState,
|
|||
return GenesisState{
|
||||
Accounts: accounts,
|
||||
AuthData: authData,
|
||||
BankData: bankData,
|
||||
StakingData: stakingData,
|
||||
MintData: mintData,
|
||||
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.
|
||||
type GenesisAccount struct {
|
||||
Address sdk.AccAddress `json:"address"`
|
||||
|
@ -69,8 +84,8 @@ type GenesisAccount struct {
|
|||
OriginalVesting sdk.Coins `json:"original_vesting"` // total vesting coins upon initialization
|
||||
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
|
||||
StartTime int64 `json:"start_time"` // vesting start time
|
||||
EndTime int64 `json:"end_time"` // vesting end time
|
||||
StartTime int64 `json:"start_time"` // vesting start time (UNIX Epoch time)
|
||||
EndTime int64 `json:"end_time"` // vesting end time (UNIX Epoch time)
|
||||
}
|
||||
|
||||
func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount {
|
||||
|
@ -190,6 +205,7 @@ func NewDefaultGenesisState() GenesisState {
|
|||
return GenesisState{
|
||||
Accounts: nil,
|
||||
AuthData: auth.DefaultGenesisState(),
|
||||
BankData: bank.DefaultGenesisState(),
|
||||
StakingData: staking.DefaultGenesisState(),
|
||||
MintData: mint.DefaultGenesisState(),
|
||||
DistrData: distr.DefaultGenesisState(),
|
||||
|
@ -216,6 +232,9 @@ func GaiaValidateGenesisState(genesisState GenesisState) error {
|
|||
if err := auth.ValidateGenesis(genesisState.AuthData); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bank.ValidateGenesis(genesisState.BankData); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := staking.ValidateGenesis(genesisState.StakingData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -232,17 +251,38 @@ func GaiaValidateGenesisState(genesisState GenesisState) error {
|
|||
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 {
|
||||
addrMap := make(map[string]bool, len(accs))
|
||||
for i := 0; i < len(accs); i++ {
|
||||
acc := accs[i]
|
||||
strAddr := string(acc.Address)
|
||||
if _, ok := addrMap[strAddr]; ok {
|
||||
return fmt.Errorf("Duplicate account in genesis state: Address %v", acc.Address)
|
||||
for _, acc := range accs {
|
||||
addrStr := acc.Address.String()
|
||||
|
||||
// disallow any duplicate accounts
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -5,17 +5,16 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"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"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -104,41 +103,86 @@ func TestGaiaAppGenState(t *testing.T) {
|
|||
|
||||
func makeMsg(name string, pk crypto.PubKey) auth.StdTx {
|
||||
desc := staking.NewDescription(name, "", "", "")
|
||||
comm := stakingTypes.CommissionMsg{}
|
||||
comm := staking.CommissionMsg{}
|
||||
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, "")
|
||||
}
|
||||
|
||||
func TestGaiaGenesisValidation(t *testing.T) {
|
||||
genTxs := make([]auth.StdTx, 2)
|
||||
// Test duplicate accounts fails
|
||||
genTxs[0] = makeMsg("test-0", pk1)
|
||||
genTxs[1] = makeMsg("test-1", pk1)
|
||||
genesisState := makeGenesisState(t, genTxs)
|
||||
genTxs := []auth.StdTx{makeMsg("test-0", pk1), makeMsg("test-1", pk2)}
|
||||
dupGenTxs := []auth.StdTx{makeMsg("test-0", pk1), makeMsg("test-1", pk1)}
|
||||
|
||||
// require duplicate accounts fails validation
|
||||
genesisState := makeGenesisState(t, dupGenTxs)
|
||||
err := GaiaValidateGenesisState(genesisState)
|
||||
require.NotNil(t, err)
|
||||
// Test bonded + jailed validator fails
|
||||
require.Error(t, err)
|
||||
|
||||
// require invalid vesting account fails validation (invalid end time)
|
||||
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.Status = sdk.Bonded
|
||||
genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1)
|
||||
err = GaiaValidateGenesisState(genesisState)
|
||||
require.NotNil(t, err)
|
||||
// Test duplicate validator fails
|
||||
require.Error(t, err)
|
||||
|
||||
// require duplicate validator fails validation
|
||||
val1.Jailed = false
|
||||
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, val2)
|
||||
err = GaiaValidateGenesisState(genesisState)
|
||||
require.NotNil(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNewDefaultGenesisAccount(t *testing.T) {
|
||||
addr := secp256k1.GenPrivKeySecp256k1([]byte("")).PubKey().Address()
|
||||
acc := NewDefaultGenesisAccount(sdk.AccAddress(addr))
|
||||
require.Equal(t, sdk.NewInt(1000), acc.Coins.AmountOf("footoken"))
|
||||
require.Equal(t, sdk.NewInt(150), acc.Coins.AmountOf(bondDenom))
|
||||
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()
|
||||
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"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
authsim "github.com/cosmos/cosmos-sdk/x/auth/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation"
|
||||
distr "github.com/cosmos/cosmos-sdk/x/distribution"
|
||||
distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation"
|
||||
|
@ -31,20 +34,21 @@ import (
|
|||
slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||
stakingsim "github.com/cosmos/cosmos-sdk/x/staking/simulation"
|
||||
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
var (
|
||||
seed int64
|
||||
numBlocks int
|
||||
blockSize int
|
||||
enabled bool
|
||||
verbose bool
|
||||
commit bool
|
||||
period int
|
||||
genesisFile string
|
||||
seed int64
|
||||
numBlocks int
|
||||
blockSize int
|
||||
enabled bool
|
||||
verbose bool
|
||||
commit bool
|
||||
period int
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&genesisFile, "SimulationGenesis", "", "Custom simulation genesis file")
|
||||
flag.Int64Var(&seed, "SimulationSeed", 42, "Simulation random seed")
|
||||
flag.IntVar(&numBlocks, "SimulationNumBlocks", 500, "Number of blocks")
|
||||
flag.IntVar(&blockSize, "SimulationBlockSize", 200, "Operations per block")
|
||||
|
@ -54,10 +58,34 @@ func init() {
|
|||
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
|
||||
|
||||
amount := int64(r.Intn(1e6))
|
||||
amount := int64(r.Intn(1e12))
|
||||
numInitiallyBonded := int64(r.Intn(250))
|
||||
numAccs := int64(len(accs))
|
||||
if numInitiallyBonded > numAccs {
|
||||
|
@ -69,7 +97,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
|
|||
|
||||
// randomly generate some genesis accounts
|
||||
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.SetCoins(coins)
|
||||
|
||||
|
@ -109,21 +137,24 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
|
|||
|
||||
authGenesis := auth.GenesisState{
|
||||
Params: auth.Params{
|
||||
MemoCostPerByte: uint64(r.Intn(10) + 1),
|
||||
MaxMemoCharacters: uint64(r.Intn(200-100) + 100),
|
||||
MaxMemoCharacters: uint64(randIntBetween(r, 100, 200)),
|
||||
TxSigLimit: uint64(r.Intn(7) + 1),
|
||||
SigVerifyCostED25519: uint64(r.Intn(1000-500) + 500),
|
||||
SigVerifyCostSecp256k1: uint64(r.Intn(1000-500) + 500),
|
||||
TxSizeCostPerByte: uint64(randIntBetween(r, 5, 15)),
|
||||
SigVerifyCostED25519: uint64(randIntBetween(r, 500, 1000)),
|
||||
SigVerifyCostSecp256k1: uint64(randIntBetween(r, 500, 1000)),
|
||||
},
|
||||
}
|
||||
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
|
||||
vp := time.Duration(r.Intn(2*172800)) * time.Second
|
||||
govGenesis := gov.GenesisState{
|
||||
StartingProposalID: uint64(r.Intn(100)),
|
||||
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,
|
||||
},
|
||||
VotingParams: gov.VotingParams{
|
||||
|
@ -142,7 +173,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
|
|||
Params: staking.Params{
|
||||
UnbondingTime: time.Duration(randIntBetween(r, 60, 60*60*24*3*2)) * time.Second,
|
||||
MaxValidators: uint16(r.Intn(250)),
|
||||
BondDenom: stakingTypes.DefaultBondDenom,
|
||||
BondDenom: staking.DefaultBondDenom,
|
||||
},
|
||||
}
|
||||
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(
|
||||
sdk.NewDecWithPrec(int64(r.Intn(99)), 2)),
|
||||
Params: mint.NewParams(
|
||||
stakingTypes.DefaultBondDenom,
|
||||
staking.DefaultBondDenom,
|
||||
sdk.NewDecWithPrec(int64(r.Intn(99)), 2),
|
||||
sdk.NewDecWithPrec(20, 2),
|
||||
sdk.NewDecWithPrec(7, 2),
|
||||
|
@ -203,6 +234,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
|
|||
genesis := GenesisState{
|
||||
Accounts: genesisAccounts,
|
||||
AuthData: authGenesis,
|
||||
BankData: bankGenesis,
|
||||
StakingData: stakingGenesis,
|
||||
MintData: mintGenesis,
|
||||
DistrData: distrGenesis,
|
||||
|
@ -216,7 +248,14 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T
|
|||
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 {
|
||||
|
@ -226,7 +265,8 @@ func randIntBetween(r *rand.Rand, min, max int) int {
|
|||
func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation {
|
||||
return []simulation.WeightedOperation{
|
||||
{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.SimulateMsgWithdrawDelegatorReward(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
|
||||
func BenchmarkFullGaiaSimulation(b *testing.B) {
|
||||
// Setup Gaia application
|
||||
var logger log.Logger
|
||||
logger = log.NewNopLogger()
|
||||
logger := log.NewNopLogger()
|
||||
|
||||
var db dbm.DB
|
||||
dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim")
|
||||
db, _ = dbm.NewGoLevelDB("Simulation", dir)
|
||||
|
@ -376,11 +416,8 @@ func TestGaiaImportExport(t *testing.T) {
|
|||
|
||||
fmt.Printf("Exporting genesis...\n")
|
||||
|
||||
appState, _, err := app.ExportAppStateAndValidators(false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
appState, _, err := app.ExportAppStateAndValidators(false, []string{})
|
||||
require.NoError(t, err)
|
||||
fmt.Printf("Importing genesis...\n")
|
||||
|
||||
newDir, _ := ioutil.TempDir("", "goleveldb-gaia-sim-2")
|
||||
|
@ -480,7 +517,7 @@ func TestGaiaSimulationAfterImport(t *testing.T) {
|
|||
|
||||
fmt.Printf("Exporting genesis...\n")
|
||||
|
||||
appState, _, err := app.ExportAppStateAndValidators(true)
|
||||
appState, _, err := app.ExportAppStateAndValidators(true, []string{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package clitest
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -14,15 +16,13 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
"github.com/cosmos/cosmos-sdk/tests"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
t.Parallel()
|
||||
f := InitFixtures(t)
|
||||
|
@ -67,13 +92,13 @@ func TestGaiaCLIMinimumFees(t *testing.T) {
|
|||
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||
|
||||
// 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)
|
||||
require.True(f.T, success)
|
||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||
|
||||
// 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)
|
||||
require.False(f.T, success)
|
||||
|
||||
|
@ -144,8 +169,9 @@ func TestGaiaCLIFeesDeduction(t *testing.T) {
|
|||
require.Equal(t, fooAmt.Int64(), fooAcc.GetCoins().AmountOf(fooDenom).Int64())
|
||||
|
||||
// insufficient funds (coins + fees) tx fails
|
||||
largeCoins := staking.TokensFromTendermintPower(10000000)
|
||||
success, _, _ = f.TxSend(
|
||||
keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 10000000),
|
||||
keyFoo, barAddr, sdk.NewCoin(fooDenom, largeCoins),
|
||||
fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)))
|
||||
require.False(t, success)
|
||||
|
||||
|
@ -178,45 +204,47 @@ func TestGaiaCLISend(t *testing.T) {
|
|||
barAddr := f.KeyAddress(keyBar)
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
// Ensure account balances match expected
|
||||
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())
|
||||
require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom))
|
||||
|
||||
// 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)
|
||||
|
||||
// Check state didn't change
|
||||
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
|
||||
f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10))
|
||||
f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens))
|
||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||
|
||||
// Ensure account balances match expected
|
||||
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)
|
||||
require.Equal(t, int64(30), fooAcc.GetCoins().AmountOf(denom).Int64())
|
||||
require.Equal(t, startTokens.Sub(sendTokens.MulRaw(2)), fooAcc.GetCoins().AmountOf(denom))
|
||||
|
||||
// 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)
|
||||
|
||||
// Ensure account balances match expected
|
||||
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)
|
||||
require.Equal(t, int64(20), fooAcc.GetCoins().AmountOf(denom).Int64())
|
||||
require.Equal(t, startTokens.Sub(sendTokens.MulRaw(3)), fooAcc.GetCoins().AmountOf(denom))
|
||||
|
||||
f.Cleanup()
|
||||
}
|
||||
|
@ -233,50 +261,48 @@ func TestGaiaCLIGasAuto(t *testing.T) {
|
|||
barAddr := f.KeyAddress(keyBar)
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
// Check state didn't change
|
||||
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
|
||||
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)
|
||||
|
||||
// Check state didn't change
|
||||
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
|
||||
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)
|
||||
|
||||
// Check state didn't change
|
||||
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
|
||||
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.True(t, success)
|
||||
cdc := app.MakeCodec()
|
||||
sendResp := struct {
|
||||
Height int64
|
||||
TxHash string
|
||||
Response abci.ResponseDeliverTx
|
||||
}{}
|
||||
sendResp := sdk.TxResponse{}
|
||||
err := cdc.UnmarshalJSON([]byte(stdout), &sendResp)
|
||||
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)
|
||||
|
||||
// Check state has changed accordingly
|
||||
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()
|
||||
}
|
||||
|
@ -289,23 +315,17 @@ func TestGaiaCLICreateValidator(t *testing.T) {
|
|||
proc := f.GDStart()
|
||||
defer proc.Stop(false)
|
||||
|
||||
fooAddr := f.KeyAddress(keyFoo)
|
||||
barAddr := f.KeyAddress(keyBar)
|
||||
barVal := sdk.ValAddress(barAddr)
|
||||
|
||||
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)
|
||||
|
||||
barAcc := f.QueryAccount(barAddr)
|
||||
require.Equal(t, int64(10), barAcc.GetCoins().AmountOf(denom).Int64())
|
||||
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
|
||||
require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom))
|
||||
|
||||
// Generate a create validator transaction and ensure correctness
|
||||
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()))
|
||||
|
||||
// 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)
|
||||
|
||||
// Create the validator
|
||||
f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewInt64Coin(denom, 2))
|
||||
f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewCoin(denom, newValTokens))
|
||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||
|
||||
// Ensure funds were deducted properly
|
||||
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
|
||||
validator := f.QueryStakingValidator(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
|
||||
validatorDelegations := f.QueryStakingDelegationsTo(barVal)
|
||||
|
@ -340,27 +361,21 @@ func TestGaiaCLICreateValidator(t *testing.T) {
|
|||
require.NotZero(t, validatorDelegations[0].Shares)
|
||||
|
||||
// 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)
|
||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||
|
||||
// Ensure bonded staking is correct
|
||||
remainingTokens := newValTokens.Sub(unbondTokens)
|
||||
validator = f.QueryStakingValidator(barVal)
|
||||
require.Equal(t, "1", validator.Tokens.String())
|
||||
require.Equal(t, remainingTokens, validator.Tokens)
|
||||
|
||||
// Get unbonding delegations from the validator
|
||||
validatorUbds := f.QueryStakingUnbondingDelegationsFrom(barVal)
|
||||
require.Len(t, validatorUbds, 1)
|
||||
require.Len(t, validatorUbds[0].Entries, 1)
|
||||
require.Equal(t, "1", validatorUbds[0].Entries[0].Balance.Amount.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)
|
||||
require.Equal(t, remainingTokens.String(), validatorUbds[0].Entries[0].Balance.String())
|
||||
|
||||
f.Cleanup()
|
||||
}
|
||||
|
@ -380,14 +395,16 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
|||
fooAddr := f.KeyAddress(keyFoo)
|
||||
|
||||
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()
|
||||
require.Empty(t, proposalsQuery)
|
||||
|
||||
// Test submit generate only for submit proposal
|
||||
proposalTokens := staking.TokensFromTendermintPower(5)
|
||||
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.Empty(t, stderr)
|
||||
msg := unmarshalStdTx(t, stdout)
|
||||
|
@ -396,11 +413,11 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
|||
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
|
||||
// Ensure transaction tags can be queried
|
||||
|
@ -409,7 +426,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
|||
|
||||
// Ensure deposit was deducted
|
||||
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
|
||||
proposal1 := f.QueryGovProposal(1)
|
||||
|
@ -422,10 +439,11 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
|||
|
||||
// Query the deposits on the proposal
|
||||
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
|
||||
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.Empty(t, stderr)
|
||||
msg = unmarshalStdTx(t, stdout)
|
||||
|
@ -434,17 +452,17 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
|||
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||
|
||||
// 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)
|
||||
|
||||
// test query deposit
|
||||
deposits := f.QueryGovDeposits(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
|
||||
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
|
||||
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
|
||||
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
|
||||
proposal1 = f.QueryGovProposal(1)
|
||||
|
@ -496,7 +514,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
|||
require.Equal(t, uint64(1), proposalsQuery[0].GetProposalID())
|
||||
|
||||
// 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)
|
||||
|
||||
// Test limit on proposals query
|
||||
|
@ -517,12 +535,13 @@ func TestGaiaCLIQueryTxPagination(t *testing.T) {
|
|||
fooAddr := f.KeyAddress(keyFoo)
|
||||
barAddr := f.KeyAddress(keyBar)
|
||||
|
||||
accFoo := f.QueryAccount(fooAddr)
|
||||
seq := accFoo.GetSequence()
|
||||
|
||||
for i := 1; i <= 30; i++ {
|
||||
success := executeWrite(t, fmt.Sprintf(
|
||||
"gaiacli tx send %s --amount=%dfootoken --to=%s --from=foo",
|
||||
f.Flags(), i, barAddr), app.DefaultKeyPass)
|
||||
success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, int64(i)), fmt.Sprintf("--sequence=%d", seq))
|
||||
require.True(t, success)
|
||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||
seq++
|
||||
}
|
||||
|
||||
// perPage = 15, 2 pages
|
||||
|
@ -571,7 +590,7 @@ func TestGaiaCLIValidateSignatures(t *testing.T) {
|
|||
require.Empty(t, stderr)
|
||||
|
||||
// write unsigned tx to file
|
||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
||||
unsignedTxFile := WriteToNewTempFile(t, stdout)
|
||||
defer os.Remove(unsignedTxFile.Name())
|
||||
|
||||
// validate we can successfully sign
|
||||
|
@ -584,7 +603,7 @@ func TestGaiaCLIValidateSignatures(t *testing.T) {
|
|||
require.Equal(t, fooAddr.String(), stdTx.GetSigners()[0].String())
|
||||
|
||||
// write signed tx to file
|
||||
signedTxFile := writeToNewTempFile(t, stdout)
|
||||
signedTxFile := WriteToNewTempFile(t, stdout)
|
||||
defer os.Remove(signedTxFile.Name())
|
||||
|
||||
// validate signatures
|
||||
|
@ -594,7 +613,7 @@ func TestGaiaCLIValidateSignatures(t *testing.T) {
|
|||
// modify the transaction
|
||||
stdTx.Memo = "MODIFIED-ORIGINAL-TX-BAD"
|
||||
bz := marshalStdTx(t, stdTx)
|
||||
modSignedTxFile := writeToNewTempFile(t, string(bz))
|
||||
modSignedTxFile := WriteToNewTempFile(t, string(bz))
|
||||
defer os.Remove(modSignedTxFile.Name())
|
||||
|
||||
// validate signature validation failure due to different transaction sig bytes
|
||||
|
@ -616,7 +635,8 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
|||
barAddr := f.KeyAddress(keyBar)
|
||||
|
||||
// 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.Empty(t, stderr)
|
||||
msg := unmarshalStdTx(t, stdout)
|
||||
|
@ -625,7 +645,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
|||
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||
|
||||
// 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.Empty(t, stderr)
|
||||
msg = unmarshalStdTx(t, stdout)
|
||||
|
@ -634,7 +654,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
|||
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||
|
||||
// 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.NotEmpty(t, stderr)
|
||||
msg = unmarshalStdTx(t, stdout)
|
||||
|
@ -642,7 +662,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
|||
require.Equal(t, len(msg.Msgs), 1)
|
||||
|
||||
// Write the output to disk
|
||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
||||
unsignedTxFile := WriteToNewTempFile(t, stdout)
|
||||
defer os.Remove(unsignedTxFile.Name())
|
||||
|
||||
// Test sign --validate-signatures
|
||||
|
@ -659,7 +679,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
|||
require.Equal(t, fooAddr.String(), msg.GetSigners()[0].String())
|
||||
|
||||
// Write the output to disk
|
||||
signedTxFile := writeToNewTempFile(t, stdout)
|
||||
signedTxFile := WriteToNewTempFile(t, stdout)
|
||||
defer os.Remove(signedTxFile.Name())
|
||||
|
||||
// Test sign --validate-signatures
|
||||
|
@ -670,27 +690,26 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
|||
|
||||
// Ensure foo has right amount of funds
|
||||
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
|
||||
success, stdout, _ = f.TxBroadcast(signedTxFile.Name())
|
||||
require.True(t, success)
|
||||
|
||||
var result struct {
|
||||
Response abci.ResponseDeliverTx
|
||||
}
|
||||
var result sdk.TxResponse
|
||||
|
||||
// Unmarshal the response and ensure that gas was properly used
|
||||
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.Response.GasWanted))
|
||||
require.Equal(t, msg.Fee.Gas, uint64(result.GasUsed))
|
||||
require.Equal(t, msg.Fee.Gas, uint64(result.GasWanted))
|
||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||
|
||||
// Ensure account state
|
||||
barAcc := f.QueryAccount(barAddr)
|
||||
fooAcc = f.QueryAccount(fooAddr)
|
||||
require.Equal(t, int64(10), barAcc.GetCoins().AmountOf(denom).Int64())
|
||||
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64())
|
||||
require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom))
|
||||
require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom))
|
||||
|
||||
f.Cleanup()
|
||||
}
|
||||
|
@ -716,7 +735,7 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
|
|||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
||||
unsignedTxFile := WriteToNewTempFile(t, stdout)
|
||||
defer os.Remove(unsignedTxFile.Name())
|
||||
|
||||
// Sign with foo's key
|
||||
|
@ -724,7 +743,7 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
|
|||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
fooSignatureFile := writeToNewTempFile(t, stdout)
|
||||
fooSignatureFile := WriteToNewTempFile(t, stdout)
|
||||
defer os.Remove(fooSignatureFile.Name())
|
||||
|
||||
// Multisign, not enough signatures
|
||||
|
@ -732,7 +751,7 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
|
|||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
signedTxFile := writeToNewTempFile(t, stdout)
|
||||
signedTxFile := WriteToNewTempFile(t, stdout)
|
||||
defer os.Remove(signedTxFile.Name())
|
||||
|
||||
// Validate the multisignature
|
||||
|
@ -744,6 +763,42 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
|
|||
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) {
|
||||
t.Parallel()
|
||||
f := InitFixtures(t)
|
||||
|
@ -769,7 +824,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
|||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
||||
unsignedTxFile := WriteToNewTempFile(t, stdout)
|
||||
defer os.Remove(unsignedTxFile.Name())
|
||||
|
||||
// Sign with foo's key
|
||||
|
@ -777,7 +832,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
|||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
fooSignatureFile := writeToNewTempFile(t, stdout)
|
||||
fooSignatureFile := WriteToNewTempFile(t, stdout)
|
||||
defer os.Remove(fooSignatureFile.Name())
|
||||
|
||||
// Sign with baz's key
|
||||
|
@ -785,7 +840,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
|||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
bazSignatureFile := writeToNewTempFile(t, stdout)
|
||||
bazSignatureFile := WriteToNewTempFile(t, stdout)
|
||||
defer os.Remove(bazSignatureFile.Name())
|
||||
|
||||
// Multisign, keys in different order
|
||||
|
@ -794,7 +849,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
|||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
signedTxFile := writeToNewTempFile(t, stdout)
|
||||
signedTxFile := WriteToNewTempFile(t, stdout)
|
||||
defer os.Remove(signedTxFile.Name())
|
||||
|
||||
// Validate the multisignature
|
||||
|
@ -832,7 +887,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
|
|||
require.Empty(t, stderr)
|
||||
|
||||
// Write the output to disk
|
||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
||||
unsignedTxFile := WriteToNewTempFile(t, stdout)
|
||||
defer os.Remove(unsignedTxFile.Name())
|
||||
|
||||
// Sign with foo's key
|
||||
|
@ -840,7 +895,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
|
|||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
fooSignatureFile := writeToNewTempFile(t, stdout)
|
||||
fooSignatureFile := WriteToNewTempFile(t, stdout)
|
||||
defer os.Remove(fooSignatureFile.Name())
|
||||
|
||||
// Sign with bar's key
|
||||
|
@ -848,7 +903,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
|
|||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
barSignatureFile := writeToNewTempFile(t, stdout)
|
||||
barSignatureFile := WriteToNewTempFile(t, stdout)
|
||||
defer os.Remove(barSignatureFile.Name())
|
||||
|
||||
// Multisign
|
||||
|
@ -857,7 +912,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
|
|||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
signedTxFile := writeToNewTempFile(t, stdout)
|
||||
signedTxFile := WriteToNewTempFile(t, stdout)
|
||||
defer os.Remove(signedTxFile.Name())
|
||||
|
||||
// Validate the multisignature
|
||||
|
@ -909,10 +964,7 @@ func TestGaiadCollectGentxs(t *testing.T) {
|
|||
f.UnsafeResetAll()
|
||||
|
||||
// Initialize keys
|
||||
f.KeysDelete(keyFoo)
|
||||
f.KeysDelete(keyBar)
|
||||
f.KeysAdd(keyFoo)
|
||||
f.KeysAdd(keyBar)
|
||||
|
||||
// Configure json output
|
||||
f.CLIConfig("output", "json")
|
||||
|
@ -932,6 +984,42 @@ func TestGaiadCollectGentxs(t *testing.T) {
|
|||
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) {
|
||||
t.Parallel()
|
||||
f := InitFixtures(t)
|
||||
|
@ -944,4 +1032,19 @@ func TestSlashingGetParams(t *testing.T) {
|
|||
require.Equal(t, time.Duration(120000000000), params.MaxEvidenceAge)
|
||||
require.Equal(t, int64(100), params.SignedBlocksWindow)
|
||||
require.Equal(t, sdk.NewDecWithPrec(5, 1), params.MinSignedPerWindow)
|
||||
|
||||
sinfo := f.QuerySigningInfo(f.GDTendermint("show-validator"))
|
||||
require.Equal(t, int64(0), sinfo.StartHeight)
|
||||
require.False(t, sinfo.Tombstoned)
|
||||
}
|
||||
|
||||
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"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"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/server"
|
||||
"github.com/cosmos/cosmos-sdk/tests"
|
||||
|
@ -34,15 +35,22 @@ const (
|
|||
feeDenom = "feetoken"
|
||||
fee2Denom = "fee2token"
|
||||
keyBaz = "baz"
|
||||
keyVesting = "vesting"
|
||||
keyFooBarBaz = "foobarbaz"
|
||||
)
|
||||
|
||||
var startCoins = sdk.Coins{
|
||||
sdk.NewInt64Coin(feeDenom, 1000000),
|
||||
sdk.NewInt64Coin(fee2Denom, 1000000),
|
||||
sdk.NewInt64Coin(fooDenom, 1000),
|
||||
sdk.NewInt64Coin(denom, 150),
|
||||
}
|
||||
var (
|
||||
startCoins = sdk.Coins{
|
||||
sdk.NewCoin(feeDenom, staking.TokensFromTendermintPower(1000000)),
|
||||
sdk.NewCoin(fee2Denom, staking.TokensFromTendermintPower(1000000)),
|
||||
sdk.NewCoin(fooDenom, staking.TokensFromTendermintPower(1000)),
|
||||
sdk.NewCoin(denom, staking.TokensFromTendermintPower(150)),
|
||||
}
|
||||
|
||||
vestingCoins = sdk.Coins{
|
||||
sdk.NewCoin(feeDenom, staking.TokensFromTendermintPower(500000)),
|
||||
}
|
||||
)
|
||||
|
||||
//___________________________________________________________________________________
|
||||
// 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
|
||||
// and initializes a chain with 1 validator
|
||||
func InitFixtures(t *testing.T) (f *Fixtures) {
|
||||
|
@ -92,6 +116,7 @@ func InitFixtures(t *testing.T) (f *Fixtures) {
|
|||
f.KeysAdd(keyFoo)
|
||||
f.KeysAdd(keyBar)
|
||||
f.KeysAdd(keyBaz)
|
||||
f.KeysAdd(keyVesting)
|
||||
f.KeysAdd(keyFooBarBaz, "--multisig-threshold=2", fmt.Sprintf(
|
||||
"--multisig=%s,%s,%s", keyFoo, keyBar, keyBaz))
|
||||
|
||||
|
@ -104,6 +129,12 @@ func InitFixtures(t *testing.T) (f *Fixtures) {
|
|||
|
||||
// Start an account with tokens
|
||||
f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins)
|
||||
f.AddGenesisAccount(
|
||||
f.KeyAddress(keyVesting), startCoins,
|
||||
fmt.Sprintf("--vesting-amount=%s", vestingCoins),
|
||||
fmt.Sprintf("--vesting-start-time=%d", time.Now().UTC().UnixNano()),
|
||||
fmt.Sprintf("--vesting-end-time=%d", time.Now().Add(60*time.Second).UTC().UnixNano()),
|
||||
)
|
||||
f.GenTx(keyFoo)
|
||||
f.CollectGenTxs()
|
||||
return
|
||||
|
@ -137,7 +168,7 @@ func (f *Fixtures) UnsafeResetAll(flags ...string) {
|
|||
// GDInit is gaiad init
|
||||
// NOTE: GDInit sets the ChainID for the Fixtures instance
|
||||
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)
|
||||
|
||||
var chainID string
|
||||
|
@ -179,6 +210,21 @@ func (f *Fixtures) GDStart(flags ...string) *tests.Process {
|
|||
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
|
||||
|
||||
|
@ -194,6 +240,18 @@ func (f *Fixtures) KeysAdd(name string, flags ...string) {
|
|||
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
|
||||
func (f *Fixtures) KeysShow(name string, flags ...string) keys.KeyOutput {
|
||||
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
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -236,12 +294,18 @@ func (f *Fixtures) TxSign(signer, fileName string, flags ...string) (bool, strin
|
|||
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) {
|
||||
cmd := fmt.Sprintf("gaiacli tx broadcast %v %v", f.Flags(), fileName)
|
||||
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
|
||||
func (f *Fixtures) TxMultisign(fileName, name string, signaturesFiles []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(" --amount=%v --moniker=%v --commission-rate=%v", amount, from, "0.05")
|
||||
cmd += fmt.Sprintf(" --commission-max-rate=%v --commission-max-change-rate=%v", "0.20", "0.10")
|
||||
cmd += fmt.Sprintf(" --min-self-delegation=%v", "1")
|
||||
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
||||
}
|
||||
|
||||
// TxStakingUnbond is gaiacli tx staking unbond
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -314,10 +379,10 @@ func (f *Fixtures) QueryAccount(address sdk.AccAddress, flags ...string) auth.Ba
|
|||
// 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())
|
||||
out, _ := tests.ExecuteT(f.T, cmd, "")
|
||||
var txs []tx.Info
|
||||
var txs []sdk.TxResponse
|
||||
cdc := app.MakeCodec()
|
||||
err := cdc.UnmarshalJSON([]byte(out), &txs)
|
||||
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
|
||||
|
||||
// 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
|
||||
func (f *Fixtures) QuerySlashingParams() slashing.Params {
|
||||
cmd := fmt.Sprintf("gaiacli query slashing params %s", f.Flags())
|
||||
|
@ -569,7 +646,8 @@ func queryTags(tags []string) (out string) {
|
|||
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_")
|
||||
require.Nil(t, err)
|
||||
_, err = fp.WriteString(s)
|
||||
|
|
|
@ -25,7 +25,7 @@ import (
|
|||
at "github.com/cosmos/cosmos-sdk/x/auth"
|
||||
auth "github.com/cosmos/cosmos-sdk/x/auth/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"
|
||||
gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
|
||||
sl "github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
|
@ -35,6 +35,7 @@ import (
|
|||
|
||||
authcmd "github.com/cosmos/cosmos-sdk/x/auth/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"
|
||||
govClient "github.com/cosmos/cosmos-sdk/x/gov/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
|
||||
mc := []sdk.ModuleClients{
|
||||
govClient.NewModuleClient(gv.StoreKey, cdc),
|
||||
distClient.NewModuleClient(dist.StoreKey, cdc),
|
||||
distClient.NewModuleClient(distcmd.StoreKey, cdc),
|
||||
stakingClient.NewModuleClient(st.StoreKey, cdc),
|
||||
slashingClient.NewModuleClient(sl.StoreKey, cdc),
|
||||
}
|
||||
|
@ -84,7 +85,7 @@ func main() {
|
|||
// Construct Root Command
|
||||
rootCmd.AddCommand(
|
||||
rpc.StatusCommand(),
|
||||
client.ConfigCmd(),
|
||||
client.ConfigCmd(app.DefaultCLIHome),
|
||||
queryCmd(cdc, mc),
|
||||
txCmd(cdc, mc),
|
||||
client.LineBreak,
|
||||
|
@ -114,7 +115,7 @@ func queryCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command {
|
|||
}
|
||||
|
||||
queryCmd.AddCommand(
|
||||
rpc.ValidatorCommand(),
|
||||
rpc.ValidatorCommand(cdc),
|
||||
rpc.BlockCommand(),
|
||||
tx.SearchTxCmd(cdc),
|
||||
tx.QueryTxCmd(cdc),
|
||||
|
@ -140,7 +141,8 @@ func txCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command {
|
|||
client.LineBreak,
|
||||
authcmd.GetSignCommand(cdc),
|
||||
authcmd.GetMultiSignCommand(cdc),
|
||||
bankcmd.GetBroadcastCommand(cdc),
|
||||
authcmd.GetBroadcastCommand(cdc),
|
||||
authcmd.GetEncodeCommand(cdc),
|
||||
client.LineBreak,
|
||||
)
|
||||
|
||||
|
@ -161,6 +163,7 @@ func registerRoutes(rs *lcd.RestServer) {
|
|||
tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
|
||||
auth.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, at.StoreKey)
|
||||
bank.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
||||
dist.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, distcmd.StoreKey)
|
||||
staking.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
||||
slashing.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
|
||||
gov.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
|
||||
|
|
|
@ -43,6 +43,7 @@ func main() {
|
|||
rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc))
|
||||
rootCmd.AddCommand(gaiaInit.GenTxCmd(ctx, cdc))
|
||||
rootCmd.AddCommand(gaiaInit.AddGenesisAccountCmd(ctx, cdc))
|
||||
rootCmd.AddCommand(gaiaInit.ValidateGenesisCmd(ctx, cdc))
|
||||
rootCmd.AddCommand(client.NewCompletionCmd(rootCmd, true))
|
||||
|
||||
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 {
|
||||
return app.NewGaiaApp(
|
||||
logger, db, traceStore, true,
|
||||
baseapp.SetPruning(store.NewPruningOptions(viper.GetString("pruning"))),
|
||||
baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))),
|
||||
baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)),
|
||||
)
|
||||
}
|
||||
|
||||
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) {
|
||||
if height != -1 {
|
||||
gApp := app.NewGaiaApp(logger, db, traceStore, false)
|
||||
|
@ -73,8 +74,8 @@ func exportAppStateAndTMValidators(
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return gApp.ExportAppStateAndValidators(forZeroHeight)
|
||||
return gApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
|
||||
}
|
||||
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)
|
||||
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
|
||||
id := app.LastCommitID()
|
||||
|
@ -178,7 +178,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp
|
|||
)
|
||||
|
||||
// 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.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakingKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), slashing.DefaultCodespace)
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package init
|
||||
|
||||
// DONTCOVER
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
|
@ -44,7 +46,7 @@ func CollectGenTxsCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
|
|||
return err
|
||||
}
|
||||
|
||||
genDoc, err := loadGenesisDoc(cdc, config.GenesisFile())
|
||||
genDoc, err := LoadGenesisDoc(cdc, config.GenesisFile())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package init
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -17,7 +16,7 @@ import (
|
|||
"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 {
|
||||
cmd := &cobra.Command{
|
||||
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])
|
||||
if err != nil {
|
||||
kb, err := keys.GetKeyBaseFromDir(viper.GetString(flagClientHome))
|
||||
kb, err := keys.NewKeyBaseFromDir(viper.GetString(flagClientHome))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := kb.Get(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addr = info.GetAddress()
|
||||
}
|
||||
|
||||
coins, err := sdk.ParseCoins(args[1])
|
||||
if err != nil {
|
||||
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()
|
||||
if !common.FileExists(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 {
|
||||
return err
|
||||
}
|
||||
|
@ -59,7 +68,12 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command
|
|||
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 {
|
||||
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(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
|
||||
}
|
||||
|
||||
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 {
|
||||
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.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()
|
||||
addr1 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
|
||||
type args struct {
|
||||
appState app.GenesisState
|
||||
addr sdk.AccAddress
|
||||
coins sdk.Coins
|
||||
appState app.GenesisState
|
||||
addr sdk.AccAddress
|
||||
coins sdk.Coins
|
||||
vestingAmt sdk.Coins
|
||||
vestingStart int64
|
||||
vestingEnd int64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -30,16 +33,55 @@ func TestAddGenesisAccount(t *testing.T) {
|
|||
app.GenesisState{},
|
||||
addr1,
|
||||
sdk.Coins{},
|
||||
sdk.Coins{},
|
||||
0,
|
||||
0,
|
||||
},
|
||||
false},
|
||||
{"dup account", args{
|
||||
app.GenesisState{Accounts: []app.GenesisAccount{{Address: addr1}}},
|
||||
addr1,
|
||||
sdk.Coins{}}, true},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"dup account",
|
||||
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 {
|
||||
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))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package init
|
||||
|
||||
// DONTCOVER
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
@ -26,15 +28,17 @@ import (
|
|||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
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"
|
||||
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultAmount = "100" + stakingTypes.DefaultBondDenom
|
||||
var (
|
||||
defaultTokens = staking.TokensFromTendermintPower(100)
|
||||
defaultAmount = defaultTokens.String() + staking.DefaultBondDenom
|
||||
defaultCommissionRate = "0.1"
|
||||
defaultCommissionMaxRate = "0.2"
|
||||
defaultCommissionMaxChangeRate = "0.01"
|
||||
defaultMinSelfDelegation = "1"
|
||||
)
|
||||
|
||||
// GenTxCmd builds the gaiad gentx command.
|
||||
|
@ -52,7 +56,8 @@ following delegation and commission default parameters:
|
|||
commission rate: %s
|
||||
commission max 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 {
|
||||
|
||||
config := ctx.Config
|
||||
|
@ -61,12 +66,19 @@ following delegation and commission default parameters:
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ip, err := server.ExternalIP()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
// Read --nodeID, if empty take it from priv_validator.json
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -76,7 +88,7 @@ following delegation and commission default parameters:
|
|||
return err
|
||||
}
|
||||
|
||||
kb, err := keys.GetKeyBaseFromDir(viper.GetString(flagClientHome))
|
||||
kb, err := keys.NewKeyBaseFromDir(viper.GetString(flagClientHome))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -120,7 +132,8 @@ following delegation and commission default parameters:
|
|||
|
||||
// write the unsigned transaction to the buffer
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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(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.FlagOutputDocument, "",
|
||||
"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.FsMinSelfDelegation)
|
||||
cmd.Flags().AddFlagSet(cli.FsAmount)
|
||||
cmd.Flags().AddFlagSet(cli.FsPk)
|
||||
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.FlagIP, ip) // --ip
|
||||
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
|
||||
if config.Moniker == "" {
|
||||
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) == "" {
|
||||
viper.Set(cli.FlagCommissionMaxChangeRate, defaultCommissionMaxChangeRate)
|
||||
}
|
||||
if viper.GetString(cli.FlagMinSelfDelegation) == "" {
|
||||
viper.Set(cli.FlagMinSelfDelegation, defaultMinSelfDelegation)
|
||||
}
|
||||
}
|
||||
|
||||
func makeOutputFilepath(rootDir, nodeID string) (string, error) {
|
||||
|
|
|
@ -19,9 +19,11 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
flagOverwrite = "overwrite"
|
||||
flagClientHome = "home-client"
|
||||
flagMoniker = "moniker"
|
||||
flagOverwrite = "overwrite"
|
||||
flagClientHome = "home-client"
|
||||
flagVestingStart = "vesting-start-time"
|
||||
flagVestingEnd = "vesting-end-time"
|
||||
flagVestingAmt = "vesting-amount"
|
||||
)
|
||||
|
||||
type printInfo struct {
|
||||
|
@ -32,25 +34,25 @@ type printInfo struct {
|
|||
AppMessage json.RawMessage `json:"app_message"`
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
func displayInfo(cdc *codec.Codec, info printInfo) error {
|
||||
out, err := codec.MarshalJSONIndent(cdc, info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "%s\n", string(out))
|
||||
|
||||
fmt.Fprintf(os.Stderr, "%s\n", string(out)) // nolint: errcheck
|
||||
return nil
|
||||
}
|
||||
|
||||
// get cmd to initialize all files for tendermint and application
|
||||
// nolint
|
||||
func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
|
||||
// InitCmd returns a command that initializes all files needed for Tendermint
|
||||
// and the respective application.
|
||||
func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { // nolint: golint
|
||||
cmd := &cobra.Command{
|
||||
Use: "init",
|
||||
Use: "init [moniker]",
|
||||
Short: "Initialize private validator, p2p, genesis, and application configuration files",
|
||||
Long: `Initialize validators's and node's configuration files.`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
config := ctx.Config
|
||||
config.SetRoot(viper.GetString(cli.HomeFlag))
|
||||
|
||||
|
@ -64,7 +66,7 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
|
|||
return err
|
||||
}
|
||||
|
||||
config.Moniker = viper.GetString(flagMoniker)
|
||||
config.Moniker = args[0]
|
||||
|
||||
var appState json.RawMessage
|
||||
genFile := config.GenesisFile()
|
||||
|
@ -81,7 +83,6 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
|
|||
toPrint := newPrintInfo(config.Moniker, chainID, nodeID, "", appState)
|
||||
|
||||
cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config)
|
||||
|
||||
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().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(flagMoniker, "", "set the validator's moniker")
|
||||
cmd.MarkFlagRequired(flagMoniker)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -8,20 +8,16 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"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"
|
||||
abciServer "github.com/tendermint/tendermint/abci/server"
|
||||
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
"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) {
|
||||
|
@ -36,10 +32,7 @@ func TestInitCmd(t *testing.T) {
|
|||
cdc := app.MakeCodec()
|
||||
cmd := InitCmd(ctx, cdc)
|
||||
|
||||
viper.Set(flagMoniker, "gaianode-test")
|
||||
|
||||
err = cmd.RunE(nil, nil)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, cmd.RunE(nil, []string{"gaianode-test"}))
|
||||
}
|
||||
|
||||
func setupClientHome(t *testing.T) func() {
|
||||
|
@ -64,11 +57,9 @@ func TestEmptyState(t *testing.T) {
|
|||
|
||||
ctx := server.NewContext(cfg, logger)
|
||||
cdc := app.MakeCodec()
|
||||
viper.Set(flagMoniker, "gaianode-test")
|
||||
|
||||
cmd := InitCmd(ctx, cdc)
|
||||
err = cmd.RunE(nil, nil)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, cmd.RunE(nil, []string{"gaianode-test"}))
|
||||
|
||||
old := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
|
@ -88,7 +79,6 @@ func TestEmptyState(t *testing.T) {
|
|||
w.Close()
|
||||
os.Stdout = old
|
||||
out := <-outC
|
||||
require.Contains(t, out, "WARNING: State is not initialized")
|
||||
require.Contains(t, out, "genesis_time")
|
||||
require.Contains(t, out, "chain_id")
|
||||
require.Contains(t, out, "consensus_params")
|
||||
|
@ -103,7 +93,6 @@ func TestStartStandAlone(t *testing.T) {
|
|||
os.RemoveAll(home)
|
||||
}()
|
||||
viper.Set(cli.HomeFlag, home)
|
||||
viper.Set(client.FlagName, "moniker")
|
||||
defer setupClientHome(t)()
|
||||
|
||||
logger := log.NewNopLogger()
|
||||
|
@ -112,8 +101,7 @@ func TestStartStandAlone(t *testing.T) {
|
|||
ctx := server.NewContext(cfg, logger)
|
||||
cdc := app.MakeCodec()
|
||||
initCmd := InitCmd(ctx, cdc)
|
||||
err = initCmd.RunE(nil, nil)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, initCmd.RunE(nil, []string{"gaianode-test"}))
|
||||
|
||||
app, err := mock.NewApp(home, logger)
|
||||
require.Nil(t, err)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package init
|
||||
|
||||
// DONTCOVER
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -7,6 +9,8 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
|
@ -188,23 +192,31 @@ func initTestnet(config *tmconfig.Config, cdc *codec.Codec) error {
|
|||
return err
|
||||
}
|
||||
|
||||
accTokens := staking.TokensFromTendermintPower(1000)
|
||||
accStakingTokens := staking.TokensFromTendermintPower(500)
|
||||
accs = append(accs, app.GenesisAccount{
|
||||
Address: addr,
|
||||
Coins: sdk.Coins{
|
||||
sdk.NewInt64Coin(fmt.Sprintf("%stoken", nodeDirName), 1000),
|
||||
sdk.NewInt64Coin(stakingtypes.DefaultBondDenom, 500),
|
||||
sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), accTokens),
|
||||
sdk.NewCoin(stakingtypes.DefaultBondDenom, accStakingTokens),
|
||||
},
|
||||
})
|
||||
|
||||
valTokens := staking.TokensFromTendermintPower(100)
|
||||
msg := staking.NewMsgCreateValidator(
|
||||
sdk.ValAddress(addr),
|
||||
valPubKeys[i],
|
||||
sdk.NewInt64Coin(stakingtypes.DefaultBondDenom, 100),
|
||||
sdk.NewCoin(stakingtypes.DefaultBondDenom, valTokens),
|
||||
staking.NewDescription(nodeDirName, "", "", ""),
|
||||
staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
|
||||
sdk.OneInt(),
|
||||
)
|
||||
kb, err := keys.NewKeyBaseFromDir(clientDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo)
|
||||
txBldr := authtx.NewTxBuilderFromCLI().WithChainID(chainID).WithMemo(memo)
|
||||
txBldr := authtx.NewTxBuilderFromCLI().WithChainID(chainID).WithMemo(memo).WithKeybase(kb)
|
||||
|
||||
signedTx, err := txBldr.SignStdTx(nodeDirName, app.DefaultKeyPass, tx, false)
|
||||
if err != nil {
|
||||
|
@ -295,7 +307,7 @@ func collectGenFiles(
|
|||
nodeID, valPubKey := nodeIDs[i], valPubKeys[i]
|
||||
initCfg := newInitConfig(chainID, gentxsDir, moniker, nodeID, valPubKey)
|
||||
|
||||
genDoc, err := loadGenesisDoc(cdc, config.GenesisFile())
|
||||
genDoc, err := LoadGenesisDoc(cdc, config.GenesisFile())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -88,7 +88,8 @@ func InitializeNodeValidatorFiles(
|
|||
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)
|
||||
if err != nil {
|
||||
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 (
|
||||
"crypto/hmac"
|
||||
"crypto/sha512"
|
||||
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"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))
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
// Check items can be parsed
|
||||
purpose, err := hardenedInt(spl[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -91,15 +80,30 @@ func NewParamsFromPath(path string) (*BIP44Params, error) {
|
|||
if err != nil {
|
||||
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])
|
||||
if err != nil {
|
||||
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{
|
||||
purpose: purpose,
|
||||
coinType: coinType,
|
||||
|
@ -132,7 +136,7 @@ func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params {
|
|||
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 {
|
||||
change := uint32(0)
|
||||
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)
|
||||
// sha512 does not err
|
||||
_, _ = mac.Write(data)
|
||||
|
||||
I := mac.Sum(nil)
|
||||
copy(IL[:], I[:32])
|
||||
copy(IR[:], I[32:])
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/cosmos/go-bip39"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var defaultBIP39Passphrase = ""
|
||||
|
@ -21,7 +21,27 @@ func mnemonicToSeed(mnemonic string) []byte {
|
|||
func ExampleStringifyPathParams() {
|
||||
path := NewParams(44, 0, 0, false, 0)
|
||||
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) {
|
||||
|
@ -60,6 +80,11 @@ func TestParamsFromPath(t *testing.T) {
|
|||
{"44'/0'/0'/0/0'"}, // fifth field must not have '
|
||||
{"44'/-1'/0'/0/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 {
|
||||
|
@ -80,14 +105,39 @@ func ExampleSomeBIP32TestVecs() {
|
|||
fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)")
|
||||
fmt.Println()
|
||||
// cosmos
|
||||
priv, _ := DerivePrivateKeyForPath(master, ch, FullFundraiserPath)
|
||||
fmt.Println(hex.EncodeToString(priv[:]))
|
||||
priv, err := DerivePrivateKeyForPath(master, ch, FullFundraiserPath)
|
||||
if err != nil {
|
||||
fmt.Println("INVALID")
|
||||
} else {
|
||||
fmt.Println(hex.EncodeToString(priv[:]))
|
||||
}
|
||||
// bitcoin
|
||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0")
|
||||
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[:]))
|
||||
}
|
||||
// ether
|
||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0")
|
||||
fmt.Println(hex.EncodeToString(priv[:]))
|
||||
priv, err = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0")
|
||||
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("keys generated via https://coinomi.com/recovery-phrase-tool.html")
|
||||
|
@ -121,6 +171,8 @@ func ExampleSomeBIP32TestVecs() {
|
|||
// bfcb217c058d8bbafd5e186eae936106ca3e943889b0b4a093ae13822fd3170c
|
||||
// e77c3de76965ad89997451de97b95bb65ede23a6bf185a55d80363d92ee37c3d
|
||||
// 7fc4d8a8146dea344ba04c593517d3f377fa6cded36cd55aee0a0bb968e651bc
|
||||
// INVALID
|
||||
// INVALID
|
||||
//
|
||||
// keys generated via https://coinomi.com/recovery-phrase-tool.html
|
||||
//
|
||||
|
|
|
@ -4,23 +4,23 @@ import (
|
|||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cosmos/go-bip39"
|
||||
"errors"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto"
|
||||
"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/types"
|
||||
|
||||
"github.com/cosmos/go-bip39"
|
||||
|
||||
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
||||
)
|
||||
|
||||
var _ Keybase = dbKeybase{}
|
||||
|
@ -30,6 +30,7 @@ var _ Keybase = dbKeybase{}
|
|||
// Find a list of all supported languages in the BIP 39 spec (word lists).
|
||||
type Language int
|
||||
|
||||
//noinspection ALL
|
||||
const (
|
||||
// English is the default language to create a mnemonic.
|
||||
// It is the only supported language by this package.
|
||||
|
@ -54,7 +55,7 @@ const (
|
|||
|
||||
const (
|
||||
// used for deriving seed from mnemonic
|
||||
defaultBIP39Passphrase = ""
|
||||
DefaultBIP39Passphrase = ""
|
||||
|
||||
// bits of entropy to draw when creating a mnemonic
|
||||
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
|
||||
// using the provided password.
|
||||
// It returns the generated mnemonic and the key Info.
|
||||
|
@ -109,41 +113,15 @@ func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string
|
|||
return
|
||||
}
|
||||
|
||||
seed := bip39.NewSeed(mnemonic, defaultBIP39Passphrase)
|
||||
seed := bip39.NewSeed(mnemonic, DefaultBIP39Passphrase)
|
||||
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
|
||||
return
|
||||
}
|
||||
|
||||
// TEMPORARY METHOD UNTIL WE FIGURE OUT USER FACING HD DERIVATION API
|
||||
func (kb dbKeybase) CreateKey(name, mnemonic, passwd string) (info Info, err error) {
|
||||
words := strings.Split(mnemonic, " ")
|
||||
if len(words) != 12 && len(words) != 24 {
|
||||
err = fmt.Errorf("recovering only works with 12 word (fundraiser) or 24 word mnemonics, got: %v words", len(words))
|
||||
return
|
||||
}
|
||||
seed, err := bip39.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
|
||||
// CreateAccount converts a mnemonic to a private key and persists it, encrypted with the given password.
|
||||
func (kb dbKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) {
|
||||
hdPath := hd.NewFundraiserParams(account, index)
|
||||
return kb.Derive(name, mnemonic, bip39Passwd, encryptPasswd, *hdPath)
|
||||
}
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String())
|
||||
|
||||
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String())
|
||||
return
|
||||
}
|
||||
|
||||
// CreateLedger creates a new locally-stored reference to a Ledger keypair
|
||||
// It returns the created key info and an error if the Ledger could not be queried
|
||||
func (kb dbKeybase) CreateLedger(name string, path crypto.DerivationPath, algo SigningAlgo) (Info, error) {
|
||||
func (kb dbKeybase) CreateLedger(name string, algo SigningAlgo, account uint32, index uint32) (Info, error) {
|
||||
if algo != Secp256k1 {
|
||||
return nil, ErrUnsupportedSigningAlgo
|
||||
}
|
||||
priv, err := crypto.NewPrivKeyLedgerSecp256k1(path)
|
||||
|
||||
hdPath := hd.NewFundraiserParams(account, index)
|
||||
priv, err := crypto.NewPrivKeyLedgerSecp256k1(*hdPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
// It returns the created key info
|
||||
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) {
|
||||
|
@ -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
|
||||
// else store the public key only
|
||||
if passwd != "" {
|
||||
info = kb.writeLocalKey(secp256k1.PrivKeySecp256k1(derivedPriv), name, passwd)
|
||||
info = kb.writeLocalKey(name, secp256k1.PrivKeySecp256k1(derivedPriv), passwd)
|
||||
} else {
|
||||
pubk := secp256k1.PrivKeySecp256k1(derivedPriv).PubKey()
|
||||
info = kb.writeOfflineKey(pubk, name)
|
||||
info = kb.writeOfflineKey(name, pubk)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -362,7 +343,7 @@ func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
kb.writeOfflineKey(pubKey, name)
|
||||
kb.writeOfflineKey(name, pubKey)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -410,10 +391,10 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kb.writeLocalKey(key, name, newpass)
|
||||
kb.writeLocalKey(name, key, newpass)
|
||||
return nil
|
||||
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()
|
||||
}
|
||||
|
||||
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
|
||||
privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase)
|
||||
// make Info
|
||||
pub := priv.PubKey()
|
||||
info := newLocalInfo(name, pub, privArmor)
|
||||
kb.writeInfo(info, name)
|
||||
kb.writeInfo(name, 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)
|
||||
kb.writeInfo(info, name)
|
||||
kb.writeInfo(name, 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)
|
||||
kb.writeInfo(info, name)
|
||||
kb.writeInfo(name, info)
|
||||
return info
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeInfo(info Info, name string) {
|
||||
func (kb dbKeybase) writeInfo(name string, info Info) {
|
||||
// write the info by key
|
||||
key := infoKey(name)
|
||||
kb.db.SetSync(key, writeInfo(info))
|
||||
|
|
|
@ -2,6 +2,7 @@ package keys
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"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/mintkey"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
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
|
||||
func TestKeyManagement(t *testing.T) {
|
||||
// make the storage with reasonable defaults
|
||||
db := dbm.NewMemDB()
|
||||
cstore := New(
|
||||
db,
|
||||
)
|
||||
cstore := New(db)
|
||||
|
||||
algo := Secp256k1
|
||||
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
|
||||
// signatures
|
||||
func TestSignVerify(t *testing.T) {
|
||||
cstore := New(
|
||||
dbm.NewMemDB(),
|
||||
)
|
||||
cstore := New(dbm.NewMemDB())
|
||||
algo := Secp256k1
|
||||
|
||||
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
|
||||
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.Equal(t, n2, newInfo.GetName())
|
||||
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 (
|
||||
"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/types"
|
||||
)
|
||||
|
@ -23,20 +21,20 @@ type Keybase interface {
|
|||
// CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic
|
||||
// key from that.
|
||||
CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error)
|
||||
// CreateKey takes a mnemonic and derives, a password. This method is temporary
|
||||
CreateKey(name, mnemonic, passwd string) (info Info, err error)
|
||||
// CreateFundraiserKey takes a mnemonic and derives, a password
|
||||
CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error)
|
||||
// Compute a BIP39 seed from th mnemonic and bip39Passwd.
|
||||
|
||||
// CreateAccount creates an account based using the BIP44 path (44'/118'/{account}'/0/{index}
|
||||
CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error)
|
||||
|
||||
// Derive computes a BIP39 seed from th mnemonic and bip39Passwd.
|
||||
// Derive private key from the seed using the BIP44 params.
|
||||
// Encrypt the key to disk using encryptPasswd.
|
||||
// See https://github.com/cosmos/cosmos-sdk/issues/2095
|
||||
Derive(name, mnemonic, bip39Passwd,
|
||||
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)
|
||||
Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, 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)
|
||||
|
||||
// The following operations will *only* work on locally-stored keys
|
||||
|
@ -46,10 +44,10 @@ type Keybase interface {
|
|||
Export(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)
|
||||
|
||||
// Close closes the database.
|
||||
// CloseDB closes the database.
|
||||
CloseDB()
|
||||
}
|
||||
|
||||
|
@ -123,12 +121,12 @@ func (i localInfo) GetAddress() types.AccAddress {
|
|||
|
||||
// ledgerInfo is the public information about a Ledger key
|
||||
type ledgerInfo struct {
|
||||
Name string `json:"name"`
|
||||
PubKey crypto.PubKey `json:"pubkey"`
|
||||
Path ccrypto.DerivationPath `json:"path"`
|
||||
Name string `json:"name"`
|
||||
PubKey crypto.PubKey `json:"pubkey"`
|
||||
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{
|
||||
Name: name,
|
||||
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
|
||||
|
||||
import (
|
||||
ledger "github.com/zondax/ledger-cosmos-go"
|
||||
)
|
||||
import ledger "github.com/zondax/ledger-cosmos-go"
|
||||
|
||||
// If ledger support (build tag) has been enabled, which implies a CGO dependency,
|
||||
// set the discoverLedger function which is responsible for loading the Ledger
|
|
@ -2,12 +2,15 @@ package crypto
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
secp256k1 "github.com/btcsuite/btcd/btcec"
|
||||
tmbtcec "github.com/tendermint/btcd/btcec"
|
||||
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -22,12 +25,10 @@ type (
|
|||
// dependencies when Ledger support is potentially not enabled.
|
||||
discoverLedgerFn func() (LedgerSECP256K1, error)
|
||||
|
||||
// DerivationPath represents a Ledger derivation path.
|
||||
DerivationPath []uint32
|
||||
|
||||
// LedgerSECP256K1 reflects an interface a Ledger API must implement for
|
||||
// the SECP256K1 scheme.
|
||||
LedgerSECP256K1 interface {
|
||||
Close() error
|
||||
GetPublicKeySECP256K1([]uint32) ([]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
|
||||
// ledger attached.
|
||||
CachedPubKey tmcrypto.PubKey
|
||||
Path DerivationPath
|
||||
ledger LedgerSECP256K1
|
||||
Path hd.BIP44Params
|
||||
}
|
||||
)
|
||||
|
||||
// NewPrivKeyLedgerSecp256k1 will generate a new key and store the public key
|
||||
// for later use.
|
||||
//
|
||||
// CONTRACT: The ledger device, ledgerDevice, must be loaded and set prior to
|
||||
// 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()
|
||||
func NewPrivKeyLedgerSecp256k1(path hd.BIP44Params) (tmcrypto.PrivKey, error) {
|
||||
device, err := getLedgerDevice()
|
||||
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 := pkl.getPubKey()
|
||||
pubKey, err := getPubKey(device, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pkl.CachedPubKey = pubKey
|
||||
return pkl, err
|
||||
return PrivKeyLedgerSecp256k1{pubKey, path}, nil
|
||||
}
|
||||
|
||||
// PubKey returns the cached public key.
|
||||
|
@ -75,21 +66,27 @@ func (pkl PrivKeyLedgerSecp256k1) PubKey() tmcrypto.PubKey {
|
|||
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
|
||||
// from disk.
|
||||
func (pkl PrivKeyLedgerSecp256k1) ValidateKey() error {
|
||||
// getPubKey will return an error if the ledger is not
|
||||
pub, err := pkl.getPubKey()
|
||||
device, err := getLedgerDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer warnIfErrors(device.Close)
|
||||
|
||||
// verify this matches cached address
|
||||
if !pub.Equals(pkl.CachedPubKey) {
|
||||
return fmt.Errorf("cached key does not match retrieved key")
|
||||
}
|
||||
|
||||
return nil
|
||||
return validateKey(device, pkl)
|
||||
}
|
||||
|
||||
// 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
|
||||
// refer to the same public key.
|
||||
func (pkl PrivKeyLedgerSecp256k1) Equals(other tmcrypto.PrivKey) bool {
|
||||
if ledger, ok := other.(*PrivKeyLedgerSecp256k1); ok {
|
||||
return pkl.CachedPubKey.Equals(ledger.CachedPubKey)
|
||||
if otherKey, ok := other.(PrivKeyLedgerSecp256k1); ok {
|
||||
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.
|
||||
|
@ -116,45 +156,37 @@ func (pkl PrivKeyLedgerSecp256k1) Equals(other tmcrypto.PrivKey) bool {
|
|||
// Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, returning
|
||||
// an error, so this should only trigger if the private key is held in memory
|
||||
// for a while before use.
|
||||
func (pkl PrivKeyLedgerSecp256k1) Sign(msg []byte) ([]byte, error) {
|
||||
sig, err := pkl.signLedgerSecp256k1(msg)
|
||||
func sign(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte) ([]byte, error) {
|
||||
err := validateKey(device, pkl)
|
||||
if err != nil {
|
||||
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
|
||||
// since this involves IO, it may return an error, which is not exposed
|
||||
// in the PubKey interface, so this function allows better error handling
|
||||
func (pkl PrivKeyLedgerSecp256k1) getPubKey() (key tmcrypto.PubKey, err error) {
|
||||
key, err = pkl.pubkeyLedgerSecp256k1()
|
||||
func getPubKey(device LedgerSECP256K1, path hd.BIP44Params) (tmcrypto.PubKey, error) {
|
||||
publicKey, err := device.GetPublicKeySECP256K1(path.DerivationPath())
|
||||
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
|
||||
cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256())
|
||||
cmp, err := btcec.ParsePubKey(publicKey[:], btcec.S256())
|
||||
if err != nil {
|
||||
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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"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/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) {
|
||||
if os.Getenv(ledgerEnabledEnv) == "" {
|
||||
t.Skip(fmt.Sprintf("Set '%s' to run code on a real ledger", ledgerEnabledEnv))
|
||||
}
|
||||
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}
|
||||
|
||||
msg := getFakeTx(50)
|
||||
path := *hd.NewFundraiserParams(0, 0)
|
||||
priv, err := NewPrivKeyLedgerSecp256k1(path)
|
||||
require.Nil(t, err, "%s", err)
|
||||
|
||||
|
@ -48,17 +153,3 @@ func TestRealLedgerSecp256k1(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Equal(t, pub, bpub)
|
||||
}
|
||||
|
||||
// TestRealLedgerErrorHandling calls. These tests assume
|
||||
// the ledger is not plugged in....
|
||||
func TestRealLedgerErrorHandling(t *testing.T) {
|
||||
if os.Getenv(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