Merge branch 'develop'
This commit is contained in:
commit
3f44589861
|
@ -8,6 +8,7 @@ defaults: &defaults
|
|||
GOBIN: /tmp/workspace/bin
|
||||
|
||||
jobs:
|
||||
|
||||
setup_dependencies:
|
||||
<<: *defaults
|
||||
steps:
|
||||
|
@ -27,12 +28,6 @@ jobs:
|
|||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make get_vendor_deps
|
||||
- run:
|
||||
name: linter
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
go get -u github.com/tendermint/lint/golint
|
||||
go get -u github.com/alecthomas/gometalinter
|
||||
- run:
|
||||
name: binaries
|
||||
command: |
|
||||
|
@ -55,7 +50,7 @@ jobs:
|
|||
|
||||
lint:
|
||||
<<: *defaults
|
||||
parallelism: 4
|
||||
parallelism: 1
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
|
@ -63,28 +58,17 @@ jobs:
|
|||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
name: Get metalinter
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make get_tools
|
||||
- run:
|
||||
name: Lint source
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
gometalinter --disable-all --enable='golint' --vendor ./...
|
||||
|
||||
test_unit:
|
||||
<<: *defaults
|
||||
parallelism: 4
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
name: Test unit
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make test_unit
|
||||
|
||||
make test_lint
|
||||
|
||||
test_cli:
|
||||
<<: *defaults
|
||||
parallelism: 1
|
||||
|
@ -100,10 +84,10 @@ jobs:
|
|||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make test_cli
|
||||
|
||||
|
||||
test_cover:
|
||||
<<: *defaults
|
||||
parallelism: 4
|
||||
parallelism: 2
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
|
@ -111,6 +95,7 @@ jobs:
|
|||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run: mkdir -p /tmp/logs
|
||||
- run:
|
||||
name: Run tests
|
||||
command: |
|
||||
|
@ -119,15 +104,18 @@ jobs:
|
|||
for pkg in $(go list github.com/cosmos/cosmos-sdk/... | grep -v /vendor/ | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test | circleci tests split --split-by=timings); do
|
||||
id=$(basename "$pkg")
|
||||
|
||||
go test -timeout 5m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg"
|
||||
GOCACHE=off go test -v -timeout 8m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"
|
||||
done
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- "profiles/*"
|
||||
- store_artifacts:
|
||||
path: /tmp/logs
|
||||
|
||||
upload_coverage:
|
||||
<<: *defaults
|
||||
parallelism: 1
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
|
@ -157,9 +145,6 @@ workflows:
|
|||
- test_cli:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- test_unit:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- test_cover:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# CODEOWNERS: https://help.github.com/articles/about-codeowners/
|
||||
|
||||
# Primary repo maintainers
|
||||
* @ebuchman @rigelrozanski @cwgoes
|
||||
|
||||
# Precious documentation
|
||||
/docs/ @zramsay @jolesbi
|
|
@ -10,3 +10,4 @@ v If a checkbox is n/a - please still include it but + a little note why
|
|||
* [ ] Updated CHANGELOG.md
|
||||
* [ ] Updated Gaia/Examples
|
||||
* [ ] 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)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*.swp
|
||||
*.swo
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
# Build
|
||||
vendor
|
||||
|
@ -15,6 +16,7 @@ docs/_build
|
|||
examples/basecoin/app/data
|
||||
baseapp/data/*
|
||||
client/lcd/keys/*
|
||||
mytestnet
|
||||
|
||||
# Testing
|
||||
coverage.txt
|
||||
|
@ -26,5 +28,9 @@ profile.out
|
|||
*.log
|
||||
vagrant
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# Graphviz
|
||||
dependency-graph.png
|
||||
|
|
147
CHANGELOG.md
147
CHANGELOG.md
|
@ -1,14 +1,139 @@
|
|||
# Changelog
|
||||
|
||||
## 0.19.1
|
||||
## 0.20.0
|
||||
|
||||
*June 25, 2018*
|
||||
*July 10th, 2018*
|
||||
|
||||
* Update to Tendermint v0.21.0 (fixes websocket memory leak and a few other
|
||||
things)
|
||||
BREAKING CHANGES
|
||||
* msg.GetSignBytes() returns sorted JSON (by key)
|
||||
* msg.GetSignBytes() field changes
|
||||
* `msg_bytes` -> `msgs`
|
||||
* `fee_bytes` -> `fee`
|
||||
* Update Tendermint to v0.22.2
|
||||
* Default ports changed from 466xx to 266xx
|
||||
* Amino JSON uses type names instead of prefix bytes
|
||||
* ED25519 addresses are the first 20-bytes of the SHA256 of the raw 32-byte
|
||||
pubkey (Instead of RIPEMD160)
|
||||
* go-crypto, abci, tmlibs have been merged into Tendermint
|
||||
* The keys sub-module is now in the SDK
|
||||
* Various other fixes
|
||||
* [auth] Signers of a transaction now only sign over their own account and sequence number
|
||||
* [auth] Removed MsgChangePubKey
|
||||
* [auth] Removed SetPubKey from account mapper
|
||||
* [auth] AltBytes renamed to Memo, now a string, max 100 characters, costs a bit of gas
|
||||
* [baseapp] NewBaseApp now takes option functions as parameters
|
||||
* [types] `GetMsg()` -> `GetMsgs()` as txs wrap many messages
|
||||
* [types] Removed GetMemo from Tx (it is still on StdTx)
|
||||
* [types] renamed rational.Evaluate to rational.Round{Int64, Int}
|
||||
* [types] Renamed `sdk.Address` to `sdk.AccAddress`/`sdk.ValAddress`
|
||||
* [types] `sdk.AccAddress`/`sdk.ValAddress` natively marshals to Bech32 in String, Sprintf (when used with `%s`), and MarshalJSON
|
||||
* [keys] Keybase and Ledger support from go-crypto merged into the SDK in the `crypto` folder
|
||||
* [cli] Rearranged commands under subcommands
|
||||
* [x/slashing] Update slashing for unbonding period
|
||||
* Slash according to power at time of infraction instead of power at
|
||||
time of discovery
|
||||
* Iterate through unbonding delegations & redelegations which contributed
|
||||
to an infraction, slash them proportional to their stake at the time
|
||||
* Add REST endpoint to unrevoke a validator previously revoked for downtime
|
||||
* Add REST endpoint to retrieve liveness signing information for a validator
|
||||
* [x/stake] Remove Tick and add EndBlocker
|
||||
* [x/stake] most index keys nolonger hold a value - inputs are rearranged to form the desired key
|
||||
* [x/stake] store-value for delegation, validator, ubd, and red do not hold duplicate information contained store-key
|
||||
* [x/stake] Introduce concept of unbonding for delegations and validators
|
||||
* `gaiacli stake unbond` replaced with `gaiacli stake begin-unbonding`
|
||||
* Introduced:
|
||||
* `gaiacli stake complete-unbonding`
|
||||
* `gaiacli stake begin-redelegation`
|
||||
* `gaiacli stake complete-redelegation`
|
||||
* [lcd] Switch key creation output to return bech32
|
||||
* [lcd] Removed shorthand CLI flags (`a`, `c`, `n`, `o`)
|
||||
* [gaiad] genesis transactions now use bech32 addresses / pubkeys
|
||||
|
||||
BREAKING CHANGES:
|
||||
* Changes the default ports from `4665X` to `2665X`
|
||||
DEPRECATED
|
||||
* [cli] Deprecated `--name` flag in commands that send txs, in favor of `--from`
|
||||
|
||||
FEATURES
|
||||
* [x/gov] Implemented MVP
|
||||
* Supported proposal types: just binary (pass/fail) TextProposals for now
|
||||
* Proposals need deposits to be votable; deposits are burned if proposal fails
|
||||
* Delegators delegate votes to validator by default but can override (for their stake)
|
||||
* [gaiacli] Ledger support added
|
||||
- You can now use a Ledger with `gaiacli --ledger` for all key-related commands
|
||||
- Ledger keys can be named and tracked locally in the key DB
|
||||
* [gaiacli] You can now attach a simple text-only memo to any transaction, with the `--memo` flag
|
||||
* [gaiacli] added the following flags for commands that post transactions to the chain:
|
||||
* async -- send the tx without waiting for a tendermint response
|
||||
* json -- return the output in json format for increased readability
|
||||
* print-response -- return the tx response. (includes fields like gas cost)
|
||||
* [lcd] Queried TXs now include the tx hash to identify each tx
|
||||
* [mockapp] CompleteSetup() no longer takes a testing parameter
|
||||
* [x/bank] Add benchmarks for signing and delivering a block with a single bank transaction
|
||||
* Run with `cd x/bank && go test --bench=.`
|
||||
* [tools] make get_tools installs tendermint's linter, and gometalinter
|
||||
* [tools] Switch gometalinter to the stable version
|
||||
* [tools] Add the following linters
|
||||
* misspell
|
||||
* gofmt
|
||||
* go vet -composites=false
|
||||
* unconvert
|
||||
* ineffassign
|
||||
* errcheck
|
||||
* unparam
|
||||
* gocyclo
|
||||
* [tools] Added `make format` command to automate fixing misspell and gofmt errors.
|
||||
* [server] Default config now creates a profiler at port 6060, and increase p2p send/recv rates
|
||||
* [types] Switches internal representation of Int/Uint/Rat to use pointers
|
||||
* [types] Added MinInt and MinUint functions
|
||||
* [gaiad] `unsafe_reset_all` now resets addrbook.json
|
||||
* [democoin] add x/oracle, x/assoc
|
||||
* [tests] created a randomized testing framework.
|
||||
- Currently bank has limited functionality in the framework
|
||||
- Auth has its invariants checked within the framework
|
||||
* [x/stake] Allow validator to be created with starting delegation by a third-party delegator on behalf of validator.
|
||||
* [tests] Add WaitForNextNBlocksTM helper method
|
||||
* [keys] New keys now have 24 word recovery keys, for heightened security
|
||||
|
||||
IMPROVEMENTS
|
||||
* [x/bank] Now uses go-wire codec instead of 'encoding/json'
|
||||
* [x/auth] Now uses go-wire codec instead of 'encoding/json'
|
||||
* revised use of endblock and beginblock
|
||||
* [stake] module reorganized to include `types` and `keeper` package
|
||||
* [stake] keeper always loads the store (instead passing around which doesn't really boost efficiency)
|
||||
* [stake] edit-validator changes now can use the keyword [do-not-modify] to not modify unspecified `--flag` (aka won't set them to `""` value)
|
||||
* [stake] offload more generic functionality from the handler into the keeper
|
||||
* [types] added common tag constants
|
||||
* [keys] improve error message when deleting non-existent key
|
||||
* [gaiacli] improve error messages on `send` and `account` commands
|
||||
* added contributing guidelines
|
||||
* [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/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)
|
||||
* [x/stake] bond count was counting revoked validators as bonded, fixed
|
||||
* [gaia] Added self delegation for validators in the genesis creation
|
||||
* [lcd] tests now don't depend on raw json text
|
||||
* 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
|
||||
* [x/stake] fix bug when unbonding/redelegating using `--shares-percent`
|
||||
* \#1010 - two validators can't bond with the same pubkey anymore
|
||||
|
||||
|
||||
## 0.19.0
|
||||
|
@ -18,26 +143,34 @@ BREAKING CHANGES:
|
|||
BREAKING CHANGES
|
||||
* msg.GetSignBytes() now returns bech32-encoded addresses in all cases
|
||||
* [lcd] REST end-points now include gas
|
||||
* sdk.Coin now uses sdk.Int, a big.Int wrapper with 256bit range cap
|
||||
|
||||
FEATURES
|
||||
* [x/auth] Added AccountNumbers to BaseAccount and StdTxs to allow for replay protection with account pruning
|
||||
* [lcd] added an endpoint to query for the SDK version of the connected node
|
||||
|
||||
IMPROVEMENTS
|
||||
* export command now writes current validator set for Tendermint
|
||||
* [tests] Application module tests now use a mock application
|
||||
* [gaiacli] Fix error message when account isn't found when running gaiacli account
|
||||
* [lcd] refactored to eliminate use of global variables, and interdependent tests
|
||||
* [tests] Added testnet command to gaiad
|
||||
* [tests] Added localnet targets to Makefile
|
||||
* [x/stake] More stake tests added to test ByPower index
|
||||
|
||||
FIXES
|
||||
* Fixes consensus fault on testnet - see postmortem [here](https://github.com/cosmos/cosmos-sdk/issues/1197#issuecomment-396823021)
|
||||
* [x/stake] bonded inflation removed, non-bonded inflation partially implemented
|
||||
* [x/stake] bonded inflation removed, non-bonded inflation partially implemented
|
||||
* [lcd] Switch to bech32 for addresses on all human readable inputs and outputs
|
||||
* [lcd] fixed tx indexing/querying
|
||||
* [cli] Added `--gas` flag to specify transaction gas limit
|
||||
* [gaia] Registered slashing message handler
|
||||
* [x/slashing] Set signInfo.StartHeight correctly for newly bonded validators
|
||||
|
||||
FEATURES
|
||||
* [docs] Reorganize documentation
|
||||
* [docs] Update staking spec, create WIP spec for slashing, and fees
|
||||
|
||||
## 0.18.0
|
||||
|
||||
*June 9, 2018*
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
# Contributing
|
||||
|
||||
Thank you for considering making contributions to Cosmos-SDK and related repositories! Start by taking a look at this [coding repo](https://github.com/tendermint/coding) for overall information on repository workflow and standards.
|
||||
|
||||
Please follow standard github best practices: fork the repo, branch from the tip of develop, make some commits, and submit a pull request to develop. See the [open issues](https://github.com/cosmos/cosmos-sdk/issues) for things we need help with!
|
||||
|
||||
Please make sure to use `gofmt` before every commit - the easiest way to do this is have your editor run it for you upon saving a file. Additionally please ensure that your code is lint compliant by running `make lint`
|
||||
|
||||
Looking for a good place to start contributing? How about checking out some [good first issues](https://github.com/cosmos/cosmos-sdk/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
|
||||
|
||||
## Forking
|
||||
|
||||
Please note that Go requires code to live under absolute paths, which complicates forking.
|
||||
While my fork lives at `https://github.com/rigeyrigerige/cosmos-sdk`,
|
||||
the code should never exist at `$GOPATH/src/github.com/rigeyrigerige/cosmos-sdk`.
|
||||
Instead, we use `git remote` to add the fork as a new remote for the original repo,
|
||||
`$GOPATH/src/github.com/cosmos/cosmos-sdk `, and do all the work there.
|
||||
|
||||
For instance, to create a fork and work on a branch of it, I would:
|
||||
|
||||
* Create the fork on github, using the fork button.
|
||||
* Go to the original repo checked out locally (i.e. `$GOPATH/src/github.com/cosmos/cosmos-sdk`)
|
||||
* `git remote rename origin upstream`
|
||||
* `git remote add origin git@github.com:ebuchman/basecoin.git`
|
||||
|
||||
Now `origin` refers to my fork and `upstream` refers to the Cosmos-SDK version.
|
||||
So I can `git push -u origin master` to update my fork, and make pull requests to Cosmos-SDK from there.
|
||||
Of course, replace `ebuchman` with your git handle.
|
||||
|
||||
To pull in updates from the origin repo, run
|
||||
|
||||
* `git fetch upstream`
|
||||
* `git rebase upstream/master` (or whatever branch you want)
|
||||
|
||||
Please don't make Pull Requests to `master`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
We use [dep](https://github.com/golang/dep) to manage dependencies.
|
||||
|
||||
That said, the master branch of every Cosmos repository should just build
|
||||
with `go get`, which means they should be kept up-to-date with their
|
||||
dependencies so we can get away with telling people they can just `go get` our
|
||||
software.
|
||||
|
||||
Since some dependencies are not under our control, a third party may break our
|
||||
build, in which case we can fall back on `dep ensure` (or `make
|
||||
get_vendor_deps`). Even for dependencies under our control, dep helps us to
|
||||
keep multiple repos in sync as they evolve. Anything with an executable, such
|
||||
as apps, tools, and the core, should use dep.
|
||||
|
||||
Run `dep status` to get a list of vendor dependencies that may not be
|
||||
up-to-date.
|
||||
|
||||
## Testing
|
||||
|
||||
All repos should be hooked up to [CircleCI](https://circleci.com/).
|
||||
|
||||
If they have `.go` files in the root directory, they will be automatically
|
||||
tested by circle using `go test -v -race ./...`. If not, they will need a
|
||||
`circle.yml`. Ideally, every repo has a `Makefile` that defines `make test` and
|
||||
includes its continuous integration status using a badge in the `README.md`.
|
||||
|
||||
## Branching Model and Release
|
||||
|
||||
User-facing repos should adhere to the branching model: http://nvie.com/posts/a-successful-git-branching-model/.
|
||||
That is, these repos should be well versioned, and any merge to master requires a version bump and tagged release.
|
||||
|
||||
Libraries need not follow the model strictly, but would be wise to.
|
||||
|
||||
The SDK utilizes [semantic versioning](https://semver.org/).
|
||||
|
||||
### Development Procedure:
|
||||
- the latest state of development is on `develop`
|
||||
- `develop` must never fail `make test` or `make test_cli`
|
||||
- `develop` should not fail `make test_lint`
|
||||
- no --force onto `develop` (except when reverting a broken commit, which should seldom happen)
|
||||
- create a development branch either on github.com/cosmos/cosmos-sdk, or your fork (using `git remote add origin`)
|
||||
- before submitting a pull request, begin `git rebase` on top of `develop`
|
||||
|
||||
### Pull Merge Procedure:
|
||||
- ensure pull branch is rebased on develop
|
||||
- run `make test` and `make test_cli` to ensure that all tests pass
|
||||
- merge pull request
|
||||
- push master may request that pull requests be rebased on top of `unstable`
|
||||
|
||||
### Release Procedure:
|
||||
- start on `develop`
|
||||
- prepare changelog/release issue
|
||||
- bump versions
|
||||
- push to release-vX.X.X to run CI
|
||||
- merge to master
|
||||
- merge master back to develop
|
||||
|
||||
### Hotfix Procedure:
|
||||
- start on `master`
|
||||
- checkout a new branch named hotfix-vX.X.X
|
||||
- make the required changes
|
||||
- these changes should be small and an absolute necessity
|
||||
- add a note to CHANGELOG.md
|
||||
- bump versions
|
||||
- push to hotfix-vX.X.X to run the extended integration tests on the CI
|
||||
- merge hotfix-vX.X.X to master
|
||||
- merge hotfix-vX.X.X to develop
|
||||
- delete the hotfix-vX.X.X branch
|
45
Dockerfile
45
Dockerfile
|
@ -1,34 +1,35 @@
|
|||
# Simple usage with a mounted data directory:
|
||||
# > docker build -t gaia .
|
||||
# > docker run -v $HOME/.gaiad:/root/.gaiad gaia init
|
||||
# > docker run -v $HOME/.gaiad:/root/.gaiad gaia start
|
||||
|
||||
FROM alpine:edge
|
||||
# > docker run -it -p 46657:46657 -p 46656:46656 -v ~/.gaiad:/root/.gaiad -v ~/.gaiacli:/root/.gaiacli gaia gaiad init
|
||||
# > docker run -it -p 46657:46657 -p 46656:46656 -v ~/.gaiad:/root/.gaiad -v ~/.gaiacli:/root/.gaiacli gaia gaiad start
|
||||
FROM golang:alpine AS build-env
|
||||
|
||||
# Set up dependencies
|
||||
ENV PACKAGES go glide make git libc-dev bash
|
||||
ENV PACKAGES make git libc-dev bash gcc linux-headers eudev-dev
|
||||
|
||||
# Set up GOPATH & PATH
|
||||
|
||||
ENV GOPATH /root/go
|
||||
ENV BASE_PATH $GOPATH/src/github.com/cosmos
|
||||
ENV REPO_PATH $BASE_PATH/cosmos-sdk
|
||||
ENV WORKDIR /cosmos/
|
||||
ENV PATH $GOPATH/bin:$PATH
|
||||
|
||||
# Link expected Go repo path
|
||||
|
||||
RUN mkdir -p $WORKDIR $GOPATH/pkg $ $GOPATH/bin $BASE_PATH
|
||||
# Set working directory for the build
|
||||
WORKDIR /go/src/github.com/cosmos/cosmos-sdk
|
||||
|
||||
# Add source files
|
||||
|
||||
ADD . $REPO_PATH
|
||||
COPY . .
|
||||
|
||||
# Install minimum necessary dependencies, build Cosmos SDK, remove packages
|
||||
RUN apk add --no-cache $PACKAGES && \
|
||||
cd $REPO_PATH && make get_tools && make get_vendor_deps && make build && make install && \
|
||||
apk del $PACKAGES
|
||||
make get_tools && \
|
||||
make get_vendor_deps && \
|
||||
make build && \
|
||||
make install
|
||||
|
||||
# Set entrypoint
|
||||
# Final image
|
||||
FROM alpine:edge
|
||||
|
||||
ENTRYPOINT ["gaiad"]
|
||||
# Install ca-certificates
|
||||
RUN apk add --update ca-certificates
|
||||
WORKDIR /root
|
||||
|
||||
# Copy over binaries from the build-env
|
||||
COPY --from=build-env /go/bin/gaiad /usr/bin/gaiad
|
||||
COPY --from=build-env /go/bin/gaiacli /usr/bin/gaiacli
|
||||
|
||||
# Run gaiad by default, omit entrypoint to ease using container with gaiacli
|
||||
CMD ["gaiad"]
|
||||
|
|
|
@ -1,23 +1,41 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/bartekn/go-bip39"
|
||||
packages = ["."]
|
||||
revision = "a05967ea095d81c8fe4833776774cfaff8e5036c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/beorn7/perks"
|
||||
packages = ["quantile"]
|
||||
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/bgentry/speakeasy"
|
||||
packages = ["."]
|
||||
revision = "4aabc24848ce5fd31929f7d1e4ea74d3709c14cd"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/brejski/hid"
|
||||
packages = ["."]
|
||||
revision = "06112dcfcc50a7e0e4fd06e17f9791e788fdaafc"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/btcsuite/btcd"
|
||||
packages = ["btcec"]
|
||||
revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64"
|
||||
revision = "fdfc19097e7ac6b57035062056f5b7b4638b8898"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/btcsuite/btcutil"
|
||||
packages = ["bech32"]
|
||||
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
|
||||
revision = "ab6388e0c60ae4834a1f57511e20c17b5f78be4b"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
|
@ -42,7 +60,11 @@
|
|||
packages = [
|
||||
"log",
|
||||
"log/level",
|
||||
"log/term"
|
||||
"log/term",
|
||||
"metrics",
|
||||
"metrics/discard",
|
||||
"metrics/internal/lv",
|
||||
"metrics/prometheus"
|
||||
]
|
||||
revision = "4dc7be5d2d12881735283bcab7352178e190fc71"
|
||||
version = "v0.6.0"
|
||||
|
@ -81,8 +103,8 @@
|
|||
"ptypes/duration",
|
||||
"ptypes/timestamp"
|
||||
]
|
||||
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
|
||||
version = "v1.0.0"
|
||||
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
@ -125,12 +147,6 @@
|
|||
]
|
||||
revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/howeyc/crc16"
|
||||
packages = ["."]
|
||||
revision = "2b2a61e366a66d3efb279e46176e7291001e0354"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
packages = ["."]
|
||||
|
@ -161,6 +177,12 @@
|
|||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/matttproud/golang_protobuf_extensions"
|
||||
packages = ["pbutil"]
|
||||
revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mitchellh/mapstructure"
|
||||
|
@ -185,6 +207,42 @@
|
|||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/prometheus/client_golang"
|
||||
packages = [
|
||||
"prometheus",
|
||||
"prometheus/promhttp"
|
||||
]
|
||||
revision = "ae27198cdd90bf12cd134ad79d1366a6cf49f632"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/prometheus/client_model"
|
||||
packages = ["go"]
|
||||
revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/prometheus/common"
|
||||
packages = [
|
||||
"expfmt",
|
||||
"internal/bitbucket.org/ww/goautoneg",
|
||||
"model"
|
||||
]
|
||||
revision = "7600349dcfe1abd18d72d3a1770870d9800a7801"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/prometheus/procfs"
|
||||
packages = [
|
||||
".",
|
||||
"internal/util",
|
||||
"nfs",
|
||||
"xfs"
|
||||
]
|
||||
revision = "ae68e2d4c00fed4943b5f6698d504a5fe083da8a"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/rcrowley/go-metrics"
|
||||
|
@ -209,8 +267,8 @@
|
|||
[[projects]]
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
|
||||
version = "v0.0.3"
|
||||
revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b"
|
||||
version = "v0.0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
@ -227,8 +285,8 @@
|
|||
[[projects]]
|
||||
name = "github.com/spf13/viper"
|
||||
packages = ["."]
|
||||
revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736"
|
||||
version = "v1.0.2"
|
||||
revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/stretchr/testify"
|
||||
|
@ -236,8 +294,8 @@
|
|||
"assert",
|
||||
"require"
|
||||
]
|
||||
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
|
||||
version = "v1.2.2"
|
||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
@ -256,19 +314,7 @@
|
|||
"leveldb/table",
|
||||
"leveldb/util"
|
||||
]
|
||||
revision = "0d5a0ceb10cf9ab89fdd744cc8c50a83134f6697"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/abci"
|
||||
packages = [
|
||||
"client",
|
||||
"example/code",
|
||||
"example/kvstore",
|
||||
"server",
|
||||
"types"
|
||||
]
|
||||
revision = "198dccf0ddfd1bb176f87657e3286a05a6ed9540"
|
||||
version = "v0.12.0"
|
||||
revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
@ -283,40 +329,42 @@
|
|||
[[projects]]
|
||||
name = "github.com/tendermint/go-amino"
|
||||
packages = ["."]
|
||||
revision = "ed62928576cfcaf887209dc96142cd79cdfff389"
|
||||
version = "0.9.9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/go-crypto"
|
||||
packages = [
|
||||
".",
|
||||
"keys",
|
||||
"keys/bcrypt",
|
||||
"keys/words",
|
||||
"keys/words/wordlist"
|
||||
]
|
||||
revision = "915416979bf70efa4bcbf1c6cd5d64c5fff9fc19"
|
||||
version = "v0.6.2"
|
||||
revision = "2106ca61d91029c931fd54968c2bb02dc96b1412"
|
||||
version = "0.10.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/iavl"
|
||||
packages = [
|
||||
".",
|
||||
"sha256truncated"
|
||||
]
|
||||
revision = "c9206995e8f948e99927f5084a88a7e94ca256da"
|
||||
version = "v0.8.0-rc0"
|
||||
packages = ["."]
|
||||
revision = "35f66e53d9b01e83b30de68b931f54b2477a94c9"
|
||||
version = "v0.9.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/tendermint"
|
||||
packages = [
|
||||
"abci/client",
|
||||
"abci/example/code",
|
||||
"abci/example/kvstore",
|
||||
"abci/server",
|
||||
"abci/types",
|
||||
"blockchain",
|
||||
"cmd/tendermint/commands",
|
||||
"config",
|
||||
"consensus",
|
||||
"consensus/types",
|
||||
"crypto",
|
||||
"crypto/merkle",
|
||||
"crypto/tmhash",
|
||||
"evidence",
|
||||
"libs/autofile",
|
||||
"libs/bech32",
|
||||
"libs/cli",
|
||||
"libs/cli/flags",
|
||||
"libs/clist",
|
||||
"libs/common",
|
||||
"libs/db",
|
||||
"libs/events",
|
||||
"libs/flowrate",
|
||||
"libs/log",
|
||||
"libs/pubsub",
|
||||
"libs/pubsub/query",
|
||||
"lite",
|
||||
|
@ -347,26 +395,13 @@
|
|||
"types",
|
||||
"version"
|
||||
]
|
||||
revision = "46369a1ab76f274ab47179c4176221842b8207b4"
|
||||
version = "v0.21.0"
|
||||
revision = "5ff65274b84ea905787a48512cc3124385bddf2f"
|
||||
version = "v0.22.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/tmlibs"
|
||||
packages = [
|
||||
"autofile",
|
||||
"bech32",
|
||||
"cli",
|
||||
"cli/flags",
|
||||
"clist",
|
||||
"common",
|
||||
"db",
|
||||
"flowrate",
|
||||
"log",
|
||||
"merkle",
|
||||
"merkle/tmhash"
|
||||
]
|
||||
revision = "49596e0a1f48866603813df843c9409fc19805c6"
|
||||
version = "v0.9.0"
|
||||
name = "github.com/zondax/ledger-goclient"
|
||||
packages = ["."]
|
||||
revision = "065cbf938a16f20335c40cfe180f9cd4955c6a5a"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
@ -379,6 +414,7 @@
|
|||
"nacl/secretbox",
|
||||
"openpgp/armor",
|
||||
"openpgp/errors",
|
||||
"pbkdf2",
|
||||
"poly1305",
|
||||
"ripemd160",
|
||||
"salsa20/salsa"
|
||||
|
@ -395,15 +431,16 @@
|
|||
"http2/hpack",
|
||||
"idna",
|
||||
"internal/timeseries",
|
||||
"netutil",
|
||||
"trace"
|
||||
]
|
||||
revision = "afe8f62b1d6bbd81f31868121a50b06d8188e1f9"
|
||||
revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
revision = "a200a19cb90b19de298170992778b1fda7217bd6"
|
||||
revision = "1b2967e3c290b7c545b3db0deeda16e9be4f98a2"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/text"
|
||||
|
@ -427,18 +464,23 @@
|
|||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "google.golang.org/genproto"
|
||||
packages = ["googleapis/rpc/status"]
|
||||
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200"
|
||||
revision = "e92b116572682a5b432ddd840aeaba2a559eeff1"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/grpc"
|
||||
packages = [
|
||||
".",
|
||||
"balancer",
|
||||
"balancer/base",
|
||||
"balancer/roundrobin",
|
||||
"codes",
|
||||
"connectivity",
|
||||
"credentials",
|
||||
"encoding",
|
||||
"encoding/proto",
|
||||
"grpclb/grpc_lb_v1/messages",
|
||||
"grpclog",
|
||||
"internal",
|
||||
|
@ -447,13 +489,15 @@
|
|||
"naming",
|
||||
"peer",
|
||||
"resolver",
|
||||
"resolver/dns",
|
||||
"resolver/passthrough",
|
||||
"stats",
|
||||
"status",
|
||||
"tap",
|
||||
"transport"
|
||||
]
|
||||
revision = "5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e"
|
||||
version = "v1.7.5"
|
||||
revision = "d11072e7ca9811b1100b80ca0269ac831f06d024"
|
||||
version = "v1.11.3"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/yaml.v2"
|
||||
|
@ -464,6 +508,6 @@
|
|||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "3a437ed2c22314c3762584ff52c76a58916fd9e9fef00035aa01ae65bce08637"
|
||||
inputs-digest = "33d51b55781a1579d2fde1bba70077b49c5804862ea56c72136f013c8498e0c6"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
41
Gopkg.toml
41
Gopkg.toml
|
@ -28,18 +28,14 @@
|
|||
name = "github.com/bgentry/speakeasy"
|
||||
version = "~0.1.0"
|
||||
|
||||
[[constraint]]
|
||||
[[override]]
|
||||
name = "github.com/golang/protobuf"
|
||||
version = "~1.0.0"
|
||||
version = "=1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mattn/go-isatty"
|
||||
version = "~0.0.3"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/pkg/errors"
|
||||
version = "~0.8.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/cobra"
|
||||
version = "~0.0.1"
|
||||
|
@ -48,38 +44,33 @@
|
|||
name = "github.com/spf13/viper"
|
||||
version = "~1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/pkg/errors"
|
||||
version = "=0.8.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "~1.2.1"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/tendermint/abci"
|
||||
version = "=0.12.0"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/tendermint/go-crypto"
|
||||
version = "=0.6.2"
|
||||
version = "=1.2.1"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/tendermint/go-amino"
|
||||
version = "=0.9.9"
|
||||
version = "=0.10.1"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/tendermint/iavl"
|
||||
version = "=0.8.0-rc0"
|
||||
version = "=v0.9.2"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/tendermint/tendermint"
|
||||
version = "=0.21.0"
|
||||
version = "=0.22.2"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/tendermint/tmlibs"
|
||||
version = "=v0.9.0"
|
||||
[[constraint]]
|
||||
name = "github.com/bartekn/go-bip39"
|
||||
branch = "master"
|
||||
|
||||
# this got updated and broke, so locked to an old working commit ...
|
||||
[[override]]
|
||||
name = "google.golang.org/genproto"
|
||||
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200"
|
||||
[[constraint]]
|
||||
name = "github.com/zondax/ledger-goclient"
|
||||
revision = "065cbf938a16f20335c40cfe180f9cd4955c6a5a"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
|
|
37
Makefile
37
Makefile
|
@ -1,9 +1,9 @@
|
|||
PACKAGES=$(shell go list ./... | grep -v '/vendor/')
|
||||
PACKAGES_NOCLITEST=$(shell go list ./... | grep -v '/vendor/' | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test)
|
||||
COMMIT_HASH := $(shell git rev-parse --short HEAD)
|
||||
BUILD_FLAGS = -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}"
|
||||
BUILD_FLAGS = -tags netgo -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}"
|
||||
|
||||
all: check_tools get_vendor_deps install install_examples test_lint test
|
||||
all: get_tools get_vendor_deps install install_examples test_lint test
|
||||
|
||||
########################################
|
||||
### CI
|
||||
|
@ -36,11 +36,11 @@ else
|
|||
go build $(BUILD_FLAGS) -o build/democli ./examples/democoin/cmd/democli
|
||||
endif
|
||||
|
||||
install:
|
||||
install:
|
||||
go install $(BUILD_FLAGS) ./cmd/gaia/cmd/gaiad
|
||||
go install $(BUILD_FLAGS) ./cmd/gaia/cmd/gaiacli
|
||||
|
||||
install_examples:
|
||||
install_examples:
|
||||
go install $(BUILD_FLAGS) ./examples/basecoin/cmd/basecoind
|
||||
go install $(BUILD_FLAGS) ./examples/basecoin/cmd/basecli
|
||||
go install $(BUILD_FLAGS) ./examples/democoin/cmd/democoind
|
||||
|
@ -89,7 +89,7 @@ godocs:
|
|||
|
||||
test: test_unit
|
||||
|
||||
test_cli:
|
||||
test_cli:
|
||||
@go test -count 1 -p 1 `go list github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test`
|
||||
|
||||
test_unit:
|
||||
|
@ -102,7 +102,13 @@ test_cover:
|
|||
@bash tests/test_cover.sh
|
||||
|
||||
test_lint:
|
||||
gometalinter --disable-all --enable='golint' --vendor ./...
|
||||
gometalinter.v2 --config=tools/gometalinter.json ./...
|
||||
!(gometalinter.v2 --disable-all --enable='errcheck' --vendor ./... | grep -v "client/")
|
||||
find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs gofmt -d -s
|
||||
|
||||
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
|
||||
|
||||
benchmark:
|
||||
@go test -bench=. $(PACKAGES_NOCLITEST)
|
||||
|
@ -133,12 +139,27 @@ devdoc_update:
|
|||
|
||||
|
||||
########################################
|
||||
### Remote validator nodes using terraform and ansible
|
||||
### Local validator nodes using docker and docker-compose
|
||||
|
||||
# Build linux binary
|
||||
build-linux:
|
||||
GOOS=linux GOARCH=amd64 $(MAKE) build
|
||||
|
||||
build-docker-gaiadnode:
|
||||
$(MAKE) -C networks/local
|
||||
|
||||
# Run a 4-node testnet locally
|
||||
localnet-start: localnet-stop
|
||||
@if ! [ -f build/node0/gaiad/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/gaiad:Z tendermint/gaiadnode testnet --v 4 --o . --starting-ip-address 192.168.10.2 ; fi
|
||||
docker-compose up
|
||||
|
||||
# Stop testnet
|
||||
localnet-stop:
|
||||
docker-compose down
|
||||
|
||||
########################################
|
||||
### Remote validator nodes using terraform and ansible
|
||||
|
||||
TESTNET_NAME?=remotenet
|
||||
SERVERS?=4
|
||||
BINARY=$(CURDIR)/build/gaiad
|
||||
|
@ -160,4 +181,4 @@ remotenet-status:
|
|||
# To avoid unintended conflicts with file names, always add to .PHONY
|
||||
# unless there is a reason not to.
|
||||
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
|
||||
.PHONY: build build_examples install install_examples install_debug dist check_tools get_tools get_vendor_deps draw_deps test test_cli test_unit test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update remotenet-start remotenet-stop remotenet-status
|
||||
.PHONY: build build_examples install install_examples install_debug dist check_tools get_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 remotenet-start remotenet-stop remotenet-status format
|
||||
|
|
65
README.md
65
README.md
|
@ -4,7 +4,7 @@
|
|||
[![version](https://img.shields.io/github/tag/cosmos/cosmos-sdk.svg)](https://github.com/cosmos/cosmos-sdk/releases/latest)
|
||||
[![API Reference](https://godoc.org/github.com/cosmos/cosmos-sdk?status.svg
|
||||
)](https://godoc.org/github.com/cosmos/cosmos-sdk)
|
||||
[![Rocket.Chat](https://demo.rocket.chat/images/join-chat.svg)](https://cosmos.rocket.chat/)
|
||||
[![riot.im](https://img.shields.io/badge/riot.im-JOIN%20CHAT-green.svg)](https://riot.im/app/#/room/#cosmos-sdk:matrix.org)
|
||||
[![license](https://img.shields.io/github/license/cosmos/cosmos-sdk.svg)](https://github.com/cosmos/cosmos-sdk/blob/master/LICENSE)
|
||||
[![LoC](https://tokei.rs/b1/github/cosmos/cosmos-sdk)](https://github.com/cosmos/cosmos-sdk)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/cosmos/cosmos-sdk)](https://goreportcard.com/report/github.com/cosmos/cosmos-sdk)
|
||||
|
@ -14,11 +14,15 @@ Branch | Tests | Coverage
|
|||
develop | [![CircleCI](https://circleci.com/gh/cosmos/cosmos-sdk/tree/develop.svg?style=shield)](https://circleci.com/gh/cosmos/cosmos-sdk/tree/develop) | [![codecov](https://codecov.io/gh/cosmos/cosmos-sdk/branch/develop/graph/badge.svg)](https://codecov.io/gh/cosmos/cosmos-sdk)
|
||||
master | [![CircleCI](https://circleci.com/gh/cosmos/cosmos-sdk/tree/master.svg?style=shield)](https://circleci.com/gh/cosmos/cosmos-sdk/tree/master) | [![codecov](https://codecov.io/gh/cosmos/cosmos-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/cosmos/cosmos-sdk)
|
||||
|
||||
**WARNING**: the libraries are still undergoing breaking changes as we get better ideas and start building out the Apps.
|
||||
The Cosmos-SDK is a framework for building blockchain applications in Golang.
|
||||
It is being used to build `Gaia`, the first implementation of the [Cosmos Hub](https://cosmos.network),
|
||||
|
||||
**WARNING**: The SDK has mostly stabilized, but we are still making some
|
||||
breaking changes.
|
||||
|
||||
**Note**: Requires [Go 1.10+](https://golang.org/dl/)
|
||||
|
||||
## Testnet
|
||||
## Gaia Testnet
|
||||
|
||||
For more information on connecting to the testnet, see
|
||||
[cmd/gaia/testnets](/cmd/gaia/testnets)
|
||||
|
@ -26,59 +30,14 @@ For more information on connecting to the testnet, see
|
|||
For the latest status of the testnet, see the [status
|
||||
file](/cmd/gaia/testnets/STATUS.md).
|
||||
|
||||
## Install
|
||||
|
||||
## Overview
|
||||
See the [install instructions](/docs/install.md)
|
||||
|
||||
The Cosmos-SDK is a platform for building multi-asset Proof-of-Stake (PoS) blockchains, like the [Cosmos Hub](https://cosmos.network). It is both a library for building and securely interacting with blockchain applications.
|
||||
## Quick Start
|
||||
|
||||
The goal of the Cosmos-SDK is to allow developers to easily create custom interoperable blockchain applications within the Cosmos Network without having to recreate common blockchain functionality, thus abstracting away the complexities of building a Tendermint ABCI application. We envision the SDK as the `npm`-like framework to build secure blockchain applications on top of Tendermint.
|
||||
|
||||
In terms of its design, the SDK optimizes flexibility and security. The framework is designed around a modular execution stack which allows applications to mix and match elements as desired. In addition, all modules are sandboxed for greater application security.
|
||||
|
||||
It is based on two major principles:
|
||||
|
||||
- **Composability**: Anyone can create a module for the Cosmos-SDK and integrating the already-built modules is as simple as importing them into your blockchain application.
|
||||
|
||||
- **Capabilities**: The SDK is inspired by capabilities-based security, and informed by years of wrestling with blockchain state machines. Most developers will need to access other 3rd party modules when building their own modules. Given that the Cosmos-SDK is an open framework and that we assume that some those modules may be malicious, we designed the SDK using object-capabilities (_ocaps_) based principles. In practice, this means that instead of having each module keep an access control list for other modules, each module implements `keepers` that can be passed to other modules to grant a pre-defined set of capabilities. For example, if an instance of module A's `keepers` is passed to module B, the latter will be able to call a restricted set of module A's functions.
|
||||
|
||||
The capabilities of each `keeper` are defined by the module's developer, and it's their job to understand and audit the safety of foreign code from 3rd party modules based on the capabilities they are passing into each 3rd party module. For a deeper look at capabilities, you can read this [article](http://habitatchronicles.com/2017/05/what-are-capabilities/).
|
||||
|
||||
_Note: For now the Cosmos-SDK only exists in [Golang](https://golang.org/), which means that developers can only develop SDK modules in Golang. In the future, we expect that the SDK will be implemented in other programming languages. Funding opportunities supported by the Tendermint team may be available eventually._
|
||||
|
||||
## Application architecture
|
||||
|
||||
#### Modules
|
||||
|
||||
The Cosmos-SDK has all the necessary pre-built modules to add functionality on top of a `BaseApp`, which is the template to build a blockchain dApp in Cosmos. In this context, a `module` is a fundamental unit in the Cosmos-SDK. Each module is an extension of the `BaseApp` functionalities that defines transactions, handles application state and the state transition logic. Each module also contains handlers for messages and transactions, as well as REST and CLI for secure user interactions.
|
||||
|
||||
Some of the most important modules already integrated in the SDK are:
|
||||
|
||||
- `Auth`: Defines a standard account structure (`BaseAccount`) and how transaction signers are authenticated.
|
||||
- `Bank`: Defines how coins (i.e cryptocurrencies) are transferred.
|
||||
- `Governance`: Governance related implementation including proposals and voting.
|
||||
- `Staking`: Proof of Stake related implementation including bonding and delegation transactions, inflation, fees, unbonding, etc.
|
||||
- `IBC`: Defines the intereoperability of blockchain zones according to the specifications of the [IBC Protocol](https://cosmos.network/whitepaper#inter-blockchain-communication-ibc).
|
||||
|
||||
|
||||
#### Directories
|
||||
|
||||
The key directories of the SDK are:
|
||||
|
||||
- `baseapp`: Defines the template for a basic [ABCI ](https://cosmos.network/whitepaper#abci) application so that your Cosmos-SDK application can communicate with the underlying Tendermint node.
|
||||
- `client`: CLI and REST server tooling.
|
||||
- `server`: RPC server to communicate with the node.
|
||||
- `examples`: Contains examples on how to build working standalone applications.
|
||||
- `store`: Contains code for the multistore (_i.e._ state). Each module can create any number of `KVStores` (key-value stores) from the multistore.
|
||||
- `types`: Common types required in any SDK-based application.
|
||||
- `x`(for e**X**tensions): Folder for storing the `BaseApp` module and all the already-built modules described in the previous section.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Golang](https://golang.org/doc/install)
|
||||
|
||||
## Getting Started
|
||||
|
||||
See the [documentation](https://cosmos-sdk.readthedocs.io).
|
||||
- [Documentation](/docs)
|
||||
- [Examples](/examples)
|
||||
|
||||
## Disambiguation
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# Security
|
||||
|
||||
As part of our [Coordinated Vulnerability Disclosure
|
||||
Policy](https://tendermint.com/security), we operate a bug bounty.
|
||||
See the policy for more details on submissions and rewards.
|
||||
|
||||
The following is a list of examples of the kinds of bugs we're most interested in for
|
||||
the Cosmos SDK. See [here](https://github.com/tendermint/tendermint/blob/master/SECURITY.md) for vulnerabilities we are interested in for Tendermint, and lower-level libraries, e.g. IAVL.
|
||||
|
||||
## Modules
|
||||
- x/staking
|
||||
- x/slashing
|
||||
- x/types
|
||||
- x/gov
|
||||
|
||||
We are interested in bugs in other modules, however the above are most likely to have
|
||||
significant vulnerabilities, due to the complexity / nuance involved
|
||||
|
||||
## How we process Tx parameters
|
||||
- Integer operations on tx parameters, especially sdk.Int / sdk.Uint
|
||||
- Gas calculation & parameter choices
|
||||
- Tx signature verification (code in x/auth/ante.go)
|
||||
- Possible Node DoS vectors. (Perhaps due to Gas weighting / non constant timing)
|
||||
|
||||
## Handling private keys
|
||||
- HD key derivation, local and Ledger, and all key-management functionality
|
||||
- Side-channel attack vectors with our implementations
|
||||
- e.g. key exfiltration based on time or memory-access patterns when decrypting privkey
|
||||
|
|
@ -7,13 +7,14 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
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"
|
||||
|
||||
"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"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
)
|
||||
|
@ -63,11 +64,8 @@ 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.
|
||||
// .valUpdates accumulate in DeliverTx and are reset in BeginBlock.
|
||||
// QUESTION: should we put valUpdates in the deliverState.ctx?
|
||||
checkState *state // for CheckTx
|
||||
deliverState *state // for DeliverTx
|
||||
valUpdates []abci.Validator // cached validator changes from DeliverTx
|
||||
signedValidators []abci.SigningValidator // absent validators from begin block
|
||||
}
|
||||
|
||||
|
@ -75,7 +73,8 @@ var _ abci.Application = (*BaseApp)(nil)
|
|||
|
||||
// Create and name new BaseApp
|
||||
// NOTE: The db is used to store the version number for now.
|
||||
func NewBaseApp(name string, cdc *wire.Codec, logger log.Logger, db dbm.DB) *BaseApp {
|
||||
// Accepts variable number of option functions, which act on the BaseApp to set configuration choices
|
||||
func NewBaseApp(name string, cdc *wire.Codec, logger log.Logger, db dbm.DB, options ...func(*BaseApp)) *BaseApp {
|
||||
app := &BaseApp{
|
||||
Logger: logger,
|
||||
name: name,
|
||||
|
@ -88,6 +87,9 @@ func NewBaseApp(name string, cdc *wire.Codec, logger log.Logger, db dbm.DB) *Bas
|
|||
}
|
||||
// Register the undefined & root codespaces, which should not be used by any modules
|
||||
app.codespacer.RegisterOrPanic(sdk.CodespaceRoot)
|
||||
for _, option := range options {
|
||||
option(app)
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
|
@ -124,6 +126,11 @@ func (app *BaseApp) SetTxDecoder(txDecoder sdk.TxDecoder) {
|
|||
}
|
||||
|
||||
// default custom logic for transaction decoding
|
||||
// TODO: remove auth and wire dependencies from baseapp
|
||||
// - move this to auth.DefaultTxDecoder
|
||||
// - set the default here to JSON decode like docs/examples/app1 (it will fail
|
||||
// for multiple messages ;))
|
||||
// - pass a TxDecoder into NewBaseApp, instead of a codec.
|
||||
func defaultTxDecoder(cdc *wire.Codec) sdk.TxDecoder {
|
||||
return func(txBytes []byte) (sdk.Tx, sdk.Error) {
|
||||
var tx = auth.StdTx{}
|
||||
|
@ -165,13 +172,19 @@ func (app *BaseApp) Router() Router { return app.router }
|
|||
|
||||
// load latest application version
|
||||
func (app *BaseApp) LoadLatestVersion(mainKey sdk.StoreKey) error {
|
||||
app.cms.LoadLatestVersion()
|
||||
err := app.cms.LoadLatestVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return app.initFromStore(mainKey)
|
||||
}
|
||||
|
||||
// load application version
|
||||
func (app *BaseApp) LoadVersion(version int64, mainKey sdk.StoreKey) error {
|
||||
app.cms.LoadVersion(version)
|
||||
err := app.cms.LoadVersion(version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return app.initFromStore(mainKey)
|
||||
}
|
||||
|
||||
|
@ -180,7 +193,7 @@ func (app *BaseApp) LastCommitID() sdk.CommitID {
|
|||
return app.cms.LastCommitID()
|
||||
}
|
||||
|
||||
// the last commited block height
|
||||
// the last committed block height
|
||||
func (app *BaseApp) LastBlockHeight() int64 {
|
||||
return app.cms.LastCommitID().Version
|
||||
}
|
||||
|
@ -192,51 +205,18 @@ func (app *BaseApp) initFromStore(mainKey sdk.StoreKey) error {
|
|||
// TODO: we don't actually need the main store here
|
||||
main := app.cms.GetKVStore(mainKey)
|
||||
if main == nil {
|
||||
return errors.New("BaseApp expects MultiStore with 'main' KVStore")
|
||||
return errors.New("baseapp expects MultiStore with 'main' KVStore")
|
||||
}
|
||||
|
||||
// XXX: Do we really need the header? What does it have that we want
|
||||
// here that's not already in the CommitID ? If an app wants to have it,
|
||||
// they can do so in their BeginBlocker. If we force it in baseapp,
|
||||
// then either we force the AppHash to change with every block (since the header
|
||||
// will be in the merkle store) or we can't write the state and the header to the
|
||||
// db atomically without doing some surgery on the store interfaces ...
|
||||
|
||||
// if we've committed before, we expect <dbHeaderKey> to exist in the db
|
||||
/*
|
||||
var lastCommitID = app.cms.LastCommitID()
|
||||
var header abci.Header
|
||||
|
||||
if !lastCommitID.IsZero() {
|
||||
headerBytes := app.db.Get(dbHeaderKey)
|
||||
if len(headerBytes) == 0 {
|
||||
errStr := fmt.Sprintf("Version > 0 but missing key %s", dbHeaderKey)
|
||||
return errors.New(errStr)
|
||||
}
|
||||
err := proto.Unmarshal(headerBytes, &header)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to parse Header")
|
||||
}
|
||||
lastVersion := lastCommitID.Version
|
||||
if header.Height != lastVersion {
|
||||
errStr := fmt.Sprintf("Expected db://%s.Height %v but got %v", dbHeaderKey, lastVersion, header.Height)
|
||||
return errors.New(errStr)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// initialize Check state
|
||||
app.setCheckState(abci.Header{})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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, nil, app.Logger)
|
||||
return sdk.NewContext(app.checkState.ms, header, true, app.Logger)
|
||||
}
|
||||
return sdk.NewContext(app.deliverState.ms, header, false, nil, app.Logger)
|
||||
return sdk.NewContext(app.deliverState.ms, header, false, app.Logger)
|
||||
}
|
||||
|
||||
type state struct {
|
||||
|
@ -252,7 +232,7 @@ func (app *BaseApp) setCheckState(header abci.Header) {
|
|||
ms := app.cms.CacheMultiStore()
|
||||
app.checkState = &state{
|
||||
ms: ms,
|
||||
ctx: sdk.NewContext(ms, header, true, nil, app.Logger),
|
||||
ctx: sdk.NewContext(ms, header, true, app.Logger),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,7 +240,7 @@ func (app *BaseApp) setDeliverState(header abci.Header) {
|
|||
ms := app.cms.CacheMultiStore()
|
||||
app.deliverState = &state{
|
||||
ms: ms,
|
||||
ctx: sdk.NewContext(ms, header, false, nil, app.Logger),
|
||||
ctx: sdk.NewContext(ms, header, false, app.Logger),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -288,12 +268,13 @@ func (app *BaseApp) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOp
|
|||
// Implements ABCI
|
||||
// InitChain runs the initialization logic directly on the CommitMultiStore and commits it.
|
||||
func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) {
|
||||
// Initialize the deliver state and check state with ChainID and run initChain
|
||||
app.setDeliverState(abci.Header{ChainID: req.ChainId})
|
||||
app.setCheckState(abci.Header{ChainID: req.ChainId})
|
||||
|
||||
if app.initChainer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize the deliver state and run initChain
|
||||
app.setDeliverState(abci.Header{})
|
||||
app.initChainer(app.deliverState.ctx, req) // no error
|
||||
|
||||
// NOTE: we don't commit, but BeginBlock for block 1
|
||||
|
@ -317,16 +298,39 @@ func (app *BaseApp) FilterPeerByPubKey(info string) abci.ResponseQuery {
|
|||
return abci.ResponseQuery{}
|
||||
}
|
||||
|
||||
// Implements ABCI.
|
||||
// Delegates to CommitMultiStore if it implements Queryable
|
||||
func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
|
||||
path := strings.Split(req.Path, "/")
|
||||
func splitPath(requestPath string) (path []string) {
|
||||
path = strings.Split(requestPath, "/")
|
||||
// first element is empty string
|
||||
if len(path) > 0 && path[0] == "" {
|
||||
path = path[1:]
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// Implements ABCI.
|
||||
// Delegates to CommitMultiStore if it implements Queryable
|
||||
func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
|
||||
path := splitPath(req.Path)
|
||||
if len(path) == 0 {
|
||||
msg := "no query path provided"
|
||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||
}
|
||||
switch path[0] {
|
||||
// "/app" prefix for special application queries
|
||||
if len(path) >= 2 && path[0] == "app" {
|
||||
case "app":
|
||||
return handleQueryApp(app, path, req)
|
||||
case "store":
|
||||
return handleQueryStore(app, path, req)
|
||||
case "p2p":
|
||||
return handleQueryP2P(app, path, req)
|
||||
}
|
||||
|
||||
msg := "unknown query path"
|
||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||
}
|
||||
|
||||
func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
|
||||
if len(path) >= 2 {
|
||||
var result sdk.Result
|
||||
switch path[1] {
|
||||
case "simulate":
|
||||
|
@ -337,6 +341,11 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
|
|||
} else {
|
||||
result = app.Simulate(tx)
|
||||
}
|
||||
case "version":
|
||||
return abci.ResponseQuery{
|
||||
Code: uint32(sdk.ABCICodeOK),
|
||||
Value: []byte(version.GetVersion()),
|
||||
}
|
||||
default:
|
||||
result = sdk.ErrUnknownRequest(fmt.Sprintf("Unknown query: %s", path)).Result()
|
||||
}
|
||||
|
@ -346,28 +355,40 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
|
|||
Value: value,
|
||||
}
|
||||
}
|
||||
msg := "Expected second parameter to be either simulate or version, neither was present"
|
||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||
}
|
||||
|
||||
func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
|
||||
// "/store" prefix for store queries
|
||||
if len(path) >= 1 && path[0] == "store" {
|
||||
queryable, ok := app.cms.(sdk.Queryable)
|
||||
if !ok {
|
||||
msg := "multistore doesn't support queries"
|
||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||
}
|
||||
req.Path = "/" + strings.Join(path[1:], "/")
|
||||
return queryable.Query(req)
|
||||
queryable, ok := app.cms.(sdk.Queryable)
|
||||
if !ok {
|
||||
msg := "multistore doesn't support queries"
|
||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||
}
|
||||
req.Path = "/" + strings.Join(path[1:], "/")
|
||||
return queryable.Query(req)
|
||||
}
|
||||
|
||||
func handleQueryP2P(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
|
||||
// "/p2p" prefix for p2p queries
|
||||
if len(path) >= 4 && path[0] == "p2p" {
|
||||
if len(path) >= 4 {
|
||||
if path[1] == "filter" {
|
||||
if path[2] == "addr" {
|
||||
return app.FilterPeerByAddrPort(path[3])
|
||||
}
|
||||
if path[2] == "pubkey" {
|
||||
// TODO: this should be changed to `id`
|
||||
// NOTE: this changed in tendermint and we didn't notice...
|
||||
return app.FilterPeerByPubKey(path[3])
|
||||
}
|
||||
} else {
|
||||
msg := "Expected second parameter to be filter"
|
||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||
}
|
||||
}
|
||||
msg := "unknown query path"
|
||||
|
||||
msg := "Expected path is p2p filter <addr|pubkey> <parameter>"
|
||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||
}
|
||||
|
||||
|
@ -375,12 +396,16 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
|
|||
func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) {
|
||||
// Initialize the DeliverTx state.
|
||||
// If this is the first block, it should already
|
||||
// be initialized in InitChain. It may also be nil
|
||||
// if this is a test and InitChain was never called.
|
||||
// be initialized in InitChain.
|
||||
// Otherwise app.deliverState will be nil, since it
|
||||
// is reset on Commit.
|
||||
if app.deliverState == nil {
|
||||
app.setDeliverState(req.Header)
|
||||
} else {
|
||||
// In the first block, app.deliverState.ctx will already be initialized
|
||||
// by InitChain. Context is now updated with Header information.
|
||||
app.deliverState.ctx = app.deliverState.ctx.WithBlockHeader(req.Header)
|
||||
}
|
||||
app.valUpdates = nil
|
||||
if app.beginBlocker != nil {
|
||||
res = app.beginBlocker(app.deliverState.ctx, req)
|
||||
}
|
||||
|
@ -425,13 +450,8 @@ func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
|
|||
result = app.runTx(runTxModeDeliver, txBytes, tx)
|
||||
}
|
||||
|
||||
// After-handler hooks.
|
||||
if result.IsOK() {
|
||||
app.valUpdates = append(app.valUpdates, result.ValidatorUpdates...)
|
||||
} else {
|
||||
// Even though the Result.Code is not OK, there are still effects,
|
||||
// namely fee deductions and sequence incrementing.
|
||||
}
|
||||
// Even though the Result.Code is not OK, there are still effects,
|
||||
// namely fee deductions and sequence incrementing.
|
||||
|
||||
// Tell the blockchain engine (i.e. Tendermint).
|
||||
return abci.ResponseDeliverTx{
|
||||
|
@ -444,52 +464,27 @@ func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
|
|||
}
|
||||
}
|
||||
|
||||
// nolint - Mostly for testing
|
||||
func (app *BaseApp) Check(tx sdk.Tx) (result sdk.Result) {
|
||||
return app.runTx(runTxModeCheck, nil, tx)
|
||||
}
|
||||
// Basic validator for msgs
|
||||
func validateBasicTxMsgs(msgs []sdk.Msg) sdk.Error {
|
||||
if msgs == nil || len(msgs) == 0 {
|
||||
// TODO: probably shouldn't be ErrInternal. Maybe new ErrInvalidMessage, or ?
|
||||
return sdk.ErrInternal("Tx.GetMsgs() must return at least one message in list")
|
||||
}
|
||||
|
||||
// nolint - full tx execution
|
||||
func (app *BaseApp) Simulate(tx sdk.Tx) (result sdk.Result) {
|
||||
return app.runTx(runTxModeSimulate, nil, tx)
|
||||
}
|
||||
|
||||
// nolint
|
||||
func (app *BaseApp) Deliver(tx sdk.Tx) (result sdk.Result) {
|
||||
return app.runTx(runTxModeDeliver, nil, tx)
|
||||
}
|
||||
|
||||
// txBytes may be nil in some cases, eg. in tests.
|
||||
// Also, in the future we may support "internal" transactions.
|
||||
func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk.Result) {
|
||||
// Handle any panics.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
switch r.(type) {
|
||||
case sdk.ErrorOutOfGas:
|
||||
log := fmt.Sprintf("Out of gas in location: %v", r.(sdk.ErrorOutOfGas).Descriptor)
|
||||
result = sdk.ErrOutOfGas(log).Result()
|
||||
default:
|
||||
log := fmt.Sprintf("Recovered: %v\nstack:\n%v", r, string(debug.Stack()))
|
||||
result = sdk.ErrInternal(log).Result()
|
||||
}
|
||||
for _, msg := range msgs {
|
||||
// Validate the Msg.
|
||||
err := msg.ValidateBasic()
|
||||
if err != nil {
|
||||
err = err.WithDefaultCodespace(sdk.CodespaceRoot)
|
||||
return err
|
||||
}
|
||||
}()
|
||||
|
||||
// Get the Msg.
|
||||
var msg = tx.GetMsg()
|
||||
if msg == nil {
|
||||
return sdk.ErrInternal("Tx.GetMsg() returned nil").Result()
|
||||
}
|
||||
|
||||
// Validate the Msg.
|
||||
err := msg.ValidateBasic()
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *BaseApp) getContextForAnte(mode runTxMode, txBytes []byte) (ctx sdk.Context) {
|
||||
// Get the context
|
||||
var ctx sdk.Context
|
||||
if mode == runTxModeCheck || mode == runTxModeSimulate {
|
||||
ctx = app.checkState.ctx.WithTxBytes(txBytes)
|
||||
} else {
|
||||
|
@ -502,55 +497,130 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
|||
ctx = ctx.WithIsCheckTx(false)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Iterates through msgs and executes them
|
||||
func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg) (result sdk.Result) {
|
||||
// accumulate results
|
||||
logs := make([]string, 0, len(msgs))
|
||||
var data []byte // NOTE: we just append them all (?!)
|
||||
var tags sdk.Tags // also just append them all
|
||||
var code sdk.ABCICodeType
|
||||
for msgIdx, msg := range msgs {
|
||||
// Match route.
|
||||
msgType := msg.Type()
|
||||
handler := app.router.Route(msgType)
|
||||
if handler == nil {
|
||||
return sdk.ErrUnknownRequest("Unrecognized Msg type: " + msgType).Result()
|
||||
}
|
||||
|
||||
msgResult := handler(ctx, msg)
|
||||
|
||||
// NOTE: GasWanted is determined by ante handler and
|
||||
// GasUsed by the GasMeter
|
||||
|
||||
// Append Data and Tags
|
||||
data = append(data, msgResult.Data...)
|
||||
tags = append(tags, msgResult.Tags...)
|
||||
|
||||
// Stop execution and return on first failed message.
|
||||
if !msgResult.IsOK() {
|
||||
logs = append(logs, fmt.Sprintf("Msg %d failed: %s", msgIdx, msgResult.Log))
|
||||
code = msgResult.Code
|
||||
break
|
||||
}
|
||||
|
||||
// Construct usable logs in multi-message transactions.
|
||||
logs = append(logs, fmt.Sprintf("Msg %d: %s", msgIdx, msgResult.Log))
|
||||
}
|
||||
|
||||
// Set the final gas values.
|
||||
result = sdk.Result{
|
||||
Code: code,
|
||||
Data: data,
|
||||
Log: strings.Join(logs, "\n"),
|
||||
GasUsed: ctx.GasMeter().GasConsumed(),
|
||||
// TODO: FeeAmount/FeeDenom
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns deliverState if app is in runTxModeDeliver, otherwhise returns checkstate
|
||||
func getState(app *BaseApp, mode runTxMode) *state {
|
||||
if mode == runTxModeCheck || mode == runTxModeSimulate {
|
||||
return app.checkState
|
||||
}
|
||||
return app.deliverState
|
||||
}
|
||||
|
||||
// txBytes may be nil in some cases, eg. in tests.
|
||||
// Also, in the future we may support "internal" transactions.
|
||||
func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk.Result) {
|
||||
//NOTE: GasWanted should be returned by the AnteHandler.
|
||||
// GasUsed is determined by the GasMeter.
|
||||
// We need access to the context to get the gas meter so
|
||||
// we initialize upfront
|
||||
var gasWanted int64
|
||||
ctx := app.getContextForAnte(mode, txBytes)
|
||||
|
||||
// Handle any panics.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
switch rType := r.(type) {
|
||||
case sdk.ErrorOutOfGas:
|
||||
log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor)
|
||||
result = sdk.ErrOutOfGas(log).Result()
|
||||
default:
|
||||
log := fmt.Sprintf("recovered: %v\nstack:\n%v", r, string(debug.Stack()))
|
||||
result = sdk.ErrInternal(log).Result()
|
||||
}
|
||||
}
|
||||
result.GasWanted = gasWanted
|
||||
result.GasUsed = ctx.GasMeter().GasConsumed()
|
||||
}()
|
||||
|
||||
// Get the Msg.
|
||||
var msgs = tx.GetMsgs()
|
||||
|
||||
err := validateBasicTxMsgs(msgs)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
// Run the ante handler.
|
||||
if app.anteHandler != nil {
|
||||
newCtx, result, abort := app.anteHandler(ctx, tx)
|
||||
newCtx, anteResult, abort := app.anteHandler(ctx, tx)
|
||||
if abort {
|
||||
return result
|
||||
return anteResult
|
||||
}
|
||||
if !newCtx.IsZero() {
|
||||
ctx = newCtx
|
||||
}
|
||||
gasWanted = result.GasWanted
|
||||
}
|
||||
|
||||
// Match route.
|
||||
msgType := msg.Type()
|
||||
handler := app.router.Route(msgType)
|
||||
if handler == nil {
|
||||
return sdk.ErrUnknownRequest("Unrecognized Msg type: " + msgType).Result()
|
||||
}
|
||||
// CacheWrap the state in case it fails.
|
||||
msCache := getState(app, mode).CacheMultiStore()
|
||||
ctx = ctx.WithMultiStore(msCache)
|
||||
|
||||
// Get the correct cache
|
||||
var msCache sdk.CacheMultiStore
|
||||
if mode == runTxModeCheck || mode == runTxModeSimulate {
|
||||
// CacheWrap app.checkState.ms in case it fails.
|
||||
msCache = app.checkState.CacheMultiStore()
|
||||
ctx = ctx.WithMultiStore(msCache)
|
||||
} else {
|
||||
// CacheWrap app.deliverState.ms in case it fails.
|
||||
msCache = app.deliverState.CacheMultiStore()
|
||||
ctx = ctx.WithMultiStore(msCache)
|
||||
}
|
||||
result = app.runMsgs(ctx, msgs)
|
||||
result.GasWanted = gasWanted
|
||||
|
||||
result = handler(ctx, msg)
|
||||
|
||||
// Set gas utilized
|
||||
result.GasUsed = ctx.GasMeter().GasConsumed()
|
||||
|
||||
// If not a simulated run and result was successful, write to app.checkState.ms or app.deliverState.ms
|
||||
if mode != runTxModeSimulate && result.IsOK() {
|
||||
// Only update state if all messages pass and we're not in a simulation.
|
||||
if result.IsOK() && mode != runTxModeSimulate {
|
||||
msCache.Write()
|
||||
}
|
||||
|
||||
return result
|
||||
return
|
||||
}
|
||||
|
||||
// Implements ABCI
|
||||
func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) {
|
||||
if app.endBlocker != nil {
|
||||
res = app.endBlocker(app.deliverState.ctx, req)
|
||||
} else {
|
||||
res.ValidatorUpdates = app.valUpdates
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -570,6 +640,7 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) {
|
|||
// Write the Deliver state and commit the MultiStore
|
||||
app.deliverState.ms.Write()
|
||||
commitID := app.cms.Commit()
|
||||
// TODO: this is missing a module identifier and dumps byte array
|
||||
app.Logger.Debug("Commit synced",
|
||||
"commit", commitID,
|
||||
)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,24 +1,48 @@
|
|||
package baseapp
|
||||
|
||||
import (
|
||||
"github.com/tendermint/abci/server"
|
||||
abci "github.com/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/tendermint/tendermint/abci/server"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
// nolint - Mostly for testing
|
||||
func (app *BaseApp) Check(tx sdk.Tx) (result sdk.Result) {
|
||||
return app.runTx(runTxModeCheck, nil, tx)
|
||||
}
|
||||
|
||||
// nolint - full tx execution
|
||||
func (app *BaseApp) Simulate(tx sdk.Tx) (result sdk.Result) {
|
||||
return app.runTx(runTxModeSimulate, nil, tx)
|
||||
}
|
||||
|
||||
// nolint
|
||||
func (app *BaseApp) Deliver(tx sdk.Tx) (result sdk.Result) {
|
||||
return app.runTx(runTxModeDeliver, nil, tx)
|
||||
}
|
||||
|
||||
// RunForever - BasecoinApp execution and cleanup
|
||||
func RunForever(app abci.Application) {
|
||||
|
||||
// Start the ABCI server
|
||||
srv, err := server.NewServer("0.0.0.0:46658", "socket", app)
|
||||
srv, err := server.NewServer("0.0.0.0:26658", "socket", app)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
return
|
||||
}
|
||||
err = srv.Start()
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
return
|
||||
}
|
||||
srv.Start()
|
||||
|
||||
// Wait forever
|
||||
cmn.TrapSignal(func() {
|
||||
// Cleanup
|
||||
srv.Stop()
|
||||
err := srv.Stop()
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ var isAlpha = regexp.MustCompile(`^[a-zA-Z]+$`).MatchString
|
|||
// AddRoute - TODO add description
|
||||
func (rtr *router) AddRoute(r string, h sdk.Handler) Router {
|
||||
if !isAlpha(r) {
|
||||
panic("route expressions can only contain alphanumeric characters")
|
||||
panic("route expressions can only contain alphabet characters")
|
||||
}
|
||||
rtr.routes = append(rtr.routes, route{r, h})
|
||||
|
||||
|
|
|
@ -3,13 +3,15 @@ package context
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
|
@ -30,26 +32,47 @@ func (ctx CoreContext) BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit,
|
|||
}
|
||||
|
||||
if res.CheckTx.Code != uint32(0) {
|
||||
return res, errors.Errorf("CheckTx failed: (%d) %s",
|
||||
return res, errors.Errorf("checkTx failed: (%d) %s",
|
||||
res.CheckTx.Code,
|
||||
res.CheckTx.Log)
|
||||
}
|
||||
if res.DeliverTx.Code != uint32(0) {
|
||||
return res, errors.Errorf("DeliverTx failed: (%d) %s",
|
||||
return res, errors.Errorf("deliverTx failed: (%d) %s",
|
||||
res.DeliverTx.Code,
|
||||
res.DeliverTx.Log)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Query from Tendermint with the provided key and storename
|
||||
func (ctx CoreContext) Query(key cmn.HexBytes, storeName string) (res []byte, err error) {
|
||||
return ctx.query(key, storeName, "key")
|
||||
// Broadcast the transaction bytes to Tendermint
|
||||
func (ctx CoreContext) 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
|
||||
}
|
||||
|
||||
// Query information about the connected node
|
||||
func (ctx CoreContext) Query(path string) (res []byte, err error) {
|
||||
return ctx.query(path, nil)
|
||||
}
|
||||
|
||||
// QueryStore from Tendermint with the provided key and storename
|
||||
func (ctx CoreContext) QueryStore(key cmn.HexBytes, storeName string) (res []byte, err error) {
|
||||
return ctx.queryStore(key, storeName, "key")
|
||||
}
|
||||
|
||||
// Query from Tendermint with the provided storename and subspace
|
||||
func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName string) (res []sdk.KVPair, err error) {
|
||||
resRaw, err := ctx.query(subspace, storeName, "subspace")
|
||||
resRaw, err := ctx.queryStore(subspace, storeName, "subspace")
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
@ -58,8 +81,7 @@ func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName
|
|||
}
|
||||
|
||||
// Query from Tendermint with the provided storename and path
|
||||
func (ctx CoreContext) query(key cmn.HexBytes, storeName, endPath string) (res []byte, err error) {
|
||||
path := fmt.Sprintf("/store/%s/%s", storeName, endPath)
|
||||
func (ctx CoreContext) query(path string, key common.HexBytes) (res []byte, err error) {
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return res, err
|
||||
|
@ -75,13 +97,19 @@ func (ctx CoreContext) query(key cmn.HexBytes, storeName, endPath string) (res [
|
|||
}
|
||||
resp := result.Response
|
||||
if resp.Code != uint32(0) {
|
||||
return res, errors.Errorf("Query failed: (%d) %s", resp.Code, resp.Log)
|
||||
return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log)
|
||||
}
|
||||
return resp.Value, nil
|
||||
}
|
||||
|
||||
// Query from Tendermint with the provided storename and path
|
||||
func (ctx CoreContext) queryStore(key cmn.HexBytes, storeName, endPath string) (res []byte, err error) {
|
||||
path := fmt.Sprintf("/store/%s/%s", storeName, endPath)
|
||||
return ctx.query(path, key)
|
||||
}
|
||||
|
||||
// Get the from address from the name flag
|
||||
func (ctx CoreContext) GetFromAddress() (from sdk.Address, err error) {
|
||||
func (ctx CoreContext) GetFromAddress() (from sdk.AccAddress, err error) {
|
||||
|
||||
keybase, err := keys.GetKeyBase()
|
||||
if err != nil {
|
||||
|
@ -95,29 +123,40 @@ func (ctx CoreContext) GetFromAddress() (from sdk.Address, err error) {
|
|||
|
||||
info, err := keybase.Get(name)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("No key for: %s", name)
|
||||
return nil, errors.Errorf("no key for: %s", name)
|
||||
}
|
||||
|
||||
return info.PubKey.Address(), nil
|
||||
return sdk.AccAddress(info.GetPubKey().Address()), nil
|
||||
}
|
||||
|
||||
// sign and build the transaction from the msg
|
||||
func (ctx CoreContext) SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *wire.Codec) ([]byte, error) {
|
||||
func (ctx CoreContext) SignAndBuild(name, passphrase string, msgs []sdk.Msg, cdc *wire.Codec) ([]byte, error) {
|
||||
|
||||
// build the Sign Messsage from the Standard Message
|
||||
chainID := ctx.ChainID
|
||||
if chainID == "" {
|
||||
return nil, errors.Errorf("Chain ID required but not specified")
|
||||
return nil, errors.Errorf("chain ID required but not specified")
|
||||
}
|
||||
accnum := ctx.AccountNumber
|
||||
sequence := ctx.Sequence
|
||||
memo := ctx.Memo
|
||||
|
||||
fee := sdk.Coin{}
|
||||
if ctx.Fee != "" {
|
||||
parsedFee, err := sdk.ParseCoin(ctx.Fee)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fee = parsedFee
|
||||
}
|
||||
|
||||
signMsg := auth.StdSignMsg{
|
||||
ChainID: chainID,
|
||||
AccountNumbers: []int64{accnum},
|
||||
Sequences: []int64{sequence},
|
||||
Msg: msg,
|
||||
Fee: auth.NewStdFee(ctx.Gas, sdk.Coin{}), // TODO run simulate to estimate gas?
|
||||
ChainID: chainID,
|
||||
AccountNumber: accnum,
|
||||
Sequence: sequence,
|
||||
Msgs: msgs,
|
||||
Memo: memo,
|
||||
Fee: auth.NewStdFee(ctx.Gas, fee), // TODO run simulate to estimate gas?
|
||||
}
|
||||
|
||||
keybase, err := keys.GetKeyBase()
|
||||
|
@ -140,14 +179,13 @@ func (ctx CoreContext) SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *w
|
|||
}}
|
||||
|
||||
// marshal bytes
|
||||
tx := auth.NewStdTx(signMsg.Msg, signMsg.Fee, sigs)
|
||||
tx := auth.NewStdTx(signMsg.Msgs, signMsg.Fee, sigs, memo)
|
||||
|
||||
return cdc.MarshalBinary(tx)
|
||||
}
|
||||
|
||||
// sign and build the transaction from the msg
|
||||
func (ctx CoreContext) EnsureSignBuildBroadcast(name string, msg sdk.Msg, cdc *wire.Codec) (res *ctypes.ResultBroadcastTxCommit, err error) {
|
||||
|
||||
func (ctx CoreContext) ensureSignBuild(name string, msgs []sdk.Msg, cdc *wire.Codec) (tyBytes []byte, err error) {
|
||||
ctx, err = EnsureAccountNumber(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -158,26 +196,95 @@ func (ctx CoreContext) EnsureSignBuildBroadcast(name string, msg sdk.Msg, cdc *w
|
|||
return nil, err
|
||||
}
|
||||
|
||||
passphrase, err := ctx.GetPassphraseFromStdin(name)
|
||||
var txBytes []byte
|
||||
|
||||
keybase, err := keys.GetKeyBase()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txBytes, err := ctx.SignAndBuild(name, passphrase, msg, cdc)
|
||||
info, err := keybase.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var passphrase string
|
||||
// Only need a passphrase for locally-stored keys
|
||||
if info.GetType() == "local" {
|
||||
passphrase, err = ctx.GetPassphraseFromStdin(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error fetching passphrase: %v", err)
|
||||
}
|
||||
}
|
||||
txBytes, err = ctx.SignAndBuild(name, passphrase, msgs, cdc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error signing transaction: %v", err)
|
||||
}
|
||||
|
||||
return ctx.BroadcastTx(txBytes)
|
||||
return txBytes, err
|
||||
}
|
||||
|
||||
// sign and build the transaction from the msg
|
||||
func (ctx CoreContext) EnsureSignBuildBroadcast(name string, msgs []sdk.Msg, cdc *wire.Codec) (err error) {
|
||||
|
||||
txBytes, err := ctx.ensureSignBuild(name, msgs, cdc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.Async {
|
||||
res, err := ctx.BroadcastTxAsync(txBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.JSON {
|
||||
type toJSON struct {
|
||||
TxHash string
|
||||
}
|
||||
valueToJSON := toJSON{res.Hash.String()}
|
||||
JSON, err := cdc.MarshalJSON(valueToJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(JSON))
|
||||
} else {
|
||||
fmt.Println("Async tx sent. tx hash: ", res.Hash.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
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
|
||||
}
|
||||
valueToJSON := toJSON{res.Height, res.Hash.String(), fmt.Sprintf("%+v", res.DeliverTx)}
|
||||
JSON, err := cdc.MarshalJSON(valueToJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(JSON))
|
||||
return nil
|
||||
}
|
||||
if ctx.PrintResponse {
|
||||
fmt.Printf("Committed at block %d. Hash: %s Response:%+v \n", res.Height, res.Hash.String(), res.DeliverTx)
|
||||
} else {
|
||||
fmt.Printf("Committed at block %d. Hash: %s \n", res.Height, res.Hash.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// get the next sequence for the account address
|
||||
func (ctx CoreContext) GetAccountNumber(address []byte) (int64, error) {
|
||||
if ctx.Decoder == nil {
|
||||
return 0, errors.New("AccountDecoder required but not provided")
|
||||
return 0, errors.New("accountDecoder required but not provided")
|
||||
}
|
||||
|
||||
res, err := ctx.Query(auth.AddressStoreKey(address), ctx.AccountStore)
|
||||
res, err := ctx.QueryStore(auth.AddressStoreKey(address), ctx.AccountStore)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -198,10 +305,10 @@ func (ctx CoreContext) GetAccountNumber(address []byte) (int64, error) {
|
|||
// get the next sequence for the account address
|
||||
func (ctx CoreContext) NextSequence(address []byte) (int64, error) {
|
||||
if ctx.Decoder == nil {
|
||||
return 0, errors.New("AccountDecoder required but not provided")
|
||||
return 0, errors.New("accountDecoder required but not provided")
|
||||
}
|
||||
|
||||
res, err := ctx.Query(auth.AddressStoreKey(address), ctx.AccountStore)
|
||||
res, err := ctx.QueryStore(auth.AddressStoreKey(address), ctx.AccountStore)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -229,7 +336,7 @@ func (ctx CoreContext) GetPassphraseFromStdin(name string) (pass string, err err
|
|||
// GetNode prepares a simple rpc.Client
|
||||
func (ctx CoreContext) GetNode() (rpcclient.Client, error) {
|
||||
if ctx.Client == nil {
|
||||
return nil, errors.New("Must define node URI")
|
||||
return nil, errors.New("must define node URI")
|
||||
}
|
||||
return ctx.Client, nil
|
||||
}
|
||||
|
|
|
@ -11,14 +11,20 @@ type CoreContext struct {
|
|||
ChainID string
|
||||
Height int64
|
||||
Gas int64
|
||||
Fee string
|
||||
TrustNode bool
|
||||
NodeURI string
|
||||
FromAddressName string
|
||||
AccountNumber int64
|
||||
Sequence int64
|
||||
Memo string
|
||||
Client rpcclient.Client
|
||||
Decoder auth.AccountDecoder
|
||||
AccountStore string
|
||||
UseLedger bool
|
||||
Async bool
|
||||
JSON bool
|
||||
PrintResponse bool
|
||||
}
|
||||
|
||||
// WithChainID - return a copy of the context with an updated chainID
|
||||
|
@ -39,6 +45,12 @@ func (c CoreContext) WithGas(gas int64) CoreContext {
|
|||
return c
|
||||
}
|
||||
|
||||
// WithFee - return a copy of the context with an updated fee
|
||||
func (c CoreContext) WithFee(fee string) CoreContext {
|
||||
c.Fee = fee
|
||||
return c
|
||||
}
|
||||
|
||||
// WithTrustNode - return a copy of the context with an updated TrustNode flag
|
||||
func (c CoreContext) WithTrustNode(trustNode bool) CoreContext {
|
||||
c.TrustNode = trustNode
|
||||
|
@ -70,6 +82,12 @@ func (c CoreContext) WithSequence(sequence int64) CoreContext {
|
|||
return c
|
||||
}
|
||||
|
||||
// WithMemo - return a copy of the context with an updated memo
|
||||
func (c CoreContext) WithMemo(memo string) CoreContext {
|
||||
c.Memo = memo
|
||||
return c
|
||||
}
|
||||
|
||||
// WithClient - return a copy of the context with an updated RPC client instance
|
||||
func (c CoreContext) WithClient(client rpcclient.Client) CoreContext {
|
||||
c.Client = client
|
||||
|
@ -87,3 +105,9 @@ func (c CoreContext) WithAccountStore(accountStore string) CoreContext {
|
|||
c.AccountStore = accountStore
|
||||
return c
|
||||
}
|
||||
|
||||
// WithUseLedger - return a copy of the context with an updated UseLedger
|
||||
func (c CoreContext) WithUseLedger(useLedger bool) CoreContext {
|
||||
c.UseLedger = useLedger
|
||||
return c
|
||||
}
|
||||
|
|
|
@ -27,18 +27,31 @@ func NewCoreContextFromViper() CoreContext {
|
|||
chainID = def
|
||||
}
|
||||
}
|
||||
// TODO: Remove the following deprecation code after Gaia-7000 is launched
|
||||
keyName := viper.GetString(client.FlagName)
|
||||
if keyName != "" {
|
||||
fmt.Println("** Note --name is deprecated and will be removed next release. Please use --from instead **")
|
||||
} else {
|
||||
keyName = viper.GetString(client.FlagFrom)
|
||||
}
|
||||
return CoreContext{
|
||||
ChainID: chainID,
|
||||
Height: viper.GetInt64(client.FlagHeight),
|
||||
Gas: viper.GetInt64(client.FlagGas),
|
||||
Fee: viper.GetString(client.FlagFee),
|
||||
TrustNode: viper.GetBool(client.FlagTrustNode),
|
||||
FromAddressName: viper.GetString(client.FlagName),
|
||||
FromAddressName: keyName,
|
||||
NodeURI: nodeURI,
|
||||
AccountNumber: viper.GetInt64(client.FlagAccountNumber),
|
||||
Sequence: viper.GetInt64(client.FlagSequence),
|
||||
Memo: viper.GetString(client.FlagMemo),
|
||||
Client: rpc,
|
||||
Decoder: nil,
|
||||
AccountStore: "acc",
|
||||
UseLedger: viper.GetBool(client.FlagUseLedger),
|
||||
Async: viper.GetBool(client.FlagAsync),
|
||||
JSON: viper.GetBool(client.FlagJson),
|
||||
PrintResponse: viper.GetBool(client.FlagPrintResponse),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,7 +68,7 @@ func defaultChainID() (string, error) {
|
|||
return doc.ChainID, nil
|
||||
}
|
||||
|
||||
// EnsureSequence - automatically set sequence number if none provided
|
||||
// EnsureAccount - automatically set account number if none provided
|
||||
func EnsureAccountNumber(ctx CoreContext) (CoreContext, error) {
|
||||
// Should be viper.IsSet, but this does not work - https://github.com/spf13/viper/pull/331
|
||||
if viper.GetInt64(client.FlagAccountNumber) != 0 {
|
||||
|
|
|
@ -4,15 +4,21 @@ import "github.com/spf13/cobra"
|
|||
|
||||
// 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"
|
||||
)
|
||||
|
||||
// LineBreak can be included in a command list to provide a blank line
|
||||
|
@ -24,8 +30,9 @@ 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(FlagUseLedger, false, "Use a connected Ledger device")
|
||||
c.Flags().String(FlagChainID, "", "Chain ID of tendermint node")
|
||||
c.Flags().String(FlagNode, "tcp://localhost:46657", "<host>:<port> to tendermint rpc interface for this chain")
|
||||
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")
|
||||
}
|
||||
return cmds
|
||||
|
@ -34,13 +41,19 @@ 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(FlagName, "", "Name of private key with which to sign")
|
||||
c.Flags().String(FlagFrom, "", "Name of private key with which to sign")
|
||||
c.Flags().String(FlagName, "", "DEPRECATED - Name 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")
|
||||
c.Flags().String(FlagFee, "", "Fee to pay along with transaction")
|
||||
c.Flags().String(FlagChainID, "", "Chain ID of tendermint node")
|
||||
c.Flags().String(FlagNode, "tcp://localhost:46657", "<host>:<port> to tendermint rpc interface for this chain")
|
||||
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().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)")
|
||||
}
|
||||
return cmds
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) {
|
|||
return "", err
|
||||
}
|
||||
if len(pass) < MinPassLength {
|
||||
return "", errors.Errorf("Password must be at least %d characters", MinPassLength)
|
||||
return "", errors.Errorf("password must be at least %d characters", MinPassLength)
|
||||
}
|
||||
return pass, nil
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ func GetCheckPassword(prompt, prompt2 string, buf *bufio.Reader) (string, error)
|
|||
return "", err
|
||||
}
|
||||
if pass != pass2 {
|
||||
return "", errors.New("Passphrases don't match")
|
||||
return "", errors.New("passphrases don't match")
|
||||
}
|
||||
return pass, nil
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
"github.com/tendermint/go-crypto/keys/words"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
)
|
||||
|
||||
// GetKeyBase initializes a keybase based on the given db.
|
||||
|
@ -11,7 +10,6 @@ import (
|
|||
func GetKeyBase(db dbm.DB) keys.Keybase {
|
||||
keybase := keys.New(
|
||||
db,
|
||||
words.MustLoadCodec("english"),
|
||||
)
|
||||
return keybase
|
||||
}
|
||||
|
|
|
@ -12,8 +12,10 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
ccrypto "github.com/cosmos/cosmos-sdk/crypto"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -21,6 +23,8 @@ const (
|
|||
flagRecover = "recover"
|
||||
flagNoBackup = "no-backup"
|
||||
flagDryRun = "dry-run"
|
||||
flagAccount = "account"
|
||||
flagIndex = "index"
|
||||
)
|
||||
|
||||
func addKeyCommand() *cobra.Command {
|
||||
|
@ -32,13 +36,18 @@ If you select --seed/-s you can recover a key from the seed
|
|||
phrase, otherwise, a new key will be generated.`,
|
||||
RunE: runAddCmd,
|
||||
}
|
||||
cmd.Flags().StringP(flagType, "t", "ed25519", "Type of private key (ed25519|secp256k1|ledger)")
|
||||
cmd.Flags().StringP(flagType, "t", "secp256k1", "Type of private key (secp256k1|ed25519)")
|
||||
cmd.Flags().Bool(client.FlagUseLedger, false, "Store a local reference to a private key on a Ledger device")
|
||||
cmd.Flags().Bool(flagRecover, false, "Provide seed phrase to recover existing key instead of creating")
|
||||
cmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
|
||||
cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
|
||||
cmd.Flags().Uint32(flagAccount, 0, "Account number for HD derivation")
|
||||
cmd.Flags().Uint32(flagIndex, 0, "Index number for HD derivation")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
// TODO remove the above when addressing #1446
|
||||
func runAddCmd(cmd *cobra.Command, args []string) error {
|
||||
var kb keys.Keybase
|
||||
var err error
|
||||
|
@ -53,7 +62,7 @@ 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 errors.New("you must provide a name for the key")
|
||||
}
|
||||
name = args[0]
|
||||
kb, err = GetKeyBase()
|
||||
|
@ -70,21 +79,34 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
pass, err = client.GetCheckPassword(
|
||||
"Enter a passphrase for your key:",
|
||||
"Repeat the passphrase:", buf)
|
||||
if err != nil {
|
||||
return err
|
||||
// ask for a password when generating a local key
|
||||
if !viper.GetBool(client.FlagUseLedger) {
|
||||
pass, err = client.GetCheckPassword(
|
||||
"Enter a passphrase for your key:",
|
||||
"Repeat the passphrase:", buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool(flagRecover) {
|
||||
if viper.GetBool(client.FlagUseLedger) {
|
||||
account := uint32(viper.GetInt(flagAccount))
|
||||
index := uint32(viper.GetInt(flagIndex))
|
||||
path := ccrypto.DerivationPath{44, 118, account, 0, index}
|
||||
algo := keys.SigningAlgo(viper.GetString(flagType))
|
||||
info, err := kb.CreateLedger(name, path, algo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printCreate(info, "")
|
||||
} else if viper.GetBool(flagRecover) {
|
||||
seed, err := client.GetSeed(
|
||||
"Enter your recovery seed phrase:", buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := kb.Recover(name, pass, seed)
|
||||
info, err := kb.CreateKey(name, seed, pass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -92,8 +114,8 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
|||
viper.Set(flagNoBackup, true)
|
||||
printCreate(info, "")
|
||||
} else {
|
||||
algo := keys.CryptoAlgo(viper.GetString(flagType))
|
||||
info, seed, err := kb.Create(name, pass, algo)
|
||||
algo := keys.SigningAlgo(viper.GetString(flagType))
|
||||
info, seed, err := kb.CreateMnemonic(name, keys.English, pass, algo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -108,7 +130,7 @@ func printCreate(info keys.Info, seed string) {
|
|||
case "text":
|
||||
printInfo(info)
|
||||
// print seed unless requested not to.
|
||||
if !viper.GetBool(flagNoBackup) {
|
||||
if !viper.GetBool(client.FlagUseLedger) && !viper.GetBool(flagNoBackup) {
|
||||
fmt.Println("**Important** write this seed phrase in a safe place.")
|
||||
fmt.Println("It is the only way to recover your account if you ever forget your password.")
|
||||
fmt.Println()
|
||||
|
@ -139,7 +161,6 @@ func printCreate(info keys.Info, seed string) {
|
|||
type NewKeyBody struct {
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
Seed string `json:"seed"`
|
||||
}
|
||||
|
||||
// add new key REST handler
|
||||
|
@ -172,16 +193,11 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
|
|||
w.Write([]byte("You have to specify a password for the locally stored account."))
|
||||
return
|
||||
}
|
||||
if m.Seed == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("You have to specify a seed for the locally stored account."))
|
||||
return
|
||||
}
|
||||
|
||||
// check if already exists
|
||||
infos, err := kb.List()
|
||||
for _, i := range infos {
|
||||
if i.Name == m.Name {
|
||||
if i.GetName() == m.Name {
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
w.Write([]byte(fmt.Sprintf("Account with name %s already exists.", m.Name)))
|
||||
return
|
||||
|
@ -189,22 +205,38 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// create account
|
||||
info, err := kb.Recover(m.Name, m.Password, m.Seed)
|
||||
info, mnemonic, err := kb.CreateMnemonic(m.Name, keys.English, m.Password, keys.Secp256k1)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
w.Write([]byte(info.PubKey.Address().String()))
|
||||
|
||||
keyOutput, err := Bech32KeyOutput(info)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
keyOutput.Seed = mnemonic
|
||||
|
||||
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
|
||||
func getSeed(algo keys.CryptoAlgo) string {
|
||||
func getSeed(algo keys.SigningAlgo) string {
|
||||
kb := client.MockKeyBase()
|
||||
pass := "throwing-this-key-away"
|
||||
name := "inmemorykey"
|
||||
|
||||
_, seed, _ := kb.Create(name, pass, algo)
|
||||
_, seed, _ := kb.CreateMnemonic(name, keys.English, pass, algo)
|
||||
return seed
|
||||
}
|
||||
|
||||
|
@ -212,11 +244,11 @@ func getSeed(algo keys.CryptoAlgo) string {
|
|||
func SeedRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
algoType := vars["type"]
|
||||
// algo type defaults to ed25519
|
||||
// algo type defaults to secp256k1
|
||||
if algoType == "" {
|
||||
algoType = "ed25519"
|
||||
algoType = "secp256k1"
|
||||
}
|
||||
algo := keys.CryptoAlgo(algoType)
|
||||
algo := keys.SigningAlgo(algoType)
|
||||
|
||||
seed := getSeed(algo)
|
||||
w.Write([]byte(seed))
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/gorilla/mux"
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -25,14 +25,19 @@ func deleteKeyCommand() *cobra.Command {
|
|||
func runDeleteCmd(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
buf := client.BufferStdin()
|
||||
oldpass, err := client.GetPassword(
|
||||
"DANGER - enter password to permanently delete key:", buf)
|
||||
kb, err := GetKeyBase()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kb, err := GetKeyBase()
|
||||
_, err = kb.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := client.BufferStdin()
|
||||
oldpass, err := client.GetPassword(
|
||||
"DANGER - enter password to permanently delete key:", buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/gorilla/mux"
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -28,7 +28,7 @@ var showKeysCmd = &cobra.Command{
|
|||
func getKey(name string) (keys.Info, error) {
|
||||
kb, err := GetKeyBase()
|
||||
if err != nil {
|
||||
return keys.Info{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kb.Get(name)
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/gorilla/mux"
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
|
|
@ -6,9 +6,9 @@ import (
|
|||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
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"
|
||||
|
||||
|
@ -48,10 +48,11 @@ func SetKeyBase(kb keys.Keybase) {
|
|||
|
||||
// used for outputting keys.Info over REST
|
||||
type KeyOutput struct {
|
||||
Name string `json:"name"`
|
||||
Address string `json:"address"`
|
||||
PubKey string `json:"pub_key"`
|
||||
Seed string `json:"seed,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Address sdk.AccAddress `json:"address"`
|
||||
PubKey string `json:"pub_key"`
|
||||
Seed string `json:"seed,omitempty"`
|
||||
}
|
||||
|
||||
// create a list of KeyOutput in bech32 format
|
||||
|
@ -69,17 +70,15 @@ func Bech32KeysOutput(infos []keys.Info) ([]KeyOutput, error) {
|
|||
|
||||
// create a KeyOutput in bech32 format
|
||||
func Bech32KeyOutput(info keys.Info) (KeyOutput, error) {
|
||||
bechAccount, err := sdk.Bech32ifyAcc(sdk.Address(info.PubKey.Address().Bytes()))
|
||||
if err != nil {
|
||||
return KeyOutput{}, err
|
||||
}
|
||||
bechPubKey, err := sdk.Bech32ifyAccPub(info.PubKey)
|
||||
account := sdk.AccAddress(info.GetPubKey().Address().Bytes())
|
||||
bechPubKey, err := sdk.Bech32ifyAccPub(info.GetPubKey())
|
||||
if err != nil {
|
||||
return KeyOutput{}, err
|
||||
}
|
||||
return KeyOutput{
|
||||
Name: info.Name,
|
||||
Address: bechAccount,
|
||||
Name: info.GetName(),
|
||||
Type: info.GetType(),
|
||||
Address: account,
|
||||
PubKey: bechPubKey,
|
||||
}, nil
|
||||
}
|
||||
|
@ -91,7 +90,7 @@ func printInfo(info keys.Info) {
|
|||
}
|
||||
switch viper.Get(cli.OutputFlag) {
|
||||
case "text":
|
||||
fmt.Printf("NAME:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n")
|
||||
fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n")
|
||||
printKeyOutput(ko)
|
||||
case "json":
|
||||
out, err := MarshalJSON(ko)
|
||||
|
@ -109,7 +108,7 @@ func printInfos(infos []keys.Info) {
|
|||
}
|
||||
switch viper.Get(cli.OutputFlag) {
|
||||
case "text":
|
||||
fmt.Printf("NAME:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n")
|
||||
fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n")
|
||||
for _, ko := range kos {
|
||||
printKeyOutput(ko)
|
||||
}
|
||||
|
@ -123,5 +122,5 @@ func printInfos(infos []keys.Info) {
|
|||
}
|
||||
|
||||
func printKeyOutput(ko KeyOutput) {
|
||||
fmt.Printf("%s\t%s\t%s\n", ko.Name, ko.Address, ko.PubKey)
|
||||
fmt.Printf("%s\t%s\t%s\t%s\n", ko.Name, ko.Type, ko.Address, ko.PubKey)
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,25 +4,24 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
tmserver "github.com/tendermint/tendermint/rpc/lib/server"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
|
||||
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"
|
||||
version "github.com/cosmos/cosmos-sdk/version"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
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/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmserver "github.com/tendermint/tendermint/rpc/lib/server"
|
||||
)
|
||||
|
||||
// ServeCommand will generate a long-running rest server
|
||||
|
@ -31,6 +30,7 @@ import (
|
|||
func ServeCommand(cdc *wire.Codec) *cobra.Command {
|
||||
flagListenAddr := "laddr"
|
||||
flagCORS := "cors"
|
||||
flagMaxOpenConnections := "max-open"
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rest-server",
|
||||
|
@ -38,32 +38,40 @@ func ServeCommand(cdc *wire.Codec) *cobra.Command {
|
|||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
listenAddr := viper.GetString(flagListenAddr)
|
||||
handler := createHandler(cdc)
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).
|
||||
With("module", "rest-server")
|
||||
listener, err := tmserver.StartHTTPServer(listenAddr, handler, logger)
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "rest-server")
|
||||
maxOpen := viper.GetInt(flagMaxOpenConnections)
|
||||
|
||||
listener, err := tmserver.StartHTTPServer(
|
||||
listenAddr, handler, logger,
|
||||
tmserver.Config{MaxOpenConnections: maxOpen},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("REST server started")
|
||||
|
||||
// Wait forever and cleanup
|
||||
// wait forever and cleanup
|
||||
cmn.TrapSignal(func() {
|
||||
err := listener.Close()
|
||||
logger.Error("Error closing listener", "err", err)
|
||||
logger.Error("error closing listener", "err", err)
|
||||
})
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringP(flagListenAddr, "a", "tcp://localhost:1317", "Address for server to listen on")
|
||||
cmd.Flags().String(flagCORS, "", "Set to domains that can make CORS requests (* for all)")
|
||||
cmd.Flags().StringP(client.FlagChainID, "c", "", "ID of chain we connect to")
|
||||
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
|
||||
|
||||
cmd.Flags().String(flagListenAddr, "tcp://localhost:1317", "The address for the server to listen on")
|
||||
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.FlagNode, "tcp://localhost:26657", "Address of the node to connect to")
|
||||
cmd.Flags().Int(flagMaxOpenConnections, 1000, "The number of maximum open connections")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func createHandler(cdc *wire.Codec) http.Handler {
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/version", version.RequestHandler).Methods("GET")
|
||||
|
||||
kb, err := keys.GetKeyBase() //XXX
|
||||
if err != nil {
|
||||
|
@ -72,7 +80,10 @@ func createHandler(cdc *wire.Codec) http.Handler {
|
|||
|
||||
ctx := context.NewCoreContextFromViper()
|
||||
|
||||
// TODO make more functional? aka r = keys.RegisterRoutes(r)
|
||||
// TODO: make more functional? aka r = keys.RegisterRoutes(r)
|
||||
r.HandleFunc("/version", CLIVersionRequestHandler).Methods("GET")
|
||||
r.HandleFunc("/node_version", NodeVersionRequestHandler(ctx)).Methods("GET")
|
||||
|
||||
keys.RegisterRoutes(r)
|
||||
rpc.RegisterRoutes(ctx, r)
|
||||
tx.RegisterRoutes(ctx, r, cdc)
|
||||
|
@ -80,5 +91,8 @@ func createHandler(cdc *wire.Codec) http.Handler {
|
|||
bank.RegisterRoutes(ctx, r, cdc, kb)
|
||||
ibc.RegisterRoutes(ctx, r, cdc, kb)
|
||||
stake.RegisterRoutes(ctx, r, cdc, kb)
|
||||
slashing.RegisterRoutes(ctx, r, cdc, kb)
|
||||
gov.RegisterRoutes(ctx, r, cdc)
|
||||
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -15,18 +15,18 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
crkeys "github.com/tendermint/go-crypto/keys"
|
||||
crkeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmcfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
nm "github.com/tendermint/tendermint/node"
|
||||
pvm "github.com/tendermint/tendermint/privval"
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
tmrpc "github.com/tendermint/tendermint/rpc/lib/server"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
keys "github.com/cosmos/cosmos-sdk/client/keys"
|
||||
|
@ -80,33 +80,33 @@ func GetKB(t *testing.T) crkeys.Keybase {
|
|||
}
|
||||
|
||||
// add an address to the store return name and password
|
||||
func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (addr sdk.Address, seed string) {
|
||||
func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (addr sdk.AccAddress, seed string) {
|
||||
var info crkeys.Info
|
||||
var err error
|
||||
info, seed, err = kb.Create(name, password, crkeys.AlgoEd25519)
|
||||
info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1)
|
||||
require.NoError(t, err)
|
||||
addr = info.PubKey.Address()
|
||||
addr = sdk.AccAddress(info.GetPubKey().Address())
|
||||
return
|
||||
}
|
||||
|
||||
// strt TM and the LCD in process, listening on their respective sockets
|
||||
// nValidators = number of validators
|
||||
// initAddrs = accounts to initialize with some steaks
|
||||
func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.Address) (cleanup func(), validatorsPKs []crypto.PubKey, port string) {
|
||||
func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress) (cleanup func(), validatorsPKs []crypto.PubKey, port string) {
|
||||
|
||||
config := GetConfig()
|
||||
config.Consensus.TimeoutCommit = 1000
|
||||
config.Consensus.TimeoutCommit = 100
|
||||
config.Consensus.SkipTimeoutCommit = false
|
||||
config.TxIndex.IndexAllTags = true
|
||||
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
logger = log.NewFilter(logger, log.AllowError())
|
||||
logger = log.NewFilter(logger, log.AllowDebug())
|
||||
privValidatorFile := config.PrivValidatorFile()
|
||||
privVal := pvm.LoadOrGenFilePV(privValidatorFile)
|
||||
privVal.Reset()
|
||||
db := dbm.NewMemDB()
|
||||
app := gapp.NewGaiaApp(logger, db)
|
||||
cdc = gapp.MakeCodec() // XXX
|
||||
cdc = gapp.MakeCodec()
|
||||
|
||||
genesisFile := config.GenesisFile()
|
||||
genDoc, err := tmtypes.GenesisDocFromFile(genesisFile)
|
||||
|
@ -132,7 +132,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.Address) (
|
|||
for _, gdValidator := range genDoc.Validators {
|
||||
pk := gdValidator.PubKey
|
||||
validatorsPKs = append(validatorsPKs, pk) // append keys for output
|
||||
appGenTx, _, _, err := gapp.GaiaAppGenTxNF(cdc, pk, pk.Address(), "test_val1", true)
|
||||
appGenTx, _, _, err := gapp.GaiaAppGenTxNF(cdc, pk, sdk.AccAddress(pk.Address()), "test_val1")
|
||||
require.NoError(t, err)
|
||||
appGenTxs = append(appGenTxs, appGenTx)
|
||||
}
|
||||
|
@ -143,9 +143,10 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.Address) (
|
|||
// add some tokens to init accounts
|
||||
for _, addr := range initAddrs {
|
||||
accAuth := auth.NewBaseAccountWithAddress(addr)
|
||||
accAuth.Coins = sdk.Coins{{"steak", 100}}
|
||||
accAuth.Coins = sdk.Coins{sdk.NewCoin("steak", 100)}
|
||||
acc := gapp.NewGenesisAccount(&accAuth)
|
||||
genesisState.Accounts = append(genesisState.Accounts, acc)
|
||||
genesisState.StakeData.Pool.LooseTokens += 100
|
||||
}
|
||||
|
||||
appState, err := wire.MarshalJSONIndent(cdc, genesisState)
|
||||
|
@ -168,7 +169,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.Address) (
|
|||
|
||||
//time.Sleep(time.Second)
|
||||
//tests.WaitForHeight(2, port)
|
||||
tests.WaitForStart(port)
|
||||
tests.WaitForLCDStart(port)
|
||||
tests.WaitForHeight(1, port)
|
||||
|
||||
// for use in defer
|
||||
|
@ -192,6 +193,7 @@ func startTM(tmcfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc,
|
|||
proxy.NewLocalClientCreator(app),
|
||||
genDocProvider,
|
||||
dbProvider,
|
||||
nm.DefaultMetricsProvider,
|
||||
logger.With("module", "node"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -212,7 +214,7 @@ func startTM(tmcfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc,
|
|||
// start the LCD. note this blocks!
|
||||
func startLCD(logger log.Logger, listenAddr string, cdc *wire.Codec) (net.Listener, error) {
|
||||
handler := createHandler(cdc)
|
||||
return tmrpc.StartHTTPServer(listenAddr, handler, logger)
|
||||
return tmrpc.StartHTTPServer(listenAddr, handler, logger, tmrpc.Config{})
|
||||
}
|
||||
|
||||
// make a test lcd test request
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package lcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
)
|
||||
|
||||
// cli version REST handler endpoint
|
||||
func CLIVersionRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
v := version.GetVersion()
|
||||
w.Write([]byte(v))
|
||||
}
|
||||
|
||||
// connected node version REST handler endpoint
|
||||
func NodeVersionRequestHandler(ctx context.CoreContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
version, err := ctx.Query("/app/version")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(fmt.Sprintf("Could't query version. Error: %s", err.Error())))
|
||||
return
|
||||
}
|
||||
w.Write(version)
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ func BlockCommand() *cobra.Command {
|
|||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: printBlock,
|
||||
}
|
||||
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
|
||||
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)")
|
||||
|
|
|
@ -18,7 +18,7 @@ const (
|
|||
|
||||
// XXX: remove this when not needed
|
||||
func todoNotImplemented(_ *cobra.Command, _ []string) error {
|
||||
return errors.New("TODO: Command not yet implemented")
|
||||
return errors.New("todo: Command not yet implemented")
|
||||
}
|
||||
|
||||
// AddCommands adds a number of rpc-related subcommands
|
||||
|
@ -36,7 +36,7 @@ func initClientCommand() *cobra.Command {
|
|||
RunE: todoNotImplemented,
|
||||
}
|
||||
cmd.Flags().StringP(client.FlagChainID, "c", "", "ID of chain we connect to")
|
||||
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
|
||||
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
|
||||
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)")
|
||||
|
|
|
@ -18,7 +18,7 @@ func statusCommand() *cobra.Command {
|
|||
Short: "Query remote node for status",
|
||||
RunE: printNodeStatus,
|
||||
}
|
||||
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
|
||||
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ func NodeSyncingRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
syncing := status.SyncInfo.Syncing
|
||||
syncing := status.SyncInfo.CatchingUp
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
|
|
|
@ -24,7 +24,7 @@ func ValidatorCommand() *cobra.Command {
|
|||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: printValidators,
|
||||
}
|
||||
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
|
||||
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")
|
||||
return cmd
|
||||
|
@ -32,10 +32,10 @@ func ValidatorCommand() *cobra.Command {
|
|||
|
||||
// Validator output in bech32 format
|
||||
type ValidatorOutput struct {
|
||||
Address string `json:"address"` // in bech32
|
||||
PubKey string `json:"pub_key"` // in bech32
|
||||
Accum int64 `json:"accum"`
|
||||
VotingPower int64 `json:"voting_power"`
|
||||
Address sdk.ValAddress `json:"address"` // in bech32
|
||||
PubKey string `json:"pub_key"` // in bech32
|
||||
Accum int64 `json:"accum"`
|
||||
VotingPower int64 `json:"voting_power"`
|
||||
}
|
||||
|
||||
// Validators at a certain height output in bech32 format
|
||||
|
@ -45,17 +45,13 @@ type ResultValidatorsOutput struct {
|
|||
}
|
||||
|
||||
func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error) {
|
||||
bechAddress, err := sdk.Bech32ifyVal(validator.Address)
|
||||
if err != nil {
|
||||
return ValidatorOutput{}, err
|
||||
}
|
||||
bechValPubkey, err := sdk.Bech32ifyValPub(validator.PubKey)
|
||||
if err != nil {
|
||||
return ValidatorOutput{}, err
|
||||
}
|
||||
|
||||
return ValidatorOutput{
|
||||
Address: bechAddress,
|
||||
Address: sdk.ValAddress(validator.Address),
|
||||
PubKey: bechValPubkey,
|
||||
Accum: validator.Accum,
|
||||
VotingPower: validator.VotingPower,
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
// Tx Broadcast Body
|
||||
type BroadcastTxBody struct {
|
||||
TxBytes string `json="tx"`
|
||||
TxBytes string `json:"tx"`
|
||||
}
|
||||
|
||||
// BroadcastTx REST Handler
|
||||
|
|
|
@ -2,15 +2,16 @@ package tx
|
|||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"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/abci/types"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
|
@ -42,7 +43,7 @@ func QueryTxCmd(cdc *wire.Codec) *cobra.Command {
|
|||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
|
||||
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")
|
||||
|
@ -70,7 +71,7 @@ func queryTx(cdc *wire.Codec, ctx context.CoreContext, hashHexStr string, trustN
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return json.MarshalIndent(info, "", " ")
|
||||
return wire.MarshalJSONIndent(cdc, info)
|
||||
}
|
||||
|
||||
func formatTxResult(cdc *wire.Codec, res *ctypes.ResultTx) (txInfo, error) {
|
||||
|
@ -81,6 +82,7 @@ func formatTxResult(cdc *wire.Codec, res *ctypes.ResultTx) (txInfo, error) {
|
|||
}
|
||||
|
||||
info := txInfo{
|
||||
Hash: res.Hash,
|
||||
Height: res.Height,
|
||||
Tx: tx,
|
||||
Result: res.TxResult,
|
||||
|
@ -90,6 +92,7 @@ func formatTxResult(cdc *wire.Codec, res *ctypes.ResultTx) (txInfo, error) {
|
|||
|
||||
// txInfo is used to prepare info to display
|
||||
type txInfo struct {
|
||||
Hash common.HexBytes `json:"hash"`
|
||||
Height int64 `json:"height"`
|
||||
Tx sdk.Tx `json:"tx"`
|
||||
Result abci.ResponseDeliverTx `json:"result"`
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -43,7 +44,7 @@ func SearchTxCmd(cdc *wire.Codec) *cobra.Command {
|
|||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
|
||||
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")
|
||||
|
@ -54,7 +55,7 @@ func SearchTxCmd(cdc *wire.Codec) *cobra.Command {
|
|||
|
||||
func searchTxs(ctx context.CoreContext, cdc *wire.Codec, tags []string) ([]txInfo, error) {
|
||||
if len(tags) == 0 {
|
||||
return nil, errors.New("Must declare at least one tag to search")
|
||||
return nil, errors.New("must declare at least one tag to search")
|
||||
}
|
||||
// XXX: implement ANY
|
||||
query := strings.Join(tags, " AND ")
|
||||
|
@ -107,7 +108,12 @@ func SearchTxRequestHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.Han
|
|||
}
|
||||
keyValue := strings.Split(tag, "=")
|
||||
key := keyValue[0]
|
||||
value := keyValue[1]
|
||||
value, err := url.QueryUnescape(keyValue[1])
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Could not decode address: " + err.Error()))
|
||||
return
|
||||
}
|
||||
if strings.HasSuffix(key, "_bech32") {
|
||||
bech32address := strings.Trim(value, "'")
|
||||
prefix := strings.Split(bech32address, "1")[0]
|
||||
|
@ -118,7 +124,7 @@ func SearchTxRequestHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.Han
|
|||
return
|
||||
}
|
||||
|
||||
tag = strings.TrimRight(key, "_bech32") + "='" + sdk.Address(bz).String() + "'"
|
||||
tag = strings.TrimRight(key, "_bech32") + "='" + sdk.AccAddress(bz).String() + "'"
|
||||
}
|
||||
|
||||
txs, err := searchTxs(ctx, cdc, []string{tag})
|
||||
|
|
|
@ -5,15 +5,15 @@ import (
|
|||
"net/http"
|
||||
|
||||
keybase "github.com/cosmos/cosmos-sdk/client/keys"
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
)
|
||||
|
||||
// REST request body
|
||||
// TODO does this need to be exposed?
|
||||
type SignTxBody struct {
|
||||
Name string `json="name"`
|
||||
Password string `json="password"`
|
||||
TxBytes string `json="tx"`
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
TxBytes string `json:"tx"`
|
||||
}
|
||||
|
||||
// sign transaction REST Handler
|
||||
|
|
|
@ -4,17 +4,18 @@ import (
|
|||
"encoding/json"
|
||||
"os"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
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"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
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/slashing"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
|
@ -36,11 +37,13 @@ type GaiaApp struct {
|
|||
cdc *wire.Codec
|
||||
|
||||
// keys to access the substores
|
||||
keyMain *sdk.KVStoreKey
|
||||
keyAccount *sdk.KVStoreKey
|
||||
keyIBC *sdk.KVStoreKey
|
||||
keyStake *sdk.KVStoreKey
|
||||
keySlashing *sdk.KVStoreKey
|
||||
keyMain *sdk.KVStoreKey
|
||||
keyAccount *sdk.KVStoreKey
|
||||
keyIBC *sdk.KVStoreKey
|
||||
keyStake *sdk.KVStoreKey
|
||||
keySlashing *sdk.KVStoreKey
|
||||
keyGov *sdk.KVStoreKey
|
||||
keyFeeCollection *sdk.KVStoreKey
|
||||
|
||||
// Manage getting and setting accounts
|
||||
accountMapper auth.AccountMapper
|
||||
|
@ -49,6 +52,7 @@ type GaiaApp struct {
|
|||
ibcMapper ibc.Mapper
|
||||
stakeKeeper stake.Keeper
|
||||
slashingKeeper slashing.Keeper
|
||||
govKeeper gov.Keeper
|
||||
}
|
||||
|
||||
func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
||||
|
@ -56,20 +60,22 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
|||
|
||||
// create your application object
|
||||
var app = &GaiaApp{
|
||||
BaseApp: bam.NewBaseApp(appName, cdc, logger, db),
|
||||
cdc: cdc,
|
||||
keyMain: sdk.NewKVStoreKey("main"),
|
||||
keyAccount: sdk.NewKVStoreKey("acc"),
|
||||
keyIBC: sdk.NewKVStoreKey("ibc"),
|
||||
keyStake: sdk.NewKVStoreKey("stake"),
|
||||
keySlashing: sdk.NewKVStoreKey("slashing"),
|
||||
BaseApp: bam.NewBaseApp(appName, cdc, logger, db),
|
||||
cdc: cdc,
|
||||
keyMain: sdk.NewKVStoreKey("main"),
|
||||
keyAccount: sdk.NewKVStoreKey("acc"),
|
||||
keyIBC: sdk.NewKVStoreKey("ibc"),
|
||||
keyStake: sdk.NewKVStoreKey("stake"),
|
||||
keySlashing: sdk.NewKVStoreKey("slashing"),
|
||||
keyGov: sdk.NewKVStoreKey("gov"),
|
||||
keyFeeCollection: sdk.NewKVStoreKey("fee"),
|
||||
}
|
||||
|
||||
// define the accountMapper
|
||||
app.accountMapper = auth.NewAccountMapper(
|
||||
app.cdc,
|
||||
app.keyAccount, // target store
|
||||
&auth.BaseAccount{}, // prototype
|
||||
app.keyAccount, // target store
|
||||
auth.ProtoBaseAccount, // prototype
|
||||
)
|
||||
|
||||
// add handlers
|
||||
|
@ -77,20 +83,23 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
|||
app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace))
|
||||
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.RegisterCodespace(slashing.DefaultCodespace))
|
||||
app.govKeeper = gov.NewKeeper(app.cdc, app.keyGov, app.coinKeeper, app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace))
|
||||
app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.keyFeeCollection)
|
||||
|
||||
// register message routes
|
||||
app.Router().
|
||||
AddRoute("bank", bank.NewHandler(app.coinKeeper)).
|
||||
AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.coinKeeper)).
|
||||
AddRoute("stake", stake.NewHandler(app.stakeKeeper)).
|
||||
AddRoute("slashing", slashing.NewHandler(app.slashingKeeper))
|
||||
AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)).
|
||||
AddRoute("gov", gov.NewHandler(app.govKeeper))
|
||||
|
||||
// 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.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing, app.keyGov, app.keyFeeCollection)
|
||||
err := app.LoadLatestVersion(app.keyMain)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
|
@ -106,6 +115,7 @@ func MakeCodec() *wire.Codec {
|
|||
bank.RegisterWire(cdc)
|
||||
stake.RegisterWire(cdc)
|
||||
slashing.RegisterWire(cdc)
|
||||
gov.RegisterWire(cdc)
|
||||
auth.RegisterWire(cdc)
|
||||
sdk.RegisterWire(cdc)
|
||||
wire.RegisterCrypto(cdc)
|
||||
|
@ -122,11 +132,15 @@ 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 {
|
||||
validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper)
|
||||
|
||||
tags, _ := gov.EndBlocker(ctx, app.govKeeper)
|
||||
|
||||
return abci.ResponseEndBlock{
|
||||
ValidatorUpdates: validatorUpdates,
|
||||
Tags: tags,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,7 +164,13 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci
|
|||
}
|
||||
|
||||
// load the initial stake information
|
||||
stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData)
|
||||
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, "")
|
||||
}
|
||||
|
||||
gov.InitGenesis(ctx, app.govKeeper, gov.DefaultGenesisState())
|
||||
|
||||
return abci.ResponseInitChain{}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error {
|
||||
|
|
|
@ -5,17 +5,23 @@ import (
|
|||
"errors"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
|
||||
"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"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
)
|
||||
|
||||
var (
|
||||
// bonded tokens given to genesis validators/accounts
|
||||
freeFermionVal = int64(100)
|
||||
freeFermionsAcc = int64(50)
|
||||
)
|
||||
|
||||
// State to Unmarshal
|
||||
type GenesisState struct {
|
||||
Accounts []GenesisAccount `json:"accounts"`
|
||||
|
@ -24,8 +30,8 @@ type GenesisState struct {
|
|||
|
||||
// GenesisAccount doesn't need pubkey or sequence
|
||||
type GenesisAccount struct {
|
||||
Address sdk.Address `json:"address"`
|
||||
Coins sdk.Coins `json:"coins"`
|
||||
Address sdk.AccAddress `json:"address"`
|
||||
Coins sdk.Coins `json:"coins"`
|
||||
}
|
||||
|
||||
func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount {
|
||||
|
@ -50,25 +56,15 @@ func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount) {
|
|||
}
|
||||
}
|
||||
|
||||
var (
|
||||
flagName = "name"
|
||||
flagClientHome = "home-client"
|
||||
flagOWK = "owk"
|
||||
|
||||
// bonded tokens given to genesis validators/accounts
|
||||
freeFermionVal = int64(100)
|
||||
freeFermionsAcc = int64(50)
|
||||
)
|
||||
|
||||
// get app init parameters for server init command
|
||||
func GaiaAppInit() server.AppInit {
|
||||
fsAppGenState := pflag.NewFlagSet("", pflag.ContinueOnError)
|
||||
|
||||
fsAppGenTx := pflag.NewFlagSet("", pflag.ContinueOnError)
|
||||
fsAppGenTx.String(flagName, "", "validator moniker, required")
|
||||
fsAppGenTx.String(flagClientHome, DefaultCLIHome,
|
||||
fsAppGenTx.String(server.FlagName, "", "validator moniker, required")
|
||||
fsAppGenTx.String(server.FlagClientHome, DefaultCLIHome,
|
||||
"home directory for the client, used for key generation")
|
||||
fsAppGenTx.Bool(flagOWK, false, "overwrite the accounts created")
|
||||
fsAppGenTx.Bool(server.FlagOWK, false, "overwrite the accounts created")
|
||||
|
||||
return server.AppInit{
|
||||
FlagsAppGenState: fsAppGenState,
|
||||
|
@ -80,24 +76,21 @@ func GaiaAppInit() server.AppInit {
|
|||
|
||||
// simple genesis tx
|
||||
type GaiaGenTx struct {
|
||||
Name string `json:"name"`
|
||||
Address sdk.Address `json:"address"`
|
||||
PubKey crypto.PubKey `json:"pub_key"`
|
||||
Name string `json:"name"`
|
||||
Address sdk.AccAddress `json:"address"`
|
||||
PubKey string `json:"pub_key"`
|
||||
}
|
||||
|
||||
// Generate a gaia genesis transaction with flags
|
||||
func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey) (
|
||||
func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey, genTxConfig config.GenTx) (
|
||||
appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) {
|
||||
clientRoot := viper.GetString(flagClientHome)
|
||||
overwrite := viper.GetBool(flagOWK)
|
||||
name := viper.GetString(flagName)
|
||||
if name == "" {
|
||||
if genTxConfig.Name == "" {
|
||||
return nil, nil, tmtypes.GenesisValidator{}, errors.New("Must specify --name (validator moniker)")
|
||||
}
|
||||
|
||||
var addr sdk.Address
|
||||
var addr sdk.AccAddress
|
||||
var secret string
|
||||
addr, secret, err = server.GenerateSaveCoinKey(clientRoot, name, "1234567890", overwrite)
|
||||
addr, secret, err = server.GenerateSaveCoinKey(genTxConfig.CliRoot, genTxConfig.Name, "1234567890", genTxConfig.Overwrite)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -107,20 +100,22 @@ func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey) (
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cliPrint = json.RawMessage(bz)
|
||||
appGenTx,_,validator,err = GaiaAppGenTxNF(cdc, pk, addr, name, overwrite)
|
||||
|
||||
appGenTx, _, validator, err = GaiaAppGenTxNF(cdc, pk, addr, genTxConfig.Name)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate a gaia genesis transaction without flags
|
||||
func GaiaAppGenTxNF(cdc *wire.Codec, pk crypto.PubKey, addr sdk.Address, name string, overwrite bool) (
|
||||
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: pk,
|
||||
PubKey: sdk.MustBech32ifyAccPub(pk),
|
||||
}
|
||||
bz, err = wire.MarshalJSONIndent(cdc, gaiaGenTx)
|
||||
if err != nil {
|
||||
|
@ -160,23 +155,35 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState
|
|||
// 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", 1000},
|
||||
{"steak", freeFermionsAcc},
|
||||
{genTx.Name + "Token", sdk.NewInt(1000)},
|
||||
{"steak", sdk.NewInt(freeFermionsAcc)},
|
||||
}
|
||||
acc := NewGenesisAccount(&accAuth)
|
||||
genaccs[i] = acc
|
||||
stakeData.Pool.LooseUnbondedTokens += freeFermionsAcc // increase the supply
|
||||
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens + freeFermionsAcc // increase the supply
|
||||
|
||||
// add the validator
|
||||
if len(genTx.Name) > 0 {
|
||||
desc := stake.NewDescription(genTx.Name, "", "", "")
|
||||
validator := stake.NewValidator(genTx.Address, genTx.PubKey, desc)
|
||||
validator.PoolShares = stake.NewBondedShares(sdk.NewRat(freeFermionVal))
|
||||
validator := stake.NewValidator(genTx.Address,
|
||||
sdk.MustGetAccPubKeyBech32(genTx.PubKey), desc)
|
||||
|
||||
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens + 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)
|
||||
|
||||
// pool logic
|
||||
stakeData.Pool.BondedTokens += freeFermionVal
|
||||
stakeData.Pool.BondedShares = sdk.NewRat(stakeData.Pool.BondedTokens)
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,16 +5,16 @@ import (
|
|||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/stretchr/testify/assert"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
func TestToAccount(t *testing.T) {
|
||||
priv := crypto.GenPrivKeyEd25519()
|
||||
addr := sdk.Address(priv.PubKey().Address())
|
||||
addr := sdk.AccAddress(priv.PubKey().Address())
|
||||
authAcc := auth.NewBaseAccountWithAddress(addr)
|
||||
genAcc := NewGenesisAccount(&authAcc)
|
||||
assert.Equal(t, authAcc, *genAcc.ToAccount())
|
||||
require.Equal(t, authAcc, *genAcc.ToAccount())
|
||||
}
|
||||
|
||||
func TestGaiaAppGenTx(t *testing.T) {
|
||||
|
|
|
@ -3,11 +3,14 @@ package clitest
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
|
@ -15,140 +18,225 @@ import (
|
|||
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"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
func TestGaiaCLISend(t *testing.T) {
|
||||
var (
|
||||
pass = "1234567890"
|
||||
gaiadHome = ""
|
||||
gaiacliHome = ""
|
||||
)
|
||||
|
||||
tests.ExecuteT(t, "gaiad unsafe_reset_all")
|
||||
pass := "1234567890"
|
||||
executeWrite(t, "gaiacli keys delete foo", pass)
|
||||
executeWrite(t, "gaiacli keys delete bar", pass)
|
||||
chainID := executeInit(t, "gaiad init -o --name=foo")
|
||||
executeWrite(t, "gaiacli keys add bar", pass)
|
||||
func init() {
|
||||
gaiadHome, gaiacliHome = getTestingHomeDirs()
|
||||
}
|
||||
|
||||
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), pass)
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), pass)
|
||||
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), pass)
|
||||
|
||||
// get a free port, also setup some common flags
|
||||
servAddr, port, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
flags := fmt.Sprintf("--node=%v --chain-id=%v", servAddr, chainID)
|
||||
flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID)
|
||||
|
||||
// start gaiad server
|
||||
proc := tests.GoExecuteT(t, fmt.Sprintf("gaiad start --rpc.laddr=%v", servAddr))
|
||||
proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr))
|
||||
|
||||
defer proc.Stop(false)
|
||||
tests.WaitForStart(port)
|
||||
|
||||
fooAddr, _ := executeGetAddrPK(t, "gaiacli keys show foo --output=json")
|
||||
fooCech, err := sdk.Bech32ifyAcc(fooAddr)
|
||||
require.NoError(t, err)
|
||||
barAddr, _ := executeGetAddrPK(t, "gaiacli keys show bar --output=json")
|
||||
barCech, err := sdk.Bech32ifyAcc(barAddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags))
|
||||
assert.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak"))
|
||||
|
||||
executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barCech), pass)
|
||||
tests.WaitForTMStart(port)
|
||||
tests.WaitForNextHeightTM(port)
|
||||
|
||||
barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags))
|
||||
assert.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak"))
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags))
|
||||
assert.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak"))
|
||||
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))
|
||||
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), pass)
|
||||
tests.WaitForNextHeightTM(port)
|
||||
|
||||
barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli 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))
|
||||
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
// test autosequencing
|
||||
executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barCech), pass)
|
||||
executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), pass)
|
||||
tests.WaitForNextHeightTM(port)
|
||||
|
||||
barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags))
|
||||
assert.Equal(t, int64(20), barAcc.GetCoins().AmountOf("steak"))
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags))
|
||||
assert.Equal(t, int64(30), fooAcc.GetCoins().AmountOf("steak"))
|
||||
barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli 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))
|
||||
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), pass)
|
||||
tests.WaitForNextHeightTM(port)
|
||||
|
||||
barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli 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))
|
||||
require.Equal(t, int64(20), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
}
|
||||
|
||||
func TestGaiaCLICreateValidator(t *testing.T) {
|
||||
|
||||
tests.ExecuteT(t, "gaiad unsafe_reset_all")
|
||||
pass := "1234567890"
|
||||
executeWrite(t, "gaiacli keys delete foo", pass)
|
||||
executeWrite(t, "gaiacli keys delete bar", pass)
|
||||
chainID := executeInit(t, "gaiad init -o --name=foo")
|
||||
executeWrite(t, "gaiacli keys add bar", pass)
|
||||
tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe_reset_all", gaiadHome))
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s foo", gaiacliHome), pass)
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), pass)
|
||||
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), pass)
|
||||
|
||||
// get a free port, also setup some common flags
|
||||
servAddr, port, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
flags := fmt.Sprintf("--node=%v --chain-id=%v", servAddr, chainID)
|
||||
flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID)
|
||||
|
||||
// start gaiad server
|
||||
proc := tests.GoExecuteT(t, fmt.Sprintf("gaiad start --rpc.laddr=%v", servAddr))
|
||||
proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr))
|
||||
|
||||
defer proc.Stop(false)
|
||||
tests.WaitForStart(port)
|
||||
|
||||
fooAddr, _ := executeGetAddrPK(t, "gaiacli keys show foo --output=json")
|
||||
fooCech, err := sdk.Bech32ifyAcc(fooAddr)
|
||||
require.NoError(t, err)
|
||||
barAddr, barPubKey := executeGetAddrPK(t, "gaiacli keys show bar --output=json")
|
||||
barCech, err := sdk.Bech32ifyAcc(barAddr)
|
||||
require.NoError(t, err)
|
||||
barCeshPubKey, err := sdk.Bech32ifyValPub(barPubKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barCech), pass)
|
||||
tests.WaitForTMStart(port)
|
||||
tests.WaitForNextHeightTM(port)
|
||||
|
||||
barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags))
|
||||
assert.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak"))
|
||||
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags))
|
||||
assert.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak"))
|
||||
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)
|
||||
|
||||
executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), pass)
|
||||
tests.WaitForNextHeightTM(port)
|
||||
|
||||
barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli 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))
|
||||
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
// create validator
|
||||
cvStr := fmt.Sprintf("gaiacli stake create-validator %v", flags)
|
||||
cvStr += fmt.Sprintf(" --name=%v", "bar")
|
||||
cvStr += fmt.Sprintf(" --address-validator=%v", barCech)
|
||||
cvStr += fmt.Sprintf(" --pubkey=%v", barCeshPubKey)
|
||||
cvStr += fmt.Sprintf(" --from=%s", "bar")
|
||||
cvStr += fmt.Sprintf(" --address-validator=%s", barAddr)
|
||||
cvStr += fmt.Sprintf(" --pubkey=%s", barCeshPubKey)
|
||||
cvStr += fmt.Sprintf(" --amount=%v", "2steak")
|
||||
cvStr += fmt.Sprintf(" --moniker=%v", "bar-vally")
|
||||
|
||||
executeWrite(t, cvStr, pass)
|
||||
tests.WaitForNextHeightTM(port)
|
||||
|
||||
barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags))
|
||||
require.Equal(t, int64(8), barAcc.GetCoins().AmountOf("steak"), "%v", barAcc)
|
||||
barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli 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 %v --output=json %v", barCech, flags))
|
||||
assert.Equal(t, validator.Owner, barAddr)
|
||||
assert.Equal(t, "2/1", validator.PoolShares.Amount.String())
|
||||
validator := executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %s --output=json %v", barAddr, flags))
|
||||
require.Equal(t, validator.Owner, barAddr)
|
||||
require.Equal(t, "2/1", validator.PoolShares.Amount.String())
|
||||
|
||||
// unbond a single share
|
||||
unbondStr := fmt.Sprintf("gaiacli stake unbond %v", flags)
|
||||
unbondStr += fmt.Sprintf(" --name=%v", "bar")
|
||||
unbondStr += fmt.Sprintf(" --address-validator=%v", barCech)
|
||||
unbondStr += fmt.Sprintf(" --address-delegator=%v", barCech)
|
||||
unbondStr += fmt.Sprintf(" --shares=%v", "1")
|
||||
unbondStr += fmt.Sprintf(" --sequence=%v", "1")
|
||||
t.Log(fmt.Sprintf("debug unbondStr: %v\n", unbondStr))
|
||||
unbondStr := fmt.Sprintf("gaiacli stake unbond begin %v", flags)
|
||||
unbondStr += fmt.Sprintf(" --from=%s", "bar")
|
||||
unbondStr += fmt.Sprintf(" --address-validator=%s", barAddr)
|
||||
unbondStr += fmt.Sprintf(" --address-delegator=%s", barAddr)
|
||||
unbondStr += fmt.Sprintf(" --shares-amount=%v", "1")
|
||||
|
||||
executeWrite(t, unbondStr, pass)
|
||||
success := executeWrite(t, unbondStr, pass)
|
||||
require.True(t, success)
|
||||
tests.WaitForNextHeightTM(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))
|
||||
require.Equal(t, int64(9), barAcc.GetCoins().AmountOf("steak"), "%v", barAcc)
|
||||
validator = executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %v --output=json %v", barCech, flags))
|
||||
assert.Equal(t, "1/1", validator.PoolShares.Amount.String())
|
||||
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.PoolShares.Amount.String())
|
||||
}
|
||||
|
||||
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), pass)
|
||||
executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), pass)
|
||||
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), pass)
|
||||
|
||||
// get a free port, also setup some common flags
|
||||
servAddr, port, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
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.WaitForNextHeightTM(port)
|
||||
|
||||
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))
|
||||
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
executeWrite(t, fmt.Sprintf("gaiacli gov submit-proposal %v --proposer=%s --deposit=5steak --type=Text --title=Test --description=test --from=foo", flags, fooAddr), pass)
|
||||
tests.WaitForNextHeightTM(port)
|
||||
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(45), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
proposal1 := executeGetProposal(t, fmt.Sprintf("gaiacli gov query-proposal --proposalID=1 --output=json %v", flags))
|
||||
require.Equal(t, int64(1), proposal1.ProposalID)
|
||||
require.Equal(t, gov.StatusToString(gov.StatusDepositPeriod), proposal1.Status)
|
||||
|
||||
executeWrite(t, fmt.Sprintf("gaiacli gov deposit %v --depositer=%s --deposit=10steak --proposalID=1 --from=foo", flags, fooAddr), pass)
|
||||
tests.WaitForNextHeightTM(port)
|
||||
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
|
||||
require.Equal(t, int64(35), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
proposal1 = executeGetProposal(t, fmt.Sprintf("gaiacli gov query-proposal --proposalID=1 --output=json %v", flags))
|
||||
require.Equal(t, int64(1), proposal1.ProposalID)
|
||||
require.Equal(t, gov.StatusToString(gov.StatusVotingPeriod), proposal1.Status)
|
||||
|
||||
executeWrite(t, fmt.Sprintf("gaiacli gov vote %v --proposalID=1 --voter=%s --option=Yes --from=foo", flags, fooAddr), pass)
|
||||
tests.WaitForNextHeightTM(port)
|
||||
|
||||
vote := executeGetVote(t, fmt.Sprintf("gaiacli gov query-vote --proposalID=1 --voter=%s --output=json %v", fooAddr, flags))
|
||||
require.Equal(t, int64(1), vote.ProposalID)
|
||||
require.Equal(t, gov.VoteOptionToString(gov.OptionYes), vote.Option)
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
// helper methods
|
||||
|
||||
func getTestingHomeDirs() (string, string) {
|
||||
tmpDir := os.TempDir()
|
||||
gaiadHome := fmt.Sprintf("%s%s.test_gaiad", tmpDir, string(os.PathSeparator))
|
||||
gaiacliHome := fmt.Sprintf("%s%s.test_gaiacli", tmpDir, string(os.PathSeparator))
|
||||
return gaiadHome, gaiacliHome
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
// executors
|
||||
|
||||
func executeWrite(t *testing.T, cmdStr string, writes ...string) {
|
||||
func executeWrite(t *testing.T, cmdStr string, writes ...string) bool {
|
||||
proc := tests.GoExecuteT(t, cmdStr)
|
||||
|
||||
for _, write := range writes {
|
||||
_, err := proc.StdinPipe.Write([]byte(write + "\n"))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
stdout, stderr, err := proc.ReadAll()
|
||||
if err != nil {
|
||||
fmt.Println("Err on proc.ReadAll()", err, cmdStr)
|
||||
}
|
||||
// Log output.
|
||||
if len(stdout) > 0 {
|
||||
t.Log("Stdout:", cmn.Green(string(stdout)))
|
||||
}
|
||||
if len(stderr) > 0 {
|
||||
t.Log("Stderr:", cmn.Red(string(stderr)))
|
||||
}
|
||||
|
||||
proc.Wait()
|
||||
return proc.ExitState.Success()
|
||||
// bz := proc.StdoutBuffer.Bytes()
|
||||
// fmt.Println("EXEC WRITE", string(bz))
|
||||
}
|
||||
|
@ -166,18 +254,15 @@ func executeInit(t *testing.T, cmdStr string) (chainID string) {
|
|||
return
|
||||
}
|
||||
|
||||
func executeGetAddrPK(t *testing.T, cmdStr string) (sdk.Address, crypto.PubKey) {
|
||||
func executeGetAddrPK(t *testing.T, cmdStr string) (sdk.AccAddress, crypto.PubKey) {
|
||||
out := tests.ExecuteT(t, cmdStr)
|
||||
var ko keys.KeyOutput
|
||||
keys.UnmarshalJSON([]byte(out), &ko)
|
||||
|
||||
address, err := sdk.GetAccAddressBech32(ko.Address)
|
||||
require.NoError(t, err)
|
||||
|
||||
pk, err := sdk.GetAccPubKeyBech32(ko.PubKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
return address, pk
|
||||
return ko.Address, pk
|
||||
}
|
||||
|
||||
func executeGetAccount(t *testing.T, cmdStr string) auth.BaseAccount {
|
||||
|
@ -202,3 +287,21 @@ func executeGetValidator(t *testing.T, cmdStr string) stake.Validator {
|
|||
require.NoError(t, err, "out %v\n, err %v", out, err)
|
||||
return validator
|
||||
}
|
||||
|
||||
func executeGetProposal(t *testing.T, cmdStr string) gov.ProposalRest {
|
||||
out := tests.ExecuteT(t, cmdStr)
|
||||
var proposal gov.ProposalRest
|
||||
cdc := app.MakeCodec()
|
||||
err := cdc.UnmarshalJSON([]byte(out), &proposal)
|
||||
require.NoError(t, err, "out %v\n, err %v", out, err)
|
||||
return proposal
|
||||
}
|
||||
|
||||
func executeGetVote(t *testing.T, cmdStr string) gov.VoteRest {
|
||||
out := tests.ExecuteT(t, cmdStr)
|
||||
var vote gov.VoteRest
|
||||
cdc := app.MakeCodec()
|
||||
err := cdc.UnmarshalJSON([]byte(out), &vote)
|
||||
require.NoError(t, err, "out %v\n, err %v", out, err)
|
||||
return vote
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package main
|
|||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
|
@ -13,6 +13,7 @@ import (
|
|||
"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"
|
||||
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"
|
||||
|
@ -94,13 +95,34 @@ func main() {
|
|||
stakecmd.GetCmdCreateValidator(cdc),
|
||||
stakecmd.GetCmdEditValidator(cdc),
|
||||
stakecmd.GetCmdDelegate(cdc),
|
||||
stakecmd.GetCmdUnbond(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.AddCommand(
|
||||
client.PostCommands(
|
||||
govcmd.GetCmdSubmitProposal(cdc),
|
||||
govcmd.GetCmdDeposit(cdc),
|
||||
govcmd.GetCmdVote(cdc),
|
||||
)...)
|
||||
rootCmd.AddCommand(
|
||||
govCmd,
|
||||
)
|
||||
|
||||
//Add auth and bank commands
|
||||
rootCmd.AddCommand(
|
||||
client.GetCommands(
|
||||
|
@ -120,5 +142,9 @@ func main() {
|
|||
|
||||
// prepare and add flags
|
||||
executor := cli.PrepareMainCmd(rootCmd, "GA", app.DefaultCLIHome)
|
||||
executor.Execute()
|
||||
err := executor.Execute()
|
||||
if err != nil {
|
||||
// handle with #870
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,11 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
|
@ -31,7 +31,11 @@ func main() {
|
|||
|
||||
// prepare and add flags
|
||||
executor := cli.PrepareBaseCmd(rootCmd, "GA", app.DefaultNodeHome)
|
||||
executor.Execute()
|
||||
err := executor.Execute()
|
||||
if err != nil {
|
||||
// handle with #870
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func newApp(logger log.Logger, db dbm.DB) abci.Application {
|
||||
|
|
|
@ -8,11 +8,11 @@ import (
|
|||
"path"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
abci "github.com/tendermint/abci/types"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
bam "github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
@ -157,8 +157,8 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
|||
// define the accountMapper
|
||||
app.accountMapper = auth.NewAccountMapper(
|
||||
app.cdc,
|
||||
app.keyAccount, // target store
|
||||
&auth.BaseAccount{}, // prototype
|
||||
app.keyAccount, // target store
|
||||
auth.ProtoBaseAccount, // prototype
|
||||
)
|
||||
|
||||
// add handlers
|
||||
|
@ -197,6 +197,7 @@ func MakeCodec() *wire.Codec {
|
|||
auth.RegisterWire(cdc)
|
||||
sdk.RegisterWire(cdc)
|
||||
wire.RegisterCrypto(cdc)
|
||||
cdc.Seal()
|
||||
return cdc
|
||||
}
|
||||
|
||||
|
@ -210,6 +211,7 @@ 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 {
|
||||
validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper)
|
||||
|
||||
|
@ -226,8 +228,7 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci
|
|||
var genesisState gaia.GenesisState
|
||||
err := app.cdc.UnmarshalJSON(stateJSON, &genesisState)
|
||||
if err != nil {
|
||||
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
|
||||
// return sdk.ErrGenesisParse("").TraceCause(err, "")
|
||||
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 // return sdk.ErrGenesisParse("").TraceCause(err, "")
|
||||
}
|
||||
|
||||
// load the accounts
|
||||
|
@ -237,7 +238,10 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci
|
|||
}
|
||||
|
||||
// load the initial stake information
|
||||
stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData)
|
||||
return abci.ResponseInitChain{}
|
||||
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, "")
|
||||
}
|
||||
|
||||
return abci.ResponseInitChain{}
|
||||
}
|
||||
|
|
|
@ -11,14 +11,16 @@ import (
|
|||
"strings"
|
||||
|
||||
gaia "github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/spf13/cobra"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(txCmd)
|
||||
rootCmd.AddCommand(pubkeyCmd)
|
||||
rootCmd.AddCommand(addrCmd)
|
||||
rootCmd.AddCommand(hackCmd)
|
||||
rootCmd.AddCommand(rawBytesCmd)
|
||||
}
|
||||
|
@ -37,10 +39,16 @@ var txCmd = &cobra.Command{
|
|||
|
||||
var pubkeyCmd = &cobra.Command{
|
||||
Use: "pubkey",
|
||||
Short: "Decode a pubkey from hex or base64",
|
||||
Short: "Decode a pubkey from hex, base64, or bech32",
|
||||
RunE: runPubKeyCmd,
|
||||
}
|
||||
|
||||
var addrCmd = &cobra.Command{
|
||||
Use: "addr",
|
||||
Short: "Convert an address between hex and bech32",
|
||||
RunE: runAddrCmd,
|
||||
}
|
||||
|
||||
var hackCmd = &cobra.Command{
|
||||
Use: "hack",
|
||||
Short: "Boilerplate to Hack on an existing state by scripting some Go...",
|
||||
|
@ -80,30 +88,98 @@ func runPubKeyCmd(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
pubkeyString := args[0]
|
||||
var pubKeyI crypto.PubKey
|
||||
|
||||
// try hex, then base64
|
||||
// try hex, then base64, then bech32
|
||||
pubkeyBytes, err := hex.DecodeString(pubkeyString)
|
||||
if err != nil {
|
||||
var err2 error
|
||||
pubkeyBytes, err2 = base64.StdEncoding.DecodeString(pubkeyString)
|
||||
if err2 != nil {
|
||||
return fmt.Errorf(`Expected hex or base64. Got errors:
|
||||
var err3 error
|
||||
pubKeyI, err3 = sdk.GetAccPubKeyBech32(pubkeyString)
|
||||
if err3 != nil {
|
||||
var err4 error
|
||||
pubKeyI, err4 = sdk.GetValPubKeyBech32(pubkeyString)
|
||||
|
||||
if err4 != nil {
|
||||
return fmt.Errorf(`Expected hex, base64, or bech32. Got errors:
|
||||
hex: %v,
|
||||
base64: %v
|
||||
`, err, err2)
|
||||
bech32 acc: %v
|
||||
bech32 val: %v
|
||||
`, err, err2, err3, err4)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
cdc := gaia.MakeCodec()
|
||||
var pubKey crypto.PubKeyEd25519
|
||||
copy(pubKey[:], pubkeyBytes)
|
||||
if pubKeyI == nil {
|
||||
copy(pubKey[:], pubkeyBytes)
|
||||
} else {
|
||||
pubKey = pubKeyI.(crypto.PubKeyEd25519)
|
||||
pubkeyBytes = pubKey[:]
|
||||
}
|
||||
|
||||
cdc := gaia.MakeCodec()
|
||||
pubKeyJSONBytes, err := cdc.MarshalJSON(pubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accPub, err := sdk.Bech32ifyAccPub(pubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valPub, err := sdk.Bech32ifyValPub(pubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runAddrCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("Expected single arg")
|
||||
}
|
||||
|
||||
addrString := args[0]
|
||||
var addr []byte
|
||||
|
||||
// try hex, then bech32
|
||||
var err error
|
||||
addr, err = hex.DecodeString(addrString)
|
||||
if err != nil {
|
||||
var err2 error
|
||||
addr, err2 = sdk.AccAddressFromBech32(addrString)
|
||||
if err2 != nil {
|
||||
var err3 error
|
||||
addr, err3 = sdk.ValAddressFromBech32(addrString)
|
||||
|
||||
if err3 != nil {
|
||||
return fmt.Errorf(`Expected hex or bech32. Got errors:
|
||||
hex: %v,
|
||||
bech32 acc: %v
|
||||
bech32 val: %v
|
||||
`, err, err2, err3)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
accAddr := sdk.AccAddress(addr)
|
||||
valAddr := sdk.ValAddress(addr)
|
||||
|
||||
fmt.Println("Address:", addr)
|
||||
fmt.Println("Bech32 Acc:", accAddr)
|
||||
fmt.Println("Bech32 Val:", valAddr)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
# Connect to the `gaia-6002` Testnet
|
||||
|
||||
Note: We are aware this documentation is sub-par. We are working to
|
||||
improve the tooling and the documentation to make this process as painless as
|
||||
possible. In the meantime, join the
|
||||
[Validator Chat](https://riot.im/app/#/room/#cosmos_validators:matrix.org)
|
||||
for technical support. Thanks very much for your patience. :)
|
||||
Note: We are aware this documentation is a work in progress. We are actively
|
||||
working to improve the tooling and the documentation to make this process as painless as
|
||||
possible. In the meantime, join the [Validator Chat](https://riot.im/app/#/room/#cosmos_validators:matrix.org)
|
||||
for technical support, and [open issues](https://github.com/cosmos/cosmos-sdk) if you run into any! Thanks very much for your patience and support. :)
|
||||
|
||||
## Setting Up a New Node
|
||||
|
||||
|
@ -13,13 +12,20 @@ These instructions are for setting up a brand new full node from scratch. If you
|
|||
### Install Go
|
||||
|
||||
Install `go` by following the [official docs](https://golang.org/doc/install).
|
||||
**Go 1.10+** is required for the Cosmos SDK.
|
||||
**Go 1.10+** is required for the Cosmos SDK. Remember to properly setup your `$GOPATH`, `$GOBIN`, and `$PATH` variables, for example:
|
||||
|
||||
```bash
|
||||
mkdir -p $HOME/go/bin
|
||||
echo "export GOPATH=$HOME/go" >> ~/.bash_profile
|
||||
echo "export GOBIN=$GOPATH/bin" >> ~/.bash_profile
|
||||
echo "export PATH=$PATH:$GOBIN" >> ~/.bash_profile
|
||||
```
|
||||
|
||||
### Install Cosmos SDK
|
||||
|
||||
Next, let's install the testnet's version of the Cosmos SDK.
|
||||
Next, let's install the testnet's version of the Cosmos SDK.
|
||||
|
||||
```
|
||||
```bash
|
||||
mkdir -p $GOPATH/src/github.com/cosmos
|
||||
cd $GOPATH/src/github.com/cosmos
|
||||
git clone https://github.com/cosmos/cosmos-sdk
|
||||
|
@ -29,22 +35,27 @@ make get_tools && make get_vendor_deps && make install
|
|||
|
||||
That will install the `gaiad` and `gaiacli` binaries. Verify that everything is OK:
|
||||
|
||||
```
|
||||
gaiad version
|
||||
0.19.0-<commit>
|
||||
```bash
|
||||
$ gaiad version
|
||||
0.19.0-c6711810
|
||||
|
||||
$ gaiacli version
|
||||
0.19.0-c6711810
|
||||
```
|
||||
|
||||
### Node Setup
|
||||
|
||||
Create the required configuration files:
|
||||
Create the required configuration files, and initialize the node:
|
||||
|
||||
```
|
||||
gaiad init
|
||||
```bash
|
||||
gaiad init --name <your_custom_name>
|
||||
```
|
||||
|
||||
Name your node by editing the `moniker` in `$HOME/.gaiad/config/config.toml`. Note that only ASCII characters are supported. Using Unicode renders your node unconnectable.
|
||||
> *NOTE:* Note that only ASCII characters are supported for the `--name`. Using Unicode renders your node unreachable.
|
||||
|
||||
```
|
||||
You can also edit this `name` in the `~/.gaiad/config/config.toml` file:
|
||||
|
||||
```toml
|
||||
# A custom human readable name for this node
|
||||
moniker = "<your_custom_name>"
|
||||
```
|
||||
|
@ -59,12 +70,12 @@ These instructions are for full nodes that have ran on previous testnets and wou
|
|||
|
||||
First, remove the outdated files and reset the data.
|
||||
|
||||
```
|
||||
```bash
|
||||
rm $HOME/.gaiad/config/addrbook.json $HOME/.gaiad/config/genesis.json
|
||||
gaiad unsafe_reset_all
|
||||
```
|
||||
|
||||
Your node is now in a pristine state while keeping the original `priv_validator.json` and `config.toml`. If you had any sentry nodes or full nodes setup before,
|
||||
Your node is now in a pristine state while keeping the original `priv_validator.json` and `config.toml`. If you had any sentry nodes or full nodes setup before,
|
||||
your node will still try to connect to them, but may fail if they haven't also
|
||||
been upgraded.
|
||||
|
||||
|
@ -74,7 +85,7 @@ been upgraded.
|
|||
|
||||
Now it is time to upgrade the software:
|
||||
|
||||
```
|
||||
```bash
|
||||
cd $GOPATH/src/github.com/cosmos/cosmos-sdk
|
||||
git fetch --all && git checkout v0.19.0
|
||||
make update_tools && make get_vendor_deps && make install
|
||||
|
@ -88,171 +99,367 @@ Your full node has been cleanly upgraded!
|
|||
|
||||
Copy the testnet's `genesis.json` file and place it in `gaiad`'s config directory.
|
||||
|
||||
```
|
||||
```bash
|
||||
mkdir -p $HOME/.gaiad/config
|
||||
cp -a $GOPATH/src/github.com/cosmos/cosmos-sdk/cmd/gaia/testnets/gaia-6002/genesis.json $HOME/.gaiad/config/genesis.json
|
||||
```
|
||||
|
||||
### Add Seed Nodes
|
||||
|
||||
Your node needs to know how to find peers. You'll need to add healthy seed nodes to `$HOME/.gaiad/config/config.toml`. Here are some seed nodes you can use:
|
||||
Your node needs to know how to find peers. You'll need to add healthy seed nodes to `$HOME/.gaiad/config/config.toml`. Here are some seed nodes you can use:
|
||||
|
||||
```
|
||||
```toml
|
||||
# Comma separated list of seed nodes to connect to
|
||||
seeds = "38aa9bec3998f12ae9088b21a2d910d19d565c27@gaia-6002.coinculture.net:46656,80a35a46ce09cfb31ee220c8141a25e73e0b239b@seed.cosmos.cryptium.ch:46656,80a35a46ce09cfb31ee220c8141a25e73e0b239b@35.198.166.171:46656,032fa56301de335d835057fb6ad9f7ce2242a66d@165.227.236.213:46656"
|
||||
seeds = "38aa9bec3998f12ae9088b21a2d910d19d565c27@gaia-6002.coinculture.net:46656,1e124dd15bd9955a7ea844ab003b1b47f0998b70@seed.cosmos.cryptium.ch:46656"
|
||||
```
|
||||
|
||||
You can also [ask other validators](https://riot.im/app/#/room/#cosmos_validators:matrix.org) for a persistent peer and add it under the `persistent_peers` key. For more information on seeds and peers, [read this](https://github.com/tendermint/tendermint/blob/develop/docs/using-tendermint.md#peers).
|
||||
If those seeds aren't working, you can find more seeds and persistent peers on the [Cosmos Explorer](https://explorecosmos.network/nodes). Open the the `Full Nodes` pane and select nodes that do not have private (`10.x.x.x`) or [local IP addresses](https://en.wikipedia.org/wiki/Private_network). The `Persistent Peer` field contains the connection string. For best results use 4-6.
|
||||
|
||||
For more information on seeds and peers, [read this](https://github.com/tendermint/tendermint/blob/develop/docs/using-tendermint.md#peers).
|
||||
|
||||
## Run a Full Node
|
||||
|
||||
Start the full node with this command:
|
||||
|
||||
```
|
||||
```bash
|
||||
gaiad start
|
||||
```
|
||||
|
||||
Check that everything is running smoothly:
|
||||
|
||||
```
|
||||
```bash
|
||||
gaiacli status
|
||||
```
|
||||
|
||||
View the status of the network with the [Cosmos Explorer](https://explorecosmos.network). Once your full node syncs up to the current block height, you should see it appear on the [list of full nodes](https://explorecosmos.network/validators). If it doesn't show up, that's ok--the Explorer does not connect to every node.
|
||||
|
||||
## Generate Keys
|
||||
## Generating Keys
|
||||
|
||||
You'll need a private and public key pair \(a.k.a. `sk, pk` respectively\) to be able to receive funds, send txs, bond tx, etc.
|
||||
### A Note on Keys in Cosmos:
|
||||
|
||||
There are three types of key representations that are used in this tutorial:
|
||||
|
||||
- `cosmosaccaddr`
|
||||
* Derived from account keys generated by `gaiacli keys add`
|
||||
* Used to receive funds
|
||||
* e.g. `cosmosaccaddr15h6vd5f0wqps26zjlwrc6chah08ryu4hzzdwhc`
|
||||
|
||||
- `cosmosaccpub`
|
||||
* Derived from account keys generated by `gaiacli keys add`
|
||||
* e.g. `cosmosaccpub1zcjduc3q7fu03jnlu2xpl75s2nkt7krm6grh4cc5aqth73v0zwmea25wj2hsqhlqzm`
|
||||
|
||||
- `cosmosvalpub`
|
||||
* Generated when the node is created with `gaiad init`.
|
||||
* Get this value with `gaiad tendermint show_validator`
|
||||
* e.g. `cosmosvalpub1zcjduc3qcyj09qc03elte23zwshdx92jm6ce88fgc90rtqhjx8v0608qh5ssp0w94c`
|
||||
|
||||
### Key Generation
|
||||
|
||||
You'll need an account private and public key pair \(a.k.a. `sk, pk` respectively\) to be able to receive funds, send txs, bond tx, etc.
|
||||
|
||||
To generate a new key \(default _ed25519_ elliptic curve\):
|
||||
|
||||
```
|
||||
gaiacli keys add <your_key_name>
|
||||
```bash
|
||||
gaiacli keys add <account_name>
|
||||
```
|
||||
|
||||
Next, you will have to create a passphrase. Save the _seed_ _phrase_ in a safe place in case you forget the password.
|
||||
Next, you will have to create a passphrase to protect the key on disk. The output of the above command will contain a _seed phrase_. Save the _seed phrase_ in a safe place in case you forget the password!
|
||||
|
||||
If you check your private keys, you'll now see `<your_key_name>`:
|
||||
If you check your private keys, you'll now see `<account_name>`:
|
||||
|
||||
```
|
||||
gaiacli keys show <your_key_name>
|
||||
```bash
|
||||
gaiacli keys show <account_name>
|
||||
```
|
||||
|
||||
You can see all your available keys by typing:
|
||||
|
||||
```
|
||||
```bash
|
||||
gaiacli keys list
|
||||
```
|
||||
|
||||
View the validator pubkey for your node by typing:
|
||||
|
||||
```
|
||||
```bash
|
||||
gaiad tendermint show_validator
|
||||
```
|
||||
|
||||
Save your address and pubkey to environment variables for later use:
|
||||
|
||||
```
|
||||
MYADDR=<your_newly_generated_address>
|
||||
MYPUBKEY=<your_newly_generated_public_key>
|
||||
```
|
||||
|
||||
**WARNING:** We strongly recommend NOT using the same passphrase for multiple keys. The Tendermint team and the Interchain Foundation will not be responsible for the loss of funds.
|
||||
|
||||
## Get Tokens
|
||||
## Fund your account
|
||||
|
||||
The best way to get tokens is from the [Cosmos Testnet Faucet](https://faucetcosmos.network). If the faucet is not working for you, try asking [#cosmos-validators](https://riot.im/app/#/room/#cosmos-validators:matrix.org).
|
||||
The best way to get tokens is from the [Cosmos Testnet Faucet](https://faucetcosmos.network). If the faucet is not working for you, try asking [#cosmos-validators](https://riot.im/app/#/room/#cosmos-validators:matrix.org). The faucet needs the `cosmosaccaddr` from the account you wish to use for staking.
|
||||
|
||||
After receiving tokens to your address, you can view your account's balance by typing:
|
||||
After receiving tokens to your address, you can view your account's balance by typing:
|
||||
|
||||
```
|
||||
gaiacli account <your_newly_generated_address>
|
||||
```bash
|
||||
gaiacli account <account_cosmosaccaddr>
|
||||
```
|
||||
|
||||
Note: When you query an account balance with zero tokens, you will get this error: `No account with address <your_newly_generated_address> was found in the state.` This is expected! We're working on improving our error messages.
|
||||
|
||||
## Send Tokens
|
||||
|
||||
```
|
||||
gaiacli send --amount=10faucetToken --chain-id=<name_of_testnet_chain> --name=<key_name> --to=<destination_address>
|
||||
```
|
||||
|
||||
Note: The `--amount` flag accepts the format `--amount=<value|coin_name>`.
|
||||
|
||||
Now, view the updated balances of the origin and destination accounts:
|
||||
|
||||
```
|
||||
gaiacli account <origin_address>
|
||||
gaiacli account <destination_address>
|
||||
```
|
||||
|
||||
You can also check your balance at a given block by using the `--block` flag:
|
||||
|
||||
```
|
||||
gaiacli account <your_address> --block=<block_height>
|
||||
```
|
||||
> _*Note:*_ When you query an account balance with zero tokens, you will get this error: `No account with address <account_cosmosaccaddr> was found in the state.` This can also happen if you fund the account before your node has fully synced with the chain. These are both normal. Also, we're working on improving our error messages!
|
||||
|
||||
## Run a Validator Node
|
||||
|
||||
[Validators](https://cosmos.network/validators) are responsible for committing new blocks to the blockchain through voting. A validator's stake is slashed if they become unavailable, double sign a transaction, or don't cast their votes. If you only want to run a full node, a VM in the cloud is fine. However, if you are want to become a validator for the Hub's `mainnet`, you should research hardened setups. Please read [Sentry Node Architecture](https://github.com/cosmos/cosmos/blob/master/VALIDATORS_FAQ.md#how-can-validators-protect-themselves-from-denial-of-service-attacks) to protect your node from DDOS and ensure high-availability. Also see the [technical requirements](https://github.com/cosmos/cosmos/blob/master/VALIDATORS_FAQ.md#technical-requirements)). There's also more info on our [website](https://cosmos.network/validators).
|
||||
|
||||
Your `pubkey` can be used to create a new validator by staking tokens. You can find your validator pubkey by running:
|
||||
### Create Your Validator
|
||||
|
||||
```
|
||||
Your `cosmosvalpub` can be used to create a new validator by staking tokens. You can find your validator pubkey by running:
|
||||
|
||||
```bash
|
||||
gaiad tendermint show_validator
|
||||
```
|
||||
|
||||
Next, craft your `gaiacli stake create-validator` command:
|
||||
|
||||
```
|
||||
gaiacli stake create-validator --amount=5steak --pubkey=<your_node_pubkey> --address-validator=<your_address> --moniker=satoshi --chain-id=<name_of_the_testnet_chain> --name=<key_name>
|
||||
> _*NOTE:*_ Don't use more `steak` thank you have! You can always get more by using the [Faucet](https://faucetcosmos.network/)!
|
||||
|
||||
```bash
|
||||
gaiacli stake create-validator \
|
||||
--amount=5steak \
|
||||
--pubkey=$(gaiad tendermint show_validator) \
|
||||
--address-validator=<account_cosmosaccaddr>
|
||||
--moniker="choose a moniker" \
|
||||
--chain-id=gaia-6002 \
|
||||
--from=<key_name>
|
||||
```
|
||||
|
||||
You can add more information to the validator, such as`--website`, `--keybase-sig`, or `--details`. Here's how:
|
||||
### Edit Validator Description
|
||||
|
||||
```
|
||||
gaiacli stake edit-validator --details="To the cosmos !" --website="https://cosmos.network"
|
||||
You can edit your validator's public description. This info is to identify your validator, and will be relied on by delegators to decide which validators to stake to. Make sure to provide input for every flag below, otherwise the field will default to empty (`--moniker` defaults to the machine name).
|
||||
|
||||
The `--keybase-sig` is a 16-digit string that is generated with a [keybase.io](https://keybase.io) account. It's a cryptographically secure method of verifying your identity across multiple online networks. The Keybase API allows us to retrieve your Keybase avatar. This is how you can add a logo to your validator profile.
|
||||
|
||||
```bash
|
||||
gaiacli stake edit-validator
|
||||
--address-validator=<account_cosmosaccaddr>
|
||||
--moniker="choose a moniker" \
|
||||
--website="https://cosmos.network" \
|
||||
--keybase-sig="6A0D65E29A4CBC8E"
|
||||
--details="To infinity and beyond!"
|
||||
--chain-id=gaia-6002 \
|
||||
--from=<key_name>
|
||||
```
|
||||
|
||||
### View Validator Description
|
||||
View the validator's information with this command:
|
||||
|
||||
```
|
||||
gaiacli stake validator --address-validator=<your_address> --chain-id=<name_of_the_testnet_chain>
|
||||
```bash
|
||||
gaiacli stake validator \
|
||||
--address-validator=<account_cosmosaccaddr> \
|
||||
--chain-id=gaia-6002
|
||||
```
|
||||
|
||||
To check that the validator is active, look for it here:
|
||||
Your validator is active if the following command returns anything:
|
||||
|
||||
```
|
||||
gaiacli advanced tendermint validator-set
|
||||
```bash
|
||||
gaiacli advanced tendermint validator-set | grep "$(gaiad tendermint show_validator)"
|
||||
```
|
||||
|
||||
**Note:** To be in the validator set, you need to have more total voting power than the 100th validator.
|
||||
You should also be able to see your validator on the [Explorer](https://explorecosmos.network/validators). You are looking for the `bech32` encoded `address` in the `~/.gaiad/config/priv_validator.json` file.
|
||||
|
||||
> _*Note:*_ To be in the validator set, you need to have more total voting power than the 100th validator. This is not normally an issue.
|
||||
|
||||
### Problem #1: My validator has `voting_power: 0`
|
||||
|
||||
Your validator has become auto-unbonded. In `gaia-6002`, we unbond validators if they do not vote on `50` of the last `100` blocks. Since blocks are proposed every ~2 seconds, a validator unresponsive for ~100 seconds will become unbonded. This usually happens when your `gaiad` process crashes.
|
||||
|
||||
Here's how you can return the voting power back to your validator. First, if `gaiad` is not running, start it up again:
|
||||
|
||||
```bash
|
||||
gaiad start
|
||||
```
|
||||
|
||||
Wait for your full node to catch up to the latest block. Next, run the following command. Note that `<cosmosaccaddr>` is the address of your validator account, and `<name>` is the name of the validator account. You can find this info by running `gaiacli keys list`.
|
||||
|
||||
```bash
|
||||
gaiacli stake unrevoke <cosmosaccaddr> --chain-id=gaia-6002 --from=<name>
|
||||
```
|
||||
|
||||
**WARNING:** If you don't wait for `gaiad` to sync before running `unrevoke`, you will receive an error message telling you your validator is still jailed.
|
||||
|
||||
Lastly, check your validator again to see if your voting power is back.
|
||||
|
||||
```bash
|
||||
gaiacli status
|
||||
```
|
||||
|
||||
You may notice that your voting power is less than it used to be. That's because you got slashed for downtime!
|
||||
|
||||
### Problem #2: My `gaiad` crashes because of `too many open files`
|
||||
|
||||
The default number of files Linux can open (per-process) is `1024`. `gaiad` is known to open more than `1024` files. This causes the process to crash. A quick fix is to run `ulimit -n 4096` (increase the number of open files allowed) and then restart the process with `gaiad start`. If you are using `systemd` or another process manager to launch `gaiad` this may require some configuration at that level. A sample `systemd` file to fix this issue is below:
|
||||
|
||||
```toml
|
||||
# /etc/systemd/system/gaiad.service
|
||||
[Unit]
|
||||
Description=Cosmos Gaia Node
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=ubuntu
|
||||
WorkingDirectory=/home/ubuntu
|
||||
ExecStart=/home/ubuntu/go/bin/gaiad start
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
LimitNOFILE=4096
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
## Delegating to a Validator
|
||||
|
||||
On the upcoming mainnet, you can delegate `atom` to a validator. These [delegators](https://cosmos.network/resources/delegators) can receive part of the validator's fee revenue. Read more about the [Cosmos Token Model](https://github.com/cosmos/cosmos/raw/master/Cosmos_Token_Model.pdf).
|
||||
On the upcoming mainnet, you can delegate `Atom` to a validator. These [delegators](https://cosmos.network/resources/delegators) can receive part of the validator's fee revenue. Read more about the [Cosmos Token Model](https://github.com/cosmos/cosmos/raw/master/Cosmos_Token_Model.pdf).
|
||||
|
||||
### Bond Tokens
|
||||
|
||||
On the testnet, we delegate `steak` instead of `atom`. Here's how you can bond tokens to a testnet validator:
|
||||
On the testnet, we delegate `steak` instead of `Atom`. Here's how you can bond tokens to a testnet validator:
|
||||
|
||||
```
|
||||
gaiacli stake delegate --amount=10steak --address-delegator=<your_address> --address-validator=<bonded_validator_address> --name=<key_name> --chain-id=<name_of_testnet_chain>
|
||||
```bash
|
||||
gaiacli stake delegate \
|
||||
--amount=10steak \
|
||||
--address-delegator=<account_cosmosaccaddr> \
|
||||
--address-validator=<validator_cosmosaccaddr> \
|
||||
--from=<key_name> \
|
||||
--chain-id=gaia-6002
|
||||
```
|
||||
|
||||
While tokens are bonded, they are pooled with all the other bonded tokens in the network. Validators and delegators obtain a percentage of shares that equal their stake in this pool.
|
||||
While tokens are bonded, they are pooled with all the other bonded tokens in the network. Validators and delegators obtain a percentage of shares that equal their stake in this pool.
|
||||
|
||||
> _*NOTE:*_ Don't use more `steak` thank you have! You can always get more by using the [Faucet](https://faucetcosmos.network/)!
|
||||
|
||||
### Unbond Tokens
|
||||
|
||||
If for any reason the validator misbehaves, or you want to unbond a certain amount of tokens, use this following command. You can unbond a specific amount of`shares`\(eg:`12.1`\) or all of them \(`MAX`\).
|
||||
|
||||
```
|
||||
gaiacli stake unbond --address-delegator=<your_address> --address-validator=<bonded_validator_address> --shares=MAX --name=<key_name> --chain-id=<name_of_testnet_chain>
|
||||
```bash
|
||||
gaiacli stake unbond \
|
||||
--address-delegator=<account_cosmosaccaddr> \
|
||||
--address-validator=<validator_cosmosaccaddr> \
|
||||
--shares=MAX \
|
||||
--from=<key_name> \
|
||||
--chain-id=gaia-6002
|
||||
```
|
||||
|
||||
You can check your balance and your stake delegation to see that the unbonding went through successfully.
|
||||
|
||||
```bash
|
||||
gaiacli account <account_cosmosaccaddr>
|
||||
|
||||
gaiacli stake delegation \
|
||||
--address-delegator=<account_cosmosaccaddr> \
|
||||
--address-validator=<validator_cosmosaccaddr> \
|
||||
--chain-id=gaia-6002
|
||||
```
|
||||
gaiacli account <your_address>
|
||||
gaiacli stake delegation --address-delegator=<your_address> --address-validator=<bonded_validator_address> --chain-id=<name_of_testnet_chain>
|
||||
|
||||
## Governance
|
||||
|
||||
Governance is the process from which users in the Cosmos Hub can come to consensus on software upgrades, parameters of the mainnet or on custom text proposals. This is done through voting on proposals, which will be submitted by `Atom` holders on the mainnet.
|
||||
|
||||
Some considerations about the voting process:
|
||||
|
||||
- Voting is done by bonded `Atom` holders on a 1 bonded `Atom` 1 vote basis
|
||||
- Delegators inherit the vote of their validator if they don't vote
|
||||
- **Validators MUST vote on every proposal**. If a validator does not vote on a proposal, they will be **partially slashed**
|
||||
- Votes are tallied at the end of the voting period (2 weeks on mainnet). Each address can vote multiple times to update its `Option` value (paying the transaction fee each time), only the last casted vote will count as valid
|
||||
- Voters can choose between options `Yes`, `No`, `NoWithVeto` and `Abstain`
|
||||
At the end of the voting period, a proposal is accepted if `(YesVotes/(YesVotes+NoVotes+NoWithVetoVotes))>1/2` and `(NoWithVetoVotes/(YesVotes+NoVotes+NoWithVetoVotes))<1/3`. It is rejected otherwise
|
||||
|
||||
For more information about the governance process and how it works, please check out the Governance module [specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/governance).
|
||||
|
||||
### Create a Governance proposal
|
||||
|
||||
In order to create a governance proposal, you must submit an initial deposit along with the proposal details:
|
||||
|
||||
- `title`: Title of the proposal
|
||||
- `description`: Description of the proposal
|
||||
- `type`: Type of proposal. Must be of value _Text_ (types _SoftwareUpgrade_ and _ParameterChange_ not supported yet).
|
||||
|
||||
```bash
|
||||
gaiacli gov submit-proposal \
|
||||
--title=<title> \
|
||||
--description=<description> \
|
||||
--type=<Text/ParameterChange/SoftwareUpgrade> \
|
||||
--proposer=<account_cosmosaccaddr> \
|
||||
--deposit=<40steak> \
|
||||
--from=<name> \
|
||||
--chain-id=gaia-7000
|
||||
```
|
||||
|
||||
|
||||
### Increase deposit
|
||||
|
||||
In order for a proposal to be broadcasted to the network, the amount deposited must be above a `minDeposit` value (default: `10 steak`). If the proposal you previously created didn't meet this requirement, you can still increase the total amount deposited to activate it. Once the minimum deposit is reached, the proposal enters voting period:
|
||||
|
||||
```bash
|
||||
gaiacli gov deposit \
|
||||
--proposalID=<proposal_id> \
|
||||
--depositer=<account_cosmosaccaddr> \
|
||||
--deposit=<200steak> \
|
||||
--from=<name> \
|
||||
--chain-id=gaia-7000
|
||||
```
|
||||
|
||||
> _NOTE_: Proposals that don't meet this requirement will be deleted after `MaxDepositPeriod` is reached.
|
||||
|
||||
#### Query proposal
|
||||
|
||||
Once created, you can now query information of the proposal:
|
||||
|
||||
```bash
|
||||
gaiacli gov query-proposal \
|
||||
--proposalID=<proposal_id> \
|
||||
--chain-id=gaia-7000
|
||||
```
|
||||
|
||||
### Vote on a proposal
|
||||
|
||||
After a proposal's deposit reaches the `MinDeposit` value, the voting period opens. Bonded `Atom` holders can then cast vote on it:
|
||||
|
||||
```bash
|
||||
gaiacli gov vote \
|
||||
--proposalID=<proposal_id> \
|
||||
--voter=<account_cosmosaccaddr> \
|
||||
--option=<Yes/No/NoWithVeto/Abstain> \
|
||||
--from=<name> \
|
||||
--chain-id=gaia-7000
|
||||
```
|
||||
|
||||
#### Query vote
|
||||
|
||||
Check the vote with the option you just submitted:
|
||||
|
||||
```bash
|
||||
gaiacli gov query-vote \
|
||||
--proposalID=<proposal_id> \
|
||||
--voter=<account_cosmosaccaddr> \
|
||||
--chain-id=gaia-7000
|
||||
```
|
||||
|
||||
## Other Operations
|
||||
|
||||
### Send Tokens
|
||||
|
||||
```bash
|
||||
gaiacli send \
|
||||
--amount=10faucetToken \
|
||||
--chain-id=gaia-6002 \
|
||||
--from=<key_name> \
|
||||
--to=<destination_cosmosaccaddr>
|
||||
```
|
||||
|
||||
> _*NOTE:*_ The `--amount` flag accepts the format `--amount=<value|coin_name>`.
|
||||
|
||||
Now, view the updated balances of the origin and destination accounts:
|
||||
|
||||
```bash
|
||||
gaiacli account <account_cosmosaccaddr>
|
||||
gaiacli account <destination_cosmosaccaddr>
|
||||
```
|
||||
|
||||
You can also check your balance at a given block by using the `--block` flag:
|
||||
|
||||
```bash
|
||||
gaiacli account <account_cosmosaccaddr> --block=<block_height>
|
||||
```
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-amino"
|
||||
tcrypto "github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
var cdc = amino.NewCodec()
|
||||
|
||||
func init() {
|
||||
RegisterAmino(cdc)
|
||||
tcrypto.RegisterAmino(cdc)
|
||||
}
|
||||
|
||||
// RegisterAmino registers all go-crypto related types in the given (amino) codec.
|
||||
func RegisterAmino(cdc *amino.Codec) {
|
||||
cdc.RegisterConcrete(PrivKeyLedgerSecp256k1{},
|
||||
"tendermint/PrivKeyLedgerSecp256k1", nil)
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
tcrypto "github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
type byter interface {
|
||||
Bytes() []byte
|
||||
}
|
||||
|
||||
func checkAminoBinary(t *testing.T, src byter, dst interface{}, size int) {
|
||||
// Marshal to binary bytes.
|
||||
bz, err := cdc.MarshalBinaryBare(src)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
// Make sure this is compatible with current (Bytes()) encoding.
|
||||
require.Equal(t, src.Bytes(), bz, "Amino binary vs Bytes() mismatch")
|
||||
// Make sure we have the expected length.
|
||||
if size != -1 {
|
||||
require.Equal(t, size, len(bz), "Amino binary size mismatch")
|
||||
}
|
||||
// Unmarshal.
|
||||
err = cdc.UnmarshalBinaryBare(bz, dst)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
}
|
||||
|
||||
func checkAminoJSON(t *testing.T, src interface{}, dst interface{}, isNil bool) {
|
||||
// Marshal to JSON bytes.
|
||||
js, err := cdc.MarshalJSON(src)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
if isNil {
|
||||
require.Equal(t, string(js), `null`)
|
||||
} else {
|
||||
require.Contains(t, string(js), `"type":`)
|
||||
require.Contains(t, string(js), `"value":`)
|
||||
}
|
||||
// Unmarshal.
|
||||
err = cdc.UnmarshalJSON(js, dst)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
}
|
||||
|
||||
//nolint
|
||||
func ExamplePrintRegisteredTypes() {
|
||||
cdc.PrintTypes(os.Stdout)
|
||||
// Output: | Type | Name | Prefix | Length | Notes |
|
||||
//| ---- | ---- | ------ | ----- | ------ |
|
||||
//| PrivKeyLedgerSecp256k1 | tendermint/PrivKeyLedgerSecp256k1 | 0x10CAB393 | variable | |
|
||||
//| PubKeyEd25519 | tendermint/PubKeyEd25519 | 0x1624DE64 | 0x20 | |
|
||||
//| PubKeySecp256k1 | tendermint/PubKeySecp256k1 | 0xEB5AE987 | 0x21 | |
|
||||
//| PrivKeyEd25519 | tendermint/PrivKeyEd25519 | 0xA3288910 | 0x40 | |
|
||||
//| PrivKeySecp256k1 | tendermint/PrivKeySecp256k1 | 0xE1B0F79B | 0x20 | |
|
||||
//| SignatureEd25519 | tendermint/SignatureEd25519 | 0x2031EA53 | 0x40 | |
|
||||
//| SignatureSecp256k1 | tendermint/SignatureSecp256k1 | 0x7FC4A495 | variable | |
|
||||
}
|
||||
|
||||
func TestKeyEncodings(t *testing.T) {
|
||||
cases := []struct {
|
||||
privKey tcrypto.PrivKey
|
||||
privSize, pubSize int // binary sizes with the amino overhead
|
||||
}{
|
||||
{
|
||||
privKey: tcrypto.GenPrivKeyEd25519(),
|
||||
privSize: 69,
|
||||
pubSize: 37,
|
||||
},
|
||||
{
|
||||
privKey: tcrypto.GenPrivKeySecp256k1(),
|
||||
privSize: 37,
|
||||
pubSize: 38,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
||||
// Check (de/en)codings of PrivKeys.
|
||||
var priv2, priv3 tcrypto.PrivKey
|
||||
checkAminoBinary(t, tc.privKey, &priv2, tc.privSize)
|
||||
require.EqualValues(t, tc.privKey, priv2)
|
||||
checkAminoJSON(t, tc.privKey, &priv3, false) // TODO also check Prefix bytes.
|
||||
require.EqualValues(t, tc.privKey, priv3)
|
||||
|
||||
// Check (de/en)codings of Signatures.
|
||||
var sig1, sig2, sig3 tcrypto.Signature
|
||||
sig1, err := tc.privKey.Sign([]byte("something"))
|
||||
require.NoError(t, err)
|
||||
checkAminoBinary(t, sig1, &sig2, -1) // Signature size changes for Secp anyways.
|
||||
require.EqualValues(t, sig1, sig2)
|
||||
checkAminoJSON(t, sig1, &sig3, false) // TODO also check Prefix bytes.
|
||||
require.EqualValues(t, sig1, sig3)
|
||||
|
||||
// Check (de/en)codings of PubKeys.
|
||||
pubKey := tc.privKey.PubKey()
|
||||
var pub2, pub3 tcrypto.PubKey
|
||||
checkAminoBinary(t, pubKey, &pub2, tc.pubSize)
|
||||
require.EqualValues(t, pubKey, pub2)
|
||||
checkAminoJSON(t, pubKey, &pub3, false) // TODO also check Prefix bytes.
|
||||
require.EqualValues(t, pubKey, pub3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilEncodings(t *testing.T) {
|
||||
|
||||
// Check nil Signature.
|
||||
var a, b tcrypto.Signature
|
||||
checkAminoJSON(t, &a, &b, true)
|
||||
require.EqualValues(t, a, b)
|
||||
|
||||
// Check nil PubKey.
|
||||
var c, d tcrypto.PubKey
|
||||
checkAminoJSON(t, &c, &d, true)
|
||||
require.EqualValues(t, c, d)
|
||||
|
||||
// Check nil PrivKey.
|
||||
var e, f tcrypto.PrivKey
|
||||
checkAminoJSON(t, &e, &f, true)
|
||||
require.EqualValues(t, e, f)
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// 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
|
||||
}
|
|
@ -0,0 +1,297 @@
|
|||
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
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
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
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
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")
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package hd
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/bartekn/go-bip39"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
type addrData struct {
|
||||
Mnemonic string
|
||||
Master string
|
||||
Seed string
|
||||
Priv string
|
||||
Pub string
|
||||
Addr string
|
||||
}
|
||||
|
||||
func initFundraiserTestVectors(t *testing.T) []addrData {
|
||||
// NOTE: atom fundraiser address
|
||||
// var hdPath string = "m/44'/118'/0'/0/0"
|
||||
var hdToAddrTable []addrData
|
||||
|
||||
b, err := ioutil.ReadFile("test.json")
|
||||
if err != nil {
|
||||
t.Fatalf("could not read fundraiser test vector file (test.json): %s", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(b, &hdToAddrTable)
|
||||
if err != nil {
|
||||
t.Fatalf("could not decode test vectors (test.json): %s", err)
|
||||
}
|
||||
return hdToAddrTable
|
||||
}
|
||||
|
||||
func TestFundraiserCompatibility(t *testing.T) {
|
||||
hdToAddrTable := initFundraiserTestVectors(t)
|
||||
|
||||
for i, d := range hdToAddrTable {
|
||||
privB, _ := hex.DecodeString(d.Priv)
|
||||
pubB, _ := hex.DecodeString(d.Pub)
|
||||
addrB, _ := hex.DecodeString(d.Addr)
|
||||
seedB, _ := hex.DecodeString(d.Seed)
|
||||
masterB, _ := hex.DecodeString(d.Master)
|
||||
|
||||
seed := bip39.NewSeed(d.Mnemonic, "")
|
||||
|
||||
t.Log("================================")
|
||||
t.Logf("ROUND: %d MNEMONIC: %s", i, d.Mnemonic)
|
||||
|
||||
master, ch := ComputeMastersFromSeed(seed)
|
||||
priv, err := DerivePrivateKeyForPath(master, ch, "44'/118'/0'/0/0")
|
||||
require.NoError(t, err)
|
||||
pub := crypto.PrivKeySecp256k1(priv).PubKey()
|
||||
|
||||
t.Log("\tNODEJS GOLANG\n")
|
||||
t.Logf("SEED \t%X %X\n", seedB, seed)
|
||||
t.Logf("MSTR \t%X %X\n", masterB, master)
|
||||
t.Logf("PRIV \t%X %X\n", privB, priv)
|
||||
t.Logf("PUB \t%X %X\n", pubB, pub)
|
||||
|
||||
require.Equal(t, seedB, seed)
|
||||
require.Equal(t, master[:], masterB, fmt.Sprintf("Expected masters to match for %d", i))
|
||||
require.Equal(t, priv[:], privB, "Expected priv keys to match")
|
||||
var pubBFixed [33]byte
|
||||
copy(pubBFixed[:], pubB)
|
||||
require.Equal(t, pub, crypto.PubKeySecp256k1(pubBFixed), fmt.Sprintf("Expected pub keys to match for %d", i))
|
||||
|
||||
addr := pub.Address()
|
||||
t.Logf("ADDR \t%X %X\n", addrB, addr)
|
||||
require.Equal(t, addr, crypto.Address(addrB), fmt.Sprintf("Expected addresses to match %d", i))
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
// Package hd provides basic functionality Hierarchical Deterministic Wallets.
|
||||
//
|
||||
// The user must understand the overall concept of the BIP 32 and the BIP 44 specs:
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
|
||||
//
|
||||
// In combination with the bip39 package in go-crypto this package provides the functionality for deriving keys using a
|
||||
// BIP 44 HD path, or, more general, by passing a BIP 32 path.
|
||||
//
|
||||
// In particular, this package (together with bip39) provides all necessary functionality to derive keys from
|
||||
// mnemonics generated during the cosmos fundraiser.
|
||||
package hd
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha512"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
// BIP44Prefix is the parts of the BIP32 HD path that are fixed by what we used during the fundraiser.
|
||||
const (
|
||||
BIP44Prefix = "44'/118'/"
|
||||
FullFundraiserPath = BIP44Prefix + "0'/0/0"
|
||||
)
|
||||
|
||||
// BIP44Params wraps BIP 44 params (5 level BIP 32 path).
|
||||
// To receive a canonical string representation ala
|
||||
// m / purpose' / coin_type' / account' / change / address_index
|
||||
// call String() on a BIP44Params instance.
|
||||
type BIP44Params struct {
|
||||
purpose uint32
|
||||
coinType uint32
|
||||
account uint32
|
||||
change bool
|
||||
addressIdx uint32
|
||||
}
|
||||
|
||||
// NewParams creates a BIP 44 parameter object from the params:
|
||||
// m / purpose' / coin_type' / account' / change / address_index
|
||||
func NewParams(purpose, coinType, account uint32, change bool, addressIdx uint32) *BIP44Params {
|
||||
return &BIP44Params{
|
||||
purpose: purpose,
|
||||
coinType: coinType,
|
||||
account: account,
|
||||
change: change,
|
||||
addressIdx: addressIdx,
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params {
|
||||
return NewParams(44, 118, account, false, addressIdx)
|
||||
}
|
||||
|
||||
func (p BIP44Params) String() string {
|
||||
var changeStr string
|
||||
if p.change {
|
||||
changeStr = "1"
|
||||
} else {
|
||||
changeStr = "0"
|
||||
}
|
||||
// m / purpose' / coin_type' / account' / change / address_index
|
||||
return fmt.Sprintf("%d'/%d'/%d'/%s/%d",
|
||||
p.purpose,
|
||||
p.coinType,
|
||||
p.account,
|
||||
changeStr,
|
||||
p.addressIdx)
|
||||
}
|
||||
|
||||
// ComputeMastersFromSeed returns the master public key, master secret, and chain code in hex.
|
||||
func ComputeMastersFromSeed(seed []byte) (secret [32]byte, chainCode [32]byte) {
|
||||
masterSecret := []byte("Bitcoin seed")
|
||||
secret, chainCode = i64(masterSecret, seed)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DerivePrivateKeyForPath derives the private key by following the BIP 32/44 path from privKeyBytes,
|
||||
// using the given chainCode.
|
||||
func DerivePrivateKeyForPath(privKeyBytes [32]byte, chainCode [32]byte, path string) ([32]byte, error) {
|
||||
data := privKeyBytes
|
||||
parts := strings.Split(path, "/")
|
||||
for _, part := range parts {
|
||||
// do we have an apostrophe?
|
||||
harden := part[len(part)-1:] == "'"
|
||||
// harden == private derivation, else public derivation:
|
||||
if harden {
|
||||
part = part[:len(part)-1]
|
||||
}
|
||||
idx, err := strconv.Atoi(part)
|
||||
if err != nil {
|
||||
return [32]byte{}, fmt.Errorf("invalid BIP 32 path: %s", err)
|
||||
}
|
||||
if idx < 0 {
|
||||
return [32]byte{}, errors.New("invalid BIP 32 path: index negative ot too large")
|
||||
}
|
||||
data, chainCode = derivePrivateKey(data, chainCode, uint32(idx), harden)
|
||||
}
|
||||
var derivedKey [32]byte
|
||||
n := copy(derivedKey[:], data[:])
|
||||
if n != 32 || len(data) != 32 {
|
||||
return [32]byte{}, fmt.Errorf("expected a (secp256k1) key of length 32, got length: %v", len(data))
|
||||
}
|
||||
|
||||
return derivedKey, nil
|
||||
}
|
||||
|
||||
// derivePrivateKey derives the private key with index and chainCode.
|
||||
// If harden is true, the derivation is 'hardened'.
|
||||
// It returns the new private key and new chain code.
|
||||
// For more information on hardened keys see:
|
||||
// - https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
|
||||
func derivePrivateKey(privKeyBytes [32]byte, chainCode [32]byte, index uint32, harden bool) ([32]byte, [32]byte) {
|
||||
var data []byte
|
||||
if harden {
|
||||
index = index | 0x80000000
|
||||
data = append([]byte{byte(0)}, privKeyBytes[:]...)
|
||||
} else {
|
||||
// this can't return an error:
|
||||
pubkey := crypto.PrivKeySecp256k1(privKeyBytes).PubKey()
|
||||
|
||||
public := pubkey.(crypto.PubKeySecp256k1)
|
||||
data = public[:]
|
||||
}
|
||||
data = append(data, uint32ToBytes(index)...)
|
||||
data2, chainCode2 := i64(chainCode[:], data)
|
||||
x := addScalars(privKeyBytes[:], data2[:])
|
||||
return x, chainCode2
|
||||
}
|
||||
|
||||
// modular big endian addition
|
||||
func addScalars(a []byte, b []byte) [32]byte {
|
||||
aInt := new(big.Int).SetBytes(a)
|
||||
bInt := new(big.Int).SetBytes(b)
|
||||
sInt := new(big.Int).Add(aInt, bInt)
|
||||
x := sInt.Mod(sInt, btcec.S256().N).Bytes()
|
||||
x2 := [32]byte{}
|
||||
copy(x2[32-len(x):], x)
|
||||
return x2
|
||||
}
|
||||
|
||||
func uint32ToBytes(i uint32) []byte {
|
||||
b := [4]byte{}
|
||||
binary.BigEndian.PutUint32(b[:], i)
|
||||
return b[:]
|
||||
}
|
||||
|
||||
// i64 returns the two halfs of the SHA512 HMAC of key and data.
|
||||
func i64(key []byte, data []byte) (IL [32]byte, IR [32]byte) {
|
||||
mac := hmac.New(sha512.New, key)
|
||||
// sha512 does not err
|
||||
_, _ = mac.Write(data)
|
||||
I := mac.Sum(nil)
|
||||
copy(IL[:], I[:32])
|
||||
copy(IR[:], I[32:])
|
||||
return
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package hd
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/bip39"
|
||||
)
|
||||
|
||||
//nolint
|
||||
func ExampleStringifyPathParams() {
|
||||
path := NewParams(44, 0, 0, false, 0)
|
||||
fmt.Println(path.String())
|
||||
// Output: 44'/0'/0'/0/0
|
||||
}
|
||||
|
||||
//nolint
|
||||
func ExampleSomeBIP32TestVecs() {
|
||||
|
||||
seed := bip39.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)")
|
||||
fmt.Println()
|
||||
// cosmos
|
||||
priv, _ := DerivePrivateKeyForPath(master, ch, FullFundraiserPath)
|
||||
fmt.Println(hex.EncodeToString(priv[:]))
|
||||
// bitcoin
|
||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0")
|
||||
fmt.Println(hex.EncodeToString(priv[:]))
|
||||
// ether
|
||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0")
|
||||
fmt.Println(hex.EncodeToString(priv[:]))
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html")
|
||||
fmt.Println()
|
||||
|
||||
seed = bip39.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 " +
|
||||
"gun second farm pact pulse someone armed")
|
||||
master, ch = ComputeMastersFromSeed(seed)
|
||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420")
|
||||
fmt.Println(hex.EncodeToString(priv[:]))
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("BIP 32 example")
|
||||
fmt.Println()
|
||||
|
||||
// bip32 path: m/0/7
|
||||
seed = bip39.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[:]))
|
||||
|
||||
// Output: keys from fundraiser test-vector (cosmos, bitcoin, ether)
|
||||
//
|
||||
// bfcb217c058d8bbafd5e186eae936106ca3e943889b0b4a093ae13822fd3170c
|
||||
// e77c3de76965ad89997451de97b95bb65ede23a6bf185a55d80363d92ee37c3d
|
||||
// 7fc4d8a8146dea344ba04c593517d3f377fa6cded36cd55aee0a0bb968e651bc
|
||||
//
|
||||
// keys generated via https://coinomi.com/recovery-phrase-tool.html
|
||||
//
|
||||
// a61f10c5fecf40c084c94fa54273b6f5d7989386be4a37669e6d6f7b0169c163
|
||||
// 32c4599843de3ef161a629a461d12c60b009b676c35050be5f7ded3a3b23501f
|
||||
//
|
||||
// BIP 32 example
|
||||
//
|
||||
// c4c11d8c03625515905d7e89d25dfc66126fbc629ecca6db489a1a72fc4bda78
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,382 @@
|
|||
package keys
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
tcrypto "github.com/tendermint/tendermint/crypto"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/bip39"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
)
|
||||
|
||||
var _ Keybase = dbKeybase{}
|
||||
|
||||
// Language is a language to create the BIP 39 mnemonic in.
|
||||
// Currently, only english is supported though.
|
||||
// Find a list of all supported languages in the BIP 39 spec (word lists).
|
||||
type Language int
|
||||
|
||||
const (
|
||||
// English is the default language to create a mnemonic.
|
||||
// It is the only supported language by this package.
|
||||
English Language = iota + 1
|
||||
// Japanese is currently not supported.
|
||||
Japanese
|
||||
// Korean is currently not supported.
|
||||
Korean
|
||||
// Spanish is currently not supported.
|
||||
Spanish
|
||||
// ChineseSimplified is currently not supported.
|
||||
ChineseSimplified
|
||||
// ChineseTraditional is currently not supported.
|
||||
ChineseTraditional
|
||||
// French is currently not supported.
|
||||
French
|
||||
// Italian is currently not supported.
|
||||
Italian
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnsupportedSigningAlgo is raised when the caller tries to use a different signing scheme than secp256k1.
|
||||
ErrUnsupportedSigningAlgo = errors.New("unsupported signing algo: only secp256k1 is supported")
|
||||
// ErrUnsupportedLanguage is raised when the caller tries to use a different language than english for creating
|
||||
// a mnemonic sentence.
|
||||
ErrUnsupportedLanguage = errors.New("unsupported language: only english is supported")
|
||||
)
|
||||
|
||||
// dbKeybase combines encryption and storage implementation to provide
|
||||
// a full-featured key manager
|
||||
type dbKeybase struct {
|
||||
db dbm.DB
|
||||
}
|
||||
|
||||
// New creates a new keybase instance using the passed DB for reading and writing keys.
|
||||
func New(db dbm.DB) Keybase {
|
||||
return dbKeybase{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateMnemonic generates a new key and persists it to storage, encrypted
|
||||
// using the provided password.
|
||||
// It returns the generated mnemonic and the key Info.
|
||||
// It returns an error if it fails to
|
||||
// generate a key for the given algo type, or if another key is
|
||||
// already stored under the same name.
|
||||
func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, mnemonic string, err error) {
|
||||
if language != English {
|
||||
return nil, "", ErrUnsupportedLanguage
|
||||
}
|
||||
if algo != Secp256k1 {
|
||||
err = ErrUnsupportedSigningAlgo
|
||||
return
|
||||
}
|
||||
|
||||
// default number of words (24):
|
||||
mnemonicS, err := bip39.NewMnemonic(bip39.FreshKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mnemonic = strings.Join(mnemonicS, " ")
|
||||
seed := bip39.MnemonicToSeed(mnemonic)
|
||||
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
|
||||
return
|
||||
}
|
||||
|
||||
// TEMPORARY METHOD UNTIL WE FIGURE OUT USER FACING HD DERIVATION API
|
||||
func (kb dbKeybase) CreateKey(name, mnemonic, passwd string) (info Info, err error) {
|
||||
words := strings.Split(mnemonic, " ")
|
||||
if len(words) != 12 && len(words) != 24 {
|
||||
err = fmt.Errorf("recovering only works with 12 word (fundraiser) or 24 word mnemonics, got: %v words", len(words))
|
||||
return
|
||||
}
|
||||
seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
|
||||
return
|
||||
}
|
||||
|
||||
// CreateFundraiserKey converts a mnemonic to a private key and persists it,
|
||||
// encrypted with the given password.
|
||||
// TODO(ismail)
|
||||
func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error) {
|
||||
words := strings.Split(mnemonic, " ")
|
||||
if len(words) != 12 {
|
||||
err = fmt.Errorf("recovering only works with 12 word (fundraiser), got: %v words", len(words))
|
||||
return
|
||||
}
|
||||
seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
|
||||
return
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Derive(name, mnemonic, passwd string, params hd.BIP44Params) (info Info, err error) {
|
||||
seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
info, err = kb.persistDerivedKey(seed, passwd, name, params.String())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CreateLedger creates a new locally-stored reference to a Ledger keypair
|
||||
// It returns the created key info and an error if the Ledger could not be queried
|
||||
func (kb dbKeybase) CreateLedger(name string, path crypto.DerivationPath, algo SigningAlgo) (Info, error) {
|
||||
if algo != Secp256k1 {
|
||||
return nil, ErrUnsupportedSigningAlgo
|
||||
}
|
||||
priv, err := crypto.NewPrivKeyLedgerSecp256k1(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pub := priv.PubKey()
|
||||
return kb.writeLedgerKey(pub, path, name), nil
|
||||
}
|
||||
|
||||
// CreateOffline creates a new reference to an offline keypair
|
||||
// It returns the created key info
|
||||
func (kb dbKeybase) CreateOffline(name string, pub tcrypto.PubKey) (Info, error) {
|
||||
return kb.writeOfflineKey(pub, name), nil
|
||||
}
|
||||
|
||||
func (kb *dbKeybase) persistDerivedKey(seed []byte, passwd, name, fullHdPath string) (info Info, err error) {
|
||||
// create master key and derive first key:
|
||||
masterPriv, ch := hd.ComputeMastersFromSeed(seed)
|
||||
derivedPriv, err := hd.DerivePrivateKeyForPath(masterPriv, ch, fullHdPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// if we have a password, use it to encrypt the private key and store it
|
||||
// else store the public key only
|
||||
if passwd != "" {
|
||||
info = kb.writeLocalKey(tcrypto.PrivKeySecp256k1(derivedPriv), name, passwd)
|
||||
} else {
|
||||
pubk := tcrypto.PrivKeySecp256k1(derivedPriv).PubKey()
|
||||
info = kb.writeOfflineKey(pubk, name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// List returns the keys from storage in alphabetical order.
|
||||
func (kb dbKeybase) List() ([]Info, error) {
|
||||
var res []Info
|
||||
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
|
||||
}
|
||||
res = append(res, info)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Get returns the public information about one key.
|
||||
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 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 tcrypto.Signature, pub tcrypto.PubKey, err error) {
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var priv tcrypto.PrivKey
|
||||
switch info.(type) {
|
||||
case localInfo:
|
||||
linfo := info.(localInfo)
|
||||
if linfo.PrivKeyArmor == "" {
|
||||
err = fmt.Errorf("private key not available")
|
||||
return
|
||||
}
|
||||
priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
case ledgerInfo:
|
||||
linfo := info.(ledgerInfo)
|
||||
priv, err = crypto.NewPrivKeyLedgerSecp256k1(linfo.Path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case offlineInfo:
|
||||
linfo := info.(offlineInfo)
|
||||
fmt.Printf("Bytes to sign:\n%s", msg)
|
||||
buf := bufio.NewReader(os.Stdin)
|
||||
fmt.Printf("\nEnter Amino-encoded signature:\n")
|
||||
// Will block until user inputs the signature
|
||||
signed, err := buf.ReadString('\n')
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cdc.MustUnmarshalBinary([]byte(signed), sig)
|
||||
return sig, linfo.GetPubKey(), nil
|
||||
}
|
||||
sig, err = priv.Sign(msg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pub = priv.PubKey()
|
||||
return sig, pub, nil
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Export(name string) (armor string, err error) {
|
||||
bz := kb.db.Get(infoKey(name))
|
||||
if bz == nil {
|
||||
return "", fmt.Errorf("no key to export with name %s", name)
|
||||
}
|
||||
return armorInfoBytes(bz), nil
|
||||
}
|
||||
|
||||
// ExportPubKey returns public keys in ASCII armored format.
|
||||
// Retrieve a Info object by its name and return the public key in
|
||||
// a portable format.
|
||||
func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) {
|
||||
bz := kb.db.Get(infoKey(name))
|
||||
if bz == nil {
|
||||
return "", fmt.Errorf("no key to export with name %s", name)
|
||||
}
|
||||
info, err := readInfo(bz)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return armorPubKeyBytes(info.GetPubKey().Bytes()), nil
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Import(name string, armor string) (err error) {
|
||||
bz := kb.db.Get(infoKey(name))
|
||||
if len(bz) > 0 {
|
||||
return errors.New("Cannot overwrite data for name " + name)
|
||||
}
|
||||
infoBytes, err := unarmorInfoBytes(armor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
kb.db.Set(infoKey(name), infoBytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImportPubKey imports ASCII-armored public keys.
|
||||
// Store a new Info object holding a public key only, i.e. it will
|
||||
// not be possible to sign with it as it lacks the secret key.
|
||||
func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
|
||||
bz := kb.db.Get(infoKey(name))
|
||||
if len(bz) > 0 {
|
||||
return errors.New("Cannot overwrite data for name " + name)
|
||||
}
|
||||
pubBytes, err := unarmorPubKeyBytes(armor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pubKey, err := tcrypto.PubKeyFromBytes(pubBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
kb.writeOfflineKey(pubKey, name)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes key forever, but we must present the
|
||||
// proper passphrase before deleting it (for security).
|
||||
// A passphrase of 'yes' is used to delete stored
|
||||
// references to offline and Ledger / HW wallet keys
|
||||
func (kb dbKeybase) Delete(name, passphrase string) error {
|
||||
// verify we have the proper password before deleting
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch info.(type) {
|
||||
case localInfo:
|
||||
linfo := info.(localInfo)
|
||||
_, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kb.db.DeleteSync(infoKey(name))
|
||||
return nil
|
||||
case ledgerInfo:
|
||||
case offlineInfo:
|
||||
if passphrase != "yes" {
|
||||
return fmt.Errorf("enter 'yes' exactly to delete the key - this cannot be undone")
|
||||
}
|
||||
kb.db.DeleteSync(infoKey(name))
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update changes the passphrase with which an already stored key is
|
||||
// encrypted.
|
||||
//
|
||||
// oldpass must be the current passphrase used for encryption,
|
||||
// newpass will be the only valid passphrase from this time forward.
|
||||
func (kb dbKeybase) Update(name, oldpass, newpass string) error {
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch info.(type) {
|
||||
case localInfo:
|
||||
linfo := info.(localInfo)
|
||||
key, err := unarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kb.writeLocalKey(key, name, newpass)
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("locally stored key required")
|
||||
}
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeLocalKey(priv tcrypto.PrivKey, name, passphrase string) Info {
|
||||
// encrypt private key using passphrase
|
||||
privArmor := encryptArmorPrivKey(priv, passphrase)
|
||||
// make Info
|
||||
pub := priv.PubKey()
|
||||
info := newLocalInfo(name, pub, privArmor)
|
||||
kb.writeInfo(info, name)
|
||||
return info
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeLedgerKey(pub tcrypto.PubKey, path crypto.DerivationPath, name string) Info {
|
||||
info := newLedgerInfo(name, pub, path)
|
||||
kb.writeInfo(info, name)
|
||||
return info
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeOfflineKey(pub tcrypto.PubKey, name string) Info {
|
||||
info := newOfflineInfo(name, pub)
|
||||
kb.writeInfo(info, name)
|
||||
return info
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeInfo(info Info, name string) {
|
||||
// write the info by key
|
||||
kb.db.SetSync(infoKey(name), writeInfo(info))
|
||||
}
|
||||
|
||||
func infoKey(name string) []byte {
|
||||
return []byte(fmt.Sprintf("%s.info", name))
|
||||
}
|
|
@ -0,0 +1,382 @@
|
|||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
)
|
||||
|
||||
// TestKeyManagement makes sure we can manipulate these keys well
|
||||
func TestKeyManagement(t *testing.T) {
|
||||
// make the storage with reasonable defaults
|
||||
cstore := New(
|
||||
dbm.NewMemDB(),
|
||||
)
|
||||
|
||||
algo := Secp256k1
|
||||
n1, n2, n3 := "personal", "business", "other"
|
||||
p1, p2 := "1234", "really-secure!@#$"
|
||||
|
||||
// Check empty state
|
||||
l, err := cstore.List()
|
||||
require.Nil(t, err)
|
||||
assert.Empty(t, l)
|
||||
|
||||
_, _, err = cstore.CreateMnemonic(n1, English, p1, Ed25519)
|
||||
require.Error(t, err, "ed25519 keys are currently not supported by keybase")
|
||||
|
||||
// create some keys
|
||||
_, err = cstore.Get(n1)
|
||||
require.Error(t, err)
|
||||
i, _, err := cstore.CreateMnemonic(n1, English, p1, algo)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, n1, i.GetName())
|
||||
_, _, err = cstore.CreateMnemonic(n2, English, p2, algo)
|
||||
require.NoError(t, err)
|
||||
|
||||
// we can get these keys
|
||||
i2, err := cstore.Get(n2)
|
||||
require.NoError(t, err)
|
||||
_, err = cstore.Get(n3)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// list shows them in order
|
||||
keyS, err := cstore.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(keyS))
|
||||
// note these are in alphabetical order
|
||||
require.Equal(t, n2, keyS[0].GetName())
|
||||
require.Equal(t, n1, keyS[1].GetName())
|
||||
require.Equal(t, i2.GetPubKey(), keyS[0].GetPubKey())
|
||||
|
||||
// deleting a key removes it
|
||||
err = cstore.Delete("bad name", "foo")
|
||||
require.NotNil(t, err)
|
||||
err = cstore.Delete(n1, p1)
|
||||
require.NoError(t, err)
|
||||
keyS, err = cstore.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(keyS))
|
||||
_, err = cstore.Get(n1)
|
||||
require.Error(t, err)
|
||||
|
||||
// create an offline key
|
||||
o1 := "offline"
|
||||
priv1 := crypto.GenPrivKeyEd25519()
|
||||
pub1 := priv1.PubKey()
|
||||
i, err = cstore.CreateOffline(o1, pub1)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, pub1, i.GetPubKey())
|
||||
require.Equal(t, o1, i.GetName())
|
||||
keyS, err = cstore.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(keyS))
|
||||
|
||||
// delete the offline key
|
||||
err = cstore.Delete(o1, "no")
|
||||
require.NotNil(t, err)
|
||||
err = cstore.Delete(o1, "yes")
|
||||
require.NoError(t, err)
|
||||
keyS, err = cstore.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(keyS))
|
||||
}
|
||||
|
||||
// TestSignVerify does some detailed checks on how we sign and validate
|
||||
// signatures
|
||||
func TestSignVerify(t *testing.T) {
|
||||
cstore := New(
|
||||
dbm.NewMemDB(),
|
||||
)
|
||||
algo := Secp256k1
|
||||
|
||||
n1, n2, n3 := "some dude", "a dudette", "dude-ish"
|
||||
p1, p2, p3 := "1234", "foobar", "foobar"
|
||||
|
||||
// create two users and get their info
|
||||
i1, _, err := cstore.CreateMnemonic(n1, English, p1, algo)
|
||||
require.Nil(t, err)
|
||||
|
||||
i2, _, err := cstore.CreateMnemonic(n2, English, p2, algo)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Import a public key
|
||||
armor, err := cstore.ExportPubKey(n2)
|
||||
require.Nil(t, err)
|
||||
cstore.ImportPubKey(n3, armor)
|
||||
i3, err := cstore.Get(n3)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, i3.GetName(), n3)
|
||||
|
||||
// let's try to sign some messages
|
||||
d1 := []byte("my first message")
|
||||
d2 := []byte("some other important info!")
|
||||
d3 := []byte("feels like I forgot something...")
|
||||
|
||||
// try signing both data with both ..
|
||||
s11, pub1, err := cstore.Sign(n1, p1, d1)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i1.GetPubKey(), pub1)
|
||||
|
||||
s12, pub1, err := cstore.Sign(n1, p1, d2)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i1.GetPubKey(), pub1)
|
||||
|
||||
s21, pub2, err := cstore.Sign(n2, p2, d1)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i2.GetPubKey(), pub2)
|
||||
|
||||
s22, pub2, err := cstore.Sign(n2, p2, d2)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i2.GetPubKey(), pub2)
|
||||
|
||||
// let's try to validate and make sure it only works when everything is proper
|
||||
cases := []struct {
|
||||
key crypto.PubKey
|
||||
data []byte
|
||||
sig crypto.Signature
|
||||
valid bool
|
||||
}{
|
||||
// proper matches
|
||||
{i1.GetPubKey(), d1, s11, true},
|
||||
// change data, pubkey, or signature leads to fail
|
||||
{i1.GetPubKey(), d2, s11, false},
|
||||
{i2.GetPubKey(), d1, s11, false},
|
||||
{i1.GetPubKey(), d1, s21, false},
|
||||
// make sure other successes
|
||||
{i1.GetPubKey(), d2, s12, true},
|
||||
{i2.GetPubKey(), d1, s21, true},
|
||||
{i2.GetPubKey(), d2, s22, true},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
valid := tc.key.VerifyBytes(tc.data, tc.sig)
|
||||
require.Equal(t, tc.valid, valid, "%d", i)
|
||||
}
|
||||
|
||||
// Now try to sign data with a secret-less key
|
||||
_, _, err = cstore.Sign(n3, p3, d3)
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
func assertPassword(t *testing.T, cstore Keybase, name, pass, badpass string) {
|
||||
err := cstore.Update(name, badpass, pass)
|
||||
require.NotNil(t, err)
|
||||
err = cstore.Update(name, pass, pass)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
}
|
||||
|
||||
// TestExportImport tests exporting and importing
|
||||
func TestExportImport(t *testing.T) {
|
||||
|
||||
// make the storage with reasonable defaults
|
||||
db := dbm.NewMemDB()
|
||||
cstore := New(
|
||||
db,
|
||||
)
|
||||
|
||||
info, _, err := cstore.CreateMnemonic("john", English, "secretcpw", Secp256k1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
|
||||
john, err := cstore.Get("john")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
johnAddr := info.GetPubKey().Address()
|
||||
|
||||
armor, err := cstore.Export("john")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cstore.Import("john2", armor)
|
||||
require.NoError(t, err)
|
||||
|
||||
john2, err := cstore.Get("john2")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, john.GetPubKey().Address(), johnAddr)
|
||||
require.Equal(t, john.GetName(), "john")
|
||||
require.Equal(t, john, john2)
|
||||
}
|
||||
|
||||
//
|
||||
func TestExportImportPubKey(t *testing.T) {
|
||||
// make the storage with reasonable defaults
|
||||
db := dbm.NewMemDB()
|
||||
cstore := New(
|
||||
db,
|
||||
)
|
||||
|
||||
// CreateMnemonic a private-public key pair and ensure consistency
|
||||
notPasswd := "n9y25ah7"
|
||||
info, _, err := cstore.CreateMnemonic("john", English, notPasswd, Secp256k1)
|
||||
require.Nil(t, err)
|
||||
require.NotEqual(t, info, "")
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
addr := info.GetPubKey().Address()
|
||||
john, err := cstore.Get("john")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, john.GetName(), "john")
|
||||
require.Equal(t, john.GetPubKey().Address(), addr)
|
||||
|
||||
// Export the public key only
|
||||
armor, err := cstore.ExportPubKey("john")
|
||||
require.NoError(t, err)
|
||||
// Import it under a different name
|
||||
err = cstore.ImportPubKey("john-pubkey-only", armor)
|
||||
require.NoError(t, err)
|
||||
// Ensure consistency
|
||||
john2, err := cstore.Get("john-pubkey-only")
|
||||
require.NoError(t, err)
|
||||
// Compare the public keys
|
||||
require.True(t, john.GetPubKey().Equals(john2.GetPubKey()))
|
||||
// Ensure the original key hasn't changed
|
||||
john, err = cstore.Get("john")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, john.GetPubKey().Address(), addr)
|
||||
require.Equal(t, john.GetName(), "john")
|
||||
|
||||
// Ensure keys cannot be overwritten
|
||||
err = cstore.ImportPubKey("john-pubkey-only", armor)
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
// TestAdvancedKeyManagement verifies update, import, export functionality
|
||||
func TestAdvancedKeyManagement(t *testing.T) {
|
||||
|
||||
// make the storage with reasonable defaults
|
||||
cstore := New(
|
||||
dbm.NewMemDB(),
|
||||
)
|
||||
|
||||
algo := Secp256k1
|
||||
n1, n2 := "old-name", "new name"
|
||||
p1, p2 := "1234", "foobar"
|
||||
|
||||
// make sure key works with initial password
|
||||
_, _, err := cstore.CreateMnemonic(n1, English, p1, algo)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
assertPassword(t, cstore, n1, p1, p2)
|
||||
|
||||
// update password requires the existing password
|
||||
err = cstore.Update(n1, "jkkgkg", p2)
|
||||
require.NotNil(t, err)
|
||||
assertPassword(t, cstore, n1, p1, p2)
|
||||
|
||||
// then it changes the password when correct
|
||||
err = cstore.Update(n1, p1, p2)
|
||||
require.NoError(t, err)
|
||||
// p2 is now the proper one!
|
||||
assertPassword(t, cstore, n1, p2, p1)
|
||||
|
||||
// exporting requires the proper name and passphrase
|
||||
_, err = cstore.Export(n1 + ".notreal")
|
||||
require.NotNil(t, err)
|
||||
_, err = cstore.Export(" " + n1)
|
||||
require.NotNil(t, err)
|
||||
_, err = cstore.Export(n1 + " ")
|
||||
require.NotNil(t, err)
|
||||
_, err = cstore.Export("")
|
||||
require.NotNil(t, err)
|
||||
exported, err := cstore.Export(n1)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
|
||||
// import succeeds
|
||||
err = cstore.Import(n2, exported)
|
||||
require.NoError(t, err)
|
||||
|
||||
// second import fails
|
||||
err = cstore.Import(n2, exported)
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
// TestSeedPhrase verifies restoring from a seed phrase
|
||||
func TestSeedPhrase(t *testing.T) {
|
||||
|
||||
// make the storage with reasonable defaults
|
||||
cstore := New(
|
||||
dbm.NewMemDB(),
|
||||
)
|
||||
|
||||
algo := Secp256k1
|
||||
n1, n2 := "lost-key", "found-again"
|
||||
p1, p2 := "1234", "foobar"
|
||||
|
||||
// make sure key works with initial password
|
||||
info, mnemonic, err := cstore.CreateMnemonic(n1, English, p1, algo)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
require.Equal(t, n1, info.GetName())
|
||||
assert.NotEmpty(t, mnemonic)
|
||||
|
||||
// now, let us delete this key
|
||||
err = cstore.Delete(n1, p1)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
_, err = cstore.Get(n1)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// let us re-create it from the mnemonic-phrase
|
||||
params := *hd.NewFundraiserParams(0, 0)
|
||||
newInfo, err := cstore.Derive(n2, mnemonic, p2, params)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, n2, newInfo.GetName())
|
||||
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
|
||||
require.Equal(t, info.GetPubKey(), newInfo.GetPubKey())
|
||||
}
|
||||
|
||||
func ExampleNew() {
|
||||
// Select the encryption and storage for your cryptostore
|
||||
cstore := New(
|
||||
dbm.NewMemDB(),
|
||||
)
|
||||
|
||||
sec := Secp256k1
|
||||
|
||||
// Add keys and see they return in alphabetical order
|
||||
bob, _, err := cstore.CreateMnemonic("Bob", English, "friend", sec)
|
||||
if err != nil {
|
||||
// this should never happen
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
// return info here just like in List
|
||||
fmt.Println(bob.GetName())
|
||||
}
|
||||
cstore.CreateMnemonic("Alice", English, "secret", sec)
|
||||
cstore.CreateMnemonic("Carl", English, "mitm", sec)
|
||||
info, _ := cstore.List()
|
||||
for _, i := range info {
|
||||
fmt.Println(i.GetName())
|
||||
}
|
||||
|
||||
// We need to use passphrase to generate a signature
|
||||
tx := []byte("deadbeef")
|
||||
sig, pub, err := cstore.Sign("Bob", "friend", tx)
|
||||
if err != nil {
|
||||
fmt.Println("don't accept real passphrase")
|
||||
}
|
||||
|
||||
// and we can validate the signature with publicly available info
|
||||
binfo, _ := cstore.Get("Bob")
|
||||
if !binfo.GetPubKey().Equals(bob.GetPubKey()) {
|
||||
fmt.Println("Get and Create return different keys")
|
||||
}
|
||||
|
||||
if pub.Equals(binfo.GetPubKey()) {
|
||||
fmt.Println("signed by Bob")
|
||||
}
|
||||
if !pub.VerifyBytes(tx, sig) {
|
||||
fmt.Println("invalid signature")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Bob
|
||||
// Alice
|
||||
// Bob
|
||||
// Carl
|
||||
// signed by Bob
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package keys
|
||||
|
||||
// SigningAlgo defines an algorithm to derive key-pairs which can be used for cryptographic signing.
|
||||
type SigningAlgo string
|
||||
|
||||
const (
|
||||
// Secp256k1 uses the Bitcoin secp256k1 ECDSA parameters.
|
||||
Secp256k1 = SigningAlgo("secp256k1")
|
||||
// Ed25519 represents the Ed25519 signature system.
|
||||
// It is currently not supported for end-user keys (wallets/ledgers).
|
||||
Ed25519 = SigningAlgo("ed25519")
|
||||
)
|
|
@ -0,0 +1,2 @@
|
|||
output = "text"
|
||||
keydir = ".mykeys"
|
|
@ -0,0 +1,130 @@
|
|||
package keys
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/bcrypt"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
const (
|
||||
blockTypePrivKey = "TENDERMINT PRIVATE KEY"
|
||||
blockTypeKeyInfo = "TENDERMINT KEY INFO"
|
||||
blockTypePubKey = "TENDERMINT PUBLIC KEY"
|
||||
)
|
||||
|
||||
// Make bcrypt security parameter var, so it can be changed within the lcd test
|
||||
// Making the bcrypt security parameter a var shouldn't be a security issue:
|
||||
// One can't verify an invalid key by maliciously changing the bcrypt
|
||||
// parameter during a runtime vulnerability. The main security
|
||||
// threat this then exposes would be something that changes this during
|
||||
// runtime before the user creates their key. This vulnerability must
|
||||
// succeed to update this to that same value before every subsequent call
|
||||
// to gaiacli keys in future startups / or the attacker must get access
|
||||
// to the filesystem. However, with a similar threat model (changing
|
||||
// variables in runtime), one can cause the user to sign a different tx
|
||||
// than what they see, which is a significantly cheaper attack then breaking
|
||||
// a bcrypt hash. (Recall that the nonce still exists to break rainbow tables)
|
||||
// TODO: Consider increasing default
|
||||
var BcryptSecurityParameter = 12
|
||||
|
||||
func armorInfoBytes(bz []byte) string {
|
||||
return armorBytes(bz, blockTypeKeyInfo)
|
||||
}
|
||||
|
||||
func armorPubKeyBytes(bz []byte) string {
|
||||
return armorBytes(bz, blockTypePubKey)
|
||||
}
|
||||
|
||||
func armorBytes(bz []byte, blockType string) string {
|
||||
header := map[string]string{
|
||||
"type": "Info",
|
||||
"version": "0.0.0",
|
||||
}
|
||||
return crypto.EncodeArmor(blockType, header, bz)
|
||||
}
|
||||
|
||||
func unarmorInfoBytes(armorStr string) (bz []byte, err error) {
|
||||
return unarmorBytes(armorStr, blockTypeKeyInfo)
|
||||
}
|
||||
|
||||
func unarmorPubKeyBytes(armorStr string) (bz []byte, err error) {
|
||||
return unarmorBytes(armorStr, blockTypePubKey)
|
||||
}
|
||||
|
||||
func unarmorBytes(armorStr, blockType string) (bz []byte, err error) {
|
||||
bType, header, bz, err := crypto.DecodeArmor(armorStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if bType != blockType {
|
||||
err = fmt.Errorf("Unrecognized armor type %q, expected: %q", bType, blockType)
|
||||
return
|
||||
}
|
||||
if header["version"] != "0.0.0" {
|
||||
err = fmt.Errorf("Unrecognized version: %v", header["version"])
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func encryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string {
|
||||
saltBytes, encBytes := encryptPrivKey(privKey, passphrase)
|
||||
header := map[string]string{
|
||||
"kdf": "bcrypt",
|
||||
"salt": fmt.Sprintf("%X", saltBytes),
|
||||
}
|
||||
armorStr := crypto.EncodeArmor(blockTypePrivKey, header, encBytes)
|
||||
return armorStr
|
||||
}
|
||||
|
||||
func unarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) {
|
||||
var privKey crypto.PrivKey
|
||||
blockType, header, encBytes, err := crypto.DecodeArmor(armorStr)
|
||||
if err != nil {
|
||||
return privKey, err
|
||||
}
|
||||
if blockType != blockTypePrivKey {
|
||||
return privKey, fmt.Errorf("Unrecognized armor type: %v", blockType)
|
||||
}
|
||||
if header["kdf"] != "bcrypt" {
|
||||
return privKey, fmt.Errorf("Unrecognized KDF type: %v", header["KDF"])
|
||||
}
|
||||
if header["salt"] == "" {
|
||||
return privKey, fmt.Errorf("Missing salt bytes")
|
||||
}
|
||||
saltBytes, err := hex.DecodeString(header["salt"])
|
||||
if err != nil {
|
||||
return privKey, fmt.Errorf("Error decoding salt: %v", err.Error())
|
||||
}
|
||||
privKey, err = decryptPrivKey(saltBytes, encBytes, passphrase)
|
||||
return privKey, err
|
||||
}
|
||||
|
||||
func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) {
|
||||
saltBytes = crypto.CRandBytes(16)
|
||||
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
|
||||
if err != nil {
|
||||
cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error())
|
||||
}
|
||||
key = crypto.Sha256(key) // Get 32 bytes
|
||||
privKeyBytes := privKey.Bytes()
|
||||
return saltBytes, crypto.EncryptSymmetric(privKeyBytes, key)
|
||||
}
|
||||
|
||||
func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) {
|
||||
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
|
||||
if err != nil {
|
||||
cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error())
|
||||
}
|
||||
key = crypto.Sha256(key) // Get 32 bytes
|
||||
privKeyBytes, err := crypto.DecryptSymmetric(encBytes, key)
|
||||
if err != nil {
|
||||
return privKey, err
|
||||
}
|
||||
privKey, err = crypto.PrivKeyFromBytes(privKeyBytes)
|
||||
return privKey, err
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package keys
|
||||
|
||||
import (
|
||||
ccrypto "github.com/cosmos/cosmos-sdk/crypto"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
)
|
||||
|
||||
// Keybase exposes operations on a generic keystore
|
||||
type Keybase interface {
|
||||
|
||||
// CRUD on the keystore
|
||||
List() ([]Info, error)
|
||||
Get(name string) (Info, error)
|
||||
Delete(name, passphrase string) error
|
||||
|
||||
// Sign some bytes, looking up the private key to use
|
||||
Sign(name, passphrase string, msg []byte) (crypto.Signature, crypto.PubKey, error)
|
||||
|
||||
// CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic
|
||||
// key from that.
|
||||
CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error)
|
||||
// CreateKey takes a mnemonic and derives, a password. This method is temporary
|
||||
CreateKey(name, mnemonic, passwd string) (info Info, err error)
|
||||
// CreateFundraiserKey takes a mnemonic and derives, a password
|
||||
CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error)
|
||||
// Derive derives a key from the passed mnemonic using a BIP44 path.
|
||||
Derive(name, mnemonic, passwd string, params hd.BIP44Params) (Info, error)
|
||||
// Create, store, and return a new Ledger key reference
|
||||
CreateLedger(name string, path ccrypto.DerivationPath, algo SigningAlgo) (info Info, err error)
|
||||
|
||||
// Create, store, and return a new offline key reference
|
||||
CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error)
|
||||
|
||||
// The following operations will *only* work on locally-stored keys
|
||||
Update(name, oldpass, newpass string) error
|
||||
Import(name string, armor string) (err error)
|
||||
ImportPubKey(name string, armor string) (err error)
|
||||
Export(name string) (armor string, err error)
|
||||
ExportPubKey(name string) (armor string, err error)
|
||||
}
|
||||
|
||||
// Info is the publicly exposed information about a keypair
|
||||
type Info interface {
|
||||
// Human-readable type for key listing
|
||||
GetType() string
|
||||
// Name of the key
|
||||
GetName() string
|
||||
// Public key
|
||||
GetPubKey() crypto.PubKey
|
||||
}
|
||||
|
||||
var _ Info = &localInfo{}
|
||||
var _ Info = &ledgerInfo{}
|
||||
var _ Info = &offlineInfo{}
|
||||
|
||||
// localInfo is the public information about a locally stored key
|
||||
type localInfo struct {
|
||||
Name string `json:"name"`
|
||||
PubKey crypto.PubKey `json:"pubkey"`
|
||||
PrivKeyArmor string `json:"privkey.armor"`
|
||||
}
|
||||
|
||||
func newLocalInfo(name string, pub crypto.PubKey, privArmor string) Info {
|
||||
return &localInfo{
|
||||
Name: name,
|
||||
PubKey: pub,
|
||||
PrivKeyArmor: privArmor,
|
||||
}
|
||||
}
|
||||
|
||||
func (i localInfo) GetType() string {
|
||||
return "local"
|
||||
}
|
||||
|
||||
func (i localInfo) GetName() string {
|
||||
return i.Name
|
||||
}
|
||||
|
||||
func (i localInfo) GetPubKey() crypto.PubKey {
|
||||
return i.PubKey
|
||||
}
|
||||
|
||||
// ledgerInfo is the public information about a Ledger key
|
||||
type ledgerInfo struct {
|
||||
Name string `json:"name"`
|
||||
PubKey crypto.PubKey `json:"pubkey"`
|
||||
Path ccrypto.DerivationPath `json:"path"`
|
||||
}
|
||||
|
||||
func newLedgerInfo(name string, pub crypto.PubKey, path ccrypto.DerivationPath) Info {
|
||||
return &ledgerInfo{
|
||||
Name: name,
|
||||
PubKey: pub,
|
||||
Path: path,
|
||||
}
|
||||
}
|
||||
|
||||
func (i ledgerInfo) GetType() string {
|
||||
return "ledger"
|
||||
}
|
||||
|
||||
func (i ledgerInfo) GetName() string {
|
||||
return i.Name
|
||||
}
|
||||
|
||||
func (i ledgerInfo) GetPubKey() crypto.PubKey {
|
||||
return i.PubKey
|
||||
}
|
||||
|
||||
// offlineInfo is the public information about an offline key
|
||||
type offlineInfo struct {
|
||||
Name string `json:"name"`
|
||||
PubKey crypto.PubKey `json:"pubkey"`
|
||||
}
|
||||
|
||||
func newOfflineInfo(name string, pub crypto.PubKey) Info {
|
||||
return &offlineInfo{
|
||||
Name: name,
|
||||
PubKey: pub,
|
||||
}
|
||||
}
|
||||
|
||||
func (i offlineInfo) GetType() string {
|
||||
return "offline"
|
||||
}
|
||||
|
||||
func (i offlineInfo) GetName() string {
|
||||
return i.Name
|
||||
}
|
||||
|
||||
func (i offlineInfo) GetPubKey() crypto.PubKey {
|
||||
return i.PubKey
|
||||
}
|
||||
|
||||
// encoding info
|
||||
func writeInfo(i Info) []byte {
|
||||
return cdc.MustMarshalBinary(i)
|
||||
}
|
||||
|
||||
// decoding info
|
||||
func readInfo(bz []byte) (info Info, err error) {
|
||||
err = cdc.UnmarshalBinary(bz, &info)
|
||||
return
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package keys
|
||||
|
||||
import (
|
||||
ccrypto "github.com/cosmos/cosmos-sdk/crypto"
|
||||
amino "github.com/tendermint/go-amino"
|
||||
tcrypto "github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
var cdc = amino.NewCodec()
|
||||
|
||||
func init() {
|
||||
tcrypto.RegisterAmino(cdc)
|
||||
cdc.RegisterInterface((*Info)(nil), nil)
|
||||
cdc.RegisterConcrete(ccrypto.PrivKeyLedgerSecp256k1{},
|
||||
"tendermint/PrivKeyLedgerSecp256k1", nil)
|
||||
cdc.RegisterConcrete(localInfo{}, "crypto/keys/localInfo", nil)
|
||||
cdc.RegisterConcrete(ledgerInfo{}, "crypto/keys/ledgerInfo", nil)
|
||||
cdc.RegisterConcrete(offlineInfo{}, "crypto/keys/offlineInfo", nil)
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
ledger "github.com/zondax/ledger-goclient"
|
||||
)
|
||||
|
||||
var device *ledger.Ledger
|
||||
|
||||
// Ledger derivation path
|
||||
type DerivationPath = []uint32
|
||||
|
||||
// getLedger gets a copy of the device, and caches it
|
||||
func getLedger() (*ledger.Ledger, error) {
|
||||
var err error
|
||||
if device == nil {
|
||||
device, err = ledger.FindLedger()
|
||||
}
|
||||
return device, err
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
secp256k1 "github.com/btcsuite/btcd/btcec"
|
||||
ledger "github.com/zondax/ledger-goclient"
|
||||
|
||||
tcrypto "github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
func pubkeyLedgerSecp256k1(device *ledger.Ledger, path DerivationPath) (pub tcrypto.PubKey, err error) {
|
||||
key, err := device.GetPublicKeySECP256K1(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching public key: %v", err)
|
||||
}
|
||||
var p tcrypto.PubKeySecp256k1
|
||||
// Reserialize in the 33-byte compressed format
|
||||
cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256())
|
||||
copy(p[:], cmp.SerializeCompressed())
|
||||
pub = p
|
||||
return
|
||||
}
|
||||
|
||||
func signLedgerSecp256k1(device *ledger.Ledger, path DerivationPath, msg []byte) (sig tcrypto.Signature, err error) {
|
||||
bsig, err := device.SignSECP256K1(path, msg)
|
||||
if err != nil {
|
||||
return sig, err
|
||||
}
|
||||
sig = tcrypto.SignatureSecp256k1FromBytes(bsig)
|
||||
return
|
||||
}
|
||||
|
||||
// PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano
|
||||
// we cache the PubKey from the first call to use it later
|
||||
type PrivKeyLedgerSecp256k1 struct {
|
||||
// PubKey should be private, but we want to encode it via go-amino
|
||||
// so we can view the address later, even without having the ledger
|
||||
// attached
|
||||
CachedPubKey tcrypto.PubKey
|
||||
Path DerivationPath
|
||||
}
|
||||
|
||||
// NewPrivKeyLedgerSecp256k1 will generate a new key and store the
|
||||
// public key for later use.
|
||||
func NewPrivKeyLedgerSecp256k1(path DerivationPath) (tcrypto.PrivKey, error) {
|
||||
var pk PrivKeyLedgerSecp256k1
|
||||
pk.Path = path
|
||||
// cache the pubkey for later use
|
||||
pubKey, err := pk.getPubKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pk.CachedPubKey = pubKey
|
||||
return &pk, err
|
||||
}
|
||||
|
||||
// ValidateKey allows us to verify the sanity of a key
|
||||
// after loading it from disk
|
||||
func (pk PrivKeyLedgerSecp256k1) ValidateKey() error {
|
||||
// getPubKey will return an error if the ledger is not
|
||||
pub, err := pk.getPubKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// verify this matches cached address
|
||||
if !pub.Equals(pk.CachedPubKey) {
|
||||
return fmt.Errorf("cached key does not match retrieved key")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertIsPrivKeyInner fulfils PrivKey Interface
|
||||
func (pk *PrivKeyLedgerSecp256k1) AssertIsPrivKeyInner() {}
|
||||
|
||||
// Bytes fulfils PrivKey Interface - but it stores the cached pubkey so we can verify
|
||||
// the same key when we reconnect to a ledger
|
||||
func (pk PrivKeyLedgerSecp256k1) Bytes() []byte {
|
||||
return cdc.MustMarshalBinaryBare(pk)
|
||||
}
|
||||
|
||||
// Sign calls the ledger and stores the PubKey for future use
|
||||
//
|
||||
// Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes,
|
||||
// returning an error, so this should only trigger if the privkey is held
|
||||
// in memory for a while before use.
|
||||
func (pk PrivKeyLedgerSecp256k1) Sign(msg []byte) (tcrypto.Signature, error) {
|
||||
dev, err := getLedger()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, err := signLedgerSecp256k1(dev, pk.Path, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
// PubKey returns the stored PubKey
|
||||
func (pk PrivKeyLedgerSecp256k1) PubKey() tcrypto.PubKey {
|
||||
return pk.CachedPubKey
|
||||
}
|
||||
|
||||
// getPubKey reads the pubkey the ledger itself
|
||||
// since this involves IO, it may return an error, which is not exposed
|
||||
// in the PubKey interface, so this function allows better error handling
|
||||
func (pk PrivKeyLedgerSecp256k1) getPubKey() (key tcrypto.PubKey, err error) {
|
||||
dev, err := getLedger()
|
||||
if err != nil {
|
||||
return key, fmt.Errorf("cannot connect to Ledger device - error: %v", err)
|
||||
}
|
||||
key, err = pubkeyLedgerSecp256k1(dev, pk.Path)
|
||||
if err != nil {
|
||||
return key, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err)
|
||||
}
|
||||
return key, err
|
||||
}
|
||||
|
||||
// Equals fulfils PrivKey Interface - makes sure both keys refer to the
|
||||
// same
|
||||
func (pk PrivKeyLedgerSecp256k1) Equals(other tcrypto.PrivKey) bool {
|
||||
if ledger, ok := other.(*PrivKeyLedgerSecp256k1); ok {
|
||||
return pk.CachedPubKey.Equals(ledger.CachedPubKey)
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
tcrypto "github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
func TestRealLedgerSecp256k1(t *testing.T) {
|
||||
|
||||
if os.Getenv("WITH_LEDGER") == "" {
|
||||
t.Skip("Set WITH_LEDGER to run code on real ledger")
|
||||
}
|
||||
msg := []byte("kuhehfeohg")
|
||||
|
||||
path := DerivationPath{44, 60, 0, 0, 0}
|
||||
|
||||
priv, err := NewPrivKeyLedgerSecp256k1(path)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
pub := priv.PubKey()
|
||||
sig, err := priv.Sign(msg)
|
||||
require.Nil(t, err)
|
||||
|
||||
valid := pub.VerifyBytes(msg, sig)
|
||||
require.True(t, valid)
|
||||
|
||||
// now, let's serialize the key and make sure it still works
|
||||
bs := priv.Bytes()
|
||||
priv2, err := tcrypto.PrivKeyFromBytes(bs)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
|
||||
// make sure we get the same pubkey when we load from disk
|
||||
pub2 := priv2.PubKey()
|
||||
require.Equal(t, pub, pub2)
|
||||
|
||||
// signing with the loaded key should match the original pubkey
|
||||
sig, err = priv2.Sign(msg)
|
||||
require.Nil(t, err)
|
||||
valid = pub.VerifyBytes(msg, sig)
|
||||
require.True(t, valid)
|
||||
|
||||
// make sure pubkeys serialize properly as well
|
||||
bs = pub.Bytes()
|
||||
bpub, err := tcrypto.PubKeyFromBytes(bs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, pub, bpub)
|
||||
}
|
||||
|
||||
// TestRealLedgerErrorHandling calls. These tests assume
|
||||
// the ledger is not plugged in....
|
||||
func TestRealLedgerErrorHandling(t *testing.T) {
|
||||
if os.Getenv("WITH_LEDGER") != "" {
|
||||
t.Skip("Skipping on WITH_LEDGER as it tests unplugged cases")
|
||||
}
|
||||
|
||||
// first, try to generate a key, must return an error
|
||||
// (no panic)
|
||||
path := DerivationPath{44, 60, 0, 0, 0}
|
||||
_, err := NewPrivKeyLedgerSecp256k1(path)
|
||||
require.Error(t, err)
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
gaiadnode0:
|
||||
container_name: gaiadnode0
|
||||
image: "tendermint/gaiadnode"
|
||||
ports:
|
||||
- "26656-26657:26656-26657"
|
||||
environment:
|
||||
- ID=0
|
||||
- LOG=$${LOG:-gaiad.log}
|
||||
volumes:
|
||||
- ./build:/gaiad:Z
|
||||
networks:
|
||||
localnet:
|
||||
ipv4_address: 192.168.10.2
|
||||
|
||||
gaiadnode1:
|
||||
container_name: gaiadnode1
|
||||
image: "tendermint/gaiadnode"
|
||||
ports:
|
||||
- "26659-26660:26656-26657"
|
||||
environment:
|
||||
- ID=1
|
||||
- LOG=$${LOG:-gaiad.log}
|
||||
volumes:
|
||||
- ./build:/gaiad:Z
|
||||
networks:
|
||||
localnet:
|
||||
ipv4_address: 192.168.10.3
|
||||
|
||||
gaiadnode2:
|
||||
container_name: gaiadnode2
|
||||
image: "tendermint/gaiadnode"
|
||||
environment:
|
||||
- ID=2
|
||||
- LOG=$${LOG:-gaiad.log}
|
||||
ports:
|
||||
- "26661-26662:26656-26657"
|
||||
volumes:
|
||||
- ./build:/gaiad:Z
|
||||
networks:
|
||||
localnet:
|
||||
ipv4_address: 192.168.10.4
|
||||
|
||||
gaiadnode3:
|
||||
container_name: gaiadnode3
|
||||
image: "tendermint/gaiadnode"
|
||||
environment:
|
||||
- ID=3
|
||||
- LOG=$${LOG:-gaiad.log}
|
||||
ports:
|
||||
- "26663-26664:26656-26657"
|
||||
volumes:
|
||||
- ./build:/gaiad:Z
|
||||
networks:
|
||||
localnet:
|
||||
ipv4_address: 192.168.10.5
|
||||
|
||||
networks:
|
||||
localnet:
|
||||
driver: bridge
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
-
|
||||
subnet: 192.168.10.0/16
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = python -msphinx
|
||||
SPHINXPROJ = Cosmos-SDK
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
@ -0,0 +1,66 @@
|
|||
# Cosmos SDK Documentation
|
||||
|
||||
NOTE: This documentation is a work-in-progress!
|
||||
|
||||
- [Overview](overview)
|
||||
- [Overview](overview/overview.md) - An overview of the Cosmos-SDK
|
||||
- [The Object-Capability Model](overview/capabilities.md) - Security by
|
||||
least-privilege
|
||||
- [Application Architecture](overview/apps.md) - Layers in the application architecture
|
||||
- [Install](install.md) - Install the library and example applications
|
||||
- [Core](core)
|
||||
- [Introduction](core/intro.md) - Intro to the tutorial
|
||||
- [App1 - The Basics](core/app1.md)
|
||||
- [Messages](core/app1.md#messages) - Messages contain the content of a transaction
|
||||
- [Stores](core/app1.md#kvstore) - KVStore is a Merkle Key-Value store.
|
||||
- [Handlers](core/app1.md#handlers) - Handlers are the workhorse of the app!
|
||||
- [Tx](core/app1.md#tx) - Transactions are the ultimate input to the
|
||||
application
|
||||
- [BaseApp](core/app1.md#baseapp) - BaseApp is the base layer of the application
|
||||
- [App2 - Transactions](core/app2.md)
|
||||
- [Amino](core/app2.md#amino) - Amino is the primary serialization library used in the SDK
|
||||
- [Ante Handler](core/app2.md#antehandler) - The AnteHandler
|
||||
authenticates transactions
|
||||
- [App3 - Modules: Auth and Bank](core/app3.md)
|
||||
- [auth.Account](core/app3.md#accounts) - Accounts are the prototypical object kept in the store
|
||||
- [auth.AccountMapper](core/app3.md#account-mapper) - AccountMapper gets and sets Account on a KVStore
|
||||
- [auth.StdTx](core/app3.md#stdtx) - `StdTx` is the default implementation of `Tx`
|
||||
- [auth.StdSignBytes](core/app3.md#signing) - `StdTx` must be signed with certain
|
||||
information
|
||||
- [auth.AnteHandler](core/app3.md#antehandler) - The `AnteHandler`
|
||||
verifies `StdTx`, manages accounts, and deducts fees
|
||||
- [bank.CoinKeeper](core/app3.md#coinkeeper) - CoinKeeper allows for coin
|
||||
transfers on an underlying AccountMapper
|
||||
- [App4 - ABCI](core/app4.md)
|
||||
- [ABCI](core/app4.md#abci) - ABCI is the interface between Tendermint
|
||||
and the Cosmos-SDK
|
||||
- [InitChain](core/app4.md#initchain) - Initialize the application
|
||||
store
|
||||
- [BeginBlock](core/app4.md#beginblock) - BeginBlock runs at the
|
||||
beginning of every block and updates the app about validator behaviour
|
||||
- [EndBlock](core/app4.md#endblock) - EndBlock runs at the
|
||||
end of every block and lets the app change the validator set.
|
||||
- [Query](core/app4.md#query) - Query the application store
|
||||
- [CheckTx](core/app4.md#checktx) - CheckTx only runs the AnteHandler
|
||||
- [App5 - Basecoin](core/app5.md) -
|
||||
- [Directory Structure](core/app5.md#directory-structure) - Keep your
|
||||
application code organized
|
||||
- [Tendermint Node](core/app5.md#tendermint-node) - Run a full
|
||||
blockchain node with your app
|
||||
- [Clients](core/app5.md#clients) - Hook up your app to CLI and REST
|
||||
interfaces for clients to use!
|
||||
|
||||
- [Modules](modules)
|
||||
- [Bank](modules/README.md#bank)
|
||||
- [Staking](modules/README.md#stake)
|
||||
- [Slashing](modules/README.md#slashing)
|
||||
- [Provisions](modules/README.md#provisions)
|
||||
- [Governance](modules/README.md#governance)
|
||||
- [IBC](modules/README.md#ibc)
|
||||
|
||||
- [Clients](clients)
|
||||
- [Running a Node](clients/node.md) - Run a full node!
|
||||
- [Key Management](clients/keys.md) - Managing user keys
|
||||
- [CLI](clients/cli.md) - Queries and transactions via command line
|
||||
- [REST Light Client Daemon](clients/rest.md) - Queries and transactions via REST
|
||||
API
|
|
@ -197,9 +197,9 @@ We'll have ``alice`` send some ``mycoin`` to ``bob``, who has now joined the net
|
|||
|
||||
::
|
||||
|
||||
gaiacli send --amount=1000mycoin --sequence=0 --name=alice --to=5A35E4CC7B7DC0A5CB49CEA91763213A9AE92AD6 --chain-id=test-chain-Uv1EVU
|
||||
gaiacli send --amount=1000mycoin --sequence=0 --from=alice --to=5A35E4CC7B7DC0A5CB49CEA91763213A9AE92AD6 --chain-id=test-chain-Uv1EVU
|
||||
|
||||
where the ``--sequence`` flag is to be incremented for each transaction, the ``--name`` flag is the sender (alice), and the ``--to`` flag takes ``bob``'s address. You'll see something like:
|
||||
where the ``--sequence`` flag is to be incremented for each transaction, the ``--from`` flag is the sender (alice), and the ``--to`` flag takes ``bob``'s address. You'll see something like:
|
||||
|
||||
::
|
||||
|
||||
|
@ -264,7 +264,7 @@ Now ``bob`` can create a validator with that pubkey.
|
|||
|
||||
::
|
||||
|
||||
gaiacli stake create-validator --amount=10mycoin --name=bob --address-validator=<address> --pub-key=<pubkey> --moniker=bobby
|
||||
gaiacli stake create-validator --amount=10mycoin --from=bob --address-validator=<address> --pub-key=<pubkey> --moniker=bobby
|
||||
|
||||
with an output like:
|
||||
|
||||
|
@ -291,7 +291,7 @@ To confirm for certain the new validator is active, ask the tendermint node:
|
|||
|
||||
::
|
||||
|
||||
curl localhost:46657/validators
|
||||
curl localhost:26657/validators
|
||||
|
||||
If you now kill either node, blocks will stop streaming in, because
|
||||
there aren't enough validators online. Turn it back on and they will
|
||||
|
@ -306,13 +306,13 @@ First let's have ``alice`` send some coins to ``charlie``:
|
|||
|
||||
::
|
||||
|
||||
gaiacli send --amount=1000mycoin --sequence=2 --name=alice --to=48F74F48281C89E5E4BE9092F735EA519768E8EF
|
||||
gaiacli send --amount=1000mycoin --sequence=2 --from=alice --to=48F74F48281C89E5E4BE9092F735EA519768E8EF
|
||||
|
||||
Then ``charlie`` will delegate some mycoin to ``bob``:
|
||||
|
||||
::
|
||||
|
||||
gaiacli stake delegate --amount=10mycoin --address-delegator=<charlie's address> --address-validator=<bob's address> --name=charlie
|
||||
gaiacli stake delegate --amount=10mycoin --address-delegator=<charlie's address> --address-validator=<bob's address> --from=charlie
|
||||
|
||||
You'll see output like:
|
||||
|
||||
|
@ -396,7 +396,7 @@ your VotingPower reduce and your account balance increase.
|
|||
|
||||
::
|
||||
|
||||
gaiacli stake unbond --amount=5mycoin --name=charlie --address-delegator=<address> --address-validator=<address>
|
||||
gaiacli stake unbond --amount=5mycoin --from=charlie --address-delegator=<address> --address-validator=<address>
|
||||
gaiacli account 48F74F48281C89E5E4BE9092F735EA519768E8EF
|
||||
|
||||
See the bond decrease with ``gaiacli stake delegation`` like above.
|
|
@ -0,0 +1,216 @@
|
|||
//TODO update .rst
|
||||
|
||||
# Staking Module
|
||||
|
||||
## Overview
|
||||
|
||||
The Cosmos Hub is a Tendermint-based Delegated Proof of Stake (DPos) blockchain
|
||||
system that serves as a backbone of the Cosmos ecosystem. It is operated and
|
||||
secured by an open and globally decentralized set of validators. Tendermint is
|
||||
a Byzantine fault-tolerant distributed protocol for consensus among distrusting
|
||||
parties, in this case the group of validators which produce the blocks for the
|
||||
Cosmos Hub. To avoid the nothing-at-stake problem, a validator in Tendermint
|
||||
needs to lock up coins in a bond deposit. Each bond's atoms are illiquid, they
|
||||
cannot be transferred - in order to become liquid, they must be unbonded, a
|
||||
process which will take 3 weeks by default at Cosmos Hub launch. Tendermint
|
||||
protocol messages are signed by the validator's private key and are therefor
|
||||
attributable. Validators acting outside protocol specifications can be made
|
||||
accountable through punishing by slashing (burning) their bonded Atoms. On the
|
||||
other hand, validators are rewarded for their service of securing blockchain
|
||||
network by the inflationary provisions and transactions fees. This incentivizes
|
||||
correct behavior of the validators and provides the economic security of the
|
||||
network.
|
||||
|
||||
The native token of the Cosmos Hub is called the Atom; becoming a validator of the
|
||||
Cosmos Hub requires holding Atoms. However, not all Atom holders are validators
|
||||
of the Cosmos Hub. More precisely, there is a selection process that determines
|
||||
the validator set as a subset of all validators (Atom holders that
|
||||
want to become a validator). The other option for Atom holders is to delegate
|
||||
their atoms to validators, i.e., being a delegator. A delegator is an Atom
|
||||
holder that has put its Atoms at stake by delegating it to a validator. By bonding
|
||||
Atoms to secure the network (and taking a risk of being slashed in case of
|
||||
misbehaviour), a user is rewarded with inflationary provisions and transaction
|
||||
fees proportional to the amount of its bonded Atoms. The Cosmos Hub is
|
||||
designed to efficiently facilitate a small numbers of validators (hundreds),
|
||||
and large numbers of delegators (tens of thousands). More precisely, it is the
|
||||
role of the Staking module of the Cosmos Hub to support various staking
|
||||
functionality including validator set selection, delegating, bonding and
|
||||
withdrawing Atoms, and the distribution of inflationary provisions and
|
||||
transaction fees.
|
||||
|
||||
## Basic Terms and Definitions
|
||||
|
||||
* Cosmsos Hub - a Tendermint-based Delegated Proof of Stake (DPos)
|
||||
blockchain system
|
||||
* Atom - native token of the Cosmsos Hub
|
||||
* Atom holder - an entity that holds some amount of Atoms
|
||||
* Pool - Global object within the Cosmos Hub which accounts global state
|
||||
including the total amount of bonded, unbonding, and unbonded atoms
|
||||
* Validator Share - Share which a validator holds to represent its portion of
|
||||
bonded, unbonding or unbonded atoms in the pool
|
||||
* Delegation Share - Shares which a delegation bond holds to represent its
|
||||
portion of bonded, unbonding or unbonded shares in a validator
|
||||
* Bond Atoms - a process of locking Atoms in a delegation share which holds them
|
||||
under protocol control.
|
||||
* Slash Atoms - the process of burning atoms in the pool and assoiated
|
||||
validator shares of a misbehaving validator, (not behaving according to the
|
||||
protocol specification). This process devalues the worth of delegation shares
|
||||
of the given validator
|
||||
* Unbond Shares - Process of retrieving atoms from shares. If the shares are
|
||||
bonded the shares must first remain in an inbetween unbonding state for the
|
||||
duration of the unbonding period
|
||||
* Redelegating Shares - Process of redelegating atoms from one validator to
|
||||
another. This process is instantaneous, but the redelegated atoms are
|
||||
retrospecively slashable if the old validator is found to misbehave for any
|
||||
blocks before the redelegation. These atoms are simultaniously slashable
|
||||
for any new blocks which the new validator misbehavess
|
||||
* Validator - entity with atoms which is either actively validating the Tendermint
|
||||
protocol (bonded validator) or vying to validate .
|
||||
* Bonded Validator - a validator whose atoms are currently bonded and liable to
|
||||
be slashed. These validators are to be able to sign protocol messages for
|
||||
Tendermint consensus. At Cosmos Hub genesis there is a maximum of 100
|
||||
bonded validator positions. Only Bonded Validators receive atom provisions
|
||||
and fee rewards.
|
||||
* Delegator - an Atom holder that has bonded Atoms to a validator
|
||||
* Unbonding period - time required in the unbonding state when unbonding
|
||||
shares. Time slashable to old validator after a redelegation. Time for which
|
||||
validators can be slashed after an infraction. To provide the requisite
|
||||
cryptoeconomic security guarantees, all of these must be equal.
|
||||
* Atom provisions - The process of increasing the Atom supply. Atoms are
|
||||
periodically created on the Cosmos Hub and issued to bonded Atom holders.
|
||||
The goal of inflation is to incentize most of the Atoms in existence to be
|
||||
bonded. Atoms are distributed unbonded and using the fee_distribution mechanism
|
||||
* Transaction fees - transaction fee is a fee that is included in a Cosmsos Hub
|
||||
transaction. The fees are collected by the current validator set and
|
||||
distributed among validators and delegators in proportion to their bonded
|
||||
Atom share
|
||||
* Commission fee - a fee taken from the transaction fees by a validator for
|
||||
their service
|
||||
|
||||
## The pool and the share
|
||||
|
||||
At the core of the Staking module is the concept of a pool which denotes a
|
||||
collection of Atoms contributed by different Atom holders. There are three
|
||||
pools in the Staking module: the bonded, unbonding, and unbonded pool. Bonded
|
||||
Atoms are part of the global bonded pool. If a validator or delegator wants to
|
||||
unbond its shares, these Shares are moved to the the unbonding pool for the
|
||||
duration of the unbonding period. From here normally Atoms will be moved
|
||||
directly into the delegators wallet, however under the situation thatn an
|
||||
entire validator gets unbonded, the Atoms of the delegations will remain with
|
||||
the validator and moved to the unbonded pool. For each pool, the total amount
|
||||
of bonded, unbonding, or unbonded Atoms are tracked as well as the current
|
||||
amount of issued pool-shares, the specific holdings of these shares by
|
||||
validators are tracked in protocol by the validator object.
|
||||
|
||||
A share is a unit of Atom distribution and the value of the share
|
||||
(share-to-atom exchange rate) can change during system execution. The
|
||||
share-to-atom exchange rate can be computed as:
|
||||
|
||||
`share-to-atom-exchange-rate = size of the pool / ammount of issued shares`
|
||||
|
||||
Then for each validator (in a per validator data structure) the protocol keeps
|
||||
track of the amount of shares the validator owns in a pool. At any point in
|
||||
time, the exact amount of Atoms a validator has in the pool can be computed as
|
||||
the number of shares it owns multiplied with the current share-to-atom exchange
|
||||
rate:
|
||||
|
||||
`validator-coins = validator.Shares * share-to-atom-exchange-rate`
|
||||
|
||||
The benefit of such accounting of the pool resources is the fact that a
|
||||
modification to the pool from bonding/unbonding/slashing of Atoms affects only
|
||||
global data (size of the pool and the number of shares) and not the related
|
||||
validator data structure, i.e., the data structure of other validators do not
|
||||
need to be modified. This has the advantage that modifying global data is much
|
||||
cheaper computationally than modifying data of every validator. Let's explain
|
||||
this further with several small examples:
|
||||
|
||||
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
XXX TODO make way less verbose lets use bullet points to describe the example
|
||||
XXX Also need to update to not include bonded atom provisions all atoms are
|
||||
XXX redistributed with the fee pool now
|
||||
|
||||
We consider initially 4 validators p1, p2, p3 and p4, and that each validator
|
||||
has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have
|
||||
issued initially 40 shares (note that the initial distribution of the shares,
|
||||
i.e., share-to-atom exchange rate can be set to any meaningful value), i.e.,
|
||||
share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we
|
||||
have, the size of the pool is 40 Atoms, and the amount of issued shares is
|
||||
equal to 40. And for each validator we store in their corresponding data
|
||||
structure that each has 10 shares of the bonded pool. Now lets assume that the
|
||||
validator p4 starts process of unbonding of 5 shares. Then the total size of
|
||||
the pool is decreased and now it will be 35 shares and the amount of Atoms is
|
||||
35 . Note that the only change in other data structures needed is reducing the
|
||||
number of shares for a validator p4 from 10 to 5.
|
||||
|
||||
Let's consider now the case where a validator p1 wants to bond 15 more atoms to
|
||||
the pool. Now the size of the pool is 50, and as the exchange rate hasn't
|
||||
changed (1 share is still worth 1 Atom), we need to create more shares, i.e. we
|
||||
now have 50 shares in the pool in total. Validators p2, p3 and p4 still have
|
||||
(correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we
|
||||
don't need to modify anything in their corresponding data structures. But p1
|
||||
now has 25 shares, so we update the amount of shares owned by p1 in its
|
||||
data structure. Note that apart from the size of the pool that is in Atoms, all
|
||||
other data structures refer only to shares.
|
||||
|
||||
Finally, let's consider what happens when new Atoms are created and added to
|
||||
the pool due to inflation. Let's assume that the inflation rate is 10 percent
|
||||
and that it is applied to the current state of the pool. This means that 5
|
||||
Atoms are created and added to the pool and that each validator now
|
||||
proportionally increase it's Atom count. Let's analyse how this change is
|
||||
reflected in the data structures. First, the size of the pool is increased and
|
||||
is now 55 atoms. As a share of each validator in the pool hasn't changed, this
|
||||
means that the total number of shares stay the same (50) and that the amount of
|
||||
shares of each validator stays the same (correspondingly 25, 10, 10, 5). But
|
||||
the exchange rate has changed and each share is now worth 55/50 Atoms per
|
||||
share, so each validator has effectively increased amount of Atoms it has. So
|
||||
validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms.
|
||||
|
||||
The concepts of the pool and its shares is at the core of the accounting in the
|
||||
Staking module. It is used for managing the global pools (such as bonding and
|
||||
unbonding pool), but also for distribution of Atoms between validator and its
|
||||
delegators (we will explain this in section X).
|
||||
|
||||
#### Delegator shares
|
||||
|
||||
A validator is, depending on its status, contributing Atoms to either the
|
||||
unbonding or unbonded pool - the validator in turn holds some amount of pool
|
||||
shares. Not all of a validator's Atoms (and respective shares) are necessarily
|
||||
owned by the validator, some may be owned by delegators to that validator. The
|
||||
mechanism for distribution of Atoms (and shares) between a validator and its
|
||||
delegators is based on a notion of delegator shares. More precisely, every
|
||||
validator is issuing (local) delegator shares
|
||||
(`Validator.IssuedDelegatorShares`) that represents some portion of global
|
||||
shares managed by the validator (`Validator.GlobalStakeShares`). The principle
|
||||
behind managing delegator shares is the same as described in [Section](#The
|
||||
pool and the share). We now illustrate it with an example.
|
||||
|
||||
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
XXX TODO make way less verbose lets use bullet points to describe the example
|
||||
XXX Also need to update to not include bonded atom provisions all atoms are
|
||||
XXX redistributed with the fee pool now
|
||||
|
||||
Let's consider 4 validators p1, p2, p3 and p4, and assume that each validator
|
||||
has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have
|
||||
issued initially 40 global shares, i.e., that
|
||||
`share-to-atom-exchange-rate = 1 atom per share`. So we will set
|
||||
`GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the
|
||||
Validator data structure of each validator `Validator.GlobalStakeShares = 10`.
|
||||
Furthermore, each validator issued 10 delegator shares which are initially
|
||||
owned by itself, i.e., `Validator.IssuedDelegatorShares = 10`, where
|
||||
`delegator-share-to-global-share-ex-rate = 1 global share per delegator share`.
|
||||
Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and
|
||||
consider what are the updates we need to make to the data structures. First,
|
||||
`GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, for
|
||||
validator p1 we have `Validator.GlobalStakeShares = 15`, but we also need to
|
||||
issue also additional delegator shares, i.e.,
|
||||
`Validator.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator
|
||||
shares of validator p1, where each delegator share is worth 1 global shares,
|
||||
i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due to
|
||||
inflation. In that case, we only need to update `GlobalState.BondedPool` which
|
||||
is now equal to 50 Atoms as created Atoms are added to the bonded pool. Note
|
||||
that the amount of global and delegator shares stay the same but they are now
|
||||
worth more as share-to-atom-exchange-rate is now worth 50/45 Atoms per share.
|
||||
Therefore, a delegator d1 now owns:
|
||||
|
||||
`delegatorCoins = 5 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 5.55 Atoms`
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
# Testnet Setup
|
||||
|
||||
**Note:** This document is incomplete and may not be up-to-date with the
|
||||
state of the code.
|
||||
|
||||
See the [installation guide](../sdk/install.html) for details on
|
||||
installation.
|
||||
|
||||
Here is a quick example to get you off your feet:
|
||||
|
||||
First, generate a couple of genesis transactions to be incorporated into
|
||||
the genesis file, this will create two keys with the password
|
||||
`1234567890`:
|
||||
|
||||
```
|
||||
gaiad init gen-tx --name=foo --home=$HOME/.gaiad1
|
||||
gaiad init gen-tx --name=bar --home=$HOME/.gaiad2
|
||||
gaiacli keys list
|
||||
```
|
||||
|
||||
**Note:** If you've already run these tests you may need to overwrite
|
||||
keys using the `--owk` flag When you list the keys you should see two
|
||||
addresses, we'll need these later so take note. Now let's actually
|
||||
create the genesis files for both nodes:
|
||||
|
||||
```
|
||||
cp -a ~/.gaiad2/config/gentx/. ~/.gaiad1/config/gentx/
|
||||
cp -a ~/.gaiad1/config/gentx/. ~/.gaiad2/config/gentx/
|
||||
gaiad init --gen-txs --home=$HOME/.gaiad1 --chain-id=test-chain
|
||||
gaiad init --gen-txs --home=$HOME/.gaiad2 --chain-id=test-chain
|
||||
```
|
||||
|
||||
**Note:** If you've already run these tests you may need to overwrite
|
||||
genesis using the `-o` flag. What we just did is copy the genesis
|
||||
transactions between each of the nodes so there is a common genesis
|
||||
transaction set; then we created both genesis files independently from
|
||||
each home directory. Importantly both nodes have independently created
|
||||
their `genesis.json` and `config.toml` files, which should be identical
|
||||
between nodes.
|
||||
|
||||
Great, now that we've initialized the chains, we can start both nodes in
|
||||
the background:
|
||||
|
||||
```
|
||||
gaiad start --home=$HOME/.gaiad1 &> gaia1.log &
|
||||
NODE1_PID=$!
|
||||
gaia start --home=$HOME/.gaiad2 &> gaia2.log &
|
||||
NODE2_PID=$!
|
||||
```
|
||||
|
||||
Note that we save the PID so we can later kill the processes. You can
|
||||
peak at your logs with `tail gaia1.log`, or follow them for a bit with
|
||||
`tail -f gaia1.log`.
|
||||
|
||||
Nice. We can also lookup the validator set:
|
||||
|
||||
```
|
||||
gaiacli validatorset
|
||||
```
|
||||
|
||||
Then, we try to transfer some `steak` to another account:
|
||||
|
||||
```
|
||||
gaiacli account <FOO-ADDR>
|
||||
gaiacli account <BAR-ADDR>
|
||||
gaiacli send --amount=10steak --to=<BAR-ADDR> --from=foo --chain-id=test-chain
|
||||
```
|
||||
|
||||
**Note:** We need to be careful with the `chain-id` and `sequence`
|
||||
|
||||
Check the balance & sequence with:
|
||||
|
||||
```
|
||||
gaiacli account <BAR-ADDR>
|
||||
```
|
||||
|
||||
To confirm for certain the new validator is active, check tendermint:
|
||||
|
||||
```
|
||||
curl localhost:46657/validators
|
||||
```
|
||||
|
||||
Finally, to relinquish all your power, unbond some coins. You should see
|
||||
your VotingPower reduce and your account balance increase.
|
||||
|
||||
```
|
||||
gaiacli unbond --chain-id=<chain-id> --from=test
|
||||
```
|
||||
|
||||
That's it!
|
||||
|
||||
**Note:** TODO demonstrate edit-candidacy **Note:** TODO demonstrate
|
||||
delegation **Note:** TODO demonstrate unbond of delegation **Note:**
|
||||
TODO demonstrate unbond candidate
|
|
@ -0,0 +1,8 @@
|
|||
# CLI
|
||||
|
||||
See `gaiacli --help` for more details.
|
||||
|
||||
Also see the [testnet
|
||||
tutorial](https://github.com/cosmos/cosmos-sdk/tree/develop/cmd/gaia/testnets).
|
||||
|
||||
TODO: cleanup the UX and document this properly.
|
|
@ -0,0 +1,10 @@
|
|||
# Keys
|
||||
|
||||
See the [Tendermint specification](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/encoding.md#public-key-cryptography) for how we work with keys.
|
||||
|
||||
See `gaiacli keys --help`.
|
||||
|
||||
Also see the [testnet
|
||||
tutorial](https://github.com/cosmos/cosmos-sdk/tree/develop/cmd/gaia/testnets).
|
||||
|
||||
TODO: cleanup the UX and document this properly
|
|
@ -17,6 +17,13 @@ paths:
|
|||
responses:
|
||||
200:
|
||||
description: Plaintext version i.e. "v0.5.0"
|
||||
/node_version:
|
||||
get:
|
||||
summary: Version of the connected node
|
||||
description: Get the version of the SDK running on the connected node to compare against expected
|
||||
responses:
|
||||
200:
|
||||
description: Plaintext version i.e. "v0.5.0"
|
||||
/node_info:
|
||||
get:
|
||||
description: Only the node info. Block information can be queried via /block/latest
|
||||
|
@ -41,7 +48,7 @@ paths:
|
|||
type: string
|
||||
listen_addr:
|
||||
type: string
|
||||
example: 192.168.56.1:46656
|
||||
example: 192.168.56.1:26656
|
||||
version:
|
||||
description: Tendermint version
|
||||
type: string
|
|
@ -0,0 +1,29 @@
|
|||
# Ledger // Cosmos
|
||||
|
||||
### Ledger Support for account keys
|
||||
|
||||
`gaiacli` now supports derivation of account keys from a Ledger seed. To use this functionality you will need the following:
|
||||
|
||||
- A running `gaiad` instance connected to the network you wish to use.
|
||||
- A `gaiacli` instance configured to connect to your chosen `gaiad` instance.
|
||||
- A LedgerNano with the `ledger-cosmos` app installed
|
||||
* Install the Cosmos app onto your Ledger by following the instructions in the [`ledger-cosmos`](https://github.com/cosmos/ledger-cosmos/blob/master/docs/BUILD.md) repository.
|
||||
* A production-ready version of this app will soon be included in the [Ledger Apps Store](https://www.ledgerwallet.com/apps)
|
||||
|
||||
> **NOTE:** Cosmos keys are derived acording to the [BIP 44 Hierarchical Deterministic wallet spec](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki). For more information on Cosmos derivation paths [see the hd package](https://github.com/cosmos/cosmos-sdk/blob/develop/crypto/keys/hd/hdpath.go#L30).
|
||||
|
||||
Once you have the Cosmos app installed on your Ledger, and the Ledger is accessible from the machine you are using `gaiacli` from you can create a new account key using the Ledger:
|
||||
|
||||
```bash
|
||||
$ gaiacli keys add {{ .Key.Name }} --ledger
|
||||
NAME: TYPE: ADDRESS: PUBKEY:
|
||||
{{ .Key.Name }} ledger cosmosaccaddr1aw64xxr80lwqqdk8u2xhlrkxqaxamkr3e2g943 cosmosaccpub1addwnpepqvhs678gh9aqrjc2tg2vezw86csnvgzqq530ujkunt5tkuc7lhjkz5mj629
|
||||
```
|
||||
|
||||
This key will only be accessible while the Ledger is plugged in and unlocked. To send some coins with this key, run the following:
|
||||
|
||||
```bash
|
||||
$ gaiacli send --from {{ .Key.Name }} --to {{ .Destination.AccAddr }} --chain-id=gaia-7000
|
||||
```
|
||||
|
||||
You will be asked to review and confirm the transaction on the Ledger. Once you do this you should see the result in the console! Now you can use your Ledger to manage your Atoms and Stake!
|
|
@ -0,0 +1,10 @@
|
|||
# Running a Node
|
||||
|
||||
TODO: document `gaiad`
|
||||
|
||||
Options for running the `gaiad` binary are effectively the same as for `tendermint`.
|
||||
See `gaiad --help` and the
|
||||
[guide to using Tendermint](https://github.com/tendermint/tendermint/blob/master/docs/using-tendermint.md)
|
||||
for more details.
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# REST
|
||||
|
||||
See `gaiacli advanced rest-server --help` for more.
|
||||
|
||||
Also see the
|
||||
[work in progress API specification](https://github.com/cosmos/cosmos-sdk/pull/1314)
|
170
docs/conf.py
170
docs/conf.py
|
@ -1,170 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cosmos-SDK documentation build configuration file, created by
|
||||
# sphinx-quickstart on Fri Sep 1 21:37:02 2017.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
import sphinx_rtd_theme
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = []
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Cosmos-SDK'
|
||||
copyright = u'2018, The Authors'
|
||||
author = u'The Authors'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = u''
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u''
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'old']
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
# html_theme = 'alabaster'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Custom sidebar templates, must be a dictionary that maps document names
|
||||
# to template names.
|
||||
#
|
||||
# This is required for the alabaster theme
|
||||
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
|
||||
html_sidebars = {
|
||||
'**': [
|
||||
'about.html',
|
||||
'navigation.html',
|
||||
'relations.html', # needs 'show_related': True theme option to display
|
||||
'searchbox.html',
|
||||
'donate.html',
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# -- Options for HTMLHelp output ------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Cosmos-SDKdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'Cosmos-SDK.tex', u'Cosmos-SDK Documentation',
|
||||
u'The Authors', 'manual'),
|
||||
]
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'cosmos-sdk', u'Cosmos-SDK Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'Cosmos-SDK', u'Cosmos-SDK Documentation',
|
||||
author, 'Cosmos-SDK', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
|
@ -0,0 +1,495 @@
|
|||
# The Basics
|
||||
|
||||
Here we introduce the basic components of an SDK by building `App1`, a simple bank.
|
||||
Users have an account address and an account, and they can send coins around.
|
||||
It has no authentication, and just uses JSON for serialization.
|
||||
|
||||
The complete code can be found in [app1.go](examples/app1.go).
|
||||
|
||||
## Messages
|
||||
|
||||
Messages are the primary inputs to the application state machine.
|
||||
They define the content of transactions and can contain arbitrary information.
|
||||
Developers can create messages by implementing the `Msg` interface:
|
||||
|
||||
```go
|
||||
type Msg interface {
|
||||
// Return the message type.
|
||||
// Must be alphanumeric or empty.
|
||||
// Must correspond to name of message handler (XXX).
|
||||
Type() string
|
||||
|
||||
// ValidateBasic does a simple validation check that
|
||||
// doesn't require access to any other information.
|
||||
ValidateBasic() error
|
||||
|
||||
// Get the canonical byte representation of the Msg.
|
||||
// This is what is signed.
|
||||
GetSignBytes() []byte
|
||||
|
||||
// Signers returns the addrs of signers that must sign.
|
||||
// CONTRACT: All signatures must be present to be valid.
|
||||
// CONTRACT: Returns addrs in some deterministic order.
|
||||
GetSigners() []AccAddress
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
The `Msg` interface allows messages to define basic validity checks, as well as
|
||||
what needs to be signed and who needs to sign it.
|
||||
|
||||
For instance, take the simple token sending message type from app1.go:
|
||||
|
||||
```go
|
||||
// MsgSend to send coins from Input to Output
|
||||
type MsgSend struct {
|
||||
From sdk.AccAddress `json:"from"`
|
||||
To sdk.AccAddress `json:"to"`
|
||||
Amount sdk.Coins `json:"amount"`
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgSend) Type() string { return "bank" }
|
||||
```
|
||||
|
||||
It specifies that the message should be JSON marshaled and signed by the sender:
|
||||
|
||||
```go
|
||||
// Implements Msg. JSON encode the message.
|
||||
func (msg MsgSend) GetSignBytes() []byte {
|
||||
bz, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
// Implements Msg. Return the signer.
|
||||
func (msg MsgSend) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{msg.From}
|
||||
}
|
||||
```
|
||||
|
||||
Note Addresses in the SDK are arbitrary byte arrays that are
|
||||
[Bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) encoded
|
||||
when displayed as a string or rendered in JSON. Typically, addresses are the hash of
|
||||
a public key, so we can use them to uniquely identify the required signers for a
|
||||
transaction.
|
||||
|
||||
|
||||
The basic validity check ensures the From and To address are specified and the
|
||||
Amount is positive:
|
||||
|
||||
```go
|
||||
// Implements Msg. Ensure the addresses are good and the
|
||||
// amount is positive.
|
||||
func (msg MsgSend) ValidateBasic() sdk.Error {
|
||||
if len(msg.From) == 0 {
|
||||
return sdk.ErrInvalidAddress("From address is empty")
|
||||
}
|
||||
if len(msg.To) == 0 {
|
||||
return sdk.ErrInvalidAddress("To address is empty")
|
||||
}
|
||||
if !msg.Amount.IsPositive() {
|
||||
return sdk.ErrInvalidCoins("Amount is not positive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
Note the `ValidateBasic` method is called automatically by the SDK!
|
||||
|
||||
## KVStore
|
||||
|
||||
The basic persistence layer for an SDK application is the KVStore:
|
||||
|
||||
```go
|
||||
type KVStore interface {
|
||||
Store
|
||||
|
||||
// Get returns nil iff key doesn't exist. Panics on nil key.
|
||||
Get(key []byte) []byte
|
||||
|
||||
// Has checks if a key exists. Panics on nil key.
|
||||
Has(key []byte) bool
|
||||
|
||||
// Set sets the key. Panics on nil key.
|
||||
Set(key, value []byte)
|
||||
|
||||
// Delete deletes the key. Panics on nil key.
|
||||
Delete(key []byte)
|
||||
|
||||
// Iterator over a domain of keys in ascending order. End is exclusive.
|
||||
// Start must be less than end, or the Iterator is invalid.
|
||||
// CONTRACT: No writes may happen within a domain while an iterator exists over it.
|
||||
Iterator(start, end []byte) Iterator
|
||||
|
||||
// Iterator over a domain of keys in descending order. End is exclusive.
|
||||
// Start must be greater than end, or the Iterator is invalid.
|
||||
// CONTRACT: No writes may happen within a domain while an iterator exists over it.
|
||||
ReverseIterator(start, end []byte) Iterator
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Note it is unforgiving - it panics on nil keys!
|
||||
|
||||
The primary implementation of the KVStore is currently the IAVL store. In the future, we plan to support other Merkle KVStores,
|
||||
like Ethereum's radix trie.
|
||||
|
||||
As we'll soon see, apps have many distinct KVStores, each with a different name and for a different concern.
|
||||
Access to a store is mediated by *object-capability keys*, which must be granted to a handler during application startup.
|
||||
|
||||
## Handlers
|
||||
|
||||
Now that we have a message type and a store interface, we can define our state transition function using a handler:
|
||||
|
||||
```go
|
||||
// Handler defines the core of the state transition function of an application.
|
||||
type Handler func(ctx Context, msg Msg) Result
|
||||
```
|
||||
|
||||
Along with the message, the Handler takes environmental information (a `Context`), and returns a `Result`.
|
||||
All information necessary for processing a message should be available in the context.
|
||||
|
||||
Where is the KVStore in all of this? Access to the KVStore in a message handler is restricted by the Context via object-capability keys.
|
||||
Only handlers which were given explict access to a store's key will be able to access that store during message processsing.
|
||||
|
||||
### Context
|
||||
|
||||
The SDK uses a `Context` to propogate common information across functions.
|
||||
Most importantly, the `Context` restricts access to KVStores based on object-capability keys.
|
||||
Only handlers which have been given explicit access to a key will be able to access the corresponding store.
|
||||
|
||||
For instance, the FooHandler can only load the store it's given the key for:
|
||||
|
||||
```go
|
||||
// newFooHandler returns a Handler that can access a single store.
|
||||
func newFooHandler(key sdk.StoreKey) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
store := ctx.KVStore(key)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Context` is modeled after the Golang
|
||||
[context.Context](https://golang.org/pkg/context/), which has
|
||||
become ubiquitous in networking middleware and routing applications as a means
|
||||
to easily propogate request context through handler functions.
|
||||
Many methods on SDK objects receive a context as the first argument.
|
||||
|
||||
The Context also contains the
|
||||
[block header](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/blockchain.md#header),
|
||||
which includes the latest timestamp from the blockchain and other information about the latest block.
|
||||
|
||||
See the [Context API
|
||||
docs](https://godoc.org/github.com/cosmos/cosmos-sdk/types#Context) for more details.
|
||||
|
||||
### Result
|
||||
|
||||
Handler takes a Context and Msg and returns a Result.
|
||||
Result is motivated by the corresponding [ABCI result](https://github.com/tendermint/tendermint/blob/master/abci/types/types.proto#L165).
|
||||
It contains return values, error information, logs, and meta data about the transaction:
|
||||
|
||||
```go
|
||||
// Result is the union of ResponseDeliverTx and ResponseCheckTx.
|
||||
type Result struct {
|
||||
|
||||
// Code is the response code, is stored back on the chain.
|
||||
Code ABCICodeType
|
||||
|
||||
// Data is any data returned from the app.
|
||||
Data []byte
|
||||
|
||||
// Log is just debug information. NOTE: nondeterministic.
|
||||
Log string
|
||||
|
||||
// GasWanted is the maximum units of work we allow this tx to perform.
|
||||
GasWanted int64
|
||||
|
||||
// GasUsed is the amount of gas actually consumed. NOTE: unimplemented
|
||||
GasUsed int64
|
||||
|
||||
// Tx fee amount and denom.
|
||||
FeeAmount int64
|
||||
FeeDenom string
|
||||
|
||||
// Tags are used for transaction indexing and pubsub.
|
||||
Tags Tags
|
||||
}
|
||||
```
|
||||
|
||||
We'll talk more about these fields later in the tutorial. For now, note that a
|
||||
`0` value for the `Code` is considered a success, and everything else is a
|
||||
failure. The `Tags` can contain meta data about the transaction that will allow
|
||||
us to easily lookup transactions that pertain to particular accounts or actions.
|
||||
|
||||
### Handler
|
||||
|
||||
Let's define our handler for App1:
|
||||
|
||||
```go
|
||||
// Handle MsgSend.
|
||||
// NOTE: msg.From, msg.To, and msg.Amount were already validated
|
||||
// in ValidateBasic().
|
||||
func handleMsgSend(ctx sdk.Context, key *sdk.KVStoreKey, msg MsgSend) sdk.Result {
|
||||
// Load the store.
|
||||
store := ctx.KVStore(key)
|
||||
|
||||
// Debit from the sender.
|
||||
if res := handleFrom(store, msg.From, msg.Amount); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
// Credit the receiver.
|
||||
if res := handleTo(store, msg.To, msg.Amount); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
// Return a success (Code 0).
|
||||
// Add list of key-value pair descriptors ("tags").
|
||||
return sdk.Result{
|
||||
Tags: msg.Tags(),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We have only a single message type, so just one message-specific function to define, `handleMsgSend`.
|
||||
|
||||
Note this handler has unrestricted access to the store specified by the capability key `keyAcc`,
|
||||
so it must define what to store and how to encode it. Later, we'll introduce
|
||||
higher-level abstractions so Handlers are restricted in what they can do.
|
||||
For this first example, we use a simple account that is JSON encoded:
|
||||
|
||||
```go
|
||||
type appAccount struct {
|
||||
Coins sdk.Coins `json:"coins"`
|
||||
}
|
||||
```
|
||||
|
||||
Coins is a useful type provided by the SDK for multi-asset accounts.
|
||||
We could just use an integer here for a single coin type, but
|
||||
it's worth [getting to know
|
||||
Coins](https://godoc.org/github.com/cosmos/cosmos-sdk/types#Coins).
|
||||
|
||||
|
||||
Now we're ready to handle the two parts of the MsgSend:
|
||||
|
||||
```go
|
||||
func handleFrom(store sdk.KVStore, from sdk.AccAddress, amt sdk.Coins) sdk.Result {
|
||||
// Get sender account from the store.
|
||||
accBytes := store.Get(from)
|
||||
if accBytes == nil {
|
||||
// Account was not added to store. Return the result of the error.
|
||||
return sdk.NewError(2, 101, "Account not added to store").Result()
|
||||
}
|
||||
|
||||
// Unmarshal the JSON account bytes.
|
||||
var acc appAccount
|
||||
err := json.Unmarshal(accBytes, &acc)
|
||||
if err != nil {
|
||||
// InternalError
|
||||
return sdk.ErrInternal("Error when deserializing account").Result()
|
||||
}
|
||||
|
||||
// Deduct msg amount from sender account.
|
||||
senderCoins := acc.Coins.Minus(amt)
|
||||
|
||||
// If any coin has negative amount, return insufficient coins error.
|
||||
if !senderCoins.IsNotNegative() {
|
||||
return sdk.ErrInsufficientCoins("Insufficient coins in account").Result()
|
||||
}
|
||||
|
||||
// Set acc coins to new amount.
|
||||
acc.Coins = senderCoins
|
||||
|
||||
// Encode sender account.
|
||||
accBytes, err = json.Marshal(acc)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Account encoding error").Result()
|
||||
}
|
||||
|
||||
// Update store with updated sender account
|
||||
store.Set(from, accBytes)
|
||||
return sdk.Result{}
|
||||
}
|
||||
|
||||
func handleTo(store sdk.KVStore, to sdk.AccAddress, amt sdk.Coins) sdk.Result {
|
||||
// Add msg amount to receiver account
|
||||
accBytes := store.Get(to)
|
||||
var acc appAccount
|
||||
if accBytes == nil {
|
||||
// Receiver account does not already exist, create a new one.
|
||||
acc = appAccount{}
|
||||
} else {
|
||||
// Receiver account already exists. Retrieve and decode it.
|
||||
err := json.Unmarshal(accBytes, &acc)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Account decoding error").Result()
|
||||
}
|
||||
}
|
||||
|
||||
// Add amount to receiver's old coins
|
||||
receiverCoins := acc.Coins.Plus(amt)
|
||||
|
||||
// Update receiver account
|
||||
acc.Coins = receiverCoins
|
||||
|
||||
// Encode receiver account
|
||||
accBytes, err := json.Marshal(acc)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Account encoding error").Result()
|
||||
}
|
||||
|
||||
// Update store with updated receiver account
|
||||
store.Set(to, accBytes)
|
||||
return sdk.Result{}
|
||||
}
|
||||
```
|
||||
|
||||
The handler is straight forward. We first load the KVStore from the context using the granted capability key.
|
||||
Then we make two state transitions: one for the sender, one for the receiver.
|
||||
Each one involves JSON unmarshalling the account bytes from the store, mutating
|
||||
the `Coins`, and JSON marshalling back into the store.
|
||||
|
||||
And that's that!
|
||||
|
||||
## Tx
|
||||
|
||||
The final piece before putting it all together is the `Tx`.
|
||||
While `Msg` contains the content for particular functionality in the application, the actual input
|
||||
provided by the user is a serialized `Tx`. Applications may have many implementations of the `Msg` interface,
|
||||
but they should have only a single implementation of `Tx`:
|
||||
|
||||
|
||||
```go
|
||||
// Transactions wrap messages.
|
||||
type Tx interface {
|
||||
// Gets the Msgs.
|
||||
GetMsgs() []Msg
|
||||
}
|
||||
```
|
||||
|
||||
The `Tx` just wraps a `[]Msg`, and may include additional authentication data, like signatures and account nonces.
|
||||
Applications must specify how their `Tx` is decoded, as this is the ultimate input into the application.
|
||||
We'll talk more about `Tx` types later, specifically when we introduce the `StdTx`.
|
||||
|
||||
In this first application, we won't have any authentication at all. This might
|
||||
make sense in a private network where access is controlled by alternative means,
|
||||
like client-side TLS certificates, but in general, we'll want to bake the authentication
|
||||
right into our state machine. We'll use `Tx` to do that
|
||||
in the next app. For now, the `Tx` just embeds `MsgSend` and uses JSON:
|
||||
|
||||
|
||||
```go
|
||||
// Simple tx to wrap the Msg.
|
||||
type app1Tx struct {
|
||||
MsgSend
|
||||
}
|
||||
|
||||
// This tx only has one Msg.
|
||||
func (tx app1Tx) GetMsgs() []sdk.Msg {
|
||||
return []sdk.Msg{tx.MsgSend}
|
||||
}
|
||||
|
||||
// JSON decode MsgSend.
|
||||
func txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) {
|
||||
var tx app1Tx
|
||||
err := json.Unmarshal(txBytes, &tx)
|
||||
if err != nil {
|
||||
return nil, sdk.ErrTxDecode(err.Error())
|
||||
}
|
||||
return tx, nil
|
||||
}
|
||||
```
|
||||
|
||||
## BaseApp
|
||||
|
||||
Finally, we stitch it all together using the `BaseApp`.
|
||||
|
||||
The BaseApp is an abstraction over the [Tendermint
|
||||
ABCI](https://github.com/tendermint/tendermint/tree/master/abci) that
|
||||
simplifies application development by handling common low-level concerns.
|
||||
It serves as the mediator between the two key components of an SDK app: the store
|
||||
and the message handlers. The BaseApp implements the
|
||||
[`abci.Application`](https://godoc.org/github.com/tendermint/tendermint/abci/types#Application) interface.
|
||||
See the [BaseApp API
|
||||
documentation](https://godoc.org/github.com/cosmos/cosmos-sdk/baseapp) for more details.
|
||||
|
||||
Here is the complete setup for App1:
|
||||
|
||||
```go
|
||||
func NewApp1(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
||||
cdc := wire.NewCodec()
|
||||
|
||||
// Create the base application object.
|
||||
app := bapp.NewBaseApp(app1Name, cdc, logger, db)
|
||||
|
||||
// Create a capability key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
|
||||
// Determine how transactions are decoded.
|
||||
app.SetTxDecoder(txDecoder)
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler receives the keyAccount and thus
|
||||
// gets access to the account store.
|
||||
app.Router().
|
||||
AddRoute("bank", NewApp1Handler(keyAccount))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
||||
```
|
||||
|
||||
Every app will have such a function that defines the setup of the app.
|
||||
It will typically be contained in an `app.go` file.
|
||||
We'll talk about how to connect this app object with the CLI, a REST API,
|
||||
the logger, and the filesystem later in the tutorial. For now, note that this is where we
|
||||
register handlers for messages and grant them access to stores.
|
||||
|
||||
Here, we have only a single Msg type, `bank`, a single store for accounts, and a single handler.
|
||||
The handler is granted access to the store by giving it the capability key.
|
||||
In future apps, we'll have multiple stores and handlers, and not every handler will get access to every store.
|
||||
|
||||
After setting the transaction decoder and the message handling routes, the final
|
||||
step is to mount the stores and load the latest version.
|
||||
Since we only have one store, we only mount one.
|
||||
|
||||
## Execution
|
||||
|
||||
We're now done the core logic of the app! From here, we can write tests in Go
|
||||
that initialize the store with accounts and execute transactions by calling
|
||||
the `app.DeliverTx` method.
|
||||
|
||||
In a real setup, the app would run as an ABCI application on top of the
|
||||
Tendermint consensus engine. It would be initialized by a Genesis file, and it
|
||||
would be driven by blocks of transactions committed by the underlying Tendermint
|
||||
consensus. We'll talk more about ABCI and how this all works a bit later, but
|
||||
feel free to check the
|
||||
[specification](https://github.com/tendermint/tendermint/blob/master/docs/abci-spec.md).
|
||||
We'll also see how to connect our app to a complete suite of components
|
||||
for running and using a live blockchain application.
|
||||
|
||||
For now, we note the follow sequence of events occurs when a transaction is
|
||||
received (through `app.DeliverTx`):
|
||||
|
||||
- serialized transaction is received by `app.DeliverTx`
|
||||
- transaction is deserialized using `TxDecoder`
|
||||
- for each message in the transaction, run `msg.ValidateBasic()`
|
||||
- for each message in the transaction, load the appropriate handler and execute
|
||||
it with the message
|
||||
|
||||
## Conclusion
|
||||
|
||||
We now have a complete implementation of a simple app!
|
||||
|
||||
In the next section, we'll add another Msg type and another store. Once we have multiple message types
|
||||
we'll need a better way of decoding transactions, since we'll need to decode
|
||||
into the `Msg` interface. This is where we introduce Amino, a superior encoding scheme that lets us decode into interface types!
|
|
@ -0,0 +1,301 @@
|
|||
# Transactions
|
||||
|
||||
In the previous app we built a simple `bank` with one message type for sending
|
||||
coins and one store for storing accounts.
|
||||
Here we build `App2`, which expands on `App1` by introducing
|
||||
|
||||
- a new message type for issuing new coins
|
||||
- a new store for coin metadata (like who can issue coins)
|
||||
- a requirement that transactions include valid signatures
|
||||
|
||||
Along the way, we'll be introduced to Amino for encoding and decoding
|
||||
transactions and to the AnteHandler for processing them.
|
||||
|
||||
The complete code can be found in [app2.go](examples/app2.go).
|
||||
|
||||
|
||||
## Message
|
||||
|
||||
Let's introduce a new message type for issuing coins:
|
||||
|
||||
```go
|
||||
// MsgIssue to allow a registered issuer
|
||||
// to issue new coins.
|
||||
type MsgIssue struct {
|
||||
Issuer sdk.AccAddress
|
||||
Receiver sdk.AccAddress
|
||||
Coin sdk.Coin
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgIssue) Type() string { return "issue" }
|
||||
```
|
||||
|
||||
Note the `Type()` method returns `"issue"`, so this message is of a different
|
||||
type and will be executed by a different handler than `MsgSend`. The other
|
||||
methods for `MsgIssue` are similar to `MsgSend`.
|
||||
|
||||
## Handler
|
||||
|
||||
We'll need a new handler to support the new message type. It just checks if the
|
||||
sender of the `MsgIssue` is the correct issuer for the given coin type, as per the information
|
||||
in the issuer store:
|
||||
|
||||
```go
|
||||
// Handle MsgIssue
|
||||
func handleMsgIssue(keyIssue *sdk.KVStoreKey, keyAcc *sdk.KVStoreKey) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
issueMsg, ok := msg.(MsgIssue)
|
||||
if !ok {
|
||||
return sdk.NewError(2, 1, "MsgIssue is malformed").Result()
|
||||
}
|
||||
|
||||
// Retrieve stores
|
||||
issueStore := ctx.KVStore(keyIssue)
|
||||
accStore := ctx.KVStore(keyAcc)
|
||||
|
||||
// Handle updating coin info
|
||||
if res := handleIssuer(issueStore, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
// Issue coins to receiver using previously defined handleTo function
|
||||
if res := handleTo(accStore, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
return sdk.Result{
|
||||
// Return result with Issue msg tags
|
||||
Tags: issueMsg.Tags(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleIssuer(store sdk.KVStore, issuer sdk.AccAddress, coin sdk.Coin) sdk.Result {
|
||||
// the issuer address is stored directly under the coin denomination
|
||||
denom := []byte(coin.Denom)
|
||||
infoBytes := store.Get(denom)
|
||||
if infoBytes == nil {
|
||||
return sdk.ErrInvalidCoins(fmt.Sprintf("Unknown coin type %s", coin.Denom)).Result()
|
||||
}
|
||||
|
||||
var coinInfo coinInfo
|
||||
err := json.Unmarshal(infoBytes, &coinInfo)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Error when deserializing coinInfo").Result()
|
||||
}
|
||||
|
||||
// Msg Issuer is not authorized to issue these coins
|
||||
if !bytes.Equal(coinInfo.Issuer, issuer) {
|
||||
return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result()
|
||||
}
|
||||
|
||||
return sdk.Result{}
|
||||
}
|
||||
|
||||
// coinInfo stores meta data about a coin
|
||||
type coinInfo struct {
|
||||
Issuer sdk.AccAddress `json:"issuer"`
|
||||
}
|
||||
```
|
||||
|
||||
Note we've introduced the `coinInfo` type to store the issuer address for each coin.
|
||||
We JSON serialize this type and store it directly under the denomination in the
|
||||
issuer store. We could of course add more fields and logic around this,
|
||||
like including the current supply of coins in existence, and enforcing a maximum supply,
|
||||
but that's left as an excercise for the reader :).
|
||||
|
||||
## Amino
|
||||
|
||||
Now that we have two implementations of `Msg`, we won't know before hand
|
||||
which type is contained in a serialized `Tx`. Ideally, we would use the
|
||||
`Msg` interface inside our `Tx` implementation, but the JSON decoder can't
|
||||
decode into interface types. In fact, there's no standard way to unmarshal
|
||||
into interfaces in Go. This is one of the primary reasons we built
|
||||
[Amino](https://github.com/tendermint/go-amino) :).
|
||||
|
||||
While SDK developers can encode transactions and state objects however they
|
||||
like, Amino is the recommended format. The goal of Amino is to improve over the latest version of Protocol Buffers,
|
||||
`proto3`. To that end, Amino is compatible with the subset of `proto3` that
|
||||
excludes the `oneof` keyword.
|
||||
|
||||
While `oneof` provides union types, Amino aims to provide interfaces.
|
||||
The main difference being that with union types, you have to know all the types
|
||||
up front. But anyone can implement an interface type whenever and however
|
||||
they like.
|
||||
|
||||
To implement interface types, Amino allows any concrete implementation of an
|
||||
interface to register a globally unique name that is carried along whenever the
|
||||
type is serialized. This allows Amino to seamlessly deserialize into interface
|
||||
types!
|
||||
|
||||
The primary use for Amino in the SDK is for messages that implement the
|
||||
`Msg` interface. By registering each message with a distinct name, they are each
|
||||
given a distinct Amino prefix, allowing them to be easily distinguished in
|
||||
transactions.
|
||||
|
||||
Amino can also be used for persistent storage of interfaces.
|
||||
|
||||
To use Amino, simply create a codec, and then register types:
|
||||
|
||||
```
|
||||
func NewCodec() *wire.Codec {
|
||||
cdc := wire.NewCodec()
|
||||
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
|
||||
cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil)
|
||||
cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil)
|
||||
return cdc
|
||||
}
|
||||
```
|
||||
|
||||
Amino supports encoding and decoding in both a binary and JSON format.
|
||||
See the [codec API docs](https://godoc.org/github.com/tendermint/go-amino#Codec) for more details.
|
||||
|
||||
## Tx
|
||||
|
||||
Now that we're using Amino, we can embed the `Msg` interface directly in our
|
||||
`Tx`. We can also add a public key and a signature for authentication.
|
||||
|
||||
```go
|
||||
// Simple tx to wrap the Msg.
|
||||
type app2Tx struct {
|
||||
sdk.Msg
|
||||
|
||||
PubKey crypto.PubKey
|
||||
Signature crypto.Signature
|
||||
}
|
||||
|
||||
// This tx only has one Msg.
|
||||
func (tx app2Tx) GetMsgs() []sdk.Msg {
|
||||
return []sdk.Msg{tx.Msg}
|
||||
}
|
||||
```
|
||||
|
||||
We don't need a custom TxDecoder function anymore, since we're just using the
|
||||
Amino codec!
|
||||
|
||||
## AnteHandler
|
||||
|
||||
Now that we have an implementation of `Tx` that includes more than just the Msg,
|
||||
we need to specify how that extra information is validated and processed. This
|
||||
is the role of the `AnteHandler`. The word `ante` here denotes "before", as the
|
||||
`AnteHandler` is run before a `Handler`. While an app can have many Handlers,
|
||||
one for each set of messages, it can have only a single `AnteHandler` that
|
||||
corresponds to its single implementation of `Tx`.
|
||||
|
||||
|
||||
The AnteHandler resembles a Handler:
|
||||
|
||||
|
||||
```go
|
||||
type AnteHandler func(ctx Context, tx Tx) (newCtx Context, result Result, abort bool)
|
||||
```
|
||||
|
||||
Like Handler, AnteHandler takes a Context that restricts its access to stores
|
||||
according to whatever capability keys it was granted. Instead of a `Msg`,
|
||||
however, it takes a `Tx`.
|
||||
|
||||
Like Handler, AnteHandler returns a `Result` type, but it also returns a new
|
||||
`Context` and an `abort bool`.
|
||||
|
||||
For `App2`, we simply check if the PubKey matches the Address, and the Signature validates with the PubKey:
|
||||
|
||||
```go
|
||||
// Simple anteHandler that ensures msg signers have signed.
|
||||
// Provides no replay protection.
|
||||
func antehandler(ctx sdk.Context, tx sdk.Tx) (_ sdk.Context, _ sdk.Result, abort bool) {
|
||||
appTx, ok := tx.(app2Tx)
|
||||
if !ok {
|
||||
// set abort boolean to true so that we don't continue to process failed tx
|
||||
return ctx, sdk.ErrTxDecode("Tx must be of format app2Tx").Result(), true
|
||||
}
|
||||
|
||||
// expect only one msg in app2Tx
|
||||
msg := tx.GetMsgs()[0]
|
||||
|
||||
signerAddrs := msg.GetSigners()
|
||||
|
||||
if len(signerAddrs) != len(appTx.GetSignatures()) {
|
||||
return ctx, sdk.ErrUnauthorized("Number of signatures do not match required amount").Result(), true
|
||||
}
|
||||
|
||||
signBytes := msg.GetSignBytes()
|
||||
for i, addr := range signerAddrs {
|
||||
sig := appTx.GetSignatures()[i]
|
||||
|
||||
// check that submitted pubkey belongs to required address
|
||||
if !bytes.Equal(sig.PubKey.Address(), addr) {
|
||||
return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true
|
||||
}
|
||||
|
||||
// check that signature is over expected signBytes
|
||||
if !sig.PubKey.VerifyBytes(signBytes, sig.Signature) {
|
||||
return ctx, sdk.ErrUnauthorized("Signature verification failed").Result(), true
|
||||
}
|
||||
}
|
||||
|
||||
// authentication passed, app to continue processing by sending msg to handler
|
||||
return ctx, sdk.Result{}, false
|
||||
}
|
||||
```
|
||||
|
||||
## App2
|
||||
|
||||
Let's put it all together now to get App2:
|
||||
|
||||
```go
|
||||
func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
||||
|
||||
cdc := NewCodec()
|
||||
|
||||
// Create the base application object.
|
||||
app := bapp.NewBaseApp(app2Name, cdc, logger, db)
|
||||
|
||||
// Create a key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
// Create a key for accessing the issue store.
|
||||
keyIssue := sdk.NewKVStoreKey("issue")
|
||||
|
||||
// set antehandler function
|
||||
app.SetAnteHandler(antehandler)
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler gets access to the account store.
|
||||
app.Router().
|
||||
AddRoute("send", handleMsgSend(keyAccount)).
|
||||
AddRoute("issue", handleMsgIssue(keyAccount, keyIssue))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount, keyIssue)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
||||
```
|
||||
|
||||
The main difference here, compared to `App1`, is that we use a second capability
|
||||
key for a second store that is *only* passed to a second handler, the
|
||||
`handleMsgIssue`. The first `handleMsgSend` has no access to this second store and cannot read or write to
|
||||
it, ensuring a strong separation of concerns.
|
||||
|
||||
Note also that we do not need to use `SetTxDecoder` here - now that we're using
|
||||
Amino, we simply create a codec, register our types on the codec, and pass the
|
||||
codec into `NewBaseApp`. The SDK takes care of the rest for us!
|
||||
|
||||
## Conclusion
|
||||
|
||||
We've expanded on our first app by adding a new message type for issuing coins,
|
||||
and by checking signatures. We learned how to use Amino for decoding into
|
||||
interface types, allowing us to support multiple Msg types, and we learned how
|
||||
to use the AnteHandler to validate transactions.
|
||||
|
||||
Unfortunately, our application is still insecure, because any valid transaction
|
||||
can be replayed multiple times to drain someones account! Besides, validating
|
||||
signatures and preventing replays aren't things developers should have to think
|
||||
about.
|
||||
|
||||
In the next section, we introduce the built-in SDK modules `auth` and `bank`,
|
||||
which respectively provide secure implementations for all our transaction authentication
|
||||
and coin transfering needs.
|
|
@ -0,0 +1,370 @@
|
|||
# Modules
|
||||
|
||||
In the previous app, we introduced a new `Msg` type and used Amino to encode
|
||||
transactions. We also introduced additional data to the `Tx`, and used a simple
|
||||
`AnteHandler` to validate it.
|
||||
|
||||
Here, in `App3`, we introduce two built-in SDK modules to
|
||||
replace the `Msg`, `Tx`, `Handler`, and `AnteHandler` implementations we've seen
|
||||
so far: `x/auth` and `x/bank`.
|
||||
|
||||
The `x/auth` module implements `Tx` and `AnteHandler` - it has everything we need to
|
||||
authenticate transactions. It also includes a new `Account` type that simplifies
|
||||
working with accounts in the store.
|
||||
|
||||
The `x/bank` module implements `Msg` and `Handler` - it has everything we need
|
||||
to transfer coins between accounts.
|
||||
|
||||
Here, we'll introduce the important types from `x/auth` and `x/bank`, and use
|
||||
them to build `App3`, our shortest app yet. The complete code can be found in
|
||||
[app3.go](examples/app3.go), and at the end of this section.
|
||||
|
||||
For more details, see the
|
||||
[x/auth](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth) and
|
||||
[x/bank](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank) API documentation.
|
||||
|
||||
## Accounts
|
||||
|
||||
The `x/auth` module defines a model of accounts much like Ethereum.
|
||||
In this model, an account contains:
|
||||
|
||||
- Address for identification
|
||||
- PubKey for authentication
|
||||
- AccountNumber to prune empty accounts
|
||||
- Sequence to prevent transaction replays
|
||||
- Coins to carry a balance
|
||||
|
||||
Note that the `AccountNumber` is a unique number that is assigned when the account is
|
||||
created, and the `Sequence` is incremented by one every time a transaction is
|
||||
sent from the account.
|
||||
|
||||
### Account
|
||||
|
||||
The `Account` interface captures this account model with getters and setters:
|
||||
|
||||
```go
|
||||
// Account is a standard account using a sequence number for replay protection
|
||||
// and a pubkey for authentication.
|
||||
type Account interface {
|
||||
GetAddress() sdk.AccAddress
|
||||
SetAddress(sdk.AccAddress) error // errors if already set.
|
||||
|
||||
GetPubKey() crypto.PubKey // can return nil.
|
||||
SetPubKey(crypto.PubKey) error
|
||||
|
||||
GetAccountNumber() int64
|
||||
SetAccountNumber(int64) error
|
||||
|
||||
GetSequence() int64
|
||||
SetSequence(int64) error
|
||||
|
||||
GetCoins() sdk.Coins
|
||||
SetCoins(sdk.Coins) error
|
||||
}
|
||||
```
|
||||
|
||||
Note this is a low-level interface - it allows any of the fields to be over
|
||||
written. As we'll soon see, access can be restricted using the `Keeper`
|
||||
paradigm.
|
||||
|
||||
### BaseAccount
|
||||
|
||||
The default implementation of `Account` is the `BaseAccount`:
|
||||
|
||||
```go
|
||||
// BaseAccount - base account structure.
|
||||
// Extend this by embedding this in your AppAccount.
|
||||
// See the examples/basecoin/types/account.go for an example.
|
||||
type BaseAccount struct {
|
||||
Address sdk.AccAddress `json:"address"`
|
||||
Coins sdk.Coins `json:"coins"`
|
||||
PubKey crypto.PubKey `json:"public_key"`
|
||||
AccountNumber int64 `json:"account_number"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
}
|
||||
```
|
||||
|
||||
It simply contains a field for each of the methods.
|
||||
|
||||
### AccountMapper
|
||||
|
||||
In previous apps using our `appAccount`, we handled
|
||||
marshaling/unmarshaling the account from the store ourselves, by performing
|
||||
operations directly on the KVStore. But unrestricted access to a KVStore isn't really the interface we want
|
||||
to work with in our applications. In the SDK, we use the term `Mapper` to refer
|
||||
to an abstaction over a KVStore that handles marshalling and unmarshalling a
|
||||
particular data type to and from the underlying store.
|
||||
|
||||
The `x/auth` module provides an `AccountMapper` that allows us to get and
|
||||
set `Account` types to the store. Note the benefit of using the `Account`
|
||||
interface here - developers can implement their own account type that extends
|
||||
the `BaseAccount` to store additional data without requiring another lookup from
|
||||
the store.
|
||||
|
||||
Creating an AccountMapper is easy - we just need to specify a codec, a
|
||||
capability key, and a prototype of the object being encoded
|
||||
|
||||
```go
|
||||
accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount)
|
||||
```
|
||||
|
||||
Then we can get, modify, and set accounts. For instance, we could double the
|
||||
amount of coins in an account:
|
||||
|
||||
```go
|
||||
acc := accountMapper.GetAccount(ctx, addr)
|
||||
acc.SetCoins(acc.Coins.Plus(acc.Coins))
|
||||
accountMapper.SetAccount(ctx, addr)
|
||||
```
|
||||
|
||||
Note that the `AccountMapper` takes a `Context` as the first argument, and will
|
||||
load the KVStore from there using the capability key it was granted on creation.
|
||||
|
||||
Also note that you must explicitly call `SetAccount` after mutating an account
|
||||
for the change to persist!
|
||||
|
||||
See the [AccountMapper API
|
||||
docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth#AccountMapper) for more information.
|
||||
|
||||
## StdTx
|
||||
|
||||
Now that we have a native model for accounts, it's time to introduce the native
|
||||
`Tx` type, the `auth.StdTx`:
|
||||
|
||||
```go
|
||||
// StdTx is a standard way to wrap a Msg with Fee and Signatures.
|
||||
// NOTE: the first signature is the FeePayer (Signatures must not be nil).
|
||||
type StdTx struct {
|
||||
Msgs []sdk.Msg `json:"msg"`
|
||||
Fee StdFee `json:"fee"`
|
||||
Signatures []StdSignature `json:"signatures"`
|
||||
Memo string `json:"memo"`
|
||||
}
|
||||
```
|
||||
|
||||
This is the standard form for a transaction in the SDK. Besides the Msgs, it
|
||||
includes:
|
||||
|
||||
- a fee to be paid by the first signer
|
||||
- replay protecting nonces in the signature
|
||||
- a memo of prunable additional data
|
||||
|
||||
Details on how these components are validated is provided under
|
||||
[auth.AnteHandler](#antehandler) below.
|
||||
|
||||
The standard form for signatures is `StdSignature`:
|
||||
|
||||
```go
|
||||
// StdSignature wraps the Signature and includes counters for replay protection.
|
||||
// It also includes an optional public key, which must be provided at least in
|
||||
// the first transaction made by the account.
|
||||
type StdSignature struct {
|
||||
crypto.PubKey `json:"pub_key"` // optional
|
||||
crypto.Signature `json:"signature"`
|
||||
AccountNumber int64 `json:"account_number"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
}
|
||||
```
|
||||
|
||||
The signature includes both an `AccountNumber` and a `Sequence`.
|
||||
The `Sequence` must match the one in the
|
||||
corresponding account when the transaction is processed, and will increment by
|
||||
one with every transaction. This prevents the same
|
||||
transaction from being replayed multiple times, resolving the insecurity that
|
||||
remains in App2.
|
||||
|
||||
The `AccountNumber` is also for replay protection - it allows accounts to be
|
||||
deleted from the store when they run out of accounts. If an account receives
|
||||
coins after it is deleted, the account will be re-created, with the Sequence
|
||||
reset to 0, but a new AccountNumber. If it weren't for the AccountNumber, the
|
||||
last sequence of transactions made by the account before it was deleted could be
|
||||
replayed!
|
||||
|
||||
Finally, the standard form for a transaction fee is `StdFee`:
|
||||
|
||||
```go
|
||||
// StdFee includes the amount of coins paid in fees and the maximum
|
||||
// gas to be used by the transaction. The ratio yields an effective "gasprice",
|
||||
// which must be above some miminum to be accepted into the mempool.
|
||||
type StdFee struct {
|
||||
Amount sdk.Coins `json:"amount"`
|
||||
Gas int64 `json:"gas"`
|
||||
}
|
||||
```
|
||||
|
||||
The fee must be paid by the first signer. This allows us to quickly check if the
|
||||
transaction fee can be paid, and reject the transaction if not.
|
||||
|
||||
## Signing
|
||||
|
||||
The `StdTx` supports multiple messages and multiple signers.
|
||||
To sign the transaction, each signer must collect the following information:
|
||||
|
||||
- the ChainID
|
||||
- the AccountNumber and Sequence for the given signer's account (from the
|
||||
blockchain)
|
||||
- the transaction fee
|
||||
- the list of transaction messages
|
||||
- an optional memo
|
||||
|
||||
Then they can compute the transaction bytes to sign using the
|
||||
`auth.StdSignBytes` function:
|
||||
|
||||
```go
|
||||
bytesToSign := StdSignBytes(chainID, accNum, accSequence, fee, msgs, memo)
|
||||
```
|
||||
|
||||
Note these bytes are unique for each signer, as they depend on the particular
|
||||
signers AccountNumber, Sequence, and optional memo. To facilitate easy
|
||||
inspection before signing, the bytes are actually just a JSON encoded form of
|
||||
all the relevant information.
|
||||
|
||||
## AnteHandler
|
||||
|
||||
As we saw in `App2`, we can use an `AnteHandler` to authenticate transactions
|
||||
before we handle any of their internal messages. While previously we implemented
|
||||
our own simple `AnteHandler`, the `x/auth` module provides a much more advanced
|
||||
one that uses `AccountMapper` and works with `StdTx`:
|
||||
|
||||
```go
|
||||
app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper))
|
||||
```
|
||||
|
||||
The AnteHandler provided by `x/auth` enforces the following rules:
|
||||
|
||||
- the memo must not be too big
|
||||
- the right number of signatures must be provided (one for each unique signer
|
||||
returned by `msg.GetSigner` for each `msg`)
|
||||
- any account signing for the first-time must include a public key in the
|
||||
StdSignature
|
||||
- the signatures must be valid when authenticated in the same order as specified
|
||||
by the messages
|
||||
|
||||
Note that validating
|
||||
signatures requires checking that the correct account number and sequence was
|
||||
used by each signer, as this information is required in the `StdSignBytes`.
|
||||
|
||||
If any of the above are not satisfied, the AnteHandelr returns an error.
|
||||
|
||||
If all of the above verifications pass, the AnteHandler makes the following
|
||||
changes to the state:
|
||||
|
||||
- increment account sequence by one for all signers
|
||||
- set the pubkey in the account for any first-time signers
|
||||
- deduct the fee from the first signer's account
|
||||
|
||||
Recall that incrementing the `Sequence` prevents "replay attacks" where
|
||||
the same message could be executed over and over again.
|
||||
|
||||
The PubKey is required for signature verification, but it is only required in
|
||||
the StdSignature once. From that point on, it will be stored in the account.
|
||||
|
||||
The fee is paid by the first address returned by `msg.GetSigners()` for the first `Msg`,
|
||||
as provided by the `FeePayer(tx Tx) sdk.AccAddress` function.
|
||||
|
||||
## CoinKeeper
|
||||
|
||||
Now that we've seen the `auth.AccountMapper` and how its used to build a
|
||||
complete AnteHandler, it's time to look at how to build higher-level
|
||||
abstractions for taking action on accounts.
|
||||
|
||||
Earlier, we noted that `Mappers` are abstactions over KVStores that handle
|
||||
marshalling and unmarshalling data types to and from underlying stores.
|
||||
We can build another abstraction on top of `Mappers` that we call `Keepers`,
|
||||
which expose only limitted functionality on the underlying types stored by the `Mapper`.
|
||||
|
||||
For instance, the `x/bank` module defines the canonical versions of `MsgSend`
|
||||
and `MsgIssue` for the SDK, as well as a `Handler` for processing them. However,
|
||||
rather than passing a `KVStore` or even an `AccountMapper` directly to the handler,
|
||||
we introduce a `bank.Keeper`, which can only be used to transfer coins in and out of accounts.
|
||||
This allows us to determine up front that the only effect the bank module's
|
||||
`Handler` can have on the store is to change the amount of coins in an account -
|
||||
it can't increment sequence numbers, change PubKeys, or otherwise.
|
||||
|
||||
|
||||
A `bank.Keeper` is easily instantiated from an `AccountMapper`:
|
||||
|
||||
```go
|
||||
coinKeeper = bank.NewKeeper(accountMapper)
|
||||
```
|
||||
|
||||
We can then use it within a handler, instead of working directly with the
|
||||
`AccountMapper`. For instance, to add coins to an account:
|
||||
|
||||
```go
|
||||
// Finds account with addr in AccountMapper.
|
||||
// Adds coins to account's coin array.
|
||||
// Sets updated account in AccountMapper
|
||||
app.coinKeeper.AddCoins(ctx, addr, coins)
|
||||
```
|
||||
|
||||
See the [bank.Keeper API
|
||||
docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#Keeper) for the full set of methods.
|
||||
|
||||
Note we can refine the `bank.Keeper` by restricting it's method set. For
|
||||
instance, the
|
||||
[bank.ViewKeeper](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#ViewKeeper)
|
||||
is a read-only version, while the
|
||||
[bank.SendKeeper](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#SendKeeper)
|
||||
only executes transfers of coins from input accounts to output
|
||||
accounts.
|
||||
|
||||
We use this `Keeper` paradigm extensively in the SDK as the way to define what
|
||||
kind of functionality each module gets access to. In particular, we try to
|
||||
follow the *principle of least authority*.
|
||||
Rather than providing full blown access to the `KVStore` or the `AccountMapper`,
|
||||
we restrict access to a small number of functions that do very specific things.
|
||||
|
||||
## App3
|
||||
|
||||
With the `auth.AccountMapper` and `bank.Keeper` in hand,
|
||||
we're now ready to build `App3`.
|
||||
The `x/auth` and `x/bank` modules do all the heavy lifting:
|
||||
|
||||
```go
|
||||
func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
||||
|
||||
// Create the codec with registered Msg types
|
||||
cdc := NewCodec()
|
||||
|
||||
// Create the base application object.
|
||||
app := bapp.NewBaseApp(app3Name, cdc, logger, db)
|
||||
|
||||
// Create a key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
keyFees := sdk.NewKVStoreKey("fee") // TODO
|
||||
|
||||
// Set various mappers/keepers to interact easily with underlying stores
|
||||
accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount)
|
||||
coinKeeper := bank.NewKeeper(accountMapper)
|
||||
feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees)
|
||||
|
||||
app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper))
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler gets access to
|
||||
app.Router().
|
||||
AddRoute("send", bank.NewHandler(coinKeeper))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount, keyFees)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
||||
```
|
||||
|
||||
Note we use `bank.NewHandler`, which handles only `bank.MsgSend`,
|
||||
and receives only the `bank.Keeper`. See the
|
||||
[x/bank API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank)
|
||||
for more details.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Armed with native modules for authentication and coin transfer,
|
||||
emboldened by the paradigm of mappers and keepers,
|
||||
and ever invigorated by the desire to build secure state-machines,
|
||||
we find ourselves here with a full-blown, all-checks-in-place, multi-asset
|
||||
cryptocurrency - the beating heart of the Cosmos-SDK.
|
|
@ -0,0 +1,73 @@
|
|||
# ABCI
|
||||
|
||||
The Application BlockChain Interface, or ABCI, is a powerfully
|
||||
delineated boundary between the Cosmos-SDK and Tendermint.
|
||||
It separates the logical state transition machine of your application from
|
||||
its secure replication across many physical machines.
|
||||
|
||||
By providing a clear, language agnostic boundary between applications and consensus,
|
||||
ABCI provides tremendous developer flexibility and [support in many
|
||||
languages](https://tendermint.com/ecosystem). That said, it is still quite a low-level protocol, and
|
||||
requires frameworks to be built to abstract over that low-level componentry.
|
||||
The Cosmos-SDK is one such framework.
|
||||
|
||||
While we've already seen `DeliverTx`, the workhorse of any ABCI application,
|
||||
here we will introduce the other ABCI requests sent by Tendermint, and
|
||||
how we can use them to build more advanced applications. For a more complete
|
||||
depiction of the ABCI and how its used, see
|
||||
[the
|
||||
specification](https://github.com/tendermint/tendermint/blob/master/docs/abci-spec.md)
|
||||
|
||||
## InitChain
|
||||
|
||||
In our previous apps, we built out all the core logic, but we never specified
|
||||
how the store should be initialized. For that, we use the `app.InitChain` method,
|
||||
which is called once by Tendermint the very first time the application boots up.
|
||||
|
||||
The InitChain request contains a variety of Tendermint information, like the consensus
|
||||
parameters and an initial validator set, but it also contains an opaque blob of
|
||||
application specific bytes - typically JSON encoded.
|
||||
Apps can decide what to do with all of this information by calling the
|
||||
`app.SetInitChainer` method.
|
||||
|
||||
For instance, let's introduce a `GenesisAccount` struct that can be JSON encoded
|
||||
and part of a genesis file. Then we can populate the store with such accounts
|
||||
during InitChain:
|
||||
|
||||
```go
|
||||
TODO
|
||||
```
|
||||
|
||||
If we include a correctly formatted `GenesisAccount` in our Tendermint
|
||||
genesis.json file, the store will be initialized with those accounts and they'll
|
||||
be able to send transactions!
|
||||
|
||||
## BeginBlock
|
||||
|
||||
BeginBlock is called at the beginning of each block, before processing any
|
||||
transactions with DeliverTx.
|
||||
It contains information on what validators have signed.
|
||||
|
||||
## EndBlock
|
||||
|
||||
EndBlock is called at the end of each block, after processing all transactions
|
||||
with DeliverTx.
|
||||
It allows the application to return updates to the validator set.
|
||||
|
||||
## Commit
|
||||
|
||||
Commit is called after EndBlock. It persists the application state and returns
|
||||
the Merkle root hash to be included in the next Tendermint block. The root hash
|
||||
can be in Query for Merkle proofs of the state.
|
||||
|
||||
## Query
|
||||
|
||||
Query allows queries into the application store according to a path.
|
||||
|
||||
## CheckTx
|
||||
|
||||
CheckTx is used for the mempool. It only runs the AnteHandler. This is so
|
||||
potentially expensive message handling doesn't begin until the transaction has
|
||||
actually been committed in a block. The AnteHandler authenticates the sender and
|
||||
ensures they have enough to pay the fee for the transaction. If the transaction
|
||||
later fails, the sender still pays the fee.
|
|
@ -0,0 +1,72 @@
|
|||
# App5 - Basecoin
|
||||
|
||||
As we've seen, the SDK provides a flexible yet comprehensive framework for building state
|
||||
machines and defining their transitions, including authenticating transactions,
|
||||
executing messages, controlling access to stores, and updating the validator set.
|
||||
|
||||
Until now, we have focused on building only isolated ABCI applications to
|
||||
demonstrate and explain the various features and flexibilities of the SDK.
|
||||
Here, we'll connect our ABCI application to Tendermint so we can run a full
|
||||
blockchain node, and introduce command line and HTTP interfaces for interacting with it.
|
||||
|
||||
But first, let's talk about how source code should be laid out.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
TODO
|
||||
|
||||
## Tendermint Node
|
||||
|
||||
Since the Cosmos-SDK is written in Go, Cosmos-SDK applications can be compiled
|
||||
with Tendermint into a single binary. Of course, like any ABCI application, they
|
||||
can also run as separate processes that communicate with Tendermint via socket.
|
||||
|
||||
For more details on what's involved in starting a Tendermint full node, see the
|
||||
[NewNode](https://godoc.org/github.com/tendermint/tendermint/node#NewNode)
|
||||
function in `github.com/tendermint/tendermint/node`.
|
||||
|
||||
The `server` package in the Cosmos-SDK simplifies
|
||||
connecting an application with a Tendermint node.
|
||||
For instance, the following `main.go` file will give us a complete full node
|
||||
using the Basecoin application we built:
|
||||
|
||||
```go
|
||||
//TODO imports
|
||||
|
||||
func main() {
|
||||
cdc := app.MakeCodec()
|
||||
ctx := server.NewDefaultContext()
|
||||
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "basecoind",
|
||||
Short: "Basecoin Daemon (server)",
|
||||
PersistentPreRunE: server.PersistentPreRunEFn(ctx),
|
||||
}
|
||||
|
||||
server.AddCommands(ctx, cdc, rootCmd, server.DefaultAppInit,
|
||||
server.ConstructAppCreator(newApp, "basecoin"))
|
||||
|
||||
// prepare and add flags
|
||||
rootDir := os.ExpandEnv("$HOME/.basecoind")
|
||||
executor := cli.PrepareBaseCmd(rootCmd, "BC", rootDir)
|
||||
executor.Execute()
|
||||
}
|
||||
|
||||
func newApp(logger log.Logger, db dbm.DB) abci.Application {
|
||||
return app.NewBasecoinApp(logger, db)
|
||||
}
|
||||
```
|
||||
|
||||
Note we utilize the popular [cobra library](https://github.com/spf13/cobra)
|
||||
for the CLI, in concert with the [viper library](https://github.com/spf13/library)
|
||||
for managing configuration. See our [cli library](https://github.com/tendermint/blob/master/tmlibs/cli/setup.go)
|
||||
for more details.
|
||||
|
||||
TODO: compile and run the binary
|
||||
|
||||
Options for running the `basecoind` binary are effectively the same as for `tendermint`.
|
||||
See [Using Tendermint](TODO) for more details.
|
||||
|
||||
## Clients
|
||||
|
||||
TODO
|
|
@ -0,0 +1,235 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
bapp "github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
)
|
||||
|
||||
const (
|
||||
app1Name = "App1"
|
||||
)
|
||||
|
||||
func NewApp1(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
||||
|
||||
cdc := wire.NewCodec()
|
||||
|
||||
// Create the base application object.
|
||||
app := bapp.NewBaseApp(app1Name, cdc, logger, db)
|
||||
|
||||
// Create a key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
|
||||
// Determine how transactions are decoded.
|
||||
app.SetTxDecoder(txDecoder)
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler gets access to the account store.
|
||||
app.Router().
|
||||
AddRoute("send", handleMsgSend(keyAccount))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Msg
|
||||
|
||||
// MsgSend implements sdk.Msg
|
||||
var _ sdk.Msg = MsgSend{}
|
||||
|
||||
// MsgSend to send coins from Input to Output
|
||||
type MsgSend struct {
|
||||
From sdk.AccAddress `json:"from"`
|
||||
To sdk.AccAddress `json:"to"`
|
||||
Amount sdk.Coins `json:"amount"`
|
||||
}
|
||||
|
||||
// NewMsgSend
|
||||
func NewMsgSend(from, to sdk.AccAddress, amt sdk.Coins) MsgSend {
|
||||
return MsgSend{from, to, amt}
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgSend) Type() string { return "send" }
|
||||
|
||||
// Implements Msg. Ensure the addresses are good and the
|
||||
// amount is positive.
|
||||
func (msg MsgSend) ValidateBasic() sdk.Error {
|
||||
if len(msg.From) == 0 {
|
||||
return sdk.ErrInvalidAddress("From address is empty")
|
||||
}
|
||||
if len(msg.To) == 0 {
|
||||
return sdk.ErrInvalidAddress("To address is empty")
|
||||
}
|
||||
if !msg.Amount.IsPositive() {
|
||||
return sdk.ErrInvalidCoins("Amount is not positive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements Msg. JSON encode the message.
|
||||
func (msg MsgSend) GetSignBytes() []byte {
|
||||
bz, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sdk.MustSortJSON(bz)
|
||||
}
|
||||
|
||||
// Implements Msg. Return the signer.
|
||||
func (msg MsgSend) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{msg.From}
|
||||
}
|
||||
|
||||
// Returns the sdk.Tags for the message
|
||||
func (msg MsgSend) Tags() sdk.Tags {
|
||||
return sdk.NewTags("sender", []byte(msg.From.String())).
|
||||
AppendTag("receiver", []byte(msg.To.String()))
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Handler for the message
|
||||
|
||||
// Handle MsgSend.
|
||||
// NOTE: msg.From, msg.To, and msg.Amount were already validated
|
||||
// in ValidateBasic().
|
||||
func handleMsgSend(key *sdk.KVStoreKey) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
sendMsg, ok := msg.(MsgSend)
|
||||
if !ok {
|
||||
// Create custom error message and return result
|
||||
// Note: Using unreserved error codespace
|
||||
return sdk.NewError(2, 1, "MsgSend is malformed").Result()
|
||||
}
|
||||
|
||||
// Load the store.
|
||||
store := ctx.KVStore(key)
|
||||
|
||||
// Debit from the sender.
|
||||
if res := handleFrom(store, sendMsg.From, sendMsg.Amount); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
// Credit the receiver.
|
||||
if res := handleTo(store, sendMsg.To, sendMsg.Amount); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
// Return a success (Code 0).
|
||||
// Add list of key-value pair descriptors ("tags").
|
||||
return sdk.Result{
|
||||
Tags: sendMsg.Tags(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience Handlers
|
||||
func handleFrom(store sdk.KVStore, from sdk.AccAddress, amt sdk.Coins) sdk.Result {
|
||||
// Get sender account from the store.
|
||||
accBytes := store.Get(from)
|
||||
if accBytes == nil {
|
||||
// Account was not added to store. Return the result of the error.
|
||||
return sdk.NewError(2, 101, "Account not added to store").Result()
|
||||
}
|
||||
|
||||
// Unmarshal the JSON account bytes.
|
||||
var acc appAccount
|
||||
err := json.Unmarshal(accBytes, &acc)
|
||||
if err != nil {
|
||||
// InternalError
|
||||
return sdk.ErrInternal("Error when deserializing account").Result()
|
||||
}
|
||||
|
||||
// Deduct msg amount from sender account.
|
||||
senderCoins := acc.Coins.Minus(amt)
|
||||
|
||||
// If any coin has negative amount, return insufficient coins error.
|
||||
if !senderCoins.IsNotNegative() {
|
||||
return sdk.ErrInsufficientCoins("Insufficient coins in account").Result()
|
||||
}
|
||||
|
||||
// Set acc coins to new amount.
|
||||
acc.Coins = senderCoins
|
||||
|
||||
// Encode sender account.
|
||||
accBytes, err = json.Marshal(acc)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Account encoding error").Result()
|
||||
}
|
||||
|
||||
// Update store with updated sender account
|
||||
store.Set(from, accBytes)
|
||||
return sdk.Result{}
|
||||
}
|
||||
|
||||
func handleTo(store sdk.KVStore, to sdk.AccAddress, amt sdk.Coins) sdk.Result {
|
||||
// Add msg amount to receiver account
|
||||
accBytes := store.Get(to)
|
||||
var acc appAccount
|
||||
if accBytes == nil {
|
||||
// Receiver account does not already exist, create a new one.
|
||||
acc = appAccount{}
|
||||
} else {
|
||||
// Receiver account already exists. Retrieve and decode it.
|
||||
err := json.Unmarshal(accBytes, &acc)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Account decoding error").Result()
|
||||
}
|
||||
}
|
||||
|
||||
// Add amount to receiver's old coins
|
||||
receiverCoins := acc.Coins.Plus(amt)
|
||||
|
||||
// Update receiver account
|
||||
acc.Coins = receiverCoins
|
||||
|
||||
// Encode receiver account
|
||||
accBytes, err := json.Marshal(acc)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Account encoding error").Result()
|
||||
}
|
||||
|
||||
// Update store with updated receiver account
|
||||
store.Set(to, accBytes)
|
||||
return sdk.Result{}
|
||||
}
|
||||
|
||||
// Simple account struct
|
||||
type appAccount struct {
|
||||
Coins sdk.Coins `json:"coins"`
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Tx
|
||||
|
||||
// Simple tx to wrap the Msg.
|
||||
type app1Tx struct {
|
||||
MsgSend
|
||||
}
|
||||
|
||||
// This tx only has one Msg.
|
||||
func (tx app1Tx) GetMsgs() []sdk.Msg {
|
||||
return []sdk.Msg{tx.MsgSend}
|
||||
}
|
||||
|
||||
// JSON decode MsgSend.
|
||||
func txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) {
|
||||
var tx app1Tx
|
||||
err := json.Unmarshal(txBytes, &tx)
|
||||
if err != nil {
|
||||
return nil, sdk.ErrTxDecode(err.Error())
|
||||
}
|
||||
return tx, nil
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
bapp "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"
|
||||
)
|
||||
|
||||
const (
|
||||
app2Name = "App2"
|
||||
)
|
||||
|
||||
var (
|
||||
issuer = crypto.GenPrivKeyEd25519().PubKey().Address()
|
||||
)
|
||||
|
||||
func NewCodec() *wire.Codec {
|
||||
cdc := wire.NewCodec()
|
||||
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
|
||||
cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil)
|
||||
cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil)
|
||||
return cdc
|
||||
}
|
||||
|
||||
func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
||||
|
||||
cdc := NewCodec()
|
||||
|
||||
// Create the base application object.
|
||||
app := bapp.NewBaseApp(app2Name, cdc, logger, db)
|
||||
|
||||
// Create a key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
// Create a key for accessing the issue store.
|
||||
keyIssue := sdk.NewKVStoreKey("issue")
|
||||
|
||||
// set antehandler function
|
||||
app.SetAnteHandler(antehandler)
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler gets access to the account store.
|
||||
app.Router().
|
||||
AddRoute("send", handleMsgSend(keyAccount)).
|
||||
AddRoute("issue", handleMsgIssue(keyAccount, keyIssue))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount, keyIssue)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Msgs
|
||||
|
||||
// MsgIssue to allow a registered issuer
|
||||
// to issue new coins.
|
||||
type MsgIssue struct {
|
||||
Issuer sdk.AccAddress
|
||||
Receiver sdk.AccAddress
|
||||
Coin sdk.Coin
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgIssue) Type() string { return "issue" }
|
||||
|
||||
// Implements Msg. Ensures addresses are valid and Coin is positive
|
||||
func (msg MsgIssue) ValidateBasic() sdk.Error {
|
||||
if len(msg.Issuer) == 0 {
|
||||
return sdk.ErrInvalidAddress("Issuer address cannot be empty")
|
||||
}
|
||||
|
||||
if len(msg.Receiver) == 0 {
|
||||
return sdk.ErrInvalidAddress("Receiver address cannot be empty")
|
||||
}
|
||||
|
||||
// Cannot issue zero or negative coins
|
||||
if !msg.Coin.IsPositive() {
|
||||
return sdk.ErrInvalidCoins("Cannot issue 0 or negative coin amounts")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements Msg. Get canonical sign bytes for MsgIssue
|
||||
func (msg MsgIssue) GetSignBytes() []byte {
|
||||
bz, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sdk.MustSortJSON(bz)
|
||||
}
|
||||
|
||||
// Implements Msg. Return the signer.
|
||||
func (msg MsgIssue) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{msg.Issuer}
|
||||
}
|
||||
|
||||
// Returns the sdk.Tags for the message
|
||||
func (msg MsgIssue) Tags() sdk.Tags {
|
||||
return sdk.NewTags("issuer", []byte(msg.Issuer.String())).
|
||||
AppendTag("receiver", []byte(msg.Receiver.String()))
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Handler for the message
|
||||
|
||||
// Handle MsgIssue.
|
||||
func handleMsgIssue(keyIssue *sdk.KVStoreKey, keyAcc *sdk.KVStoreKey) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
issueMsg, ok := msg.(MsgIssue)
|
||||
if !ok {
|
||||
return sdk.NewError(2, 1, "MsgIssue is malformed").Result()
|
||||
}
|
||||
|
||||
// Retrieve stores
|
||||
issueStore := ctx.KVStore(keyIssue)
|
||||
accStore := ctx.KVStore(keyAcc)
|
||||
|
||||
// Handle updating coin info
|
||||
if res := handleIssuer(issueStore, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
// Issue coins to receiver using previously defined handleTo function
|
||||
if res := handleTo(accStore, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
return sdk.Result{
|
||||
// Return result with Issue msg tags
|
||||
Tags: issueMsg.Tags(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleIssuer(store sdk.KVStore, issuer sdk.AccAddress, coin sdk.Coin) sdk.Result {
|
||||
// the issuer address is stored directly under the coin denomination
|
||||
denom := []byte(coin.Denom)
|
||||
infoBytes := store.Get(denom)
|
||||
if infoBytes == nil {
|
||||
return sdk.ErrInvalidCoins(fmt.Sprintf("Unknown coin type %s", coin.Denom)).Result()
|
||||
}
|
||||
|
||||
var coinInfo coinInfo
|
||||
err := json.Unmarshal(infoBytes, &coinInfo)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Error when deserializing coinInfo").Result()
|
||||
}
|
||||
|
||||
// Msg Issuer is not authorized to issue these coins
|
||||
if !bytes.Equal(coinInfo.Issuer, issuer) {
|
||||
return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result()
|
||||
}
|
||||
|
||||
return sdk.Result{}
|
||||
}
|
||||
|
||||
// coinInfo stores meta data about a coin
|
||||
type coinInfo struct {
|
||||
Issuer sdk.AccAddress `json:"issuer"`
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Tx
|
||||
|
||||
// Simple tx to wrap the Msg.
|
||||
type app2Tx struct {
|
||||
sdk.Msg
|
||||
Signatures []auth.StdSignature
|
||||
}
|
||||
|
||||
// This tx only has one Msg.
|
||||
func (tx app2Tx) GetMsgs() []sdk.Msg {
|
||||
return []sdk.Msg{tx.Msg}
|
||||
}
|
||||
|
||||
func (tx app2Tx) GetSignatures() []auth.StdSignature {
|
||||
return tx.Signatures
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
|
||||
// Simple anteHandler that ensures msg signers have signed.
|
||||
// Provides no replay protection.
|
||||
func antehandler(ctx sdk.Context, tx sdk.Tx) (_ sdk.Context, _ sdk.Result, abort bool) {
|
||||
appTx, ok := tx.(app2Tx)
|
||||
if !ok {
|
||||
// set abort boolean to true so that we don't continue to process failed tx
|
||||
return ctx, sdk.ErrTxDecode("Tx must be of format app2Tx").Result(), true
|
||||
}
|
||||
|
||||
// expect only one msg in app2Tx
|
||||
msg := tx.GetMsgs()[0]
|
||||
|
||||
signerAddrs := msg.GetSigners()
|
||||
|
||||
if len(signerAddrs) != len(appTx.GetSignatures()) {
|
||||
return ctx, sdk.ErrUnauthorized("Number of signatures do not match required amount").Result(), true
|
||||
}
|
||||
|
||||
signBytes := msg.GetSignBytes()
|
||||
for i, addr := range signerAddrs {
|
||||
sig := appTx.GetSignatures()[i]
|
||||
|
||||
// check that submitted pubkey belongs to required address
|
||||
if !bytes.Equal(sig.PubKey.Address(), addr) {
|
||||
return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true
|
||||
}
|
||||
|
||||
// check that signature is over expected signBytes
|
||||
if !sig.PubKey.VerifyBytes(signBytes, sig.Signature) {
|
||||
return ctx, sdk.ErrUnauthorized("Signature verification failed").Result(), true
|
||||
}
|
||||
}
|
||||
|
||||
// authentication passed, app to continue processing by sending msg to handler
|
||||
return ctx, sdk.Result{}, false
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
bapp "github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
const (
|
||||
app3Name = "App3"
|
||||
)
|
||||
|
||||
func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
||||
|
||||
// Create the codec with registered Msg types
|
||||
cdc := NewCodec()
|
||||
|
||||
// Create the base application object.
|
||||
app := bapp.NewBaseApp(app3Name, cdc, logger, db)
|
||||
|
||||
// Create a key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
keyFees := sdk.NewKVStoreKey("fee") // TODO
|
||||
|
||||
// Set various mappers/keepers to interact easily with underlying stores
|
||||
accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount)
|
||||
coinKeeper := bank.NewKeeper(accountMapper)
|
||||
feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees)
|
||||
|
||||
app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper))
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler gets access to
|
||||
app.Router().
|
||||
AddRoute("send", bank.NewHandler(coinKeeper))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount, keyFees)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
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"
|
||||
|
||||
bapp "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"
|
||||
)
|
||||
|
||||
const (
|
||||
app4Name = "App4"
|
||||
)
|
||||
|
||||
func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
||||
|
||||
cdc := NewCodec()
|
||||
|
||||
// Create the base application object.
|
||||
app := bapp.NewBaseApp(app3Name, cdc, logger, db)
|
||||
|
||||
// Create a key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
|
||||
// Set various mappers/keepers to interact easily with underlying stores
|
||||
accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount)
|
||||
coinKeeper := bank.NewKeeper(accountMapper)
|
||||
|
||||
// TODO
|
||||
keyFees := sdk.NewKVStoreKey("fee")
|
||||
feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees)
|
||||
|
||||
app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper))
|
||||
|
||||
// Set InitChainer
|
||||
app.SetInitChainer(NewInitChainer(cdc, accountMapper))
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler gets access to the account store.
|
||||
app.Router().
|
||||
AddRoute("send", bank.NewHandler(coinKeeper))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount, keyFees)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
// Application state at Genesis has accounts with starting balances
|
||||
type GenesisState struct {
|
||||
Accounts []*GenesisAccount `json:"accounts"`
|
||||
}
|
||||
|
||||
// GenesisAccount doesn't need pubkey or sequence
|
||||
type GenesisAccount struct {
|
||||
Address sdk.AccAddress `json:"address"`
|
||||
Coins sdk.Coins `json:"coins"`
|
||||
}
|
||||
|
||||
// Converts GenesisAccount to auth.BaseAccount for storage in account store
|
||||
func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount, err error) {
|
||||
baseAcc := auth.BaseAccount{
|
||||
Address: ga.Address,
|
||||
Coins: ga.Coins.Sort(),
|
||||
}
|
||||
return &baseAcc, nil
|
||||
}
|
||||
|
||||
// InitChainer will set initial balances for accounts as well as initial coin metadata
|
||||
// MsgIssue can no longer be used to create new coin
|
||||
func NewInitChainer(cdc *wire.Codec, accountMapper auth.AccountMapper) sdk.InitChainer {
|
||||
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
stateJSON := req.AppStateBytes
|
||||
|
||||
genesisState := new(GenesisState)
|
||||
err := cdc.UnmarshalJSON(stateJSON, genesisState)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, gacc := range genesisState.Accounts {
|
||||
acc, err := gacc.ToAccount()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
acc.AccountNumber = accountMapper.GetNextAccountNumber(ctx)
|
||||
accountMapper.SetAccount(ctx, acc)
|
||||
}
|
||||
|
||||
return abci.ResponseInitChain{}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
# Introduction
|
||||
|
||||
Welcome to the Cosmos-SDK Core Documentation.
|
||||
|
||||
Here you will learn how to use the Cosmos-SDK to build Basecoin, a
|
||||
complete proof-of-stake cryptocurrency system
|
||||
|
||||
We proceed through a series of increasingly advanced and complete implementations of
|
||||
the Basecoin application, with each implementation showcasing a new component of
|
||||
the SDK:
|
||||
|
||||
- App1 - The Basics - Messages, Stores, Handlers, BaseApp
|
||||
- App2 - Transactions - Amino and AnteHandler
|
||||
- App3 - Modules - `x/auth` and `x/bank`
|
||||
- App4 - Validator Set Changes - Change the Tendermint validator set
|
||||
- App5 - Basecoin - Bringing it all together
|
|
@ -0,0 +1,72 @@
|
|||
# MultiStore
|
||||
|
||||
TODO: reconcile this with everything ... would be nice to have this explanation
|
||||
somewhere but where does it belong ? So far we've already showed how to use it
|
||||
all by creating KVStore keys and calling app.MountStoresIAVL !
|
||||
|
||||
|
||||
The Cosmos-SDK provides a special Merkle database called a `MultiStore` to be used for all application
|
||||
storage. The MultiStore consists of multiple Stores that must be mounted to the
|
||||
MultiStore during application setup. Stores are mounted to the MultiStore using a capabilities key,
|
||||
ensuring that only parts of the program with access to the key can access the store.
|
||||
|
||||
The goals of the MultiStore are as follows:
|
||||
|
||||
- Enforce separation of concerns at the storage level
|
||||
- Restrict access to storage using capabilities
|
||||
- Support multiple Store implementations in a single MultiStore, for instance the Tendermint IAVL tree and
|
||||
the Ethereum Patricia Trie
|
||||
- Merkle proofs for various queries (existence, absence, range, etc.) on current and retained historical state
|
||||
- Allow for iteration within Stores
|
||||
- Provide caching for intermediate state during execution of blocks and transactions (including for iteration)
|
||||
|
||||
- Support historical state pruning and snapshotting
|
||||
|
||||
Currently, all Stores in the MultiStore must satisfy the `KVStore` interface,
|
||||
which defines a simple key-value store. In the future,
|
||||
we may support more kinds of stores, such as a HeapStore
|
||||
or a NDStore for multidimensional storage.
|
||||
|
||||
## Mounting Stores
|
||||
|
||||
Stores are mounted during application setup. To mount some stores, first create
|
||||
their capability-keys:
|
||||
|
||||
```
|
||||
fooKey := sdk.NewKVStoreKey("foo")
|
||||
barKey := sdk.NewKVStoreKey("bar")
|
||||
catKey := sdk.NewKVStoreKey("cat")
|
||||
```
|
||||
|
||||
Stores are mounted directly on the BaseApp.
|
||||
They can either specify their own database, or share the primary one already
|
||||
passed to the BaseApp.
|
||||
|
||||
In this example, `foo` and `bar` will share the primary database, while `cat` will
|
||||
specify its own:
|
||||
|
||||
```
|
||||
catDB := dbm.NewMemDB()
|
||||
app.MountStore(fooKey, sdk.StoreTypeIAVL)
|
||||
app.MountStore(barKey, sdk.StoreTypeIAVL)
|
||||
app.MountStoreWithDB(catKey, sdk.StoreTypeIAVL, catDB)
|
||||
```
|
||||
|
||||
## Accessing Stores
|
||||
|
||||
In the Cosmos-SDK, the only way to access a store is with a capability-key.
|
||||
Only modules given explicit access to the capability-key will
|
||||
be able to access the corresponding store. Access to the MultiStore is mediated
|
||||
through the `Context`.
|
||||
|
||||
## Notes
|
||||
|
||||
TODO: move this to the spec
|
||||
|
||||
In the example above, all IAVL nodes (inner and leaf) will be stored
|
||||
in mainDB with the prefix of "s/k:foo/" and "s/k:bar/" respectively,
|
||||
thus sharing the mainDB. All IAVL nodes (inner and leaf) for the
|
||||
cat KVStore are stored separately in catDB with the prefix of
|
||||
"s/\_/". The "s/k:KEY/" and "s/\_/" prefixes are there to
|
||||
disambiguate store items from other items of non-storage concern.
|
||||
|
320
docs/guide.md
320
docs/guide.md
|
@ -1,320 +0,0 @@
|
|||
## Introduction
|
||||
|
||||
If you want to see some examples, take a look at the [examples/basecoin](/examples/basecoin) directory.
|
||||
|
||||
## Design Goals
|
||||
|
||||
The design of the Cosmos SDK is based on the principles of "capabilities systems".
|
||||
|
||||
## Capabilities systems
|
||||
|
||||
### Need for module isolation
|
||||
### Capability is implied permission
|
||||
### TODO Link to thesis
|
||||
|
||||
## Tx & Msg
|
||||
|
||||
The SDK distinguishes between transactions (Tx) and messages
|
||||
(Msg). A Tx is a Msg wrapped with authentication and fee data.
|
||||
|
||||
### Messages
|
||||
|
||||
Users can create messages containing arbitrary information by
|
||||
implementing the `Msg` interface:
|
||||
|
||||
```go
|
||||
type Msg interface {
|
||||
|
||||
// Return the message type.
|
||||
// Must be alphanumeric or empty.
|
||||
Type() string
|
||||
|
||||
// Get the canonical byte representation of the Msg.
|
||||
GetSignBytes() []byte
|
||||
|
||||
// ValidateBasic does a simple validation check that
|
||||
// doesn't require access to any other information.
|
||||
ValidateBasic() error
|
||||
|
||||
// Signers returns the addrs of signers that must sign.
|
||||
// CONTRACT: All signatures must be present to be valid.
|
||||
// CONTRACT: Returns addrs in some deterministic order.
|
||||
GetSigners() []Address
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Messages must specify their type via the `Type()` method. The type should
|
||||
correspond to the messages handler, so there can be many messages with the same
|
||||
type.
|
||||
|
||||
Messages must also specify how they are to be authenticated. The `GetSigners()`
|
||||
method return a list of addresses that must sign the message, while the
|
||||
`GetSignBytes()` method returns the bytes that must be signed for a signature
|
||||
to be valid.
|
||||
|
||||
Addresses in the SDK are arbitrary byte arrays that are hex-encoded when
|
||||
displayed as a string or rendered in JSON.
|
||||
|
||||
Messages can specify basic self-consistency checks using the `ValidateBasic()`
|
||||
method to enforce that message contents are well formed before any actual logic
|
||||
begins.
|
||||
|
||||
For instance, the `Basecoin` message types are defined in `x/bank/tx.go`:
|
||||
|
||||
```go
|
||||
type MsgSend struct {
|
||||
Inputs []Input `json:"inputs"`
|
||||
Outputs []Output `json:"outputs"`
|
||||
}
|
||||
|
||||
type MsgIssue struct {
|
||||
Banker sdk.Address `json:"banker"`
|
||||
Outputs []Output `json:"outputs"`
|
||||
}
|
||||
```
|
||||
|
||||
Each specifies the addresses that must sign the message:
|
||||
|
||||
```go
|
||||
func (msg MsgSend) GetSigners() []sdk.Address {
|
||||
addrs := make([]sdk.Address, len(msg.Inputs))
|
||||
for i, in := range msg.Inputs {
|
||||
addrs[i] = in.Address
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
func (msg MsgIssue) GetSigners() []sdk.Address {
|
||||
return []sdk.Address{msg.Banker}
|
||||
}
|
||||
```
|
||||
|
||||
### Transactions
|
||||
|
||||
A transaction is a message with additional information for authentication:
|
||||
|
||||
```go
|
||||
type Tx interface {
|
||||
|
||||
GetMsg() Msg
|
||||
|
||||
// Signatures returns the signature of signers who signed the Msg.
|
||||
// CONTRACT: Length returned is same as length of
|
||||
// pubkeys returned from MsgKeySigners, and the order
|
||||
// matches.
|
||||
// CONTRACT: If the signature is missing (ie the Msg is
|
||||
// invalid), then the corresponding signature is
|
||||
// .Empty().
|
||||
GetSignatures() []StdSignature
|
||||
}
|
||||
```
|
||||
|
||||
The `tx.GetSignatures()` method returns a list of signatures, which must match
|
||||
the list of addresses returned by `tx.Msg.GetSigners()`. The signatures come in
|
||||
a standard form:
|
||||
|
||||
```go
|
||||
type StdSignature struct {
|
||||
crypto.PubKey // optional
|
||||
crypto.Signature
|
||||
Sequence int64
|
||||
}
|
||||
```
|
||||
|
||||
It contains the signature itself, as well as the corresponding account's
|
||||
sequence number. The sequence number is expected to increment every time a
|
||||
message is signed by a given account. This prevents "replay attacks", where
|
||||
the same message could be executed over and over again.
|
||||
|
||||
The `StdSignature` can also optionally include the public key for verifying the
|
||||
signature. An application can store the public key for each address it knows
|
||||
about, making it optional to include the public key in the transaction. In the
|
||||
case of Basecoin, the public key only needs to be included in the first
|
||||
transaction send by a given account - after that, the public key is forever
|
||||
stored by the application and can be left out of transactions.
|
||||
|
||||
The address responsible for paying the transactions fee is the first address
|
||||
returned by msg.GetSigners(). The convenience function `FeePayer(tx Tx)` is provided
|
||||
to return this.
|
||||
|
||||
The standard way to create a transaction from a message is to use the `StdTx`:
|
||||
|
||||
```go
|
||||
type StdTx struct {
|
||||
Msg
|
||||
Signatures []StdSignature
|
||||
}
|
||||
```
|
||||
|
||||
### Encoding and Decoding Transactions
|
||||
|
||||
Messages and transactions are designed to be generic enough for developers to
|
||||
specify their own encoding schemes. This enables the SDK to be used as the
|
||||
framwork for constructing already specified cryptocurrency state machines, for
|
||||
instance Ethereum.
|
||||
|
||||
When initializing an application, a developer must specify a `TxDecoder`
|
||||
function which determines how an arbitrary byte array should be unmarshalled
|
||||
into a `Tx`:
|
||||
|
||||
```go
|
||||
type TxDecoder func(txBytes []byte) (Tx, error)
|
||||
```
|
||||
|
||||
In `Basecoin`, we use the Tendermint wire format and the `go-amino` library for
|
||||
encoding and decoding all message types. The `go-amino` library has the nice
|
||||
property that it can unmarshal into interface types, but it requires the
|
||||
relevant types to be registered ahead of type. Registration happens on a
|
||||
`Codec` object, so as not to taint the global name space.
|
||||
|
||||
For instance, in `Basecoin`, we wish to register the `MsgSend` and `MsgIssue`
|
||||
types:
|
||||
|
||||
```go
|
||||
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
|
||||
cdc.RegisterConcrete(bank.MsgSend{}, "cosmos-sdk/MsgSend", nil)
|
||||
cdc.RegisterConcrete(bank.MsgIssue{}, "cosmos-sdk/MsgIssue", nil)
|
||||
```
|
||||
|
||||
Note how each concrete type is given a name - these name determine the type's
|
||||
unique "prefix bytes" during encoding. A registered type will always use the
|
||||
same prefix-bytes, regardless of what interface it is satisfying. For more
|
||||
details, see the [go-amino documentation](https://github.com/tendermint/go-amino/blob/develop).
|
||||
|
||||
|
||||
## Storage
|
||||
|
||||
### MultiStore
|
||||
|
||||
MultiStore is like a root filesystem of an operating system, except
|
||||
all the entries are fully Merkleized. You mount onto a MultiStore
|
||||
any number of Stores. Currently only KVStores are supported, but in
|
||||
the future we may support more kinds of stores, such as a HeapStore
|
||||
or a NDStore for multidimensional storage.
|
||||
|
||||
The MultiStore as well as all mounted stores provide caching (aka
|
||||
cache-wrapping) for intermediate state (aka software transactional
|
||||
memory) during the execution of transactions. In the case of the
|
||||
KVStore, this also works for iterators. For example, after running
|
||||
the app's AnteHandler, the MultiStore is cache-wrapped (and each
|
||||
store is also cache-wrapped) so that should processing of the
|
||||
transaction fail, at least the transaction fees are paid and
|
||||
sequence incremented.
|
||||
|
||||
The MultiStore as well as all stores support (or will support)
|
||||
historical state pruning and snapshotting and various kinds of
|
||||
queries with proofs.
|
||||
|
||||
### KVStore
|
||||
|
||||
Here we'll focus on the IAVLStore, which is a kind of KVStore.
|
||||
|
||||
IAVLStore is a fast balanced dynamic Merkle store that also supports
|
||||
iteration, and of course cache-wrapping, state pruning, and various
|
||||
queries with proofs, such as proofs of existence, absence, range,
|
||||
and so on.
|
||||
|
||||
Here's how you mount them to a MultiStore.
|
||||
|
||||
```go
|
||||
mainDB, catDB := dbm.NewMemDB(), dbm.NewMemDB()
|
||||
fooKey := sdk.NewKVStoreKey("foo")
|
||||
barKey := sdk.NewKVStoreKey("bar")
|
||||
catKey := sdk.NewKVStoreKey("cat")
|
||||
ms := NewCommitMultiStore(mainDB)
|
||||
ms.MountStoreWithDB(fooKey, sdk.StoreTypeIAVL, nil)
|
||||
ms.MountStoreWithDB(barKey, sdk.StoreTypeIAVL, nil)
|
||||
ms.MountStoreWithDB(catKey, sdk.StoreTypeIAVL, catDB)
|
||||
```
|
||||
|
||||
In the example above, all IAVL nodes (inner and leaf) will be stored
|
||||
in mainDB with the prefix of "s/k:foo/" and "s/k:bar/" respectively,
|
||||
thus sharing the mainDB. All IAVL nodes (inner and leaf) for the
|
||||
cat KVStore are stored separately in catDB with the prefix of
|
||||
"s/\_/". The "s/k:KEY/" and "s/\_/" prefixes are there to
|
||||
disambiguate store items from other items of non-storage concern.
|
||||
|
||||
|
||||
## Context
|
||||
|
||||
The SDK uses a `Context` to propogate common information across functions. The
|
||||
`Context` is modeled after the Golang `context.Context` object, which has
|
||||
become ubiquitous in networking middleware and routing applications as a means
|
||||
to easily propogate request context through handler functions.
|
||||
|
||||
The main information stored in the `Context` includes the application
|
||||
MultiStore (see below), the last block header, and the transaction bytes.
|
||||
Effectively, the context contains all data that may be necessary for processing
|
||||
a transaction.
|
||||
|
||||
Many methods on SDK objects receive a context as the first argument.
|
||||
|
||||
## Handler
|
||||
|
||||
Transaction processing in the SDK is defined through `Handler` functions:
|
||||
|
||||
```go
|
||||
type Handler func(ctx Context, tx Tx) Result
|
||||
```
|
||||
|
||||
A handler takes a context and a transaction and returns a result. All
|
||||
information necessary for processing a transaction should be available in the
|
||||
context.
|
||||
|
||||
While the context holds the entire application state (all referenced from the
|
||||
root MultiStore), a particular handler only needs a particular kind of access
|
||||
to a particular store (or two or more). Access to stores is managed using
|
||||
capabilities keys and mappers. When a handler is initialized, it is passed a
|
||||
key or mapper that gives it access to the relevant stores.
|
||||
|
||||
```go
|
||||
// File: cosmos-sdk/examples/basecoin/app/init_stores.go
|
||||
app.BaseApp.MountStore(app.capKeyMainStore, sdk.StoreTypeIAVL)
|
||||
app.accountMapper = auth.NewAccountMapper(
|
||||
app.capKeyMainStore, // target store
|
||||
&types.AppAccount{}, // prototype
|
||||
)
|
||||
|
||||
// File: cosmos-sdk/examples/basecoin/app/init_handlers.go
|
||||
app.router.AddRoute("bank", bank.NewHandler(app.accountMapper))
|
||||
|
||||
// File: cosmos-sdk/x/bank/handler.go
|
||||
// NOTE: Technically, NewHandler only needs a CoinMapper
|
||||
func NewHandler(am sdk.AccountMapper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
cm := CoinMapper{am}
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## AnteHandler
|
||||
|
||||
### Handling Fee payment
|
||||
### Handling Authentication
|
||||
|
||||
## Accounts and x/auth
|
||||
|
||||
### sdk.Account
|
||||
### auth.BaseAccount
|
||||
### auth.AccountMapper
|
||||
|
||||
## Wire codec
|
||||
|
||||
### Why another codec?
|
||||
### vs encoding/json
|
||||
### vs protobuf
|
||||
|
||||
## KVStore example
|
||||
|
||||
## Basecoin example
|
||||
|
||||
The quintessential SDK application is Basecoin - a simple
|
||||
multi-asset cryptocurrency. Basecoin consists of a set of
|
||||
accounts stored in a Merkle tree, where each account may have
|
||||
many coins. There are two message types: MsgSend and MsgIssue.
|
||||
MsgSend allows coins to be sent around, while MsgIssue allows a
|
||||
set of predefined users to issue new coins.
|
||||
|
||||
## Conclusion
|
|
@ -9,4 +9,3 @@ Welcome to the Cosmos SDK!
|
|||
This location for our documentation has been deprecated, please see:
|
||||
|
||||
- https://cosmos.network/docs/
|
||||
-
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
# Install
|
||||
|
||||
The fastest and easiest way to install the Cosmos SDK binaries
|
||||
is to run [this script](https://github.com/cosmos/cosmos-sdk/blob/develop/scripts/install_sdk_ubuntu.sh) on a fresh Ubuntu instance. Similarly, you can run [this script](https://github.com/cosmos/cosmos-sdk/blob/develop/scripts/install_sdk_bsd.sh) on a fresh FreeBSD instance. Read the scripts before running them to ensure no untrusted connection is being made, for example we're making curl requests to download golang. Also read the comments / instructions carefully (i.e., reset your terminal after running the script).
|
||||
|
||||
Cosmos SDK can be installed to
|
||||
`$GOPATH/src/github.com/cosmos/cosmos-sdk` like a normal Go program:
|
||||
|
||||
```
|
||||
go get github.com/cosmos/cosmos-sdk
|
||||
```
|
||||
|
||||
If the dependencies have been updated with breaking changes, or if
|
||||
another branch is required, `dep` is used for dependency management.
|
||||
Thus, assuming you've already run `go get` or otherwise cloned the repo,
|
||||
the correct way to install is:
|
||||
|
||||
```
|
||||
cd $GOPATH/src/github.com/cosmos/cosmos-sdk
|
||||
make get_tools
|
||||
make get_vendor_deps
|
||||
make install
|
||||
make install_examples
|
||||
```
|
||||
|
||||
This will install `gaiad` and `gaiacli` and four example binaries:
|
||||
`basecoind`, `basecli`, `democoind`, and `democli`.
|
||||
|
||||
Verify that everything is OK by running:
|
||||
|
||||
```
|
||||
gaiad version
|
||||
```
|
||||
|
||||
you should see:
|
||||
|
||||
```
|
||||
0.17.3-a5a78eb
|
||||
```
|
||||
|
||||
then with:
|
||||
|
||||
```
|
||||
gaiacli version
|
||||
```
|
||||
you should see the same version (or a later one for both).
|
||||
|
||||
## Update
|
||||
|
||||
Get latest code (you can also `git fetch` only the version desired),
|
||||
ensure the dependencies are up to date, then recompile.
|
||||
|
||||
```
|
||||
cd $GOPATH/src/github.com/cosmos/cosmos-sdk
|
||||
git fetch -a origin
|
||||
git checkout VERSION
|
||||
make get_vendor_deps
|
||||
make install
|
||||
```
|
|
@ -1,36 +0,0 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=python -msphinx
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
set SPHINXPROJ=Cosmos-SDK
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The Sphinx module was not found. Make sure you have Sphinx installed,
|
||||
echo.then set the SPHINXBUILD environment variable to point to the full
|
||||
echo.path of the 'sphinx-build' executable. Alternatively you may add the
|
||||
echo.Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
popd
|
|
@ -0,0 +1,51 @@
|
|||
# Bank
|
||||
|
||||
The `x/bank` module is for transferring coins between accounts.
|
||||
|
||||
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank).
|
||||
|
||||
# Stake
|
||||
|
||||
The `x/stake` module is for Cosmos Delegated-Proof-of-Stake.
|
||||
|
||||
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/stake).
|
||||
|
||||
See the
|
||||
[specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/staking)
|
||||
|
||||
# Slashing
|
||||
|
||||
The `x/slashing` module is for Cosmos Delegated-Proof-of-Stake.
|
||||
|
||||
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/slashing)
|
||||
|
||||
See the
|
||||
[specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/slashing)
|
||||
|
||||
# Provisions
|
||||
|
||||
The `x/provisions` module is for distributing fees and inflation across bonded
|
||||
stakeholders.
|
||||
|
||||
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/provisions)
|
||||
|
||||
See the
|
||||
[specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/provisions)
|
||||
|
||||
# Governance
|
||||
|
||||
The `x/gov` module is for bonded stakeholders to make proposals and vote on them.
|
||||
|
||||
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/gov)
|
||||
|
||||
See the
|
||||
[specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/governance)
|
||||
|
||||
# IBC
|
||||
|
||||
The `x/ibc` module is for InterBlockchain Communication.
|
||||
|
||||
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/ibc)
|
||||
|
||||
See the
|
||||
[specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/ibc)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue