Merge pull request #2603 from cosmos/release/v0.25.0

Release 0.25.0
This commit is contained in:
Jae Kwon 2018-10-30 17:49:47 -07:00 committed by GitHub
commit 7c5c14f289
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
641 changed files with 113604 additions and 17130 deletions

View File

@ -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

View File

@ -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
View File

@ -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

View File

@ -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

View File

@ -1,2 +0,0 @@
* @jaekwon
* @ebuchman

View File

@ -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
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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
View 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
View 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]
}

View File

@ -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
View 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
View 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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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
View 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")
}

View File

@ -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
View 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
View 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("-------------------------------------")
}

View File

@ -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")
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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
View 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
View 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)
}

View 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

View File

@ -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
}

View File

@ -0,0 +1,3 @@
package statik
//This just for fixing the error in importing empty github.com/cosmos/cosmos-sdk/client/lcd/statik

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -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>

View 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>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"swagger-ui.css","sourceRoot":""}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -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{})
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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")
}

View File

@ -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)
}
}

View File

@ -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
View 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)
}

View File

@ -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
}

View 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)
})
}
}

View File

@ -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.

View File

@ -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)
}

View File

@ -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()

View 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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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), &params)
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
}

View File

@ -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))
}

View File

@ -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(

View File

@ -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,
}

View File

@ -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
View 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
View 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
View 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
View 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
}

View File

@ -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

View File

@ -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()
}

View File

@ -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 | |
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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"
)

View File

@ -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)

View File

@ -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[:]))

View File

@ -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))
}

View File

@ -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())
}

View 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