commit
7c5c14f289
@ -3,7 +3,7 @@ version: 2
|
||||
defaults: &defaults
|
||||
working_directory: /go/src/github.com/cosmos/cosmos-sdk
|
||||
docker:
|
||||
- image: circleci/golang:1.10.3
|
||||
- image: circleci/golang:1.11.1
|
||||
environment:
|
||||
GOBIN: /tmp/workspace/bin
|
||||
|
||||
@ -39,14 +39,6 @@ jobs:
|
||||
paths:
|
||||
- bin
|
||||
- profiles
|
||||
- save_cache:
|
||||
key: v1-pkg-cache
|
||||
paths:
|
||||
- /go/pkg
|
||||
- save_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
paths:
|
||||
- /go/src/github.com/cosmos/cosmos-sdk
|
||||
|
||||
lint:
|
||||
<<: *defaults
|
||||
@ -54,14 +46,17 @@ jobs:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- checkout
|
||||
- run:
|
||||
name: dependencies
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make get_vendor_deps
|
||||
- run:
|
||||
name: Get metalinter
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make get_tools
|
||||
make get_dev_tools
|
||||
- run:
|
||||
name: Lint source
|
||||
@ -69,21 +64,24 @@ jobs:
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make test_lint
|
||||
|
||||
test_cli:
|
||||
integration_tests:
|
||||
<<: *defaults
|
||||
parallelism: 1
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- checkout
|
||||
- run:
|
||||
name: dependencies
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make get_vendor_deps
|
||||
- run:
|
||||
name: Test cli
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make test_cli
|
||||
make test_examples
|
||||
|
||||
test_sim_modules:
|
||||
<<: *defaults
|
||||
@ -91,10 +89,12 @@ jobs:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- checkout
|
||||
- run:
|
||||
name: dependencies
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make get_vendor_deps
|
||||
- run:
|
||||
name: Test individual module simulations
|
||||
command: |
|
||||
@ -107,10 +107,12 @@ jobs:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- checkout
|
||||
- run:
|
||||
name: dependencies
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make get_vendor_deps
|
||||
- run:
|
||||
name: Test individual module simulations
|
||||
command: |
|
||||
@ -123,32 +125,55 @@ jobs:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- checkout
|
||||
- run:
|
||||
name: dependencies
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make get_vendor_deps
|
||||
- run:
|
||||
name: Test full Gaia simulation
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make test_sim_gaia_fast
|
||||
|
||||
test_sim_gaia_multi_seed:
|
||||
<<: *defaults
|
||||
parallelism: 1
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- checkout
|
||||
- run:
|
||||
name: dependencies
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make get_vendor_deps
|
||||
- run:
|
||||
name: Test multi-seed Gaia simulation
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make test_sim_gaia_multi_seed
|
||||
|
||||
test_cover:
|
||||
<<: *defaults
|
||||
parallelism: 4
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- checkout
|
||||
- run:
|
||||
name: dependencies
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make get_vendor_deps
|
||||
- run: mkdir -p /tmp/logs
|
||||
- run:
|
||||
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"
|
||||
@ -166,8 +191,12 @@ jobs:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- checkout
|
||||
- run:
|
||||
name: dependencies
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make get_vendor_deps
|
||||
- run:
|
||||
name: gather
|
||||
command: |
|
||||
@ -196,6 +225,12 @@ jobs:
|
||||
- run:
|
||||
name: run localnet and exit on failure
|
||||
command: |
|
||||
pushd /tmp
|
||||
wget https://dl.google.com/go/go1.11.linux-amd64.tar.gz
|
||||
sudo tar -xvf go1.11.linux-amd64.tar.gz
|
||||
sudo rm -rf /usr/local/go
|
||||
sudo mv go /usr/local
|
||||
popd
|
||||
set -x
|
||||
make get_tools
|
||||
make get_vendor_deps
|
||||
@ -212,7 +247,7 @@ workflows:
|
||||
- lint:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- test_cli:
|
||||
- integration_tests:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- test_sim_modules:
|
||||
@ -224,6 +259,9 @@ workflows:
|
||||
- test_sim_gaia_fast:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- test_sim_gaia_multi_seed:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- test_cover:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
|
||||
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -4,14 +4,17 @@ v Before smashing the submit button please review the checkboxes.
|
||||
v If a checkbox is n/a - please still include it but + a little note why
|
||||
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > -->
|
||||
|
||||
- Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/cosmos-sdk/blob/develop/CONTRIBUTING.md#pr-targeting))
|
||||
|
||||
- [ ] Linked to github-issue with discussion and accepted design OR link to spec that describes this work.
|
||||
- [ ] Updated all relevant documentation (`docs/`)
|
||||
- [ ] Updated all relevant code comments
|
||||
- [ ] Wrote tests
|
||||
- [ ] Added entries in `PENDING.md` that include links to the relevant issue or PR that most accurately describes the change.
|
||||
- [ ] Updated `cmd/gaia` and `examples/`
|
||||
___________________________________
|
||||
- [ ] Updated relevant documentation (`docs/`)
|
||||
- [ ] Added entries in `PENDING.md` with issue #
|
||||
- [ ] rereviewed `Files changed` in the github PR explorer
|
||||
|
||||
______
|
||||
|
||||
For Admin Use:
|
||||
- [ ] Added appropriate labels to PR (ex. wip, ready-for-review, docs)
|
||||
- [ ] Reviewers Assigned
|
||||
- [ ] Squashed all commits, uses message "Merge pull request #XYZ: [title]" ([coding standards](https://github.com/tendermint/coding/blob/master/README.md#merging-a-pr))
|
||||
- Added appropriate labels to PR (ex. wip, ready-for-review, docs)
|
||||
- Reviewers Assigned
|
||||
- Squashed all commits, uses message "Merge pull request #XYZ: [title]" ([coding standards](https://github.com/tendermint/coding/blob/master/README.md#merging-a-pr))
|
||||
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@ -2,11 +2,15 @@
|
||||
.DS_Store
|
||||
*.swp
|
||||
*.swo
|
||||
*.swl
|
||||
*.swm
|
||||
*.swn
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
# Build
|
||||
vendor
|
||||
.vendor-new
|
||||
build
|
||||
tools/bin/*
|
||||
examples/build/*
|
||||
@ -16,6 +20,7 @@ docs/_build
|
||||
examples/basecoin/app/data
|
||||
baseapp/data/*
|
||||
client/lcd/keys/*
|
||||
client/lcd/statik/statik.go
|
||||
mytestnet
|
||||
|
||||
# Testing
|
||||
@ -34,3 +39,8 @@ vagrant
|
||||
|
||||
# Graphviz
|
||||
dependency-graph.png
|
||||
|
||||
# Latex
|
||||
*.aux
|
||||
*.out
|
||||
*.synctex.gz
|
||||
|
||||
345
CHANGELOG.md
345
CHANGELOG.md
@ -1,5 +1,250 @@
|
||||
# Changelog
|
||||
|
||||
## 0.25.0
|
||||
|
||||
*October 24th, 2018*
|
||||
|
||||
BREAKING CHANGES
|
||||
|
||||
* Gaia REST API (`gaiacli advanced rest-server`)
|
||||
* [x/stake] Validator.Owner renamed to Validator.Operator
|
||||
* [\#595](https://github.com/cosmos/cosmos-sdk/issues/595) Connections to the REST server are now secured using Transport Layer Security by default. The --insecure flag is provided to switch back to insecure HTTP.
|
||||
* [gaia-lite] [\#2258](https://github.com/cosmos/cosmos-sdk/issues/2258) Split `GET stake/delegators/{delegatorAddr}` into `GET stake/delegators/{delegatorAddr}/delegations`, `GET stake/delegators/{delegatorAddr}/unbonding_delegations` and `GET stake/delegators/{delegatorAddr}/redelegations`
|
||||
|
||||
* Gaia CLI (`gaiacli`)
|
||||
* [x/stake] Validator.Owner renamed to Validator.Operator
|
||||
* [cli] unsafe_reset_all, show_validator, and show_node_id have been renamed to unsafe-reset-all, show-validator, and show-node-id
|
||||
* [cli] [\#1983](https://github.com/cosmos/cosmos-sdk/issues/1983) --print-response now defaults to true in commands that create and send a transaction
|
||||
* [cli] [\#1983](https://github.com/cosmos/cosmos-sdk/issues/1983) you can now pass --pubkey or --address to gaiacli keys show to return a plaintext representation of the key's address or public key for use with other commands
|
||||
* [cli] [\#2061](https://github.com/cosmos/cosmos-sdk/issues/2061) changed proposalID in governance REST endpoints to proposal-id
|
||||
* [cli] [\#2014](https://github.com/cosmos/cosmos-sdk/issues/2014) `gaiacli advanced` no longer exists - to access `ibc`, `rest-server`, and `validator-set` commands use `gaiacli ibc`, `gaiacli rest-server`, and `gaiacli tendermint`, respectively
|
||||
* [makefile] `get_vendor_deps` no longer updates lock file it just updates vendor directory. Use `update_vendor_deps` to update the lock file. [#2152](https://github.com/cosmos/cosmos-sdk/pull/2152)
|
||||
* [cli] [\#2221](https://github.com/cosmos/cosmos-sdk/issues/2221) All commands that
|
||||
utilize a validator's operator address must now use the new Bech32 prefix,
|
||||
`cosmosvaloper`.
|
||||
* [cli] [\#2190](https://github.com/cosmos/cosmos-sdk/issues/2190) `gaiacli init --gen-txs` is now `gaiacli init --with-txs` to reduce confusion
|
||||
* [cli] [\#2073](https://github.com/cosmos/cosmos-sdk/issues/2073) --from can now be either an address or a key name
|
||||
* [cli] [\#1184](https://github.com/cosmos/cosmos-sdk/issues/1184) Subcommands reorganisation, see [\#2390](https://github.com/cosmos/cosmos-sdk/pull/2390) for a comprehensive list of changes.
|
||||
* [cli] [\#2524](https://github.com/cosmos/cosmos-sdk/issues/2524) Add support offline mode to `gaiacli tx sign`. Lookups are not performed if the flag `--offline` is on.
|
||||
* [cli] [\#2570](https://github.com/cosmos/cosmos-sdk/pull/2570) Add commands to query deposits on proposals
|
||||
|
||||
* Gaia
|
||||
* Make the transient store key use a distinct store key. [#2013](https://github.com/cosmos/cosmos-sdk/pull/2013)
|
||||
* [x/stake] [\#1901](https://github.com/cosmos/cosmos-sdk/issues/1901) Validator type's Owner field renamed to Operator; Validator's GetOwner() renamed accordingly to comply with the SDK's Validator interface.
|
||||
* [docs] [#2001](https://github.com/cosmos/cosmos-sdk/pull/2001) Update slashing spec for slashing period
|
||||
* [x/stake, x/slashing] [#1305](https://github.com/cosmos/cosmos-sdk/issues/1305) - Rename "revoked" to "jailed"
|
||||
* [x/stake] [#1676] Revoked and jailed validators put into the unbonding state
|
||||
* [x/stake] [#1877] Redelegations/unbonding-delegation from unbonding validator have reduced time
|
||||
* [x/slashing] [\#1789](https://github.com/cosmos/cosmos-sdk/issues/1789) Slashing changes for Tendermint validator set offset (NextValSet)
|
||||
* [x/stake] [\#2040](https://github.com/cosmos/cosmos-sdk/issues/2040) Validator
|
||||
operator type has now changed to `sdk.ValAddress`
|
||||
* [x/stake] [\#2221](https://github.com/cosmos/cosmos-sdk/issues/2221) New
|
||||
Bech32 prefixes have been introduced for a validator's consensus address and
|
||||
public key: `cosmosvalcons` and `cosmosvalconspub` respectively. Also, existing Bech32 prefixes have been
|
||||
renamed for accounts and validator operators:
|
||||
* `cosmosaccaddr` / `cosmosaccpub` => `cosmos` / `cosmospub`
|
||||
* `cosmosvaladdr` / `cosmosvalpub` => `cosmosvaloper` / `cosmosvaloperpub`
|
||||
* [x/stake] [#1013] TendermintUpdates now uses transient store
|
||||
* [x/stake] [\#2435](https://github.com/cosmos/cosmos-sdk/issues/2435) Remove empty bytes from the ValidatorPowerRank store key
|
||||
* [x/gov] [\#2195](https://github.com/cosmos/cosmos-sdk/issues/2195) Governance uses BFT Time
|
||||
* [x/gov] [\#2256](https://github.com/cosmos/cosmos-sdk/issues/2256) Removed slashing for governance non-voting validators
|
||||
* [simulation] [\#2162](https://github.com/cosmos/cosmos-sdk/issues/2162) Added back correct supply invariants
|
||||
* [x/slashing] [\#2430](https://github.com/cosmos/cosmos-sdk/issues/2430) Simulate more slashes, check if validator is jailed before jailing
|
||||
* [x/stake] [\#2393](https://github.com/cosmos/cosmos-sdk/issues/2393) Removed `CompleteUnbonding` and `CompleteRedelegation` Msg types, and instead added unbonding/redelegation queues to endblocker
|
||||
* [x/mock/simulation] [\#2501](https://github.com/cosmos/cosmos-sdk/issues/2501) Simulate transactions & invariants for fee distribution, and fix bugs discovered in the process
|
||||
* [x/auth] Simulate random fee payments
|
||||
* [cmd/gaia/app] Simulate non-zero inflation
|
||||
* [x/stake] Call hooks correctly in several cases related to delegation/validator updates
|
||||
* [x/stake] Check full supply invariants, including yet-to-be-withdrawn fees
|
||||
* [x/stake] Remove no-longer-in-use store key
|
||||
* [x/slashing] Call hooks correctly when a validator is slashed
|
||||
* [x/slashing] Truncate withdrawals (unbonding, redelegation) and burn change
|
||||
* [x/mock/simulation] Ensure the simulation cannot set a proposer address of nil
|
||||
* [x/mock/simulation] Add more event logs on begin block / end block for clarity
|
||||
* [x/mock/simulation] Correctly set validator power in abci.RequestBeginBlock
|
||||
* [x/minting] Correctly call stake keeper to track inflated supply
|
||||
* [x/distribution] Sanity check for nonexistent rewards
|
||||
* [x/distribution] Truncate withdrawals and return change to the community pool
|
||||
* [x/distribution] Add sanity checks for incorrect accum / total accum relations
|
||||
* [x/distribution] Correctly calculate total power using Tendermint updates
|
||||
* [x/distribution] Simulate withdrawal transactions
|
||||
* [x/distribution] Fix a bug where the fee pool was not correctly tracked on WithdrawDelegatorRewardsAll
|
||||
* [x/stake] [\#1673](https://github.com/cosmos/cosmos-sdk/issues/1673) Validators are no longer deleted until they can no longer possibly be slashed
|
||||
* [\#1890](https://github.com/cosmos/cosmos-sdk/issues/1890) Start chain with initial state + sequence of transactions
|
||||
* [cli] Rename `gaiad init gentx` to `gaiad gentx`.
|
||||
* [cli] Add `--skip-genesis` flag to `gaiad init` to prevent `genesis.json` generation.
|
||||
* Drop `GenesisTx` in favor of a signed `StdTx` with only one `MsgCreateValidator` message.
|
||||
* [cli] Port `gaiad init` and `gaiad testnet` to work with `StdTx` genesis transactions.
|
||||
* [cli] Add `--moniker` flag to `gaiad init` to override moniker when generating `genesis.json` - i.e. it takes effect when running with the `--with-txs` flag, it is ignored otherwise.
|
||||
|
||||
* SDK
|
||||
* [core] [\#2219](https://github.com/cosmos/cosmos-sdk/issues/2219) Update to Tendermint 0.24.0
|
||||
* Validator set updates delayed by one block
|
||||
* BFT timestamp that can safely be used by applications
|
||||
* Fixed maximum block size enforcement
|
||||
* [core] [\#1807](https://github.com/cosmos/cosmos-sdk/issues/1807) Switch from use of rational to decimal
|
||||
* [types] [\#1901](https://github.com/cosmos/cosmos-sdk/issues/1901) Validator interface's GetOwner() renamed to GetOperator()
|
||||
* [x/slashing] [#2122](https://github.com/cosmos/cosmos-sdk/pull/2122) - Implement slashing period
|
||||
* [types] [\#2119](https://github.com/cosmos/cosmos-sdk/issues/2119) Parsed error messages and ABCI log errors to make them more human readable.
|
||||
* [types] [\#2407](https://github.com/cosmos/cosmos-sdk/issues/2407) MulInt method added to big decimal in order to improve efficiency of slashing
|
||||
* [simulation] Rename TestAndRunTx to Operation [#2153](https://github.com/cosmos/cosmos-sdk/pull/2153)
|
||||
* [simulation] Remove log and testing.TB from Operation and Invariants, in favor of using errors [\#2282](https://github.com/cosmos/cosmos-sdk/issues/2282)
|
||||
* [simulation] Remove usage of keys and addrs in the types, in favor of simulation.Account [\#2384](https://github.com/cosmos/cosmos-sdk/issues/2384)
|
||||
* [tools] Removed gocyclo [#2211](https://github.com/cosmos/cosmos-sdk/issues/2211)
|
||||
* [baseapp] Remove `SetTxDecoder` in favor of requiring the decoder be set in baseapp initialization. [#1441](https://github.com/cosmos/cosmos-sdk/issues/1441)
|
||||
* [baseapp] [\#1921](https://github.com/cosmos/cosmos-sdk/issues/1921) Add minimumFees field to BaseApp.
|
||||
* [store] Change storeInfo within the root multistore to use tmhash instead of ripemd160 [\#2308](https://github.com/cosmos/cosmos-sdk/issues/2308)
|
||||
* [codec] [\#2324](https://github.com/cosmos/cosmos-sdk/issues/2324) All referrences to wire have been renamed to codec. Additionally, wire.NewCodec is now codec.New().
|
||||
* [types] [\#2343](https://github.com/cosmos/cosmos-sdk/issues/2343) Make sdk.Msg have a names field, to facilitate automatic tagging.
|
||||
* [baseapp] [\#2366](https://github.com/cosmos/cosmos-sdk/issues/2366) Automatically add action tags to all messages
|
||||
* [x/auth] [\#2377](https://github.com/cosmos/cosmos-sdk/issues/2377) auth.StdSignMsg -> txbuilder.StdSignMsg
|
||||
* [x/staking] [\#2244](https://github.com/cosmos/cosmos-sdk/issues/2244) staking now holds a consensus-address-index instead of a consensus-pubkey-index
|
||||
* [x/staking] [\#2236](https://github.com/cosmos/cosmos-sdk/issues/2236) more distribution hooks for distribution
|
||||
* [x/stake] [\#2394](https://github.com/cosmos/cosmos-sdk/issues/2394) Split up UpdateValidator into distinct state transitions applied only in EndBlock
|
||||
* [x/slashing] [\#2480](https://github.com/cosmos/cosmos-sdk/issues/2480) Fix signing info handling bugs & faulty slashing
|
||||
* [x/stake] [\#2412](https://github.com/cosmos/cosmos-sdk/issues/2412) Added an unbonding validator queue to EndBlock to automatically update validator.Status when finished Unbonding
|
||||
* [x/stake] [\#2500](https://github.com/cosmos/cosmos-sdk/issues/2500) Block conflicting redelegations until we add an index
|
||||
* [x/params] Global Paramstore refactored
|
||||
* [types] [\#2506](https://github.com/cosmos/cosmos-sdk/issues/2506) sdk.Dec MarshalJSON now marshals as a normal Decimal, with 10 digits of decimal precision
|
||||
* [x/stake] [\#2508](https://github.com/cosmos/cosmos-sdk/issues/2508) Utilize Tendermint power for validator power key
|
||||
* [x/stake] [\#2531](https://github.com/cosmos/cosmos-sdk/issues/2531) Remove all inflation logic
|
||||
* [x/mint] [\#2531](https://github.com/cosmos/cosmos-sdk/issues/2531) Add minting module and inflation logic
|
||||
* [x/auth] [\#2540](https://github.com/cosmos/cosmos-sdk/issues/2540) Rename `AccountMapper` to `AccountKeeper`.
|
||||
* [types] [\#2456](https://github.com/cosmos/cosmos-sdk/issues/2456) Renamed msg.Name() and msg.Type() to msg.Type() and msg.Route() respectively
|
||||
|
||||
* Tendermint
|
||||
* Update tendermint version from v0.23.0 to v0.25.0, notable changes
|
||||
* Mempool now won't build too large blocks, or too computationally expensive blocks
|
||||
* Maximum tx sizes and gas are now removed, and are implicitly the blocks maximums
|
||||
* ABCI validators no longer send the pubkey. The pubkey is only sent in validator updates
|
||||
* Validator set changes are now delayed by one block
|
||||
* Block header now includes the next validator sets hash
|
||||
* BFT time is implemented
|
||||
* Secp256k1 signature format has changed
|
||||
* There is now a threshold multisig format
|
||||
* See the [tendermint changelog](https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md) for other changes.
|
||||
|
||||
FEATURES
|
||||
|
||||
* Gaia REST API (`gaiacli advanced rest-server`)
|
||||
* [gaia-lite] Endpoints to query staking pool and params
|
||||
* [gaia-lite] [\#2110](https://github.com/cosmos/cosmos-sdk/issues/2110) Add support for `simulate=true` requests query argument to endpoints that send txs to run simulations of transactions
|
||||
* [gaia-lite] [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add support for `generate_only=true` query argument to generate offline unsigned transactions
|
||||
* [gaia-lite] [\#1953](https://github.com/cosmos/cosmos-sdk/issues/1953) Add /sign endpoint to sign transactions generated with `generate_only=true`.
|
||||
* [gaia-lite] [\#1954](https://github.com/cosmos/cosmos-sdk/issues/1954) Add /broadcast endpoint to broadcast transactions signed by the /sign endpoint.
|
||||
* [gaia-lite] [\#2113](https://github.com/cosmos/cosmos-sdk/issues/2113) Rename `/accounts/{address}/send` to `/bank/accounts/{address}/transfers`, rename `/accounts/{address}` to `/auth/accounts/{address}`, replace `proposal-id` with `proposalId` in all gov endpoints
|
||||
* [gaia-lite] [\#2478](https://github.com/cosmos/cosmos-sdk/issues/2478) Add query gov proposal's deposits endpoint
|
||||
* [gaia-lite] [\#2477](https://github.com/cosmos/cosmos-sdk/issues/2477) Add query validator's outgoing redelegations and unbonding delegations endpoints
|
||||
|
||||
* Gaia CLI (`gaiacli`)
|
||||
* [cli] Cmds to query staking pool and params
|
||||
* [gov][cli] [\#2062](https://github.com/cosmos/cosmos-sdk/issues/2062) added `--proposal` flag to `submit-proposal` that allows a JSON file containing a proposal to be passed in
|
||||
* [\#2040](https://github.com/cosmos/cosmos-sdk/issues/2040) Add `--bech` to `gaiacli keys show` and respective REST endpoint to
|
||||
provide desired Bech32 prefix encoding
|
||||
* [cli] [\#2047](https://github.com/cosmos/cosmos-sdk/issues/2047) [\#2306](https://github.com/cosmos/cosmos-sdk/pull/2306) Passing --gas=simulate triggers a simulation of the tx before the actual execution.
|
||||
The gas estimate obtained via the simulation will be used as gas limit in the actual execution.
|
||||
* [cli] [\#2047](https://github.com/cosmos/cosmos-sdk/issues/2047) The --gas-adjustment flag can be used to adjust the estimate obtained via the simulation triggered by --gas=simulate.
|
||||
* [cli] [\#2110](https://github.com/cosmos/cosmos-sdk/issues/2110) Add --dry-run flag to perform a simulation of a transaction without broadcasting it. The --gas flag is ignored as gas would be automatically estimated.
|
||||
* [cli] [\#2204](https://github.com/cosmos/cosmos-sdk/issues/2204) Support generating and broadcasting messages with multiple signatures via command line:
|
||||
* [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add --generate-only flag to build an unsigned transaction and write it to STDOUT.
|
||||
* [\#1953](https://github.com/cosmos/cosmos-sdk/issues/1953) New `sign` command to sign transactions generated with the --generate-only flag.
|
||||
* [\#1954](https://github.com/cosmos/cosmos-sdk/issues/1954) New `broadcast` command to broadcast transactions generated offline and signed with the `sign` command.
|
||||
* [cli] [\#2220](https://github.com/cosmos/cosmos-sdk/issues/2220) Add `gaiacli config` feature to interactively create CLI config files to reduce the number of required flags
|
||||
* [stake][cli] [\#1672](https://github.com/cosmos/cosmos-sdk/issues/1672) Introduced
|
||||
new commission flags for validator commands `create-validator` and `edit-validator`.
|
||||
* [stake][cli] [\#1890](https://github.com/cosmos/cosmos-sdk/issues/1890) Add `--genesis-format` flag to `gaiacli tx create-validator` to produce transactions in genesis-friendly format.
|
||||
* [cli][\#2554](https://github.com/cosmos/cosmos-sdk/issues/2554) Make `gaiacli keys show` multisig ready.
|
||||
|
||||
* Gaia
|
||||
* [cli] [\#2170](https://github.com/cosmos/cosmos-sdk/issues/2170) added ability to show the node's address via `gaiad tendermint show-address`
|
||||
* [simulation] [\#2313](https://github.com/cosmos/cosmos-sdk/issues/2313) Reworked `make test_sim_gaia_slow` to `make test_sim_gaia_full`, now simulates from multiple starting seeds in parallel
|
||||
* [cli] [\#1921] (https://github.com/cosmos/cosmos-sdk/issues/1921)
|
||||
* New configuration file `gaiad.toml` is now created to host Gaia-specific configuration.
|
||||
* New --minimum_fees/minimum_fees flag/config option to set a minimum fee.
|
||||
|
||||
* SDK
|
||||
* [querier] added custom querier functionality, so ABCI query requests can be handled by keepers
|
||||
* [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) allow operations to specify future operations
|
||||
* [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) Add benchmarking capabilities, with makefile commands "test_sim_gaia_benchmark, test_sim_gaia_profile"
|
||||
* [simulation] [\#2349](https://github.com/cosmos/cosmos-sdk/issues/2349) Add time-based future scheduled operations to simulator
|
||||
* [x/auth] [\#2376](https://github.com/cosmos/cosmos-sdk/issues/2376) Remove FeePayer() from StdTx
|
||||
* [x/stake] [\#1672](https://github.com/cosmos/cosmos-sdk/issues/1672) Implement
|
||||
basis for the validator commission model.
|
||||
* [x/auth] Support account removal in the account mapper.
|
||||
|
||||
|
||||
IMPROVEMENTS
|
||||
* [tools] Improved terraform and ansible scripts for infrastructure deployment
|
||||
* [tools] Added ansible script to enable process core dumps
|
||||
|
||||
* Gaia REST API (`gaiacli advanced rest-server`)
|
||||
* [x/stake] [\#2000](https://github.com/cosmos/cosmos-sdk/issues/2000) Added tests for new staking endpoints
|
||||
* [gaia-lite] [\#2445](https://github.com/cosmos/cosmos-sdk/issues/2445) Standarized REST error responses
|
||||
* [gaia-lite] Added example to Swagger specification for /keys/seed.
|
||||
* [x/stake] Refactor REST utils
|
||||
|
||||
* Gaia CLI (`gaiacli`)
|
||||
* [cli] [\#2060](https://github.com/cosmos/cosmos-sdk/issues/2060) removed `--select` from `block` command
|
||||
* [cli] [\#2128](https://github.com/cosmos/cosmos-sdk/issues/2128) fixed segfault when exporting directly after `gaiad init`
|
||||
* [cli] [\#1255](https://github.com/cosmos/cosmos-sdk/issues/1255) open KeyBase in read-only mode
|
||||
for query-purpose CLI commands
|
||||
|
||||
* Gaia
|
||||
* [x/stake] [#2023](https://github.com/cosmos/cosmos-sdk/pull/2023) Terminate iteration loop in `UpdateBondedValidators` and `UpdateBondedValidatorsFull` when the first revoked validator is encountered and perform a sanity check.
|
||||
* [x/auth] Signature verification's gas cost now accounts for pubkey type. [#2046](https://github.com/tendermint/tendermint/pull/2046)
|
||||
* [x/stake] [x/slashing] Ensure delegation invariants to jailed validators [#1883](https://github.com/cosmos/cosmos-sdk/issues/1883).
|
||||
* [x/stake] Improve speed of GetValidator, which was shown to be a performance bottleneck. [#2046](https://github.com/tendermint/tendermint/pull/2200)
|
||||
* [x/stake] [\#2435](https://github.com/cosmos/cosmos-sdk/issues/2435) Improve memory efficiency of getting the various store keys
|
||||
* [genesis] [\#2229](https://github.com/cosmos/cosmos-sdk/issues/2229) Ensure that there are no duplicate accounts or validators in the genesis state.
|
||||
* [genesis] [\#2450](https://github.com/cosmos/cosmos-sdk/issues/2450) Validate staking genesis parameters.
|
||||
* Add SDK validation to `config.toml` (namely disabling `create_empty_blocks`) [\#1571](https://github.com/cosmos/cosmos-sdk/issues/1571)
|
||||
* [\#1941](https://github.com/cosmos/cosmos-sdk/issues/1941)(https://github.com/cosmos/cosmos-sdk/issues/1941) Version is now inferred via `git describe --tags`.
|
||||
* [x/distribution] [\#1671](https://github.com/cosmos/cosmos-sdk/issues/1671) add distribution types and tests
|
||||
|
||||
* SDK
|
||||
* [tools] Make get_vendor_deps deletes `.vendor-new` directories, in case scratch files are present.
|
||||
* [spec] Added simple piggy bank distribution spec
|
||||
* [cli] [\#1632](https://github.com/cosmos/cosmos-sdk/issues/1632) Add integration tests to ensure `basecoind init && basecoind` start sequences run successfully for both `democoin` and `basecoin` examples.
|
||||
* [store] Speedup IAVL iteration, and consequently everything that requires IAVL iteration. [#2143](https://github.com/cosmos/cosmos-sdk/issues/2143)
|
||||
* [store] [\#1952](https://github.com/cosmos/cosmos-sdk/issues/1952), [\#2281](https://github.com/cosmos/cosmos-sdk/issues/2281) Update IAVL dependency to v0.11.0
|
||||
* [simulation] Make timestamps randomized [#2153](https://github.com/cosmos/cosmos-sdk/pull/2153)
|
||||
* [simulation] Make logs not just pure strings, speeding it up by a large factor at greater block heights [\#2282](https://github.com/cosmos/cosmos-sdk/issues/2282)
|
||||
* [simulation] Add a concept of weighting the operations [\#2303](https://github.com/cosmos/cosmos-sdk/issues/2303)
|
||||
* [simulation] Logs get written to file if large, and also get printed on panics [\#2285](https://github.com/cosmos/cosmos-sdk/issues/2285)
|
||||
* [simulation] Bank simulations now makes testing auth configurable [\#2425](https://github.com/cosmos/cosmos-sdk/issues/2425)
|
||||
* [gaiad] [\#1992](https://github.com/cosmos/cosmos-sdk/issues/1992) Add optional flag to `gaiad testnet` to make config directory of daemon (default `gaiad`) and cli (default `gaiacli`) configurable
|
||||
* [x/stake] Add stake `Queriers` for Gaia-lite endpoints. This increases the staking endpoints performance by reusing the staking `keeper` logic for queries. [#2249](https://github.com/cosmos/cosmos-sdk/pull/2149)
|
||||
* [store] [\#2017](https://github.com/cosmos/cosmos-sdk/issues/2017) Refactor
|
||||
gas iterator gas consumption to only consume gas for iterator creation and `Next`
|
||||
calls which includes dynamic consumption of value length.
|
||||
* [types/decimal] [\#2378](https://github.com/cosmos/cosmos-sdk/issues/2378) - Added truncate functionality to decimal
|
||||
* [client] [\#1184](https://github.com/cosmos/cosmos-sdk/issues/1184) Remove unused `client/tx/sign.go`.
|
||||
* [tools] [\#2464](https://github.com/cosmos/cosmos-sdk/issues/2464) Lock binary dependencies to a specific version
|
||||
* #2573 [x/distribution] add accum invariance
|
||||
|
||||
BUG FIXES
|
||||
|
||||
* Gaia CLI (`gaiacli`)
|
||||
* [cli] [\#1997](https://github.com/cosmos/cosmos-sdk/issues/1997) Handle panics gracefully when `gaiacli stake {delegation,unbond}` fail to unmarshal delegation.
|
||||
* [cli] [\#2265](https://github.com/cosmos/cosmos-sdk/issues/2265) Fix JSON formatting of the `gaiacli send` command.
|
||||
* [cli] [\#2547](https://github.com/cosmos/cosmos-sdk/issues/2547) Mark --to and --amount as required flags for `gaiacli tx send`.
|
||||
|
||||
* Gaia
|
||||
* [x/stake] Return correct Tendermint validator update set on `EndBlocker` by not
|
||||
including non previously bonded validators that have zero power. [#2189](https://github.com/cosmos/cosmos-sdk/issues/2189)
|
||||
|
||||
* SDK
|
||||
* [\#1988](https://github.com/cosmos/cosmos-sdk/issues/1988) Make us compile on OpenBSD (disable ledger) [#1988] (https://github.com/cosmos/cosmos-sdk/issues/1988)
|
||||
* [\#2105](https://github.com/cosmos/cosmos-sdk/issues/2105) Fix DB Iterator leak, which may leak a go routine.
|
||||
* [ledger] [\#2064](https://github.com/cosmos/cosmos-sdk/issues/2064) Fix inability to sign and send transactions via the LCD by
|
||||
loading a Ledger device at runtime.
|
||||
* [\#2158](https://github.com/cosmos/cosmos-sdk/issues/2158) Fix non-deterministic ordering of validator iteration when slashing in `gov EndBlocker`
|
||||
* [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) Make simulation stop on SIGTERM
|
||||
* [\#2388](https://github.com/cosmos/cosmos-sdk/issues/2388) Remove dependency on deprecated tendermint/tmlibs repository.
|
||||
* [\#2416](https://github.com/cosmos/cosmos-sdk/issues/2416) Refactored `InitializeTestLCD` to properly include proposing validator in genesis state.
|
||||
* #2573 [x/distribution] accum invariance bugfix
|
||||
* #2573 [x/slashing] unbonding-delegation slashing invariance bugfix
|
||||
|
||||
## 0.24.2
|
||||
|
||||
*August 22nd, 2018*
|
||||
@ -7,7 +252,7 @@
|
||||
BUG FIXES
|
||||
|
||||
* Tendermint
|
||||
- Fix unbounded consensus WAL growth
|
||||
- Fix unbounded consensus WAL growth
|
||||
|
||||
## 0.24.1
|
||||
|
||||
@ -25,36 +270,36 @@ BUG FIXES
|
||||
BREAKING CHANGES
|
||||
|
||||
* Gaia REST API (`gaiacli advanced rest-server`)
|
||||
- [x/stake] \#1880 More REST-ful endpoints (large refactor)
|
||||
- [x/slashing] \#1866 `/slashing/signing_info` takes cosmosvalpub instead of cosmosvaladdr
|
||||
- [x/stake] [\#1880](https://github.com/cosmos/cosmos-sdk/issues/1880) More REST-ful endpoints (large refactor)
|
||||
- [x/slashing] [\#1866](https://github.com/cosmos/cosmos-sdk/issues/1866) `/slashing/signing_info` takes cosmosvalpub instead of cosmosvaladdr
|
||||
- use time.Time instead of int64 for time. See Tendermint v0.23.0
|
||||
- Signatures are no longer Amino encoded with prefixes (just encoded as raw
|
||||
bytes) - see Tendermint v0.23.0
|
||||
|
||||
* Gaia CLI (`gaiacli`)
|
||||
- [x/stake] change `--keybase-sig` to `--identity`
|
||||
- [x/stake] \#1828 Force user to specify amount on create-validator command by removing default
|
||||
- [x/stake] [\#1828](https://github.com/cosmos/cosmos-sdk/issues/1828) Force user to specify amount on create-validator command by removing default
|
||||
- [x/gov] Change `--proposalID` to `--proposal-id`
|
||||
- [x/stake, x/gov] \#1606 Use `--from` instead of adhoc flags like `--address-validator`
|
||||
- [x/stake, x/gov] [\#1606](https://github.com/cosmos/cosmos-sdk/issues/1606) Use `--from` instead of adhoc flags like `--address-validator`
|
||||
and `--proposer` to indicate the sender address.
|
||||
- \#1551 Remove `--name` completely
|
||||
- [\#1551](https://github.com/cosmos/cosmos-sdk/issues/1551) Remove `--name` completely
|
||||
- Genesis/key creation (`gaiad init`) now supports user-provided key passwords
|
||||
|
||||
* Gaia
|
||||
- [x/stake] Inflation doesn't use rationals in calculation (performance boost)
|
||||
- [x/stake] Persist a map from `addr->pubkey` in the state since BeginBlock
|
||||
doesn't provide pubkeys.
|
||||
- [x/gov] \#1781 Added tags sub-package, changed tags to use dash-case
|
||||
- [x/gov] \#1688 Governance parameters are now stored in globalparams store
|
||||
- [x/gov] \#1859 Slash validators who do not vote on a proposal
|
||||
- [x/gov] \#1914 added TallyResult type that gets stored in Proposal after tallying is finished
|
||||
- [x/gov] [\#1781](https://github.com/cosmos/cosmos-sdk/issues/1781) Added tags sub-package, changed tags to use dash-case
|
||||
- [x/gov] [\#1688](https://github.com/cosmos/cosmos-sdk/issues/1688) Governance parameters are now stored in globalparams store
|
||||
- [x/gov] [\#1859](https://github.com/cosmos/cosmos-sdk/issues/1859) Slash validators who do not vote on a proposal
|
||||
- [x/gov] [\#1914](https://github.com/cosmos/cosmos-sdk/issues/1914) added TallyResult type that gets stored in Proposal after tallying is finished
|
||||
|
||||
* SDK
|
||||
- [baseapp] Msgs are no longer run on CheckTx, removed `ctx.IsCheckTx()`
|
||||
- [baseapp] NewBaseApp constructor takes sdk.TxDecoder as argument instead of wire.Codec
|
||||
- [types] sdk.NewCoin takes sdk.Int, sdk.NewInt64Coin takes int64
|
||||
- [x/auth] Default TxDecoder can be found in `x/auth` rather than baseapp
|
||||
- [client] \#1551: Refactored `CoreContext` to `TxContext` and `QueryContext`
|
||||
- [client] [\#1551](https://github.com/cosmos/cosmos-sdk/issues/1551): Refactored `CoreContext` to `TxContext` and `QueryContext`
|
||||
- Removed all tx related fields and logic (building & signing) to separate
|
||||
structure `TxContext` in `x/auth/client/context`
|
||||
|
||||
@ -74,7 +319,7 @@ FEATURES
|
||||
|
||||
* Gaia CLI (`gaiacli`)
|
||||
- [x/gov] added `query-proposals` command. Can filter by `depositer`, `voter`, and `status`
|
||||
- [x/stake] \#2043 Added staking query cli cmds for unbonding-delegations and redelegations
|
||||
- [x/stake] [\#2043](https://github.com/cosmos/cosmos-sdk/issues/2043) Added staking query cli cmds for unbonding-delegations and redelegations
|
||||
|
||||
* Gaia
|
||||
- [networks] Added ansible scripts to upgrade seed nodes on a network
|
||||
@ -87,7 +332,7 @@ FEATURES
|
||||
- Simulates Tendermint's algorithm for validator set updates
|
||||
- Simulates validator signing/downtime with a Markov chain, and occaisional double-signatures
|
||||
- Includes simulated operations & invariants for staking, slashing, governance, and bank modules
|
||||
- [store] \#1481 Add transient store
|
||||
- [store] [\#1481](https://github.com/cosmos/cosmos-sdk/issues/1481) Add transient store
|
||||
- [baseapp] Initialize validator set on ResponseInitChain
|
||||
- [baseapp] added BaseApp.Seal - ability to seal baseapp parameters once they've been set
|
||||
- [cosmos-sdk-cli] New `cosmos-sdk-cli` tool to quickly initialize a new
|
||||
@ -97,39 +342,41 @@ FEATURES
|
||||
IMPROVEMENTS
|
||||
|
||||
* Gaia
|
||||
- [spec] \#967 Inflation and distribution specs drastically improved
|
||||
- [x/gov] \#1773 Votes on a proposal can now be queried
|
||||
- [spec] [\#967](https://github.com/cosmos/cosmos-sdk/issues/967) Inflation and distribution specs drastically improved
|
||||
- [x/gov] [\#1773](https://github.com/cosmos/cosmos-sdk/issues/1773) Votes on a proposal can now be queried
|
||||
- [x/gov] Initial governance parameters can now be set in the genesis file
|
||||
- [x/stake] \#1815 Sped up the processing of `EditValidator` txs.
|
||||
- [config] \#1930 Transactions indexer indexes all tags by default.
|
||||
- [x/stake] [\#1815](https://github.com/cosmos/cosmos-sdk/issues/1815) Sped up the processing of `EditValidator` txs.
|
||||
- [config] [\#1930](https://github.com/cosmos/cosmos-sdk/issues/1930) Transactions indexer indexes all tags by default.
|
||||
- [ci] [#2057](https://github.com/cosmos/cosmos-sdk/pull/2057) Run `make localnet-start` on every commit and ensure network reaches at least 10 blocks
|
||||
|
||||
* SDK
|
||||
- [baseapp] \#1587 Allow any alphanumeric character in route
|
||||
- [baseapp] [\#1587](https://github.com/cosmos/cosmos-sdk/issues/1587) Allow any alphanumeric character in route
|
||||
- [baseapp] Allow any alphanumeric character in route
|
||||
- [tools] Remove `rm -rf vendor/` from `make get_vendor_deps`
|
||||
- [x/auth] Recover ErrorOutOfGas panic in order to set sdk.Result attributes correctly
|
||||
- [x/auth] [\#2376](https://github.com/cosmos/cosmos-sdk/issues/2376) No longer runs any signature in a multi-msg, if any account/sequence number is wrong.
|
||||
- [x/auth] [\#2376](https://github.com/cosmos/cosmos-sdk/issues/2376) No longer charge gas for subtracting fees
|
||||
- [x/bank] Unit tests are now table-driven
|
||||
- [tests] Add tests to example apps in docs
|
||||
- [tests] Fixes ansible scripts to work with AWS too
|
||||
- [tests] \#1806 CLI tests are now behind the build flag 'cli_test', so go test works on a new repo
|
||||
- [tests] [\#1806](https://github.com/cosmos/cosmos-sdk/issues/1806) CLI tests are now behind the build flag 'cli_test', so go test works on a new repo
|
||||
|
||||
BUG FIXES
|
||||
|
||||
* Gaia CLI (`gaiacli`)
|
||||
- \#1766 Fixes bad example for keybase identity
|
||||
- [x/stake] \#2021 Fixed repeated CLI commands in staking
|
||||
- [\#1766](https://github.com/cosmos/cosmos-sdk/issues/1766) Fixes bad example for keybase identity
|
||||
- [x/stake] [\#2021](https://github.com/cosmos/cosmos-sdk/issues/2021) Fixed repeated CLI commands in staking
|
||||
|
||||
* Gaia
|
||||
- [x/stake] [#2077](https://github.com/cosmos/cosmos-sdk/pull/2077) Fixed invalid cliff power comparison
|
||||
- \#1804 Fixes gen-tx genesis generation logic temporarily until upstream updates
|
||||
- \#1799 Fix `gaiad export`
|
||||
- \#1839 Fixed bug where intra-tx counter wasn't set correctly for genesis validators
|
||||
- [x/stake] \#1858 Fixed bug where the cliff validator was not updated correctly
|
||||
- [tests] \#1675 Fix non-deterministic `test_cover`
|
||||
- [tests] \#1551 Fixed invalid LCD test JSON payload in `doIBCTransfer`
|
||||
- [\#1804](https://github.com/cosmos/cosmos-sdk/issues/1804) Fixes gen-tx genesis generation logic temporarily until upstream updates
|
||||
- [\#1799](https://github.com/cosmos/cosmos-sdk/issues/1799) Fix `gaiad export`
|
||||
- [\#1839](https://github.com/cosmos/cosmos-sdk/issues/1839) Fixed bug where intra-tx counter wasn't set correctly for genesis validators
|
||||
- [x/stake] [\#1858](https://github.com/cosmos/cosmos-sdk/issues/1858) Fixed bug where the cliff validator was not updated correctly
|
||||
- [tests] [\#1675](https://github.com/cosmos/cosmos-sdk/issues/1675) Fix non-deterministic `test_cover`
|
||||
- [tests] [\#1551](https://github.com/cosmos/cosmos-sdk/issues/1551) Fixed invalid LCD test JSON payload in `doIBCTransfer`
|
||||
- [basecoin] Fixes coin transaction failure and account query [discussion](https://forum.cosmos.network/t/unmarshalbinarybare-expected-to-read-prefix-bytes-75fbfab8-since-it-is-registered-concrete-but-got-0a141dfa/664/6)
|
||||
- [x/gov] \#1757 Fix VoteOption conversion to String
|
||||
- [x/gov] [\#1757](https://github.com/cosmos/cosmos-sdk/issues/1757) Fix VoteOption conversion to String
|
||||
* [x/stake] [#2083] Fix broken invariant of bonded validator power decrease
|
||||
|
||||
## 0.23.1
|
||||
@ -157,9 +404,9 @@ IMPROVEMENTS
|
||||
BUG FIXES
|
||||
* [tendermint] Update to v0.22.6
|
||||
- Fixes some security vulnerabilities reported in the [Bug Bounty](https://hackerone.com/tendermint)
|
||||
* \#1797 Fix off-by-one error in slashing for downtime
|
||||
* \#1787 Fixed bug where Tally fails due to revoked/unbonding validator
|
||||
* \#1666 Add intra-tx counter to the genesis validators
|
||||
* [\#1797](https://github.com/cosmos/cosmos-sdk/issues/1797) Fix off-by-one error in slashing for downtime
|
||||
* [\#1787](https://github.com/cosmos/cosmos-sdk/issues/1787) Fixed bug where Tally fails due to revoked/unbonding validator
|
||||
* [\#1666](https://github.com/cosmos/cosmos-sdk/issues/1666) Add intra-tx counter to the genesis validators
|
||||
|
||||
## 0.22.0
|
||||
|
||||
@ -205,8 +452,8 @@ IMPROVEMENTS
|
||||
* [store] Pruning strategy configurable with pruning flag on gaiad start
|
||||
|
||||
BUG FIXES
|
||||
* \#1630 - redelegation nolonger removes tokens from the delegator liquid account
|
||||
* [keys] \#1629 - updating password no longer asks for a new password when the first entered password was incorrect
|
||||
* [\#1630](https://github.com/cosmos/cosmos-sdk/issues/1630) - redelegation nolonger removes tokens from the delegator liquid account
|
||||
* [keys] [\#1629](https://github.com/cosmos/cosmos-sdk/issues/1629) - updating password no longer asks for a new password when the first entered password was incorrect
|
||||
* [lcd] importing an account would create a random account
|
||||
* [server] 'gaiad init' command family now writes provided name as the moniker in `config.toml`
|
||||
* [build] Added Ledger build support via `LEDGER_ENABLED=true|false`
|
||||
@ -322,9 +569,9 @@ IMPROVEMENTS
|
||||
* [docs] Added commands for governance CLI on testnet README
|
||||
|
||||
BUG FIXES
|
||||
* [x/slashing] \#1510 Unrevoked validators cannot un-revoke themselves
|
||||
* [x/stake] \#1513 Validators slashed to zero power are unbonded and removed from the store
|
||||
* [x/stake] \#1567 Validators decreased in power but not unbonded are now updated in Tendermint
|
||||
* [x/slashing] [\#1510](https://github.com/cosmos/cosmos-sdk/issues/1510) Unrevoked validators cannot un-revoke themselves
|
||||
* [x/stake] [\#1513](https://github.com/cosmos/cosmos-sdk/issues/1513) Validators slashed to zero power are unbonded and removed from the store
|
||||
* [x/stake] [\#1567](https://github.com/cosmos/cosmos-sdk/issues/1567) Validators decreased in power but not unbonded are now updated in Tendermint
|
||||
* [x/stake] error strings lower case
|
||||
* [x/stake] pool loose tokens now accounts for unbonding and unbonding tokens not associated with any validator
|
||||
* [x/stake] fix revoke bytes ordering (was putting revoked candidates at the top of the list)
|
||||
@ -334,20 +581,20 @@ BUG FIXES
|
||||
* Retry on HTTP request failure in CLI tests, add option to retry tests in Makefile
|
||||
* Fixed bug where chain ID wasn't passed properly in x/bank REST handler, removed Viper hack from ante handler
|
||||
* Fixed bug where `democli account` didn't decode the account data correctly
|
||||
* \#872 - recovery phrases no longer all end in `abandon`
|
||||
* \#887 - limit the size of rationals that can be passed in from user input
|
||||
* \#1052 - Make all now works
|
||||
* \#1258 - printing big.rat's can no longer overflow int64
|
||||
* \#1259 - fix bug where certain tests that could have a nil pointer in defer
|
||||
* \#1343 - fixed unnecessary parallelism in CI
|
||||
* \#1353 - CLI: Show pool shares fractions in human-readable format
|
||||
* \#1367 - set ChainID in InitChain
|
||||
* \#1461 - CLI tests now no longer reset your local environment data
|
||||
* \#1505 - `gaiacli stake validator` no longer panics if validator doesn't exist
|
||||
* \#1565 - fix cliff validator persisting when validator set shrinks from max
|
||||
* \#1287 - prevent zero power validators at genesis
|
||||
* [\#872](https://github.com/cosmos/cosmos-sdk/issues/872) - recovery phrases no longer all end in `abandon`
|
||||
* [\#887](https://github.com/cosmos/cosmos-sdk/issues/887) - limit the size of rationals that can be passed in from user input
|
||||
* [\#1052](https://github.com/cosmos/cosmos-sdk/issues/1052) - Make all now works
|
||||
* [\#1258](https://github.com/cosmos/cosmos-sdk/issues/1258) - printing big.rat's can no longer overflow int64
|
||||
* [\#1259](https://github.com/cosmos/cosmos-sdk/issues/1259) - fix bug where certain tests that could have a nil pointer in defer
|
||||
* [\#1343](https://github.com/cosmos/cosmos-sdk/issues/1343) - fixed unnecessary parallelism in CI
|
||||
* [\#1353](https://github.com/cosmos/cosmos-sdk/issues/1353) - CLI: Show pool shares fractions in human-readable format
|
||||
* [\#1367](https://github.com/cosmos/cosmos-sdk/issues/1367) - set ChainID in InitChain
|
||||
* [\#1461](https://github.com/cosmos/cosmos-sdk/issues/1461) - CLI tests now no longer reset your local environment data
|
||||
* [\#1505](https://github.com/cosmos/cosmos-sdk/issues/1505) - `gaiacli stake validator` no longer panics if validator doesn't exist
|
||||
* [\#1565](https://github.com/cosmos/cosmos-sdk/issues/1565) - fix cliff validator persisting when validator set shrinks from max
|
||||
* [\#1287](https://github.com/cosmos/cosmos-sdk/issues/1287) - prevent zero power validators at genesis
|
||||
* [x/stake] fix bug when unbonding/redelegating using `--shares-percent`
|
||||
* \#1010 - two validators can't bond with the same pubkey anymore
|
||||
* [\#1010](https://github.com/cosmos/cosmos-sdk/issues/1010) - two validators can't bond with the same pubkey anymore
|
||||
|
||||
|
||||
## 0.19.0
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
* @jaekwon
|
||||
* @ebuchman
|
||||
@ -133,6 +133,17 @@ Libraries need not follow the model strictly, but would be wise to.
|
||||
|
||||
The SDK utilizes [semantic versioning](https://semver.org/).
|
||||
|
||||
### PR Targeting
|
||||
|
||||
Ensure that you base and target your PR on the correct branch:
|
||||
- `release/vxx.yy.zz` for a merge into a release candidate
|
||||
- `master` for a merge of a release
|
||||
- `develop` in the usual case
|
||||
|
||||
All feature additions should be targeted against `develop`. Bug fixes for an outstanding release candidate
|
||||
should be targeted against the release candidate branch. Release candidate branches themselves should be the
|
||||
only pull requests targeted directly against master.
|
||||
|
||||
### Development Procedure:
|
||||
- the latest state of development is on `develop`
|
||||
- `develop` must never fail `make test` or `make test_cli`
|
||||
|
||||
150
Gopkg.lock
generated
150
Gopkg.lock
generated
@ -1,6 +1,14 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:7736fc6da04620727f8f3aa2ced8d77be8e074a302820937aa5993848c769b27"
|
||||
name = "github.com/ZondaX/hid-go"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "48b08affede2cea076a3cf13b2e3f72ed262b743"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:09a7f74eb6bb3c0f14d8926610c87f569c5cff68e978d30e9a3540aeb626fdf0"
|
||||
name = "github.com/bartekn/go-bip39"
|
||||
@ -26,19 +34,11 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:70f6b224a59b2fa453debffa85c77f71063d8754b90c8c4fbad5794e2c382b0f"
|
||||
name = "github.com/brejski/hid"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "06112dcfcc50a7e0e4fd06e17f9791e788fdaafc"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:2c00f064ba355903866cbfbf3f7f4c0fe64af6638cc7d1b8bdcf3181bc67f1d8"
|
||||
digest = "1:c0decf632843204d2b8781de7b26e7038584e2dcccc7e2f401e88ae85b1df2b7"
|
||||
name = "github.com/btcsuite/btcd"
|
||||
packages = ["btcec"]
|
||||
pruneopts = "UT"
|
||||
revision = "f899737d7f2764dc13e4d01ff00108ec58f766a9"
|
||||
revision = "2a560b2036bee5e3679ec2133eb6520b2f195213"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:386de157f7d19259a7f9c81f26ce011223ce0f090353c1152ffdf730d7d10ac2"
|
||||
@ -47,6 +47,13 @@
|
||||
pruneopts = "UT"
|
||||
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e8a3550c8786316675ff54ad6f09d265d129c9d986919af7f541afba50d87ce2"
|
||||
name = "github.com/cosmos/go-bip39"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "52158e4697b87de16ed390e1bdaf813e581008fa"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
@ -95,12 +102,12 @@
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c4a2528ccbcabf90f9f3c464a5fc9e302d592861bbfd0b7135a7de8a943d0406"
|
||||
digest = "1:586ea76dbd0374d6fb649a91d70d652b7fe0ccffb8910a77468e7702e7901f3d"
|
||||
name = "github.com/go-stack/stack"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc"
|
||||
version = "v1.7.0"
|
||||
revision = "2fee6af1a9795aafbe0253a0cfbdf668e1fb8a9a"
|
||||
version = "v1.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:35621fe20f140f05a0c4ef662c26c0ab4ee50bca78aa30fe87d33120bd28165e"
|
||||
@ -164,8 +171,7 @@
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:12247a2e99a060cc692f6680e5272c8adf0b8f572e6bce0d7095e624c958a240"
|
||||
digest = "1:ea40c24cdbacd054a6ae9de03e62c5f252479b96c716375aace5c120d68647c8"
|
||||
name = "github.com/hashicorp/hcl"
|
||||
packages = [
|
||||
".",
|
||||
@ -179,7 +185,8 @@
|
||||
"json/token",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168"
|
||||
revision = "8cb6e5b959231cc1119e43259c4a608f9c51a241"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
||||
@ -214,12 +221,12 @@
|
||||
version = "v1.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d4d17353dbd05cb52a2a52b7fe1771883b682806f68db442b436294926bbfafb"
|
||||
digest = "1:0981502f9816113c9c8c4ac301583841855c8cf4da8c72f696b3ebedf6d0e4e5"
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||
version = "v0.0.3"
|
||||
revision = "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c"
|
||||
version = "v0.0.4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ff5ebae34cfbf047d505ee150de27e60570e8c394b3b8fdbb720ff6ac71985fc"
|
||||
@ -230,12 +237,20 @@
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:5ab79470a1d0fb19b041a624415612f8236b3c06070161a910562f2b2d064355"
|
||||
digest = "1:78bbb1ba5b7c3f2ed0ea1eab57bdd3859aec7e177811563edc41198a760b06af"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ae18d6b8b3205b561c79e8e5f69bff09736185f4"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e32dfc6abff6a3633ef4d9a1022fd707c8ef26f1e1e8f855dc58dc415ce7c8f3"
|
||||
name = "github.com/mitchellh/mapstructure"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "f15292f7a699fcc1a38a80977f80a046874ba8ac"
|
||||
revision = "fe40af7a9c397fa3ddba203c38a5042c5d0475ad"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e"
|
||||
@ -293,7 +308,7 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8c49953a1414305f2ff5465147ee576dd705487c35b15918fcd4efdc0cb7a290"
|
||||
digest = "1:ef1dd9945e58ee9b635273d28c0ef3fa3742a7dedc038ebe207fd63e6ce000ef"
|
||||
name = "github.com/prometheus/procfs"
|
||||
packages = [
|
||||
".",
|
||||
@ -302,7 +317,15 @@
|
||||
"xfs",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "05ee40e3a273f7245e8777337fc7b46e533a9a92"
|
||||
revision = "418d78d0b9a7b7de3a6bbc8a23def624cc977bb2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ea0700160aca4ef099f4e06686a665a87691f4248dddd40796925eda2e46bd64"
|
||||
name = "github.com/rakyll/statik"
|
||||
packages = ["fs"]
|
||||
pruneopts = "UT"
|
||||
revision = "aa8a7b1baecd0f31a436bf7956fcdcc609a83035"
|
||||
version = "v0.1.4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c4556a44e350b50a490544d9b06e9fba9c286c21d6c0e47f54f3a9214597298c"
|
||||
@ -312,15 +335,15 @@
|
||||
revision = "e2704e165165ec55d062f5919b4b29494e9fa790"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:bd1ae00087d17c5a748660b8e89e1043e1e5479d0fea743352cda2f8dd8c4f84"
|
||||
digest = "1:6a4a11ba764a56d2758899ec6f3848d24698d48442ebce85ee7a3f63284526cd"
|
||||
name = "github.com/spf13/afero"
|
||||
packages = [
|
||||
".",
|
||||
"mem",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "787d034dfe70e44075ccc060d346146ef53270ad"
|
||||
version = "v1.1.1"
|
||||
revision = "d40851caa0d747393da1ffb28f7f9d8b4eeffebd"
|
||||
version = "v1.1.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f"
|
||||
@ -339,12 +362,12 @@
|
||||
version = "v0.0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8a020f916b23ff574845789daee6818daf8d25a4852419aae3f0b12378ba432a"
|
||||
digest = "1:68ea4e23713989dc20b1bded5d9da2c5f9be14ff9885beef481848edd18c26cb"
|
||||
name = "github.com/spf13/jwalterweatherman"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "14d3d4c518341bea657dd8a226f5121c0ff8c9f2"
|
||||
revision = "4a4406e478ca629068e7768fc33f3f044173c0a6"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:dab83a1bbc7ad3d7a6ba1a1cc1760f25ac38cdf7d96a5cdd55cd915a4f5ceaf9"
|
||||
@ -374,8 +397,7 @@
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f2ffd421680b0a3f7887501b3c6974bcf19217ecd301d0e2c9b681940ec363d5"
|
||||
digest = "1:b3cfb8d82b1601a846417c3f31c03a7961862cb2c98dcf0959c473843e6d9a2b"
|
||||
name = "github.com/syndtr/goleveldb"
|
||||
packages = [
|
||||
"leveldb",
|
||||
@ -392,7 +414,14 @@
|
||||
"leveldb/util",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "ae2bd5eed72d46b28834ec3f60db3a3ebedd8dbd"
|
||||
revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:605b6546f3f43745695298ec2d342d3e952b6d91cdf9f349bea9315f677d759f"
|
||||
name = "github.com/tendermint/btcd"
|
||||
packages = ["btcec"]
|
||||
pruneopts = "UT"
|
||||
revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@ -407,23 +436,23 @@
|
||||
revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e0a2a4be1e20c305badc2b0a7a9ab7fef6da500763bec23ab81df3b5f9eec9ee"
|
||||
digest = "1:2c971a45c89ca2ccc735af50919cdee05fbdc54d4bf50625073693300e31ead8"
|
||||
name = "github.com/tendermint/go-amino"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "a8328986c1608950fa5d3d1c0472cccc4f8fc02c"
|
||||
version = "v0.12.0-rc0"
|
||||
revision = "faa6e731944e2b7b6a46ad202902851e8ce85bee"
|
||||
version = "v0.12.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d4a15d404afbf591e8be16fcda7f5ac87948d5c7531f9d909fd84cc730ab16e2"
|
||||
digest = "1:53397098d6acb7613358683cc84ae59281a60c6033f0bff62fa8d3f279c6c430"
|
||||
name = "github.com/tendermint/iavl"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "35f66e53d9b01e83b30de68b931f54b2477a94c9"
|
||||
version = "v0.9.2"
|
||||
revision = "3acc91fb8811db2c5409a855ae1f8e441fe98e2d"
|
||||
version = "v0.11.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4f15e95fe3888cc75dd34f407d6394cbc7fd3ff24920851b92b295f6a8b556e6"
|
||||
digest = "1:f9c7a1f3ee087476f4883c33cc7c1bdbe56b9670b2fb27855ea2f386393272f5"
|
||||
name = "github.com/tendermint/tendermint"
|
||||
packages = [
|
||||
"abci/client",
|
||||
@ -441,6 +470,8 @@
|
||||
"crypto/ed25519",
|
||||
"crypto/encoding/amino",
|
||||
"crypto/merkle",
|
||||
"crypto/multisig",
|
||||
"crypto/multisig/bitarray",
|
||||
"crypto/secp256k1",
|
||||
"crypto/tmhash",
|
||||
"crypto/xsalsa20symmetric",
|
||||
@ -452,6 +483,7 @@
|
||||
"libs/clist",
|
||||
"libs/common",
|
||||
"libs/db",
|
||||
"libs/errors",
|
||||
"libs/events",
|
||||
"libs/flowrate",
|
||||
"libs/log",
|
||||
@ -460,7 +492,6 @@
|
||||
"lite",
|
||||
"lite/client",
|
||||
"lite/errors",
|
||||
"lite/files",
|
||||
"lite/proxy",
|
||||
"mempool",
|
||||
"node",
|
||||
@ -483,24 +514,26 @@
|
||||
"state/txindex/kv",
|
||||
"state/txindex/null",
|
||||
"types",
|
||||
"types/time",
|
||||
"version",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "81df19e68ab1519399fccf0cab81cb75bf9d782e"
|
||||
version = "v0.23.1-rc0"
|
||||
revision = "90eda9bfb6e6daeed1c8015df41cb36772d91778"
|
||||
version = "v0.25.1-rc0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4dcb0dd65feecb068ce23a234d1a07c7868a1e39f52a6defcae0bb371d03abf6"
|
||||
digest = "1:7886f86064faff6f8d08a3eb0e8c773648ff5a2e27730831e2bfbf07467f6666"
|
||||
name = "github.com/zondax/ledger-goclient"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "4296ee5701e945f9b3a7dbe51f402e0b9be57259"
|
||||
revision = "58598458c11bc0ad1c1b8dac3dc3e11eaf270b79"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:7a71fffde456d746c52f9cd09c50b034533a3180fb1f6320abb149f2ccc579e5"
|
||||
digest = "1:aaff04fa01d9b824fde6799759cc597b3ac3671b9ad31924c28b6557d0ee5284"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"bcrypt",
|
||||
"blowfish",
|
||||
"chacha20poly1305",
|
||||
"curve25519",
|
||||
@ -517,7 +550,8 @@
|
||||
"salsa20/salsa",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "de0752318171da717af4ce24d0a2e8626afaeb11"
|
||||
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
|
||||
source = "https://github.com/tendermint/crypto"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1"
|
||||
@ -536,15 +570,14 @@
|
||||
revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a989b95f72fce8876213e8e20492525b4cf69a9e7fee7f1d9897983ee0d547e9"
|
||||
digest = "1:4bd75b1a219bc590b05c976bbebf47f4e993314ebb5c7cbf2efe05a09a184d54"
|
||||
name = "golang.org/x/sys"
|
||||
packages = [
|
||||
"cpu",
|
||||
"unix",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "1c9583448a9c3aa0f9a6a5241bf73c0bd8aafded"
|
||||
revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
|
||||
@ -570,12 +603,11 @@
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:077c1c599507b3b3e9156d17d36e1e61928ee9b53a5b420f10f28ebd4a0b275c"
|
||||
name = "google.golang.org/genproto"
|
||||
packages = ["googleapis/rpc/status"]
|
||||
pruneopts = "UT"
|
||||
revision = "d0a8f471bba2dbb160885b0000d814ee5d559bad"
|
||||
revision = "383e8b2c3b9e36c4076b235b32537292176bae20"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74"
|
||||
@ -626,15 +658,20 @@
|
||||
"github.com/bartekn/go-bip39",
|
||||
"github.com/bgentry/speakeasy",
|
||||
"github.com/btcsuite/btcd/btcec",
|
||||
"github.com/cosmos/go-bip39",
|
||||
"github.com/golang/protobuf/proto",
|
||||
"github.com/gorilla/mux",
|
||||
"github.com/mattn/go-isatty",
|
||||
"github.com/mitchellh/go-homedir",
|
||||
"github.com/pelletier/go-toml",
|
||||
"github.com/pkg/errors",
|
||||
"github.com/rakyll/statik/fs",
|
||||
"github.com/spf13/cobra",
|
||||
"github.com/spf13/pflag",
|
||||
"github.com/spf13/viper",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"github.com/stretchr/testify/require",
|
||||
"github.com/syndtr/goleveldb/leveldb/opt",
|
||||
"github.com/tendermint/go-amino",
|
||||
"github.com/tendermint/iavl",
|
||||
"github.com/tendermint/tendermint/abci/server",
|
||||
@ -646,6 +683,7 @@
|
||||
"github.com/tendermint/tendermint/crypto/ed25519",
|
||||
"github.com/tendermint/tendermint/crypto/encoding/amino",
|
||||
"github.com/tendermint/tendermint/crypto/merkle",
|
||||
"github.com/tendermint/tendermint/crypto/multisig",
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1",
|
||||
"github.com/tendermint/tendermint/crypto/tmhash",
|
||||
"github.com/tendermint/tendermint/crypto/xsalsa20symmetric",
|
||||
@ -655,6 +693,9 @@
|
||||
"github.com/tendermint/tendermint/libs/common",
|
||||
"github.com/tendermint/tendermint/libs/db",
|
||||
"github.com/tendermint/tendermint/libs/log",
|
||||
"github.com/tendermint/tendermint/lite",
|
||||
"github.com/tendermint/tendermint/lite/errors",
|
||||
"github.com/tendermint/tendermint/lite/proxy",
|
||||
"github.com/tendermint/tendermint/node",
|
||||
"github.com/tendermint/tendermint/p2p",
|
||||
"github.com/tendermint/tendermint/privval",
|
||||
@ -666,8 +707,7 @@
|
||||
"github.com/tendermint/tendermint/types",
|
||||
"github.com/tendermint/tendermint/version",
|
||||
"github.com/zondax/ledger-goclient",
|
||||
"golang.org/x/crypto/blowfish",
|
||||
"golang.org/x/crypto/ripemd160",
|
||||
"golang.org/x/crypto/bcrypt",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
49
Gopkg.toml
49
Gopkg.toml
@ -49,24 +49,61 @@
|
||||
|
||||
[[override]]
|
||||
name = "github.com/tendermint/go-amino"
|
||||
version = "=v0.12.0-rc0"
|
||||
version = "=v0.12.0"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/tendermint/iavl"
|
||||
version = "=v0.9.2"
|
||||
version = "=v0.11.0"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/tendermint/tendermint"
|
||||
version = "=v0.23.1-rc0"
|
||||
version = "=0.25.1-rc0"
|
||||
|
||||
## deps without releases:
|
||||
|
||||
[[override]]
|
||||
name = "golang.org/x/crypto"
|
||||
source = "https://github.com/tendermint/crypto"
|
||||
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/bartekn/go-bip39"
|
||||
revision = "a05967ea095d81c8fe4833776774cfaff8e5036c"
|
||||
name = "github.com/cosmos/go-bip39"
|
||||
revision = "52158e4697b87de16ed390e1bdaf813e581008fa"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/zondax/ledger-goclient"
|
||||
revision = "4296ee5701e945f9b3a7dbe51f402e0b9be57259"
|
||||
version = "=v0.1.0"
|
||||
|
||||
## transitive deps, with releases:
|
||||
|
||||
[[override]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
version = "=v1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/rakyll/statik"
|
||||
version = "=v0.1.4"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
version = "1.0.0"
|
||||
|
||||
## transitive deps, without releases:
|
||||
#
|
||||
|
||||
[[override]]
|
||||
name = "github.com/syndtr/goleveldb"
|
||||
revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445"
|
||||
|
||||
[[override]]
|
||||
name = "golang.org/x/sys"
|
||||
revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf"
|
||||
|
||||
[[override]]
|
||||
name = "google.golang.org/genproto"
|
||||
revision = "383e8b2c3b9e36c4076b235b32537292176bae20"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
|
||||
96
Makefile
96
Makefile
@ -1,10 +1,15 @@
|
||||
PACKAGES_NOSIMULATION=$(shell go list ./... | grep -v '/simulation')
|
||||
PACKAGES_SIMTEST=$(shell go list ./... | grep '/simulation')
|
||||
COMMIT_HASH := $(shell git rev-parse --short HEAD)
|
||||
VERSION := $(shell git describe --tags --long | sed 's/v\(.*\)/\1/')
|
||||
BUILD_TAGS = netgo ledger
|
||||
BUILD_FLAGS = -tags "${BUILD_TAGS}" -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}"
|
||||
BUILD_FLAGS = -tags "${BUILD_TAGS}" -ldflags "-X github.com/cosmos/cosmos-sdk/version.Version=${VERSION}"
|
||||
GCC := $(shell command -v gcc 2> /dev/null)
|
||||
LEDGER_ENABLED ?= true
|
||||
UNAME_S := $(shell uname -s)
|
||||
GOTOOLS = \
|
||||
github.com/golang/dep/cmd/dep \
|
||||
github.com/alecthomas/gometalinter \
|
||||
github.com/rakyll/statik
|
||||
all: get_tools get_vendor_deps install install_examples install_cosmos-sdk-cli test_lint test
|
||||
|
||||
########################################
|
||||
@ -17,15 +22,21 @@ ci: get_tools get_vendor_deps install test_cover test_lint test
|
||||
|
||||
check-ledger:
|
||||
ifeq ($(LEDGER_ENABLED),true)
|
||||
ifndef GCC
|
||||
$(error "gcc not installed for ledger support, please install")
|
||||
endif
|
||||
ifeq ($(UNAME_S),OpenBSD)
|
||||
$(info "OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988)")
|
||||
TMP_BUILD_TAGS := $(BUILD_TAGS)
|
||||
BUILD_TAGS = $(filter-out ledger, $(TMP_BUILD_TAGS))
|
||||
else
|
||||
ifndef GCC
|
||||
$(error "gcc not installed for ledger support, please install or set LEDGER_ENABLED to false in the Makefile")
|
||||
endif
|
||||
endif
|
||||
else
|
||||
TMP_BUILD_TAGS := $(BUILD_TAGS)
|
||||
BUILD_TAGS = $(filter-out ledger, $(TMP_BUILD_TAGS))
|
||||
endif
|
||||
|
||||
build: check-ledger
|
||||
build: check-ledger update_gaia_lite_docs
|
||||
ifeq ($(OS),Windows_NT)
|
||||
go build $(BUILD_FLAGS) -o build/gaiad.exe ./cmd/gaia/cmd/gaiad
|
||||
go build $(BUILD_FLAGS) -o build/gaiacli.exe ./cmd/gaia/cmd/gaiacli
|
||||
@ -37,6 +48,9 @@ endif
|
||||
build-linux:
|
||||
LEDGER_ENABLED=false GOOS=linux GOARCH=amd64 $(MAKE) build
|
||||
|
||||
update_gaia_lite_docs:
|
||||
@statik -src=client/lcd/swagger-ui -dest=client/lcd -f
|
||||
|
||||
build_cosmos-sdk-cli:
|
||||
ifeq ($(OS),Windows_NT)
|
||||
go build $(BUILD_FLAGS) -o build/cosmos-sdk-cli.exe ./cmd/cosmos-sdk-cli
|
||||
@ -57,7 +71,7 @@ else
|
||||
go build $(BUILD_FLAGS) -o build/democli ./examples/democoin/cmd/democli
|
||||
endif
|
||||
|
||||
install: check-ledger
|
||||
install: check-ledger update_gaia_lite_docs
|
||||
go install $(BUILD_FLAGS) ./cmd/gaia/cmd/gaiad
|
||||
go install $(BUILD_FLAGS) ./cmd/gaia/cmd/gaiacli
|
||||
|
||||
@ -81,25 +95,36 @@ dist:
|
||||
### Tools & dependencies
|
||||
|
||||
check_tools:
|
||||
cd tools && $(MAKE) check_tools
|
||||
|
||||
check_dev_tools:
|
||||
cd tools && $(MAKE) check_dev_tools
|
||||
@# https://stackoverflow.com/a/25668869
|
||||
@echo "Found tools: $(foreach tool,$(notdir $(GOTOOLS)),\
|
||||
$(if $(shell which $(tool)),$(tool),$(error "No $(tool) in PATH")))"
|
||||
|
||||
update_tools:
|
||||
cd tools && $(MAKE) update_tools
|
||||
@echo "--> Updating tools to correct version"
|
||||
./scripts/get_tools.sh
|
||||
|
||||
update_dev_tools:
|
||||
cd tools && $(MAKE) update_dev_tools
|
||||
@echo "--> Downloading linters (this may take awhile)"
|
||||
$(GOPATH)/src/github.com/alecthomas/gometalinter/scripts/install.sh -b $(GOBIN)
|
||||
go get -u github.com/tendermint/lint/golint
|
||||
|
||||
get_tools:
|
||||
cd tools && $(MAKE) get_tools
|
||||
@echo "--> Installing tools"
|
||||
./scripts/get_tools.sh
|
||||
|
||||
get_dev_tools:
|
||||
cd tools && $(MAKE) get_dev_tools
|
||||
@echo "--> Downloading linters (this may take awhile)"
|
||||
$(GOPATH)/src/github.com/alecthomas/gometalinter/scripts/install.sh -b $(GOBIN)
|
||||
go get github.com/tendermint/lint/golint
|
||||
|
||||
get_vendor_deps:
|
||||
@echo "--> Generating vendor directory via dep ensure"
|
||||
@rm -rf .vendor-new
|
||||
@dep ensure -v -vendor-only
|
||||
|
||||
update_vendor_deps:
|
||||
@echo "--> Running dep ensure"
|
||||
@rm -rf .vendor-new
|
||||
@dep ensure -v
|
||||
|
||||
draw_deps:
|
||||
@ -124,11 +149,15 @@ test: test_unit
|
||||
test_cli:
|
||||
@go test -count 1 -p 1 `go list github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test` -tags=cli_test
|
||||
|
||||
test_examples:
|
||||
@go test -count 1 -p 1 `go list github.com/cosmos/cosmos-sdk/examples/basecoin/cli_test` -tags=cli_test
|
||||
@go test -count 1 -p 1 `go list github.com/cosmos/cosmos-sdk/examples/democoin/cli_test` -tags=cli_test
|
||||
|
||||
test_unit:
|
||||
@go test $(PACKAGES_NOSIMULATION)
|
||||
@VERSION=$(VERSION) go test $(PACKAGES_NOSIMULATION)
|
||||
|
||||
test_race:
|
||||
@go test -race $(PACKAGES_NOSIMULATION)
|
||||
@VERSION=$(VERSION) go test -race $(PACKAGES_NOSIMULATION)
|
||||
|
||||
test_sim_modules:
|
||||
@echo "Running individual module simulations..."
|
||||
@ -140,25 +169,36 @@ test_sim_gaia_nondeterminism:
|
||||
|
||||
test_sim_gaia_fast:
|
||||
@echo "Running quick Gaia simulation. This may take several minutes..."
|
||||
@go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=200 -timeout 24h
|
||||
@go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=400 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=9 -v -timeout 24h
|
||||
|
||||
test_sim_gaia_slow:
|
||||
@echo "Running full Gaia simulation. This may take awhile!"
|
||||
@go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 -SimulationVerbose=true -v -timeout 24h
|
||||
test_sim_gaia_multi_seed:
|
||||
@echo "Running multi-seed Gaia simulation. This may take awhile!"
|
||||
@bash scripts/multisim.sh 10
|
||||
|
||||
SIM_NUM_BLOCKS ?= 210
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
test_cover:
|
||||
@bash tests/test_cover.sh
|
||||
@export VERSION=$(VERSION); bash tests/test_cover.sh
|
||||
|
||||
test_lint:
|
||||
gometalinter.v2 --config=tools/gometalinter.json ./...
|
||||
!(gometalinter.v2 --disable-all --enable='errcheck' --vendor ./... | grep -v "client/")
|
||||
gometalinter --config=tools/gometalinter.json ./...
|
||||
!(gometalinter --exclude /usr/lib/go/src/ --exclude client/lcd/statik/statik.go --exclude 'vendor/*' --disable-all --enable='errcheck' --vendor ./... | grep -v "client/")
|
||||
find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs gofmt -d -s
|
||||
dep status >> /dev/null
|
||||
!(grep -n branch Gopkg.toml)
|
||||
|
||||
format:
|
||||
find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs gofmt -w -s
|
||||
find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs misspell -w
|
||||
find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/lcd/statik/statik.go" | xargs gofmt -w -s
|
||||
find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/lcd/statik/statik.go" | xargs misspell -w
|
||||
|
||||
benchmark:
|
||||
@go test -bench=. $(PACKAGES_NOSIMULATION)
|
||||
@ -196,7 +236,7 @@ build-docker-gaiadnode:
|
||||
|
||||
# Run a 4-node testnet locally
|
||||
localnet-start: localnet-stop
|
||||
@if ! [ -f build/node0/gaiad/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/gaiad:Z tendermint/gaiadnode testnet --v 4 --o . --starting-ip-address 192.168.10.2 ; fi
|
||||
@if ! [ -f build/node0/gaiad/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/gaiad:Z tendermint/gaiadnode testnet --v 4 -o . --starting-ip-address 192.168.10.2 ; fi
|
||||
docker-compose up -d
|
||||
|
||||
# Stop testnet
|
||||
@ -210,4 +250,4 @@ localnet-stop:
|
||||
check_tools check_dev_tools get_tools get_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_modules test_sim_gaia_fast test_sim_gaia_slow update_tools update_dev_tools
|
||||
format check-ledger test_sim_gaia_nondeterminism test_sim_modules test_sim_gaia_fast test_sim_gaia_multi_seed update_tools update_dev_tools
|
||||
|
||||
@ -7,7 +7,6 @@ BREAKING CHANGES
|
||||
* Gaia CLI (`gaiacli`)
|
||||
|
||||
* Gaia
|
||||
* Make the transient store key use a distinct store key. [#2013](https://github.com/cosmos/cosmos-sdk/pull/2013)
|
||||
|
||||
* SDK
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ breaking changes.
|
||||
## Gaia Testnet
|
||||
|
||||
To join the latest testnet, follow
|
||||
[the guide](https://cosmos.network/docs/getting-started/full-node.html#setting-up-a-new-node).
|
||||
[the guide](./docs/getting-started/join-testnet.md).
|
||||
|
||||
For status updates and genesis files, see the
|
||||
[testnets repo](https://github.com/cosmos/testnets).
|
||||
@ -30,15 +30,14 @@ For status updates and genesis files, see the
|
||||
## Install
|
||||
|
||||
See the
|
||||
[install instructions](https://cosmos.network/docs/getting-started/installation.html).
|
||||
[install instructions](./docs/getting-started/installation.md).
|
||||
|
||||
## Quick Start
|
||||
|
||||
See the [Cosmos Docs](https://cosmos.network/docs/)
|
||||
|
||||
- [Getting started with the SDK](https://cosmos.network/docs/sdk/core/intro.html)
|
||||
- [Getting started with the SDK](./docs/sdk/core/intro.md)
|
||||
- [SDK Examples](/examples)
|
||||
- [Join the testnet](https://cosmos.network/docs/getting-started/full-node.html#run-a-full-node)
|
||||
|
||||
## Disambiguation
|
||||
|
||||
|
||||
@ -14,10 +14,10 @@ import (
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
)
|
||||
|
||||
// Key to store the header in the DB itself.
|
||||
@ -41,13 +41,14 @@ const (
|
||||
// BaseApp reflects the ABCI application implementation.
|
||||
type BaseApp struct {
|
||||
// initialized on creation
|
||||
Logger log.Logger
|
||||
name string // application name from abci.Info
|
||||
db dbm.DB // common DB backend
|
||||
cms sdk.CommitMultiStore // Main (uncached) state
|
||||
router Router // handle any kind of message
|
||||
codespacer *sdk.Codespacer // handle module codespacing
|
||||
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
|
||||
Logger log.Logger
|
||||
name string // application name from abci.Info
|
||||
db dbm.DB // common DB backend
|
||||
cms sdk.CommitMultiStore // Main (uncached) state
|
||||
router Router // handle any kind of message
|
||||
queryRouter QueryRouter // router for redirecting query calls
|
||||
codespacer *sdk.Codespacer // handle module codespacing
|
||||
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
|
||||
|
||||
anteHandler sdk.AnteHandler // ante handler for fee and auth
|
||||
|
||||
@ -63,9 +64,12 @@ type BaseApp struct {
|
||||
// checkState is set on initialization and reset on Commit.
|
||||
// deliverState is set in InitChain and BeginBlock and cleared on Commit.
|
||||
// See methods setCheckState and setDeliverState.
|
||||
checkState *state // for CheckTx
|
||||
deliverState *state // for DeliverTx
|
||||
signedValidators []abci.SigningValidator // absent validators from begin block
|
||||
checkState *state // for CheckTx
|
||||
deliverState *state // for DeliverTx
|
||||
voteInfos []abci.VoteInfo // absent validators from begin block
|
||||
|
||||
// minimum fees for spam prevention
|
||||
minimumFees sdk.Coins
|
||||
|
||||
// flag for sealing
|
||||
sealed bool
|
||||
@ -84,13 +88,14 @@ var _ abci.Application = (*BaseApp)(nil)
|
||||
// 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 {
|
||||
app := &BaseApp{
|
||||
Logger: logger,
|
||||
name: name,
|
||||
db: db,
|
||||
cms: store.NewCommitMultiStore(db),
|
||||
router: NewRouter(),
|
||||
codespacer: sdk.NewCodespacer(),
|
||||
txDecoder: txDecoder,
|
||||
Logger: logger,
|
||||
name: name,
|
||||
db: db,
|
||||
cms: store.NewCommitMultiStore(db),
|
||||
router: NewRouter(),
|
||||
queryRouter: NewQueryRouter(),
|
||||
codespacer: sdk.NewCodespacer(),
|
||||
txDecoder: txDecoder,
|
||||
}
|
||||
|
||||
// Register the undefined & root codespaces, which should not be used by
|
||||
@ -118,13 +123,20 @@ func (app *BaseApp) RegisterCodespace(codespace sdk.CodespaceType) sdk.Codespace
|
||||
return app.codespacer.RegisterNext(codespace)
|
||||
}
|
||||
|
||||
// Mount a store to the provided key in the BaseApp multistore
|
||||
// Mount IAVL stores to the provided keys in the BaseApp multistore
|
||||
func (app *BaseApp) MountStoresIAVL(keys ...*sdk.KVStoreKey) {
|
||||
for _, key := range keys {
|
||||
app.MountStore(key, sdk.StoreTypeIAVL)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
func (app *BaseApp) MountStoreWithDB(key sdk.StoreKey, typ sdk.StoreType, db dbm.DB) {
|
||||
app.cms.MountStoreWithDB(key, typ, db)
|
||||
@ -179,10 +191,13 @@ func (app *BaseApp) initFromStore(mainKey sdk.StoreKey) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMinimumFees sets the minimum fees.
|
||||
func (app *BaseApp) SetMinimumFees(fees sdk.Coins) { app.minimumFees = fees }
|
||||
|
||||
// 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)
|
||||
return sdk.NewContext(app.checkState.ms, header, true, app.Logger).WithMinimumFees(app.minimumFees)
|
||||
}
|
||||
return sdk.NewContext(app.deliverState.ms, header, false, app.Logger)
|
||||
}
|
||||
@ -200,7 +215,7 @@ func (app *BaseApp) setCheckState(header abci.Header) {
|
||||
ms := app.cms.CacheMultiStore()
|
||||
app.checkState = &state{
|
||||
ms: ms,
|
||||
ctx: sdk.NewContext(ms, header, true, app.Logger),
|
||||
ctx: sdk.NewContext(ms, header, true, app.Logger).WithMinimumFees(app.minimumFees),
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,6 +281,7 @@ func (app *BaseApp) FilterPeerByPubKey(info string) abci.ResponseQuery {
|
||||
return abci.ResponseQuery{}
|
||||
}
|
||||
|
||||
// Splits a string path using the delimter '/'. i.e. "this/is/funny" becomes []string{"this", "is", "funny"}
|
||||
func splitPath(requestPath string) (path []string) {
|
||||
path = strings.Split(requestPath, "/")
|
||||
// first element is empty string
|
||||
@ -291,6 +307,8 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
|
||||
return handleQueryStore(app, path, req)
|
||||
case "p2p":
|
||||
return handleQueryP2P(app, path, req)
|
||||
case "custom":
|
||||
return handleQueryCustom(app, path, req)
|
||||
}
|
||||
|
||||
msg := "unknown query path"
|
||||
@ -319,7 +337,7 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abc
|
||||
}
|
||||
|
||||
// Encode with json
|
||||
value := wire.Cdc.MustMarshalBinary(result)
|
||||
value := codec.Cdc.MustMarshalBinary(result)
|
||||
return abci.ResponseQuery{
|
||||
Code: uint32(sdk.ABCICodeOK),
|
||||
Value: value,
|
||||
@ -340,6 +358,7 @@ func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) (res a
|
||||
return queryable.Query(req)
|
||||
}
|
||||
|
||||
// nolint: unparam
|
||||
func handleQueryP2P(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
|
||||
// "/p2p" prefix for p2p queries
|
||||
if len(path) >= 4 {
|
||||
@ -362,6 +381,34 @@ func handleQueryP2P(app *BaseApp, path []string, req abci.RequestQuery) (res abc
|
||||
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"
|
||||
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()
|
||||
}
|
||||
|
||||
ctx := sdk.NewContext(app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger).
|
||||
WithMinimumFees(app.minimumFees)
|
||||
// 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
|
||||
resBytes, err := querier(ctx, path[2:], req)
|
||||
if err != nil {
|
||||
return abci.ResponseQuery{
|
||||
Code: uint32(err.ABCICode()),
|
||||
Log: err.ABCILog(),
|
||||
}
|
||||
}
|
||||
return abci.ResponseQuery{
|
||||
Code: uint32(sdk.ABCICodeOK),
|
||||
Value: resBytes,
|
||||
}
|
||||
}
|
||||
|
||||
// BeginBlock implements the ABCI application interface.
|
||||
func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) {
|
||||
if app.cms.TracingEnabled() {
|
||||
@ -388,7 +435,7 @@ 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.signedValidators = req.LastCommitInfo.GetValidators()
|
||||
app.voteInfos = req.LastCommitInfo.GetVotes()
|
||||
return
|
||||
}
|
||||
|
||||
@ -461,15 +508,14 @@ func validateBasicTxMsgs(msgs []sdk.Msg) sdk.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// retrieve the context for the ante handler and store the tx bytes; store
|
||||
// the vote infos if the tx runs within the deliverTx() state.
|
||||
func (app *BaseApp) getContextForAnte(mode runTxMode, txBytes []byte) (ctx sdk.Context) {
|
||||
// Get the context
|
||||
if mode == runTxModeCheck || mode == runTxModeSimulate {
|
||||
ctx = app.checkState.ctx.WithTxBytes(txBytes)
|
||||
} else {
|
||||
ctx = app.deliverState.ctx.WithTxBytes(txBytes)
|
||||
ctx = ctx.WithSigningValidators(app.signedValidators)
|
||||
ctx = getState(app, mode).ctx.WithTxBytes(txBytes)
|
||||
if mode == runTxModeDeliver {
|
||||
ctx = ctx.WithVoteInfos(app.voteInfos)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -482,10 +528,10 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re
|
||||
var code sdk.ABCICodeType
|
||||
for msgIdx, msg := range msgs {
|
||||
// Match route.
|
||||
msgType := msg.Type()
|
||||
handler := app.router.Route(msgType)
|
||||
msgRoute := msg.Route()
|
||||
handler := app.router.Route(msgRoute)
|
||||
if handler == nil {
|
||||
return sdk.ErrUnknownRequest("Unrecognized Msg type: " + msgType).Result()
|
||||
return sdk.ErrUnknownRequest("Unrecognized Msg type: " + msgRoute).Result()
|
||||
}
|
||||
|
||||
var msgResult sdk.Result
|
||||
@ -493,6 +539,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re
|
||||
if mode != runTxModeCheck {
|
||||
msgResult = handler(ctx, msg)
|
||||
}
|
||||
msgResult.Tags = append(msgResult.Tags, sdk.MakeTag("action", []byte(msg.Type())))
|
||||
|
||||
// NOTE: GasWanted is determined by ante handler and
|
||||
// GasUsed by the GasMeter
|
||||
@ -535,6 +582,13 @@ func getState(app *BaseApp, mode runTxMode) *state {
|
||||
return app.deliverState
|
||||
}
|
||||
|
||||
func (app *BaseApp) initializeContext(ctx sdk.Context, mode runTxMode) sdk.Context {
|
||||
if mode == runTxModeSimulate {
|
||||
ctx = ctx.WithMultiStore(getState(app, runTxModeSimulate).CacheMultiStore())
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
// runTx processes a transaction. The transactions is proccessed via an
|
||||
// anteHandler. txBytes may be nil in some cases, eg. in tests. Also, in the
|
||||
// future we may support "internal" transactions.
|
||||
@ -543,7 +597,9 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
||||
// determined by the GasMeter. We need access to the context to get the gas
|
||||
// meter so we initialize upfront.
|
||||
var gasWanted int64
|
||||
var msCache sdk.CacheMultiStore
|
||||
ctx := app.getContextForAnte(mode, txBytes)
|
||||
ctx = app.initializeContext(ctx, mode)
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@ -562,15 +618,13 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
||||
}()
|
||||
|
||||
var msgs = tx.GetMsgs()
|
||||
|
||||
err := validateBasicTxMsgs(msgs)
|
||||
if err != nil {
|
||||
if err := validateBasicTxMsgs(msgs); err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
// run the ante handler
|
||||
if app.anteHandler != nil {
|
||||
newCtx, result, abort := app.anteHandler(ctx, tx)
|
||||
newCtx, result, abort := app.anteHandler(ctx, tx, (mode == runTxModeSimulate))
|
||||
if abort {
|
||||
return result
|
||||
}
|
||||
@ -581,9 +635,15 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
||||
gasWanted = result.GasWanted
|
||||
}
|
||||
|
||||
if mode == runTxModeSimulate {
|
||||
result = app.runMsgs(ctx, msgs, mode)
|
||||
result.GasWanted = gasWanted
|
||||
return
|
||||
}
|
||||
|
||||
// Keep the state in a transient CacheWrap in case processing the messages
|
||||
// fails.
|
||||
msCache := getState(app, mode).CacheMultiStore()
|
||||
msCache = getState(app, mode).CacheMultiStore()
|
||||
if msCache.TracingEnabled() {
|
||||
msCache = msCache.WithTracingContext(sdk.TraceContext(
|
||||
map[string]interface{}{"txHash": cmn.HexBytes(tmhash.Sum(txBytes)).String()},
|
||||
@ -594,8 +654,8 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
||||
result = app.runMsgs(ctx, msgs, mode)
|
||||
result.GasWanted = gasWanted
|
||||
|
||||
// only update state if all messages pass and we're not in a simulation
|
||||
if result.IsOK() && mode != runTxModeSimulate {
|
||||
// only update state if all messages pass
|
||||
if result.IsOK() {
|
||||
msCache.Write()
|
||||
}
|
||||
|
||||
|
||||
@ -14,8 +14,8 @@ import (
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -34,14 +34,14 @@ func defaultLogger() log.Logger {
|
||||
func newBaseApp(name string, options ...func(*BaseApp)) *BaseApp {
|
||||
logger := defaultLogger()
|
||||
db := dbm.NewMemDB()
|
||||
codec := wire.NewCodec()
|
||||
codec := codec.New()
|
||||
registerTestCodec(codec)
|
||||
return NewBaseApp(name, logger, db, testTxDecoder(codec), options...)
|
||||
}
|
||||
|
||||
func registerTestCodec(cdc *wire.Codec) {
|
||||
func registerTestCodec(cdc *codec.Codec) {
|
||||
// register Tx, Msg
|
||||
sdk.RegisterWire(cdc)
|
||||
sdk.RegisterCodec(cdc)
|
||||
|
||||
// register test types
|
||||
cdc.RegisterConcrete(&txTest{}, "cosmos-sdk/baseapp/txTest", nil)
|
||||
@ -290,8 +290,8 @@ type txTest struct {
|
||||
func (tx txTest) GetMsgs() []sdk.Msg { return tx.Msgs }
|
||||
|
||||
const (
|
||||
typeMsgCounter = "msgCounter"
|
||||
typeMsgCounter2 = "msgCounter2"
|
||||
routeMsgCounter = "msgCounter"
|
||||
routeMsgCounter2 = "msgCounter2"
|
||||
)
|
||||
|
||||
// ValidateBasic() fails on negative counters.
|
||||
@ -301,7 +301,8 @@ type msgCounter struct {
|
||||
}
|
||||
|
||||
// Implements Msg
|
||||
func (msg msgCounter) Type() string { return typeMsgCounter }
|
||||
func (msg msgCounter) Route() string { return routeMsgCounter }
|
||||
func (msg msgCounter) Type() string { return "counter1" }
|
||||
func (msg msgCounter) GetSignBytes() []byte { return nil }
|
||||
func (msg msgCounter) GetSigners() []sdk.AccAddress { return nil }
|
||||
func (msg msgCounter) ValidateBasic() sdk.Error {
|
||||
@ -324,14 +325,14 @@ type msgNoRoute struct {
|
||||
msgCounter
|
||||
}
|
||||
|
||||
func (tx msgNoRoute) Type() string { return "noroute" }
|
||||
func (tx msgNoRoute) Route() string { return "noroute" }
|
||||
|
||||
// a msg we dont know how to decode
|
||||
type msgNoDecode struct {
|
||||
msgCounter
|
||||
}
|
||||
|
||||
func (tx msgNoDecode) Type() string { return typeMsgCounter }
|
||||
func (tx msgNoDecode) Route() string { return routeMsgCounter }
|
||||
|
||||
// Another counter msg. Duplicate of msgCounter
|
||||
type msgCounter2 struct {
|
||||
@ -339,7 +340,8 @@ type msgCounter2 struct {
|
||||
}
|
||||
|
||||
// Implements Msg
|
||||
func (msg msgCounter2) Type() string { return typeMsgCounter2 }
|
||||
func (msg msgCounter2) Route() string { return routeMsgCounter2 }
|
||||
func (msg msgCounter2) Type() string { return "counter2" }
|
||||
func (msg msgCounter2) GetSignBytes() []byte { return nil }
|
||||
func (msg msgCounter2) GetSigners() []sdk.AccAddress { return nil }
|
||||
func (msg msgCounter2) ValidateBasic() sdk.Error {
|
||||
@ -350,7 +352,7 @@ func (msg msgCounter2) ValidateBasic() sdk.Error {
|
||||
}
|
||||
|
||||
// amino decode
|
||||
func testTxDecoder(cdc *wire.Codec) sdk.TxDecoder {
|
||||
func testTxDecoder(cdc *codec.Codec) sdk.TxDecoder {
|
||||
return func(txBytes []byte) (sdk.Tx, sdk.Error) {
|
||||
var tx txTest
|
||||
if len(txBytes) == 0 {
|
||||
@ -365,7 +367,7 @@ func testTxDecoder(cdc *wire.Codec) sdk.TxDecoder {
|
||||
}
|
||||
|
||||
func anteHandlerTxTest(t *testing.T, capKey *sdk.KVStoreKey, storeKey []byte) sdk.AnteHandler {
|
||||
return func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||
return func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||
store := ctx.KVStore(capKey)
|
||||
msgCounter := tx.(txTest).Counter
|
||||
res = incrementingCounter(t, store, storeKey, msgCounter)
|
||||
@ -438,7 +440,7 @@ func TestCheckTx(t *testing.T) {
|
||||
anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, counterKey)) }
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
// TODO: can remove this once CheckTx doesnt process msgs.
|
||||
bapp.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { return sdk.Result{} })
|
||||
bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { return sdk.Result{} })
|
||||
}
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
@ -448,7 +450,7 @@ func TestCheckTx(t *testing.T) {
|
||||
app.InitChain(abci.RequestInitChain{})
|
||||
|
||||
// Create same codec used in txDecoder
|
||||
codec := wire.NewCodec()
|
||||
codec := codec.New()
|
||||
registerTestCodec(codec)
|
||||
|
||||
for i := int64(0); i < nTxs; i++ {
|
||||
@ -484,12 +486,14 @@ func TestDeliverTx(t *testing.T) {
|
||||
|
||||
// test increments in the handler
|
||||
deliverKey := []byte("deliver-key")
|
||||
routerOpt := func(bapp *BaseApp) { bapp.Router().AddRoute(typeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey)) }
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
bapp.Router().AddRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
|
||||
}
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
|
||||
// Create same codec used in txDecoder
|
||||
codec := wire.NewCodec()
|
||||
codec := codec.New()
|
||||
registerTestCodec(codec)
|
||||
|
||||
nBlocks := 3
|
||||
@ -525,18 +529,18 @@ func TestMultiMsgDeliverTx(t *testing.T) {
|
||||
deliverKey := []byte("deliver-key")
|
||||
deliverKey2 := []byte("deliver-key2")
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
bapp.Router().AddRoute(typeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
|
||||
bapp.Router().AddRoute(typeMsgCounter2, handlerMsgCounter(t, capKey1, deliverKey2))
|
||||
bapp.Router().AddRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
|
||||
bapp.Router().AddRoute(routeMsgCounter2, handlerMsgCounter(t, capKey1, deliverKey2))
|
||||
}
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
|
||||
// Create same codec used in txDecoder
|
||||
codec := wire.NewCodec()
|
||||
codec := codec.New()
|
||||
registerTestCodec(codec)
|
||||
|
||||
// run a multi-msg tx
|
||||
// with all msgs the same type
|
||||
// with all msgs the same route
|
||||
{
|
||||
app.BeginBlock(abci.RequestBeginBlock{})
|
||||
tx := newTxCounter(0, 0, 1, 2)
|
||||
@ -595,14 +599,14 @@ func TestSimulateTx(t *testing.T) {
|
||||
gasConsumed := int64(5)
|
||||
|
||||
anteOpt := func(bapp *BaseApp) {
|
||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasConsumed))
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
bapp.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
ctx.GasMeter().ConsumeGas(gasConsumed, "test")
|
||||
return sdk.Result{GasUsed: ctx.GasMeter().GasConsumed()}
|
||||
})
|
||||
@ -613,8 +617,8 @@ func TestSimulateTx(t *testing.T) {
|
||||
app.InitChain(abci.RequestInitChain{})
|
||||
|
||||
// Create same codec used in txDecoder
|
||||
codec := wire.NewCodec()
|
||||
registerTestCodec(codec)
|
||||
cdc := codec.New()
|
||||
registerTestCodec(cdc)
|
||||
|
||||
nBlocks := 3
|
||||
for blockN := 0; blockN < nBlocks; blockN++ {
|
||||
@ -626,15 +630,15 @@ func TestSimulateTx(t *testing.T) {
|
||||
// simulate a message, check gas reported
|
||||
result := app.Simulate(tx)
|
||||
require.True(t, result.IsOK(), result.Log)
|
||||
require.Equal(t, int64(gasConsumed), result.GasUsed)
|
||||
require.Equal(t, gasConsumed, result.GasUsed)
|
||||
|
||||
// simulate again, same result
|
||||
result = app.Simulate(tx)
|
||||
require.True(t, result.IsOK(), result.Log)
|
||||
require.Equal(t, int64(gasConsumed), result.GasUsed)
|
||||
require.Equal(t, gasConsumed, result.GasUsed)
|
||||
|
||||
// simulate by calling Query with encoded tx
|
||||
txBytes, err := codec.MarshalBinary(tx)
|
||||
txBytes, err := cdc.MarshalBinary(tx)
|
||||
require.Nil(t, err)
|
||||
query := abci.RequestQuery{
|
||||
Path: "/app/simulate",
|
||||
@ -644,7 +648,7 @@ func TestSimulateTx(t *testing.T) {
|
||||
require.True(t, queryResult.IsOK(), queryResult.Log)
|
||||
|
||||
var res sdk.Result
|
||||
wire.Cdc.MustUnmarshalBinary(queryResult.Value, &res)
|
||||
codec.Cdc.MustUnmarshalBinary(queryResult.Value, &res)
|
||||
require.Nil(t, err, "Result unmarshalling failed")
|
||||
require.True(t, res.IsOK(), res.Log)
|
||||
require.Equal(t, gasConsumed, res.GasUsed, res.Log)
|
||||
@ -659,10 +663,12 @@ func TestSimulateTx(t *testing.T) {
|
||||
|
||||
func TestRunInvalidTransaction(t *testing.T) {
|
||||
anteOpt := func(bapp *BaseApp) {
|
||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return })
|
||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||
return
|
||||
})
|
||||
}
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
bapp.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return })
|
||||
bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return })
|
||||
}
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
@ -719,7 +725,7 @@ func TestRunInvalidTransaction(t *testing.T) {
|
||||
tx.Msgs = append(tx.Msgs, msgNoDecode{})
|
||||
|
||||
// new codec so we can encode the tx, but we shouldn't be able to decode
|
||||
newCdc := wire.NewCodec()
|
||||
newCdc := codec.New()
|
||||
registerTestCodec(newCdc)
|
||||
newCdc.RegisterConcrete(&msgNoDecode{}, "cosmos-sdk/baseapp/msgNoDecode", nil)
|
||||
|
||||
@ -734,7 +740,7 @@ func TestRunInvalidTransaction(t *testing.T) {
|
||||
func TestTxGasLimits(t *testing.T) {
|
||||
gasGranted := int64(10)
|
||||
anteOpt := func(bapp *BaseApp) {
|
||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted))
|
||||
|
||||
// NOTE/TODO/XXX:
|
||||
@ -767,7 +773,7 @@ func TestTxGasLimits(t *testing.T) {
|
||||
}
|
||||
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
bapp.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
count := msg.(msgCounter).Counter
|
||||
ctx.GasMeter().ConsumeGas(count, "counter-handler")
|
||||
return sdk.Result{}
|
||||
@ -817,92 +823,3 @@ func TestTxGasLimits(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------------
|
||||
// Queries
|
||||
|
||||
// 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) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||
store := ctx.KVStore(capKey1)
|
||||
store.Set(key, value)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
bapp.Router().AddRoute(typeMsgCounter, 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,9 +1,12 @@
|
||||
// nolint: golint
|
||||
package baseapp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
)
|
||||
|
||||
// File for storing in-package BaseApp optional functions,
|
||||
@ -20,9 +23,100 @@ func SetPruning(pruning string) func(*BaseApp) {
|
||||
case "syncable":
|
||||
pruningEnum = sdk.PruneSyncable
|
||||
default:
|
||||
panic(fmt.Sprintf("Invalid pruning strategy: %s", pruning))
|
||||
panic(fmt.Sprintf("invalid pruning strategy: %s", pruning))
|
||||
}
|
||||
return func(bap *BaseApp) {
|
||||
bap.cms.SetPruning(pruningEnum)
|
||||
}
|
||||
}
|
||||
|
||||
// SetMinimumFees returns an option that sets the minimum fees on the app.
|
||||
func SetMinimumFees(minFees string) func(*BaseApp) {
|
||||
fees, err := sdk.ParseCoins(minFees)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("invalid minimum fees: %v", err))
|
||||
}
|
||||
return func(bap *BaseApp) { bap.SetMinimumFees(fees) }
|
||||
}
|
||||
|
||||
func (app *BaseApp) SetName(name string) {
|
||||
if app.sealed {
|
||||
panic("SetName() on sealed BaseApp")
|
||||
}
|
||||
app.name = name
|
||||
}
|
||||
|
||||
func (app *BaseApp) SetDB(db dbm.DB) {
|
||||
if app.sealed {
|
||||
panic("SetDB() on sealed BaseApp")
|
||||
}
|
||||
app.db = db
|
||||
}
|
||||
|
||||
func (app *BaseApp) SetCMS(cms store.CommitMultiStore) {
|
||||
if app.sealed {
|
||||
panic("SetEndBlocker() on sealed BaseApp")
|
||||
}
|
||||
app.cms = cms
|
||||
}
|
||||
|
||||
func (app *BaseApp) SetInitChainer(initChainer sdk.InitChainer) {
|
||||
if app.sealed {
|
||||
panic("SetInitChainer() on sealed BaseApp")
|
||||
}
|
||||
app.initChainer = initChainer
|
||||
}
|
||||
|
||||
func (app *BaseApp) SetBeginBlocker(beginBlocker sdk.BeginBlocker) {
|
||||
if app.sealed {
|
||||
panic("SetBeginBlocker() on sealed BaseApp")
|
||||
}
|
||||
app.beginBlocker = beginBlocker
|
||||
}
|
||||
|
||||
func (app *BaseApp) SetEndBlocker(endBlocker sdk.EndBlocker) {
|
||||
if app.sealed {
|
||||
panic("SetEndBlocker() on sealed BaseApp")
|
||||
}
|
||||
app.endBlocker = endBlocker
|
||||
}
|
||||
|
||||
func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) {
|
||||
if app.sealed {
|
||||
panic("SetAnteHandler() on sealed BaseApp")
|
||||
}
|
||||
app.anteHandler = ah
|
||||
}
|
||||
|
||||
func (app *BaseApp) SetAddrPeerFilter(pf sdk.PeerFilter) {
|
||||
if app.sealed {
|
||||
panic("SetAddrPeerFilter() on sealed BaseApp")
|
||||
}
|
||||
app.addrPeerFilter = pf
|
||||
}
|
||||
|
||||
func (app *BaseApp) SetPubKeyPeerFilter(pf sdk.PeerFilter) {
|
||||
if app.sealed {
|
||||
panic("SetPubKeyPeerFilter() on sealed BaseApp")
|
||||
}
|
||||
app.pubkeyPeerFilter = pf
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
96
baseapp/query_test.go
Normal file
96
baseapp/query_test.go
Normal file
@ -0,0 +1,96 @@
|
||||
package baseapp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
abci "github.com/tendermint/tendermint/abci/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)
|
||||
}
|
||||
41
baseapp/queryrouter.go
Normal file
41
baseapp/queryrouter.go
Normal file
@ -0,0 +1,41 @@
|
||||
package baseapp
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// QueryRouter provides queryables for each query path.
|
||||
type QueryRouter interface {
|
||||
AddRoute(r string, h sdk.Querier) (rtr QueryRouter)
|
||||
Route(path string) (h sdk.Querier)
|
||||
}
|
||||
|
||||
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{
|
||||
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) {
|
||||
panic("route expressions can only contain alphanumeric characters")
|
||||
}
|
||||
if rtr.routes[r] != nil {
|
||||
panic("route has already been initialized")
|
||||
}
|
||||
rtr.routes[r] = q
|
||||
return rtr
|
||||
}
|
||||
|
||||
// Returns the sdk.Querier for a certain route path
|
||||
func (rtr *queryrouter) Route(path string) (h sdk.Querier) {
|
||||
return rtr.routes[path]
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
package baseapp
|
||||
|
||||
import (
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// nolint - Setter functions
|
||||
func (app *BaseApp) SetName(name string) {
|
||||
if app.sealed {
|
||||
panic("SetName() on sealed BaseApp")
|
||||
}
|
||||
app.name = name
|
||||
}
|
||||
func (app *BaseApp) SetDB(db dbm.DB) {
|
||||
if app.sealed {
|
||||
panic("SetDB() on sealed BaseApp")
|
||||
}
|
||||
app.db = db
|
||||
}
|
||||
func (app *BaseApp) SetCMS(cms store.CommitMultiStore) {
|
||||
if app.sealed {
|
||||
panic("SetEndBlocker() on sealed BaseApp")
|
||||
}
|
||||
app.cms = cms
|
||||
}
|
||||
func (app *BaseApp) SetTxDecoder(txDecoder sdk.TxDecoder) {
|
||||
if app.sealed {
|
||||
panic("SetTxDecoder() on sealed BaseApp")
|
||||
}
|
||||
app.txDecoder = txDecoder
|
||||
}
|
||||
func (app *BaseApp) SetInitChainer(initChainer sdk.InitChainer) {
|
||||
if app.sealed {
|
||||
panic("SetInitChainer() on sealed BaseApp")
|
||||
}
|
||||
app.initChainer = initChainer
|
||||
}
|
||||
func (app *BaseApp) SetBeginBlocker(beginBlocker sdk.BeginBlocker) {
|
||||
if app.sealed {
|
||||
panic("SetBeginBlocker() on sealed BaseApp")
|
||||
}
|
||||
app.beginBlocker = beginBlocker
|
||||
}
|
||||
func (app *BaseApp) SetEndBlocker(endBlocker sdk.EndBlocker) {
|
||||
if app.sealed {
|
||||
panic("SetEndBlocker() on sealed BaseApp")
|
||||
}
|
||||
app.endBlocker = endBlocker
|
||||
}
|
||||
func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) {
|
||||
if app.sealed {
|
||||
panic("SetAnteHandler() on sealed BaseApp")
|
||||
}
|
||||
app.anteHandler = ah
|
||||
}
|
||||
func (app *BaseApp) SetAddrPeerFilter(pf sdk.PeerFilter) {
|
||||
if app.sealed {
|
||||
panic("SetAddrPeerFilter() on sealed BaseApp")
|
||||
}
|
||||
app.addrPeerFilter = pf
|
||||
}
|
||||
func (app *BaseApp) SetPubKeyPeerFilter(pf sdk.PeerFilter) {
|
||||
if app.sealed {
|
||||
panic("SetPubKeyPeerFilter() on sealed BaseApp")
|
||||
}
|
||||
app.pubkeyPeerFilter = pf
|
||||
}
|
||||
func (app *BaseApp) Router() Router {
|
||||
if app.sealed {
|
||||
panic("Router() on sealed BaseApp")
|
||||
}
|
||||
return app.router
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
131
client/config.go
Normal file
131
client/config.go
Normal file
@ -0,0 +1,131 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pelletier/go-toml"
|
||||
"github.com/spf13/cobra"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
type cliConfig struct {
|
||||
Home string `toml:"home"`
|
||||
ChainID string `toml:"chain_id"`
|
||||
TrustNode bool `toml:"trust_node"`
|
||||
Encoding string `toml:"encoding"`
|
||||
Output string `toml:"output"`
|
||||
Node string `toml:"node"`
|
||||
Trace bool `toml:"trace"`
|
||||
}
|
||||
|
||||
// ConfigCmd returns a CLI command to interactively create a
|
||||
// Gaia CLI config file.
|
||||
func ConfigCmd() *cobra.Command {
|
||||
cfg := &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Interactively creates a Gaia CLI config file",
|
||||
RunE: runConfigCmd,
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func runConfigCmd(cmd *cobra.Command, args []string) error {
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stdin := BufferStdin()
|
||||
gaiaCLIHome, err := handleGaiaCLIHome(home, stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
node, err := handleNode(stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
trustNode, err := handleTrustNode(stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encoding := "btc"
|
||||
output := "text"
|
||||
var chainID string
|
||||
chainID, err = types.DefaultChainID()
|
||||
if err != nil {
|
||||
fmt.Println("Couldn't populate ChainID, so using an empty one.")
|
||||
}
|
||||
|
||||
cfg := &cliConfig{
|
||||
Home: gaiaCLIHome,
|
||||
ChainID: chainID,
|
||||
TrustNode: trustNode,
|
||||
Encoding: encoding,
|
||||
Output: output,
|
||||
Node: node,
|
||||
Trace: false,
|
||||
}
|
||||
|
||||
return createGaiaCLIConfig(cfg)
|
||||
}
|
||||
|
||||
func handleGaiaCLIHome(dir string, stdin *bufio.Reader) (string, error) {
|
||||
dirName := ".gaiacli"
|
||||
home, err := GetString(fmt.Sprintf("Where is your gaiacli home directory? (Default: ~/%s)", dirName), stdin)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if home == "" {
|
||||
home = path.Join(dir, dirName)
|
||||
}
|
||||
|
||||
return home, nil
|
||||
}
|
||||
|
||||
func handleNode(stdin *bufio.Reader) (string, error) {
|
||||
defaultNode := "tcp://localhost:26657"
|
||||
node, err := GetString(fmt.Sprintf("Where is your validator node running? (Default: %s)", defaultNode), stdin)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if node == "" {
|
||||
node = defaultNode
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func handleTrustNode(stdin *bufio.Reader) (bool, error) {
|
||||
return GetConfirmation("Do you trust this node?", stdin)
|
||||
}
|
||||
|
||||
func createGaiaCLIConfig(cfg *cliConfig) error {
|
||||
cfgPath := path.Join(cfg.Home, "config")
|
||||
err := os.MkdirAll(cfgPath, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := toml.Marshal(*cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfgFile := path.Join(cfgPath, "config.toml")
|
||||
if info, err := os.Stat(cfgFile); err == nil && !info.IsDir() {
|
||||
err = os.Rename(cfgFile, path.Join(cfgPath, "config.toml-old"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(cfgFile, data, os.ModePerm)
|
||||
}
|
||||
170
client/context/broadcast.go
Normal file
170
client/context/broadcast.go
Normal file
@ -0,0 +1,170 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
// TODO: This should get deleted eventually, and perhaps
|
||||
// ctypes.ResultBroadcastTx be stripped of unused fields, and
|
||||
// ctypes.ResultBroadcastTxCommit returned for tendermint RPC BroadcastTxSync.
|
||||
//
|
||||
// The motivation is that we want a unified type to return, and the better
|
||||
// option is the one that can hold CheckTx/DeliverTx responses optionally.
|
||||
func resultBroadcastTxToCommit(res *ctypes.ResultBroadcastTx) *ctypes.ResultBroadcastTxCommit {
|
||||
return &ctypes.ResultBroadcastTxCommit{
|
||||
Hash: res.Hash,
|
||||
// NOTE: other fields are unused for async.
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastTx broadcasts a transactions either synchronously or asynchronously
|
||||
// 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) {
|
||||
if ctx.Async {
|
||||
res, err := ctx.broadcastTxAsync(txBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resCommit := resultBroadcastTxToCommit(res)
|
||||
return resCommit, err
|
||||
}
|
||||
|
||||
return ctx.broadcastTxCommit(txBytes)
|
||||
}
|
||||
|
||||
// BroadcastTxAndAwaitCommit broadcasts transaction bytes to a Tendermint node
|
||||
// and waits for a commit.
|
||||
func (ctx CLIContext) BroadcastTxAndAwaitCommit(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := node.BroadcastTxCommit(tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
if !res.CheckTx.IsOK() {
|
||||
return res, errors.Errorf(res.CheckTx.Log)
|
||||
}
|
||||
|
||||
if !res.DeliverTx.IsOK() {
|
||||
return res, errors.Errorf(res.DeliverTx.Log)
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// BroadcastTxSync broadcasts transaction bytes to a Tendermint node
|
||||
// synchronously.
|
||||
func (ctx CLIContext) BroadcastTxSync(tx []byte) (*ctypes.ResultBroadcastTx, error) {
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := node.BroadcastTxSync(tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// BroadcastTxAsync broadcasts transaction bytes to a Tendermint node
|
||||
// asynchronously.
|
||||
func (ctx CLIContext) BroadcastTxAsync(tx []byte) (*ctypes.ResultBroadcastTx, error) {
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := node.BroadcastTxAsync(tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
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.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.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
|
||||
}
|
||||
@ -1,35 +1,56 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
cskeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmlite "github.com/tendermint/tendermint/lite"
|
||||
tmliteProxy "github.com/tendermint/tendermint/lite/proxy"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
)
|
||||
|
||||
const ctxAccStoreName = "acc"
|
||||
|
||||
var (
|
||||
verifier tmlite.Verifier
|
||||
)
|
||||
|
||||
// CLIContext implements a typical CLI context created in SDK modules for
|
||||
// transaction handling and queries.
|
||||
type CLIContext struct {
|
||||
Codec *wire.Codec
|
||||
AccDecoder auth.AccountDecoder
|
||||
Client rpcclient.Client
|
||||
Logger io.Writer
|
||||
Height int64
|
||||
NodeURI string
|
||||
FromAddressName string
|
||||
AccountStore string
|
||||
TrustNode bool
|
||||
UseLedger bool
|
||||
Async bool
|
||||
JSON bool
|
||||
PrintResponse bool
|
||||
Codec *codec.Codec
|
||||
AccDecoder auth.AccountDecoder
|
||||
Client rpcclient.Client
|
||||
Output io.Writer
|
||||
Height int64
|
||||
NodeURI string
|
||||
From string
|
||||
AccountStore string
|
||||
TrustNode bool
|
||||
UseLedger bool
|
||||
Async bool
|
||||
JSON bool
|
||||
PrintResponse bool
|
||||
Verifier tmlite.Verifier
|
||||
DryRun bool
|
||||
GenerateOnly bool
|
||||
fromAddress types.AccAddress
|
||||
fromName string
|
||||
Indent bool
|
||||
}
|
||||
|
||||
// NewCLIContext returns a new initialized CLIContext with parameters from the
|
||||
@ -42,22 +63,109 @@ func NewCLIContext() CLIContext {
|
||||
rpc = rpcclient.NewHTTP(nodeURI, "/websocket")
|
||||
}
|
||||
|
||||
from := viper.GetString(client.FlagFrom)
|
||||
fromAddress, fromName := fromFields(from)
|
||||
|
||||
// We need to use a single verifier for all contexts
|
||||
if verifier == nil {
|
||||
verifier = createVerifier()
|
||||
}
|
||||
|
||||
return CLIContext{
|
||||
Client: rpc,
|
||||
NodeURI: nodeURI,
|
||||
AccountStore: ctxAccStoreName,
|
||||
FromAddressName: viper.GetString(client.FlagFrom),
|
||||
Height: viper.GetInt64(client.FlagHeight),
|
||||
TrustNode: viper.GetBool(client.FlagTrustNode),
|
||||
UseLedger: viper.GetBool(client.FlagUseLedger),
|
||||
Async: viper.GetBool(client.FlagAsync),
|
||||
JSON: viper.GetBool(client.FlagJson),
|
||||
PrintResponse: viper.GetBool(client.FlagPrintResponse),
|
||||
Client: rpc,
|
||||
Output: os.Stdout,
|
||||
NodeURI: nodeURI,
|
||||
AccountStore: ctxAccStoreName,
|
||||
From: viper.GetString(client.FlagFrom),
|
||||
Height: viper.GetInt64(client.FlagHeight),
|
||||
TrustNode: viper.GetBool(client.FlagTrustNode),
|
||||
UseLedger: viper.GetBool(client.FlagUseLedger),
|
||||
Async: viper.GetBool(client.FlagAsync),
|
||||
JSON: viper.GetBool(client.FlagJson),
|
||||
PrintResponse: viper.GetBool(client.FlagPrintResponse),
|
||||
Verifier: verifier,
|
||||
DryRun: viper.GetBool(client.FlagDryRun),
|
||||
GenerateOnly: viper.GetBool(client.FlagGenerateOnly),
|
||||
fromAddress: fromAddress,
|
||||
fromName: fromName,
|
||||
Indent: viper.GetBool(client.FlagIndentResponse),
|
||||
}
|
||||
}
|
||||
|
||||
func createVerifier() tmlite.Verifier {
|
||||
trustNodeDefined := viper.IsSet(client.FlagTrustNode)
|
||||
if !trustNodeDefined {
|
||||
return nil
|
||||
}
|
||||
|
||||
trustNode := viper.GetBool(client.FlagTrustNode)
|
||||
if trustNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
home := viper.GetString(cli.HomeFlag)
|
||||
nodeURI := viper.GetString(client.FlagNode)
|
||||
|
||||
var errMsg bytes.Buffer
|
||||
if chainID == "" {
|
||||
errMsg.WriteString("--chain-id ")
|
||||
}
|
||||
if home == "" {
|
||||
errMsg.WriteString("--home ")
|
||||
}
|
||||
if nodeURI == "" {
|
||||
errMsg.WriteString("--node ")
|
||||
}
|
||||
if errMsg.Len() != 0 {
|
||||
fmt.Printf("Must specify these options: %s when --trust-node is false\n", errMsg.String())
|
||||
os.Exit(1)
|
||||
}
|
||||
node := rpcclient.NewHTTP(nodeURI, "/websocket")
|
||||
verifier, err := tmliteProxy.NewVerifier(chainID, filepath.Join(home, ".gaialite"), node, log.NewNopLogger())
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Create verifier failed: %s\n", err.Error())
|
||||
fmt.Printf("Please check network connection and verify the address of the node to connect to\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
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 *wire.Codec) CLIContext {
|
||||
func (ctx CLIContext) WithCodec(cdc *codec.Codec) CLIContext {
|
||||
ctx.Codec = cdc
|
||||
return ctx
|
||||
}
|
||||
@ -69,9 +177,9 @@ func (ctx CLIContext) WithAccountDecoder(decoder auth.AccountDecoder) CLIContext
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithLogger returns a copy of the context with an updated logger.
|
||||
func (ctx CLIContext) WithLogger(w io.Writer) CLIContext {
|
||||
ctx.Logger = w
|
||||
// WithOutput returns a copy of the context with an updated output writer (e.g. stdout).
|
||||
func (ctx CLIContext) WithOutput(w io.Writer) CLIContext {
|
||||
ctx.Output = w
|
||||
return ctx
|
||||
}
|
||||
|
||||
@ -81,10 +189,9 @@ func (ctx CLIContext) WithAccountStore(accountStore string) CLIContext {
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithFromAddressName returns a copy of the context with an updated from
|
||||
// address.
|
||||
func (ctx CLIContext) WithFromAddressName(addrName string) CLIContext {
|
||||
ctx.FromAddressName = addrName
|
||||
// WithFrom returns a copy of the context with an updated from address or name.
|
||||
func (ctx CLIContext) WithFrom(from string) CLIContext {
|
||||
ctx.From = from
|
||||
return ctx
|
||||
}
|
||||
|
||||
@ -113,3 +220,9 @@ func (ctx CLIContext) WithUseLedger(useLedger bool) CLIContext {
|
||||
ctx.UseLedger = useLedger
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithVerifier - return a copy of the context with an updated Verifier
|
||||
func (ctx CLIContext) WithVerifier(verifier tmlite.Verifier) CLIContext {
|
||||
ctx.Verifier = verifier
|
||||
return ctx
|
||||
}
|
||||
|
||||
@ -11,3 +11,11 @@ func ErrInvalidAccount(addr sdk.AccAddress) error {
|
||||
return errors.Errorf(`No account with address %s was found in the state.
|
||||
Are you sure there has been a transaction involving it?`, addr)
|
||||
}
|
||||
|
||||
// ErrVerifyCommit returns a common error reflecting that the blockchain commit at a given
|
||||
// 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.
|
||||
Can't verify blockchain proof at this height. Please set --trust-node to true and try again`, height)
|
||||
}
|
||||
|
||||
@ -2,18 +2,22 @@ package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/common"
|
||||
"strings"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
tmliteErr "github.com/tendermint/tendermint/lite/errors"
|
||||
tmliteProxy "github.com/tendermint/tendermint/lite/proxy"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// GetNode returns an RPC client. If the context's client is not defined, an
|
||||
@ -27,8 +31,13 @@ func (ctx CLIContext) GetNode() (rpcclient.Client, error) {
|
||||
}
|
||||
|
||||
// Query performs a query for information about the connected node.
|
||||
func (ctx CLIContext) Query(path string) (res []byte, err error) {
|
||||
return ctx.query(path, nil)
|
||||
func (ctx CLIContext) Query(path string, data cmn.HexBytes) (res []byte, err error) {
|
||||
return ctx.query(path, data)
|
||||
}
|
||||
|
||||
// Query information about the connected node with a data payload
|
||||
func (ctx CLIContext) QueryWithData(path string, data []byte) (res []byte, err error) {
|
||||
return ctx.query(path, data)
|
||||
}
|
||||
|
||||
// QueryStore performs a query from a Tendermint node with the provided key and
|
||||
@ -72,22 +81,13 @@ func (ctx CLIContext) GetAccount(address []byte) (auth.Account, error) {
|
||||
}
|
||||
|
||||
// GetFromAddress returns the from address from the context's name.
|
||||
func (ctx CLIContext) GetFromAddress() (from sdk.AccAddress, err error) {
|
||||
if ctx.FromAddressName == "" {
|
||||
return nil, errors.Errorf("must provide a from address name")
|
||||
}
|
||||
func (ctx CLIContext) GetFromAddress() (sdk.AccAddress, error) {
|
||||
return ctx.fromAddress, nil
|
||||
}
|
||||
|
||||
keybase, err := keys.GetKeyBase()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := keybase.Get(ctx.FromAddressName)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("no key for: %s", ctx.FromAddressName)
|
||||
}
|
||||
|
||||
return sdk.AccAddress(info.GetPubKey().Address()), nil
|
||||
// GetFromName returns the key name for the current context.
|
||||
func (ctx CLIContext) GetFromName() (string, error) {
|
||||
return ctx.fromName, nil
|
||||
}
|
||||
|
||||
// GetAccountNumber returns the next account number for the given account
|
||||
@ -112,49 +112,6 @@ func (ctx CLIContext) GetAccountSequence(address []byte) (int64, error) {
|
||||
return account.GetSequence(), nil
|
||||
}
|
||||
|
||||
// BroadcastTx broadcasts transaction bytes to a Tendermint node.
|
||||
func (ctx CLIContext) BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := node.BroadcastTxCommit(tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
if !res.CheckTx.IsOK() {
|
||||
return res, errors.Errorf("checkTx failed: (%d) %s",
|
||||
res.CheckTx.Code,
|
||||
res.CheckTx.Log)
|
||||
}
|
||||
|
||||
if !res.DeliverTx.IsOK() {
|
||||
return res, errors.Errorf("deliverTx failed: (%d) %s",
|
||||
res.DeliverTx.Code,
|
||||
res.DeliverTx.Log)
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// BroadcastTxAsync broadcasts transaction bytes to a Tendermint node
|
||||
// asynchronously.
|
||||
func (ctx CLIContext) BroadcastTxAsync(tx []byte) (*ctypes.ResultBroadcastTx, error) {
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := node.BroadcastTxAsync(tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// EnsureAccountExists ensures that an account exists for a given context. An
|
||||
// error is returned if it does not.
|
||||
func (ctx CLIContext) EnsureAccountExists() error {
|
||||
@ -191,95 +148,9 @@ func (ctx CLIContext) EnsureAccountExistsFromAddr(addr sdk.AccAddress) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsureBroadcastTx 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) EnsureBroadcastTx(txBytes []byte) error {
|
||||
if ctx.Async {
|
||||
return ctx.ensureBroadcastTxAsync(txBytes)
|
||||
}
|
||||
|
||||
return ctx.ensureBroadcastTx(txBytes)
|
||||
}
|
||||
|
||||
func (ctx CLIContext) ensureBroadcastTxAsync(txBytes []byte) error {
|
||||
res, err := ctx.BroadcastTxAsync(txBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.JSON {
|
||||
type toJSON struct {
|
||||
TxHash string
|
||||
}
|
||||
|
||||
if ctx.Logger != nil {
|
||||
resJSON := toJSON{res.Hash.String()}
|
||||
bz, err := ctx.Codec.MarshalJSON(resJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Logger.Write(bz)
|
||||
io.WriteString(ctx.Logger, "\n")
|
||||
}
|
||||
} else {
|
||||
if ctx.Logger != nil {
|
||||
io.WriteString(ctx.Logger, fmt.Sprintf("Async tx sent (tx hash: %s)\n", res.Hash))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx CLIContext) ensureBroadcastTx(txBytes []byte) error {
|
||||
res, err := ctx.BroadcastTx(txBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.JSON {
|
||||
// since JSON is intended for automated scripts, always include
|
||||
// response in JSON mode.
|
||||
type toJSON struct {
|
||||
Height int64
|
||||
TxHash string
|
||||
Response string
|
||||
}
|
||||
|
||||
if ctx.Logger != nil {
|
||||
resJSON := toJSON{res.Height, res.Hash.String(), fmt.Sprintf("%+v", res.DeliverTx)}
|
||||
bz, err := ctx.Codec.MarshalJSON(resJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Logger.Write(bz)
|
||||
io.WriteString(ctx.Logger, "\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if ctx.Logger != 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.Logger, resStr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// query performs a query from a Tendermint node with the provided store name
|
||||
// and path.
|
||||
func (ctx CLIContext) query(path string, key common.HexBytes) (res []byte, err error) {
|
||||
func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err error) {
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return res, err
|
||||
@ -297,15 +168,93 @@ func (ctx CLIContext) query(path string, key common.HexBytes) (res []byte, err e
|
||||
|
||||
resp := result.Response
|
||||
if !resp.IsOK() {
|
||||
return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log)
|
||||
return res, errors.Errorf(resp.Log)
|
||||
}
|
||||
|
||||
// data from trusted node or subspace query doesn't need verification
|
||||
if ctx.TrustNode || !isQueryStoreWithProof(path) {
|
||||
return resp.Value, nil
|
||||
}
|
||||
|
||||
err = ctx.verifyProof(path, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp.Value, nil
|
||||
}
|
||||
|
||||
// Verify verifies the consensus proof at given height.
|
||||
func (ctx CLIContext) Verify(height int64) (tmtypes.SignedHeader, error) {
|
||||
check, err := tmliteProxy.GetCertifiedCommit(height, ctx.Client, ctx.Verifier)
|
||||
switch {
|
||||
case tmliteErr.IsErrCommitNotFound(err):
|
||||
return tmtypes.SignedHeader{}, ErrVerifyCommit(height)
|
||||
case err != nil:
|
||||
return tmtypes.SignedHeader{}, err
|
||||
}
|
||||
|
||||
return check, nil
|
||||
}
|
||||
|
||||
// verifyProof perform response proof verification.
|
||||
func (ctx CLIContext) verifyProof(_ string, resp abci.ResponseQuery) error {
|
||||
if ctx.Verifier == nil {
|
||||
return fmt.Errorf("missing valid certifier to verify data from distrusted node")
|
||||
}
|
||||
|
||||
// the AppHash for height H is in header H+1
|
||||
commit, err := ctx.Verify(resp.Height + 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var multiStoreProof store.MultiStoreProof
|
||||
cdc := codec.New()
|
||||
|
||||
err = cdc.UnmarshalBinary(resp.Proof, &multiStoreProof)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshalBinary rangeProof")
|
||||
}
|
||||
|
||||
// verify the substore commit hash against trusted appHash
|
||||
substoreCommitHash, err := store.VerifyMultiStoreCommitInfo(
|
||||
multiStoreProof.StoreName, multiStoreProof.StoreInfos, commit.Header.AppHash,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed in verifying the proof against appHash")
|
||||
}
|
||||
|
||||
err = store.VerifyRangeProof(resp.Key, resp.Value, substoreCommitHash, &multiStoreProof.RangeProof)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed in the range proof verification")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// queryStore performs a query from a Tendermint node with the provided a store
|
||||
// name and path.
|
||||
func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([]byte, error) {
|
||||
path := fmt.Sprintf("/store/%s/%s", storeName, endPath)
|
||||
return ctx.query(path, key)
|
||||
}
|
||||
|
||||
// isQueryStoreWithProof expects a format like /<queryType>/<storeName>/<subpath>
|
||||
// queryType can be app or store.
|
||||
func isQueryStoreWithProof(path string) bool {
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
return false
|
||||
}
|
||||
|
||||
paths := strings.SplitN(path[1:], "/", 3)
|
||||
if len(paths) != 3 {
|
||||
return false
|
||||
}
|
||||
|
||||
if store.RequireProof("/" + paths[2]) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
120
client/flags.go
120
client/flags.go
@ -1,39 +1,63 @@
|
||||
package client
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// nolint
|
||||
const (
|
||||
FlagUseLedger = "ledger"
|
||||
FlagChainID = "chain-id"
|
||||
FlagNode = "node"
|
||||
FlagHeight = "height"
|
||||
FlagGas = "gas"
|
||||
FlagTrustNode = "trust-node"
|
||||
FlagFrom = "from"
|
||||
FlagName = "name"
|
||||
FlagAccountNumber = "account-number"
|
||||
FlagSequence = "sequence"
|
||||
FlagMemo = "memo"
|
||||
FlagFee = "fee"
|
||||
FlagAsync = "async"
|
||||
FlagJson = "json"
|
||||
FlagPrintResponse = "print-response"
|
||||
// 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
|
||||
GasFlagSimulate = "simulate"
|
||||
|
||||
FlagUseLedger = "ledger"
|
||||
FlagChainID = "chain-id"
|
||||
FlagNode = "node"
|
||||
FlagHeight = "height"
|
||||
FlagGas = "gas"
|
||||
FlagGasAdjustment = "gas-adjustment"
|
||||
FlagTrustNode = "trust-node"
|
||||
FlagFrom = "from"
|
||||
FlagName = "name"
|
||||
FlagAccountNumber = "account-number"
|
||||
FlagSequence = "sequence"
|
||||
FlagMemo = "memo"
|
||||
FlagFee = "fee"
|
||||
FlagAsync = "async"
|
||||
FlagJson = "json"
|
||||
FlagPrintResponse = "print-response"
|
||||
FlagDryRun = "dry-run"
|
||||
FlagGenerateOnly = "generate-only"
|
||||
FlagIndentResponse = "indent"
|
||||
)
|
||||
|
||||
// LineBreak can be included in a command list to provide a blank line
|
||||
// to help with readability
|
||||
var LineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}}
|
||||
var (
|
||||
LineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}}
|
||||
GasFlagVar = GasSetting{Gas: DefaultGasLimit}
|
||||
)
|
||||
|
||||
// GetCommands adds common flags to query commands
|
||||
func GetCommands(cmds ...*cobra.Command) []*cobra.Command {
|
||||
for _, c := range cmds {
|
||||
// TODO: make this default false when we support proofs
|
||||
c.Flags().Bool(FlagTrustNode, true, "Don't verify proofs for responses")
|
||||
c.Flags().Bool(FlagIndentResponse, false, "Add indent to JSON response")
|
||||
c.Flags().Bool(FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)")
|
||||
c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device")
|
||||
c.Flags().String(FlagChainID, "", "Chain ID of tendermint node")
|
||||
c.Flags().String(FlagNode, "tcp://localhost:26657", "<host>:<port> to tendermint rpc interface for this chain")
|
||||
c.Flags().Int64(FlagHeight, 0, "block height to query, omit to get most recent provable block")
|
||||
viper.BindPFlag(FlagTrustNode, c.Flags().Lookup(FlagTrustNode))
|
||||
viper.BindPFlag(FlagUseLedger, c.Flags().Lookup(FlagUseLedger))
|
||||
viper.BindPFlag(FlagChainID, c.Flags().Lookup(FlagChainID))
|
||||
viper.BindPFlag(FlagNode, c.Flags().Lookup(FlagNode))
|
||||
}
|
||||
return cmds
|
||||
}
|
||||
@ -41,7 +65,8 @@ func GetCommands(cmds ...*cobra.Command) []*cobra.Command {
|
||||
// PostCommands adds common flags for commands to post tx
|
||||
func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
|
||||
for _, c := range cmds {
|
||||
c.Flags().String(FlagFrom, "", "Name of private key with which to sign")
|
||||
c.Flags().Bool(FlagIndentResponse, false, "Add indent to JSON response")
|
||||
c.Flags().String(FlagFrom, "", "Name or address of private key with which to sign")
|
||||
c.Flags().Int64(FlagAccountNumber, 0, "AccountNumber number to sign the tx")
|
||||
c.Flags().Int64(FlagSequence, 0, "Sequence number to sign the tx")
|
||||
c.Flags().String(FlagMemo, "", "Memo to send along with transaction")
|
||||
@ -49,10 +74,61 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
|
||||
c.Flags().String(FlagChainID, "", "Chain ID of tendermint node")
|
||||
c.Flags().String(FlagNode, "tcp://localhost:26657", "<host>:<port> to tendermint rpc interface for this chain")
|
||||
c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device")
|
||||
c.Flags().Int64(FlagGas, 200000, "gas limit to set per-transaction")
|
||||
c.Flags().Float64(FlagGasAdjustment, DefaultGasAdjustment, "adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored ")
|
||||
c.Flags().Bool(FlagAsync, false, "broadcast transactions asynchronously")
|
||||
c.Flags().Bool(FlagJson, false, "return output in json format")
|
||||
c.Flags().Bool(FlagPrintResponse, false, "return tx response (only works with async = false)")
|
||||
c.Flags().Bool(FlagPrintResponse, true, "return tx response (only works with async = false)")
|
||||
c.Flags().Bool(FlagTrustNode, true, "Trust connected full node (don't verify proofs for responses)")
|
||||
c.Flags().Bool(FlagDryRun, false, "ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it")
|
||||
c.Flags().Bool(FlagGenerateOnly, false, "build an unsigned transaction and write it to STDOUT")
|
||||
// --gas can accept integers and "simulate"
|
||||
c.Flags().Var(&GasFlagVar, "gas", fmt.Sprintf(
|
||||
"gas limit to set per-transaction; set to %q to calculate required gas automatically (default %d)", GasFlagSimulate, DefaultGasLimit))
|
||||
viper.BindPFlag(FlagTrustNode, c.Flags().Lookup(FlagTrustNode))
|
||||
viper.BindPFlag(FlagUseLedger, c.Flags().Lookup(FlagUseLedger))
|
||||
viper.BindPFlag(FlagChainID, c.Flags().Lookup(FlagChainID))
|
||||
viper.BindPFlag(FlagNode, c.Flags().Lookup(FlagNode))
|
||||
}
|
||||
return cmds
|
||||
}
|
||||
|
||||
// Gas flag parsing functions
|
||||
|
||||
// GasSetting encapsulates the possible values passed through the --gas flag.
|
||||
type GasSetting struct {
|
||||
Simulate bool
|
||||
Gas int64
|
||||
}
|
||||
|
||||
// Type returns the flag's value type.
|
||||
func (v *GasSetting) Type() string { return "string" }
|
||||
|
||||
// Set parses and sets the value of the --gas flag.
|
||||
func (v *GasSetting) Set(s string) (err error) {
|
||||
v.Simulate, v.Gas, err = ReadGasFlag(s)
|
||||
return
|
||||
}
|
||||
|
||||
func (v *GasSetting) String() string {
|
||||
if v.Simulate {
|
||||
return GasFlagSimulate
|
||||
}
|
||||
return strconv.FormatInt(v.Gas, 10)
|
||||
}
|
||||
|
||||
// ParseGasFlag parses the value of the --gas flag.
|
||||
func ReadGasFlag(s string) (simulate bool, gas int64, err error) {
|
||||
switch s {
|
||||
case "":
|
||||
gas = DefaultGasLimit
|
||||
case GasFlagSimulate:
|
||||
simulate = true
|
||||
default:
|
||||
gas, err = strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("gas must be either integer or %q", GasFlagSimulate)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/bgentry/speakeasy"
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -24,7 +24,7 @@ func BufferStdin() *bufio.Reader {
|
||||
// It enforces the password length
|
||||
func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) {
|
||||
if inputIsTty() {
|
||||
pass, err = speakeasy.Ask(prompt)
|
||||
pass, err = speakeasy.FAsk(os.Stderr, prompt)
|
||||
} else {
|
||||
pass, err = readLineFromBuf(buf)
|
||||
}
|
||||
@ -44,13 +44,8 @@ func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) {
|
||||
|
||||
// GetSeed will request a seed phrase from stdin and trims off
|
||||
// leading/trailing spaces
|
||||
func GetSeed(prompt string, buf *bufio.Reader) (seed string, err error) {
|
||||
if inputIsTty() {
|
||||
fmt.Println(prompt)
|
||||
}
|
||||
seed, err = readLineFromBuf(buf)
|
||||
seed = strings.TrimSpace(seed)
|
||||
return
|
||||
func GetSeed(prompt string, buf *bufio.Reader) (string, error) {
|
||||
return GetString(prompt, buf)
|
||||
}
|
||||
|
||||
// GetCheckPassword will prompt for a password twice to verify they
|
||||
@ -100,6 +95,19 @@ func GetConfirmation(prompt string, buf *bufio.Reader) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetString simply returns the trimmed string output of a given reader.
|
||||
func GetString(prompt string, buf *bufio.Reader) (string, error) {
|
||||
if inputIsTty() && prompt != "" {
|
||||
PrintPrefixed(prompt)
|
||||
}
|
||||
|
||||
out, err := readLineFromBuf(buf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(out), nil
|
||||
}
|
||||
|
||||
// inputIsTty returns true iff we have an interactive prompt,
|
||||
// where we can disable echo and request to repeat the password.
|
||||
// If false, we can optimize for piped input from another command
|
||||
@ -117,3 +125,9 @@ func readLineFromBuf(buf *bufio.Reader) (string, error) {
|
||||
}
|
||||
return strings.TrimSpace(pass), nil
|
||||
}
|
||||
|
||||
// PrintPrefixed prints a string with > prefixed for use in prompts.
|
||||
func PrintPrefixed(msg string) {
|
||||
msg = fmt.Sprintf("> %s\n", msg)
|
||||
fmt.Fprint(os.Stderr, msg)
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
@ -46,7 +45,6 @@ phrase, otherwise, a new key will be generated.`,
|
||||
return cmd
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
// TODO remove the above when addressing #1446
|
||||
func runAddCmd(cmd *cobra.Command, args []string) error {
|
||||
var kb keys.Keybase
|
||||
@ -62,10 +60,10 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
||||
name = "inmemorykey"
|
||||
} else {
|
||||
if len(args) != 1 || len(args[0]) == 0 {
|
||||
return errors.New("you must provide a name for the key")
|
||||
return errMissingName()
|
||||
}
|
||||
name = args[0]
|
||||
kb, err = GetKeyBase()
|
||||
kb, err = GetKeyBaseWithWritePerm()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -128,7 +126,8 @@ func printCreate(info keys.Info, seed string) {
|
||||
output := viper.Get(cli.OutputFlag)
|
||||
switch output {
|
||||
case "text":
|
||||
printInfo(info)
|
||||
printKeyInfo(info, Bech32KeyOutput)
|
||||
|
||||
// print seed unless requested not to.
|
||||
if !viper.GetBool(client.FlagUseLedger) && !viper.GetBool(flagNoBackup) {
|
||||
fmt.Println("**Important** write this seed phrase in a safe place.")
|
||||
@ -144,11 +143,16 @@ func printCreate(info keys.Info, seed string) {
|
||||
if !viper.GetBool(flagNoBackup) {
|
||||
out.Seed = seed
|
||||
}
|
||||
json, err := MarshalJSON(out)
|
||||
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...
|
||||
}
|
||||
fmt.Println(string(json))
|
||||
fmt.Println(string(jsonString))
|
||||
default:
|
||||
panic(fmt.Sprintf("I can't speak: %s", output))
|
||||
}
|
||||
@ -165,75 +169,77 @@ type NewKeyBody struct {
|
||||
}
|
||||
|
||||
// add new key REST handler
|
||||
func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var kb keys.Keybase
|
||||
var m NewKeyBody
|
||||
func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var kb keys.Keybase
|
||||
var m NewKeyBody
|
||||
|
||||
kb, err := GetKeyBase()
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
err = json.Unmarshal(body, &m)
|
||||
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
if m.Name == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("You have to specify a name for the locally stored account."))
|
||||
return
|
||||
}
|
||||
if m.Password == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("You have to specify a password for the locally stored account."))
|
||||
return
|
||||
}
|
||||
|
||||
// check if already exists
|
||||
infos, err := kb.List()
|
||||
for _, i := range infos {
|
||||
if i.GetName() == m.Name {
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
w.Write([]byte(fmt.Sprintf("Account with name %s already exists.", m.Name)))
|
||||
kb, err := GetKeyBaseWithWritePerm()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
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()))
|
||||
return
|
||||
}
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(body, &m)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
if m.Name == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
err = errMissingName()
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
if m.Password == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
err = errMissingPassword()
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
keyOutput, err := Bech32KeyOutput(info)
|
||||
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() == m.Name {
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
err = errKeyNameConflict(m.Name)
|
||||
w.Write([]byte(err.Error()))
|
||||
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()))
|
||||
return
|
||||
}
|
||||
|
||||
keyOutput, err := Bech32KeyOutput(info)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
keyOutput.Seed = seed
|
||||
|
||||
PostProcessResponse(w, cdc, keyOutput, indent)
|
||||
}
|
||||
|
||||
keyOutput.Seed = seed
|
||||
|
||||
bz, err := json.Marshal(keyOutput)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(bz)
|
||||
}
|
||||
|
||||
// function to just a new seed to display in the UI before actually persisting it in the keybase
|
||||
@ -256,5 +262,86 @@ func SeedRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
algo := keys.SigningAlgo(algoType)
|
||||
|
||||
seed := getSeed(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"`
|
||||
}
|
||||
|
||||
// RecoverRequestHandler performs key recover request
|
||||
func RecoverRequestHandler(indent bool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
name := vars["name"]
|
||||
var m RecoverKeyBody
|
||||
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()))
|
||||
return
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
err = errMissingName()
|
||||
w.Write([]byte(err.Error()))
|
||||
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()))
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
info, err := kb.CreateKey(name, m.Seed, m.Password)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
keyOutput, err := Bech32KeyOutput(info)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
PostProcessResponse(w, cdc, keyOutput, indent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
var cdc *wire.Codec
|
||||
var cdc *codec.Codec
|
||||
|
||||
func init() {
|
||||
cdc = wire.NewCodec()
|
||||
wire.RegisterCrypto(cdc)
|
||||
cdc = codec.New()
|
||||
codec.RegisterCrypto(cdc)
|
||||
}
|
||||
|
||||
// marshal keys
|
||||
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"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/gorilla/mux"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@ -25,7 +26,7 @@ func deleteKeyCommand() *cobra.Command {
|
||||
func runDeleteCmd(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
kb, err := GetKeyBase()
|
||||
kb, err := GetKeyBaseWithWritePerm()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -68,25 +69,32 @@ func DeleteKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&m)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
kb, err = GetKeyBase()
|
||||
kb, err = GetKeyBaseWithWritePerm()
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// TODO handle error if key is not available or pass is wrong
|
||||
err = kb.Delete(name, m.Password)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
if keyerror.IsErrKeyNotFound(err) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
} else if keyerror.IsErrWrongPassword(err) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
} else if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(200)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
19
client/keys/errors.go
Normal file
19
client/keys/errors.go
Normal file
@ -0,0 +1,19 @@
|
||||
package keys
|
||||
|
||||
import "fmt"
|
||||
|
||||
func errKeyNameConflict(name string) error {
|
||||
return fmt.Errorf("acount with name %s already exists", name)
|
||||
}
|
||||
|
||||
func errMissingName() error {
|
||||
return fmt.Errorf("you have to specify a name for the locally stored account")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@ -35,35 +34,31 @@ func runListCmd(cmd *cobra.Command, args []string) error {
|
||||
// REST
|
||||
|
||||
// query key list REST handler
|
||||
func QueryKeysRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
kb, err := GetKeyBase()
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
func QueryKeysRequestHandler(indent bool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
kb, err := GetKeyBase()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
infos, err := kb.List()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
// an empty list will be JSONized as null, but we want to keep the empty list
|
||||
if len(infos) == 0 {
|
||||
PostProcessResponse(w, cdc, "[]", indent)
|
||||
return
|
||||
}
|
||||
keysOutput, err := Bech32KeysOutput(infos)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
PostProcessResponse(w, cdc, keysOutput, indent)
|
||||
}
|
||||
infos, err := kb.List()
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
// an empty list will be JSONized as null, but we want to keep the empty list
|
||||
if len(infos) == 0 {
|
||||
w.Write([]byte("[]"))
|
||||
return
|
||||
}
|
||||
keysOutput, err := Bech32KeysOutput(infos)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
output, err := json.MarshalIndent(keysOutput, "", " ")
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
w.Write(output)
|
||||
}
|
||||
|
||||
78
client/keys/mnemonic.go
Normal file
78
client/keys/mnemonic.go
Normal file
@ -0,0 +1,78 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
bip39 "github.com/bartekn/go-bip39"
|
||||
)
|
||||
|
||||
const (
|
||||
flagUserEntropy = "unsafe-entropy"
|
||||
|
||||
mnemonicEntropySize = 256
|
||||
)
|
||||
|
||||
func mnemonicKeyCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "mnemonic",
|
||||
Short: "Compute the bip39 mnemonic for some input entropy",
|
||||
Long: "Create a bip39 mnemonic, sometimes called a seed phrase, by reading from the system entropy. To pass your own entropy, use --unsafe-entropy",
|
||||
RunE: runMnemonicCmd,
|
||||
}
|
||||
cmd.Flags().Bool(flagUserEntropy, false, "Prompt the user to supply their own entropy, instead of relying on the system")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runMnemonicCmd(cmd *cobra.Command, args []string) error {
|
||||
flags := cmd.Flags()
|
||||
|
||||
userEntropy, _ := flags.GetBool(flagUserEntropy)
|
||||
|
||||
var entropySeed []byte
|
||||
|
||||
if userEntropy {
|
||||
// prompt the user to enter some entropy
|
||||
buf := client.BufferStdin()
|
||||
inputEntropy, err := client.GetString("> WARNING: Generate at least 256-bits of entropy and enter the results here:", buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !conf {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
entropySeed, err = bip39.NewEntropy(mnemonicEntropySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
mnemonic, err := bip39.NewMnemonic(entropySeed[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(mnemonic)
|
||||
|
||||
return nil
|
||||
}
|
||||
188
client/keys/new.go
Normal file
188
client/keys/new.go
Normal file
@ -0,0 +1,188 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bartekn/go-bip39"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
)
|
||||
|
||||
const (
|
||||
flagNewDefault = "default"
|
||||
flagBIP44Path = "bip44-path"
|
||||
)
|
||||
|
||||
func newKeyCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "new",
|
||||
Short: "Interactive command to derive a new private key, encrypt it, and save to disk",
|
||||
Long: `Derive a new private key using an interactive command that will prompt you for each input.
|
||||
Optionally specify a bip39 mnemonic, a bip39 passphrase to further secure the mnemonic,
|
||||
and a bip32 HD path to derive a specific account. The key will be stored under the given name
|
||||
and encrypted with the given password. The only input that is required is the encryption password.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runNewCmd,
|
||||
}
|
||||
cmd.Flags().Bool(flagNewDefault, false, "Skip the prompts and just use the default values for everything")
|
||||
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")
|
||||
return cmd
|
||||
}
|
||||
|
||||
/*
|
||||
input
|
||||
- bip39 mnemonic
|
||||
- bip39 passphrase
|
||||
- bip44 path
|
||||
- local encryption password
|
||||
output
|
||||
- armor encrypted private key (saved to file)
|
||||
*/
|
||||
func runNewCmd(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
kb, err := GetKeyBaseWithWritePerm()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := client.BufferStdin()
|
||||
|
||||
_, 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
|
||||
}
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
useDefaults, _ := flags.GetBool(flagNewDefault)
|
||||
bipFlag := flags.Lookup(flagBIP44Path)
|
||||
|
||||
bip44Params, err := getBIP44ParamsAndPath(bipFlag.Value.String(), bipFlag.Changed || useDefaults)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we're using ledger, only thing we need is the path. So generate key and
|
||||
// we're done.
|
||||
if viper.GetBool(client.FlagUseLedger) {
|
||||
algo := keys.Secp256k1
|
||||
path := bip44Params.DerivationPath() // ccrypto.DerivationPath{44, 118, account, 0, index}
|
||||
|
||||
info, err := kb.CreateLedger(name, path, algo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printCreate(info, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
var mnemonic string
|
||||
|
||||
if !useDefaults {
|
||||
mnemonic, err = client.GetString("Enter your bip39 mnemonic, or hit enter to generate one.", buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(mnemonic) == 0 {
|
||||
// read entropy seed straight from crypto.Rand and convert to mnemonic
|
||||
entropySeed, err := bip39.NewEntropy(mnemonicEntropySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mnemonic, err = bip39.NewMnemonic(entropySeed[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// get bip39 passphrase
|
||||
var bip39Passphrase string
|
||||
if !useDefaults {
|
||||
printStep()
|
||||
printPrefixed("Enter your bip39 passphrase. This is combined with the mnemonic to derive the seed")
|
||||
|
||||
bip39Passphrase, err = client.GetString("Most users should just hit enter to use the default, \"\"", buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if they use one, make them re-enter it
|
||||
if len(bip39Passphrase) != 0 {
|
||||
p2, err := client.GetString("Repeat the passphrase:", buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if bip39Passphrase != p2 {
|
||||
return errors.New("passphrases don't match")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printStep()
|
||||
|
||||
// get the encryption password
|
||||
encryptPassword, err := client.GetCheckPassword(
|
||||
"> Enter a passphrase to encrypt your key to disk:",
|
||||
"> Repeat the passphrase:", buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := kb.Derive(name, mnemonic, bip39Passphrase, encryptPassword, *bip44Params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = info
|
||||
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
|
||||
|
||||
printStep()
|
||||
|
||||
bip44Path, err = client.GetString(fmt.Sprintf("Enter your bip44 path. Default is %s\n", path), buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(bip44Path) == 0 {
|
||||
bip44Path = path
|
||||
}
|
||||
}
|
||||
|
||||
bip44params, err := hd.NewParamsFromPath(bip44Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bip44params, nil
|
||||
}
|
||||
|
||||
func printPrefixed(msg string) {
|
||||
fmt.Printf("> %s\n", msg)
|
||||
}
|
||||
|
||||
func printStep() {
|
||||
printPrefixed("-------------------------------------")
|
||||
}
|
||||
@ -19,9 +19,11 @@ func Commands() *cobra.Command {
|
||||
needs to sign with a private key.`,
|
||||
}
|
||||
cmd.AddCommand(
|
||||
mnemonicKeyCommand(),
|
||||
newKeyCommand(),
|
||||
addKeyCommand(),
|
||||
listKeysCmd,
|
||||
showKeysCmd,
|
||||
showKeysCmd(),
|
||||
client.LineBreak,
|
||||
deleteKeyCommand(),
|
||||
updateKeyCommand(),
|
||||
@ -30,11 +32,12 @@ func Commands() *cobra.Command {
|
||||
}
|
||||
|
||||
// resgister REST routes
|
||||
func RegisterRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/keys", QueryKeysRequestHandler).Methods("GET")
|
||||
r.HandleFunc("/keys", AddNewKeyRequestHandler).Methods("POST")
|
||||
func RegisterRoutes(r *mux.Router, indent bool) {
|
||||
r.HandleFunc("/keys", QueryKeysRequestHandler(indent)).Methods("GET")
|
||||
r.HandleFunc("/keys", AddNewKeyRequestHandler(indent)).Methods("POST")
|
||||
r.HandleFunc("/keys/seed", SeedRequestHandler).Methods("GET")
|
||||
r.HandleFunc("/keys/{name}", GetKeyRequestHandler).Methods("GET")
|
||||
r.HandleFunc("/keys/{name}/recover", RecoverRequestHandler(indent)).Methods("POST")
|
||||
r.HandleFunc("/keys/{name}", GetKeyRequestHandler(indent)).Methods("GET")
|
||||
r.HandleFunc("/keys/{name}", UpdateKeyRequestHandler).Methods("PUT")
|
||||
r.HandleFunc("/keys/{name}", DeleteKeyRequestHandler).Methods("DELETE")
|
||||
}
|
||||
|
||||
@ -1,67 +1,184 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"net/http"
|
||||
|
||||
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tendermint/tendermint/crypto/multisig"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
)
|
||||
|
||||
var showKeysCmd = &cobra.Command{
|
||||
Use: "show <name>",
|
||||
Short: "Show key info for the given name",
|
||||
Long: `Return public details of one local key.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
info, err := getKey(name)
|
||||
if err == nil {
|
||||
printInfo(info)
|
||||
}
|
||||
return err
|
||||
},
|
||||
const (
|
||||
// FlagAddress is the flag for the user's address on the command line.
|
||||
FlagAddress = "address"
|
||||
// FlagPublicKey represents the user's public key on the command line.
|
||||
FlagPublicKey = "pubkey"
|
||||
// FlagBechPrefix defines a desired Bech32 prefix encoding for a key.
|
||||
FlagBechPrefix = "bech"
|
||||
|
||||
flagMultiSigThreshold = "multisig-threshold"
|
||||
defaultMultiSigKeyName = "multi"
|
||||
)
|
||||
|
||||
var _ keys.Info = (*multiSigKey)(nil)
|
||||
|
||||
type multiSigKey struct {
|
||||
name string
|
||||
key crypto.PubKey
|
||||
}
|
||||
|
||||
func getKey(name string) (keys.Info, error) {
|
||||
kb, err := GetKeyBase()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (m multiSigKey) GetName() string { return m.name }
|
||||
func (m multiSigKey) GetType() keys.KeyType { return keys.TypeLocal }
|
||||
func (m multiSigKey) GetPubKey() crypto.PubKey { return m.key }
|
||||
func (m multiSigKey) GetAddress() sdk.AccAddress { return sdk.AccAddress(m.key.Address()) }
|
||||
|
||||
func showKeysCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "show [name]",
|
||||
Short: "Show key info for the given name",
|
||||
Long: `Return public details of one local key.`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: runShowCmd,
|
||||
}
|
||||
|
||||
return kb.Get(name)
|
||||
cmd.Flags().String(FlagBechPrefix, "acc", "The Bech32 prefix encoding for a key (acc|val|cons)")
|
||||
cmd.Flags().Bool(FlagAddress, false, "output the address only (overrides --output)")
|
||||
cmd.Flags().Bool(FlagPublicKey, false, "output the public key only (overrides --output)")
|
||||
cmd.Flags().Uint(flagMultiSigThreshold, 1, "K out of N required signatures")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runShowCmd(cmd *cobra.Command, args []string) (err error) {
|
||||
var info keys.Info
|
||||
|
||||
if len(args) == 1 {
|
||||
info, err = GetKeyInfo(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
pks := make([]crypto.PubKey, len(args))
|
||||
for i, keyName := range args {
|
||||
info, err := GetKeyInfo(keyName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pks[i] = info.GetPubKey()
|
||||
}
|
||||
|
||||
multisigThreshold := viper.GetInt(flagMultiSigThreshold)
|
||||
err = validateMultisigThreshold(multisigThreshold, len(args))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
multikey := multisig.NewPubKeyMultisigThreshold(multisigThreshold, pks)
|
||||
info = multiSigKey{
|
||||
name: defaultMultiSigKeyName,
|
||||
key: multikey,
|
||||
}
|
||||
}
|
||||
|
||||
isShowAddr := viper.GetBool(FlagAddress)
|
||||
isShowPubKey := viper.GetBool(FlagPublicKey)
|
||||
isOutputSet := cmd.Flag(cli.OutputFlag).Changed
|
||||
|
||||
if isShowAddr && isShowPubKey {
|
||||
return errors.New("cannot use both --address and --pubkey at once")
|
||||
}
|
||||
|
||||
if isOutputSet && (isShowAddr || isShowPubKey) {
|
||||
return errors.New("cannot use --output with --address or --pubkey")
|
||||
}
|
||||
|
||||
bechKeyOut, err := getBechKeyOut(viper.GetString(FlagBechPrefix))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case isShowAddr:
|
||||
printKeyAddress(info, bechKeyOut)
|
||||
case isShowPubKey:
|
||||
printPubKey(info, bechKeyOut)
|
||||
default:
|
||||
printKeyInfo(info, bechKeyOut)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateMultisigThreshold(k, nKeys int) error {
|
||||
if k <= 0 {
|
||||
return fmt.Errorf("threshold must be a positive integer")
|
||||
}
|
||||
if nKeys < k {
|
||||
return fmt.Errorf(
|
||||
"threshold k of n multisignature: %d < %d", nKeys, k)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBechKeyOut(bechPrefix string) (bechKeyOutFn, error) {
|
||||
switch bechPrefix {
|
||||
case "acc":
|
||||
return Bech32KeyOutput, nil
|
||||
case "val":
|
||||
return Bech32ValKeyOutput, nil
|
||||
case "cons":
|
||||
return Bech32ConsKeyOutput, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid Bech32 prefix encoding provided: %s", bechPrefix)
|
||||
}
|
||||
|
||||
///////////////////////////
|
||||
// REST
|
||||
|
||||
// get key REST handler
|
||||
func GetKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
name := vars["name"]
|
||||
func GetKeyRequestHandler(indent bool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
name := vars["name"]
|
||||
bechPrefix := r.URL.Query().Get(FlagBechPrefix)
|
||||
|
||||
info, err := getKey(name)
|
||||
// TODO check for the error if key actually does not exist, instead of assuming this as the reason
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
if bechPrefix == "" {
|
||||
bechPrefix = "acc"
|
||||
}
|
||||
|
||||
keyOutput, err := Bech32KeyOutput(info)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
output, err := json.MarshalIndent(keyOutput, "", " ")
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
bechKeyOut, err := getBechKeyOut(bechPrefix)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(output)
|
||||
info, err := GetKeyInfo(name)
|
||||
if keyerror.IsErrKeyNotFound(err) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
} else if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
keyOutput, err := bechKeyOut(info)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
PostProcessResponse(w, cdc, keyOutput, indent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -26,7 +27,7 @@ func runUpdateCmd(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
buf := client.BufferStdin()
|
||||
kb, err := GetKeyBase()
|
||||
kb, err := GetKeyBaseWithWritePerm()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -69,27 +70,35 @@ func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&m)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
kb, err = GetKeyBase()
|
||||
kb, err = GetKeyBaseWithWritePerm()
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
getNewpass := func() (string, error) { return m.NewPassword, nil }
|
||||
|
||||
// TODO check if account exists and if password is correct
|
||||
err = kb.Update(name, m.OldPassword, getNewpass)
|
||||
if err != nil {
|
||||
w.WriteHeader(401)
|
||||
if keyerror.IsErrKeyNotFound(err) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
} else if keyerror.IsErrWrongPassword(err) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
} else if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
@ -2,17 +2,20 @@ package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// KeyDBName is the directory under root where we store the keys
|
||||
@ -21,13 +24,7 @@ const KeyDBName = "keys"
|
||||
// keybase is used to make GetKeyBase a singleton
|
||||
var keybase keys.Keybase
|
||||
|
||||
// TODO make keybase take a database not load from the directory
|
||||
|
||||
// initialize a keybase based on the configuration
|
||||
func GetKeyBase() (keys.Keybase, error) {
|
||||
rootDir := viper.GetString(cli.HomeFlag)
|
||||
return GetKeyBaseFromDir(rootDir)
|
||||
}
|
||||
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.
|
||||
@ -78,10 +75,33 @@ func ReadPassphraseFromStdin(name string) (string, error) {
|
||||
return passphrase, nil
|
||||
}
|
||||
|
||||
// initialize a keybase based on the configuration
|
||||
// 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) {
|
||||
rootDir := viper.GetString(cli.HomeFlag)
|
||||
return GetKeyBaseFromDir(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)
|
||||
}
|
||||
|
||||
// GetKeyBaseFromDirWithWritePerm initializes a keybase at a particular dir with write permissions.
|
||||
func GetKeyBaseFromDirWithWritePerm(rootDir string) (keys.Keybase, error) {
|
||||
return getKeyBaseFromDirWithOpts(rootDir, nil)
|
||||
}
|
||||
|
||||
// GetKeyBaseFromDir initializes a read-only keybase at a particular dir.
|
||||
func GetKeyBaseFromDir(rootDir string) (keys.Keybase, error) {
|
||||
return getKeyBaseFromDirWithOpts(rootDir, &opt.Options{ReadOnly: true})
|
||||
}
|
||||
|
||||
func getKeyBaseFromDirWithOpts(rootDir string, o *opt.Options) (keys.Keybase, error) {
|
||||
if keybase == nil {
|
||||
db, err := dbm.NewGoLevelDB(KeyDBName, filepath.Join(rootDir, "keys"))
|
||||
db, err := dbm.NewGoLevelDBWithOpts(KeyDBName, filepath.Join(rootDir, "keys"), o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -97,11 +117,11 @@ func SetKeyBase(kb keys.Keybase) {
|
||||
|
||||
// used for outputting keys.Info over REST
|
||||
type KeyOutput struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Address sdk.AccAddress `json:"address"`
|
||||
PubKey string `json:"pub_key"`
|
||||
Seed string `json:"seed,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Address string `json:"address"`
|
||||
PubKey string `json:"pub_key"`
|
||||
Seed string `json:"seed,omitempty"`
|
||||
}
|
||||
|
||||
// create a list of KeyOutput in bech32 format
|
||||
@ -119,24 +139,61 @@ func Bech32KeysOutput(infos []keys.Info) ([]KeyOutput, error) {
|
||||
|
||||
// create a KeyOutput in bech32 format
|
||||
func Bech32KeyOutput(info keys.Info) (KeyOutput, error) {
|
||||
account := sdk.AccAddress(info.GetPubKey().Address().Bytes())
|
||||
accAddr := sdk.AccAddress(info.GetPubKey().Address().Bytes())
|
||||
bechPubKey, err := sdk.Bech32ifyAccPub(info.GetPubKey())
|
||||
if err != nil {
|
||||
return KeyOutput{}, err
|
||||
}
|
||||
|
||||
return KeyOutput{
|
||||
Name: info.GetName(),
|
||||
Type: info.GetType().String(),
|
||||
Address: account,
|
||||
Address: accAddr.String(),
|
||||
PubKey: bechPubKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func printInfo(info keys.Info) {
|
||||
ko, err := Bech32KeyOutput(info)
|
||||
// Bech32ConsKeyOutput returns key output for a consensus node's key
|
||||
// information.
|
||||
func Bech32ConsKeyOutput(keyInfo keys.Info) (KeyOutput, error) {
|
||||
consAddr := sdk.ConsAddress(keyInfo.GetPubKey().Address().Bytes())
|
||||
|
||||
bechPubKey, err := sdk.Bech32ifyConsPub(keyInfo.GetPubKey())
|
||||
if err != nil {
|
||||
return KeyOutput{}, err
|
||||
}
|
||||
|
||||
return KeyOutput{
|
||||
Name: keyInfo.GetName(),
|
||||
Type: keyInfo.GetType().String(),
|
||||
Address: consAddr.String(),
|
||||
PubKey: bechPubKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Bech32ValKeyOutput returns key output for a validator's key information.
|
||||
func Bech32ValKeyOutput(keyInfo keys.Info) (KeyOutput, error) {
|
||||
valAddr := sdk.ValAddress(keyInfo.GetPubKey().Address().Bytes())
|
||||
|
||||
bechPubKey, err := sdk.Bech32ifyValPub(keyInfo.GetPubKey())
|
||||
if err != nil {
|
||||
return KeyOutput{}, err
|
||||
}
|
||||
|
||||
return KeyOutput{
|
||||
Name: keyInfo.GetName(),
|
||||
Type: keyInfo.GetType().String(),
|
||||
Address: valAddr.String(),
|
||||
PubKey: bechPubKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) {
|
||||
ko, err := bechKeyOut(keyInfo)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
switch viper.Get(cli.OutputFlag) {
|
||||
case "text":
|
||||
fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n")
|
||||
@ -146,6 +203,7 @@ func printInfo(info keys.Info) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
}
|
||||
@ -173,3 +231,44 @@ func printInfos(infos []keys.Info) {
|
||||
func printKeyOutput(ko KeyOutput) {
|
||||
fmt.Printf("%s\t%s\t%s\t%s\n", ko.Name, ko.Type, ko.Address, ko.PubKey)
|
||||
}
|
||||
|
||||
func printKeyAddress(info keys.Info, bechKeyOut bechKeyOutFn) {
|
||||
ko, err := bechKeyOut(info)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(ko.Address)
|
||||
}
|
||||
|
||||
func printPubKey(info keys.Info, bechKeyOut bechKeyOutFn) {
|
||||
ko, err := bechKeyOut(info)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(ko.PubKey)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
case []byte:
|
||||
output = response.([]byte)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(output)
|
||||
}
|
||||
|
||||
39
client/keys/utils_test.go
Normal file
39
client/keys/utils_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/stretchr/testify/require"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
174
client/lcd/certificates.go
Normal file
174
client/lcd/certificates.go
Normal file
@ -0,0 +1,174 @@
|
||||
package lcd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// default: 30 days
|
||||
const defaultValidFor = 30 * 24 * time.Hour
|
||||
|
||||
func generateSelfSignedCert(host string) (certBytes []byte, priv *ecdsa.PrivateKey, err error) {
|
||||
priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(defaultValidFor)
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to generate serial number: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Gaia Lite"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
}
|
||||
hosts := strings.Split(host, ",")
|
||||
for _, h := range hosts {
|
||||
if ip := net.ParseIP(h); ip != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ip)
|
||||
} else {
|
||||
template.DNSNames = append(template.DNSNames, h)
|
||||
}
|
||||
}
|
||||
|
||||
certBytes, err = x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("couldn't create certificate: %s", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func writeCertAndPrivKey(certBytes []byte, priv *ecdsa.PrivateKey) (certFile string, keyFile string, err error) {
|
||||
if priv == nil {
|
||||
err = errors.New("private key is nil")
|
||||
return
|
||||
}
|
||||
certFile, err = writeCertificateFile(certBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
keyFile, err = writeKeyFile(priv)
|
||||
return
|
||||
}
|
||||
|
||||
func writeCertificateFile(certBytes []byte) (filename string, err error) {
|
||||
f, err := ioutil.TempFile("", "cert_")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
filename = f.Name()
|
||||
if err := pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}); err != nil {
|
||||
return filename, fmt.Errorf("failed to write data to %s: %s", filename, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func writeKeyFile(priv *ecdsa.PrivateKey) (filename string, err error) {
|
||||
f, err := ioutil.TempFile("", "key_")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
filename = f.Name()
|
||||
block, err := pemBlockForKey(priv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err := pem.Encode(f, block); err != nil {
|
||||
return filename, fmt.Errorf("failed to write data to %s: %s", filename, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func pemBlockForKey(priv *ecdsa.PrivateKey) (*pem.Block, error) {
|
||||
b, err := x509.MarshalECPrivateKey(priv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to marshal ECDSA private key: %v", err)
|
||||
}
|
||||
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
|
||||
|
||||
}
|
||||
|
||||
func genCertKeyFilesAndReturnFingerprint(sslHosts string) (certFile, keyFile string, fingerprint string, err error) {
|
||||
certBytes, priv, err := generateSelfSignedCert(sslHosts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
certFile, keyFile, err = writeCertAndPrivKey(certBytes, priv)
|
||||
cleanupFunc := func() {
|
||||
os.Remove(certFile)
|
||||
os.Remove(keyFile)
|
||||
}
|
||||
// Either of the files could have been written already,
|
||||
// thus clean up regardless of the error.
|
||||
if err != nil {
|
||||
defer cleanupFunc()
|
||||
return
|
||||
}
|
||||
fingerprint, err = fingerprintForCertificate(certBytes)
|
||||
if err != nil {
|
||||
defer cleanupFunc()
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func fingerprintForCertificate(certBytes []byte) (string, error) {
|
||||
cert, err := x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h := sha256.New()
|
||||
h.Write(cert.Raw)
|
||||
fingerprintBytes := h.Sum(nil)
|
||||
var buf bytes.Buffer
|
||||
for i, b := range fingerprintBytes {
|
||||
if i > 0 {
|
||||
fmt.Fprintf(&buf, ":")
|
||||
}
|
||||
fmt.Fprintf(&buf, "%02X", b)
|
||||
}
|
||||
return fmt.Sprintf("SHA256 Fingerprint=%s", buf.String()), nil
|
||||
}
|
||||
|
||||
func fingerprintFromFile(certFile string) (string, error) {
|
||||
f, err := os.Open(certFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
block, _ := pem.Decode(data)
|
||||
if block == nil {
|
||||
return "", fmt.Errorf("couldn't find PEM data in %s", certFile)
|
||||
}
|
||||
return fingerprintForCertificate(block.Bytes)
|
||||
}
|
||||
93
client/lcd/certificates_test.go
Normal file
93
client/lcd/certificates_test.go
Normal file
@ -0,0 +1,93 @@
|
||||
package lcd
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGenerateSelfSignedCert(t *testing.T) {
|
||||
host := "127.0.0.1,localhost,::1"
|
||||
certBytes, _, err := generateSelfSignedCert(host)
|
||||
require.Nil(t, err)
|
||||
cert, err := x509.ParseCertificate(certBytes)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 2, len(cert.IPAddresses))
|
||||
require.Equal(t, 1, len(cert.DNSNames))
|
||||
require.True(t, cert.IsCA)
|
||||
}
|
||||
|
||||
func TestWriteCertAndPrivKey(t *testing.T) {
|
||||
expectedPerm := "-rw-------"
|
||||
derBytes, priv, err := generateSelfSignedCert("localhost")
|
||||
require.Nil(t, err)
|
||||
type args struct {
|
||||
certBytes []byte
|
||||
priv *ecdsa.PrivateKey
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"valid certificate", args{derBytes, priv}, false},
|
||||
{"garbage", args{[]byte("some garbage"), nil}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotCertFile, gotKeyFile, err := writeCertAndPrivKey(tt.args.certBytes, tt.args.priv)
|
||||
defer os.Remove(gotCertFile)
|
||||
defer os.Remove(gotKeyFile)
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err)
|
||||
return
|
||||
}
|
||||
require.Nil(t, err)
|
||||
info, err := os.Stat(gotCertFile)
|
||||
require.Nil(t, err)
|
||||
require.True(t, info.Mode().IsRegular())
|
||||
require.Equal(t, expectedPerm, info.Mode().String())
|
||||
info, err = os.Stat(gotKeyFile)
|
||||
require.Nil(t, err)
|
||||
require.True(t, info.Mode().IsRegular())
|
||||
require.Equal(t, expectedPerm, info.Mode().String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFingerprintFromFile(t *testing.T) {
|
||||
cert := `-----BEGIN CERTIFICATE-----
|
||||
MIIBbDCCARGgAwIBAgIQSuFKYv/22v+cxtVgMUrQADAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMB4XDTE4MDkyMDIzNDQyNloXDTE5MDkyMDIzNDQyNlow
|
||||
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDIo
|
||||
ujAesRczcPVAWiLhpeV1B7hS/RI2LJaGj3QjyJ8hiUthJTPIamr8m7LuS/U5fS0o
|
||||
hY297YeTIGo9YkxClICjSTBHMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
|
||||
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MA8GA1UdEQQIMAaHBH8AAAEwCgYIKoZI
|
||||
zj0EAwIDSQAwRgIhAKnwbhX9FrGG1otCVLwhClQ3RaLxnNpCgIGTqSimb34cAiEA
|
||||
stMN+IqMCKWlZyGqxGIiyksMLMEU3lRqKNQn2EoAZJY=
|
||||
-----END CERTIFICATE-----`
|
||||
wantFingerprint := `SHA256 Fingerprint=0B:ED:9A:AA:A2:D1:7E:B2:53:56:F6:FC:C0:E6:1A:69:70:21:A2:B0:90:FC:AF:BB:EF:AE:2C:78:52:AB:68:40`
|
||||
certFile, err := ioutil.TempFile("", "test_cert_")
|
||||
require.Nil(t, err)
|
||||
_, err = certFile.Write([]byte(cert))
|
||||
require.Nil(t, err)
|
||||
err = certFile.Close()
|
||||
require.Nil(t, err)
|
||||
defer os.Remove(certFile.Name())
|
||||
fingerprint, err := fingerprintFromFile(certFile.Name())
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, wantFingerprint, fingerprint)
|
||||
|
||||
// test failure
|
||||
emptyFile, err := ioutil.TempFile("", "test_cert_")
|
||||
require.Nil(t, err)
|
||||
err = emptyFile.Close()
|
||||
require.Nil(t, err)
|
||||
defer os.Remove(emptyFile.Name())
|
||||
_, err = fingerprintFromFile(emptyFile.Name())
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,22 +1,24 @@
|
||||
package lcd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
client "github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
keys "github.com/cosmos/cosmos-sdk/client/keys"
|
||||
rpc "github.com/cosmos/cosmos-sdk/client/rpc"
|
||||
tx "github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/client/rpc"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
||||
bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
|
||||
gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
|
||||
ibc "github.com/cosmos/cosmos-sdk/x/ibc/client/rest"
|
||||
slashing "github.com/cosmos/cosmos-sdk/x/slashing/client/rest"
|
||||
stake "github.com/cosmos/cosmos-sdk/x/stake/client/rest"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rakyll/statik/fs"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
@ -24,35 +26,85 @@ import (
|
||||
tmserver "github.com/tendermint/tendermint/rpc/lib/server"
|
||||
)
|
||||
|
||||
const (
|
||||
flagListenAddr = "laddr"
|
||||
flagCORS = "cors"
|
||||
flagMaxOpenConnections = "max-open"
|
||||
flagInsecure = "insecure"
|
||||
flagSSLHosts = "ssl-hosts"
|
||||
flagSSLCertFile = "ssl-certfile"
|
||||
flagSSLKeyFile = "ssl-keyfile"
|
||||
)
|
||||
|
||||
// ServeCommand will generate a long-running rest server
|
||||
// (aka Light Client Daemon) that exposes functionality similar
|
||||
// to the cli, but over rest
|
||||
func ServeCommand(cdc *wire.Codec) *cobra.Command {
|
||||
flagListenAddr := "laddr"
|
||||
flagCORS := "cors"
|
||||
flagMaxOpenConnections := "max-open"
|
||||
func ServeCommand(cdc *codec.Codec) *cobra.Command {
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rest-server",
|
||||
Short: "Start LCD (light-client daemon), a local REST server",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
listenAddr := viper.GetString(flagListenAddr)
|
||||
handler := createHandler(cdc)
|
||||
registerSwaggerUI(handler)
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "rest-server")
|
||||
maxOpen := viper.GetInt(flagMaxOpenConnections)
|
||||
sslHosts := viper.GetString(flagSSLHosts)
|
||||
certFile := viper.GetString(flagSSLCertFile)
|
||||
keyFile := viper.GetString(flagSSLKeyFile)
|
||||
cleanupFunc := func() {}
|
||||
|
||||
listener, err := tmserver.StartHTTPServer(
|
||||
listenAddr, handler, logger,
|
||||
tmserver.Config{MaxOpenConnections: maxOpen},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
var listener net.Listener
|
||||
var fingerprint string
|
||||
if viper.GetBool(flagInsecure) {
|
||||
listener, err = tmserver.StartHTTPServer(
|
||||
listenAddr, handler, logger,
|
||||
tmserver.Config{MaxOpenConnections: maxOpen},
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if certFile != "" {
|
||||
// validateCertKeyFiles() is needed to work around tendermint/tendermint#2460
|
||||
err = validateCertKeyFiles(certFile, keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// cert/key pair is provided, read the fingerprint
|
||||
fingerprint, err = fingerprintFromFile(certFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// if certificate is not supplied, generate a self-signed one
|
||||
certFile, keyFile, fingerprint, err = genCertKeyFilesAndReturnFingerprint(sslHosts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cleanupFunc = func() {
|
||||
os.Remove(certFile)
|
||||
os.Remove(keyFile)
|
||||
}
|
||||
defer cleanupFunc()
|
||||
}
|
||||
listener, err = tmserver.StartHTTPAndTLSServer(
|
||||
listenAddr, handler,
|
||||
certFile, keyFile,
|
||||
logger,
|
||||
tmserver.Config{MaxOpenConnections: maxOpen},
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
logger.Info(fingerprint)
|
||||
}
|
||||
|
||||
logger.Info("REST server started")
|
||||
|
||||
// wait forever and cleanup
|
||||
cmn.TrapSignal(func() {
|
||||
defer cleanupFunc()
|
||||
err := listener.Close()
|
||||
logger.Error("error closing listener", "err", err)
|
||||
})
|
||||
@ -62,15 +114,24 @@ func ServeCommand(cdc *wire.Codec) *cobra.Command {
|
||||
}
|
||||
|
||||
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().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(client.FlagChainID, "", "The chain ID to connect to")
|
||||
cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node")
|
||||
cmd.Flags().String(client.FlagNode, "tcp://localhost:26657", "Address of the node to connect to")
|
||||
cmd.Flags().Int(flagMaxOpenConnections, 1000, "The number of maximum open connections")
|
||||
cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)")
|
||||
cmd.Flags().Bool(client.FlagIndentResponse, false, "Add indent to JSON response")
|
||||
viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode))
|
||||
viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID))
|
||||
viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func createHandler(cdc *wire.Codec) http.Handler {
|
||||
func createHandler(cdc *codec.Codec) *mux.Router {
|
||||
r := mux.NewRouter()
|
||||
|
||||
kb, err := keys.GetKeyBase() //XXX
|
||||
@ -78,21 +139,42 @@ func createHandler(cdc *wire.Codec) http.Handler {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc).WithLogger(os.Stdout)
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
// TODO: make more functional? aka r = keys.RegisterRoutes(r)
|
||||
r.HandleFunc("/version", CLIVersionRequestHandler).Methods("GET")
|
||||
r.HandleFunc("/node_version", NodeVersionRequestHandler(cliCtx)).Methods("GET")
|
||||
|
||||
keys.RegisterRoutes(r)
|
||||
keys.RegisterRoutes(r, cliCtx.Indent)
|
||||
rpc.RegisterRoutes(cliCtx, r)
|
||||
tx.RegisterRoutes(cliCtx, r, cdc)
|
||||
auth.RegisterRoutes(cliCtx, r, cdc, "acc")
|
||||
bank.RegisterRoutes(cliCtx, r, cdc, kb)
|
||||
ibc.RegisterRoutes(cliCtx, r, cdc, kb)
|
||||
stake.RegisterRoutes(cliCtx, r, cdc, kb)
|
||||
slashing.RegisterRoutes(cliCtx, r, cdc, kb)
|
||||
gov.RegisterRoutes(cliCtx, r, cdc)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func registerSwaggerUI(r *mux.Router) {
|
||||
statikFS, err := fs.New()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
staticServer := http.FileServer(statikFS)
|
||||
r.PathPrefix("/swagger-ui/").Handler(http.StripPrefix("/swagger-ui/", staticServer))
|
||||
}
|
||||
|
||||
func validateCertKeyFiles(certFile, keyFile string) error {
|
||||
if keyFile == "" {
|
||||
return errors.New("a key file is required")
|
||||
}
|
||||
if _, err := os.Stat(certFile); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := os.Stat(keyFile); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
3
client/lcd/statik/init.go
Normal file
3
client/lcd/statik/init.go
Normal file
@ -0,0 +1,3 @@
|
||||
package statik
|
||||
|
||||
//This just for fixing the error in importing empty github.com/cosmos/cosmos-sdk/client/lcd/statik
|
||||
BIN
client/lcd/swagger-ui/favicon-16x16.png
Normal file
BIN
client/lcd/swagger-ui/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 445 B |
BIN
client/lcd/swagger-ui/favicon-32x32.png
Normal file
BIN
client/lcd/swagger-ui/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@ -1,16 +1,60 @@
|
||||
<!doctype html>
|
||||
|
||||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>RPC</title>
|
||||
</head>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="swagger-ui.css" >
|
||||
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16" />
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
<body>
|
||||
The Cosmos RPC using Swagger is coming soon! </br>
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
See https://cosmos-staging.interblock.io/rpc/ for the RPC description
|
||||
on the `develop` branch of the SDK.
|
||||
</body>
|
||||
body
|
||||
{
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="swagger-ui-bundle.js"> </script>
|
||||
<script src="swagger-ui-standalone-preset.js"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
|
||||
// Build a system
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "./swagger.yaml",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
})
|
||||
|
||||
window.ui = ui
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
67
client/lcd/swagger-ui/oauth2-redirect.html
Normal file
67
client/lcd/swagger-ui/oauth2-redirect.html
Normal file
@ -0,0 +1,67 @@
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<body onload="run()">
|
||||
</body>
|
||||
</html>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&")
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value)
|
||||
}
|
||||
) : {}
|
||||
|
||||
isValid = qp.state === sentState
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode"||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
</script>
|
||||
41776
client/lcd/swagger-ui/swagger-ui-bundle.js
Normal file
41776
client/lcd/swagger-ui/swagger-ui-bundle.js
Normal file
File diff suppressed because it is too large
Load Diff
1
client/lcd/swagger-ui/swagger-ui-bundle.js.map
Normal file
1
client/lcd/swagger-ui/swagger-ui-bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
13173
client/lcd/swagger-ui/swagger-ui-standalone-preset.js
Normal file
13173
client/lcd/swagger-ui/swagger-ui-standalone-preset.js
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
3
client/lcd/swagger-ui/swagger-ui.css
Normal file
3
client/lcd/swagger-ui/swagger-ui.css
Normal file
File diff suppressed because one or more lines are too long
1
client/lcd/swagger-ui/swagger-ui.css.map
Normal file
1
client/lcd/swagger-ui/swagger-ui.css.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"sources":[],"names":[],"mappings":"","file":"swagger-ui.css","sourceRoot":""}
|
||||
25501
client/lcd/swagger-ui/swagger-ui.js
Normal file
25501
client/lcd/swagger-ui/swagger-ui.js
Normal file
File diff suppressed because it is too large
Load Diff
1
client/lcd/swagger-ui/swagger-ui.js.map
Normal file
1
client/lcd/swagger-ui/swagger-ui.js.map
Normal file
File diff suppressed because one or more lines are too long
1933
client/lcd/swagger-ui/swagger.yaml
Normal file
1933
client/lcd/swagger-ui/swagger.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,27 +4,31 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
keys "github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
crkeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/cosmos/cosmos-sdk/tests"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
txbuilder "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmcfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
@ -33,6 +37,7 @@ import (
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
nm "github.com/tendermint/tendermint/node"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
pvm "github.com/tendermint/tendermint/privval"
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
tmrpc "github.com/tendermint/tendermint/rpc/lib/server"
|
||||
@ -89,7 +94,7 @@ func GetKeyBase(t *testing.T) crkeys.Keybase {
|
||||
|
||||
viper.Set(cli.HomeFlag, dir)
|
||||
|
||||
keybase, err := keys.GetKeyBase()
|
||||
keybase, err := keys.GetKeyBaseWithWritePerm()
|
||||
require.NoError(t, err)
|
||||
|
||||
return keybase
|
||||
@ -110,11 +115,81 @@ 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) {
|
||||
var (
|
||||
err error
|
||||
info crkeys.Info
|
||||
seed string
|
||||
)
|
||||
|
||||
addrSeeds := AddrSeedSlice{}
|
||||
|
||||
for i := 0; i < numAddrs; i++ {
|
||||
name := fmt.Sprintf("test%d", i)
|
||||
password := "1234567890"
|
||||
info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1)
|
||||
require.NoError(t, err)
|
||||
addrSeeds = append(addrSeeds, AddrSeed{Address: sdk.AccAddress(info.GetPubKey().Address()), Seed: seed, Name: name, Password: password})
|
||||
}
|
||||
|
||||
sort.Sort(addrSeeds)
|
||||
|
||||
for i := range addrSeeds {
|
||||
addrs = append(addrs, addrSeeds[i].Address)
|
||||
seeds = append(seeds, addrSeeds[i].Seed)
|
||||
names = append(names, addrSeeds[i].Name)
|
||||
passwords = append(passwords, addrSeeds[i].Password)
|
||||
}
|
||||
|
||||
return addrs, seeds, names, passwords
|
||||
}
|
||||
|
||||
// implement `Interface` in sort package.
|
||||
type AddrSeedSlice []AddrSeed
|
||||
|
||||
func (b AddrSeedSlice) Len() int {
|
||||
return len(b)
|
||||
}
|
||||
|
||||
// Sorts lexographically by Address
|
||||
func (b AddrSeedSlice) Less(i, j int) bool {
|
||||
// bytes package already implements Comparable for []byte.
|
||||
switch bytes.Compare(b[i].Address.Bytes(), b[j].Address.Bytes()) {
|
||||
case -1:
|
||||
return true
|
||||
case 0, 1:
|
||||
return false
|
||||
default:
|
||||
panic("not fail-able with `bytes.Comparable` bounded [-1, 1].")
|
||||
}
|
||||
}
|
||||
|
||||
func (b AddrSeedSlice) Swap(i, j int) {
|
||||
b[j], b[i] = b[i], b[j]
|
||||
}
|
||||
|
||||
// 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) (func(), []crypto.PubKey, string) {
|
||||
func InitializeTestLCD(
|
||||
t *testing.T, nValidators int, initAddrs []sdk.AccAddress,
|
||||
) (cleanup func(), valConsPubKeys []crypto.PubKey, valOperAddrs []sdk.ValAddress, port string) {
|
||||
|
||||
if nValidators < 1 {
|
||||
panic("InitializeTestLCD must use at least one validator")
|
||||
}
|
||||
|
||||
config := GetConfig()
|
||||
config.Consensus.TimeoutCommit = 100
|
||||
config.Consensus.SkipTimeoutCommit = false
|
||||
@ -133,38 +208,43 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress
|
||||
|
||||
genesisFile := config.GenesisFile()
|
||||
genDoc, err := tmtypes.GenesisDocFromFile(genesisFile)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, err)
|
||||
genDoc.Validators = nil
|
||||
genDoc.SaveAs(genesisFile)
|
||||
genTxs := []json.RawMessage{}
|
||||
|
||||
if nValidators < 1 {
|
||||
panic("InitializeTestLCD must use at least one validator")
|
||||
}
|
||||
|
||||
for i := 1; i < nValidators; i++ {
|
||||
genDoc.Validators = append(genDoc.Validators,
|
||||
tmtypes.GenesisValidator{
|
||||
PubKey: ed25519.GenPrivKey().PubKey(),
|
||||
Power: 1,
|
||||
Name: "val",
|
||||
},
|
||||
// append any additional (non-proposing) validators
|
||||
for i := 0; i < nValidators; i++ {
|
||||
operPrivKey := secp256k1.GenPrivKey()
|
||||
operAddr := operPrivKey.PubKey().Address()
|
||||
pubKey := privVal.PubKey
|
||||
delegation := 100
|
||||
if i > 0 {
|
||||
pubKey = ed25519.GenPrivKey().PubKey()
|
||||
delegation = 1
|
||||
}
|
||||
msg := stake.NewMsgCreateValidator(
|
||||
sdk.ValAddress(operAddr),
|
||||
pubKey,
|
||||
sdk.NewCoin("steak", sdk.NewInt(int64(delegation))),
|
||||
stake.Description{Moniker: fmt.Sprintf("validator-%d", i+1)},
|
||||
stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
|
||||
)
|
||||
stdSignMsg := txbuilder.StdSignMsg{
|
||||
ChainID: genDoc.ChainID,
|
||||
Msgs: []sdk.Msg{msg},
|
||||
}
|
||||
sig, err := operPrivKey.Sign(stdSignMsg.Bytes())
|
||||
require.Nil(t, err)
|
||||
tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{{Signature: sig, PubKey: operPrivKey.PubKey()}}, "")
|
||||
txBytes, err := cdc.MarshalJSON(tx)
|
||||
require.Nil(t, err)
|
||||
genTxs = append(genTxs, txBytes)
|
||||
valConsPubKeys = append(valConsPubKeys, pubKey)
|
||||
valOperAddrs = append(valOperAddrs, sdk.ValAddress(operAddr))
|
||||
}
|
||||
|
||||
var validatorsPKs []crypto.PubKey
|
||||
|
||||
// NOTE: It's bad practice to reuse public key address for the owner
|
||||
// address but doing in the test for simplicity.
|
||||
var appGenTxs []json.RawMessage
|
||||
for _, gdValidator := range genDoc.Validators {
|
||||
pk := gdValidator.PubKey
|
||||
validatorsPKs = append(validatorsPKs, pk)
|
||||
|
||||
appGenTx, _, _, err := gapp.GaiaAppGenTxNF(cdc, pk, sdk.AccAddress(pk.Address()), "test_val1")
|
||||
require.NoError(t, err)
|
||||
|
||||
appGenTxs = append(appGenTxs, appGenTx)
|
||||
}
|
||||
|
||||
genesisState, err := gapp.GaiaAppGenState(cdc, appGenTxs[:])
|
||||
genesisState, err := gapp.GaiaAppGenState(cdc, genTxs)
|
||||
require.NoError(t, err)
|
||||
|
||||
// add some tokens to init accounts
|
||||
@ -173,10 +253,10 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress
|
||||
accAuth.Coins = sdk.Coins{sdk.NewInt64Coin("steak", 100)}
|
||||
acc := gapp.NewGenesisAccount(&accAuth)
|
||||
genesisState.Accounts = append(genesisState.Accounts, acc)
|
||||
genesisState.StakeData.Pool.LooseTokens = genesisState.StakeData.Pool.LooseTokens.Add(sdk.NewRat(100))
|
||||
genesisState.StakeData.Pool.LooseTokens = genesisState.StakeData.Pool.LooseTokens.Add(sdk.NewDec(100))
|
||||
}
|
||||
|
||||
appState, err := wire.MarshalJSONIndent(cdc, genesisState)
|
||||
appState, err := codec.MarshalJSONIndent(cdc, genesisState)
|
||||
require.NoError(t, err)
|
||||
genDoc.AppState = appState
|
||||
|
||||
@ -186,24 +266,30 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress
|
||||
// XXX: Need to set this so LCD knows the tendermint node address!
|
||||
viper.Set(client.FlagNode, config.RPC.ListenAddress)
|
||||
viper.Set(client.FlagChainID, genDoc.ChainID)
|
||||
// TODO Set to false once the upstream Tendermint proof verification issue is fixed.
|
||||
viper.Set(client.FlagTrustNode, true)
|
||||
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)
|
||||
|
||||
tests.WaitForNextHeightTM(tests.ExtractPortFromAddress(config.RPC.ListenAddress))
|
||||
lcd, err := startLCD(logger, listenAddr, cdc)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests.WaitForLCDStart(port)
|
||||
tests.WaitForHeight(1, port)
|
||||
|
||||
cleanup := func() {
|
||||
cleanup = func() {
|
||||
logger.Debug("cleaning up LCD initialization")
|
||||
node.Stop()
|
||||
node.Wait()
|
||||
lcd.Close()
|
||||
}
|
||||
|
||||
return cleanup, validatorsPKs, port
|
||||
return cleanup, valConsPubKeys, valOperAddrs, port
|
||||
}
|
||||
|
||||
// startTM creates and starts an in-process Tendermint node with memDB and
|
||||
@ -217,9 +303,14 @@ func startTM(
|
||||
) (*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())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, err := nm.NewNode(
|
||||
tmcfg,
|
||||
privVal,
|
||||
nodeKey,
|
||||
proxy.NewLocalClientCreator(app),
|
||||
genDocProvider,
|
||||
dbProvider,
|
||||
@ -244,7 +335,7 @@ func startTM(
|
||||
// startLCD starts the LCD.
|
||||
//
|
||||
// NOTE: This causes the thread to block.
|
||||
func startLCD(logger log.Logger, listenAddr string, cdc *wire.Codec) (net.Listener, error) {
|
||||
func startLCD(logger log.Logger, listenAddr string, cdc *codec.Codec) (net.Listener, error) {
|
||||
return tmrpc.StartHTTPServer(listenAddr, createHandler(cdc), logger, tmrpc.Config{})
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
package lcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
)
|
||||
|
||||
@ -17,13 +17,13 @@ func CLIVersionRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// connected node version REST handler endpoint
|
||||
func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
version, err := cliCtx.Query("/app/version")
|
||||
version, err := cliCtx.Query("/app/version", nil)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(fmt.Sprintf("Could't query version. Error: %s", err.Error())))
|
||||
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(version)
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,12 +8,11 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
flagSelect = "select"
|
||||
"github.com/spf13/viper"
|
||||
tmliteProxy "github.com/tendermint/tendermint/lite/proxy"
|
||||
)
|
||||
|
||||
//BlockCommand returns the verified block data for a given heights
|
||||
@ -25,9 +24,10 @@ func BlockCommand() *cobra.Command {
|
||||
RunE: printBlock,
|
||||
}
|
||||
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
|
||||
// TODO: change this to false when we can
|
||||
cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses")
|
||||
cmd.Flags().StringSlice(flagSelect, []string{"header", "tx"}, "Fields to return (header|txs|results)")
|
||||
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().String(client.FlagChainID, "", "Chain ID of Tendermint node")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -38,7 +38,6 @@ func getBlock(cliCtx context.CLIContext, height *int64) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: actually honor the --select flag!
|
||||
// header -> BlockchainInfo
|
||||
// header, tx -> Block
|
||||
// results -> BlockResults
|
||||
@ -47,13 +46,27 @@ func getBlock(cliCtx context.CLIContext, height *int64) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO move maarshalling into cmd/rest functions
|
||||
// output, err := tmwire.MarshalJSON(res)
|
||||
output, err := cdc.MarshalJSON(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if !cliCtx.TrustNode {
|
||||
check, err := cliCtx.Verify(res.Block.Height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = tmliteProxy.ValidateBlockMeta(res.BlockMeta, check)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = tmliteProxy.ValidateBlock(res.Block, check)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return output, nil
|
||||
|
||||
if cliCtx.Indent {
|
||||
return cdc.MarshalJSONIndent(res, "", " ")
|
||||
}
|
||||
return cdc.MarshalJSON(res)
|
||||
}
|
||||
|
||||
// get the current blockchain height
|
||||
@ -102,23 +115,23 @@ func BlockRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
vars := mux.Vars(r)
|
||||
height, err := strconv.ParseInt(vars["height"], 10, 64)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/block/{height}'."))
|
||||
return
|
||||
}
|
||||
chainHeight, err := GetChainHeight(cliCtx)
|
||||
if height > chainHeight {
|
||||
w.WriteHeader(404)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte("ERROR: Requested block height is bigger then the chain length."))
|
||||
return
|
||||
}
|
||||
output, err := getBlock(cliCtx, &height)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
w.Write(output)
|
||||
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,16 +140,16 @@ func LatestBlockRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
height, err := GetChainHeight(cliCtx)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
output, err := getBlock(cliCtx, &height)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
w.Write(output)
|
||||
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -40,6 +41,9 @@ func initClientCommand() *cobra.Command {
|
||||
cmd.Flags().String(flagGenesis, "", "Genesis file to verify header validity")
|
||||
cmd.Flags().String(flagCommit, "", "File with trusted and signed header")
|
||||
cmd.Flags().String(flagValHash, "", "Hash of trusted validator set (hex-encoded)")
|
||||
viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID))
|
||||
viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,8 @@ import (
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
"github.com/spf13/viper"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
@ -20,6 +22,8 @@ func statusCommand() *cobra.Command {
|
||||
}
|
||||
|
||||
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.FlagIndentResponse, false, "Add indent to JSON response")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -36,13 +40,20 @@ func getNodeStatus(cliCtx context.CLIContext) (*ctypes.ResultStatus, error) {
|
||||
// CMD
|
||||
|
||||
func printNodeStatus(cmd *cobra.Command, args []string) error {
|
||||
status, err := getNodeStatus(context.NewCLIContext())
|
||||
// No need to verify proof in getting node status
|
||||
viper.Set(client.FlagTrustNode, true)
|
||||
cliCtx := context.NewCLIContext()
|
||||
status, err := getNodeStatus(cliCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output, err := cdc.MarshalJSON(status)
|
||||
// output, err := cdc.MarshalJSONIndent(res, " ", "")
|
||||
var output []byte
|
||||
if cliCtx.Indent {
|
||||
output, err = cdc.MarshalJSONIndent(status, "", " ")
|
||||
} else {
|
||||
output, err = cdc.MarshalJSON(status)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -58,20 +69,13 @@ func NodeInfoRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
status, err := getNodeStatus(cliCtx)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
nodeInfo := status.NodeInfo
|
||||
output, err := cdc.MarshalJSON(nodeInfo)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(output)
|
||||
utils.PostProcessResponse(w, cdc, nodeInfo, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,14 +84,14 @@ func NodeSyncingRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
status, err := getNodeStatus(cliCtx)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
syncing := status.SyncInfo.CatchingUp
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@ -10,7 +11,9 @@ 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"
|
||||
"github.com/spf13/viper"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
@ -19,14 +22,17 @@ import (
|
||||
//ValidatorCommand returns the validator set for a given height
|
||||
func ValidatorCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "validator-set [height]",
|
||||
Use: "tendermint-validator-set [height]",
|
||||
Short: "Get the full tendermint validator set at given height",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: printValidators,
|
||||
}
|
||||
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
|
||||
// TODO: change this to false when we can
|
||||
cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses")
|
||||
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().String(client.FlagChainID, "", "Chain ID of Tendermint node")
|
||||
viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID))
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -45,7 +51,7 @@ type ResultValidatorsOutput struct {
|
||||
}
|
||||
|
||||
func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error) {
|
||||
bechValPubkey, err := sdk.Bech32ifyValPub(validator.PubKey)
|
||||
bechValPubkey, err := sdk.Bech32ifyConsPub(validator.PubKey)
|
||||
if err != nil {
|
||||
return ValidatorOutput{}, err
|
||||
}
|
||||
@ -70,6 +76,17 @@ func getValidators(cliCtx context.CLIContext, height *int64) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !cliCtx.TrustNode {
|
||||
check, err := cliCtx.Verify(validatorsRes.BlockHeight)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !bytes.Equal(check.ValidatorsHash, tmtypes.NewValidatorSet(validatorsRes.Validators).Hash()) {
|
||||
return nil, fmt.Errorf("got invalid validatorset")
|
||||
}
|
||||
}
|
||||
|
||||
outputValidatorsRes := ResultValidatorsOutput{
|
||||
BlockHeight: validatorsRes.BlockHeight,
|
||||
Validators: make([]ValidatorOutput, len(validatorsRes.Validators)),
|
||||
@ -82,12 +99,11 @@ func getValidators(cliCtx context.CLIContext, height *int64) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
output, err := cdc.MarshalJSON(outputValidatorsRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if cliCtx.Indent {
|
||||
return cdc.MarshalJSONIndent(outputValidatorsRes, "", " ")
|
||||
}
|
||||
return cdc.MarshalJSON(outputValidatorsRes)
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// CMD
|
||||
@ -124,26 +140,25 @@ func ValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
|
||||
height, err := strconv.ParseInt(vars["height"], 10, 64)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/validatorsets/{height}'."))
|
||||
return
|
||||
}
|
||||
|
||||
chainHeight, err := GetChainHeight(cliCtx)
|
||||
if height > chainHeight {
|
||||
w.WriteHeader(404)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte("ERROR: Requested block height is bigger then the chain length."))
|
||||
return
|
||||
}
|
||||
|
||||
output, err := getValidators(cliCtx, &height)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(output)
|
||||
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,18 +167,17 @@ func LatestValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerF
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
height, err := GetChainHeight(cliCtx)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
output, err := getValidators(cliCtx, &height)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(output)
|
||||
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,37 +1,60 @@
|
||||
package tx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// Tx Broadcast Body
|
||||
type BroadcastTxBody struct {
|
||||
TxBytes string `json:"tx"`
|
||||
const (
|
||||
// Returns with the response from CheckTx.
|
||||
flagSync = "sync"
|
||||
// Returns right away, with no response
|
||||
flagAsync = "async"
|
||||
// Only returns error if mempool.BroadcastTx errs (ie. problem with the app) or if we timeout waiting for tx to commit.
|
||||
flagBlock = "block"
|
||||
)
|
||||
|
||||
// BroadcastBody Tx Broadcast Body
|
||||
type BroadcastBody struct {
|
||||
TxBytes []byte `json:"tx"`
|
||||
Return string `json:"return"`
|
||||
}
|
||||
|
||||
// BroadcastTx REST Handler
|
||||
func BroadcastTxRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
// BroadcastTxRequest REST Handler
|
||||
// nolint: gocyclo
|
||||
func BroadcastTxRequest(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var m BroadcastTxBody
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&m)
|
||||
var m BroadcastBody
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte(err.Error()))
|
||||
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res, err := cliCtx.BroadcastTx([]byte(m.TxBytes))
|
||||
err = cdc.UnmarshalJSON(body, &m)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(string(res.Height)))
|
||||
var res interface{}
|
||||
switch m.Return {
|
||||
case flagBlock:
|
||||
res, err = cliCtx.BroadcastTx(m.TxBytes)
|
||||
case flagSync:
|
||||
res, err = cliCtx.BroadcastTxSync(m.TxBytes)
|
||||
case flagAsync:
|
||||
res, err = cliCtx.BroadcastTxAsync(m.TxBytes)
|
||||
default:
|
||||
utils.WriteErrorResponse(w, http.StatusInternalServerError, "unsupported return type. supported types: block, sync, async")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
utils.PostProcessResponse(w, cdc, res, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,25 +4,25 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
"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"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// QueryTxCmd implements the default command for a tx query.
|
||||
func QueryTxCmd(cdc *wire.Codec) *cobra.Command {
|
||||
func QueryTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "tx [hash]",
|
||||
Short: "Matches this txhash over all committed blocks",
|
||||
@ -30,11 +30,10 @@ func QueryTxCmd(cdc *wire.Codec) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// find the key to look up the account
|
||||
hashHexStr := args[0]
|
||||
trustNode := viper.GetBool(client.FlagTrustNode)
|
||||
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
output, err := queryTx(cdc, cliCtx, hashHexStr, trustNode)
|
||||
output, err := queryTx(cdc, cliCtx, hashHexStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -45,13 +44,15 @@ func QueryTxCmd(cdc *wire.Codec) *cobra.Command {
|
||||
}
|
||||
|
||||
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
|
||||
|
||||
// TODO: change this to false when we can
|
||||
cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses")
|
||||
viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode))
|
||||
cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node")
|
||||
viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID))
|
||||
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))
|
||||
return cmd
|
||||
}
|
||||
|
||||
func queryTx(cdc *wire.Codec, cliCtx context.CLIContext, hashHexStr string, trustNode bool) ([]byte, error) {
|
||||
func queryTx(cdc *codec.Codec, cliCtx context.CLIContext, hashHexStr string) ([]byte, error) {
|
||||
hash, err := hex.DecodeString(hashHexStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -62,21 +63,44 @@ func queryTx(cdc *wire.Codec, cliCtx context.CLIContext, hashHexStr string, trus
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := node.Tx(hash, !trustNode)
|
||||
res, err := node.Tx(hash, !cliCtx.TrustNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !cliCtx.TrustNode {
|
||||
err := ValidateTxResult(cliCtx, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
info, err := formatTxResult(cdc, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return wire.MarshalJSONIndent(cdc, info)
|
||||
if cliCtx.Indent {
|
||||
return cdc.MarshalJSONIndent(info, "", " ")
|
||||
}
|
||||
return cdc.MarshalJSON(info)
|
||||
}
|
||||
|
||||
func formatTxResult(cdc *wire.Codec, res *ctypes.ResultTx) (Info, error) {
|
||||
// TODO: verify the proof if requested
|
||||
// ValidateTxResult performs transaction verification
|
||||
func ValidateTxResult(cliCtx context.CLIContext, res *ctypes.ResultTx) error {
|
||||
check, err := cliCtx.Verify(res.Height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = res.Proof.Validate(check.Header.DataHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatTxResult(cdc *codec.Codec, res *ctypes.ResultTx) (Info, error) {
|
||||
tx, err := parseTx(cdc, res.Tx)
|
||||
if err != nil {
|
||||
return Info{}, err
|
||||
@ -98,7 +122,7 @@ type Info struct {
|
||||
Result abci.ResponseDeliverTx `json:"result"`
|
||||
}
|
||||
|
||||
func parseTx(cdc *wire.Codec, txBytes []byte) (sdk.Tx, error) {
|
||||
func parseTx(cdc *codec.Codec, txBytes []byte) (sdk.Tx, error) {
|
||||
var tx auth.StdTx
|
||||
|
||||
err := cdc.UnmarshalBinary(txBytes, &tx)
|
||||
@ -112,23 +136,16 @@ func parseTx(cdc *wire.Codec, txBytes []byte) (sdk.Tx, error) {
|
||||
// REST
|
||||
|
||||
// transaction query REST handler
|
||||
func QueryTxRequestHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc {
|
||||
func QueryTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
hashHexStr := vars["hash"]
|
||||
trustNode, err := strconv.ParseBool(r.FormValue("trust_node"))
|
||||
// trustNode defaults to true
|
||||
if err != nil {
|
||||
trustNode = true
|
||||
}
|
||||
|
||||
output, err := queryTx(cdc, cliCtx, hashHexStr, trustNode)
|
||||
output, err := queryTx(cdc, cliCtx, hashHexStr)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(output)
|
||||
utils.PostProcessResponse(w, cdc, output, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,11 +5,11 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
// AddCommands adds a number of tx-query related subcommands
|
||||
func AddCommands(cmd *cobra.Command, cdc *wire.Codec) {
|
||||
func AddCommands(cmd *cobra.Command, cdc *codec.Codec) {
|
||||
cmd.AddCommand(
|
||||
SearchTxCmd(cdc),
|
||||
QueryTxCmd(cdc),
|
||||
@ -17,9 +17,8 @@ func AddCommands(cmd *cobra.Command, cdc *wire.Codec) {
|
||||
}
|
||||
|
||||
// register REST routes
|
||||
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec) {
|
||||
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) {
|
||||
r.HandleFunc("/txs/{hash}", QueryTxRequestHandlerFn(cdc, cliCtx)).Methods("GET")
|
||||
r.HandleFunc("/txs", SearchTxRequestHandlerFn(cliCtx, cdc)).Methods("GET")
|
||||
// r.HandleFunc("/txs/sign", SignTxRequstHandler).Methods("POST")
|
||||
// r.HandleFunc("/txs/broadcast", BroadcastTxRequestHandler).Methods("POST")
|
||||
r.HandleFunc("/txs", BroadcastTxRequest(cliCtx, cdc)).Methods("POST")
|
||||
}
|
||||
|
||||
@ -9,12 +9,13 @@ import (
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
@ -24,10 +25,23 @@ const (
|
||||
)
|
||||
|
||||
// default client command to search through tagged transactions
|
||||
func SearchTxCmd(cdc *wire.Codec) *cobra.Command {
|
||||
func SearchTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "txs",
|
||||
Short: "Search for all transactions that match the given tags",
|
||||
Short: "Search for all transactions that match the given tags.",
|
||||
Long: strings.TrimSpace(`
|
||||
Search for transactions that match the given tags. By default, transactions must match ALL tags
|
||||
passed to the --tags option. To match any transaction, use the --any option.
|
||||
|
||||
For example:
|
||||
|
||||
$ gaiacli tendermint txs --tag test1,test2
|
||||
|
||||
will match any transaction tagged with both test1,test2. To match a transaction tagged with either
|
||||
test1 or test2, use:
|
||||
|
||||
$ gaiacli tendermint txs --tag test1,test2 --any
|
||||
`),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
tags := viper.GetStringSlice(flagTags)
|
||||
|
||||
@ -38,7 +52,13 @@ func SearchTxCmd(cdc *wire.Codec) *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
output, err := cdc.MarshalJSON(txs)
|
||||
var output []byte
|
||||
if cliCtx.Indent {
|
||||
output, err = cdc.MarshalJSONIndent(txs, "", " ")
|
||||
} else {
|
||||
output, err = cdc.MarshalJSON(txs)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -49,15 +69,17 @@ func SearchTxCmd(cdc *wire.Codec) *cobra.Command {
|
||||
}
|
||||
|
||||
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
|
||||
|
||||
// TODO: change this to false once proofs built in
|
||||
cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses")
|
||||
cmd.Flags().StringSlice(flagTags, nil, "Tags that must match (may provide multiple)")
|
||||
viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode))
|
||||
cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node")
|
||||
viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID))
|
||||
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().StringSlice(flagTags, nil, "Comma-separated list of tags that must match")
|
||||
cmd.Flags().Bool(flagAny, false, "Return transactions that match ANY tag, rather than ALL")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func searchTxs(cliCtx context.CLIContext, cdc *wire.Codec, tags []string) ([]Info, error) {
|
||||
func searchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string) ([]Info, error) {
|
||||
if len(tags) == 0 {
|
||||
return nil, errors.New("must declare at least one tag to search")
|
||||
}
|
||||
@ -71,7 +93,7 @@ func searchTxs(cliCtx context.CLIContext, cdc *wire.Codec, tags []string) ([]Inf
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prove := !viper.GetBool(client.FlagTrustNode)
|
||||
prove := !cliCtx.TrustNode
|
||||
|
||||
// TODO: take these as args
|
||||
page := 0
|
||||
@ -81,6 +103,15 @@ func searchTxs(cliCtx context.CLIContext, cdc *wire.Codec, tags []string) ([]Inf
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if prove {
|
||||
for _, tx := range res.Txs {
|
||||
err := ValidateTxResult(cliCtx, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info, err := FormatTxResults(cdc, res.Txs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -90,7 +121,7 @@ func searchTxs(cliCtx context.CLIContext, cdc *wire.Codec, tags []string) ([]Inf
|
||||
}
|
||||
|
||||
// parse the indexed txs into an array of Info
|
||||
func FormatTxResults(cdc *wire.Codec, res []*ctypes.ResultTx) ([]Info, error) {
|
||||
func FormatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]Info, error) {
|
||||
var err error
|
||||
out := make([]Info, len(res))
|
||||
for i := range res {
|
||||
@ -106,11 +137,11 @@ func FormatTxResults(cdc *wire.Codec, res []*ctypes.ResultTx) ([]Info, error) {
|
||||
// REST
|
||||
|
||||
// Search Tx REST Handler
|
||||
func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc {
|
||||
func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
tag := r.FormValue("tag")
|
||||
if tag == "" {
|
||||
w.WriteHeader(400)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("You need to provide at least a tag as a key=value pair to search for. Postfix the key with _bech32 to search bech32-encoded addresses or public keys"))
|
||||
return
|
||||
}
|
||||
@ -120,8 +151,7 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.H
|
||||
|
||||
value, err := url.QueryUnescape(keyValue[1])
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Could not decode address: " + err.Error()))
|
||||
utils.WriteErrorResponse(w, http.StatusBadRequest, sdk.AppendMsgToErr("could not decode address", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@ -130,8 +160,7 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.H
|
||||
prefix := strings.Split(bech32address, "1")[0]
|
||||
bz, err := sdk.GetFromBech32(bech32address, prefix)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte(err.Error()))
|
||||
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@ -140,8 +169,7 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.H
|
||||
|
||||
txs, err := searchTxs(cliCtx, cdc, []string{tag})
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@ -150,13 +178,6 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.H
|
||||
return
|
||||
}
|
||||
|
||||
output, err := cdc.MarshalJSON(txs)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(output)
|
||||
utils.PostProcessResponse(w, cdc, txs, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
package tx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
keybase "github.com/cosmos/cosmos-sdk/client/keys"
|
||||
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
)
|
||||
|
||||
// REST request body for signed txs
|
||||
// TODO does this need to be exposed?
|
||||
type SignTxBody struct {
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
TxBytes string `json:"tx"`
|
||||
}
|
||||
|
||||
// sign transaction REST Handler
|
||||
func SignTxRequstHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var kb keys.Keybase
|
||||
var m SignTxBody
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&m)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
kb, err = keybase.GetKeyBase()
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
//TODO check if account exists
|
||||
sig, _, err := kb.Sign(m.Name, m.Password, []byte(m.TxBytes))
|
||||
if err != nil {
|
||||
w.WriteHeader(403)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(sig)
|
||||
}
|
||||
273
client/utils/rest.go
Normal file
273
client/utils/rest.go
Normal file
@ -0,0 +1,273 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
queryArgDryRun = "simulate"
|
||||
queryArgGenerateOnly = "generate_only"
|
||||
)
|
||||
|
||||
//----------------------------------------
|
||||
// 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 int64) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(fmt.Sprintf(`{"gas_estimate":%v}`, gas)))
|
||||
}
|
||||
|
||||
// HasDryRunArg returns true if the request's URL query contains the dry run
|
||||
// argument and its value is set to "true".
|
||||
func HasDryRunArg(r *http.Request) bool {
|
||||
return urlQueryHasArg(r.URL, queryArgDryRun)
|
||||
}
|
||||
|
||||
// HasGenerateOnlyArg returns whether a URL's query "generate-only" parameter
|
||||
// is set to "true".
|
||||
func HasGenerateOnlyArg(r *http.Request) bool {
|
||||
return urlQueryHasArg(r.URL, queryArgGenerateOnly)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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, txBldr authtxb.TxBuilder, msgs []sdk.Msg) {
|
||||
stdMsg, err := txBldr.Build(msgs)
|
||||
if err != nil {
|
||||
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
output, err := txBldr.Codec.MarshalJSON(auth.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo))
|
||||
if err != nil {
|
||||
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(output)
|
||||
return
|
||||
}
|
||||
|
||||
func urlQueryHasArg(url *url.URL, arg string) bool { return url.Query().Get(arg) == "true" }
|
||||
|
||||
//----------------------------------------
|
||||
// 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"`
|
||||
ChainID string `json:"chain_id"`
|
||||
AccountNumber int64 `json:"account_number"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
Gas string `json:"gas"`
|
||||
GasAdjustment string `json:"gas_adjustment"`
|
||||
}
|
||||
|
||||
// Sanitize performs basic sanitization on a BaseReq object.
|
||||
func (br BaseReq) Sanitize() BaseReq {
|
||||
return BaseReq{
|
||||
Name: strings.TrimSpace(br.Name),
|
||||
Password: strings.TrimSpace(br.Password),
|
||||
ChainID: strings.TrimSpace(br.ChainID),
|
||||
Gas: strings.TrimSpace(br.Gas),
|
||||
GasAdjustment: strings.TrimSpace(br.GasAdjustment),
|
||||
AccountNumber: br.AccountNumber,
|
||||
Sequence: br.Sequence,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
switch {
|
||||
case len(br.Name) == 0:
|
||||
WriteErrorResponse(w, http.StatusUnauthorized, "name required but not specified")
|
||||
return false
|
||||
|
||||
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, "chainID required but not specified")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 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) {
|
||||
simulateGas, gas, err := client.ReadGasFlag(baseReq.Gas)
|
||||
if err != nil {
|
||||
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
adjustment, ok := ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
txBldr := authtxb.TxBuilder{
|
||||
Codec: cdc,
|
||||
Gas: gas,
|
||||
GasAdjustment: adjustment,
|
||||
SimulateGas: simulateGas,
|
||||
ChainID: baseReq.ChainID,
|
||||
AccountNumber: baseReq.AccountNumber,
|
||||
Sequence: baseReq.Sequence,
|
||||
}
|
||||
|
||||
if HasDryRunArg(r) || txBldr.SimulateGas {
|
||||
newBldr, err := EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, msgs)
|
||||
if err != nil {
|
||||
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if HasDryRunArg(r) {
|
||||
WriteSimulationResponse(w, newBldr.Gas)
|
||||
return
|
||||
}
|
||||
|
||||
txBldr = newBldr
|
||||
}
|
||||
|
||||
if HasGenerateOnlyArg(r) {
|
||||
WriteGenerateStdTxResponse(w, 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)
|
||||
}
|
||||
@ -1,60 +1,245 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
||||
"github.com/tendermint/go-amino"
|
||||
"github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
// SendTx implements a auxiliary handler that facilitates sending a series of
|
||||
// messages in a signed transaction given a TxContext 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.
|
||||
func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) error {
|
||||
if err := cliCtx.EnsureAccountExists(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
from, err := cliCtx.GetFromAddress()
|
||||
// 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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: (ref #1903) Allow for user supplied account number without
|
||||
// automatically doing a manual lookup.
|
||||
if txCtx.AccountNumber == 0 {
|
||||
accNum, err := cliCtx.GetAccountNumber(from)
|
||||
name, err := cliCtx.GetFromName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if txBldr.SimulateGas || cliCtx.DryRun {
|
||||
txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txCtx = txCtx.WithAccountNumber(accNum)
|
||||
fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.Gas)
|
||||
}
|
||||
if cliCtx.DryRun {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: (ref #1903) Allow for user supplied account sequence without
|
||||
// automatically doing a manual lookup.
|
||||
if txCtx.Sequence == 0 {
|
||||
accSeq, err := cliCtx.GetAccountSequence(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txCtx = txCtx.WithSequence(accSeq)
|
||||
}
|
||||
|
||||
passphrase, err := keys.GetPassphrase(cliCtx.FromAddressName)
|
||||
passphrase, err := keys.GetPassphrase(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// build and sign the transaction
|
||||
txBytes, err := txCtx.BuildAndSign(cliCtx.FromAddressName, passphrase, msgs)
|
||||
txBytes, err := txBldr.BuildAndSign(name, passphrase, msgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// broadcast to a Tendermint node
|
||||
return cliCtx.EnsureBroadcastTx(txBytes)
|
||||
_, err = cliCtx.BroadcastTx(txBytes)
|
||||
return err
|
||||
}
|
||||
|
||||
// EnrichCtxWithGas 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)
|
||||
if err != nil {
|
||||
return txBldr, err
|
||||
}
|
||||
return txBldr.WithGas(adjusted), nil
|
||||
}
|
||||
|
||||
// CalculateGas simulates the execution of a transaction and returns
|
||||
// both the estimate obtained by the query and the adjusted amount.
|
||||
func CalculateGas(queryFunc func(string, common.HexBytes) ([]byte, error), cdc *amino.Codec, txBytes []byte, adjustment float64) (estimate, adjusted int64, err error) {
|
||||
// run a simulation (via /app/simulate query) to
|
||||
// estimate gas and update TxBuilder accordingly
|
||||
rawRes, err := queryFunc("/app/simulate", txBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
estimate, err = parseQueryResponse(cdc, rawRes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
adjusted = adjustGasEstimate(estimate, adjustment)
|
||||
return
|
||||
}
|
||||
|
||||
// PrintUnsignedStdTx builds an unsigned StdTx and prints it to os.Stdout.
|
||||
// Don't perform online validation or lookups if offline is true.
|
||||
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)
|
||||
} else {
|
||||
stdTx, err = buildUnsignedStdTx(txBldr, cliCtx, msgs)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
json, err := txBldr.Codec.MarshalJSON(stdTx)
|
||||
if err == nil {
|
||||
fmt.Printf("%s\n", json)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SignStdTx appends a signature to a StdTx and returns a copy of a it. If appendSig
|
||||
// is false, it replaces the signatures already attached with the new signature.
|
||||
// Don't perform online validation or lookups if offline is true.
|
||||
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
|
||||
}
|
||||
info, err := keybase.Get(name)
|
||||
if err != nil {
|
||||
return signedStdTx, err
|
||||
}
|
||||
addr := info.GetPubKey().Address()
|
||||
|
||||
// Check whether the address is a signer
|
||||
if !isTxSigner(sdk.AccAddress(addr), stdTx.GetSigners()) {
|
||||
fmt.Fprintf(os.Stderr, "WARNING: The generated transaction's intended signer does not match the given signer: '%v'\n", name)
|
||||
}
|
||||
|
||||
if !offline && txBldr.AccountNumber == 0 {
|
||||
accNum, err := cliCtx.GetAccountNumber(addr)
|
||||
if err != nil {
|
||||
return signedStdTx, err
|
||||
}
|
||||
txBldr = txBldr.WithAccountNumber(accNum)
|
||||
}
|
||||
|
||||
if !offline && txBldr.Sequence == 0 {
|
||||
accSeq, err := cliCtx.GetAccountSequence(addr)
|
||||
if err != nil {
|
||||
return signedStdTx, err
|
||||
}
|
||||
txBldr = txBldr.WithSequence(accSeq)
|
||||
}
|
||||
|
||||
passphrase, err := keys.GetPassphrase(name)
|
||||
if err != nil {
|
||||
return signedStdTx, err
|
||||
}
|
||||
return txBldr.SignStdTx(name, passphrase, stdTx, appendSig)
|
||||
}
|
||||
|
||||
// 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 int64, err error) {
|
||||
txBytes, err := txBldr.BuildWithPubKey(name, msgs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, txBldr.GasAdjustment)
|
||||
return
|
||||
}
|
||||
|
||||
func adjustGasEstimate(estimate int64, adjustment float64) int64 {
|
||||
return int64(adjustment * float64(estimate))
|
||||
}
|
||||
|
||||
func parseQueryResponse(cdc *amino.Codec, rawRes []byte) (int64, error) {
|
||||
var simulationResult sdk.Result
|
||||
if err := cdc.UnmarshalBinary(rawRes, &simulationResult); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return simulationResult.GasUsed, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// TODO: (ref #1903) Allow for user supplied account number without
|
||||
// automatically doing a manual lookup.
|
||||
if txBldr.AccountNumber == 0 {
|
||||
accNum, err := cliCtx.GetAccountNumber(from)
|
||||
if err != nil {
|
||||
return txBldr, err
|
||||
}
|
||||
txBldr = txBldr.WithAccountNumber(accNum)
|
||||
}
|
||||
|
||||
// TODO: (ref #1903) Allow for user supplied account sequence without
|
||||
// automatically doing a manual lookup.
|
||||
if txBldr.Sequence == 0 {
|
||||
accSeq, err := cliCtx.GetAccountSequence(from)
|
||||
if err != nil {
|
||||
return txBldr, err
|
||||
}
|
||||
txBldr = txBldr.WithSequence(accSeq)
|
||||
}
|
||||
return txBldr, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return buildUnsignedStdTxOffline(txBldr, cliCtx, msgs)
|
||||
}
|
||||
|
||||
func buildUnsignedStdTxOffline(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) {
|
||||
if txBldr.SimulateGas {
|
||||
var name string
|
||||
name, err = cliCtx.GetFromName()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.Gas)
|
||||
}
|
||||
stdSignMsg, err := txBldr.Build(msgs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return auth.NewStdTx(stdSignMsg.Msgs, stdSignMsg.Fee, nil, stdSignMsg.Memo), nil
|
||||
}
|
||||
|
||||
func isTxSigner(user sdk.AccAddress, signers []sdk.AccAddress) bool {
|
||||
for _, s := range signers {
|
||||
if bytes.Equal(user.Bytes(), s.Bytes()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
58
client/utils/utils_test.go
Normal file
58
client/utils/utils_test.go
Normal file
@ -0,0 +1,58 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
func TestParseQueryResponse(t *testing.T) {
|
||||
cdc := app.MakeCodec()
|
||||
sdkResBytes := cdc.MustMarshalBinary(sdk.Result{GasUsed: 10})
|
||||
gas, err := parseQueryResponse(cdc, sdkResBytes)
|
||||
assert.Equal(t, gas, int64(10))
|
||||
assert.Nil(t, err)
|
||||
gas, err = parseQueryResponse(cdc, []byte("fuzzy"))
|
||||
assert.Equal(t, gas, int64(0))
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestCalculateGas(t *testing.T) {
|
||||
cdc := app.MakeCodec()
|
||||
makeQueryFunc := func(gasUsed int64, wantErr bool) func(string, common.HexBytes) ([]byte, error) {
|
||||
return func(string, common.HexBytes) ([]byte, error) {
|
||||
if wantErr {
|
||||
return nil, errors.New("")
|
||||
}
|
||||
return cdc.MustMarshalBinary(sdk.Result{GasUsed: gasUsed}), nil
|
||||
}
|
||||
}
|
||||
type args struct {
|
||||
queryFuncGasUsed int64
|
||||
queryFuncWantErr bool
|
||||
adjustment float64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantEstimate int64
|
||||
wantAdjusted int64
|
||||
wantErr bool
|
||||
}{
|
||||
{"error", args{0, true, 1.2}, 0, 0, true},
|
||||
{"adjusted gas", args{10, false, 1.2}, 10, 12, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
queryFunc := makeQueryFunc(tt.args.queryFuncGasUsed, tt.args.queryFuncWantErr)
|
||||
gotEstimate, gotAdjusted, err := CalculateGas(queryFunc, cdc, []byte(""), tt.args.adjustment)
|
||||
assert.Equal(t, err != nil, tt.wantErr)
|
||||
assert.Equal(t, gotEstimate, tt.wantEstimate)
|
||||
assert.Equal(t, gotAdjusted, tt.wantAdjusted)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -60,6 +60,7 @@ func resolveProjectPath(remoteProjectPath string) string {
|
||||
return gopath + string(os.PathSeparator) + "src" + string(os.PathSeparator) + remoteProjectPath
|
||||
}
|
||||
|
||||
// nolint: unparam, errcheck
|
||||
func copyBasecoinTemplate(projectName string, projectPath string, remoteProjectPath string) {
|
||||
basecoinProjectPath := resolveProjectPath(remoteBasecoinPath)
|
||||
filepath.Walk(basecoinProjectPath, func(path string, f os.FileInfo, err error) error {
|
||||
@ -88,6 +89,7 @@ func copyBasecoinTemplate(projectName string, projectPath string, remoteProjectP
|
||||
})
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
func createGopkg(projectPath string) {
|
||||
// Create gopkg.toml file
|
||||
dependencies := map[string]string{
|
||||
@ -111,6 +113,7 @@ func createGopkg(projectPath string) {
|
||||
ioutil.WriteFile(projectPath+"/Gopkg.toml", []byte(contents), os.ModePerm)
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
func createMakefile(projectPath string) {
|
||||
// Create makefile
|
||||
// TODO: Should we use tools/ directory as in Cosmos-SDK to get tools for linting etc.
|
||||
|
||||
@ -2,29 +2,33 @@ package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
bam "github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/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"
|
||||
"github.com/cosmos/cosmos-sdk/x/params"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
|
||||
bam "github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
"github.com/cosmos/cosmos-sdk/x/ibc"
|
||||
"github.com/cosmos/cosmos-sdk/x/params"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
)
|
||||
|
||||
const (
|
||||
appName = "GaiaApp"
|
||||
// DefaultKeyPass contains the default key password for genesis transactions
|
||||
DefaultKeyPass = "12345678"
|
||||
)
|
||||
|
||||
// default home directories for expected binaries
|
||||
@ -36,26 +40,30 @@ var (
|
||||
// Extended ABCI application
|
||||
type GaiaApp struct {
|
||||
*bam.BaseApp
|
||||
cdc *wire.Codec
|
||||
cdc *codec.Codec
|
||||
|
||||
// keys to access the substores
|
||||
keyMain *sdk.KVStoreKey
|
||||
keyAccount *sdk.KVStoreKey
|
||||
keyIBC *sdk.KVStoreKey
|
||||
keyStake *sdk.KVStoreKey
|
||||
tkeyStake *sdk.TransientStoreKey
|
||||
keySlashing *sdk.KVStoreKey
|
||||
keyMint *sdk.KVStoreKey
|
||||
keyDistr *sdk.KVStoreKey
|
||||
tkeyDistr *sdk.TransientStoreKey
|
||||
keyGov *sdk.KVStoreKey
|
||||
keyFeeCollection *sdk.KVStoreKey
|
||||
keyParams *sdk.KVStoreKey
|
||||
tkeyParams *sdk.TransientStoreKey
|
||||
|
||||
// Manage getting and setting accounts
|
||||
accountMapper auth.AccountMapper
|
||||
accountKeeper auth.AccountKeeper
|
||||
feeCollectionKeeper auth.FeeCollectionKeeper
|
||||
coinKeeper bank.Keeper
|
||||
ibcMapper ibc.Mapper
|
||||
bankKeeper bank.Keeper
|
||||
stakeKeeper stake.Keeper
|
||||
slashingKeeper slashing.Keeper
|
||||
mintKeeper mint.Keeper
|
||||
distrKeeper distr.Keeper
|
||||
govKeeper gov.Keeper
|
||||
paramsKeeper params.Keeper
|
||||
}
|
||||
@ -72,8 +80,11 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio
|
||||
cdc: cdc,
|
||||
keyMain: sdk.NewKVStoreKey("main"),
|
||||
keyAccount: sdk.NewKVStoreKey("acc"),
|
||||
keyIBC: sdk.NewKVStoreKey("ibc"),
|
||||
keyStake: sdk.NewKVStoreKey("stake"),
|
||||
tkeyStake: sdk.NewTransientStoreKey("transient_stake"),
|
||||
keyMint: sdk.NewKVStoreKey("mint"),
|
||||
keyDistr: sdk.NewKVStoreKey("distr"),
|
||||
tkeyDistr: sdk.NewTransientStoreKey("transient_distr"),
|
||||
keySlashing: sdk.NewKVStoreKey("slashing"),
|
||||
keyGov: sdk.NewKVStoreKey("gov"),
|
||||
keyFeeCollection: sdk.NewKVStoreKey("fee"),
|
||||
@ -81,37 +92,78 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio
|
||||
tkeyParams: sdk.NewTransientStoreKey("transient_params"),
|
||||
}
|
||||
|
||||
// define the accountMapper
|
||||
app.accountMapper = auth.NewAccountMapper(
|
||||
// define the accountKeeper
|
||||
app.accountKeeper = auth.NewAccountKeeper(
|
||||
app.cdc,
|
||||
app.keyAccount, // target store
|
||||
auth.ProtoBaseAccount, // prototype
|
||||
)
|
||||
|
||||
// add handlers
|
||||
app.coinKeeper = bank.NewKeeper(app.accountMapper)
|
||||
app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace))
|
||||
app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams)
|
||||
app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace))
|
||||
app.govKeeper = gov.NewKeeper(app.cdc, app.keyGov, app.paramsKeeper.Setter(), app.coinKeeper, app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace))
|
||||
app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.keyFeeCollection)
|
||||
app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace))
|
||||
app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper)
|
||||
app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(
|
||||
app.cdc,
|
||||
app.keyFeeCollection,
|
||||
)
|
||||
app.paramsKeeper = params.NewKeeper(
|
||||
app.cdc,
|
||||
app.keyParams, app.tkeyParams,
|
||||
)
|
||||
app.stakeKeeper = stake.NewKeeper(
|
||||
app.cdc,
|
||||
app.keyStake, app.tkeyStake,
|
||||
app.bankKeeper, app.paramsKeeper.Subspace(stake.DefaultParamspace),
|
||||
app.RegisterCodespace(stake.DefaultCodespace),
|
||||
)
|
||||
app.mintKeeper = mint.NewKeeper(app.cdc, app.keyMint,
|
||||
app.paramsKeeper.Subspace(mint.DefaultParamspace),
|
||||
app.stakeKeeper, app.feeCollectionKeeper,
|
||||
)
|
||||
app.distrKeeper = distr.NewKeeper(
|
||||
app.cdc,
|
||||
app.keyDistr,
|
||||
app.paramsKeeper.Subspace(distr.DefaultParamspace),
|
||||
app.bankKeeper, app.stakeKeeper, app.feeCollectionKeeper,
|
||||
app.RegisterCodespace(stake.DefaultCodespace),
|
||||
)
|
||||
app.slashingKeeper = slashing.NewKeeper(
|
||||
app.cdc,
|
||||
app.keySlashing,
|
||||
app.stakeKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace),
|
||||
app.RegisterCodespace(slashing.DefaultCodespace),
|
||||
)
|
||||
app.govKeeper = gov.NewKeeper(
|
||||
app.cdc,
|
||||
app.keyGov,
|
||||
app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamspace), app.bankKeeper, app.stakeKeeper,
|
||||
app.RegisterCodespace(gov.DefaultCodespace),
|
||||
)
|
||||
|
||||
// register the staking hooks
|
||||
app.stakeKeeper = app.stakeKeeper.WithHooks(
|
||||
NewHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks()))
|
||||
|
||||
// register message routes
|
||||
app.Router().
|
||||
AddRoute("bank", bank.NewHandler(app.coinKeeper)).
|
||||
AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.coinKeeper)).
|
||||
AddRoute("bank", bank.NewHandler(app.bankKeeper)).
|
||||
AddRoute("stake", stake.NewHandler(app.stakeKeeper)).
|
||||
AddRoute("distr", distr.NewHandler(app.distrKeeper)).
|
||||
AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)).
|
||||
AddRoute("gov", gov.NewHandler(app.govKeeper))
|
||||
|
||||
app.QueryRouter().
|
||||
AddRoute("gov", gov.NewQuerier(app.govKeeper)).
|
||||
AddRoute("stake", stake.NewQuerier(app.stakeKeeper, app.cdc))
|
||||
|
||||
// initialize BaseApp
|
||||
app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyStake, app.keyMint, app.keyDistr,
|
||||
app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams)
|
||||
app.SetInitChainer(app.initChainer)
|
||||
app.SetBeginBlocker(app.BeginBlocker)
|
||||
app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper))
|
||||
app.MountStoresTransient(app.tkeyParams, app.tkeyStake, app.tkeyDistr)
|
||||
app.SetEndBlocker(app.EndBlocker)
|
||||
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper))
|
||||
app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams)
|
||||
app.MountStore(app.tkeyParams, sdk.StoreTypeTransient)
|
||||
|
||||
err := app.LoadLatestVersion(app.keyMain)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
@ -121,16 +173,16 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio
|
||||
}
|
||||
|
||||
// custom tx codec
|
||||
func MakeCodec() *wire.Codec {
|
||||
var cdc = wire.NewCodec()
|
||||
ibc.RegisterWire(cdc)
|
||||
bank.RegisterWire(cdc)
|
||||
stake.RegisterWire(cdc)
|
||||
slashing.RegisterWire(cdc)
|
||||
gov.RegisterWire(cdc)
|
||||
auth.RegisterWire(cdc)
|
||||
sdk.RegisterWire(cdc)
|
||||
wire.RegisterCrypto(cdc)
|
||||
func MakeCodec() *codec.Codec {
|
||||
var cdc = codec.New()
|
||||
bank.RegisterCodec(cdc)
|
||||
stake.RegisterCodec(cdc)
|
||||
distr.RegisterCodec(cdc)
|
||||
slashing.RegisterCodec(cdc)
|
||||
gov.RegisterCodec(cdc)
|
||||
auth.RegisterCodec(cdc)
|
||||
sdk.RegisterCodec(cdc)
|
||||
codec.RegisterCrypto(cdc)
|
||||
return cdc
|
||||
}
|
||||
|
||||
@ -138,6 +190,12 @@ func MakeCodec() *wire.Codec {
|
||||
func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
|
||||
tags := slashing.BeginBlocker(ctx, req, app.slashingKeeper)
|
||||
|
||||
// distribute rewards from previous block
|
||||
distr.BeginBlocker(ctx, req, app.distrKeeper)
|
||||
|
||||
// mint new tokens for this new block
|
||||
mint.BeginBlocker(ctx, app.mintKeeper)
|
||||
|
||||
return abci.ResponseBeginBlock{
|
||||
Tags: tags.ToKVPairs(),
|
||||
}
|
||||
@ -146,10 +204,13 @@ func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) ab
|
||||
// application updates every end block
|
||||
// nolint: unparam
|
||||
func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock {
|
||||
|
||||
tags := gov.EndBlocker(ctx, app.govKeeper)
|
||||
validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper)
|
||||
|
||||
// Add these new validators to the addr -> pubkey map.
|
||||
app.slashingKeeper.AddValidators(ctx, validatorUpdates)
|
||||
|
||||
return abci.ResponseEndBlock{
|
||||
ValidatorUpdates: validatorUpdates,
|
||||
Tags: tags,
|
||||
@ -171,21 +232,57 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci
|
||||
// load the accounts
|
||||
for _, gacc := range genesisState.Accounts {
|
||||
acc := gacc.ToAccount()
|
||||
acc.AccountNumber = app.accountMapper.GetNextAccountNumber(ctx)
|
||||
app.accountMapper.SetAccount(ctx, acc)
|
||||
acc.AccountNumber = app.accountKeeper.GetNextAccountNumber(ctx)
|
||||
app.accountKeeper.SetAccount(ctx, acc)
|
||||
}
|
||||
|
||||
// load the initial stake information
|
||||
validators, err := stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData)
|
||||
if err != nil {
|
||||
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
|
||||
// return sdk.ErrGenesisParse("").TraceCause(err, "")
|
||||
panic(err) // TODO find a way to do this w/o panics
|
||||
}
|
||||
|
||||
// load the address to pubkey map
|
||||
slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.StakeData)
|
||||
slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakeData)
|
||||
gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData)
|
||||
mint.InitGenesis(ctx, app.mintKeeper, genesisState.MintData)
|
||||
distr.InitGenesis(ctx, app.distrKeeper, genesisState.DistrData)
|
||||
err = GaiaValidateGenesisState(genesisState)
|
||||
if err != nil {
|
||||
panic(err) // TODO find a way to do this w/o panics
|
||||
}
|
||||
|
||||
gov.InitGenesis(ctx, app.govKeeper, gov.DefaultGenesisState())
|
||||
if len(genesisState.GenTxs) > 0 {
|
||||
for _, genTx := range genesisState.GenTxs {
|
||||
var tx auth.StdTx
|
||||
err = app.cdc.UnmarshalJSON(genTx, &tx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
bz := app.cdc.MustMarshalBinary(tx)
|
||||
res := app.BaseApp.DeliverTx(bz)
|
||||
if !res.IsOK() {
|
||||
panic(res.Log)
|
||||
}
|
||||
}
|
||||
|
||||
validators = app.stakeKeeper.ApplyAndReturnValidatorSetUpdates(ctx)
|
||||
}
|
||||
app.slashingKeeper.AddValidators(ctx, validators)
|
||||
|
||||
// sanity check
|
||||
if len(req.Validators) > 0 {
|
||||
if len(req.Validators) != len(validators) {
|
||||
panic(fmt.Errorf("len(RequestInitChain.Validators) != len(validators) (%d != %d) ", len(req.Validators), len(validators)))
|
||||
}
|
||||
sort.Sort(abci.ValidatorUpdates(req.Validators))
|
||||
sort.Sort(abci.ValidatorUpdates(validators))
|
||||
for i, val := range validators {
|
||||
if !val.Equal(req.Validators[i]) {
|
||||
panic(fmt.Errorf("validators[%d] != req.Validators[%d] ", i, i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return abci.ResponseInitChain{
|
||||
Validators: validators,
|
||||
@ -203,16 +300,65 @@ func (app *GaiaApp) ExportAppStateAndValidators() (appState json.RawMessage, val
|
||||
accounts = append(accounts, account)
|
||||
return false
|
||||
}
|
||||
app.accountMapper.IterateAccounts(ctx, appendAccount)
|
||||
|
||||
genState := GenesisState{
|
||||
Accounts: accounts,
|
||||
StakeData: stake.WriteGenesis(ctx, app.stakeKeeper),
|
||||
}
|
||||
appState, err = wire.MarshalJSONIndent(app.cdc, genState)
|
||||
app.accountKeeper.IterateAccounts(ctx, appendAccount)
|
||||
genState := NewGenesisState(
|
||||
accounts,
|
||||
stake.WriteGenesis(ctx, app.stakeKeeper),
|
||||
mint.WriteGenesis(ctx, app.mintKeeper),
|
||||
distr.WriteGenesis(ctx, app.distrKeeper),
|
||||
gov.WriteGenesis(ctx, app.govKeeper),
|
||||
slashing.GenesisState{}, // TODO create write methods
|
||||
)
|
||||
appState, err = codec.MarshalJSONIndent(app.cdc, genState)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
validators = stake.WriteValidators(ctx, app.stakeKeeper)
|
||||
return appState, validators, nil
|
||||
}
|
||||
|
||||
//______________________________________________________________________________________________
|
||||
|
||||
// Combined Staking Hooks
|
||||
type Hooks struct {
|
||||
dh distr.Hooks
|
||||
sh slashing.Hooks
|
||||
}
|
||||
|
||||
func NewHooks(dh distr.Hooks, sh slashing.Hooks) Hooks {
|
||||
return Hooks{dh, sh}
|
||||
}
|
||||
|
||||
var _ sdk.StakingHooks = Hooks{}
|
||||
|
||||
// nolint
|
||||
func (h Hooks) OnValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) {
|
||||
h.dh.OnValidatorCreated(ctx, valAddr)
|
||||
}
|
||||
func (h Hooks) OnValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) {
|
||||
h.dh.OnValidatorModified(ctx, valAddr)
|
||||
}
|
||||
func (h Hooks) OnValidatorRemoved(ctx sdk.Context, valAddr sdk.ValAddress) {
|
||||
h.dh.OnValidatorRemoved(ctx, valAddr)
|
||||
}
|
||||
func (h Hooks) OnValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) {
|
||||
h.dh.OnValidatorBonded(ctx, consAddr, valAddr)
|
||||
h.sh.OnValidatorBonded(ctx, consAddr, valAddr)
|
||||
}
|
||||
func (h Hooks) OnValidatorPowerDidChange(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) {
|
||||
h.dh.OnValidatorPowerDidChange(ctx, consAddr, valAddr)
|
||||
h.sh.OnValidatorPowerDidChange(ctx, consAddr, valAddr)
|
||||
}
|
||||
func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) {
|
||||
h.dh.OnValidatorBeginUnbonding(ctx, consAddr, valAddr)
|
||||
h.sh.OnValidatorBeginUnbonding(ctx, consAddr, valAddr)
|
||||
}
|
||||
func (h Hooks) OnDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
|
||||
h.dh.OnDelegationCreated(ctx, delAddr, valAddr)
|
||||
}
|
||||
func (h Hooks) OnDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
|
||||
h.dh.OnDelegationSharesModified(ctx, delAddr, valAddr)
|
||||
}
|
||||
func (h Hooks) OnDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
|
||||
h.dh.OnDelegationRemoved(ctx, delAddr, valAddr)
|
||||
}
|
||||
|
||||
@ -4,8 +4,10 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
distr "github.com/cosmos/cosmos-sdk/x/distribution"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/libs/db"
|
||||
@ -21,17 +23,19 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error {
|
||||
}
|
||||
|
||||
genesisState := GenesisState{
|
||||
Accounts: genaccs,
|
||||
StakeData: stake.DefaultGenesisState(),
|
||||
Accounts: genaccs,
|
||||
StakeData: stake.DefaultGenesisState(),
|
||||
DistrData: distr.DefaultGenesisState(),
|
||||
SlashingData: slashing.DefaultGenesisState(),
|
||||
}
|
||||
|
||||
stateBytes, err := wire.MarshalJSONIndent(gapp.cdc, genesisState)
|
||||
stateBytes, err := codec.MarshalJSONIndent(gapp.cdc, genesisState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the chain
|
||||
vals := []abci.Validator{}
|
||||
vals := []abci.ValidatorUpdate{}
|
||||
gapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes})
|
||||
gapp.Commit()
|
||||
|
||||
|
||||
35
cmd/gaia/app/benchmarks/txsize_test.go
Normal file
35
cmd/gaia/app/benchmarks/txsize_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
)
|
||||
|
||||
// This will fail half the time with the second output being 173
|
||||
// This is due to secp256k1 signatures not being constant size.
|
||||
// This will be resolved when updating to tendermint v0.24.0
|
||||
// nolint: vet
|
||||
func ExampleTxSendSize() {
|
||||
cdc := app.MakeCodec()
|
||||
priv1 := secp256k1.GenPrivKeySecp256k1([]byte{0})
|
||||
addr1 := sdk.AccAddress(priv1.PubKey().Address())
|
||||
priv2 := secp256k1.GenPrivKeySecp256k1([]byte{1})
|
||||
addr2 := sdk.AccAddress(priv2.PubKey().Address())
|
||||
coins := []sdk.Coin{sdk.NewCoin("denom", sdk.NewInt(10))}
|
||||
msg1 := bank.MsgSend{
|
||||
Inputs: []bank.Input{bank.NewInput(addr1, coins)},
|
||||
Outputs: []bank.Output{bank.NewOutput(addr2, coins)},
|
||||
}
|
||||
sig, _ := priv1.Sign(msg1.GetSignBytes())
|
||||
sigs := []auth.StdSignature{{nil, sig, 0, 0}}
|
||||
tx := auth.NewStdTx([]sdk.Msg{msg1}, auth.NewStdFee(0, coins...), sigs, "")
|
||||
fmt.Println(len(cdc.MustMarshalBinaryBare([]sdk.Msg{msg1})))
|
||||
fmt.Println(len(cdc.MustMarshalBinaryBare(tx)))
|
||||
// output: 80
|
||||
// 167
|
||||
}
|
||||
@ -4,34 +4,52 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/cosmos/cosmos-sdk/server/config"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
distr "github.com/cosmos/cosmos-sdk/x/distribution"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
"github.com/cosmos/cosmos-sdk/x/mint"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// DefaultKeyPass contains the default key password for genesis transactions
|
||||
const DefaultKeyPass = "12345678"
|
||||
|
||||
var (
|
||||
// bonded tokens given to genesis validators/accounts
|
||||
freeFermionVal = int64(100)
|
||||
freeFermionsAcc = int64(50)
|
||||
freeFermionsAcc = sdk.NewInt(150)
|
||||
)
|
||||
|
||||
// State to Unmarshal
|
||||
type GenesisState struct {
|
||||
Accounts []GenesisAccount `json:"accounts"`
|
||||
StakeData stake.GenesisState `json:"stake"`
|
||||
Accounts []GenesisAccount `json:"accounts"`
|
||||
StakeData stake.GenesisState `json:"stake"`
|
||||
MintData mint.GenesisState `json:"mint"`
|
||||
DistrData distr.GenesisState `json:"distr"`
|
||||
GovData gov.GenesisState `json:"gov"`
|
||||
SlashingData slashing.GenesisState `json:"slashing"`
|
||||
GenTxs []json.RawMessage `json:"gentxs"`
|
||||
}
|
||||
|
||||
func NewGenesisState(accounts []GenesisAccount, stakeData stake.GenesisState, mintData mint.GenesisState,
|
||||
distrData distr.GenesisState, govData gov.GenesisState, slashingData slashing.GenesisState) GenesisState {
|
||||
|
||||
return GenesisState{
|
||||
Accounts: accounts,
|
||||
StakeData: stakeData,
|
||||
MintData: mintData,
|
||||
DistrData: distrData,
|
||||
GovData: govData,
|
||||
SlashingData: slashingData,
|
||||
}
|
||||
}
|
||||
|
||||
// GenesisAccount doesn't need pubkey or sequence
|
||||
@ -64,101 +82,15 @@ func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount) {
|
||||
|
||||
// get app init parameters for server init command
|
||||
func GaiaAppInit() server.AppInit {
|
||||
fsAppGenState := pflag.NewFlagSet("", pflag.ContinueOnError)
|
||||
|
||||
fsAppGenTx := pflag.NewFlagSet("", pflag.ContinueOnError)
|
||||
fsAppGenTx.String(server.FlagName, "", "validator moniker, required")
|
||||
fsAppGenTx.String(server.FlagClientHome, DefaultCLIHome,
|
||||
"home directory for the client, used for key generation")
|
||||
fsAppGenTx.Bool(server.FlagOWK, false, "overwrite the accounts created")
|
||||
|
||||
return server.AppInit{
|
||||
FlagsAppGenState: fsAppGenState,
|
||||
FlagsAppGenTx: fsAppGenTx,
|
||||
AppGenTx: GaiaAppGenTx,
|
||||
AppGenState: GaiaAppGenStateJSON,
|
||||
AppGenState: GaiaAppGenStateJSON,
|
||||
}
|
||||
}
|
||||
|
||||
// simple genesis tx
|
||||
type GaiaGenTx struct {
|
||||
Name string `json:"name"`
|
||||
Address sdk.AccAddress `json:"address"`
|
||||
PubKey string `json:"pub_key"`
|
||||
}
|
||||
|
||||
// GaiaAppGenTx generates a Gaia genesis transaction.
|
||||
func GaiaAppGenTx(
|
||||
cdc *wire.Codec, pk crypto.PubKey, genTxConfig config.GenTx,
|
||||
) (appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) {
|
||||
if genTxConfig.Name == "" {
|
||||
return nil, nil, tmtypes.GenesisValidator{}, errors.New("Must specify --name (validator moniker)")
|
||||
}
|
||||
|
||||
buf := client.BufferStdin()
|
||||
prompt := fmt.Sprintf("Password for account '%s' (default %s):", genTxConfig.Name, DefaultKeyPass)
|
||||
|
||||
keyPass, err := client.GetPassword(prompt, buf)
|
||||
if err != nil && keyPass != "" {
|
||||
// An error was returned that either failed to read the password from
|
||||
// STDIN or the given password is not empty but failed to meet minimum
|
||||
// length requirements.
|
||||
return appGenTx, cliPrint, validator, err
|
||||
}
|
||||
|
||||
if keyPass == "" {
|
||||
keyPass = DefaultKeyPass
|
||||
}
|
||||
|
||||
addr, secret, err := server.GenerateSaveCoinKey(
|
||||
genTxConfig.CliRoot,
|
||||
genTxConfig.Name,
|
||||
keyPass,
|
||||
genTxConfig.Overwrite,
|
||||
)
|
||||
if err != nil {
|
||||
return appGenTx, cliPrint, validator, err
|
||||
}
|
||||
|
||||
mm := map[string]string{"secret": secret}
|
||||
bz, err := cdc.MarshalJSON(mm)
|
||||
if err != nil {
|
||||
return appGenTx, cliPrint, validator, err
|
||||
}
|
||||
|
||||
cliPrint = json.RawMessage(bz)
|
||||
appGenTx, _, validator, err = GaiaAppGenTxNF(cdc, pk, addr, genTxConfig.Name)
|
||||
|
||||
return appGenTx, cliPrint, validator, err
|
||||
}
|
||||
|
||||
// Generate a gaia genesis transaction without flags
|
||||
func GaiaAppGenTxNF(cdc *wire.Codec, pk crypto.PubKey, addr sdk.AccAddress, name string) (
|
||||
appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) {
|
||||
|
||||
var bz []byte
|
||||
gaiaGenTx := GaiaGenTx{
|
||||
Name: name,
|
||||
Address: addr,
|
||||
PubKey: sdk.MustBech32ifyAccPub(pk),
|
||||
}
|
||||
bz, err = wire.MarshalJSONIndent(cdc, gaiaGenTx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
appGenTx = json.RawMessage(bz)
|
||||
|
||||
validator = tmtypes.GenesisValidator{
|
||||
PubKey: pk,
|
||||
Power: freeFermionVal,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Create the core parameters for genesis initialization for gaia
|
||||
// note that the pubkey input is this machines pubkey
|
||||
func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState GenesisState, err error) {
|
||||
|
||||
func GaiaAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (genesisState GenesisState, err error) {
|
||||
if len(appGenTxs) == 0 {
|
||||
err = errors.New("must provide at least genesis transaction")
|
||||
return
|
||||
@ -166,68 +98,161 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState
|
||||
|
||||
// start with the default staking genesis state
|
||||
stakeData := stake.DefaultGenesisState()
|
||||
slashingData := slashing.DefaultGenesisState()
|
||||
|
||||
// get genesis flag account information
|
||||
genaccs := make([]GenesisAccount, len(appGenTxs))
|
||||
for i, appGenTx := range appGenTxs {
|
||||
|
||||
var genTx GaiaGenTx
|
||||
err = cdc.UnmarshalJSON(appGenTx, &genTx)
|
||||
for i, genTx := range appGenTxs {
|
||||
var tx auth.StdTx
|
||||
err = cdc.UnmarshalJSON(genTx, &tx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
msgs := tx.GetMsgs()
|
||||
if len(msgs) != 1 {
|
||||
err = errors.New("must provide genesis StdTx with exactly 1 CreateValidator message")
|
||||
return
|
||||
}
|
||||
msg := msgs[0].(stake.MsgCreateValidator)
|
||||
|
||||
// create the genesis account, give'm few steaks and a buncha token with there name
|
||||
accAuth := auth.NewBaseAccountWithAddress(genTx.Address)
|
||||
accAuth.Coins = sdk.Coins{
|
||||
{genTx.Name + "Token", sdk.NewInt(1000)},
|
||||
{"steak", sdk.NewInt(freeFermionsAcc)},
|
||||
}
|
||||
acc := NewGenesisAccount(&accAuth)
|
||||
genaccs[i] = acc
|
||||
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewRat(freeFermionsAcc)) // increase the supply
|
||||
|
||||
// add the validator
|
||||
if len(genTx.Name) > 0 {
|
||||
desc := stake.NewDescription(genTx.Name, "", "", "")
|
||||
validator := stake.NewValidator(genTx.Address,
|
||||
sdk.MustGetAccPubKeyBech32(genTx.PubKey), desc)
|
||||
|
||||
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewRat(freeFermionVal)) // increase the supply
|
||||
|
||||
// add some new shares to the validator
|
||||
var issuedDelShares sdk.Rat
|
||||
validator, stakeData.Pool, issuedDelShares = validator.AddTokensFromDel(stakeData.Pool, freeFermionVal)
|
||||
stakeData.Validators = append(stakeData.Validators, validator)
|
||||
|
||||
// create the self-delegation from the issuedDelShares
|
||||
delegation := stake.Delegation{
|
||||
DelegatorAddr: validator.Owner,
|
||||
ValidatorAddr: validator.Owner,
|
||||
Shares: issuedDelShares,
|
||||
Height: 0,
|
||||
}
|
||||
|
||||
stakeData.Bonds = append(stakeData.Bonds, delegation)
|
||||
}
|
||||
genaccs[i] = genesisAccountFromMsgCreateValidator(msg, freeFermionsAcc)
|
||||
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(freeFermionsAcc)) // increase the supply
|
||||
}
|
||||
|
||||
// create the final app state
|
||||
genesisState = GenesisState{
|
||||
Accounts: genaccs,
|
||||
StakeData: stakeData,
|
||||
Accounts: genaccs,
|
||||
StakeData: stakeData,
|
||||
MintData: mint.DefaultGenesisState(),
|
||||
DistrData: distr.DefaultGenesisState(),
|
||||
GovData: gov.DefaultGenesisState(),
|
||||
SlashingData: slashingData,
|
||||
GenTxs: appGenTxs,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func genesisAccountFromMsgCreateValidator(msg stake.MsgCreateValidator, amount sdk.Int) GenesisAccount {
|
||||
accAuth := auth.NewBaseAccountWithAddress(sdk.AccAddress(msg.ValidatorAddr))
|
||||
accAuth.Coins = []sdk.Coin{
|
||||
{msg.Description.Moniker + "Token", sdk.NewInt(1000)},
|
||||
{"steak", amount},
|
||||
}
|
||||
return NewGenesisAccount(&accAuth)
|
||||
}
|
||||
|
||||
// GaiaValidateGenesisState ensures that the genesis state obeys the expected invariants
|
||||
// TODO: No validators are both bonded and jailed (#2088)
|
||||
// TODO: Error if there is a duplicate validator (#1708)
|
||||
// TODO: Ensure all state machine parameters are in genesis (#1704)
|
||||
func GaiaValidateGenesisState(genesisState GenesisState) (err error) {
|
||||
err = validateGenesisStateAccounts(genesisState.Accounts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// skip stakeData validation as genesis is created from txs
|
||||
if len(genesisState.GenTxs) > 0 {
|
||||
return nil
|
||||
}
|
||||
return stake.ValidateGenesis(genesisState.StakeData)
|
||||
}
|
||||
|
||||
// Ensures that there are no duplicate accounts in the genesis state,
|
||||
func validateGenesisStateAccounts(accs []GenesisAccount) (err 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)
|
||||
}
|
||||
addrMap[strAddr] = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GaiaAppGenState but with JSON
|
||||
func GaiaAppGenStateJSON(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) {
|
||||
|
||||
func GaiaAppGenStateJSON(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) {
|
||||
// create the final app state
|
||||
genesisState, err := GaiaAppGenState(cdc, appGenTxs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appState, err = wire.MarshalJSONIndent(cdc, genesisState)
|
||||
appState, err = codec.MarshalJSONIndent(cdc, genesisState)
|
||||
return
|
||||
}
|
||||
|
||||
// CollectStdTxs processes and validates application's genesis StdTxs and returns the list of validators,
|
||||
// appGenTxs, and persistent peers required to generate genesis.json.
|
||||
func CollectStdTxs(moniker string, genTxsDir string, cdc *codec.Codec) (
|
||||
validators []tmtypes.GenesisValidator, appGenTxs []auth.StdTx, persistentPeers string, err error) {
|
||||
var fos []os.FileInfo
|
||||
fos, err = ioutil.ReadDir(genTxsDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var addresses []string
|
||||
for _, fo := range fos {
|
||||
filename := filepath.Join(genTxsDir, fo.Name())
|
||||
if !fo.IsDir() && (filepath.Ext(filename) != ".json") {
|
||||
continue
|
||||
}
|
||||
|
||||
// get the genStdTx
|
||||
var jsonRawTx []byte
|
||||
jsonRawTx, err = ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var genStdTx auth.StdTx
|
||||
err = cdc.UnmarshalJSON(jsonRawTx, &genStdTx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
appGenTxs = append(appGenTxs, genStdTx)
|
||||
|
||||
nodeAddr := genStdTx.GetMemo()
|
||||
if len(nodeAddr) == 0 {
|
||||
err = fmt.Errorf("couldn't find node's address in %s", fo.Name())
|
||||
return
|
||||
}
|
||||
|
||||
msgs := genStdTx.GetMsgs()
|
||||
if len(msgs) != 1 {
|
||||
err = errors.New("each genesis transaction must provide a single genesis message")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: this could be decoupled from stake.MsgCreateValidator
|
||||
// TODO: and we likely want to do it for real world Gaia
|
||||
msg := msgs[0].(stake.MsgCreateValidator)
|
||||
validators = append(validators, tmtypes.GenesisValidator{
|
||||
PubKey: msg.PubKey,
|
||||
Power: freeFermionVal,
|
||||
Name: msg.Description.Moniker,
|
||||
})
|
||||
|
||||
// exclude itself from persistent peers
|
||||
if msg.Description.Moniker != moniker {
|
||||
addresses = append(addresses, nodeAddr)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(addresses)
|
||||
persistentPeers = strings.Join(addresses, ",")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func NewDefaultGenesisAccount(addr sdk.AccAddress) GenesisAccount {
|
||||
accAuth := auth.NewBaseAccountWithAddress(addr)
|
||||
accAuth.Coins = []sdk.Coin{
|
||||
{"fooToken", sdk.NewInt(1000)},
|
||||
{"steak", freeFermionsAcc},
|
||||
}
|
||||
return NewGenesisAccount(&accAuth)
|
||||
}
|
||||
|
||||
@ -5,10 +5,49 @@ import (
|
||||
|
||||
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/stake"
|
||||
stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
)
|
||||
|
||||
var (
|
||||
pk1 = ed25519.GenPrivKey().PubKey()
|
||||
pk2 = ed25519.GenPrivKey().PubKey()
|
||||
pk3 = ed25519.GenPrivKey().PubKey()
|
||||
addr1 = sdk.ValAddress(pk1.Address())
|
||||
addr2 = sdk.ValAddress(pk2.Address())
|
||||
addr3 = sdk.ValAddress(pk3.Address())
|
||||
|
||||
emptyAddr sdk.ValAddress
|
||||
emptyPubkey crypto.PubKey
|
||||
)
|
||||
|
||||
func makeGenesisState(t *testing.T, genTxs []auth.StdTx) GenesisState {
|
||||
// start with the default staking genesis state
|
||||
stakeData := stake.DefaultGenesisState()
|
||||
genAccs := make([]GenesisAccount, len(genTxs))
|
||||
|
||||
for i, genTx := range genTxs {
|
||||
msgs := genTx.GetMsgs()
|
||||
require.Equal(t, 1, len(msgs))
|
||||
msg := msgs[0].(stake.MsgCreateValidator)
|
||||
|
||||
// get genesis flag account information
|
||||
genAccs[i] = genesisAccountFromMsgCreateValidator(msg, freeFermionsAcc)
|
||||
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(freeFermionsAcc)) // increase the supply
|
||||
}
|
||||
|
||||
// create the final app state
|
||||
return GenesisState{
|
||||
Accounts: genAccs,
|
||||
StakeData: stakeData,
|
||||
GovData: gov.DefaultGenesisState(),
|
||||
}
|
||||
}
|
||||
|
||||
func TestToAccount(t *testing.T) {
|
||||
priv := ed25519.GenPrivKey()
|
||||
addr := sdk.AccAddress(priv.PubKey().Address())
|
||||
@ -34,3 +73,36 @@ func TestGaiaAppGenState(t *testing.T) {
|
||||
// TODO test with both one and two genesis transactions:
|
||||
// TODO correct: genesis account created, canididates created, pool token variance
|
||||
}
|
||||
|
||||
func makeMsg(name string, pk crypto.PubKey) auth.StdTx {
|
||||
desc := stake.NewDescription(name, "", "", "")
|
||||
comm := stakeTypes.CommissionMsg{}
|
||||
msg := stake.NewMsgCreateValidator(sdk.ValAddress(pk.Address()), pk, sdk.NewInt64Coin("steak", 50), desc, comm)
|
||||
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)
|
||||
err := GaiaValidateGenesisState(genesisState)
|
||||
require.NotNil(t, err)
|
||||
// Test bonded + jailed validator fails
|
||||
genesisState = makeGenesisState(t, genTxs)
|
||||
val1 := stakeTypes.NewValidator(addr1, pk1, stakeTypes.Description{Moniker: "test #2"})
|
||||
val1.Jailed = true
|
||||
val1.Status = sdk.Bonded
|
||||
genesisState.StakeData.Validators = append(genesisState.StakeData.Validators, val1)
|
||||
err = GaiaValidateGenesisState(genesisState)
|
||||
require.NotNil(t, err)
|
||||
// Test duplicate validator fails
|
||||
val1.Jailed = false
|
||||
genesisState = makeGenesisState(t, genTxs)
|
||||
val2 := stakeTypes.NewValidator(addr1, pk1, stakeTypes.Description{Moniker: "test #3"})
|
||||
genesisState.StakeData.Validators = append(genesisState.StakeData.Validators, val1)
|
||||
genesisState.StakeData.Validators = append(genesisState.StakeData.Validators, val2)
|
||||
err = GaiaValidateGenesisState(genesisState)
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
@ -5,19 +5,24 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authsim "github.com/cosmos/cosmos-sdk/x/auth/simulation"
|
||||
banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation"
|
||||
distr "github.com/cosmos/cosmos-sdk/x/distribution"
|
||||
distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/mint"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation"
|
||||
stake "github.com/cosmos/cosmos-sdk/x/stake"
|
||||
stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation"
|
||||
@ -29,6 +34,7 @@ var (
|
||||
blockSize int
|
||||
enabled bool
|
||||
verbose bool
|
||||
commit bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -37,43 +43,56 @@ func init() {
|
||||
flag.IntVar(&blockSize, "SimulationBlockSize", 200, "Operations per block")
|
||||
flag.BoolVar(&enabled, "SimulationEnabled", false, "Enable the simulation")
|
||||
flag.BoolVar(&verbose, "SimulationVerbose", false, "Verbose log output")
|
||||
flag.BoolVar(&commit, "SimulationCommit", false, "Have the simulation commit")
|
||||
}
|
||||
|
||||
func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage {
|
||||
func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage {
|
||||
var genesisAccounts []GenesisAccount
|
||||
|
||||
amt := int64(10000)
|
||||
|
||||
// Randomly generate some genesis accounts
|
||||
for _, acc := range accs {
|
||||
coins := sdk.Coins{sdk.Coin{"steak", sdk.NewInt(100)}}
|
||||
coins := sdk.Coins{sdk.Coin{"steak", sdk.NewInt(amt)}}
|
||||
genesisAccounts = append(genesisAccounts, GenesisAccount{
|
||||
Address: acc,
|
||||
Address: acc.Address,
|
||||
Coins: coins,
|
||||
})
|
||||
}
|
||||
|
||||
// Default genesis state
|
||||
govGenesis := gov.DefaultGenesisState()
|
||||
stakeGenesis := stake.DefaultGenesisState()
|
||||
slashingGenesis := slashing.DefaultGenesisState()
|
||||
var validators []stake.Validator
|
||||
var delegations []stake.Delegation
|
||||
|
||||
// XXX Try different numbers of initially bonded validators
|
||||
numInitiallyBonded := int64(50)
|
||||
valAddrs := make([]sdk.ValAddress, numInitiallyBonded)
|
||||
for i := 0; i < int(numInitiallyBonded); i++ {
|
||||
validator := stake.NewValidator(accs[i], keys[i].PubKey(), stake.Description{})
|
||||
validator.Tokens = sdk.NewRat(100)
|
||||
validator.DelegatorShares = sdk.NewRat(100)
|
||||
delegation := stake.Delegation{accs[i], accs[i], sdk.NewRat(100), 0}
|
||||
valAddr := sdk.ValAddress(accs[i].Address)
|
||||
valAddrs[i] = valAddr
|
||||
|
||||
validator := stake.NewValidator(valAddr, accs[i].PubKey, stake.Description{})
|
||||
validator.Tokens = sdk.NewDec(amt)
|
||||
validator.DelegatorShares = sdk.NewDec(amt)
|
||||
delegation := stake.Delegation{accs[i].Address, valAddr, sdk.NewDec(amt), 0}
|
||||
validators = append(validators, validator)
|
||||
delegations = append(delegations, delegation)
|
||||
}
|
||||
stakeGenesis.Pool.LooseTokens = sdk.NewRat(int64(100*250) + (numInitiallyBonded * 100))
|
||||
stakeGenesis.Pool.LooseTokens = sdk.NewDec(amt*250 + (numInitiallyBonded * amt))
|
||||
stakeGenesis.Validators = validators
|
||||
stakeGenesis.Bonds = delegations
|
||||
// No inflation, for now
|
||||
stakeGenesis.Params.InflationMax = sdk.NewRat(0)
|
||||
stakeGenesis.Params.InflationMin = sdk.NewRat(0)
|
||||
mintGenesis := mint.DefaultGenesisState()
|
||||
|
||||
genesis := GenesisState{
|
||||
Accounts: genesisAccounts,
|
||||
StakeData: stakeGenesis,
|
||||
Accounts: genesisAccounts,
|
||||
StakeData: stakeGenesis,
|
||||
MintData: mintGenesis,
|
||||
DistrData: distr.DefaultGenesisWithValidators(valAddrs),
|
||||
SlashingData: slashingGenesis,
|
||||
GovData: govGenesis,
|
||||
}
|
||||
|
||||
// Marshal genesis
|
||||
@ -85,31 +104,70 @@ func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json
|
||||
return appState
|
||||
}
|
||||
|
||||
func testAndRunTxs(app *GaiaApp) []simulation.TestAndRunTx {
|
||||
return []simulation.TestAndRunTx{
|
||||
banksim.TestAndRunSingleInputMsgSend(app.accountMapper),
|
||||
govsim.SimulateMsgSubmitProposal(app.govKeeper, app.stakeKeeper),
|
||||
govsim.SimulateMsgDeposit(app.govKeeper, app.stakeKeeper),
|
||||
govsim.SimulateMsgVote(app.govKeeper, app.stakeKeeper),
|
||||
stakesim.SimulateMsgCreateValidator(app.accountMapper, app.stakeKeeper),
|
||||
stakesim.SimulateMsgEditValidator(app.stakeKeeper),
|
||||
stakesim.SimulateMsgDelegate(app.accountMapper, app.stakeKeeper),
|
||||
stakesim.SimulateMsgBeginUnbonding(app.accountMapper, app.stakeKeeper),
|
||||
stakesim.SimulateMsgCompleteUnbonding(app.stakeKeeper),
|
||||
stakesim.SimulateMsgBeginRedelegate(app.accountMapper, app.stakeKeeper),
|
||||
stakesim.SimulateMsgCompleteRedelegate(app.stakeKeeper),
|
||||
slashingsim.SimulateMsgUnrevoke(app.slashingKeeper),
|
||||
func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation {
|
||||
return []simulation.WeightedOperation{
|
||||
{5, authsim.SimulateDeductFee(app.accountKeeper, app.feeCollectionKeeper)},
|
||||
{100, banksim.SingleInputSendMsg(app.accountKeeper, app.bankKeeper)},
|
||||
{50, distrsim.SimulateMsgSetWithdrawAddress(app.accountKeeper, app.distrKeeper)},
|
||||
{50, distrsim.SimulateMsgWithdrawDelegatorRewardsAll(app.accountKeeper, app.distrKeeper)},
|
||||
{50, distrsim.SimulateMsgWithdrawDelegatorReward(app.accountKeeper, app.distrKeeper)},
|
||||
{50, distrsim.SimulateMsgWithdrawValidatorRewardsAll(app.accountKeeper, app.distrKeeper)},
|
||||
{5, govsim.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, app.stakeKeeper)},
|
||||
{100, govsim.SimulateMsgDeposit(app.govKeeper, app.stakeKeeper)},
|
||||
{100, stakesim.SimulateMsgCreateValidator(app.accountKeeper, app.stakeKeeper)},
|
||||
{5, stakesim.SimulateMsgEditValidator(app.stakeKeeper)},
|
||||
{100, stakesim.SimulateMsgDelegate(app.accountKeeper, app.stakeKeeper)},
|
||||
{100, stakesim.SimulateMsgBeginUnbonding(app.accountKeeper, app.stakeKeeper)},
|
||||
{100, stakesim.SimulateMsgBeginRedelegate(app.accountKeeper, app.stakeKeeper)},
|
||||
{100, slashingsim.SimulateMsgUnjail(app.slashingKeeper)},
|
||||
}
|
||||
}
|
||||
|
||||
func invariants(app *GaiaApp) []simulation.Invariant {
|
||||
return []simulation.Invariant{
|
||||
func(t *testing.T, baseapp *baseapp.BaseApp, log string) {
|
||||
banksim.NonnegativeBalanceInvariant(app.accountMapper)(t, baseapp, log)
|
||||
govsim.AllInvariants()(t, baseapp, log)
|
||||
stakesim.AllInvariants(app.coinKeeper, app.stakeKeeper, app.accountMapper)(t, baseapp, log)
|
||||
slashingsim.AllInvariants()(t, baseapp, log)
|
||||
},
|
||||
banksim.NonnegativeBalanceInvariant(app.accountKeeper),
|
||||
govsim.AllInvariants(),
|
||||
distrsim.AllInvariants(app.distrKeeper, app.stakeKeeper),
|
||||
stakesim.AllInvariants(app.bankKeeper, app.stakeKeeper,
|
||||
app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper),
|
||||
slashingsim.AllInvariants(),
|
||||
}
|
||||
}
|
||||
|
||||
// Profile with:
|
||||
// /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()
|
||||
var db dbm.DB
|
||||
dir := os.TempDir()
|
||||
db, _ = dbm.NewGoLevelDB("Simulation", dir)
|
||||
defer func() {
|
||||
db.Close()
|
||||
os.RemoveAll(dir)
|
||||
}()
|
||||
app := NewGaiaApp(logger, db, nil)
|
||||
|
||||
// Run randomized simulation
|
||||
// TODO parameterize numbers, save for a later PR
|
||||
err := simulation.SimulateFromSeed(
|
||||
b, app.BaseApp, appStateFn, seed,
|
||||
testAndRunTxs(app),
|
||||
[]simulation.RandSetup{},
|
||||
invariants(app), // these shouldn't get ran
|
||||
numBlocks,
|
||||
blockSize,
|
||||
commit,
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
b.Fail()
|
||||
}
|
||||
if commit {
|
||||
fmt.Println("GoLevelDB Stats")
|
||||
fmt.Println(db.Stats()["leveldb.stats"])
|
||||
fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"])
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,16 +188,19 @@ func TestFullGaiaSimulation(t *testing.T) {
|
||||
require.Equal(t, "GaiaApp", app.Name())
|
||||
|
||||
// Run randomized simulation
|
||||
simulation.SimulateFromSeed(
|
||||
err := simulation.SimulateFromSeed(
|
||||
t, app.BaseApp, appStateFn, seed,
|
||||
testAndRunTxs(app),
|
||||
[]simulation.RandSetup{},
|
||||
invariants(app),
|
||||
numBlocks,
|
||||
blockSize,
|
||||
false,
|
||||
commit,
|
||||
)
|
||||
|
||||
if commit {
|
||||
fmt.Println("Database Size", db.Stats()["database.size"])
|
||||
}
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
// TODO: Make another test for the fuzzer itself, which just has noOp txs
|
||||
@ -149,7 +210,7 @@ func TestAppStateDeterminism(t *testing.T) {
|
||||
t.Skip("Skipping Gaia simulation")
|
||||
}
|
||||
|
||||
numSeeds := 5
|
||||
numSeeds := 3
|
||||
numTimesToRunPerSeed := 5
|
||||
appHashList := make([]json.RawMessage, numTimesToRunPerSeed)
|
||||
|
||||
@ -166,16 +227,16 @@ func TestAppStateDeterminism(t *testing.T) {
|
||||
testAndRunTxs(app),
|
||||
[]simulation.RandSetup{},
|
||||
[]simulation.Invariant{},
|
||||
20,
|
||||
20,
|
||||
50,
|
||||
100,
|
||||
true,
|
||||
)
|
||||
//app.Commit()
|
||||
appHash := app.LastCommitID().Hash
|
||||
fmt.Printf(">>> APP HASH: %v, %X\n", appHash, appHash)
|
||||
appHashList[j] = appHash
|
||||
}
|
||||
for k := 1; k < numTimesToRunPerSeed; k++ {
|
||||
require.Equal(t, appHashList[0], appHashList[k])
|
||||
require.Equal(t, appHashList[0], appHashList[k], "appHash list: %v", appHashList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,20 +5,24 @@ package clitest
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/cosmos/cosmos-sdk/tests"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
@ -33,17 +37,74 @@ func init() {
|
||||
gaiadHome, gaiacliHome = getTestingHomeDirs()
|
||||
}
|
||||
|
||||
func TestGaiaCLIMinimumFees(t *testing.T) {
|
||||
chainID, servAddr, port := initializeFixtures(t)
|
||||
flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID)
|
||||
|
||||
// start gaiad server with minimum fees
|
||||
proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v --minimum_fees=2feeToken", gaiadHome, servAddr))
|
||||
|
||||
defer proc.Stop(false)
|
||||
tests.WaitForTMStart(port)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome))
|
||||
barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome))
|
||||
|
||||
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
success := executeWrite(t, fmt.Sprintf(
|
||||
"gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass)
|
||||
require.False(t, success)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
}
|
||||
|
||||
func TestGaiaCLIFeesDeduction(t *testing.T) {
|
||||
chainID, servAddr, port := initializeFixtures(t)
|
||||
flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID)
|
||||
|
||||
// start gaiad server with minimum fees
|
||||
proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v --minimum_fees=1fooToken", gaiadHome, servAddr))
|
||||
|
||||
defer proc.Stop(false)
|
||||
tests.WaitForTMStart(port)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome))
|
||||
barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome))
|
||||
|
||||
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf("fooToken").Int64())
|
||||
|
||||
// test simulation
|
||||
success := executeWrite(t, fmt.Sprintf(
|
||||
"gaiacli tx send %v --amount=1000fooToken --to=%s --from=foo --fee=1fooToken --dry-run", flags, barAddr), app.DefaultKeyPass)
|
||||
require.True(t, success)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
// ensure state didn't change
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf("fooToken").Int64())
|
||||
|
||||
// insufficient funds (coins + fees)
|
||||
success = executeWrite(t, fmt.Sprintf(
|
||||
"gaiacli tx send %v --amount=1000fooToken --to=%s --from=foo --fee=1fooToken", flags, barAddr), app.DefaultKeyPass)
|
||||
require.False(t, success)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
// ensure state didn't change
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf("fooToken").Int64())
|
||||
|
||||
// test success (transfer = coins + fees)
|
||||
success = executeWrite(t, fmt.Sprintf(
|
||||
"gaiacli tx send %v --fee=300fooToken --amount=500fooToken --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass)
|
||||
require.True(t, success)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
}
|
||||
|
||||
func TestGaiaCLISend(t *testing.T) {
|
||||
tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe_reset_all", gaiadHome), "")
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s foo", gaiacliHome), app.DefaultKeyPass)
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), app.DefaultKeyPass)
|
||||
|
||||
chainID := executeInit(t, fmt.Sprintf("gaiad init -o --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome))
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys add --home=%s bar", gaiacliHome), app.DefaultKeyPass)
|
||||
|
||||
// get a free port, also setup some common flags
|
||||
servAddr, port, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
chainID, servAddr, port := initializeFixtures(t)
|
||||
flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID)
|
||||
|
||||
// start gaiad server
|
||||
@ -56,46 +117,96 @@ func TestGaiaCLISend(t *testing.T) {
|
||||
fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome))
|
||||
barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome))
|
||||
|
||||
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
|
||||
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass)
|
||||
executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags))
|
||||
barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags))
|
||||
require.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak").Int64())
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
// Test --dry-run
|
||||
success := executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo --dry-run", flags, barAddr), app.DefaultKeyPass)
|
||||
require.True(t, success)
|
||||
// Check state didn't change
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
// test autosequencing
|
||||
executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass)
|
||||
executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags))
|
||||
barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags))
|
||||
require.Equal(t, int64(20), barAcc.GetCoins().AmountOf("steak").Int64())
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(30), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
// test memo
|
||||
executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo --memo 'testmemo'", flags, barAddr), app.DefaultKeyPass)
|
||||
executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo --memo 'testmemo'", flags, barAddr), app.DefaultKeyPass)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags))
|
||||
barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags))
|
||||
require.Equal(t, int64(30), barAcc.GetCoins().AmountOf("steak").Int64())
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(20), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
}
|
||||
|
||||
func TestGaiaCLICreateValidator(t *testing.T) {
|
||||
tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe_reset_all", gaiadHome), "")
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s foo", gaiacliHome), app.DefaultKeyPass)
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), app.DefaultKeyPass)
|
||||
chainID := executeInit(t, fmt.Sprintf("gaiad init -o --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome))
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys add --home=%s bar", gaiacliHome), app.DefaultKeyPass)
|
||||
func TestGaiaCLIGasAuto(t *testing.T) {
|
||||
chainID, servAddr, port := initializeFixtures(t)
|
||||
flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID)
|
||||
|
||||
// get a free port, also setup some common flags
|
||||
servAddr, port, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
// start gaiad server
|
||||
proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr))
|
||||
|
||||
defer proc.Stop(false)
|
||||
tests.WaitForTMStart(port)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome))
|
||||
barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome))
|
||||
|
||||
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
// Test failure with auto gas disabled and very little gas set by hand
|
||||
success := executeWrite(t, fmt.Sprintf("gaiacli tx send %v --gas=10 --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass)
|
||||
require.False(t, success)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
// Check state didn't change
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
// Test failure with negative gas
|
||||
success = executeWrite(t, fmt.Sprintf("gaiacli tx send %v --gas=-100 --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass)
|
||||
require.False(t, success)
|
||||
|
||||
// Test failure with 0 gas
|
||||
success = executeWrite(t, fmt.Sprintf("gaiacli tx send %v --gas=0 --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass)
|
||||
require.False(t, success)
|
||||
|
||||
// Enable auto gas
|
||||
success, stdout, _ := executeWriteRetStdStreams(t, fmt.Sprintf("gaiacli tx send %v --json --gas=simulate --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass)
|
||||
require.True(t, success)
|
||||
// check that gas wanted == gas used
|
||||
cdc := app.MakeCodec()
|
||||
jsonOutput := struct {
|
||||
Height int64
|
||||
TxHash string
|
||||
Response abci.ResponseDeliverTx
|
||||
}{}
|
||||
require.Nil(t, cdc.UnmarshalJSON([]byte(stdout), &jsonOutput))
|
||||
require.Equal(t, jsonOutput.Response.GasWanted, jsonOutput.Response.GasUsed)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
// Check state has changed accordingly
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
}
|
||||
|
||||
func TestGaiaCLICreateValidator(t *testing.T) {
|
||||
chainID, servAddr, port := initializeFixtures(t)
|
||||
flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID)
|
||||
|
||||
// start gaiad server
|
||||
@ -107,61 +218,82 @@ func TestGaiaCLICreateValidator(t *testing.T) {
|
||||
|
||||
fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome))
|
||||
barAddr, barPubKey := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome))
|
||||
barCeshPubKey := sdk.MustBech32ifyValPub(barPubKey)
|
||||
barCeshPubKey := sdk.MustBech32ifyConsPub(barPubKey)
|
||||
|
||||
executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass)
|
||||
executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags))
|
||||
barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags))
|
||||
require.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak").Int64())
|
||||
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
|
||||
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
defaultParams := stake.DefaultParams()
|
||||
initialPool := stake.InitialPool()
|
||||
initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewDec(100)) // Delegate tx on GaiaAppGenState
|
||||
|
||||
// create validator
|
||||
cvStr := fmt.Sprintf("gaiacli stake create-validator %v", flags)
|
||||
cvStr := fmt.Sprintf("gaiacli tx create-validator %v", flags)
|
||||
cvStr += fmt.Sprintf(" --from=%s", "bar")
|
||||
cvStr += fmt.Sprintf(" --pubkey=%s", barCeshPubKey)
|
||||
cvStr += fmt.Sprintf(" --amount=%v", "2steak")
|
||||
cvStr += fmt.Sprintf(" --moniker=%v", "bar-vally")
|
||||
cvStr += fmt.Sprintf(" --commission-rate=%v", "0.05")
|
||||
cvStr += fmt.Sprintf(" --commission-max-rate=%v", "0.20")
|
||||
cvStr += fmt.Sprintf(" --commission-max-change-rate=%v", "0.10")
|
||||
|
||||
initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewDec(1))
|
||||
|
||||
// Test --generate-only
|
||||
success, stdout, stderr := executeWriteRetStdStreams(t, cvStr+" --generate-only", app.DefaultKeyPass)
|
||||
require.True(t, success)
|
||||
require.True(t, success)
|
||||
require.Empty(t, stderr)
|
||||
msg := unmarshalStdTx(t, stdout)
|
||||
require.NotZero(t, msg.Fee.Gas)
|
||||
require.Equal(t, len(msg.Msgs), 1)
|
||||
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||
|
||||
// Test --dry-run
|
||||
success = executeWrite(t, cvStr+" --dry-run", app.DefaultKeyPass)
|
||||
require.True(t, success)
|
||||
|
||||
executeWrite(t, cvStr, app.DefaultKeyPass)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags))
|
||||
barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags))
|
||||
require.Equal(t, int64(8), barAcc.GetCoins().AmountOf("steak").Int64(), "%v", barAcc)
|
||||
|
||||
validator := executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %s --output=json %v", barAddr, flags))
|
||||
require.Equal(t, validator.Owner, barAddr)
|
||||
require.True(sdk.RatEq(t, sdk.NewRat(2), validator.Tokens))
|
||||
validator := executeGetValidator(t, fmt.Sprintf("gaiacli query validator %s --output=json %v", sdk.ValAddress(barAddr), flags))
|
||||
require.Equal(t, validator.OperatorAddr, sdk.ValAddress(barAddr))
|
||||
require.True(sdk.DecEq(t, sdk.NewDec(2), validator.Tokens))
|
||||
|
||||
// unbond a single share
|
||||
unbondStr := fmt.Sprintf("gaiacli stake unbond begin %v", flags)
|
||||
unbondStr := fmt.Sprintf("gaiacli tx unbond begin %v", flags)
|
||||
unbondStr += fmt.Sprintf(" --from=%s", "bar")
|
||||
unbondStr += fmt.Sprintf(" --address-validator=%s", barAddr)
|
||||
unbondStr += fmt.Sprintf(" --validator=%s", sdk.ValAddress(barAddr))
|
||||
unbondStr += fmt.Sprintf(" --shares-amount=%v", "1")
|
||||
|
||||
success := executeWrite(t, unbondStr, app.DefaultKeyPass)
|
||||
success = executeWrite(t, unbondStr, app.DefaultKeyPass)
|
||||
require.True(t, success)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
/* // this won't be what we expect because we've only started unbonding, haven't completed
|
||||
barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags))
|
||||
barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %v %v", barCech, flags))
|
||||
require.Equal(t, int64(9), barAcc.GetCoins().AmountOf("steak").Int64(), "%v", barAcc)
|
||||
*/
|
||||
validator = executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %s --output=json %v", barAddr, flags))
|
||||
require.Equal(t, "1/1", validator.Tokens.String())
|
||||
validator = executeGetValidator(t, fmt.Sprintf("gaiacli query validator %s --output=json %v", sdk.ValAddress(barAddr), flags))
|
||||
require.Equal(t, "1.0000000000", validator.Tokens.String())
|
||||
|
||||
params := executeGetParams(t, fmt.Sprintf("gaiacli query parameters --output=json %v", flags))
|
||||
require.True(t, defaultParams.Equal(params))
|
||||
|
||||
pool := executeGetPool(t, fmt.Sprintf("gaiacli query pool --output=json %v", flags))
|
||||
require.Equal(t, initialPool.BondedTokens, pool.BondedTokens)
|
||||
}
|
||||
|
||||
func TestGaiaCLISubmitProposal(t *testing.T) {
|
||||
tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe_reset_all", gaiadHome), "")
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s foo", gaiacliHome), app.DefaultKeyPass)
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), app.DefaultKeyPass)
|
||||
chainID := executeInit(t, fmt.Sprintf("gaiad init -o --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome))
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys add --home=%s bar", gaiacliHome), app.DefaultKeyPass)
|
||||
|
||||
// get a free port, also setup some common flags
|
||||
servAddr, port, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
chainID, servAddr, port := initializeFixtures(t)
|
||||
flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID)
|
||||
|
||||
// start gaiad server
|
||||
@ -173,72 +305,122 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
||||
|
||||
fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome))
|
||||
|
||||
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
|
||||
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
proposalsQuery := tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals %v", flags), "")
|
||||
proposalsQuery, _ := tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals %v", flags), "")
|
||||
require.Equal(t, "No matching proposals found", proposalsQuery)
|
||||
|
||||
// submit a test proposal
|
||||
spStr := fmt.Sprintf("gaiacli gov submit-proposal %v", flags)
|
||||
spStr := fmt.Sprintf("gaiacli tx submit-proposal %v", flags)
|
||||
spStr += fmt.Sprintf(" --from=%s", "foo")
|
||||
spStr += fmt.Sprintf(" --deposit=%s", "5steak")
|
||||
spStr += fmt.Sprintf(" --type=%s", "Text")
|
||||
spStr += fmt.Sprintf(" --title=%s", "Test")
|
||||
spStr += fmt.Sprintf(" --description=%s", "test")
|
||||
|
||||
// Test generate only
|
||||
success, stdout, stderr := executeWriteRetStdStreams(t, spStr+" --generate-only", app.DefaultKeyPass)
|
||||
require.True(t, success)
|
||||
require.True(t, success)
|
||||
require.Empty(t, stderr)
|
||||
msg := unmarshalStdTx(t, stdout)
|
||||
require.NotZero(t, msg.Fee.Gas)
|
||||
require.Equal(t, len(msg.Msgs), 1)
|
||||
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||
|
||||
// Test --dry-run
|
||||
success = executeWrite(t, spStr+" --dry-run", app.DefaultKeyPass)
|
||||
require.True(t, success)
|
||||
|
||||
executeWrite(t, spStr, app.DefaultKeyPass)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(45), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
proposal1 := executeGetProposal(t, fmt.Sprintf("gaiacli gov query-proposal --proposal-id=1 --output=json %v", flags))
|
||||
proposal1 := executeGetProposal(t, fmt.Sprintf("gaiacli query proposal --proposal-id=1 --output=json %v", flags))
|
||||
require.Equal(t, int64(1), proposal1.GetProposalID())
|
||||
require.Equal(t, gov.StatusDepositPeriod, proposal1.GetStatus())
|
||||
|
||||
proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals %v", flags), "")
|
||||
proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals %v", flags), "")
|
||||
require.Equal(t, " 1 - Test", proposalsQuery)
|
||||
|
||||
depositStr := fmt.Sprintf("gaiacli gov deposit %v", flags)
|
||||
deposit := executeGetDeposit(t,
|
||||
fmt.Sprintf("gaiacli query deposit --proposal-id=1 --depositer=%s --output=json %v",
|
||||
fooAddr, flags))
|
||||
require.Equal(t, int64(5), deposit.Amount.AmountOf("steak").Int64())
|
||||
|
||||
depositStr := fmt.Sprintf("gaiacli tx deposit %v", flags)
|
||||
depositStr += fmt.Sprintf(" --from=%s", "foo")
|
||||
depositStr += fmt.Sprintf(" --deposit=%s", "10steak")
|
||||
depositStr += fmt.Sprintf(" --proposal-id=%s", "1")
|
||||
|
||||
// Test generate only
|
||||
success, stdout, stderr = executeWriteRetStdStreams(t, depositStr+" --generate-only", app.DefaultKeyPass)
|
||||
require.True(t, success)
|
||||
require.True(t, success)
|
||||
require.Empty(t, stderr)
|
||||
msg = unmarshalStdTx(t, stdout)
|
||||
require.NotZero(t, msg.Fee.Gas)
|
||||
require.Equal(t, len(msg.Msgs), 1)
|
||||
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||
|
||||
executeWrite(t, depositStr, app.DefaultKeyPass)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
|
||||
// test query deposit
|
||||
deposits := executeGetDeposits(t,
|
||||
fmt.Sprintf("gaiacli query deposits --proposal-id=1 --output=json %v", flags))
|
||||
require.Len(t, deposits, 1)
|
||||
require.Equal(t, int64(15), deposits[0].Amount.AmountOf("steak").Int64())
|
||||
|
||||
deposit = executeGetDeposit(t,
|
||||
fmt.Sprintf("gaiacli query deposit --proposal-id=1 --depositer=%s --output=json %v",
|
||||
fooAddr, flags))
|
||||
require.Equal(t, int64(15), deposit.Amount.AmountOf("steak").Int64())
|
||||
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(35), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
proposal1 = executeGetProposal(t, fmt.Sprintf("gaiacli gov query-proposal --proposal-id=1 --output=json %v", flags))
|
||||
proposal1 = executeGetProposal(t, fmt.Sprintf("gaiacli query proposal --proposal-id=1 --output=json %v", flags))
|
||||
require.Equal(t, int64(1), proposal1.GetProposalID())
|
||||
require.Equal(t, gov.StatusVotingPeriod, proposal1.GetStatus())
|
||||
|
||||
voteStr := fmt.Sprintf("gaiacli gov vote %v", flags)
|
||||
voteStr := fmt.Sprintf("gaiacli tx vote %v", flags)
|
||||
voteStr += fmt.Sprintf(" --from=%s", "foo")
|
||||
voteStr += fmt.Sprintf(" --proposal-id=%s", "1")
|
||||
voteStr += fmt.Sprintf(" --option=%s", "Yes")
|
||||
|
||||
// Test generate only
|
||||
success, stdout, stderr = executeWriteRetStdStreams(t, voteStr+" --generate-only", app.DefaultKeyPass)
|
||||
require.True(t, success)
|
||||
require.True(t, success)
|
||||
require.Empty(t, stderr)
|
||||
msg = unmarshalStdTx(t, stdout)
|
||||
require.NotZero(t, msg.Fee.Gas)
|
||||
require.Equal(t, len(msg.Msgs), 1)
|
||||
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||
|
||||
executeWrite(t, voteStr, app.DefaultKeyPass)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
vote := executeGetVote(t, fmt.Sprintf("gaiacli gov query-vote --proposal-id=1 --voter=%s --output=json %v", fooAddr, flags))
|
||||
vote := executeGetVote(t, fmt.Sprintf("gaiacli query vote --proposal-id=1 --voter=%s --output=json %v", fooAddr, flags))
|
||||
require.Equal(t, int64(1), vote.ProposalID)
|
||||
require.Equal(t, gov.OptionYes, vote.Option)
|
||||
|
||||
votes := executeGetVotes(t, fmt.Sprintf("gaiacli gov query-votes --proposal-id=1 --output=json %v", flags))
|
||||
votes := executeGetVotes(t, fmt.Sprintf("gaiacli query votes --proposal-id=1 --output=json %v", flags))
|
||||
require.Len(t, votes, 1)
|
||||
require.Equal(t, int64(1), votes[0].ProposalID)
|
||||
require.Equal(t, gov.OptionYes, votes[0].Option)
|
||||
|
||||
proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals --status=DepositPeriod %v", flags), "")
|
||||
proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --status=DepositPeriod %v", flags), "")
|
||||
require.Equal(t, "No matching proposals found", proposalsQuery)
|
||||
|
||||
proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals --status=VotingPeriod %v", flags), "")
|
||||
proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --status=VotingPeriod %v", flags), "")
|
||||
require.Equal(t, " 1 - Test", proposalsQuery)
|
||||
|
||||
// submit a second test proposal
|
||||
spStr = fmt.Sprintf("gaiacli gov submit-proposal %v", flags)
|
||||
spStr = fmt.Sprintf("gaiacli tx submit-proposal %v", flags)
|
||||
spStr += fmt.Sprintf(" --from=%s", "foo")
|
||||
spStr += fmt.Sprintf(" --deposit=%s", "5steak")
|
||||
spStr += fmt.Sprintf(" --type=%s", "Text")
|
||||
@ -248,10 +430,147 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
||||
executeWrite(t, spStr, app.DefaultKeyPass)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals --latest=1 %v", flags), "")
|
||||
proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --latest=1 %v", flags), "")
|
||||
require.Equal(t, " 2 - Apples", proposalsQuery)
|
||||
}
|
||||
|
||||
func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
||||
chainID, servAddr, port := initializeFixtures(t)
|
||||
flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID)
|
||||
|
||||
// start gaiad server
|
||||
proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr))
|
||||
|
||||
defer proc.Stop(false)
|
||||
tests.WaitForTMStart(port)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome))
|
||||
barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome))
|
||||
|
||||
// Test generate sendTx with default gas
|
||||
success, stdout, stderr := executeWriteRetStdStreams(t, fmt.Sprintf(
|
||||
"gaiacli tx send %v --amount=10steak --to=%s --from=foo --generate-only",
|
||||
flags, barAddr), []string{}...)
|
||||
require.True(t, success)
|
||||
require.Empty(t, stderr)
|
||||
msg := unmarshalStdTx(t, stdout)
|
||||
require.Equal(t, msg.Fee.Gas, int64(client.DefaultGasLimit))
|
||||
require.Equal(t, len(msg.Msgs), 1)
|
||||
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||
|
||||
// Test generate sendTx with --gas=$amount
|
||||
success, stdout, stderr = executeWriteRetStdStreams(t, fmt.Sprintf(
|
||||
"gaiacli tx send %v --amount=10steak --to=%s --from=foo --gas=100 --generate-only",
|
||||
flags, barAddr), []string{}...)
|
||||
require.True(t, success)
|
||||
require.Empty(t, stderr)
|
||||
msg = unmarshalStdTx(t, stdout)
|
||||
require.Equal(t, msg.Fee.Gas, int64(100))
|
||||
require.Equal(t, len(msg.Msgs), 1)
|
||||
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||
|
||||
// Test generate sendTx, estimate gas
|
||||
success, stdout, stderr = executeWriteRetStdStreams(t, fmt.Sprintf(
|
||||
"gaiacli tx send %v --amount=10steak --to=%s --from=foo --gas=simulate --generate-only",
|
||||
flags, barAddr), []string{}...)
|
||||
require.True(t, success)
|
||||
require.NotEmpty(t, stderr)
|
||||
msg = unmarshalStdTx(t, stdout)
|
||||
require.True(t, msg.Fee.Gas > 0)
|
||||
require.Equal(t, len(msg.Msgs), 1)
|
||||
|
||||
// Write the output to disk
|
||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
||||
defer os.Remove(unsignedTxFile.Name())
|
||||
|
||||
// Test sign --print-sigs
|
||||
success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf(
|
||||
"gaiacli tx sign %v --print-sigs %v", flags, unsignedTxFile.Name()))
|
||||
require.True(t, success)
|
||||
require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n", fooAddr.String()), stdout)
|
||||
|
||||
// Test sign
|
||||
success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf(
|
||||
"gaiacli tx sign %v --name=foo %v", flags, unsignedTxFile.Name()), app.DefaultKeyPass)
|
||||
require.True(t, success)
|
||||
msg = unmarshalStdTx(t, stdout)
|
||||
require.Equal(t, len(msg.Msgs), 1)
|
||||
require.Equal(t, 1, len(msg.GetSignatures()))
|
||||
require.Equal(t, fooAddr.String(), msg.GetSigners()[0].String())
|
||||
|
||||
// Write the output to disk
|
||||
signedTxFile := writeToNewTempFile(t, stdout)
|
||||
defer os.Remove(signedTxFile.Name())
|
||||
|
||||
// Test sign --print-signatures
|
||||
success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf(
|
||||
"gaiacli tx sign %v --print-sigs %v", flags, signedTxFile.Name()))
|
||||
require.True(t, success)
|
||||
require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n 0: %v\n", fooAddr.String(), fooAddr.String()), stdout)
|
||||
|
||||
// Test broadcast
|
||||
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf("gaiacli tx broadcast %v --json %v", flags, signedTxFile.Name()))
|
||||
require.True(t, success)
|
||||
var result struct {
|
||||
Response abci.ResponseDeliverTx
|
||||
}
|
||||
require.Nil(t, app.MakeCodec().UnmarshalJSON([]byte(stdout), &result))
|
||||
require.Equal(t, msg.Fee.Gas, result.Response.GasUsed)
|
||||
require.Equal(t, msg.Fee.Gas, result.Response.GasWanted)
|
||||
tests.WaitForNextNBlocksTM(2, port)
|
||||
|
||||
barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags))
|
||||
require.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak").Int64())
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
}
|
||||
|
||||
func TestGaiaCLIConfig(t *testing.T) {
|
||||
require.NoError(t, os.RemoveAll(gaiacliHome))
|
||||
require.NoError(t, os.RemoveAll(gaiadHome))
|
||||
servAddr, port, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
node := fmt.Sprintf("%s:%s", servAddr, port)
|
||||
chainID := executeInit(t, fmt.Sprintf("gaiad init -o --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome))
|
||||
executeWrite(t, fmt.Sprintf("gaiacli --home=%s config", gaiadHome), gaiacliHome, node, "y")
|
||||
config, err := ioutil.ReadFile(path.Join(gaiacliHome, "config", "config.toml"))
|
||||
require.NoError(t, err)
|
||||
expectedConfig := fmt.Sprintf(`chain_id = "%s"
|
||||
encoding = "btc"
|
||||
home = "%s"
|
||||
node = "%s"
|
||||
output = "text"
|
||||
trace = false
|
||||
trust_node = true
|
||||
`, chainID, gaiacliHome, node)
|
||||
require.Equal(t, expectedConfig, string(config))
|
||||
// ensure a backup gets created
|
||||
executeWrite(t, "gaiacli config", gaiacliHome, node, "y", "y")
|
||||
configBackup, err := ioutil.ReadFile(path.Join(gaiacliHome, "config", "config.toml-old"))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedConfig, string(configBackup))
|
||||
|
||||
require.NoError(t, os.RemoveAll(gaiadHome))
|
||||
executeWrite(t, "gaiacli config", gaiacliHome, node, "y")
|
||||
|
||||
// ensure it works without an initialized gaiad state
|
||||
expectedConfig = fmt.Sprintf(`chain_id = ""
|
||||
encoding = "btc"
|
||||
home = "%s"
|
||||
node = "%s"
|
||||
output = "text"
|
||||
trace = false
|
||||
trust_node = true
|
||||
`, gaiacliHome, node)
|
||||
config, err = ioutil.ReadFile(path.Join(gaiacliHome, "config", "config.toml"))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedConfig, string(config))
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
// helper methods
|
||||
|
||||
@ -262,10 +581,43 @@ func getTestingHomeDirs() (string, string) {
|
||||
return gaiadHome, gaiacliHome
|
||||
}
|
||||
|
||||
func initializeFixtures(t *testing.T) (chainID, servAddr, port string) {
|
||||
tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe-reset-all", gaiadHome), "")
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s foo", gaiacliHome), app.DefaultKeyPass)
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), app.DefaultKeyPass)
|
||||
|
||||
chainID = executeInit(t, fmt.Sprintf("gaiad init -o --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome))
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys add --home=%s bar", gaiacliHome), app.DefaultKeyPass)
|
||||
|
||||
// get a free port, also setup some common flags
|
||||
servAddr, port, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
func unmarshalStdTx(t *testing.T, s string) (stdTx auth.StdTx) {
|
||||
cdc := app.MakeCodec()
|
||||
require.Nil(t, cdc.UnmarshalJSON([]byte(s), &stdTx))
|
||||
return
|
||||
}
|
||||
|
||||
func writeToNewTempFile(t *testing.T, s string) *os.File {
|
||||
fp, err := ioutil.TempFile(os.TempDir(), "cosmos_cli_test_")
|
||||
require.Nil(t, err)
|
||||
_, err = fp.WriteString(s)
|
||||
require.Nil(t, err)
|
||||
return fp
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
// executors
|
||||
|
||||
func executeWrite(t *testing.T, cmdStr string, writes ...string) bool {
|
||||
func executeWrite(t *testing.T, cmdStr string, writes ...string) (exitSuccess bool) {
|
||||
exitSuccess, _, _ = executeWriteRetStdStreams(t, cmdStr, writes...)
|
||||
return
|
||||
}
|
||||
|
||||
func executeWriteRetStdStreams(t *testing.T, cmdStr string, writes ...string) (bool, string, string) {
|
||||
proc := tests.GoExecuteT(t, cmdStr)
|
||||
|
||||
for _, write := range writes {
|
||||
@ -285,16 +637,14 @@ func executeWrite(t *testing.T, cmdStr string, writes ...string) bool {
|
||||
}
|
||||
|
||||
proc.Wait()
|
||||
return proc.ExitState.Success()
|
||||
// bz := proc.StdoutBuffer.Bytes()
|
||||
// fmt.Println("EXEC WRITE", string(bz))
|
||||
return proc.ExitState.Success(), string(stdout), string(stderr)
|
||||
}
|
||||
|
||||
func executeInit(t *testing.T, cmdStr string) (chainID string) {
|
||||
out := tests.ExecuteT(t, cmdStr, app.DefaultKeyPass)
|
||||
_, stderr := tests.ExecuteT(t, cmdStr, app.DefaultKeyPass)
|
||||
|
||||
var initRes map[string]json.RawMessage
|
||||
err := json.Unmarshal([]byte(out), &initRes)
|
||||
err := json.Unmarshal([]byte(stderr), &initRes)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = json.Unmarshal(initRes["chain_id"], &chainID)
|
||||
@ -304,32 +654,38 @@ func executeInit(t *testing.T, cmdStr string) (chainID string) {
|
||||
}
|
||||
|
||||
func executeGetAddrPK(t *testing.T, cmdStr string) (sdk.AccAddress, crypto.PubKey) {
|
||||
out := tests.ExecuteT(t, cmdStr, "")
|
||||
out, _ := tests.ExecuteT(t, cmdStr, "")
|
||||
var ko keys.KeyOutput
|
||||
keys.UnmarshalJSON([]byte(out), &ko)
|
||||
|
||||
pk, err := sdk.GetAccPubKeyBech32(ko.PubKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
return ko.Address, pk
|
||||
accAddr, err := sdk.AccAddressFromBech32(ko.Address)
|
||||
require.NoError(t, err)
|
||||
|
||||
return accAddr, pk
|
||||
}
|
||||
|
||||
func executeGetAccount(t *testing.T, cmdStr string) auth.BaseAccount {
|
||||
out := tests.ExecuteT(t, cmdStr, "")
|
||||
out, _ := tests.ExecuteT(t, cmdStr, "")
|
||||
var initRes map[string]json.RawMessage
|
||||
err := json.Unmarshal([]byte(out), &initRes)
|
||||
require.NoError(t, err, "out %v, err %v", out, err)
|
||||
value := initRes["value"]
|
||||
var acc auth.BaseAccount
|
||||
cdc := wire.NewCodec()
|
||||
wire.RegisterCrypto(cdc)
|
||||
cdc := codec.New()
|
||||
codec.RegisterCrypto(cdc)
|
||||
err = cdc.UnmarshalJSON(value, &acc)
|
||||
require.NoError(t, err, "value %v, err %v", string(value), err)
|
||||
return acc
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
// stake
|
||||
|
||||
func executeGetValidator(t *testing.T, cmdStr string) stake.Validator {
|
||||
out := tests.ExecuteT(t, cmdStr, "")
|
||||
out, _ := tests.ExecuteT(t, cmdStr, "")
|
||||
var validator stake.Validator
|
||||
cdc := app.MakeCodec()
|
||||
err := cdc.UnmarshalJSON([]byte(out), &validator)
|
||||
@ -337,8 +693,29 @@ func executeGetValidator(t *testing.T, cmdStr string) stake.Validator {
|
||||
return validator
|
||||
}
|
||||
|
||||
func executeGetPool(t *testing.T, cmdStr string) stake.Pool {
|
||||
out, _ := tests.ExecuteT(t, cmdStr, "")
|
||||
var pool stake.Pool
|
||||
cdc := app.MakeCodec()
|
||||
err := cdc.UnmarshalJSON([]byte(out), &pool)
|
||||
require.NoError(t, err, "out %v\n, err %v", out, err)
|
||||
return pool
|
||||
}
|
||||
|
||||
func executeGetParams(t *testing.T, cmdStr string) stake.Params {
|
||||
out, _ := tests.ExecuteT(t, cmdStr, "")
|
||||
var params stake.Params
|
||||
cdc := app.MakeCodec()
|
||||
err := cdc.UnmarshalJSON([]byte(out), ¶ms)
|
||||
require.NoError(t, err, "out %v\n, err %v", out, err)
|
||||
return params
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
// gov
|
||||
|
||||
func executeGetProposal(t *testing.T, cmdStr string) gov.Proposal {
|
||||
out := tests.ExecuteT(t, cmdStr, "")
|
||||
out, _ := tests.ExecuteT(t, cmdStr, "")
|
||||
var proposal gov.Proposal
|
||||
cdc := app.MakeCodec()
|
||||
err := cdc.UnmarshalJSON([]byte(out), &proposal)
|
||||
@ -347,7 +724,7 @@ func executeGetProposal(t *testing.T, cmdStr string) gov.Proposal {
|
||||
}
|
||||
|
||||
func executeGetVote(t *testing.T, cmdStr string) gov.Vote {
|
||||
out := tests.ExecuteT(t, cmdStr, "")
|
||||
out, _ := tests.ExecuteT(t, cmdStr, "")
|
||||
var vote gov.Vote
|
||||
cdc := app.MakeCodec()
|
||||
err := cdc.UnmarshalJSON([]byte(out), &vote)
|
||||
@ -356,10 +733,28 @@ func executeGetVote(t *testing.T, cmdStr string) gov.Vote {
|
||||
}
|
||||
|
||||
func executeGetVotes(t *testing.T, cmdStr string) []gov.Vote {
|
||||
out := tests.ExecuteT(t, cmdStr, "")
|
||||
out, _ := tests.ExecuteT(t, cmdStr, "")
|
||||
var votes []gov.Vote
|
||||
cdc := app.MakeCodec()
|
||||
err := cdc.UnmarshalJSON([]byte(out), &votes)
|
||||
require.NoError(t, err, "out %v\n, err %v", out, err)
|
||||
return votes
|
||||
}
|
||||
|
||||
func executeGetDeposit(t *testing.T, cmdStr string) gov.Deposit {
|
||||
out, _ := tests.ExecuteT(t, cmdStr, "")
|
||||
var deposit gov.Deposit
|
||||
cdc := app.MakeCodec()
|
||||
err := cdc.UnmarshalJSON([]byte(out), &deposit)
|
||||
require.NoError(t, err, "out %v\n, err %v", out, err)
|
||||
return deposit
|
||||
}
|
||||
|
||||
func executeGetDeposits(t *testing.T, cmdStr string) []gov.Deposit {
|
||||
out, _ := tests.ExecuteT(t, cmdStr, "")
|
||||
var deposits []gov.Deposit
|
||||
cdc := app.MakeCodec()
|
||||
err := cdc.UnmarshalJSON([]byte(out), &deposits)
|
||||
require.NoError(t, err, "out %v\n, err %v", out, err)
|
||||
return deposits
|
||||
}
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
|
||||
@ -10,15 +14,24 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/client/lcd"
|
||||
"github.com/cosmos/cosmos-sdk/client/rpc"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
|
||||
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
||||
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
|
||||
distrcmd "github.com/cosmos/cosmos-sdk/x/distribution/client/cli"
|
||||
govcmd "github.com/cosmos/cosmos-sdk/x/gov/client/cli"
|
||||
ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/client/cli"
|
||||
slashingcmd "github.com/cosmos/cosmos-sdk/x/slashing/client/cli"
|
||||
stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
_ "github.com/cosmos/cosmos-sdk/client/lcd/statik"
|
||||
)
|
||||
|
||||
const (
|
||||
storeAcc = "acc"
|
||||
storeGov = "gov"
|
||||
storeSlashing = "slashing"
|
||||
storeStake = "stake"
|
||||
)
|
||||
|
||||
// rootCmd is the entry point for this binary
|
||||
@ -36,109 +49,80 @@ func main() {
|
||||
// TODO: setup keybase, viper object, etc. to be passed into
|
||||
// the below functions and eliminate global vars, like we do
|
||||
// with the cdc
|
||||
rootCmd.AddCommand(client.ConfigCmd())
|
||||
|
||||
// add standard rpc commands
|
||||
rpc.AddCommands(rootCmd)
|
||||
|
||||
//Add state commands
|
||||
tendermintCmd := &cobra.Command{
|
||||
Use: "tendermint",
|
||||
Short: "Tendermint state querying subcommands",
|
||||
//Add query commands
|
||||
queryCmd := &cobra.Command{
|
||||
Use: "query",
|
||||
Aliases: []string{"q"},
|
||||
Short: "Querying subcommands",
|
||||
}
|
||||
tendermintCmd.AddCommand(
|
||||
queryCmd.AddCommand(
|
||||
rpc.BlockCommand(),
|
||||
rpc.ValidatorCommand(),
|
||||
)
|
||||
tx.AddCommands(tendermintCmd, cdc)
|
||||
tx.AddCommands(queryCmd, cdc)
|
||||
queryCmd.AddCommand(client.LineBreak)
|
||||
queryCmd.AddCommand(client.GetCommands(
|
||||
authcmd.GetAccountCmd(storeAcc, cdc, authcmd.GetAccountDecoder(cdc)),
|
||||
stakecmd.GetCmdQueryDelegation(storeStake, cdc),
|
||||
stakecmd.GetCmdQueryDelegations(storeStake, cdc),
|
||||
stakecmd.GetCmdQueryParams(storeStake, cdc),
|
||||
stakecmd.GetCmdQueryPool(storeStake, cdc),
|
||||
govcmd.GetCmdQueryProposal(storeGov, cdc),
|
||||
govcmd.GetCmdQueryProposals(storeGov, cdc),
|
||||
govcmd.GetCmdQueryDeposit(storeGov, cdc),
|
||||
govcmd.GetCmdQueryDeposits(storeGov, cdc),
|
||||
stakecmd.GetCmdQueryRedelegation(storeStake, cdc),
|
||||
stakecmd.GetCmdQueryRedelegations(storeStake, cdc),
|
||||
slashingcmd.GetCmdQuerySigningInfo(storeSlashing, cdc),
|
||||
stakecmd.GetCmdQueryUnbondingDelegation(storeStake, cdc),
|
||||
stakecmd.GetCmdQueryUnbondingDelegations(storeStake, cdc),
|
||||
stakecmd.GetCmdQueryValidator(storeStake, cdc),
|
||||
stakecmd.GetCmdQueryValidators(storeStake, cdc),
|
||||
govcmd.GetCmdQueryVote(storeGov, cdc),
|
||||
govcmd.GetCmdQueryVotes(storeGov, cdc),
|
||||
)...)
|
||||
|
||||
//Add IBC commands
|
||||
ibcCmd := &cobra.Command{
|
||||
Use: "ibc",
|
||||
Short: "Inter-Blockchain Communication subcommands",
|
||||
//Add query commands
|
||||
txCmd := &cobra.Command{
|
||||
Use: "tx",
|
||||
Short: "Transactions subcommands",
|
||||
}
|
||||
ibcCmd.AddCommand(
|
||||
|
||||
//Add auth and bank commands
|
||||
txCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
ibccmd.IBCTransferCmd(cdc),
|
||||
ibccmd.IBCRelayCmd(cdc),
|
||||
bankcmd.GetBroadcastCommand(cdc),
|
||||
authcmd.GetSignCommand(cdc, authcmd.GetAccountDecoder(cdc)),
|
||||
)...)
|
||||
txCmd.AddCommand(client.LineBreak)
|
||||
|
||||
advancedCmd := &cobra.Command{
|
||||
Use: "advanced",
|
||||
Short: "Advanced subcommands",
|
||||
}
|
||||
|
||||
advancedCmd.AddCommand(
|
||||
tendermintCmd,
|
||||
ibcCmd,
|
||||
lcd.ServeCommand(cdc),
|
||||
)
|
||||
rootCmd.AddCommand(
|
||||
advancedCmd,
|
||||
client.LineBreak,
|
||||
)
|
||||
|
||||
//Add stake commands
|
||||
stakeCmd := &cobra.Command{
|
||||
Use: "stake",
|
||||
Short: "Stake and validation subcommands",
|
||||
}
|
||||
stakeCmd.AddCommand(
|
||||
client.GetCommands(
|
||||
stakecmd.GetCmdQueryValidator("stake", cdc),
|
||||
stakecmd.GetCmdQueryValidators("stake", cdc),
|
||||
stakecmd.GetCmdQueryDelegation("stake", cdc),
|
||||
stakecmd.GetCmdQueryDelegations("stake", cdc),
|
||||
stakecmd.GetCmdQueryUnbondingDelegation("stake", cdc),
|
||||
stakecmd.GetCmdQueryUnbondingDelegations("stake", cdc),
|
||||
stakecmd.GetCmdQueryRedelegation("stake", cdc),
|
||||
stakecmd.GetCmdQueryRedelegations("stake", cdc),
|
||||
slashingcmd.GetCmdQuerySigningInfo("slashing", cdc),
|
||||
)...)
|
||||
stakeCmd.AddCommand(
|
||||
txCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
stakecmd.GetCmdCreateValidator(cdc),
|
||||
stakecmd.GetCmdEditValidator(cdc),
|
||||
stakecmd.GetCmdDelegate(cdc),
|
||||
stakecmd.GetCmdUnbond("stake", cdc),
|
||||
stakecmd.GetCmdRedelegate("stake", cdc),
|
||||
slashingcmd.GetCmdUnrevoke(cdc),
|
||||
)...)
|
||||
rootCmd.AddCommand(
|
||||
stakeCmd,
|
||||
)
|
||||
|
||||
//Add stake commands
|
||||
govCmd := &cobra.Command{
|
||||
Use: "gov",
|
||||
Short: "Governance and voting subcommands",
|
||||
}
|
||||
govCmd.AddCommand(
|
||||
client.GetCommands(
|
||||
govcmd.GetCmdQueryProposal("gov", cdc),
|
||||
govcmd.GetCmdQueryVote("gov", cdc),
|
||||
govcmd.GetCmdQueryVotes("gov", cdc),
|
||||
govcmd.GetCmdQueryProposals("gov", cdc),
|
||||
)...)
|
||||
govCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
govcmd.GetCmdSubmitProposal(cdc),
|
||||
stakecmd.GetCmdRedelegate(storeStake, cdc),
|
||||
stakecmd.GetCmdUnbond(storeStake, cdc),
|
||||
distrcmd.GetCmdWithdrawRewards(cdc),
|
||||
distrcmd.GetCmdSetWithdrawAddr(cdc),
|
||||
govcmd.GetCmdDeposit(cdc),
|
||||
bankcmd.SendTxCmd(cdc),
|
||||
govcmd.GetCmdSubmitProposal(cdc),
|
||||
slashingcmd.GetCmdUnjail(cdc),
|
||||
govcmd.GetCmdVote(cdc),
|
||||
)...)
|
||||
rootCmd.AddCommand(
|
||||
govCmd,
|
||||
queryCmd,
|
||||
txCmd,
|
||||
lcd.ServeCommand(cdc),
|
||||
client.LineBreak,
|
||||
)
|
||||
|
||||
//Add auth and bank commands
|
||||
rootCmd.AddCommand(
|
||||
client.GetCommands(
|
||||
authcmd.GetAccountCmd("acc", cdc, authcmd.GetAccountDecoder(cdc)),
|
||||
)...)
|
||||
rootCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
bankcmd.SendTxCmd(cdc),
|
||||
)...)
|
||||
|
||||
// add proxy, version and key info
|
||||
rootCmd.AddCommand(
|
||||
keys.Commands(),
|
||||
@ -148,9 +132,35 @@ func main() {
|
||||
|
||||
// prepare and add flags
|
||||
executor := cli.PrepareMainCmd(rootCmd, "GA", app.DefaultCLIHome)
|
||||
err := executor.Execute()
|
||||
err := initConfig(rootCmd)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = executor.Execute()
|
||||
if err != nil {
|
||||
// handle with #870
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func initConfig(cmd *cobra.Command) error {
|
||||
home, err := cmd.PersistentFlags().GetString(cli.HomeFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfgFile := path.Join(home, "config", "config.toml")
|
||||
if _, err := os.Stat(cfgFile); err == nil {
|
||||
viper.SetConfigFile(cfgFile)
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := viper.BindPFlag(cli.EncodingFlag, cmd.PersistentFlags().Lookup(cli.EncodingFlag)); err != nil {
|
||||
return err
|
||||
}
|
||||
return viper.BindPFlag(cli.OutputFlag, cmd.PersistentFlags().Lookup(cli.OutputFlag))
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
)
|
||||
|
||||
@ -28,10 +29,13 @@ func main() {
|
||||
Short: "Gaia Daemon (server)",
|
||||
PersistentPreRunE: server.PersistentPreRunEFn(ctx),
|
||||
}
|
||||
appInit := app.GaiaAppInit()
|
||||
rootCmd.AddCommand(gaiaInit.InitCmd(ctx, cdc, appInit))
|
||||
rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, appInit))
|
||||
rootCmd.AddCommand(gaiaInit.GenTxCmd(ctx, cdc))
|
||||
|
||||
server.AddCommands(ctx, cdc, rootCmd, app.GaiaAppInit(),
|
||||
server.ConstructAppCreator(newApp, "gaia"),
|
||||
server.ConstructAppExporter(exportAppStateAndTMValidators, "gaia"))
|
||||
server.AddCommands(ctx, cdc, rootCmd, appInit,
|
||||
newApp, exportAppStateAndTMValidators)
|
||||
|
||||
// prepare and add flags
|
||||
executor := cli.PrepareBaseCmd(rootCmd, "GA", app.DefaultNodeHome)
|
||||
@ -43,7 +47,10 @@ func main() {
|
||||
}
|
||||
|
||||
func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application {
|
||||
return app.NewGaiaApp(logger, db, traceStore, baseapp.SetPruning(viper.GetString("pruning")))
|
||||
return app.NewGaiaApp(logger, db, traceStore,
|
||||
baseapp.SetPruning(viper.GetString("pruning")),
|
||||
baseapp.SetMinimumFees(viper.GetString("minimum_fees")),
|
||||
)
|
||||
}
|
||||
|
||||
func exportAppStateAndTMValidators(
|
||||
|
||||
@ -21,10 +21,9 @@ import (
|
||||
bam "github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/ibc"
|
||||
"github.com/cosmos/cosmos-sdk/x/params"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
@ -65,7 +64,7 @@ func runHackCmd(cmd *cobra.Command, args []string) error {
|
||||
// The following powerKey was there, but the corresponding "trouble" validator did not exist.
|
||||
// So here we do a binary search on the past states to find when the powerKey first showed up ...
|
||||
|
||||
// owner of the validator the bonds, gets revoked, later unbonds, and then later is still found in the bypower store
|
||||
// operator of the validator the bonds, gets jailed, later unbonds, and then later is still found in the bypower store
|
||||
trouble := hexToBytes("D3DC0FF59F7C3B548B7AFA365561B87FD0208AF8")
|
||||
// this is his "bypower" key
|
||||
powerKey := hexToBytes("05303030303030303030303033FFFFFFFFFFFF4C0C0000FFFED3DC0FF59F7C3B548B7AFA365561B87FD0208AF8")
|
||||
@ -127,21 +126,21 @@ var (
|
||||
// Extended ABCI application
|
||||
type GaiaApp struct {
|
||||
*bam.BaseApp
|
||||
cdc *wire.Codec
|
||||
cdc *codec.Codec
|
||||
|
||||
// keys to access the substores
|
||||
keyMain *sdk.KVStoreKey
|
||||
keyAccount *sdk.KVStoreKey
|
||||
keyIBC *sdk.KVStoreKey
|
||||
keyStake *sdk.KVStoreKey
|
||||
tkeyStake *sdk.TransientStoreKey
|
||||
keySlashing *sdk.KVStoreKey
|
||||
keyParams *sdk.KVStoreKey
|
||||
tkeyParams *sdk.TransientStoreKey
|
||||
|
||||
// Manage getting and setting accounts
|
||||
accountMapper auth.AccountMapper
|
||||
accountKeeper auth.AccountKeeper
|
||||
feeCollectionKeeper auth.FeeCollectionKeeper
|
||||
coinKeeper bank.Keeper
|
||||
ibcMapper ibc.Mapper
|
||||
bankKeeper bank.Keeper
|
||||
stakeKeeper stake.Keeper
|
||||
slashingKeeper slashing.Keeper
|
||||
paramsKeeper params.Keeper
|
||||
@ -159,38 +158,38 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp
|
||||
cdc: cdc,
|
||||
keyMain: sdk.NewKVStoreKey("main"),
|
||||
keyAccount: sdk.NewKVStoreKey("acc"),
|
||||
keyIBC: sdk.NewKVStoreKey("ibc"),
|
||||
keyStake: sdk.NewKVStoreKey("stake"),
|
||||
tkeyStake: sdk.NewTransientStoreKey("transient_stake"),
|
||||
keySlashing: sdk.NewKVStoreKey("slashing"),
|
||||
keyParams: sdk.NewKVStoreKey("params"),
|
||||
tkeyParams: sdk.NewTransientStoreKey("transient_params"),
|
||||
}
|
||||
|
||||
// define the accountMapper
|
||||
app.accountMapper = auth.NewAccountMapper(
|
||||
// define the accountKeeper
|
||||
app.accountKeeper = auth.NewAccountKeeper(
|
||||
app.cdc,
|
||||
app.keyAccount, // target store
|
||||
auth.ProtoBaseAccount, // prototype
|
||||
)
|
||||
|
||||
// add handlers
|
||||
app.coinKeeper = bank.NewKeeper(app.accountMapper)
|
||||
app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace))
|
||||
app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams)
|
||||
app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace))
|
||||
app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace))
|
||||
app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper)
|
||||
app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams)
|
||||
app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.tkeyStake, app.bankKeeper, app.paramsKeeper.Subspace(stake.DefaultParamspace), app.RegisterCodespace(stake.DefaultCodespace))
|
||||
app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), app.RegisterCodespace(slashing.DefaultCodespace))
|
||||
|
||||
// register message routes
|
||||
app.Router().
|
||||
AddRoute("bank", bank.NewHandler(app.coinKeeper)).
|
||||
AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.coinKeeper)).
|
||||
AddRoute("bank", bank.NewHandler(app.bankKeeper)).
|
||||
AddRoute("stake", stake.NewHandler(app.stakeKeeper))
|
||||
|
||||
// initialize BaseApp
|
||||
app.SetInitChainer(app.initChainer)
|
||||
app.SetBeginBlocker(app.BeginBlocker)
|
||||
app.SetEndBlocker(app.EndBlocker)
|
||||
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper))
|
||||
app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing)
|
||||
app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper))
|
||||
app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyStake, app.keySlashing, app.keyParams)
|
||||
app.MountStore(app.tkeyParams, sdk.StoreTypeTransient)
|
||||
err := app.LoadLatestVersion(app.keyMain)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
@ -202,15 +201,14 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp
|
||||
}
|
||||
|
||||
// custom tx codec
|
||||
func MakeCodec() *wire.Codec {
|
||||
var cdc = wire.NewCodec()
|
||||
ibc.RegisterWire(cdc)
|
||||
bank.RegisterWire(cdc)
|
||||
stake.RegisterWire(cdc)
|
||||
slashing.RegisterWire(cdc)
|
||||
auth.RegisterWire(cdc)
|
||||
sdk.RegisterWire(cdc)
|
||||
wire.RegisterCrypto(cdc)
|
||||
func MakeCodec() *codec.Codec {
|
||||
var cdc = codec.New()
|
||||
bank.RegisterCodec(cdc)
|
||||
stake.RegisterCodec(cdc)
|
||||
slashing.RegisterCodec(cdc)
|
||||
auth.RegisterCodec(cdc)
|
||||
sdk.RegisterCodec(cdc)
|
||||
codec.RegisterCrypto(cdc)
|
||||
cdc.Seal()
|
||||
return cdc
|
||||
}
|
||||
@ -248,7 +246,7 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci
|
||||
// load the accounts
|
||||
for _, gacc := range genesisState.Accounts {
|
||||
acc := gacc.ToAccount()
|
||||
app.accountMapper.SetAccount(ctx, acc)
|
||||
app.accountKeeper.SetAccount(ctx, acc)
|
||||
}
|
||||
|
||||
// load the initial stake information
|
||||
@ -257,6 +255,8 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci
|
||||
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 // return sdk.ErrGenesisParse("").TraceCause(err, "")
|
||||
}
|
||||
|
||||
slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakeData)
|
||||
|
||||
return abci.ResponseInitChain{
|
||||
Validators: validators,
|
||||
}
|
||||
|
||||
@ -138,11 +138,17 @@ func runPubKeyCmd(cmd *cobra.Command, args []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
consenusPub, err := sdk.Bech32ifyConsPub(pubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Address:", pubKey.Address())
|
||||
fmt.Printf("Hex: %X\n", pubkeyBytes)
|
||||
fmt.Println("JSON (base64):", string(pubKeyJSONBytes))
|
||||
fmt.Println("Bech32 Acc:", accPub)
|
||||
fmt.Println("Bech32 Val:", valPub)
|
||||
fmt.Println("Bech32 Validator Operator:", valPub)
|
||||
fmt.Println("Bech32 Validator Consensus:", consenusPub)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
120
cmd/gaia/init/gentx.go
Normal file
120
cmd/gaia/init/gentx.go
Normal file
@ -0,0 +1,120 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake/client/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
tmcli "github.com/tendermint/tendermint/libs/cli"
|
||||
"github.com/tendermint/tendermint/libs/common"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultAmount = "100steak"
|
||||
defaultCommissionRate = "0.1"
|
||||
defaultCommissionMaxRate = "0.2"
|
||||
defaultCommissionMaxChangeRate = "0.01"
|
||||
)
|
||||
|
||||
// GenTxCmd builds the gaiad gentx command.
|
||||
// nolint: errcheck
|
||||
func GenTxCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "gentx",
|
||||
Short: "Generate a genesis tx carrying a self delegation",
|
||||
Long: fmt.Sprintf(`This command is an alias of the 'gaiad tx create-validator' command'.
|
||||
|
||||
It creates a genesis piece carrying a self delegation with the
|
||||
following delegation and commission default parameters:
|
||||
|
||||
delegation amount: %s
|
||||
commission rate: %s
|
||||
commission max rate: %s
|
||||
commission max change rate: %s
|
||||
`, defaultAmount, defaultCommissionRate, defaultCommissionMaxRate, defaultCommissionMaxChangeRate),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
config := ctx.Config
|
||||
config.SetRoot(viper.GetString(tmcli.HomeFlag))
|
||||
nodeID, valPubKey, err := InitializeNodeValidatorFiles(ctx.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ip, err := server.ExternalIP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Run gaiad tx create-validator
|
||||
prepareFlagsForTxCreateValidator(config, nodeID, ip, valPubKey)
|
||||
createValidatorCmd := cli.GetCmdCreateValidator(cdc)
|
||||
|
||||
w, err := ioutil.TempFile("", "gentx")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
unsignedGenTxFilename := w.Name()
|
||||
defer os.Remove(unsignedGenTxFilename)
|
||||
os.Stdout = w
|
||||
if err = createValidatorCmd.RunE(nil, args); err != nil {
|
||||
return err
|
||||
}
|
||||
w.Close()
|
||||
|
||||
prepareFlagsForTxSign()
|
||||
signCmd := authcmd.GetSignCommand(cdc, authcmd.GetAccountDecoder(cdc))
|
||||
if w, err = prepareOutputFile(config.RootDir, nodeID); err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout = w
|
||||
return signCmd.RunE(nil, []string{unsignedGenTxFilename})
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory")
|
||||
cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id")
|
||||
cmd.Flags().String(client.FlagName, "", "name of private key with which to sign the gentx")
|
||||
cmd.MarkFlagRequired(client.FlagName)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func prepareFlagsForTxCreateValidator(config *cfg.Config, nodeID, ip string, valPubKey crypto.PubKey) {
|
||||
viper.Set(tmcli.HomeFlag, viper.GetString(flagClientHome)) // --home
|
||||
viper.Set(client.FlagFrom, viper.GetString(client.FlagName)) // --from
|
||||
viper.Set(cli.FlagNodeID, nodeID) // --node-id
|
||||
viper.Set(cli.FlagIP, ip) // --ip
|
||||
viper.Set(cli.FlagPubKey, sdk.MustBech32ifyConsPub(valPubKey)) // --pubkey
|
||||
viper.Set(cli.FlagAmount, defaultAmount) // --amount
|
||||
viper.Set(cli.FlagCommissionRate, defaultCommissionRate)
|
||||
viper.Set(cli.FlagCommissionMaxRate, defaultCommissionMaxRate)
|
||||
viper.Set(cli.FlagCommissionMaxChangeRate, defaultCommissionMaxChangeRate)
|
||||
viper.Set(cli.FlagGenesisFormat, true) // --genesis-format
|
||||
viper.Set(cli.FlagMoniker, config.Moniker) // --moniker
|
||||
if config.Moniker == "" {
|
||||
viper.Set(cli.FlagMoniker, viper.GetString(client.FlagName))
|
||||
}
|
||||
}
|
||||
|
||||
func prepareFlagsForTxSign() {
|
||||
viper.Set("offline", true)
|
||||
}
|
||||
|
||||
func prepareOutputFile(rootDir, nodeID string) (w *os.File, err error) {
|
||||
writePath := filepath.Join(rootDir, "config", "gentx")
|
||||
if err = common.EnsureDir(writePath, 0700); err != nil {
|
||||
return
|
||||
}
|
||||
filename := filepath.Join(writePath, fmt.Sprintf("gentx-%v.json", nodeID))
|
||||
return os.Create(filename)
|
||||
}
|
||||
281
cmd/gaia/init/init.go
Normal file
281
cmd/gaia/init/init.go
Normal file
@ -0,0 +1,281 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
authtx "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/privval"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
"github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
const (
|
||||
flagWithTxs = "with-txs"
|
||||
flagOverwrite = "overwrite"
|
||||
flagClientHome = "home-client"
|
||||
flagOverwriteKey = "overwrite-key"
|
||||
flagSkipGenesis = "skip-genesis"
|
||||
flagMoniker = "moniker"
|
||||
)
|
||||
|
||||
type initConfig struct {
|
||||
ChainID string
|
||||
GenTxsDir string
|
||||
Name string
|
||||
NodeID string
|
||||
ClientHome string
|
||||
WithTxs bool
|
||||
Overwrite bool
|
||||
OverwriteKey bool
|
||||
ValPubKey crypto.PubKey
|
||||
}
|
||||
|
||||
type printInfo struct {
|
||||
Moniker string `json:"moniker"`
|
||||
ChainID string `json:"chain_id"`
|
||||
NodeID string `json:"node_id"`
|
||||
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))
|
||||
return nil
|
||||
}
|
||||
|
||||
// get cmd to initialize all files for tendermint and application
|
||||
// nolint
|
||||
func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize private validator, p2p, genesis, and application configuration files",
|
||||
Long: `Initialize validators's and node's configuration files.
|
||||
|
||||
Note that only node's configuration files will be written if the flag --skip-genesis is
|
||||
enabled, and the genesis file will not be generated.
|
||||
`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
config := ctx.Config
|
||||
config.SetRoot(viper.GetString(cli.HomeFlag))
|
||||
|
||||
name := viper.GetString(client.FlagName)
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
if chainID == "" {
|
||||
chainID = fmt.Sprintf("test-chain-%v", common.RandStr(6))
|
||||
}
|
||||
nodeID, valPubKey, err := InitializeNodeValidatorFiles(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if viper.GetString(flagMoniker) != "" {
|
||||
config.Moniker = viper.GetString(flagMoniker)
|
||||
}
|
||||
if config.Moniker == "" && name != "" {
|
||||
config.Moniker = name
|
||||
}
|
||||
toPrint := printInfo{
|
||||
ChainID: chainID,
|
||||
Moniker: config.Moniker,
|
||||
NodeID: nodeID,
|
||||
}
|
||||
if viper.GetBool(flagSkipGenesis) {
|
||||
cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config)
|
||||
return displayInfo(cdc, toPrint)
|
||||
}
|
||||
|
||||
initCfg := initConfig{
|
||||
ChainID: chainID,
|
||||
GenTxsDir: filepath.Join(config.RootDir, "config", "gentx"),
|
||||
Name: name,
|
||||
NodeID: nodeID,
|
||||
ClientHome: viper.GetString(flagClientHome),
|
||||
WithTxs: viper.GetBool(flagWithTxs),
|
||||
Overwrite: viper.GetBool(flagOverwrite),
|
||||
OverwriteKey: viper.GetBool(flagOverwriteKey),
|
||||
ValPubKey: valPubKey,
|
||||
}
|
||||
appMessage, err := initWithConfig(cdc, config, initCfg)
|
||||
// print out some key information
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
toPrint.AppMessage = appMessage
|
||||
return displayInfo(cdc, toPrint)
|
||||
},
|
||||
}
|
||||
|
||||
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().Bool(flagWithTxs, false, "apply existing genesis transactions from [--home]/config/gentx/")
|
||||
cmd.Flags().String(client.FlagName, "", "name of private key with which to sign the gentx")
|
||||
cmd.Flags().String(flagMoniker, "", "overrides --name flag and set the validator's moniker to a different value; ignored if it runs without the --with-txs flag")
|
||||
cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory")
|
||||
cmd.Flags().Bool(flagOverwriteKey, false, "overwrite client's key")
|
||||
cmd.Flags().Bool(flagSkipGenesis, false, "do not create genesis.json")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// InitializeNodeValidatorFiles creates private validator and p2p configuration files.
|
||||
func InitializeNodeValidatorFiles(config *cfg.Config) (nodeID string, valPubKey crypto.PubKey, err error) {
|
||||
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nodeID = string(nodeKey.ID())
|
||||
valPubKey = ReadOrCreatePrivValidator(config.PrivValidatorFile())
|
||||
return
|
||||
}
|
||||
|
||||
func initWithConfig(cdc *codec.Codec, config *cfg.Config, initCfg initConfig) (
|
||||
appMessage json.RawMessage, err error) {
|
||||
genFile := config.GenesisFile()
|
||||
if !initCfg.Overwrite && common.FileExists(genFile) {
|
||||
err = fmt.Errorf("genesis.json file already exists: %v", genFile)
|
||||
return
|
||||
}
|
||||
|
||||
// process genesis transactions, else create default genesis.json
|
||||
var appGenTxs []auth.StdTx
|
||||
var persistentPeers string
|
||||
var genTxs []json.RawMessage
|
||||
var appState json.RawMessage
|
||||
var jsonRawTx json.RawMessage
|
||||
chainID := initCfg.ChainID
|
||||
|
||||
if initCfg.WithTxs {
|
||||
_, appGenTxs, persistentPeers, err = app.CollectStdTxs(config.Moniker, initCfg.GenTxsDir, cdc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
genTxs = make([]json.RawMessage, len(appGenTxs))
|
||||
config.P2P.PersistentPeers = persistentPeers
|
||||
for i, stdTx := range appGenTxs {
|
||||
jsonRawTx, err = cdc.MarshalJSON(stdTx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
genTxs[i] = jsonRawTx
|
||||
}
|
||||
} else {
|
||||
var ip, keyPass, secret string
|
||||
var addr sdk.AccAddress
|
||||
var signedTx auth.StdTx
|
||||
|
||||
if initCfg.Name == "" {
|
||||
err = errors.New("must specify validator's moniker (--name)")
|
||||
return
|
||||
}
|
||||
|
||||
config.Moniker = initCfg.Name
|
||||
ip, err = server.ExternalIP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
memo := fmt.Sprintf("%s@%s:26656", initCfg.NodeID, ip)
|
||||
buf := client.BufferStdin()
|
||||
prompt := fmt.Sprintf("Password for account %q (default: %q):", initCfg.Name, app.DefaultKeyPass)
|
||||
keyPass, err = client.GetPassword(prompt, buf)
|
||||
if err != nil && keyPass != "" {
|
||||
// An error was returned that either failed to read the password from
|
||||
// STDIN or the given password is not empty but failed to meet minimum
|
||||
// length requirements.
|
||||
return
|
||||
}
|
||||
if keyPass == "" {
|
||||
keyPass = app.DefaultKeyPass
|
||||
}
|
||||
|
||||
addr, secret, err = server.GenerateSaveCoinKey(initCfg.ClientHome, initCfg.Name, keyPass, initCfg.OverwriteKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
appMessage, err = json.Marshal(map[string]string{"secret": secret})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
msg := stake.NewMsgCreateValidator(
|
||||
sdk.ValAddress(addr),
|
||||
initCfg.ValPubKey,
|
||||
sdk.NewInt64Coin("steak", 100),
|
||||
stake.NewDescription(config.Moniker, "", "", ""),
|
||||
stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
|
||||
)
|
||||
txBldr := authtx.NewTxBuilderFromCLI().WithCodec(cdc).WithMemo(memo).WithChainID(chainID)
|
||||
signedTx, err = txBldr.SignStdTx(
|
||||
initCfg.Name, keyPass, auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo), false,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
jsonRawTx, err = cdc.MarshalJSON(signedTx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
genTxs = []json.RawMessage{jsonRawTx}
|
||||
}
|
||||
|
||||
cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config)
|
||||
appState, err = app.GaiaAppGenStateJSON(cdc, genTxs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = WriteGenesisFile(genFile, chainID, nil, appState)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// WriteGenesisFile creates and writes the genesis configuration to disk. An
|
||||
// error is returned if building or writing the configuration to file fails.
|
||||
// nolint: unparam
|
||||
func WriteGenesisFile(genesisFile, chainID string, validators []types.GenesisValidator, appState json.RawMessage) error {
|
||||
genDoc := types.GenesisDoc{
|
||||
ChainID: chainID,
|
||||
Validators: validators,
|
||||
AppState: appState,
|
||||
}
|
||||
|
||||
if err := genDoc.ValidateAndComplete(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return genDoc.SaveAs(genesisFile)
|
||||
}
|
||||
|
||||
// read of create the private key file for this config
|
||||
func ReadOrCreatePrivValidator(privValFile string) crypto.PubKey {
|
||||
// private validator
|
||||
var privValidator *privval.FilePV
|
||||
if common.FileExists(privValFile) {
|
||||
privValidator = privval.LoadFilePV(privValFile)
|
||||
} else {
|
||||
privValidator = privval.GenFilePV(privValFile)
|
||||
privValidator.Save()
|
||||
}
|
||||
return privValidator.GetPubKey()
|
||||
}
|
||||
146
cmd/gaia/init/init_test.go
Normal file
146
cmd/gaia/init/init_test.go
Normal file
@ -0,0 +1,146 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/cosmos/cosmos-sdk/server/mock"
|
||||
"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/log"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func TestInitCmd(t *testing.T) {
|
||||
defer server.SetupViper(t)()
|
||||
defer setupClientHome(t)()
|
||||
|
||||
logger := log.NewNopLogger()
|
||||
cfg, err := tcmd.ParseConfig()
|
||||
require.Nil(t, err)
|
||||
ctx := server.NewContext(cfg, logger)
|
||||
cdc := app.MakeCodec()
|
||||
appInit := server.AppInit{
|
||||
AppGenState: mock.AppGenState,
|
||||
}
|
||||
cmd := InitCmd(ctx, cdc, appInit)
|
||||
err = cmd.RunE(nil, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func setupClientHome(t *testing.T) func() {
|
||||
clientDir, err := ioutil.TempDir("", "mock-sdk-cmd")
|
||||
require.Nil(t, err)
|
||||
viper.Set(flagClientHome, clientDir)
|
||||
viper.Set(flagOverwriteKey, true)
|
||||
return func() {
|
||||
if err := os.RemoveAll(clientDir); err != nil {
|
||||
// TODO: Handle with #870
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyState(t *testing.T) {
|
||||
defer server.SetupViper(t)()
|
||||
defer setupClientHome(t)()
|
||||
logger := log.NewNopLogger()
|
||||
cfg, err := tcmd.ParseConfig()
|
||||
require.Nil(t, err)
|
||||
ctx := server.NewContext(cfg, logger)
|
||||
cdc := app.MakeCodec()
|
||||
appInit := server.AppInit{
|
||||
AppGenState: mock.AppGenStateEmpty,
|
||||
}
|
||||
cmd := InitCmd(ctx, cdc, appInit)
|
||||
err = cmd.RunE(nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
old := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
cmd = server.ExportCmd(ctx, cdc, nil)
|
||||
err = cmd.RunE(nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
outC := make(chan string)
|
||||
go func() {
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
outC <- buf.String()
|
||||
}()
|
||||
|
||||
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")
|
||||
require.Contains(t, out, "validators")
|
||||
require.Contains(t, out, "app_hash")
|
||||
}
|
||||
|
||||
func TestStartStandAlone(t *testing.T) {
|
||||
home, err := ioutil.TempDir("", "mock-sdk-cmd")
|
||||
require.Nil(t, err)
|
||||
defer func() {
|
||||
os.RemoveAll(home)
|
||||
}()
|
||||
viper.Set(cli.HomeFlag, home)
|
||||
viper.Set(client.FlagName, "moniker")
|
||||
defer setupClientHome(t)()
|
||||
|
||||
logger := log.NewNopLogger()
|
||||
cfg, err := tcmd.ParseConfig()
|
||||
require.Nil(t, err)
|
||||
ctx := server.NewContext(cfg, logger)
|
||||
cdc := app.MakeCodec()
|
||||
appInit := server.AppInit{
|
||||
AppGenState: mock.AppGenState,
|
||||
}
|
||||
initCmd := InitCmd(ctx, cdc, appInit)
|
||||
err = initCmd.RunE(nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
app, err := mock.NewApp(home, logger)
|
||||
require.Nil(t, err)
|
||||
svrAddr, _, err := server.FreeTCPAddr()
|
||||
require.Nil(t, err)
|
||||
svr, err := abciServer.NewServer(svrAddr, "socket", app)
|
||||
require.Nil(t, err, "error creating listener")
|
||||
svr.SetLogger(logger.With("module", "abci-server"))
|
||||
svr.Start()
|
||||
|
||||
timer := time.NewTimer(time.Duration(2) * time.Second)
|
||||
select {
|
||||
case <-timer.C:
|
||||
svr.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitNodeValidatorFiles(t *testing.T) {
|
||||
home, err := ioutil.TempDir("", "mock-sdk-cmd")
|
||||
require.Nil(t, err)
|
||||
defer func() {
|
||||
os.RemoveAll(home)
|
||||
}()
|
||||
viper.Set(cli.HomeFlag, home)
|
||||
viper.Set(client.FlagName, "moniker")
|
||||
cfg, err := tcmd.ParseConfig()
|
||||
require.Nil(t, err)
|
||||
nodeID, valPubKey, err := InitializeNodeValidatorFiles(cfg)
|
||||
require.Nil(t, err)
|
||||
require.NotEqual(t, "", nodeID)
|
||||
require.NotEqual(t, 0, len(valPubKey.Bytes()))
|
||||
}
|
||||
247
cmd/gaia/init/testnet.go
Normal file
247
cmd/gaia/init/testnet.go
Normal file
@ -0,0 +1,247 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
authtx "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
var (
|
||||
nodeDirPrefix = "node-dir-prefix"
|
||||
nValidators = "v"
|
||||
outputDir = "output-dir"
|
||||
nodeDaemonHome = "node-daemon-home"
|
||||
nodeCliHome = "node-cli-home"
|
||||
|
||||
startingIPAddress = "starting-ip-address"
|
||||
)
|
||||
|
||||
const nodeDirPerm = 0755
|
||||
|
||||
// get cmd to initialize all files for tendermint testnet and application
|
||||
func TestnetFilesCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "testnet",
|
||||
Short: "Initialize files for a Gaiad testnet",
|
||||
Long: `testnet will create "v" number of directories and populate each with
|
||||
necessary files (private validator, genesis, config, etc.).
|
||||
|
||||
Note, strict routability for addresses is turned off in the config file.
|
||||
|
||||
Example:
|
||||
|
||||
gaiad testnet --v 4 --o ./output --starting-ip-address 192.168.10.2
|
||||
`,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
config := ctx.Config
|
||||
return testnetWithConfig(config, cdc, appInit)
|
||||
},
|
||||
}
|
||||
cmd.Flags().Int(nValidators, 4,
|
||||
"Number of validators to initialize the testnet with")
|
||||
cmd.Flags().StringP(outputDir, "o", "./mytestnet",
|
||||
"Directory to store initialization data for the testnet")
|
||||
cmd.Flags().String(nodeDirPrefix, "node",
|
||||
"Prefix the directory name for each node with (node results in node0, node1, ...)")
|
||||
cmd.Flags().String(nodeDaemonHome, "gaiad",
|
||||
"Home directory of the node's daemon configuration")
|
||||
cmd.Flags().String(nodeCliHome, "gaiacli",
|
||||
"Home directory of the node's cli configuration")
|
||||
|
||||
cmd.Flags().String(startingIPAddress, "192.168.0.1",
|
||||
"Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit server.AppInit) error {
|
||||
outDir := viper.GetString(outputDir)
|
||||
numValidators := viper.GetInt(nValidators)
|
||||
|
||||
// Generate genesis.json and config.toml
|
||||
chainID := "chain-" + cmn.RandStr(6)
|
||||
monikers := make([]string, numValidators)
|
||||
nodeIDs := make([]string, numValidators)
|
||||
valPubKeys := make([]crypto.PubKey, numValidators)
|
||||
|
||||
// Generate private key, node ID, initial transaction
|
||||
for i := 0; i < numValidators; i++ {
|
||||
nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i)
|
||||
nodeDaemonHomeName := viper.GetString(nodeDaemonHome)
|
||||
nodeCliHomeName := viper.GetString(nodeCliHome)
|
||||
nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName)
|
||||
clientDir := filepath.Join(outDir, nodeDirName, nodeCliHomeName)
|
||||
gentxsDir := filepath.Join(outDir, "gentxs")
|
||||
config.SetRoot(nodeDir)
|
||||
|
||||
err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(outDir)
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(clientDir, nodeDirPerm)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(outDir)
|
||||
return err
|
||||
}
|
||||
|
||||
monikers = append(monikers, nodeDirName)
|
||||
config.Moniker = nodeDirName
|
||||
ip, err := getIP(i)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(outDir)
|
||||
return err
|
||||
}
|
||||
nodeIDs[i], valPubKeys[i], err = InitializeNodeValidatorFiles(config)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(outDir)
|
||||
return err
|
||||
}
|
||||
memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip)
|
||||
|
||||
buf := client.BufferStdin()
|
||||
prompt := fmt.Sprintf("Password for account '%s' (default %s):", nodeDirName, app.DefaultKeyPass)
|
||||
keyPass, err := client.GetPassword(prompt, buf)
|
||||
if err != nil && keyPass != "" {
|
||||
// An error was returned that either failed to read the password from
|
||||
// STDIN or the given password is not empty but failed to meet minimum
|
||||
// length requirements.
|
||||
return err
|
||||
}
|
||||
if keyPass == "" {
|
||||
keyPass = app.DefaultKeyPass
|
||||
}
|
||||
|
||||
addr, secret, err := server.GenerateSaveCoinKey(clientDir, nodeDirName, keyPass, true)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(outDir)
|
||||
return err
|
||||
}
|
||||
info := map[string]string{"secret": secret}
|
||||
cliPrint, err := json.Marshal(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Save private key seed words
|
||||
err = writeFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, cliPrint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := stake.NewMsgCreateValidator(
|
||||
sdk.ValAddress(addr),
|
||||
valPubKeys[i],
|
||||
sdk.NewInt64Coin("steak", 100),
|
||||
stake.NewDescription(nodeDirName, "", "", ""),
|
||||
stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
|
||||
)
|
||||
tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo)
|
||||
txBldr := authtx.NewTxBuilderFromCLI().WithChainID(chainID).WithMemo(memo)
|
||||
signedTx, err := txBldr.SignStdTx(nodeDirName, app.DefaultKeyPass, tx, false)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(outDir)
|
||||
return err
|
||||
}
|
||||
|
||||
txBytes, err := cdc.MarshalJSON(signedTx)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(outDir)
|
||||
return err
|
||||
}
|
||||
|
||||
// Gather gentxs folder
|
||||
err = writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBytes)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(outDir)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < numValidators; i++ {
|
||||
|
||||
nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i)
|
||||
nodeDaemonHomeName := viper.GetString(nodeDaemonHome)
|
||||
nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName)
|
||||
gentxsDir := filepath.Join(outDir, "gentxs")
|
||||
moniker := monikers[i]
|
||||
config.Moniker = nodeDirName
|
||||
config.SetRoot(nodeDir)
|
||||
|
||||
nodeID, valPubKey := nodeIDs[i], valPubKeys[i]
|
||||
// Run `init` and generate genesis.json and config.toml
|
||||
initCfg := initConfig{
|
||||
ChainID: chainID,
|
||||
GenTxsDir: gentxsDir,
|
||||
Name: moniker,
|
||||
WithTxs: true,
|
||||
Overwrite: true,
|
||||
OverwriteKey: false,
|
||||
NodeID: nodeID,
|
||||
ValPubKey: valPubKey,
|
||||
}
|
||||
if _, err := initWithConfig(cdc, config, initCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully initialized %v node directories\n", viper.GetInt(nValidators))
|
||||
return nil
|
||||
}
|
||||
|
||||
func getIP(i int) (ip string, err error) {
|
||||
ip = viper.GetString(startingIPAddress)
|
||||
if len(ip) == 0 {
|
||||
ip, err = server.ExternalIP()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
ip, err = calculateIP(ip, i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func writeFile(name string, dir string, contents []byte) error {
|
||||
writePath := filepath.Join(dir)
|
||||
file := filepath.Join(writePath, name)
|
||||
err := cmn.EnsureDir(writePath, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = cmn.WriteFile(file, contents, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func calculateIP(ip string, i int) (string, error) {
|
||||
ipv4 := net.ParseIP(ip).To4()
|
||||
if ipv4 == nil {
|
||||
return "", fmt.Errorf("%v: non ipv4 address", ip)
|
||||
}
|
||||
|
||||
for j := 0; j < i; j++ {
|
||||
ipv4[3]++
|
||||
}
|
||||
return ipv4.String(), nil
|
||||
}
|
||||
@ -34,7 +34,7 @@ See [testnets repo](https://github.com/cosmos/testnets).
|
||||
## *June 13, 2018, 17:00 EST* - Gaia-6002 is making blocks!
|
||||
|
||||
- Gaia-6002 is live and making blocks
|
||||
- Absent validators have been slashed and revoked
|
||||
- Absent validators have been slashed and jailed
|
||||
- Currently live with 17 validators
|
||||
|
||||
## *June 13, 2018, 4:30 EST* - New Testnet Gaia-6002
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package wire
|
||||
package codec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -11,7 +11,7 @@ import (
|
||||
// amino codec to marshal/unmarshal
|
||||
type Codec = amino.Codec
|
||||
|
||||
func NewCodec() *Codec {
|
||||
func New() *Codec {
|
||||
cdc := amino.NewCodec()
|
||||
return cdc
|
||||
}
|
||||
@ -42,7 +42,7 @@ func MarshalJSONIndent(cdc *Codec, obj interface{}) ([]byte, error) {
|
||||
var Cdc *Codec
|
||||
|
||||
func init() {
|
||||
cdc := NewCodec()
|
||||
cdc := New()
|
||||
RegisterCrypto(cdc)
|
||||
Cdc = cdc.Seal()
|
||||
}
|
||||
@ -55,6 +55,7 @@ func ExamplePrintRegisteredTypes() {
|
||||
//| PrivKeyLedgerSecp256k1 | tendermint/PrivKeyLedgerSecp256k1 | 0x10CAB393 | variable | |
|
||||
//| PubKeyEd25519 | tendermint/PubKeyEd25519 | 0x1624DE64 | 0x20 | |
|
||||
//| PubKeySecp256k1 | tendermint/PubKeySecp256k1 | 0xEB5AE987 | 0x21 | |
|
||||
//| PubKeyMultisigThreshold | tendermint/PubKeyMultisigThreshold | 0x22C1F7E2 | variable | |
|
||||
//| PrivKeyEd25519 | tendermint/PrivKeyEd25519 | 0xA3288910 | 0x40 | |
|
||||
//| PrivKeySecp256k1 | tendermint/PrivKeySecp256k1 | 0xE1B0F79B | 0x20 | |
|
||||
}
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bcrypt
|
||||
|
||||
import "encoding/base64"
|
||||
|
||||
const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
var bcEncoding = base64.NewEncoding(alphabet)
|
||||
|
||||
func base64Encode(src []byte) []byte {
|
||||
n := bcEncoding.EncodedLen(len(src))
|
||||
dst := make([]byte, n)
|
||||
bcEncoding.Encode(dst, src)
|
||||
for dst[n-1] == '=' {
|
||||
n--
|
||||
}
|
||||
return dst[:n]
|
||||
}
|
||||
|
||||
func base64Decode(src []byte) ([]byte, error) {
|
||||
numOfEquals := 4 - (len(src) % 4)
|
||||
for i := 0; i < numOfEquals; i++ {
|
||||
src = append(src, '=')
|
||||
}
|
||||
|
||||
dst := make([]byte, bcEncoding.DecodedLen(len(src)))
|
||||
n, err := bcEncoding.Decode(dst, src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dst[:n], nil
|
||||
}
|
||||
@ -1,297 +0,0 @@
|
||||
package bcrypt
|
||||
|
||||
// MODIFIED BY TENDERMINT TO EXPOSE NONCE
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing
|
||||
// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf
|
||||
|
||||
// The code is a port of Provos and Mazières's C implementation.
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/blowfish"
|
||||
)
|
||||
|
||||
const (
|
||||
// the minimum allowable cost as passed in to GenerateFromPassword
|
||||
MinCost int = 4
|
||||
// the maximum allowable cost as passed in to GenerateFromPassword
|
||||
MaxCost int = 31
|
||||
// the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword
|
||||
DefaultCost int = 10
|
||||
)
|
||||
|
||||
// The error returned from CompareHashAndPassword when a password and hash do
|
||||
// not match.
|
||||
var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password")
|
||||
|
||||
// The error returned from CompareHashAndPassword when a hash is too short to
|
||||
// be a bcrypt hash.
|
||||
var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password")
|
||||
|
||||
// The error returned from CompareHashAndPassword when a hash was created with
|
||||
// a bcrypt algorithm newer than this implementation.
|
||||
type HashVersionTooNewError byte
|
||||
|
||||
func (hv HashVersionTooNewError) Error() string {
|
||||
return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion)
|
||||
}
|
||||
|
||||
// The error returned from CompareHashAndPassword when a hash starts with something other than '$'
|
||||
type InvalidHashPrefixError byte
|
||||
|
||||
// Format error
|
||||
func (ih InvalidHashPrefixError) Error() string {
|
||||
return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih))
|
||||
}
|
||||
|
||||
// Invalid bcrypt cost
|
||||
type InvalidCostError int
|
||||
|
||||
func (ic InvalidCostError) Error() string {
|
||||
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost)) // nolint: unconvert
|
||||
}
|
||||
|
||||
const (
|
||||
majorVersion = '2'
|
||||
minorVersion = 'a'
|
||||
maxSaltSize = 16
|
||||
maxCryptedHashSize = 23
|
||||
encodedSaltSize = 22
|
||||
encodedHashSize = 31
|
||||
minHashSize = 59
|
||||
)
|
||||
|
||||
// magicCipherData is an IV for the 64 Blowfish encryption calls in
|
||||
// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes.
|
||||
var magicCipherData = []byte{
|
||||
0x4f, 0x72, 0x70, 0x68,
|
||||
0x65, 0x61, 0x6e, 0x42,
|
||||
0x65, 0x68, 0x6f, 0x6c,
|
||||
0x64, 0x65, 0x72, 0x53,
|
||||
0x63, 0x72, 0x79, 0x44,
|
||||
0x6f, 0x75, 0x62, 0x74,
|
||||
}
|
||||
|
||||
type hashed struct {
|
||||
hash []byte
|
||||
salt []byte
|
||||
cost int // allowed range is MinCost to MaxCost
|
||||
major byte
|
||||
minor byte
|
||||
}
|
||||
|
||||
// GenerateFromPassword returns the bcrypt hash of the password at the given
|
||||
// cost. If the cost given is less than MinCost, the cost will be set to
|
||||
// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package,
|
||||
// to compare the returned hashed password with its cleartext version.
|
||||
func GenerateFromPassword(salt []byte, password []byte, cost int) ([]byte, error) {
|
||||
if len(salt) != maxSaltSize {
|
||||
return nil, fmt.Errorf("salt len must be %v", maxSaltSize)
|
||||
}
|
||||
p, err := newFromPassword(salt, password, cost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.Hash(), nil
|
||||
}
|
||||
|
||||
// CompareHashAndPassword compares a bcrypt hashed password with its possible
|
||||
// plaintext equivalent. Returns nil on success, or an error on failure.
|
||||
func CompareHashAndPassword(hashedPassword, password []byte) error {
|
||||
p, err := newFromHash(hashedPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
otherHash, err := bcrypt(password, p.cost, p.salt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor}
|
||||
if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrMismatchedHashAndPassword
|
||||
}
|
||||
|
||||
// Cost returns the hashing cost used to create the given hashed
|
||||
// password. When, in the future, the hashing cost of a password system needs
|
||||
// to be increased in order to adjust for greater computational power, this
|
||||
// function allows one to establish which passwords need to be updated.
|
||||
func Cost(hashedPassword []byte) (int, error) {
|
||||
p, err := newFromHash(hashedPassword)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return p.cost, nil
|
||||
}
|
||||
|
||||
func newFromPassword(salt []byte, password []byte, cost int) (*hashed, error) {
|
||||
if cost < MinCost {
|
||||
cost = DefaultCost
|
||||
}
|
||||
p := new(hashed)
|
||||
p.major = majorVersion
|
||||
p.minor = minorVersion
|
||||
|
||||
err := checkCost(cost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.cost = cost
|
||||
|
||||
p.salt = base64Encode(salt)
|
||||
hash, err := bcrypt(password, p.cost, p.salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.hash = hash
|
||||
return p, err
|
||||
}
|
||||
|
||||
func newFromHash(hashedSecret []byte) (*hashed, error) {
|
||||
if len(hashedSecret) < minHashSize {
|
||||
return nil, ErrHashTooShort
|
||||
}
|
||||
p := new(hashed)
|
||||
n, err := p.decodeVersion(hashedSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashedSecret = hashedSecret[n:]
|
||||
n, err = p.decodeCost(hashedSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashedSecret = hashedSecret[n:]
|
||||
|
||||
// The "+2" is here because we'll have to append at most 2 '=' to the salt
|
||||
// when base64 decoding it in expensiveBlowfishSetup().
|
||||
p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2)
|
||||
copy(p.salt, hashedSecret[:encodedSaltSize])
|
||||
|
||||
hashedSecret = hashedSecret[encodedSaltSize:]
|
||||
p.hash = make([]byte, len(hashedSecret))
|
||||
copy(p.hash, hashedSecret)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) {
|
||||
cipherData := make([]byte, len(magicCipherData))
|
||||
copy(cipherData, magicCipherData)
|
||||
|
||||
c, err := expensiveBlowfishSetup(password, uint32(cost), salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < 24; i += 8 {
|
||||
for j := 0; j < 64; j++ {
|
||||
c.Encrypt(cipherData[i:i+8], cipherData[i:i+8])
|
||||
}
|
||||
}
|
||||
|
||||
// Bug compatibility with C bcrypt implementations. We only encode 23 of
|
||||
// the 24 bytes encrypted.
|
||||
hsh := base64Encode(cipherData[:maxCryptedHashSize])
|
||||
return hsh, nil
|
||||
}
|
||||
|
||||
func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) {
|
||||
|
||||
csalt, err := base64Decode(salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Bug compatibility with C bcrypt implementations. They use the trailing
|
||||
// NULL in the key string during expansion.
|
||||
ckey := append(key, 0)
|
||||
|
||||
c, err := blowfish.NewSaltedCipher(ckey, csalt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var i, rounds uint64
|
||||
rounds = 1 << cost
|
||||
for i = 0; i < rounds; i++ {
|
||||
blowfish.ExpandKey(ckey, c)
|
||||
blowfish.ExpandKey(csalt, c)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (p *hashed) Hash() []byte {
|
||||
arr := make([]byte, 60)
|
||||
arr[0] = '$'
|
||||
arr[1] = p.major
|
||||
n := 2
|
||||
if p.minor != 0 {
|
||||
arr[2] = p.minor
|
||||
n = 3
|
||||
}
|
||||
arr[n] = '$'
|
||||
n++
|
||||
copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost)))
|
||||
n += 2
|
||||
arr[n] = '$'
|
||||
n++
|
||||
copy(arr[n:], p.salt)
|
||||
n += encodedSaltSize
|
||||
copy(arr[n:], p.hash)
|
||||
n += encodedHashSize
|
||||
return arr[:n]
|
||||
}
|
||||
|
||||
func (p *hashed) decodeVersion(sbytes []byte) (int, error) {
|
||||
if sbytes[0] != '$' {
|
||||
return -1, InvalidHashPrefixError(sbytes[0])
|
||||
}
|
||||
if sbytes[1] > majorVersion {
|
||||
return -1, HashVersionTooNewError(sbytes[1])
|
||||
}
|
||||
p.major = sbytes[1]
|
||||
n := 3
|
||||
if sbytes[2] != '$' {
|
||||
p.minor = sbytes[2]
|
||||
n++
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// sbytes should begin where decodeVersion left off.
|
||||
func (p *hashed) decodeCost(sbytes []byte) (int, error) {
|
||||
cost, err := strconv.Atoi(string(sbytes[0:2]))
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
err = checkCost(cost)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
p.cost = cost
|
||||
return 3, nil
|
||||
}
|
||||
|
||||
func (p *hashed) String() string {
|
||||
return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor)
|
||||
}
|
||||
|
||||
func checkCost(cost int) error {
|
||||
if cost < MinCost || cost > MaxCost {
|
||||
return InvalidCostError(cost)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
package bip39
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/bartekn/go-bip39"
|
||||
)
|
||||
|
||||
// ValidSentenceLen defines the mnemonic sentence lengths supported by this BIP 39 library.
|
||||
type ValidSentenceLen uint8
|
||||
|
||||
const (
|
||||
// FundRaiser is the sentence length used during the cosmos fundraiser (12 words).
|
||||
FundRaiser ValidSentenceLen = 12
|
||||
// Size of the checksum employed for the fundraiser
|
||||
FundRaiserChecksumSize = 4
|
||||
// FreshKey is the sentence length used for newly created keys (24 words).
|
||||
FreshKey ValidSentenceLen = 24
|
||||
// Size of the checksum employed for new keys
|
||||
FreshKeyChecksumSize = 8
|
||||
)
|
||||
|
||||
// NewMnemonic will return a string consisting of the mnemonic words for
|
||||
// the given sentence length.
|
||||
func NewMnemonic(len ValidSentenceLen) (words []string, err error) {
|
||||
// len = (entropySize + checksum) / 11
|
||||
var entropySize int
|
||||
switch len {
|
||||
case FundRaiser:
|
||||
// entropySize = 128
|
||||
entropySize = int(len)*11 - FundRaiserChecksumSize
|
||||
case FreshKey:
|
||||
// entropySize = 256
|
||||
entropySize = int(len)*11 - FreshKeyChecksumSize
|
||||
}
|
||||
var entropy []byte
|
||||
entropy, err = bip39.NewEntropy(entropySize)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var mnemonic string
|
||||
mnemonic, err = bip39.NewMnemonic(entropy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
words = strings.Split(mnemonic, " ")
|
||||
return
|
||||
}
|
||||
|
||||
// MnemonicToSeed creates a BIP 39 seed from the passed mnemonic (with an empty BIP 39 password).
|
||||
// This method does not validate the mnemonics checksum.
|
||||
func MnemonicToSeed(mne string) (seed []byte) {
|
||||
// we do not checksum here...
|
||||
seed = bip39.NewSeed(mne, "")
|
||||
return
|
||||
}
|
||||
|
||||
// MnemonicToSeedWithErrChecking returns the same seed as MnemonicToSeed.
|
||||
// It creates a BIP 39 seed from the passed mnemonic (with an empty BIP 39 password).
|
||||
//
|
||||
// Different from MnemonicToSeed it validates the checksum.
|
||||
// For details on the checksum see the BIP 39 spec.
|
||||
func MnemonicToSeedWithErrChecking(mne string) (seed []byte, err error) {
|
||||
seed, err = bip39.NewSeedWithErrorChecking(mne, "")
|
||||
return
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
package bip39
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWordCodec_NewMnemonic(t *testing.T) {
|
||||
_, err := NewMnemonic(FundRaiser)
|
||||
require.NoError(t, err, "unexpected error generating fundraiser mnemonic")
|
||||
|
||||
_, err = NewMnemonic(FreshKey)
|
||||
require.NoError(t, err, "unexpected error generating new 24-word mnemonic")
|
||||
}
|
||||
@ -7,9 +7,10 @@ import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/bartekn/go-bip39"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/go-bip39"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
)
|
||||
|
||||
@ -22,7 +22,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
)
|
||||
|
||||
// BIP44Prefix is the parts of the BIP32 HD path that are fixed by what we used during the fundraiser.
|
||||
@ -55,6 +54,77 @@ func NewParams(purpose, coinType, account uint32, change bool, addressIdx uint32
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the BIP44 path and unmarshal into the struct.
|
||||
// nolint: gocyclo
|
||||
func NewParamsFromPath(path string) (*BIP44Params, error) {
|
||||
spl := strings.Split(path, "/")
|
||||
if len(spl) != 5 {
|
||||
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])
|
||||
}
|
||||
|
||||
purpose, err := hardenedInt(spl[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
coinType, err := hardenedInt(spl[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
account, err := hardenedInt(spl[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
change, err := hardenedInt(spl[3])
|
||||
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
|
||||
}
|
||||
|
||||
return &BIP44Params{
|
||||
purpose: purpose,
|
||||
coinType: coinType,
|
||||
account: account,
|
||||
change: change > 0,
|
||||
addressIdx: addressIdx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func hardenedInt(field string) (uint32, error) {
|
||||
field = strings.TrimSuffix(field, "'")
|
||||
i, err := strconv.Atoi(field)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if i < 0 {
|
||||
return 0, fmt.Errorf("fields must not be negative. got %d", i)
|
||||
}
|
||||
return uint32(i), nil
|
||||
}
|
||||
|
||||
func isHardened(field string) bool {
|
||||
return strings.HasSuffix(field, "'")
|
||||
}
|
||||
|
||||
// NewFundraiserParams creates a BIP 44 parameter object from the params:
|
||||
// m / 44' / 118' / account' / 0 / address_index
|
||||
// The fixed parameters (purpose', coin_type', and change) are determined by what was used in the fundraiser.
|
||||
@ -62,6 +132,21 @@ func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params {
|
||||
return NewParams(44, 118, account, false, addressIdx)
|
||||
}
|
||||
|
||||
// Return the BIP44 fields as an array.
|
||||
func (p BIP44Params) DerivationPath() []uint32 {
|
||||
change := uint32(0)
|
||||
if p.change {
|
||||
change = 1
|
||||
}
|
||||
return []uint32{
|
||||
p.purpose,
|
||||
p.coinType,
|
||||
p.account,
|
||||
change,
|
||||
p.addressIdx,
|
||||
}
|
||||
}
|
||||
|
||||
func (p BIP44Params) String() string {
|
||||
var changeStr string
|
||||
if p.change {
|
||||
@ -128,10 +213,15 @@ func derivePrivateKey(privKeyBytes [32]byte, chainCode [32]byte, index uint32, h
|
||||
data = append([]byte{byte(0)}, privKeyBytes[:]...)
|
||||
} else {
|
||||
// this can't return an error:
|
||||
pubkey := secp256k1.PrivKeySecp256k1(privKeyBytes).PubKey()
|
||||
_, ecPub := btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes[:])
|
||||
pubkeyBytes := ecPub.SerializeCompressed()
|
||||
data = pubkeyBytes
|
||||
|
||||
/* By using btcec, we can remove the dependency on tendermint/crypto/secp256k1
|
||||
pubkey := secp256k1.PrivKeySecp256k1(privKeyBytes).PubKey()
|
||||
public := pubkey.(secp256k1.PubKeySecp256k1)
|
||||
data = public[:]
|
||||
*/
|
||||
}
|
||||
data = append(data, uint32ToBytes(index)...)
|
||||
data2, chainCode2 := i64(chainCode[:], data)
|
||||
|
||||
@ -3,9 +3,20 @@ package hd
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/bip39"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/cosmos/go-bip39"
|
||||
)
|
||||
|
||||
var defaultBIP39Passphrase = ""
|
||||
|
||||
// return bip39 seed with empty passphrase
|
||||
func mnemonicToSeed(mnemonic string) []byte {
|
||||
return bip39.NewSeed(mnemonic, defaultBIP39Passphrase)
|
||||
}
|
||||
|
||||
//nolint
|
||||
func ExampleStringifyPathParams() {
|
||||
path := NewParams(44, 0, 0, false, 0)
|
||||
@ -13,10 +24,57 @@ func ExampleStringifyPathParams() {
|
||||
// Output: 44'/0'/0'/0/0
|
||||
}
|
||||
|
||||
func TestParamsFromPath(t *testing.T) {
|
||||
goodCases := []struct {
|
||||
params *BIP44Params
|
||||
path string
|
||||
}{
|
||||
{&BIP44Params{44, 0, 0, false, 0}, "44'/0'/0'/0/0"},
|
||||
{&BIP44Params{44, 1, 0, false, 0}, "44'/1'/0'/0/0"},
|
||||
{&BIP44Params{44, 0, 1, false, 0}, "44'/0'/1'/0/0"},
|
||||
{&BIP44Params{44, 0, 0, true, 0}, "44'/0'/0'/1/0"},
|
||||
{&BIP44Params{44, 0, 0, false, 1}, "44'/0'/0'/0/1"},
|
||||
{&BIP44Params{44, 1, 1, true, 1}, "44'/1'/1'/1/1"},
|
||||
{&BIP44Params{44, 118, 52, true, 41}, "44'/118'/52'/1/41"},
|
||||
}
|
||||
|
||||
for i, c := range goodCases {
|
||||
params, err := NewParamsFromPath(c.path)
|
||||
errStr := fmt.Sprintf("%d %v", i, c)
|
||||
assert.NoError(t, err, errStr)
|
||||
assert.EqualValues(t, c.params, params, errStr)
|
||||
assert.Equal(t, c.path, c.params.String())
|
||||
}
|
||||
|
||||
badCases := []struct {
|
||||
path string
|
||||
}{
|
||||
{"43'/0'/0'/0/0"}, // doesnt start with 44
|
||||
{"44'/1'/0'/0/0/5"}, // too many fields
|
||||
{"44'/0'/1'/0"}, // too few fields
|
||||
{"44'/0'/0'/2/0"}, // change field can only be 0/1
|
||||
{"44/0'/0'/0/0"}, // first field needs '
|
||||
{"44'/0/0'/0/0"}, // second field needs '
|
||||
{"44'/0'/0/0/0"}, // third field needs '
|
||||
{"44'/0'/0'/0'/0"}, // fourth field must not have '
|
||||
{"44'/0'/0'/0/0'"}, // fifth field must not have '
|
||||
{"44'/-1'/0'/0/0"}, // no negatives
|
||||
{"44'/0'/0'/-1/0"}, // no negatives
|
||||
}
|
||||
|
||||
for i, c := range badCases {
|
||||
params, err := NewParamsFromPath(c.path)
|
||||
errStr := fmt.Sprintf("%d %v", i, c)
|
||||
assert.Nil(t, params, errStr)
|
||||
assert.Error(t, err, errStr)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//nolint
|
||||
func ExampleSomeBIP32TestVecs() {
|
||||
|
||||
seed := bip39.MnemonicToSeed("barrel original fuel morning among eternal " +
|
||||
seed := mnemonicToSeed("barrel original fuel morning among eternal " +
|
||||
"filter ball stove pluck matrix mechanic")
|
||||
master, ch := ComputeMastersFromSeed(seed)
|
||||
fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)")
|
||||
@ -35,14 +93,14 @@ func ExampleSomeBIP32TestVecs() {
|
||||
fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html")
|
||||
fmt.Println()
|
||||
|
||||
seed = bip39.MnemonicToSeed(
|
||||
seed = mnemonicToSeed(
|
||||
"advice process birth april short trust crater change bacon monkey medal garment " +
|
||||
"gorilla ranch hour rival razor call lunar mention taste vacant woman sister")
|
||||
master, ch = ComputeMastersFromSeed(seed)
|
||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4")
|
||||
fmt.Println(hex.EncodeToString(priv[:]))
|
||||
|
||||
seed = bip39.MnemonicToSeed("idea naive region square margin day captain habit " +
|
||||
seed = mnemonicToSeed("idea naive region square margin day captain habit " +
|
||||
"gun second farm pact pulse someone armed")
|
||||
master, ch = ComputeMastersFromSeed(seed)
|
||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420")
|
||||
@ -53,7 +111,7 @@ func ExampleSomeBIP32TestVecs() {
|
||||
fmt.Println()
|
||||
|
||||
// bip32 path: m/0/7
|
||||
seed = bip39.MnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history")
|
||||
seed = mnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history")
|
||||
master, ch = ComputeMastersFromSeed(seed)
|
||||
priv, _ = DerivePrivateKeyForPath(master, ch, "0/7")
|
||||
fmt.Println(hex.EncodeToString(priv[:]))
|
||||
|
||||
@ -6,10 +6,16 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/bip39"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cosmos/go-bip39"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
||||
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
@ -41,6 +47,16 @@ const (
|
||||
French
|
||||
// Italian is currently not supported.
|
||||
Italian
|
||||
addressSuffix = "address"
|
||||
infoSuffix = "info"
|
||||
)
|
||||
|
||||
const (
|
||||
// used for deriving seed from mnemonic
|
||||
defaultBIP39Passphrase = ""
|
||||
|
||||
// bits of entropy to draw when creating a mnemonic
|
||||
defaultEntropySize = 256
|
||||
)
|
||||
|
||||
var (
|
||||
@ -82,12 +98,17 @@ func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string
|
||||
}
|
||||
|
||||
// default number of words (24):
|
||||
mnemonicS, err := bip39.NewMnemonic(bip39.FreshKey)
|
||||
// this generates a mnemonic directly from the number of words by reading system entropy.
|
||||
entropy, err := bip39.NewEntropy(defaultEntropySize)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mnemonic = strings.Join(mnemonicS, " ")
|
||||
seed := bip39.MnemonicToSeed(mnemonic)
|
||||
mnemonic, err = bip39.NewMnemonic(entropy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
seed := bip39.NewSeed(mnemonic, defaultBIP39Passphrase)
|
||||
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
|
||||
return
|
||||
}
|
||||
@ -99,7 +120,7 @@ func (kb dbKeybase) CreateKey(name, mnemonic, passwd string) (info Info, err err
|
||||
err = fmt.Errorf("recovering only works with 12 word (fundraiser) or 24 word mnemonics, got: %v words", len(words))
|
||||
return
|
||||
}
|
||||
seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic)
|
||||
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -116,7 +137,7 @@ func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Inf
|
||||
err = fmt.Errorf("recovering only works with 12 word (fundraiser), got: %v words", len(words))
|
||||
return
|
||||
}
|
||||
seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic)
|
||||
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -124,12 +145,12 @@ func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Inf
|
||||
return
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Derive(name, mnemonic, passwd string, params hd.BIP44Params) (info Info, err error) {
|
||||
seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic)
|
||||
func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params) (info Info, err error) {
|
||||
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
info, err = kb.persistDerivedKey(seed, passwd, name, params.String())
|
||||
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String())
|
||||
|
||||
return
|
||||
}
|
||||
@ -179,11 +200,16 @@ func (kb dbKeybase) List() ([]Info, error) {
|
||||
iter := kb.db.Iterator(nil, nil)
|
||||
defer iter.Close()
|
||||
for ; iter.Valid(); iter.Next() {
|
||||
info, err := readInfo(iter.Value())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
key := string(iter.Key())
|
||||
|
||||
// need to include only keys in storage that have an info suffix
|
||||
if strings.HasSuffix(key, infoSuffix) {
|
||||
info, err := readInfo(iter.Value())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, info)
|
||||
}
|
||||
res = append(res, info)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
@ -192,11 +218,20 @@ func (kb dbKeybase) List() ([]Info, error) {
|
||||
func (kb dbKeybase) Get(name string) (Info, error) {
|
||||
bs := kb.db.Get(infoKey(name))
|
||||
if len(bs) == 0 {
|
||||
return nil, fmt.Errorf("Key %s not found", name)
|
||||
return nil, keyerror.NewErrKeyNotFound(name)
|
||||
}
|
||||
return readInfo(bs)
|
||||
}
|
||||
|
||||
func (kb dbKeybase) GetByAddress(address types.AccAddress) (Info, error) {
|
||||
ik := kb.db.Get(addrKey(address))
|
||||
if len(ik) == 0 {
|
||||
return nil, fmt.Errorf("key with address %s not found", address)
|
||||
}
|
||||
bs := kb.db.Get(ik)
|
||||
return readInfo(bs)
|
||||
}
|
||||
|
||||
// Sign signs the msg with the named key.
|
||||
// It returns an error if the key doesn't exist or the decryption fails.
|
||||
func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) {
|
||||
@ -212,7 +247,7 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t
|
||||
err = fmt.Errorf("private key not available")
|
||||
return
|
||||
}
|
||||
priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -224,9 +259,15 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t
|
||||
}
|
||||
case offlineInfo:
|
||||
linfo := info.(offlineInfo)
|
||||
fmt.Printf("Bytes to sign:\n%s", msg)
|
||||
_, err := fmt.Fprintf(os.Stderr, "Bytes to sign:\n%s", msg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
buf := bufio.NewReader(os.Stdin)
|
||||
fmt.Printf("\nEnter Amino-encoded signature:\n")
|
||||
_, err = fmt.Fprintf(os.Stderr, "\nEnter Amino-encoded signature:\n")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Will block until user inputs the signature
|
||||
signed, err := buf.ReadString('\n')
|
||||
if err != nil {
|
||||
@ -256,7 +297,7 @@ func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcr
|
||||
err = fmt.Errorf("private key not available")
|
||||
return nil, err
|
||||
}
|
||||
priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -273,7 +314,7 @@ func (kb dbKeybase) Export(name string) (armor string, err error) {
|
||||
if bz == nil {
|
||||
return "", fmt.Errorf("no key to export with name %s", name)
|
||||
}
|
||||
return armorInfoBytes(bz), nil
|
||||
return mintkey.ArmorInfoBytes(bz), nil
|
||||
}
|
||||
|
||||
// ExportPubKey returns public keys in ASCII armored format.
|
||||
@ -288,7 +329,7 @@ func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return armorPubKeyBytes(info.GetPubKey().Bytes()), nil
|
||||
return mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes()), nil
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Import(name string, armor string) (err error) {
|
||||
@ -296,7 +337,7 @@ func (kb dbKeybase) Import(name string, armor string) (err error) {
|
||||
if len(bz) > 0 {
|
||||
return errors.New("Cannot overwrite data for name " + name)
|
||||
}
|
||||
infoBytes, err := unarmorInfoBytes(armor)
|
||||
infoBytes, err := mintkey.UnarmorInfoBytes(armor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -312,7 +353,7 @@ func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
|
||||
if len(bz) > 0 {
|
||||
return errors.New("Cannot overwrite data for name " + name)
|
||||
}
|
||||
pubBytes, err := unarmorPubKeyBytes(armor)
|
||||
pubBytes, err := mintkey.UnarmorPubKeyBytes(armor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -337,10 +378,11 @@ func (kb dbKeybase) Delete(name, passphrase string) error {
|
||||
switch info.(type) {
|
||||
case localInfo:
|
||||
linfo := info.(localInfo)
|
||||
_, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
_, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kb.db.DeleteSync(addrKey(linfo.GetAddress()))
|
||||
kb.db.DeleteSync(infoKey(name))
|
||||
return nil
|
||||
case ledgerInfo:
|
||||
@ -348,9 +390,11 @@ func (kb dbKeybase) Delete(name, passphrase string) error {
|
||||
if passphrase != "yes" {
|
||||
return fmt.Errorf("enter 'yes' exactly to delete the key - this cannot be undone")
|
||||
}
|
||||
kb.db.DeleteSync(addrKey(info.GetAddress()))
|
||||
kb.db.DeleteSync(infoKey(name))
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -368,7 +412,7 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro
|
||||
switch info.(type) {
|
||||
case localInfo:
|
||||
linfo := info.(localInfo)
|
||||
key, err := unarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
|
||||
key, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -383,9 +427,14 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro
|
||||
}
|
||||
}
|
||||
|
||||
// CloseDB releases the lock and closes the storage backend.
|
||||
func (kb dbKeybase) CloseDB() {
|
||||
kb.db.Close()
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeLocalKey(priv tmcrypto.PrivKey, name, passphrase string) Info {
|
||||
// encrypt private key using passphrase
|
||||
privArmor := encryptArmorPrivKey(priv, passphrase)
|
||||
privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase)
|
||||
// make Info
|
||||
pub := priv.PubKey()
|
||||
info := newLocalInfo(name, pub, privArmor)
|
||||
@ -407,9 +456,16 @@ func (kb dbKeybase) writeOfflineKey(pub tmcrypto.PubKey, name string) Info {
|
||||
|
||||
func (kb dbKeybase) writeInfo(info Info, name string) {
|
||||
// write the info by key
|
||||
kb.db.SetSync(infoKey(name), writeInfo(info))
|
||||
key := infoKey(name)
|
||||
kb.db.SetSync(key, writeInfo(info))
|
||||
// store a pointer to the infokey by address for fast lookup
|
||||
kb.db.SetSync(addrKey(info.GetAddress()), key)
|
||||
}
|
||||
|
||||
func addrKey(address types.AccAddress) []byte {
|
||||
return []byte(fmt.Sprintf("%s.%s", address.String(), addressSuffix))
|
||||
}
|
||||
|
||||
func infoKey(name string) []byte {
|
||||
return []byte(fmt.Sprintf("%s.info", name))
|
||||
return []byte(fmt.Sprintf("%s.%s", name, infoSuffix))
|
||||
}
|
||||
|
||||
@ -4,24 +4,29 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
)
|
||||
|
||||
func init() {
|
||||
BcryptSecurityParameter = 1
|
||||
mintkey.BcryptSecurityParameter = 1
|
||||
}
|
||||
|
||||
// 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(
|
||||
dbm.NewMemDB(),
|
||||
db,
|
||||
)
|
||||
|
||||
algo := Secp256k1
|
||||
@ -51,6 +56,12 @@ func TestKeyManagement(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
_, err = cstore.Get(n3)
|
||||
require.NotNil(t, err)
|
||||
_, err = cstore.GetByAddress(accAddr(i2))
|
||||
require.NoError(t, err)
|
||||
addr, err := types.AccAddressFromBech32("cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t")
|
||||
require.NoError(t, err)
|
||||
_, err = cstore.GetByAddress(addr)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// list shows them in order
|
||||
keyS, err := cstore.List()
|
||||
@ -92,6 +103,11 @@ func TestKeyManagement(t *testing.T) {
|
||||
keyS, err = cstore.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(keyS))
|
||||
|
||||
// addr cache gets nuked
|
||||
err = cstore.Delete(n2, p2)
|
||||
require.NoError(t, err)
|
||||
require.False(t, db.Has(addrKey(i2.GetAddress())))
|
||||
}
|
||||
|
||||
// TestSignVerify does some detailed checks on how we sign and validate
|
||||
@ -329,7 +345,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, 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())
|
||||
@ -387,3 +403,7 @@ func ExampleNew() {
|
||||
// Carl
|
||||
// signed by Bob
|
||||
}
|
||||
|
||||
func accAddr(info Info) types.AccAddress {
|
||||
return (types.AccAddress)(info.GetPubKey().Address())
|
||||
}
|
||||
|
||||
81
crypto/keys/keyerror/errors.go
Normal file
81
crypto/keys/keyerror/errors.go
Normal file
@ -0,0 +1,81 @@
|
||||
package keyerror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
codeKeyNotFound = 1
|
||||
codeWrongPassword = 2
|
||||
)
|
||||
|
||||
type keybaseError interface {
|
||||
error
|
||||
Code() int
|
||||
}
|
||||
|
||||
type errKeyNotFound struct {
|
||||
code int
|
||||
name string
|
||||
}
|
||||
|
||||
func (e errKeyNotFound) Code() int {
|
||||
return e.code
|
||||
}
|
||||
|
||||
func (e errKeyNotFound) Error() string {
|
||||
return fmt.Sprintf("Key %s not found", e.name)
|
||||
}
|
||||
|
||||
// NewErrKeyNotFound returns a standardized error reflecting that the specified key doesn't exist
|
||||
func NewErrKeyNotFound(name string) error {
|
||||
return errKeyNotFound{
|
||||
code: codeKeyNotFound,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// IsErrKeyNotFound returns true if the given error is errKeyNotFound
|
||||
func IsErrKeyNotFound(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if keyErr, ok := err.(keybaseError); ok {
|
||||
if keyErr.Code() == codeKeyNotFound {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type errWrongPassword struct {
|
||||
code int
|
||||
}
|
||||
|
||||
func (e errWrongPassword) Code() int {
|
||||
return e.code
|
||||
}
|
||||
|
||||
func (e errWrongPassword) Error() string {
|
||||
return fmt.Sprintf("Ciphertext decryption failed")
|
||||
}
|
||||
|
||||
// NewErrWrongPassword returns a standardized error reflecting that the specified password is wrong
|
||||
func NewErrWrongPassword() error {
|
||||
return errWrongPassword{
|
||||
code: codeWrongPassword,
|
||||
}
|
||||
}
|
||||
|
||||
// IsErrWrongPassword returns true if the given error is errWrongPassword
|
||||
func IsErrWrongPassword(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if keyErr, ok := err.(keybaseError); ok {
|
||||
if keyErr.Code() == codeWrongPassword {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user