Merge pull request #697 from cosmos/release/v0.12.0

Release/v0.12.0
This commit is contained in:
Ethan Buchman 2018-03-27 11:45:43 -04:00 committed by GitHub
commit 6f9db78be1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
133 changed files with 8917 additions and 2139 deletions

103
.circleci/config.yml Normal file
View File

@ -0,0 +1,103 @@
version: 2
defaults: &defaults
working_directory: /go/src/github.com/cosmos/cosmos-sdk
docker:
- image: circleci/golang:1.10.0
environment:
GOBIN: /tmp/workspace/bin
jobs:
setup_dependencies:
<<: *defaults
steps:
- run: mkdir -p /tmp/workspace/bin
- run: mkdir -p /tmp/workspace/profiles
- checkout
- restore_cache:
keys:
- v1-pkg-cache
- run:
name: tools
command: |
export PATH="$GOBIN:$PATH"
make get_tools
- run:
name: dependencies
command: |
export PATH="$GOBIN:$PATH"
make get_vendor_deps
- run:
name: binaries
command: |
export PATH="$GOBIN:$PATH"
make build
- persist_to_workspace:
root: /tmp/workspace
paths:
- bin
- profiles
- save_cache:
key: v1-pkg-cache
paths:
- /go/pkg
- save_cache:
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
paths:
- /go/src/github.com/cosmos/cosmos-sdk
test_cover:
<<: *defaults
parallelism: 4
steps:
- attach_workspace:
at: /tmp/workspace
- restore_cache:
key: v1-pkg-cache
- restore_cache:
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
- run:
name: Run tests
command: |
for pkg in $(go list github.com/cosmos/cosmos-sdk/... | grep -v /vendor/ | circleci tests split --split-by=timings); do
id=$(basename "$pkg")
go test -timeout 5m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg"
done
- persist_to_workspace:
root: /tmp/workspace
paths:
- "profiles/*"
upload_coverage:
<<: *defaults
steps:
- attach_workspace:
at: /tmp/workspace
- restore_cache:
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
- run:
name: gather
command: |
set -ex
echo "mode: atomic" > coverage.txt
for prof in $(ls /tmp/workspace/profiles/); do
tail -n +2 /tmp/workspace/profiles/"$prof" >> coverage.txt
done
- run:
name: upload
command: bash <(curl -s https://codecov.io/bash) -f coverage.txt
workflows:
version: 2
test-suite:
jobs:
- setup_dependencies
- test_cover:
requires:
- setup_dependencies
- upload_coverage:
requires:
- test_cover

6
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,6 @@
<!-- Thanks for filing a PR! Before hitting the button, please check the following items.-->
* [ ] Updated all relevant documentation in docs
* [ ] Updated all code comments where relevant
* [ ] Wrote tests
* [ ] Updated CHANGELOG.md

7
.gitignore vendored
View File

@ -10,6 +10,13 @@ examples/basecoin/glide.lock
examples/basecoin/app/data
baseapp/data/*
docs/_build
.DS_Store
coverage.txt
profile.out
.vscode
coverage.txt
profile.out
client/lcd/keys.db/
### Vagrant ###
.vagrant/

View File

@ -1,5 +1,46 @@
# Changelog
## 0.12.0 (March 27 2018)
BREAKING CHANGES
* Revert to old go-wire for now
* glide -> godep
* [types] ErrBadNonce -> ErrInvalidSequence
* [types] Replace tx.GetFeePayer with FeePayer(tx) - returns the first signer
* [types] NewStdTx takes the Fee
* [types] ParseAccount -> AccountDecoder; ErrTxParse -> ErrTxDecoder
* [auth] AnteHandler deducts fees
* [bank] Move some errors to `types`
* [bank] Remove sequence and signature from Input
FEATURES
* [examples/basecoin] New cool module to demonstrate use of state and custom transactions
* [basecoind] `show_node_id` command
* [lcd] Implement the Light Client Daemon and endpoints
* [types/stdlib] Queue functionality
* [store] Subspace iterator on IAVLTree
* [types] StdSignDoc is the document that gets signed (chainid, msg, sequence, fee)
* [types] CodeInvalidPubKey
* [types] StdFee, and StdTx takes the StdFee
* [specs] Progression of MVPs for IBC
* [x/ibc] Initial shell of IBC functionality (no proofs)
* [x/staking] Simple staking module with bonding/unbonding
IMPROVEMENTS
* Lots more tests!
* [client/builder] Helpers for forming and signing transactions
* [types] sdk.Address
* [specs] Staking
BUG FIXES
* [auth] Fix setting pubkey on new account
* [auth] Require signatures to include the sequences
* [baseapp] Dont panic on nil handler
* [basecoin] Check for empty bytes in account and tx
## 0.11.0 (March 1, 2017)
BREAKING CHANGES

463
Gopkg.lock generated Normal file
View File

@ -0,0 +1,463 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/bgentry/speakeasy"
packages = ["."]
revision = "4aabc24848ce5fd31929f7d1e4ea74d3709c14cd"
version = "v0.1.0"
[[projects]]
branch = "master"
name = "github.com/btcsuite/btcd"
packages = ["btcec"]
revision = "2be2f12b358dc57d70b8f501b00be450192efbc3"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
branch = "master"
name = "github.com/ebuchman/fail-test"
packages = ["."]
revision = "95f809107225be108efcf10a3509e4ea6ceef3c4"
[[projects]]
name = "github.com/fsnotify/fsnotify"
packages = ["."]
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
version = "v1.4.7"
[[projects]]
name = "github.com/go-kit/kit"
packages = [
"log",
"log/level",
"log/term"
]
revision = "4dc7be5d2d12881735283bcab7352178e190fc71"
version = "v0.6.0"
[[projects]]
name = "github.com/go-logfmt/logfmt"
packages = ["."]
revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5"
version = "v0.3.0"
[[projects]]
name = "github.com/go-stack/stack"
packages = ["."]
revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc"
version = "v1.7.0"
[[projects]]
name = "github.com/gogo/protobuf"
packages = [
"gogoproto",
"jsonpb",
"proto",
"protoc-gen-gogo/descriptor",
"sortkeys",
"types"
]
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
version = "v1.0.0"
[[projects]]
name = "github.com/golang/protobuf"
packages = [
"proto",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/timestamp"
]
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/golang/snappy"
packages = ["."]
revision = "553a641470496b2327abcac10b36396bd98e45c9"
[[projects]]
name = "github.com/gorilla/context"
packages = ["."]
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
version = "v1.1"
[[projects]]
name = "github.com/gorilla/mux"
packages = ["."]
revision = "53c1911da2b537f792e7cafcb446b05ffe33b996"
version = "v1.6.1"
[[projects]]
name = "github.com/gorilla/websocket"
packages = ["."]
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
version = "v1.2.0"
[[projects]]
branch = "master"
name = "github.com/hashicorp/hcl"
packages = [
".",
"hcl/ast",
"hcl/parser",
"hcl/printer",
"hcl/scanner",
"hcl/strconv",
"hcl/token",
"json/parser",
"json/scanner",
"json/token"
]
revision = "f40e974e75af4e271d97ce0fc917af5898ae7bda"
[[projects]]
branch = "master"
name = "github.com/howeyc/crc16"
packages = ["."]
revision = "2b2a61e366a66d3efb279e46176e7291001e0354"
[[projects]]
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
branch = "master"
name = "github.com/jmhodges/levigo"
packages = ["."]
revision = "c42d9e0ca023e2198120196f842701bb4c55d7b9"
[[projects]]
branch = "master"
name = "github.com/kr/logfmt"
packages = ["."]
revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0"
[[projects]]
name = "github.com/magiconair/properties"
packages = ["."]
revision = "c3beff4c2358b44d0493c7dda585e7db7ff28ae6"
version = "v1.7.6"
[[projects]]
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
branch = "master"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
revision = "00c29f56e2386353d58c599509e8dc3801b0d716"
[[projects]]
name = "github.com/pelletier/go-toml"
packages = ["."]
revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8"
version = "v1.1.0"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/rcrowley/go-metrics"
packages = ["."]
revision = "8732c616f52954686704c8645fe1a9d59e9df7c1"
[[projects]]
name = "github.com/spf13/afero"
packages = [
".",
"mem"
]
revision = "bb8f1927f2a9d3ab41c9340aa034f6b803f4359c"
version = "v1.0.2"
[[projects]]
name = "github.com/spf13/cast"
packages = ["."]
revision = "8965335b8c7107321228e3e3702cab9832751bac"
version = "v1.2.0"
[[projects]]
name = "github.com/spf13/cobra"
packages = ["."]
revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b"
version = "v0.0.1"
[[projects]]
branch = "master"
name = "github.com/spf13/jwalterweatherman"
packages = ["."]
revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394"
[[projects]]
name = "github.com/spf13/pflag"
packages = ["."]
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
version = "v1.0.0"
[[projects]]
name = "github.com/spf13/viper"
packages = ["."]
revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736"
version = "v1.0.2"
[[projects]]
name = "github.com/stretchr/testify"
packages = [
"assert",
"require"
]
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
version = "v1.2.1"
[[projects]]
branch = "master"
name = "github.com/syndtr/goleveldb"
packages = [
"leveldb",
"leveldb/cache",
"leveldb/comparer",
"leveldb/errors",
"leveldb/filter",
"leveldb/iterator",
"leveldb/journal",
"leveldb/memdb",
"leveldb/opt",
"leveldb/storage",
"leveldb/table",
"leveldb/util"
]
revision = "169b1b37be738edb2813dab48c97a549bcf99bb5"
[[projects]]
name = "github.com/tendermint/abci"
packages = [
"client",
"example/code",
"example/kvstore",
"server",
"types"
]
revision = "46686763ba8ea595ede16530ed4a40fb38f49f94"
version = "v0.10.2"
[[projects]]
branch = "master"
name = "github.com/tendermint/ed25519"
packages = [
".",
"edwards25519",
"extra25519"
]
revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057"
[[projects]]
name = "github.com/tendermint/go-crypto"
packages = [
".",
"keys",
"keys/bcrypt",
"keys/words",
"keys/words/wordlist"
]
revision = "c3e19f3ea26f5c3357e0bcbb799b0761ef923755"
version = "v0.5.0"
[[projects]]
name = "github.com/tendermint/go-wire"
packages = [
".",
"data"
]
revision = "fa721242b042ecd4c6ed1a934ee740db4f74e45c"
source = "github.com/tendermint/go-amino"
version = "v0.7.3"
[[projects]]
name = "github.com/tendermint/iavl"
packages = ["."]
revision = "fd37a0fa3a7454423233bc3d5ea828f38e0af787"
version = "v0.7.0"
[[projects]]
name = "github.com/tendermint/tendermint"
packages = [
"blockchain",
"cmd/tendermint/commands",
"config",
"consensus",
"consensus/types",
"evidence",
"lite",
"lite/client",
"lite/errors",
"lite/files",
"lite/proxy",
"mempool",
"node",
"p2p",
"p2p/conn",
"p2p/pex",
"p2p/trust",
"p2p/upnp",
"proxy",
"rpc/client",
"rpc/core",
"rpc/core/types",
"rpc/grpc",
"rpc/lib",
"rpc/lib/client",
"rpc/lib/server",
"rpc/lib/types",
"state",
"state/txindex",
"state/txindex/kv",
"state/txindex/null",
"types",
"types/priv_validator",
"version",
"wire"
]
revision = "6f9956990c444d53f62f2a3905ed410cfe9afe77"
version = "v0.17.1"
[[projects]]
name = "github.com/tendermint/tmlibs"
packages = [
"autofile",
"cli",
"cli/flags",
"clist",
"common",
"db",
"flowrate",
"log",
"merkle",
"pubsub",
"pubsub/query"
]
revision = "24da7009c3d8c019b40ba4287495749e3160caca"
version = "v0.7.1"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = [
"blowfish",
"curve25519",
"nacl/box",
"nacl/secretbox",
"openpgp/armor",
"openpgp/errors",
"poly1305",
"ripemd160",
"salsa20/salsa"
]
revision = "88942b9c40a4c9d203b82b3731787b672d6e809b"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = [
"context",
"http2",
"http2/hpack",
"idna",
"internal/timeseries",
"lex/httplex",
"trace"
]
revision = "6078986fec03a1dcc236c34816c71b0e05018fda"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "13d03a9a82fba647c21a0ef8fba44a795d0f0835"
[[projects]]
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"language",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable"
]
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
branch = "master"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
revision = "ab0870e398d5dd054b868c0db1481ab029b9a9f2"
[[projects]]
name = "google.golang.org/grpc"
packages = [
".",
"balancer",
"codes",
"connectivity",
"credentials",
"grpclb/grpc_lb_v1/messages",
"grpclog",
"internal",
"keepalive",
"metadata",
"naming",
"peer",
"resolver",
"stats",
"status",
"tap",
"transport"
]
revision = "5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e"
version = "v1.7.5"
[[projects]]
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "86f5ed62f8a0ee96bd888d2efdfd6d4fb100a4eb"
version = "v2.2.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "0eb39694057c8ab8c9ecbaeb25bc43cbf1d2422976a09a67392a62dcef149a7b"
solver-name = "gps-cdcl"
solver-version = 1

83
Gopkg.toml Normal file
View File

@ -0,0 +1,83 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/bgentry/speakeasy"
version = "~0.1.0"
[[constraint]]
name = "github.com/golang/protobuf"
version = "~1.0.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"
[[constraint]]
name = "github.com/spf13/viper"
version = "~1.0.0"
[[constraint]]
name = "github.com/stretchr/testify"
version = "~1.2.1"
[[constraint]]
version = "~0.10.2"
name = "github.com/tendermint/abci"
[[constraint]]
version = "~0.5.0"
name = "github.com/tendermint/go-crypto"
[[constraint]]
version = "~0.7.3"
source = "github.com/tendermint/go-amino"
name = "github.com/tendermint/go-wire"
[[constraint]]
version = "~0.7.0"
name = "github.com/tendermint/iavl"
[[constraint]]
version = "~0.17.1"
name = "github.com/tendermint/tendermint"
[[constraint]]
version = "~0.7.1"
name = "github.com/tendermint/tmlibs"
[prune]
go-tests = true
unused-packages = true

View File

@ -29,18 +29,18 @@ dist:
### Tools & dependencies
check_tools:
cd tools && $(MAKE) check
cd tools && $(MAKE) check_tools
update_tools:
cd tools && $(MAKE) glide_update
cd tools && $(MAKE) update_tools
get_tools:
cd tools && $(MAKE)
cd tools && $(MAKE) get_tools
get_vendor_deps:
@rm -rf vendor/
@echo "--> Running glide install"
@glide install
@echo "--> Running dep ensure"
@dep ensure -v
draw_deps:
@# requires brew install graphviz or apt-get install graphviz
@ -72,7 +72,9 @@ test_unit:
test_cover:
@rm -rf examples/basecoin/vendor/
@rm -rf client/lcd/keys.db ~/.tendermint_test
@bash tests/test_cover.sh
@rm -rf client/lcd/keys.db ~/.tendermint_test
benchmark:
@go test -bench=. $(PACKAGES)

View File

@ -371,6 +371,13 @@ func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk
}
}
// Match route.
msgType := msg.Type()
handler := app.router.Route(msgType)
if handler == nil {
return sdk.ErrUnknownRequest("Unrecognized Msg type: " + msgType).Result()
}
// Get the correct cache
var msCache sdk.CacheMultiStore
if isCheckTx == true {
@ -384,9 +391,6 @@ func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk
}
// Match and run route.
msgType := msg.Type()
handler := app.router.Route(msgType)
result = handler(ctx, msg)
// If result was successful, write to app.checkState.ms or app.deliverState.ms

View File

@ -327,8 +327,7 @@ func (tx testUpdatePowerTx) Get(key interface{}) (value interface{}) { return ni
func (tx testUpdatePowerTx) GetMsg() sdk.Msg { return tx }
func (tx testUpdatePowerTx) GetSignBytes() []byte { return nil }
func (tx testUpdatePowerTx) ValidateBasic() sdk.Error { return nil }
func (tx testUpdatePowerTx) GetSigners() []crypto.Address { return nil }
func (tx testUpdatePowerTx) GetFeePayer() crypto.Address { return nil }
func (tx testUpdatePowerTx) GetSigners() []sdk.Address { return nil }
func (tx testUpdatePowerTx) GetSignatures() []sdk.StdSignature { return nil }
func TestValidatorChange(t *testing.T) {
@ -430,7 +429,7 @@ func makePubKey(secret string) crypto.PubKey {
func makePrivKey(secret string) crypto.PrivKey {
privKey := crypto.GenPrivKeyEd25519FromSecret([]byte(secret))
return privKey
return privKey.Wrap()
}
func secret(index int) string {

View File

@ -1,23 +0,0 @@
machine:
environment:
GOPATH: "$HOME/.go_workspace"
PROJECT_PARENT_PATH: "$GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME"
REPO: "$PROJECT_PARENT_PATH/$CIRCLE_PROJECT_REPONAME"
PATH: "$GOPATH/bin:$PATH"
hosts:
circlehost: 127.0.0.1
localhost: 127.0.0.1
dependencies:
override:
- go version
- mkdir -p "$PROJECT_PARENT_PATH"
- ln -sf "$HOME/$CIRCLE_PROJECT_REPONAME/" "$REPO"
- env
test:
override:
- "cd $REPO && make ci"
- ls $GOPATH/bin
- bash <(curl -s https://codecov.io/bash) -f coverage.txt

134
client/builder/builder.go Normal file
View File

@ -0,0 +1,134 @@
package builder
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/wire"
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"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Broadcast the transaction bytes to Tendermint
func BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) {
node, err := client.GetNode()
if err != nil {
return nil, err
}
res, err := node.BroadcastTxCommit(tx)
if err != nil {
return res, err
}
if res.CheckTx.Code != uint32(0) {
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",
res.DeliverTx.Code,
res.DeliverTx.Log)
}
return res, err
}
// Query from Tendermint with the provided key and storename
func Query(key cmn.HexBytes, storeName string) (res []byte, err error) {
path := fmt.Sprintf("/%s/key", storeName)
node, err := client.GetNode()
if err != nil {
return res, err
}
opts := rpcclient.ABCIQueryOptions{
Height: viper.GetInt64(client.FlagHeight),
Trusted: viper.GetBool(client.FlagTrustNode),
}
result, err := node.ABCIQueryWithOptions(path, key, opts)
if err != nil {
return res, err
}
resp := result.Response
if resp.Code != uint32(0) {
return res, errors.Errorf("Query failed: (%d) %s", resp.Code, resp.Log)
}
return resp.Value, nil
}
// Get the from address from the name flag
func GetFromAddress() (from sdk.Address, err error) {
keybase, err := keys.GetKeyBase()
if err != nil {
return nil, err
}
name := viper.GetString(client.FlagName)
if name == "" {
return nil, errors.Errorf("must provide a name using --name")
}
info, err := keybase.Get(name)
if err != nil {
return nil, errors.Errorf("No key for: %s", name)
}
return info.PubKey.Address(), nil
}
// sign and build the transaction from the msg
func SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *wire.Codec) ([]byte, error) {
// build the Sign Messsage from the Standard Message
chainID := viper.GetString(client.FlagChainID)
sequence := int64(viper.GetInt(client.FlagSequence))
signMsg := sdk.StdSignMsg{
ChainID: chainID,
Sequences: []int64{sequence},
Msg: msg,
}
keybase, err := keys.GetKeyBase()
if err != nil {
return nil, err
}
// sign and build
bz := signMsg.Bytes()
sig, pubkey, err := keybase.Sign(name, passphrase, bz)
if err != nil {
return nil, err
}
sigs := []sdk.StdSignature{{
PubKey: pubkey,
Signature: sig,
Sequence: viper.GetInt64(client.FlagSequence),
}}
// marshal bytes
tx := sdk.NewStdTx(signMsg.Msg, signMsg.Fee, sigs)
return cdc.MarshalBinary(tx)
}
// sign and build the transaction from the msg
func SignBuildBroadcast(name string, passphrase string, msg sdk.Msg, cdc *wire.Codec) (*ctypes.ResultBroadcastTxCommit, error) {
txBytes, err := SignAndBuild(name, passphrase, msg, cdc)
if err != nil {
return nil, err
}
return BroadcastTx(txBytes)
}

View File

@ -9,6 +9,8 @@ const (
FlagHeight = "height"
FlagTrustNode = "trust-node"
FlagName = "name"
FlagSequence = "sequence"
FlagFee = "fee"
)
// LineBreak can be included in a command list to provide a blank line
@ -31,6 +33,8 @@ func GetCommands(cmds ...*cobra.Command) []*cobra.Command {
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().Int64(FlagSequence, 0, "Sequence number to sign the tx")
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")
}

View File

@ -1,14 +1,10 @@
package client
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/viper"
rpcclient "github.com/tendermint/tendermint/rpc/client"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
cmn "github.com/tendermint/tmlibs/common"
)
// GetNode prepares a simple rpc.Client from the flags
@ -19,53 +15,3 @@ func GetNode() (rpcclient.Client, error) {
}
return rpcclient.NewHTTP(uri, "/websocket"), nil
}
// Broadcast the transaction bytes to Tendermint
func BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) {
node, err := GetNode()
if err != nil {
return nil, err
}
res, err := node.BroadcastTxCommit(tx)
if err != nil {
return res, err
}
if res.CheckTx.Code != uint32(0) {
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",
res.DeliverTx.Code,
res.DeliverTx.Log)
}
return res, err
}
// Query from Tendermint with the provided key and storename
func Query(key cmn.HexBytes, storeName string) (res []byte, err error) {
path := fmt.Sprintf("/%s/key", storeName)
node, err := GetNode()
if err != nil {
return res, err
}
opts := rpcclient.ABCIQueryOptions{
Height: viper.GetInt64(FlagHeight),
Trusted: viper.GetBool(FlagTrustNode),
}
result, err := node.ABCIQueryWithOptions(path, key, opts)
if err != nil {
return res, err
}
resp := result.Response
if resp.Code != uint32(0) {
return res, errors.Errorf("Query failed: (%d) %s", resp.Code, resp.Log)
}
return resp.Value, nil
}

View File

@ -6,7 +6,8 @@ import (
dbm "github.com/tendermint/tmlibs/db"
)
// GetKeyBase initializes a keybase based on the configuration
// GetKeyBase initializes a keybase based on the given db.
// The KeyBase manages all activity requiring access to a key.
func GetKeyBase(db dbm.DB) keys.Keybase {
keybase := keys.New(
db,

View File

@ -1,9 +1,13 @@
package keys
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/cosmos/cosmos-sdk/client"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -120,3 +124,89 @@ func printCreate(info keys.Info, seed string) {
panic(fmt.Sprintf("I can't speak: %s", output))
}
}
// REST
type NewKeyBody struct {
Name string `json:"name"`
Password string `json:"password"`
Seed string `json:"seed"`
}
func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
var kb keys.Keybase
var m NewKeyBody
kb, err := GetKeyBase()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
body, err := ioutil.ReadAll(r.Body)
err = json.Unmarshal(body, &m)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
if m.Name == "" {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("You have to specify a name for the locally stored account."))
return
}
if m.Password == "" {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("You have to specify a password for the locally stored account."))
return
}
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 {
w.WriteHeader(http.StatusConflict)
w.Write([]byte(fmt.Sprintf("Account with name %s already exists.", m.Name)))
return
}
}
// create account
info, err := kb.Recover(m.Name, m.Password, m.Seed)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
w.Write([]byte(info.PubKey.Address().String()))
}
// function to just a new seed to display in the UI before actually persisting it in the keybase
func getSeed(algo keys.CryptoAlgo) string {
kb := client.MockKeyBase()
pass := "throwing-this-key-away"
name := "inmemorykey"
_, seed, _ := kb.Create(name, pass, algo)
return seed
}
func SeedRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
algoType := vars["type"]
// algo type defaults to ed25519
if algoType == "" {
algoType = "ed25519"
}
algo := keys.CryptoAlgo(algoType)
seed := getSeed(algo)
w.Write([]byte(seed))
}

View File

@ -1,10 +1,14 @@
package keys
import (
"encoding/json"
"fmt"
"net/http"
"github.com/cosmos/cosmos-sdk/client"
"github.com/gorilla/mux"
"github.com/pkg/errors"
keys "github.com/tendermint/go-crypto/keys"
"github.com/spf13/cobra"
)
@ -43,3 +47,41 @@ func runDeleteCmd(cmd *cobra.Command, args []string) error {
fmt.Println("Password deleted forever (uh oh!)")
return nil
}
// REST
type DeleteKeyBody struct {
Password string `json:"password"`
}
func DeleteKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
var kb keys.Keybase
var m DeleteKeyBody
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&m)
if err != nil {
w.WriteHeader(400)
w.Write([]byte(err.Error()))
return
}
kb, err = GetKeyBase()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
// TODO handle error if key is not available or pass is wrong
err = kb.Delete(name, m.Password)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.WriteHeader(200)
}

View File

@ -1,6 +1,13 @@
package keys
import "github.com/spf13/cobra"
import (
"encoding/json"
"net/http"
"github.com/spf13/cobra"
)
// CMD
// listKeysCmd represents the list command
var listKeysCmd = &cobra.Command{
@ -23,3 +30,36 @@ func runListCmd(cmd *cobra.Command, args []string) error {
}
return err
}
//REST
func QueryKeysRequestHandler(w http.ResponseWriter, r *http.Request) {
kb, err := GetKeyBase()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
infos, err := kb.List()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
// an empty list will be JSONized as null, but we want to keep the empty list
if len(infos) == 0 {
w.Write([]byte("[]"))
return
}
keysOutput := make([]KeyOutput, len(infos))
for i, info := range infos {
keysOutput[i] = KeyOutput{Name: info.Name, Address: info.PubKey.Address().String()}
}
output, err := json.MarshalIndent(keysOutput, "", " ")
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}

View File

@ -2,6 +2,7 @@ package keys
import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
)
@ -27,3 +28,12 @@ func Commands() *cobra.Command {
)
return cmd
}
func RegisterRoutes(r *mux.Router) {
r.HandleFunc("/keys", QueryKeysRequestHandler).Methods("GET")
r.HandleFunc("/keys", AddNewKeyRequestHandler).Methods("POST")
r.HandleFunc("/keys/seed", SeedRequestHandler).Methods("GET")
r.HandleFunc("/keys/{name}", GetKeyRequestHandler).Methods("GET")
r.HandleFunc("/keys/{name}", UpdateKeyRequestHandler).Methods("PUT")
r.HandleFunc("/keys/{name}", DeleteKeyRequestHandler).Methods("DELETE")
}

View File

@ -1,7 +1,12 @@
package keys
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
"github.com/pkg/errors"
keys "github.com/tendermint/go-crypto/keys"
"github.com/spf13/cobra"
)
@ -13,20 +18,51 @@ var showKeysCmd = &cobra.Command{
RunE: runShowCmd,
}
func getKey(name string) (keys.Info, error) {
kb, err := GetKeyBase()
if err != nil {
return keys.Info{}, err
}
return kb.Get(name)
}
// CMD
func runShowCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
kb, err := GetKeyBase()
if err != nil {
return err
}
info, err := kb.Get(name)
info, err := getKey(name)
if err == nil {
printInfo(info)
}
return err
}
// REST
func GetKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
info, err := getKey(name)
// TODO check for the error if key actually does not exist, instead of assuming this as the reason
if err != nil {
w.WriteHeader(404)
w.Write([]byte(err.Error()))
return
}
keyOutput := KeyOutput{Name: info.Name, Address: info.PubKey.Address().String()}
output, err := json.MarshalIndent(keyOutput, "", " ")
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}

View File

@ -1,10 +1,14 @@
package keys
import (
"encoding/json"
"fmt"
"net/http"
"github.com/cosmos/cosmos-sdk/client"
"github.com/gorilla/mux"
"github.com/pkg/errors"
keys "github.com/tendermint/go-crypto/keys"
"github.com/spf13/cobra"
)
@ -48,3 +52,42 @@ func runUpdateCmd(cmd *cobra.Command, args []string) error {
fmt.Println("Password successfully updated!")
return nil
}
// REST
type UpdateKeyBody struct {
NewPassword string `json:"new_password"`
OldPassword string `json:"old_password"`
}
func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
var kb keys.Keybase
var m UpdateKeyBody
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&m)
if err != nil {
w.WriteHeader(400)
w.Write([]byte(err.Error()))
return
}
kb, err = GetKeyBase()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
// TODO check if account exists and if password is correct
err = kb.Update(name, m.OldPassword, m.NewPassword)
if err != nil {
w.WriteHeader(401)
w.Write([]byte(err.Error()))
return
}
w.WriteHeader(200)
}

View File

@ -20,6 +20,14 @@ var (
keybase keys.Keybase
)
// used for outputting keys.Info over REST
type KeyOutput struct {
Name string `json:"name"`
Address string `json:"address"`
// TODO add pubkey?
// Pubkey string `json:"pubkey"`
}
// GetKeyBase initializes a keybase based on the configuration
func GetKeyBase() (keys.Keybase, error) {
if keybase == nil {
@ -33,6 +41,11 @@ func GetKeyBase() (keys.Keybase, error) {
return keybase, nil
}
// used to set the keybase manually in test
func SetKeyBase(kb keys.Keybase) {
keybase = kb
}
func printInfo(info keys.Info) {
switch viper.Get(cli.OutputFlag) {
case "text":

View File

@ -1,15 +1,14 @@
package keys
import (
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
"github.com/cosmos/cosmos-sdk/wire"
)
var cdc *wire.Codec
func init() {
cdc = wire.NewCodec()
crypto.RegisterWire(cdc)
wire.RegisterCrypto(cdc)
}
func MarshalJSON(o interface{}) ([]byte, error) {

1
client/lcd/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
tmp-base*

69
client/lcd/helpers.go Normal file
View File

@ -0,0 +1,69 @@
package lcd
// NOTE: COPIED VERBATIM FROM tendermint/tendermint/rpc/test/helpers.go
import (
"fmt"
"os"
"path/filepath"
"strings"
cmn "github.com/tendermint/tmlibs/common"
cfg "github.com/tendermint/tendermint/config"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
rpcclient "github.com/tendermint/tendermint/rpc/lib/client"
)
var globalConfig *cfg.Config
func waitForRPC() {
laddr := GetConfig().RPC.ListenAddress
fmt.Println("LADDR", laddr)
client := rpcclient.NewJSONRPCClient(laddr)
result := new(ctypes.ResultStatus)
for {
_, err := client.Call("status", map[string]interface{}{}, result)
if err == nil {
return
}
}
}
// f**ing long, but unique for each test
func makePathname() string {
// get path
p, err := os.Getwd()
if err != nil {
panic(err)
}
// fmt.Println(p)
sep := string(filepath.Separator)
return strings.Replace(p, sep, "_", -1)
}
func randPort() int {
return int(cmn.RandUint16()/2 + 10000)
}
func makeAddrs() (string, string, string) {
start := randPort()
return fmt.Sprintf("tcp://0.0.0.0:%d", start),
fmt.Sprintf("tcp://0.0.0.0:%d", start+1),
fmt.Sprintf("tcp://0.0.0.0:%d", start+2)
}
// GetConfig returns a config for the test cases as a singleton
func GetConfig() *cfg.Config {
if globalConfig == nil {
pathname := makePathname()
globalConfig = cfg.ResetTestRoot(pathname)
// and we use random ports to run in parallel
tm, rpc, _ := makeAddrs()
globalConfig.P2P.ListenAddress = tm
globalConfig.RPC.ListenAddress = rpc
globalConfig.TxIndex.IndexTags = "app.creator" // see kvstore application
}
return globalConfig
}

443
client/lcd/lcd_test.go Normal file
View File

@ -0,0 +1,443 @@
package lcd
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"regexp"
"testing"
"time"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
cryptoKeys "github.com/tendermint/go-crypto/keys"
tmcfg "github.com/tendermint/tendermint/config"
nm "github.com/tendermint/tendermint/node"
p2p "github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/proxy"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
tmrpc "github.com/tendermint/tendermint/rpc/lib/server"
tmtypes "github.com/tendermint/tendermint/types"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
client "github.com/cosmos/cosmos-sdk/client"
keys "github.com/cosmos/cosmos-sdk/client/keys"
bapp "github.com/cosmos/cosmos-sdk/examples/basecoin/app"
btypes "github.com/cosmos/cosmos-sdk/examples/basecoin/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
)
var (
coinDenom = "mycoin"
coinAmount = int64(10000000)
// XXX bad globals
port string // XXX: but it's the int ...
name string = "test"
password string = "0123456789"
seed string
sendAddr string
)
func TestKeys(t *testing.T) {
// empty keys
// XXX: the test comes with a key setup
/*
res, body := request(t, port, "GET", "/keys", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
assert.Equal(t, "[]", body, "Expected an empty array")
*/
// get seed
res, body := request(t, port, "GET", "/keys/seed", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
newSeed := body
reg, err := regexp.Compile(`([a-z]+ ){12}`)
require.Nil(t, err)
match := reg.MatchString(seed)
assert.True(t, match, "Returned seed has wrong foramt", seed)
newName := "test_newname"
newPassword := "0987654321"
// add key
var jsonStr = []byte(fmt.Sprintf(`{"name":"test_fail", "password":"%s"}`, password))
res, body = request(t, port, "POST", "/keys", jsonStr)
assert.Equal(t, http.StatusBadRequest, res.StatusCode, "Account creation should require a seed")
jsonStr = []byte(fmt.Sprintf(`{"name":"%s", "password":"%s", "seed": "%s"}`, newName, newPassword, newSeed))
res, body = request(t, port, "POST", "/keys", jsonStr)
assert.Equal(t, http.StatusOK, res.StatusCode, body)
addr := body
assert.Len(t, addr, 40, "Returned address has wrong format", addr)
// existing keys
res, body = request(t, port, "GET", "/keys", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var m [2]keys.KeyOutput
err = json.Unmarshal([]byte(body), &m)
require.Nil(t, err)
assert.Equal(t, m[0].Name, name, "Did not serve keys name correctly")
assert.Equal(t, m[0].Address, sendAddr, "Did not serve keys Address correctly")
assert.Equal(t, m[1].Name, newName, "Did not serve keys name correctly")
assert.Equal(t, m[1].Address, addr, "Did not serve keys Address correctly")
// select key
keyEndpoint := fmt.Sprintf("/keys/%s", newName)
res, body = request(t, port, "GET", keyEndpoint, nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var m2 keys.KeyOutput
err = json.Unmarshal([]byte(body), &m2)
require.Nil(t, err)
assert.Equal(t, newName, m2.Name, "Did not serve keys name correctly")
assert.Equal(t, addr, m2.Address, "Did not serve keys Address correctly")
// update key
jsonStr = []byte(fmt.Sprintf(`{"old_password":"%s", "new_password":"12345678901"}`, newPassword))
res, body = request(t, port, "PUT", keyEndpoint, jsonStr)
require.Equal(t, http.StatusOK, res.StatusCode, body)
// here it should say unauthorized as we changed the password before
res, body = request(t, port, "PUT", keyEndpoint, jsonStr)
require.Equal(t, http.StatusUnauthorized, res.StatusCode, body)
// delete key
jsonStr = []byte(`{"password":"12345678901"}`)
res, body = request(t, port, "DELETE", keyEndpoint, jsonStr)
require.Equal(t, http.StatusOK, res.StatusCode, body)
}
func TestVersion(t *testing.T) {
// node info
res, body := request(t, port, "GET", "/version", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
reg, err := regexp.Compile(`\d+\.\d+\.\d+(-dev)?`)
require.Nil(t, err)
match := reg.MatchString(body)
assert.True(t, match, body)
}
func TestNodeStatus(t *testing.T) {
// node info
res, body := request(t, port, "GET", "/node_info", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var nodeInfo p2p.NodeInfo
err := json.Unmarshal([]byte(body), &nodeInfo)
require.Nil(t, err, "Couldn't parse node info")
assert.NotEqual(t, p2p.NodeInfo{}, nodeInfo, "res: %v", res)
// syncing
res, body = request(t, port, "GET", "/syncing", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
// we expect that there is no other node running so the syncing state is "false"
// we c
assert.Equal(t, "false", body)
}
func TestBlock(t *testing.T) {
time.Sleep(time.Second * 2) // TODO: LOL -> wait for blocks
var resultBlock ctypes.ResultBlock
res, body := request(t, port, "GET", "/blocks/latest", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
err := json.Unmarshal([]byte(body), &resultBlock)
require.Nil(t, err, "Couldn't parse block")
assert.NotEqual(t, ctypes.ResultBlock{}, resultBlock)
// --
res, body = request(t, port, "GET", "/blocks/1", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
err = json.Unmarshal([]byte(body), &resultBlock)
require.Nil(t, err, "Couldn't parse block")
assert.NotEqual(t, ctypes.ResultBlock{}, resultBlock)
// --
res, body = request(t, port, "GET", "/blocks/1000000000", nil)
require.Equal(t, http.StatusNotFound, res.StatusCode, body)
}
func TestValidators(t *testing.T) {
var resultVals ctypes.ResultValidators
res, body := request(t, port, "GET", "/validatorsets/latest", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
err := json.Unmarshal([]byte(body), &resultVals)
require.Nil(t, err, "Couldn't parse validatorset")
assert.NotEqual(t, ctypes.ResultValidators{}, resultVals)
// --
res, body = request(t, port, "GET", "/validatorsets/1", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
err = json.Unmarshal([]byte(body), &resultVals)
require.Nil(t, err, "Couldn't parse validatorset")
assert.NotEqual(t, ctypes.ResultValidators{}, resultVals)
// --
res, body = request(t, port, "GET", "/validatorsets/1000000000", nil)
require.Equal(t, http.StatusNotFound, res.StatusCode)
}
func TestCoinSend(t *testing.T) {
// query empty
res, body := request(t, port, "GET", "/accounts/8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6", nil)
require.Equal(t, http.StatusNoContent, res.StatusCode, body)
// create TX
receiveAddr, resultTx := doSend(t, port, seed)
time.Sleep(time.Second * 2) // T
// check if tx was commited
assert.Equal(t, uint32(0), resultTx.CheckTx.Code)
assert.Equal(t, uint32(0), resultTx.DeliverTx.Code)
// query sender
res, body = request(t, port, "GET", "/accounts/"+sendAddr, nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var m auth.BaseAccount
err := json.Unmarshal([]byte(body), &m)
require.Nil(t, err)
coins := m.Coins
mycoins := coins[0]
assert.Equal(t, coinDenom, mycoins.Denom)
assert.Equal(t, coinAmount-1, mycoins.Amount)
// query receiver
res, body = request(t, port, "GET", "/accounts/"+receiveAddr, nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
err = json.Unmarshal([]byte(body), &m)
require.Nil(t, err)
coins = m.Coins
mycoins = coins[0]
assert.Equal(t, coinDenom, mycoins.Denom)
assert.Equal(t, int64(1), mycoins.Amount)
}
func TestTxs(t *testing.T) {
// TODO: re-enable once we can get txs by tag
// query wrong
// res, body := request(t, port, "GET", "/txs", nil)
// require.Equal(t, http.StatusBadRequest, res.StatusCode, body)
// query empty
// res, body = request(t, port, "GET", fmt.Sprintf("/txs?tag=coin.sender='%s'", "8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6"), nil)
// require.Equal(t, http.StatusOK, res.StatusCode, body)
// assert.Equal(t, "[]", body)
// create TX
_, resultTx := doSend(t, port, seed)
time.Sleep(time.Second * 2) // TO
// check if tx is findable
res, body := request(t, port, "GET", fmt.Sprintf("/txs/%s", resultTx.Hash), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
// // query sender
// res, body = request(t, port, "GET", fmt.Sprintf("/txs?tag=coin.sender='%s'", addr), nil)
// require.Equal(t, http.StatusOK, res.StatusCode, body)
// assert.NotEqual(t, "[]", body)
// // query receiver
// res, body = request(t, port, "GET", fmt.Sprintf("/txs?tag=coin.receiver='%s'", receiveAddr), nil)
// require.Equal(t, http.StatusOK, res.StatusCode, body)
// assert.NotEqual(t, "[]", body)
}
//__________________________________________________________
// helpers
// strt TM and the LCD in process, listening on their respective sockets
func startTMAndLCD() (*nm.Node, net.Listener, error) {
kb, err := keys.GetKeyBase() // dbm.NewMemDB()) // :(
if err != nil {
return nil, nil, err
}
var info cryptoKeys.Info
info, seed, err = kb.Create(name, password, cryptoKeys.AlgoEd25519) // XXX global seed
if err != nil {
return nil, nil, err
}
pubKey := info.PubKey
sendAddr = pubKey.Address().String() // XXX global
config := GetConfig()
config.Consensus.TimeoutCommit = 1000
config.Consensus.SkipTimeoutCommit = false
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
logger = log.NewFilter(logger, log.AllowError())
privValidatorFile := config.PrivValidatorFile()
privVal := tmtypes.LoadOrGenPrivValidatorFS(privValidatorFile)
app := bapp.NewBasecoinApp(logger, dbm.NewMemDB())
genesisFile := config.GenesisFile()
genDoc, err := tmtypes.GenesisDocFromFile(genesisFile)
if err != nil {
return nil, nil, err
}
coins := sdk.Coins{{coinDenom, coinAmount}}
appState := btypes.GenesisState{
Accounts: []*btypes.GenesisAccount{
{
Name: "tester",
Address: pubKey.Address(),
Coins: coins,
},
},
}
stateBytes, err := json.Marshal(appState)
if err != nil {
return nil, nil, err
}
genDoc.AppStateJSON = stateBytes
cdc := wire.NewCodec()
// LCD listen address
port = fmt.Sprintf("%d", 17377) // XXX
listenAddr := fmt.Sprintf("tcp://localhost:%s", port) // XXX
// XXX: need to set this so LCD knows the tendermint node address!
viper.Set(client.FlagNode, config.RPC.ListenAddress)
viper.Set(client.FlagChainID, genDoc.ChainID)
node, err := startTM(config, logger, genDoc, privVal, app)
if err != nil {
return nil, nil, err
}
lcd, err := startLCD(cdc, logger, listenAddr)
if err != nil {
return nil, nil, err
}
time.Sleep(time.Second * 2)
return node, lcd, nil
}
// Create & start in-process tendermint node with memdb
// and in-process abci application.
// TODO: need to clean up the WAL dir or enable it to be not persistent
func startTM(cfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc, privVal tmtypes.PrivValidator, app abci.Application) (*nm.Node, error) {
genDocProvider := func() (*tmtypes.GenesisDoc, error) { return genDoc, nil }
dbProvider := func(*nm.DBContext) (dbm.DB, error) { return dbm.NewMemDB(), nil }
n, err := nm.NewNode(cfg,
privVal,
proxy.NewLocalClientCreator(app),
genDocProvider,
dbProvider,
logger.With("module", "node"))
if err != nil {
return nil, err
}
err = n.Start()
if err != nil {
return nil, err
}
// wait for rpc
waitForRPC()
logger.Info("Tendermint running!")
return n, err
}
// start the LCD. note this blocks!
func startLCD(cdc *wire.Codec, logger log.Logger, listenAddr string) (net.Listener, error) {
handler := createHandler(cdc)
return tmrpc.StartHTTPServer(listenAddr, handler, logger)
}
func request(t *testing.T, port, method, path string, payload []byte) (*http.Response, string) {
var res *http.Response
var err error
url := fmt.Sprintf("http://localhost:%v%v", port, path)
req, err := http.NewRequest(method, url, bytes.NewBuffer(payload))
require.Nil(t, err)
res, err = http.DefaultClient.Do(req)
// res, err = http.Post(url, "application/json", bytes.NewBuffer(payload))
require.Nil(t, err)
output, err := ioutil.ReadAll(res.Body)
require.Nil(t, err)
return res, string(output)
}
func doSend(t *testing.T, port, seed string) (receiveAddr string, resultTx ctypes.ResultBroadcastTxCommit) {
// create receive address
kb := client.MockKeyBase()
receiveInfo, _, err := kb.Create("receive_address", "1234567890", cryptoKeys.CryptoAlgo("ed25519"))
require.Nil(t, err)
receiveAddr = receiveInfo.PubKey.Address().String()
// get the account to get the sequence
res, body := request(t, port, "GET", "/accounts/"+sendAddr, nil)
// require.Equal(t, http.StatusOK, res.StatusCode, body)
acc := auth.BaseAccount{}
err = json.Unmarshal([]byte(body), &acc)
require.Nil(t, err)
fmt.Println("BODY", body)
fmt.Println("ACC", acc)
sequence := acc.Sequence
// send
jsonStr := []byte(fmt.Sprintf(`{ "name":"%s", "password":"%s", "sequence":%d, "amount":[{ "denom": "%s", "amount": 1 }] }`, name, password, sequence, coinDenom))
res, body = request(t, port, "POST", "/accounts/"+receiveAddr+"/send", jsonStr)
require.Equal(t, http.StatusOK, res.StatusCode, body)
err = json.Unmarshal([]byte(body), &resultTx)
require.Nil(t, err)
return receiveAddr, resultTx
}

38
client/lcd/main_test.go Normal file
View File

@ -0,0 +1,38 @@
package lcd
import (
"fmt"
"os"
"testing"
nm "github.com/tendermint/tendermint/node"
)
var node *nm.Node
// See https://golang.org/pkg/testing/#hdr-Main
// for more details
func TestMain(m *testing.M) {
// start a basecoind node and LCD server in the background to test against
// run all the tests against a single server instance
node, lcd, err := startTMAndLCD()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
code := m.Run()
// tear down
// TODO: cleanup
// TODO: it would be great if TM could run without
// persiting anything in the first place
node.Stop()
node.Wait()
// just a listener ...
lcd.Close()
os.Exit(code)
}

View File

@ -1,36 +1,82 @@
package lcd
import (
"errors"
"net/http"
"os"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/client"
tmserver "github.com/tendermint/tendermint/rpc/lib/server"
cmn "github.com/tendermint/tmlibs/common"
client "github.com/cosmos/cosmos-sdk/client"
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/rest"
bank "github.com/cosmos/cosmos-sdk/x/bank/rest"
)
const (
flagBind = "bind"
flagCORS = "cors"
flagListenAddr = "laddr"
flagCORS = "cors"
)
// XXX: remove this when not needed
func todoNotImplemented(_ *cobra.Command, _ []string) error {
return errors.New("TODO: Command not yet implemented")
}
// ServeCommand will generate a long-running rest server
// (aka Light Client Daemon) that exposes functionality similar
// to the cli, but over rest
func ServeCommand() *cobra.Command {
func ServeCommand(cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "serve",
Use: "rest-server",
Short: "Start LCD (light-client daemon), a local REST server",
RunE: todoNotImplemented,
RunE: startRESTServerFn(cdc),
}
// TODO: handle unix sockets also?
cmd.Flags().StringP(flagBind, "b", "localhost:1317", "Interface and port that server binds to")
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")
return cmd
}
func startRESTServerFn(cdc *wire.Codec) func(cmd *cobra.Command, args []string) error {
return 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)
if err != nil {
return err
}
// Wait forever and cleanup
cmn.TrapSignal(func() {
err := listener.Close()
logger.Error("Error closing listener", "err", err)
})
return nil
}
}
func createHandler(cdc *wire.Codec) http.Handler {
r := mux.NewRouter()
r.HandleFunc("/version", version.VersionRequestHandler).Methods("GET")
kb, err := keys.GetKeyBase() //XXX
if err != nil {
panic(err)
}
// TODO make more functional? aka r = keys.RegisterRoutes(r)
keys.RegisterRoutes(r)
rpc.RegisterRoutes(r)
tx.RegisterRoutes(r, cdc)
auth.RegisterRoutes(r, cdc, "main")
bank.RegisterRoutes(r, cdc, kb)
return r
}

View File

@ -1,13 +1,15 @@
package rpc
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
tmwire "github.com/tendermint/tendermint/wire"
)
const (
@ -18,7 +20,7 @@ func blockCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "block [height]",
Short: "Get verified data for a the block at given height",
RunE: getBlock,
RunE: printBlock,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false when we can
@ -27,7 +29,47 @@ func blockCommand() *cobra.Command {
return cmd
}
func getBlock(cmd *cobra.Command, args []string) error {
func getBlock(height *int64) ([]byte, error) {
// get the node
node, err := client.GetNode()
if err != nil {
return nil, err
}
// TODO: actually honor the --select flag!
// header -> BlockchainInfo
// header, tx -> Block
// results -> BlockResults
res, err := node.Block(height)
if err != nil {
return nil, err
}
// TODO move maarshalling into cmd/rest functions
// output, err := tmwire.MarshalJSON(res)
output, err := json.MarshalIndent(res, "", " ")
if err != nil {
return nil, err
}
return output, nil
}
func GetChainHeight() (int64, error) {
node, err := client.GetNode()
if err != nil {
return -1, err
}
status, err := node.Status()
if err != nil {
return -1, err
}
height := status.LatestBlockHeight
return height, nil
}
// CMD
func printBlock(cmd *cobra.Command, args []string) error {
var height *int64
// optional height
if len(args) > 0 {
@ -41,26 +83,51 @@ func getBlock(cmd *cobra.Command, args []string) error {
}
}
// get the node
node, err := client.GetNode()
if err != nil {
return err
}
// TODO: actually honor the --select flag!
// header -> BlockchainInfo
// header, tx -> Block
// results -> BlockResults
res, err := node.Block(height)
if err != nil {
return err
}
output, err := tmwire.MarshalJSON(res)
// output, err := json.MarshalIndent(res, " ", "")
output, err := getBlock(height)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
// REST
func BlockRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
height, err := strconv.ParseInt(vars["height"], 10, 64)
if err != nil {
w.WriteHeader(400)
w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/block/{height}'."))
return
}
chainHeight, err := GetChainHeight()
if height > chainHeight {
w.WriteHeader(404)
w.Write([]byte("ERROR: Requested block height is bigger then the chain length."))
return
}
output, err := getBlock(&height)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}
func LatestBlockRequestHandler(w http.ResponseWriter, r *http.Request) {
height, err := GetChainHeight()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
output, err := getBlock(&height)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}

View File

@ -1,6 +1,7 @@
package rpc
import (
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -42,3 +43,12 @@ func initClientCommand() *cobra.Command {
cmd.Flags().String(flagValHash, "", "Hash of trusted validator set (hex-encoded)")
return cmd
}
func RegisterRoutes(r *mux.Router) {
r.HandleFunc("/node_info", NodeInfoRequestHandler).Methods("GET")
r.HandleFunc("/syncing", NodeSyncingRequestHandler).Methods("GET")
r.HandleFunc("/blocks/latest", LatestBlockRequestHandler).Methods("GET")
r.HandleFunc("/blocks/{height}", BlockRequestHandler).Methods("GET")
r.HandleFunc("/validatorsets/latest", LatestValidatorsetRequestHandler).Methods("GET")
r.HandleFunc("/validatorsets/{height}", ValidatorsetRequestHandler).Methods("GET")
}

View File

@ -1,40 +1,88 @@
package rpc
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/spf13/cobra"
wire "github.com/tendermint/go-wire"
"github.com/cosmos/cosmos-sdk/client"
tmwire "github.com/tendermint/tendermint/wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
)
func statusCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "status",
Short: "Query remote node for status",
RunE: checkStatus,
RunE: printNodeStatus,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
return cmd
}
func checkStatus(cmd *cobra.Command, args []string) error {
func getNodeStatus() (*ctypes.ResultStatus, error) {
// get the node
node, err := client.GetNode()
if err != nil {
return err
return &ctypes.ResultStatus{}, err
}
res, err := node.Status()
return node.Status()
}
// CMD
func printNodeStatus(cmd *cobra.Command, args []string) error {
status, err := getNodeStatus()
if err != nil {
return err
}
output, err := tmwire.MarshalJSON(res)
output, err := wire.MarshalJSON(status)
// output, err := json.MarshalIndent(res, " ", "")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
// REST
func NodeInfoRequestHandler(w http.ResponseWriter, r *http.Request) {
status, err := getNodeStatus()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
nodeInfo := status.NodeInfo
output, err := json.MarshalIndent(nodeInfo, "", " ")
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}
func NodeSyncingRequestHandler(w http.ResponseWriter, r *http.Request) {
status, err := getNodeStatus()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
syncing := status.Syncing
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write([]byte(strconv.FormatBool(syncing)))
}

View File

@ -1,20 +1,22 @@
package rpc
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
tmwire "github.com/tendermint/tendermint/wire"
)
func validatorCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "validatorset <height>",
Short: "Get the full validator set at given height",
RunE: getValidators,
RunE: printValidators,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false when we can
@ -22,7 +24,28 @@ func validatorCommand() *cobra.Command {
return cmd
}
func getValidators(cmd *cobra.Command, args []string) error {
func getValidators(height *int64) ([]byte, error) {
// get the node
node, err := client.GetNode()
if err != nil {
return nil, err
}
res, err := node.Validators(height)
if err != nil {
return nil, err
}
output, err := json.MarshalIndent(res, "", " ")
if err != nil {
return nil, err
}
return output, nil
}
// CMD
func printValidators(cmd *cobra.Command, args []string) error {
var height *int64
// optional height
if len(args) > 0 {
@ -36,22 +59,52 @@ func getValidators(cmd *cobra.Command, args []string) error {
}
}
// get the node
node, err := client.GetNode()
output, err := getValidators(height)
if err != nil {
return err
}
res, err := node.Validators(height)
if err != nil {
return err
}
output, err := tmwire.MarshalJSON(res)
// output, err := json.MarshalIndent(res, " ", "")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
// REST
func ValidatorsetRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
height, err := strconv.ParseInt(vars["height"], 10, 64)
if err != nil {
w.WriteHeader(400)
w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/validatorsets/{height}'."))
return
}
chainHeight, err := GetChainHeight()
if height > chainHeight {
w.WriteHeader(404)
w.Write([]byte("ERROR: Requested block height is bigger then the chain length."))
return
}
output, err := getValidators(&height)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}
func LatestValidatorsetRequestHandler(w http.ResponseWriter, r *http.Request) {
height, err := GetChainHeight()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
output, err := getValidators(&height)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}

33
client/tx/broadcast.go Normal file
View File

@ -0,0 +1,33 @@
package tx
import (
"encoding/json"
"net/http"
"github.com/cosmos/cosmos-sdk/client/builder"
)
type BroadcastTxBody struct {
TxBytes string `json="tx"`
}
func BroadcastTxRequestHandler(w http.ResponseWriter, r *http.Request) {
var m BroadcastTxBody
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&m)
if err != nil {
w.WriteHeader(400)
w.Write([]byte(err.Error()))
return
}
res, err := builder.BroadcastTx([]byte(m.TxBytes))
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write([]byte(string(res.Height)))
}

View File

@ -4,16 +4,19 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
abci "github.com/tendermint/abci/types"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/abci/types"
wire "github.com/tendermint/go-wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/cosmos/cosmos-sdk/wire"
)
// Get the default command for a tx query
@ -21,7 +24,7 @@ func QueryTxCmd(cmdr commander) *cobra.Command {
cmd := &cobra.Command{
Use: "tx [hash]",
Short: "Matches this txhash over all committed blocks",
RunE: cmdr.queryTxCmd,
RunE: cmdr.queryAndPrintTx,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false when we can
@ -29,42 +32,28 @@ func QueryTxCmd(cmdr commander) *cobra.Command {
return cmd
}
// command to query for a transaction
func (c commander) queryTxCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a tx hash")
}
// find the key to look up the account
hexStr := args[0]
hash, err := hex.DecodeString(hexStr)
func (c commander) queryTx(hashHexStr string, trustNode bool) ([]byte, error) {
hash, err := hex.DecodeString(hashHexStr)
if err != nil {
return err
return nil, err
}
// get the node
node, err := client.GetNode()
if err != nil {
return err
return nil, err
}
prove := !viper.GetBool(client.FlagTrustNode)
res, err := node.Tx(hash, prove)
res, err := node.Tx(hash, !trustNode)
if err != nil {
return err
return nil, err
}
info, err := formatTxResult(c.cdc, res)
if err != nil {
return err
return nil, err
}
output, err := json.MarshalIndent(info, "", " ")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
return json.MarshalIndent(info, "", " ")
}
func formatTxResult(cdc *wire.Codec, res *ctypes.ResultTx) (txInfo, error) {
@ -97,3 +86,47 @@ func parseTx(cdc *wire.Codec, txBytes []byte) (sdk.Tx, error) {
}
return tx, nil
}
// CMD
// command to query for a transaction
func (c commander) queryAndPrintTx(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a tx hash")
}
// find the key to look up the account
hashHexStr := args[0]
trustNode := viper.GetBool(client.FlagTrustNode)
output, err := c.queryTx(hashHexStr, trustNode)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
// REST
func QueryTxRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request) {
c := commander{cdc}
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
hashHexStr := vars["hash"]
trustNode, err := strconv.ParseBool(r.FormValue("trust_node"))
// trustNode defaults to true
if err != nil {
trustNode = true
}
output, err := c.queryTx(hashHexStr, trustNode)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}
}

View File

@ -1,8 +1,10 @@
package tx
import (
"github.com/gorilla/mux"
"github.com/spf13/cobra"
wire "github.com/tendermint/go-wire"
"github.com/cosmos/cosmos-sdk/wire"
)
// type used to pass around the provided cdc
@ -18,3 +20,10 @@ func AddCommands(cmd *cobra.Command, cdc *wire.Codec) {
QueryTxCmd(cmdr),
)
}
func RegisterRoutes(r *mux.Router, cdc *wire.Codec) {
// r.HandleFunc("/txs", SearchTxRequestHandler(cdc)).Methods("GET")
r.HandleFunc("/txs/{hash}", QueryTxRequestHandler(cdc)).Methods("GET")
// r.HandleFunc("/txs/sign", SignTxRequstHandler).Methods("POST")
// r.HandleFunc("/txs/broadcast", BroadcastTxRequestHandler).Methods("POST")
}

View File

@ -3,14 +3,16 @@ package tx
import (
"errors"
"fmt"
"net/http"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
wire "github.com/tendermint/go-wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/wire"
)
const (
@ -23,7 +25,7 @@ func SearchTxCmd(cmdr commander) *cobra.Command {
cmd := &cobra.Command{
Use: "txs",
Short: "Search for all transactions that match the given tags",
RunE: cmdr.searchTxCmd,
RunE: cmdr.searchAndPrintTx,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false once proofs built in
@ -33,10 +35,9 @@ func SearchTxCmd(cmdr commander) *cobra.Command {
return cmd
}
func (c commander) searchTxCmd(cmd *cobra.Command, args []string) error {
tags := viper.GetStringSlice(flagTags)
func (c commander) searchTx(tags []string) ([]byte, error) {
if len(tags) == 0 {
return 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 ")
@ -44,27 +45,25 @@ func (c commander) searchTxCmd(cmd *cobra.Command, args []string) error {
// get the node
node, err := client.GetNode()
if err != nil {
return err
return nil, err
}
prove := !viper.GetBool(client.FlagTrustNode)
res, err := node.TxSearch(query, prove)
if err != nil {
return err
return nil, err
}
info, err := formatTxResults(c.cdc, res)
if err != nil {
return err
return nil, err
}
output, err := c.cdc.MarshalJSON(info)
if err != nil {
return err
return nil, err
}
fmt.Println(string(output))
return nil
return output, nil
}
func formatTxResults(cdc *wire.Codec, res []*ctypes.ResultTx) ([]txInfo, error) {
@ -78,3 +77,40 @@ func formatTxResults(cdc *wire.Codec, res []*ctypes.ResultTx) ([]txInfo, error)
}
return out, nil
}
// CMD
func (c commander) searchAndPrintTx(cmd *cobra.Command, args []string) error {
tags := viper.GetStringSlice(flagTags)
output, err := c.searchTx(tags)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
// REST
func SearchTxRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request) {
c := commander{cdc}
return func(w http.ResponseWriter, r *http.Request) {
tag := r.FormValue("tag")
if tag == "" {
w.WriteHeader(400)
w.Write([]byte("You need to provide a tag to search for."))
return
}
tags := []string{tag}
output, err := c.searchTx(tags)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}
}

45
client/tx/sign.go Normal file
View File

@ -0,0 +1,45 @@
package tx
import (
"encoding/json"
"net/http"
keybase "github.com/cosmos/cosmos-sdk/client/keys"
keys "github.com/tendermint/go-crypto/keys"
)
type SignTxBody struct {
Name string `json="name"`
Password string `json="password"`
TxBytes string `json="tx"`
}
func SignTxRequstHandler(w http.ResponseWriter, r *http.Request) {
var kb keys.Keybase
var m SignTxBody
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&m)
if err != nil {
w.WriteHeader(400)
w.Write([]byte(err.Error()))
return
}
kb, err = keybase.GetKeyBase()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
//TODO check if account exists
sig, _, err := kb.Sign(m.Name, m.Password, []byte(m.TxBytes))
if err != nil {
w.WriteHeader(403)
w.Write([]byte(err.Error()))
return
}
w.Write(sig.Bytes())
}

View File

@ -42,7 +42,7 @@ type Msg interface {
// 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() []crypto.Address
GetSigners() []Address
}
```
@ -75,7 +75,7 @@ type SendMsg struct {
}
type IssueMsg struct {
Banker crypto.Address `json:"banker"`
Banker sdk.Address `json:"banker"`
Outputs []Output `json:"outputs"`
}
```
@ -83,16 +83,16 @@ type IssueMsg struct {
Each specifies the addresses that must sign the message:
```golang
func (msg SendMsg) GetSigners() []crypto.Address {
addrs := make([]crypto.Address, len(msg.Inputs))
func (msg SendMsg) GetSigners() []sdk.Address {
addrs := make([]sdk.Address, len(msg.Inputs))
for i, in := range msg.Inputs {
addrs[i] = in.Address
}
return addrs
}
func (msg IssueMsg) GetSigners() []crypto.Address {
return []crypto.Address{msg.Banker}
func (msg IssueMsg) GetSigners() []sdk.Address {
return []sdk.Address{msg.Banker}
}
```
@ -105,14 +105,6 @@ type Tx interface {
GetMsg() Msg
// The address that pays the base fee for this message. The fee is
// deducted before the Msg is processed.
GetFeePayer() crypto.Address
// Get the canonical byte representation of the Tx.
// Includes any signatures (or empty slots).
GetTxBytes() []byte
// 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
@ -148,8 +140,9 @@ 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.
Transactions can also specify the address responsible for paying the
transaction's fees using the `tx.GetFeePayer()` method.
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`:

View File

@ -13,7 +13,7 @@ Usually, Cosmos SDK can be installed like a normal Go program:
go get -u github.com/cosmos/cosmos-sdk
If the dependencies have been updated with breaking changes, or if
another branch is required, ``glide`` is used for dependency management.
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:
@ -24,12 +24,12 @@ repo, the correct way to install is:
make all
This will create the ``basecoin`` binary in ``$GOPATH/bin``.
``make all`` implies ``make get_vendor_deps`` and uses ``glide`` to
``make all`` implies ``make get_vendor_deps`` and uses ``dep`` to
install the correct version of all dependencies. It also tests the code,
including some cli tests to make sure your binary behaves properly.
If you need another branch, make sure to run ``git checkout <branch>``
before ``make all``. And if you switch branches a lot, especially
touching other tendermint repos, you may need to ``make fresh``
sometimes so glide doesn't get confused with all the branches and
sometimes so dep doesn't get confused with all the branches and
versions lying around.

View File

@ -46,6 +46,14 @@ paths:
other:
description: more information on versions
type: array
/syncing:
get:
summary: Syncing state of node
description: Get if the node is currently syning with other nodes
responses:
200:
description: "true" or "false"
/keys:
get:
summary: List of accounts stored locally
@ -62,7 +70,7 @@ paths:
summary: Create a new account locally
responses:
200:
description: OK
description: Returns address of the account created
requestBody:
content:
application/json:
@ -117,9 +125,12 @@ paths:
schema:
type: object
required:
- password
- new_password
- old_password
properties:
password:
new_password:
type: string
old_password:
type: string
responses:
200:
@ -147,35 +158,35 @@ paths:
description: Password is wrong
404:
description: Account is not available
/accounts/send:
post:
summary: Send coins (build -> sign -> send)
security:
- sign: []
requestBody:
content:
application/json:
schema:
type: object
properties:
fees:
$ref: "#/components/schemas/Coins"
outputs:
type: array
items:
type: object
properties:
pub_key:
$ref: "#/components/schemas/PubKey"
amount:
type: array
items:
$ref: "#/components/schemas/Coins"
responses:
202:
description: Tx was send and will probably be added to the next block
400:
description: The Tx was malformated
# /accounts/send:
# post:
# summary: Send coins (build -> sign -> send)
# security:
# - sign: []
# requestBody:
# content:
# application/json:
# schema:
# type: object
# properties:
# fees:
# $ref: "#/components/schemas/Coins"
# outputs:
# type: array
# items:
# type: object
# properties:
# pub_key:
# $ref: "#/components/schemas/PubKey"
# amount:
# type: array
# items:
# $ref: "#/components/schemas/Coins"
# responses:
# 202:
# description: Tx was send and will probably be added to the next block
# 400:
# description: The Tx was malformated
/accounts/{address}:
parameters:
@ -214,12 +225,18 @@ paths:
schema:
type: object
properties:
fees:
$ref: "#/components/schemas/Coins"
name:
type: string
password:
type: string
amount:
type: array
items:
$ref: "#/components/schemas/Coins"
chain_id:
type: string
squence:
type: number
responses:
202:
description: Tx was send and will probably be added to the next block
@ -300,72 +317,72 @@ paths:
$ref: "#/components/schemas/Delegate"
404:
description: Block at height not available
/txs:
parameters:
- in: query
name: tag
schema:
type: string
example: "coin.sender=EE5F3404034C524501629B56E0DDC38FAD651F04"
required: true
- in: query
name: page
description: Pagination page
schema:
type: number
default: 0
- in: query
name: size
description: Pagination size
schema:
type: number
default: 50
get:
summary: Query Tx
responses:
200:
description: All Tx matching the provided tags
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Tx"
404:
description: Pagination is out of bounds
/txs/sign:
post:
summary: Sign a Tx
description: Sign a Tx providing locally stored account and according password
security:
- sign: []
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/TxBuild"
responses:
200:
description: The signed Tx
content:
application/json:
schema:
$ref: "#/components/schemas/TxSigned"
401:
description: Account name and/or password where wrong
/txs/broadcast:
post:
summary: Send signed Tx
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/TxSigned"
responses:
202:
description: Tx was send and will probably be added to the next block
400:
description: The Tx was malformated
# /txs:
# parameters:
# - in: query
# name: tag
# schema:
# type: string
# example: "coin.sender=EE5F3404034C524501629B56E0DDC38FAD651F04"
# required: true
# - in: query
# name: page
# description: Pagination page
# schema:
# type: number
# default: 0
# - in: query
# name: size
# description: Pagination size
# schema:
# type: number
# default: 50
# get:
# summary: Query Tx
# responses:
# 200:
# description: All Tx matching the provided tags
# content:
# application/json:
# schema:
# type: array
# items:
# $ref: "#/components/schemas/Tx"
# 404:
# description: Pagination is out of bounds
# /txs/sign:
# post:
# summary: Sign a Tx
# description: Sign a Tx providing locally stored account and according password
# security:
# - sign: []
# requestBody:
# content:
# application/json:
# schema:
# $ref: "#/components/schemas/TxBuild"
# responses:
# 200:
# description: The signed Tx
# content:
# application/json:
# schema:
# $ref: "#/components/schemas/TxSigned"
# 401:
# description: Account name and/or password where wrong
# /txs/broadcast:
# post:
# summary: Send signed Tx
# requestBody:
# content:
# application/json:
# schema:
# $ref: "#/components/schemas/TxSigned"
# responses:
# 202:
# description: Tx was send and will probably be added to the next block
# 400:
# description: The Tx was malformated
/txs/{hash}:
parameters:
- in: path
@ -385,140 +402,140 @@ paths:
$ref: "#/components/schemas/Tx"
404:
description: Tx not available for provided hash
/delegates:
parameters:
- in: query
name: delegator
description: Query for all delegates a delegator has stake with
schema:
$ref: "#/components/schemas/Address"
get:
summary: Get a list of canidates/delegates/validators (optionally filtered by delegator)
responses:
200:
description: List of delegates, filtered by provided delegator address
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Delegate"
/delegates/bond:
post:
summary: Bond atoms (build -> sign -> send)
security:
- sign: []
requestBody:
content:
application/json:
schema:
type: array
items:
type: object
properties:
amount:
$ref: "#/components/schemas/Coins"
pub_key:
$ref: "#/components/schemas/PubKey"
responses:
202:
description: Tx was send and will probably be added to the next block
400:
description: The Tx was malformated
/delegates/unbond:
post:
summary: Unbond atoms (build -> sign -> send)
security:
- sign: []
requestBody:
content:
application/json:
schema:
type: array
items:
type: object
properties:
amount:
$ref: "#/components/schemas/Coins"
pub_key:
$ref: "#/components/schemas/PubKey"
responses:
202:
description: Tx was send and will probably be added to the next block
400:
description: The Tx was malformated
/delegates/{pubkey}:
parameters:
- in: path
name: pubkey
description: Pubkey of a delegate
required: true
schema:
type: string
example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958
get:
summary: Get a certain canidate/delegate/validator
responses:
200:
description: Delegate for specified pub_key
content:
application/json:
schema:
$ref: "#/components/schemas/Delegate"
404:
description: No delegate found for provided pub_key
/delegates/{pubkey}/bond:
parameters:
- in: path
name: pubkey
description: Pubkey of a delegate
required: true
schema:
type: string
example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958
post:
summary: Bond atoms (build -> sign -> send)
security:
- sign: []
requestBody:
content:
application/json:
schema:
type: object
properties:
amount:
$ref: "#/components/schemas/Coins"
responses:
202:
description: Tx was send and will probably be added to the next block
400:
description: The Tx was malformated
/delegates/{pubkey}/unbond:
parameters:
- in: path
name: pubkey
description: Pubkey of a delegate
required: true
schema:
type: string
example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958
post:
summary: Unbond atoms (build -> sign -> send)
security:
- sign: []
requestBody:
content:
application/json:
schema:
type: object
properties:
amount:
$ref: "#/components/schemas/Coins"
responses:
202:
description: Tx was send and will probably be added to the next block
400:
description: The Tx was malformated
# /delegates:
# parameters:
# - in: query
# name: delegator
# description: Query for all delegates a delegator has stake with
# schema:
# $ref: "#/components/schemas/Address"
# get:
# summary: Get a list of canidates/delegates/validators (optionally filtered by delegator)
# responses:
# 200:
# description: List of delegates, filtered by provided delegator address
# content:
# application/json:
# schema:
# type: array
# items:
# $ref: "#/components/schemas/Delegate"
# /delegates/bond:
# post:
# summary: Bond atoms (build -> sign -> send)
# security:
# - sign: []
# requestBody:
# content:
# application/json:
# schema:
# type: array
# items:
# type: object
# properties:
# amount:
# $ref: "#/components/schemas/Coins"
# pub_key:
# $ref: "#/components/schemas/PubKey"
# responses:
# 202:
# description: Tx was send and will probably be added to the next block
# 400:
# description: The Tx was malformated
# /delegates/unbond:
# post:
# summary: Unbond atoms (build -> sign -> send)
# security:
# - sign: []
# requestBody:
# content:
# application/json:
# schema:
# type: array
# items:
# type: object
# properties:
# amount:
# $ref: "#/components/schemas/Coins"
# pub_key:
# $ref: "#/components/schemas/PubKey"
# responses:
# 202:
# description: Tx was send and will probably be added to the next block
# 400:
# description: The Tx was malformated
# /delegates/{pubkey}:
# parameters:
# - in: path
# name: pubkey
# description: Pubkey of a delegate
# required: true
# schema:
# type: string
# example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958
# get:
# summary: Get a certain canidate/delegate/validator
# responses:
# 200:
# description: Delegate for specified pub_key
# content:
# application/json:
# schema:
# $ref: "#/components/schemas/Delegate"
# 404:
# description: No delegate found for provided pub_key
# /delegates/{pubkey}/bond:
# parameters:
# - in: path
# name: pubkey
# description: Pubkey of a delegate
# required: true
# schema:
# type: string
# example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958
# post:
# summary: Bond atoms (build -> sign -> send)
# security:
# - sign: []
# requestBody:
# content:
# application/json:
# schema:
# type: object
# properties:
# amount:
# $ref: "#/components/schemas/Coins"
# responses:
# 202:
# description: Tx was send and will probably be added to the next block
# 400:
# description: The Tx was malformated
# /delegates/{pubkey}/unbond:
# parameters:
# - in: path
# name: pubkey
# description: Pubkey of a delegate
# required: true
# schema:
# type: string
# example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958
# post:
# summary: Unbond atoms (build -> sign -> send)
# security:
# - sign: []
# requestBody:
# content:
# application/json:
# schema:
# type: object
# properties:
# amount:
# $ref: "#/components/schemas/Coins"
# responses:
# 202:
# description: Tx was send and will probably be added to the next block
# 400:
# description: The Tx was malformated
components:
schemas:

View File

@ -56,7 +56,7 @@ Strictly speaking, Golang does not implement object capabilities completely, bec
* pervasive ability to override module vars https://github.com/golang/go/issues/23161
* data-race vulnerability where 2+ goroutines can create illegal interface values
The first is easy to catch by auditing imports and using a proper dependency version control system like Glide. The second and third are unfortunate but it can be audited with some cost.
The first is easy to catch by auditing imports and using a proper dependency version control system like Dep. The second and third are unfortunate but it can be audited with some cost.
Perhaps `Go2 will implement the object capability model <https://github.com/golang/go/issues/23157>`__.
@ -156,7 +156,7 @@ implementing the ``Msg`` interface:
// 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() []crypto.Address
GetSigners() []Address
}
Messages must specify their type via the ``Type()`` method. The type should
@ -188,7 +188,7 @@ For instance, the ``Basecoin`` message types are defined in ``x/bank/tx.go``:
}
type IssueMsg struct {
Banker crypto.Address `json:"banker"`
Banker sdk.Address `json:"banker"`
Outputs []Output `json:"outputs"`
}
@ -196,16 +196,16 @@ Each specifies the addresses that must sign the message:
::
func (msg SendMsg) GetSigners() []crypto.Address {
addrs := make([]crypto.Address, len(msg.Inputs))
func (msg SendMsg) GetSigners() []sdk.Address {
addrs := make([]sdk.Address, len(msg.Inputs))
for i, in := range msg.Inputs {
addrs[i] = in.Address
}
return addrs
}
func (msg IssueMsg) GetSigners() []crypto.Address {
return []crypto.Address{msg.Banker}
func (msg IssueMsg) GetSigners() []sdk.Address {
return []sdk.Address{msg.Banker}
}
Transactions
@ -219,14 +219,6 @@ A transaction is a message with additional information for authentication:
GetMsg() Msg
// The address that pays the base fee for this message. The fee is
// deducted before the Msg is processed.
GetFeePayer() crypto.Address
// Get the canonical byte representation of the Tx.
// Includes any signatures (or empty slots).
GetTxBytes() []byte
// 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
@ -261,9 +253,6 @@ 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.
Transactions can also specify the address responsible for paying the
transaction's fees using the ``tx.GetFeePayer()`` method.
The standard way to create a transaction from a message is to use the ``StdTx``:
::

View File

@ -1,13 +1,18 @@
# Cosmos Hub Spec
This directory contains specifications for the application level components of the Cosmos Hub.
This directory contains specifications for the application level components of
the Cosmos Hub.
NOTE: the specifications are not yet complete and very much a work in progress.
- [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for sending tokens.
- [Staking](staking) - Proof of Stake related specifications including bonding and delegation transactions, inflation, fees, etc.
- [Governance](governance) - Governance related specifications including proposals and voting.
- [Other](other) - Other components of the Cosmos Hub, including the reserve pool, All in Bits vesting, etc.
- [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for
sending tokens.
- [Staking](staking) - Proof of Stake related specifications including bonding
and delegation transactions, inflation, fees, etc.
- [Governance](governance) - Governance related specifications including
proposals and voting.
- [Other](other) - Other components of the Cosmos Hub, including the reserve
pool, All in Bits vesting, etc.
The [specification for Tendermint](https://github.com/tendermint/tendermint/tree/develop/docs/specification/new-spec),
i.e. the underlying blockchain, can be found elsewhere.

View File

@ -0,0 +1,35 @@
# Governance module specification
## Abstract
This paper specifies the Governance module of the Cosmos-SDK, which was first described in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper) in June 2016.
The module enables Cosmos-SDK based blockchain to support an on-chain governance system. In this system, holders of the native staking token of the chain can vote on proposals on a 1 token 1 vote basis. Next is a list of features the module currently supports:
- **Proposal submission:** Users can submit proposals with a deposit. Once the minimum deposit is reached, proposal enters voting period
- **Vote:** Participants can vote on proposals that reached MinDeposit
- **Inheritance and penalties:** Delegators inherit their validator's vote if they don't vote themselves. If validators do not vote, they get partially slashed.
- **Signal and switch:** If a proposal of type `SoftwareUpgradeProposal` is accepted, validators can signal it and switch once enough validators have signalled.
- **Claiming deposit:** Users that deposited on proposals can recover their deposits if the proposal was accepted OR if the proposal never entered voting period.
Features that may be added in the future are described in [Future improvements](future_improvements.md)
This module will be used in the Cosmos Hub, the first Hub in the Cosmos network.
## Contents
The following specification uses *Atom* as the native staking token. The module can be adapted to any Proof-Of-Stake blockchain by replacing *Atom* with the native staking token of the chain.
1. **[Design overview](overview.md)**
2. **Implementation**
1. **[State](state.md)**
1. Procedures
2. Proposals
3. Proposal Processing Queue
2. **[Transactions](transactions.md)**
1. Proposal Submission
2. Deposit
3. Claim Deposit
4. Vote
3. **[Future improvements](future_improvements.md)**

View File

@ -0,0 +1,30 @@
# Future improvements (not in scope for MVP)
The current documentation only describes the minimum viable product for the
governance module. Future improvements may include:
* **`BountyProposals`:** If accepted, a `BountyProposal` creates an open
bounty. The `BountyProposal` specifies how many Atoms will be given upon
completion. These Atoms will be taken from the `reserve pool`. After a
`BountyProposal` is accepted by governance, anybody can submit a
`SoftwareUpgradeProposal` with the code to claim the bounty. Note that once a
`BountyProposal` is accepted, the corresponding funds in the `reserve pool`
are locked so that payment can always be honored. In order to link a
`SoftwareUpgradeProposal` to an open bounty, the submitter of the
`SoftwareUpgradeProposal` will use the `Proposal.LinkedProposal` attribute.
If a `SoftwareUpgradeProposal` linked to an open bounty is accepted by
governance, the funds that were reserved are automatically transferred to the
submitter.
* **Complex delegation:** Delegators could choose other representatives than
their validators. Ultimately, the chain of representatives would always end
up to a validator, but delegators could inherit the vote of their chosen
representative before they inherit the vote of their validator. In other
words, they would only inherit the vote of their validator if their other
appointed representative did not vote.
* **`ParameterProposals` and `WhitelistProposals`:** These proposals would
automatically change pre-defined parameters and whitelists. Upon acceptance,
these proposals would not require validators to do the signal and switch
process.
* **Better process for proposal review:** There would be two parts to
`proposal.Deposit`, one for anti-spam (same as in MVP) and an other one to
reward third party auditors.

View File

@ -10,27 +10,27 @@ The governance process is divided in a few steps that are outlined below:
- **Proposal submission:** Proposal is submitted to the blockchain with a deposit
- **Vote:** Once deposit reaches a certain value (`MinDeposit`), proposal is confirmed and vote opens. Bonded Atom holders can then send `TxGovVote` transactions to vote on the proposal
- If the proposal involves a software upgrade
- **Signal:** Validator start signaling that they are ready to switch to the new version
- **Switch:** Once more than 2/3rd validators have signaled their readiness to switch, their software automatically flips to the new version
- If the proposal involves a software upgrade:
- **Signal:** Validators start signaling that they are ready to switch to the new version
- **Switch:** Once more than 75% of validators have signaled that they are ready to switch, their software automatically flips to the new version
## Proposal submission
### Right to submit a proposal
Any Atom holder, whether bonded or unbonded, can submit proposals by sending a `TxProposal` transaction. Once a proposal is submitted, it is identified by its unique `proposalID`.
Any Atom holder, whether bonded or unbonded, can submit proposals by sending a `TxGovProposal` transaction. Once a proposal is submitted, it is identified by its unique `proposalID`.
### Proposal filter (minimum deposit)
To prevent spam, proposals must be submitted with a `deposit` in Atoms such that `0 < deposit < MinDeposit`.
Other Atom holders can increase the proposal's deposit by sending a `TxGovDeposit` transaction.
Once the proposals's deposit reaches `MinDeposit`, it enters voting period.
To prevent spam, proposals must be submitted with a deposit in Atoms. Voting period will not start as long as the proposal's deposit is smaller than the minimum deposit `MinDeposit`.
When a proposal is submitted, it has to be accompagnied by a deposit that must be strictly positive but can be inferior to `MinDeposit`. Indeed, the submitter need not pay for the entire deposit on its own. If a proposal's deposit is strictly inferior to `MinDeposit`, other Atom holders can increase the proposal's deposit by sending a `TxGovDeposit` transaction. Once the proposals's deposit reaches `MinDeposit`, it enters voting period.
### Deposit refund
There are two instances where Atom holders that deposited can claim back their deposit:
- If the proposal is accepted
- If the proposal's deposit does not reach `MinDeposit` for a period longer than `mMxDepositPeriod` (initial value: 2 months). Then the proposal is considered closed and nobody can deposit on it anymore.
- If the proposal's deposit does not reach `MinDeposit` for a period longer than `MaxDepositPeriod` (initial value: 2 months). Then the proposal is considered closed and nobody can deposit on it anymore.
In such instances, Atom holders that deposited can send a `TxGovClaimDeposit` transaction to retrieve their share of the deposit.
@ -52,7 +52,13 @@ These two categories are strictly identical except that `Urgent` proposals can b
### Participants
*Participants* are users that have the right to vote. On the Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and other users do not get the right to participate in governance. However, they can submit and deposit on proposals.
*Participants* are users that have the right to vote on proposals. On the Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and other users do not get the right to participate in governance. However, they can submit and deposit on proposals.
Note that some *participants* can be forbidden to vote on a proposal under a certain validator if:
- *participant* bonded or unbonded Atoms to said validator after proposal entered voting period
- *participant* became validator after proposal entered voting period
This does not prevent *participant* to vote with Atoms bonded to other validators. For example, if a *participant* bonded some Atoms to validator A before a proposal entered voting period and other Atoms to validator B after proposal entered voting period, only the vote under validator B will be forbidden.
### Voting period
@ -68,7 +74,7 @@ The initial option set includes the following options:
- `NoWithVeto`
- `Abstain`
`NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` allows voters to signal that they do not intend to vote in favor or against the proposal but accept the result of the vote.
`NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` option allows voters to signal that they do not intend to vote in favor or against the proposal but accept the result of the vote.
*Note: from the UI, for urgent proposals we should maybe add a Not Urgent option that casts a `NoWithVeto` vote.*
@ -80,32 +86,32 @@ In the initial version of the governance module, there will be no quorum enforce
### Threshold
Threshold is defined as the minimum ratio of `Yes` votes to `No` votes for the proposal to be accepted.
Threshold is defined as the minimum proportion of `Yes` votes (excluding `Abstain` votes) for the proposal to be accepted.
Initially, the threshold is set at 50% with a possibility to veto if more than 1/3rd of votes (excluding `Abstain` votes) are `NoWithVeto` votes. This means that proposals are accepted is the ratio of `Yes` votes to `No` votes at the end of the voting period is superior to 50% and if the number of `NoWithVeto` votes is inferior to 1/3rd of total votes (excluding `Abstain`).
Initially, the threshold is set at 50% with a possibility to veto if more than 1/3rd of votes (excluding `Abstain` votes) are `NoWithVeto` votes. This means that proposals are accepted if the proportion of `Yes` votes (excluding `Abstain` votes) at the end of the voting period is superior to 50% and if the proportion of `NoWithVeto` votes is inferior to 1/3 (excluding `Abstain` votes).
`Urgent` proposals also work with the aforementioned threshold, except there is another condition that can accelerate the acceptance of the proposal. Namely, if the ratio of `Yes` votes to `InitTotalVotingPower` exceeds 2/3, `UrgentProposal` will be immediately accepted, even if the `Voting period` is not finished. `InitTotalVotingPower` is the total voting power of all bonded Atom holders at the moment when the vote opens.
`Urgent` proposals also work with the aforementioned threshold, except there is another condition that can accelerate the acceptance of the proposal. Namely, if the ratio of `Yes` votes to `InitTotalVotingPower` exceeds 2:3, `UrgentProposal` will be immediately accepted, even if the `Voting period` is not finished. `InitTotalVotingPower` is the total voting power of all bonded Atom holders at the moment when the vote opens.
### Inheritance
If a delegator does not vote, it will inherit its validator vote.
- If the delegator votes before its validator, it will not inherit from the validator's vote.
- If the delegator votes after its validaotor, it will override its validator vote with its own vote. If the proposal is a `Urgent` proposal, it is possible that the vote will close before delegators have a chance to react and override their validator's vote. This is not a problem, as `Urgent` proposals require more than 2/3rd of the total voting power to pass before the end of the voting period. If more than 2/3rd of validators collude, they can censor the votes of delegators anyway.
- If the delegator votes after its validator, it will override its validator vote with its own. If the proposal is a `Urgent` proposal, it is possible that the vote will close before delegators have a chance to react and override their validator's vote. This is not a problem, as `Urgent` proposals require more than 2/3rd of the total voting power to pass before the end of the voting period. If more than 2/3rd of validators collude, they can censor the votes of delegators anyway.
### Validators punishment for non-voting
Validators are required to vote on all proposals to ensure that results have legitimacy. Voting is part of validators' directives and failure to do it will result in a penalty.
If a validators address is not in the list of addresses that voted on a proposal and if the vote is closed (i.e. `MinDeposit` was reached and `Voting period` is over), then this validator will automatically be partially slashed of `GovernancePenalty`.
If a validators address is not in the list of addresses that voted on a proposal and the vote is closed (i.e. `MinDeposit` was reached and `Voting period` is over), then the validator will automatically be partially slashed of `GovernancePenalty`.
*Note: Need to define values for `GovernancePenalty`*
**Exception:** If a proposal is a `Urgent` proposal and is accepted via the special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` that exceeds 2/3, validators cannot be punished for not having voted on it. That is because the proposal will close as soon as the ratio exceeds 2/3, making it mechanically impossible for some validators to vote on it.
**Exception:** If a proposal is a `Urgent` proposal and is accepted via the special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` that exceeds 2:3, validators cannot be punished for not having voted on it. That is because the proposal will close as soon as the ratio exceeds 2:3, making it mechanically impossible for some validators to vote on it.
### Governance key and governance address
Validators can make use of an additional slot where they can designate a `Governance PubKey`. By default, a validator's `Governance PubKey` will be the same as its main PubKey. Validators can change this `Governance PubKey` by sending a `Change Governance PubKey` transaction signed by their main `Consensus PubKey`. From there, they will be able to sign vote using the `Governance PrivKey` associated with their `Governance PubKey`. The `Governance PubKey` can be changed at any moment.
Validators can make use of a slot where they can designate a `Governance PubKey`. By default, a validator's `Governance PubKey` will be the same as its main PubKey. Validators can change this `Governance PubKey` by sending a `Change Governance PubKey` transaction signed by their main `Consensus PrivKey`. From there, they will be able to sign votes using the `Governance PrivKey` associated with their `Governance PubKey`. The `Governance PubKey` can be changed at any moment.
## Software Upgrade
@ -129,29 +135,124 @@ Once a block contains more than 2/3rd *precommits* where a common `SoftwareUpgra
*Disclaimer: This is a suggestion. Only structs and pseudocode. Actual logic and implementation might widely differ*
### Procedures
### State
#### Procedures
`Procedures` define the rule according to which votes are run. There can only be one active procedure at any given time. If governance wants to change a procedure, either to modify a value or add/remove a parameter, a new procedure has to be created and the previous one rendered inactive.
```Go
type Procedure struct {
VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks
MinDeposit int64 // Minimum deposit for a proposal to enter voting period.
VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks
MinDeposit int64 // Minimum deposit for a proposal to enter voting period.
OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain}
ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal}
Threshold int64 // Minimum value of Yes votes to No votes ratio for proposal to pass. Initial value: 0.5
Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5
Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
GovernancePenalty int64 // Penalty if validator does not vote
MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
GovernancePenalty int64 // Penalty if validator does not vote
ProcedureNumber int16 // Incremented each time a new procedure is created
IsActive bool // If true, procedure is active. Only one procedure can have isActive true.
}
```
### Proposals
**Store**:
- `Procedures`: a mapping `map[int16]Procedure` of procedures indexed by their `ProcedureNumber`
- `ActiveProcedureNumber`: returns current procedure number
`Proposals` are item to be voted on. They can be submitted by any Atom holder via a `TxGovSubmitProposal` transaction.
#### Proposals
`Proposals` are item to be voted on.
```Go
type Proposal struct {
Title string // Title of the proposal
Description string // Description of the proposal
Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
Category bool // false=regular, true=urgent
Deposit int64 // Current deposit on this proposal. Initial value is set at InitialDeposit
SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included
VotingStartBlock int64 // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached
InitTotalVotingPower int64 // Total voting power when proposal enters voting period (default 0)
InitProcedureNumber int16 // Procedure number of the active procedure when proposal enters voting period (default -1)
Votes map[string]int64 // Votes for each option (Yes, No, NoWithVeto, Abstain)
}
```
We also introduce a type `ValidatorGovInfo`
```Go
type ValidatorGovInfo struct {
InitVotingPower int64 // Voting power of validator when proposal enters voting period
Minus int64 // Minus of validator, used to compute validator's voting power
}
```
**Store:**
- `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their `proposalID`
- `Deposits`: A mapping `map[[]byte]int64` of deposits indexed by `<proposalID>:<depositorPubKey>` as `[]byte`. Given a `proposalID` and a `PubKey`, returns deposit (`nil` if `PubKey` has not deposited on the proposal)
- `Options`: A mapping `map[[]byte]string` of options indexed by `<proposalID>:<voterPubKey>:<validatorPubKey>` as `[]byte`. Given a `proposalID`, a `PubKey` and a validator's `PubKey`, returns option chosen by this `PubKey` for this validator (`nil` if `PubKey` has not voted under this validator)
- `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's governance infos indexed by `<proposalID>:<validatorGovPubKey>`. Returns `nil` if proposal has not entered voting period or if `PubKey` was not the governance public key of a validator when proposal entered voting period.
#### Proposal Processing Queue
**Store:**
- `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the `ProposalIDs` of proposals that reached `MinDeposit`. Each round, the oldest element of `ProposalProcessingQueue` is checked during `BeginBlock` to see if `CurrentBlock == VotingStartBlock + InitProcedure.VotingPeriod`. If it is, then the application checks if validators in `InitVotingPowerList` have voted and, if not, applies `GovernancePenalty`. After that proposal is ejected from `ProposalProcessingQueue` and the next element of the queue is evaluated. Note that if a proposal is urgent and accepted under the special condition, its `ProposalID` must be ejected from `ProposalProcessingQueue`.
And the pseudocode for the `ProposalProcessingQueue`:
```
in BeginBlock do
checkProposal() // First call of the recursive function
// Recursive function. First call in BeginBlock
func checkProposal()
if (ProposalProcessingQueue.Peek() == nil)
return
else
proposalID = ProposalProcessingQueue.Peek()
proposal = load(store, Proposals, proposalID)
initProcedure = load(store, Procedures, proposal.InitProcedureNumber)
if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3)
// proposal was urgent and accepted under the special condition
// no punishment
ProposalProcessingQueue.pop()
checkProposal()
else if (CurrentBlock == proposal.VotingStartBlock + initProcedure.VotingPeriod)
activeProcedure = load(store, Procedures, ActiveProcedureNumber)
for each validator in CurrentBondedValidators
validatorGovInfo = load(store, ValidatorGovInfos, validator.GovPubKey)
if (validatorGovInfo.InitVotingPower != nil)
// validator was bonded when vote started
validatorOption = load(store, Options, validator.GovPubKey)
if (validatorOption == nil)
// validator did not vote
slash validator by activeProcedure.GovernancePenalty
ProposalProcessingQueue.pop()
checkProposal()
```
### Transactions
#### Proposal Submission
Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` transaction.
```Go
type TxGovSubmitProposal struct {
@ -161,30 +262,17 @@ type TxGovSubmitProposal struct {
Category bool // false=regular, true=urgent
InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive.
}
type Proposal struct {
Title string // Title of the proposal
Description string // Description of the proposal
Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
Category bool // false=regular, true=urgent
Deposit int64 // Current deposit on this proposal. Initial value is set at InitialDeposit
SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included
VotingStartBlock int64 // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached.
Votes map[string]int64 // Votes for each option (Yes, No, NoWithVeto, Abstain)
}
```
Each `Proposal` is identified by its unique `proposalID`.
Additionaly, four lists will be linked to each proposal:
- `DepositorList`: List of addresses that deposited on the proposal with their associated deposit
- `VotersList`: List of addresses that voted **under each validator** with their associated option
- `InitVotingPowerList`: Snapshot of validators' voting power **when proposal enters voting period** (only saves validators whose voting power is >0).
- `MinusesList`: List of minuses for each validator. Used to compute validators' voting power when they cast a vote.
Two final parameters, `InitTotalVotingPower` and `InitProcedureNumber` associated with `proposalID` will be saved when proposal enters voting period.
We also introduce `ProposalProcessingQueue` which lists all the `ProposalIDs` of proposals that reached `MinDeposit` from oldest to newest. Each round, the oldest element of `ProposalProcessingQueue` is checked during `BeginBlock` to see if `CurrentBlock == VotingStartBlock + InitProcedure.VotingPeriod`. If it is, then the application checks if validators in `InitVotingPowerList` have voted and, if not, applies `GovernancePenalty`. After that proposal is ejected from `ProposalProcessingQueue` and the new first element of the queue is evaluated. Note that if a proposal is urgent and accepted under the special condition, its `ProposalID` must be ejected from `ProposalProcessingQueue`.
**State modifications:**
- Generate new `proposalID`
- Create new `Proposal`
- Initialise `Proposals` attributes
- Store sender's deposit in `Deposits`
- Decrease balance of sender by `InitialDeposit`
- If `MinDeposit` is reached:
- Push `proposalID` in `ProposalProcessingQueueEnd`
- Store each validator's voting power in `ValidatorGovInfos`
A `TxGovSubmitProposal` transaction can be handled according to the following pseudocode
@ -193,9 +281,10 @@ A `TxGovSubmitProposal` transaction can be handled according to the following ps
// Check if TxGovSubmitProposal is valid. If it is, create proposal //
upon receiving txGovSubmitProposal from sender do
// check if proposal is correctly formatted. Includes fee payment.
if !correctlyFormatted(txGovSubmitProposal) then
// check if proposal is correctly formatted. Includes fee payment.
throw
else
@ -205,10 +294,10 @@ upon receiving txGovSubmitProposal from sender do
throw
else
sender.AtomBalance -= InitialDeposit
sender.AtomBalance -= txGovSubmitProposal.InitialDeposit
proposalID = generate new proposalID
proposal = create new Proposal from proposalID
proposal = NewProposal()
proposal.Title = txGovSubmitProposal.Title
proposal.Description = txGovSubmitProposal.Description
@ -217,79 +306,59 @@ upon receiving txGovSubmitProposal from sender do
proposal.Deposit = txGovSubmitProposal.InitialDeposit
proposal.SubmitBlock = CurrentBlock
create depositorsList from proposalID
initiate deposit of sender in depositorsList at txGovSubmitProposal.InitialDeposit
store(Deposits, <proposalID>:<sender>, txGovSubmitProposal.InitialDeposit)
activeProcedure = load(store, Procedures, ActiveProcedureNumber)
if (txGovSubmitProposal.InitialDeposit < ActiveProcedure.MinDeposit) then
if (txGovSubmitProposal.InitialDeposit < activeProcedure.MinDeposit) then
// MinDeposit is not reached
proposal.VotingStartBlock = -1
proposal.InitTotalVotingPower = 0
proposal.InitProcedureNumber = -1
else
// MinDeposit is reached
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedureNumber = ActiveProcedureNumber
create votersList,
initVotingPowerList,
minusesList,
initProcedureNumber,
initTotalVotingPower from proposalID
snapshot(ActiveProcedure.ProcedureNumber) // Save current procedure number in initProcedureNumber
snapshot(TotalVotingPower) // Save total voting power in initTotalVotingPower
snapshot(ValidatorVotingPower) // Save validators' voting power in initVotingPowerList
for each validator in CurrentBondedValidators
// Store voting power of each bonded validator
validatorGovInfo = NewValidatorGovInfo()
validatorGovInfo.InitVotingPower = validator.VotingPower
validatorGovInfo.Minus = 0
store(ValidatorGovInfos, <proposalID>:<validator.GovPubKey>, validatorGovInfo)
ProposalProcessingQueueEnd++
ProposalProcessingQueue[ProposalProcessingQueueEnd] = proposalID
ProposalProcessingQueue.push(proposalID)
store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping
return proposalID
```
And the pseudocode for the `ProposalProcessingQueue`:
```
in BeginBlock do
checkProposal()
func checkProposal()
if (ProposalProcessingQueueBeginning == ProposalProcessingQueueEnd)
return
else
retrieve proposalID from ProposalProcessingQueue[ProposalProcessingQueueBeginning]
retrieve proposal from proposalID
retrieve initProcedureNumber from proposalID
retrieve initProcedure from initProcedureNumber
if (CurrentBlock == proposal.VotingStartBlock + initProcedure.VotingPeriod)
retrieve initVotingPowerList from proposalID
retrieve votersList from proposalID
retrieve validators from initVotingPowerList
for each validator in validators
if validator is not in votersList
slash validator by ActiveProcedure.GovernancePenalty
ProposalProcessingQueueBeginning++ // ProposalProcessingQueue will have a new element
checkProposal()
else
return
```
#### Deposit
Once a proposal is submitted, if `Proposal.Deposit < ActiveProcedure.MinDeposit`, Atom holders can send `TxGovDeposit` transactions to increase the proposal's deposit.
```Go
type TxGovDeposit struct {
ProposalID int64 // ID of the proposal
Deposit int64 // Number of Atoms to add to the proposal's deposit
ProposalID int64 // ID of the proposal
Deposit int64 // Number of Atoms to add to the proposal's deposit
}
```
**State modifications:**
- Decrease balance of sender by `deposit`
- Initialize or increase `deposit` of sender in `Deposits`
- Increase `proposal.Deposit` by sender's `deposit`
- If `MinDeposit` is reached:
- Push `proposalID` in `ProposalProcessingQueueEnd`
- Store each validator's voting power in `ValidatorGovInfos`
A `TxGovDeposit` transaction has to go through a number of checks to be valid. These checks are outlined in the following pseudocode.
```
@ -303,27 +372,28 @@ upon receiving txGovDeposit from sender do
throw
else
if !exist(txGovDeposit.proposalID) then
proposal = load(store, Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
// There is no proposal for this proposalID
throw
else
if (txGovDeposit.Deposit <= 0 OR sender.AtomBalance < txGovDeposit.Deposit)
if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit)
// deposit is negative or null OR sender has insufficient funds
throw
else
retrieve proposal from txGovDeposit.ProposalID // retrieve throws if it fails
if (proposal.Deposit >= ActiveProcedure.MinDeposit) then
else
activeProcedure = load(store, Procedures, ActiveProcedureNumber)
if (proposal.Deposit >= activeProcedure.MinDeposit) then
// MinDeposit was reached
throw
else
if (CurrentBlock >= proposal.SubmitBlock + ActiveProcedure.MaxDepositPeriod) then
if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then
// Maximum deposit period reached
throw
@ -331,36 +401,43 @@ upon receiving txGovDeposit from sender do
else
// sender can deposit
retrieve depositorsList from txGovDeposit.ProposalID
sender.AtomBalance -= txGovDeposit.Deposit
deposit = load(store, Deposits, <txGovDeposit.ProposalID>:<sender>)
if sender is in depositorsList
increase deposit of sender in depositorsList by txGovDeposit.Deposit
if (deposit == nil)
// sender has never deposited on this proposal
store(Deposits, <txGovDeposit.ProposalID>:<sender>, deposit)
else
initialise deposit of sender in depositorsList at txGovDeposit.Deposit
// sender has already deposited on this proposal
newDeposit = deposit + txGovDeposit.Deposit
store(Deposits, <txGovDeposit.ProposalID>:<sender>, newDeposit)
proposal.Deposit += txGovDeposit.Deposit
if (proposal.Deposit >= ActiveProcedure.MinDeposit) then
if (proposal.Deposit >= activeProcedure.MinDeposit) then
// MinDeposit is reached, vote opens
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedureNumber = ActiveProcedureNumber
create votersList,
initVotingPowerList,
minusesList,
initProcedureNumber,
initTotalVotingPower from proposalID
for each validator in CurrentBondedValidators
// Store voting power of each bonded validator
validatorGovInfo = NewValidatorGovInfo()
validatorGovInfo.InitVotingPower = validator.VotingPower
validatorGovInfo.Minus = 0
store(ValidatorGovInfos, <proposalID>:<validator.GovPubKey>, validatorGovInfo)
snapshot(ActiveProcedure.ProcedureNumber) // Save current procedure number in InitProcedureNumber
snapshot(TotalVotingPower) // Save total voting power in InitTotalVotingPower
snapshot(ValidatorVotingPower) // Save validators' voting power in InitVotingPowerList
ProposalProcessingQueueEnd++ // ProposalProcessingQueue will have a new element
ProposalProcessingQueue[ProposalProcessingQueueEnd] = txGovDeposit.ProposalID
ProposalProcessingQueue.push(txGovDeposit.ProposalID)
```
#### Claiming deposit
Finally, if the proposal is accepted or `MinDeposit` was not reached before the end of the `MaximumDepositPeriod`, then Atom holders can send `TxGovClaimDeposit` transaction to claim their deposits.
```Go
@ -369,6 +446,11 @@ Finally, if the proposal is accepted or `MinDeposit` was not reached before the
}
```
**State modifications:**
If conditions are met, reimburse the deposit, i.e.
- Increase `AtomBalance` of sender by `deposit`
- Set `deposit` of sender in `DepositorsList` to 0
And the associated pseudocode
```
@ -382,66 +464,63 @@ And the associated pseudocode
throw
else
if !exists(txGovClaimDeposit.ProposalID) then
proposal = load(store, Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
// There is no proposal for this proposalID
throw
else
retrieve depositorsList from txGovClaimDeposit.ProposalID
deposit = load(store, Deposits, <txGovClaimDeposit.ProposalID>:<sender>)
if sender is not in depositorsList then
if (deposit == nil)
// sender has not deposited on this proposal
throw
else
retrieve deposit from sender in depositorsList
if deposit <= 0
else
if (deposit <= 0)
// deposit has already been claimed
throw
else
retrieve proposal from txGovClaimDeposit.ProposalID
if proposal.VotingStartBlock <= 0
else
if (proposal.VotingStartBlock <= 0)
// Vote never started
if (CurrentBlock <= proposal.SubmitBlock + ActiveProcedure.MaxDepositPeriod)
activeProcedure = load(store, Procedures, ActiveProcedureNumber)
if (CurrentBlock <= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod)
// MaxDepositPeriod is not reached
throw
else
// MaxDepositPeriod is reached
set deposit of sender in depositorsList to 0
// Set sender's deposit to 0 and refund
store(Deposits, <txGovClaimDeposit.ProposalID>:<sender>, 0)
sender.AtomBalance += deposit
else
// Vote started
initProcedure = load(store, Procedures, proposal.InitProcedureNumber)
retrieve initTotalVotingPower from txGovClaimDeposit.ProposalID
retrieve initProcedureNumber from txGovClaimDeposit.ProposalID
retrieve initProcedure from initProcedureNumber // get procedure that was active when vote opened
if (proposal.Category AND proposal.Votes['Yes']/initTotalVotingPower >= 2/3) OR
((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then
if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) OR
((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then
// Proposal was accepted either because
// Proposal was urgent and special condition was met
// Voting period ended and vote satisfies threshold
set deposit of sender in depositorsList to 0
store(Deposits, <txGovClaimDeposit.ProposalID>:<sender>, 0)
sender.AtomBalance += deposit
else
throw
```
### Vote
#### Vote
Once `ActiveProcedure.MinDeposit` is reached, voting period starts. From there, bonded Atom holders are able to send `TxGovVote` transactions to cast their vote on the proposal.
@ -453,9 +532,17 @@ Once `ActiveProcedure.MinDeposit` is reached, voting period starts. From there,
}
```
**State modifications:**
- If sender is not a validator and validator has not voted, initialize or increase minus of validator by sender's `voting power`
- If sender is not a validator and validator has voted, decrease `proposal.Votes['validatorOption']` by sender's `voting power`
- If sender is not a validator, increase `[proposal.Votes['txGovVote.Option']` by sender's `voting power`
- If sender is a validator, increase `proposal.Votes['txGovVote.Option']` by validator's `InitialVotingPower - minus` (`minus` can be equal to 0)
Votes need to be tied to a validator in order to compute validator's voting power. If a delegator is bonded to multiple validators, it will have to send one transaction per validator (the UI should facilitate this so that multiple transactions can be sent in one "vote flow").
If the sender is the validator itself, then it will input its own GovernancePubKey as `ValidatorPubKey`
Next is a pseudocode proposal of the way `TxGovVote` transactions can be handled:
```
@ -469,19 +556,19 @@ Next is a pseudocode proposal of the way `TxGovVote` transactions can be handled
throw
else
if !exists(txGovVote.proposalID) OR
// Throws if
// proposalID does not exist
proposal = load(store, Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
// There is no proposal for this proposalID
throw
else
retrieve initProcedureNumber from txGovVote.ProposalID
retrieve initProcedure from initProcedureNumber // get procedure that was active when vote opened
initProcedure = load(store, Procedures, proposal.InitProcedureNumber) // get procedure that was active when vote opened
validator = load(store, Validators, txGovVote.ValidatorPubKey)
if !initProcedure.OptionSet.includes(txGovVote.Option) OR
!isValid(txGovVote.ValidatorPubKey) then
(validator == nil) then
// Throws if
// Option is not in Option Set of procedure that was active when vote opened OR if
@ -490,22 +577,19 @@ Next is a pseudocode proposal of the way `TxGovVote` transactions can be handled
throw
else
retrieve votersList from txGovVote.ProposalID
option = load(store, Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorPubKey>)
if sender is in votersList under txGovVote.ValidatorPubKey then
if (option != nil)
// sender has already voted with the Atoms bonded to ValidatorPubKey
throw
else
retrieve proposal from txGovVote.ProposalID
retrieve InitTotalVotingPower from txGovVote.ProposalID
if (proposal.VotingStartBlock < 0) OR
(CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) OR
(proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorPubKey) OR
(proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.ValidatorPubKey) OR
(proposal.Category AND proposal.Votes['Yes']/InitTotalVotingPower >= 2/3) then
(proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) then
// Throws if
// Vote has not started OR if
@ -517,66 +601,51 @@ Next is a pseudocode proposal of the way `TxGovVote` transactions can be handled
throw
else
// sender can vote, check if sender == validator and add sender to voter list
add sender to votersList under txGovVote.ValidatorPubKey
validatorGovInfo = load(store, ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorGovPubKey>)
if (sender is not equal to GovPubKey that corresponds to txGovVote.ValidatorPubKey)
// Here, sender is not the Governance PubKey of the validator whose PubKey is txGovVote.ValidatorPubKey
if (validatorGovInfo == nil)
// validator became validator after proposal entered voting period
if sender does not have bonded Atoms to txGovVote.ValidatorPubKey then
throw
throw
else
if txGovVote.ValidatorPubKey is not in votersList under txGovVote.ValidatorPubKey then
// Validator has not voted already
else
// sender can vote, check if sender == validator and store sender's option in Options
store(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorPubKey>, txGovVote.Option)
if exists(MinusesList[txGovVote.ValidatorPubKey]) then
// a minus already exists for this validator's PubKey, increase minus
// by the amount of Atoms sender has bonded to ValidatorPubKey
if (sender != validator.GovPubKey)
// Here, sender is not the Governance PubKey of the validator whose PubKey is txGovVote.ValidatorPubKey
MinusesList[txGovVote.ValidatorPubKey] += sender.bondedAmountTo(txGovVote.ValidatorPubKey)
if sender does not have bonded Atoms to txGovVote.ValidatorPubKey then
// check in Staking module
else
// a minus does not already exist for this validator's PubKey, initialise minus
// at the amount of Atoms sender has bonded to ValidatorPubKey
MinusesList[txGovVote.ValidatorPubKey] = sender.bondedAmountTo(txGovVote.ValidatorPubKey)
throw
else
// Validator has already voted
// Reduce option count chosen by validator by sender's bonded Amount
validatorOption = load(store, Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorPubKey)
retrieve validatorOption from votersList using txGovVote.ValidatorPubKey
proposal.Votes['validatorOption'] -= sender.bondedAmountTo(txGovVote.ValidatorPubKey)
if (validatorOption == nil)
// Validator has not voted already
// increase Option count chosen by sender by bonded Amount
proposal.Votes['txGovVote.Option'] += sender.bondedAmountTo(txGovVote.ValidatorPubKey)
validatorGovInfo.Minus += sender.bondedAmounTo(txGovVote.ValidatorPubKey)
store(ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorGovPubKey>, validatorGovInfo)
else
// sender is the Governance PubKey of the validator whose main PubKey is txGovVote.ValidatorPubKey
// i.e. sender == validator
else
// Validator has already voted
// Reduce votes of option chosen by validator by sender's bonded Amount
retrieve initialVotingPower from InitVotingPowerList using txGovVote.ValidatorPubKey
if exists(MinusesList[txGovVote.ValidatorPubKey]) then
// a minus exists for this validator's PubKey, decrease vote of validator by minus
proposal.Votes['validatorOption'] -= sender.bondedAmountTo(txGovVote.ValidatorPubKey)
proposal.Votes['txGovVote.Option'] += (initialVotingPower - MinusesList[txGovVote.ValidatorPubKey])
// increase votes of option chosen by sender by bonded Amount
proposal.Votes['txGovVote.Option'] += sender.bondedAmountTo(txGovVote.ValidatorPubKey)
else
// a minus does not exist for this validator's PubKey, validator votes with full voting power
else
// sender is the Governance PubKey of the validator whose main PubKey is txGovVote.ValidatorPubKey
// i.e. sender == validator
proposal.Votes['txGovVote.Option'] += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus)
proposal.Votes['txGovVote.Option'] += initialVotingPower
if (proposal.Category AND proposal.Votes['Yes']/InitTotalVotingPower >= 2/3)
// after vote is counted, if proposal is urgent and special condition is met
// remove proposalID from ProposalProcessingQueue
remove txGovVote.ProposalID from ProposalProcessingQueue
Rearrange ProposalProcessingQueue
ProposalProcessingQueueEnd--
```

View File

@ -0,0 +1,192 @@
# Design Overview
*Disclaimer: This is work in progress. Mechanisms are susceptible to change.*
The governance process is divided in a few steps that are outlined below:
* **Proposal submission:** Proposal is submitted to the blockchain with a
deposit.
* **Vote:** Once deposit reaches a certain value (`MinDeposit`), proposal is
confirmed and vote opens. Bonded Atom holders can then send `TxGovVote`
transactions to vote on the proposal.
* If the proposal involves a software upgrade:
* **Signal:** Validators start signaling that they are ready to switch to the
new version.
* **Switch:** Once more than 75% of validators have signaled that they are
ready to switch, their software automatically flips to the new version.
## Proposal submission
### Right to submit a proposal
Any Atom holder, whether bonded or unbonded, can submit proposals by sending a
`TxGovProposal` transaction. Once a proposal is submitted, it is identified by
its unique `proposalID`.
### Proposal filter (minimum deposit)
To prevent spam, proposals must be submitted with a deposit in Atoms. Voting
period will not start as long as the proposal's deposit is smaller than the
minimum deposit `MinDeposit`.
When a proposal is submitted, it has to be accompanied by a deposit that must
be strictly positive but can be inferior to `MinDeposit`. Indeed, the submitter
need not pay for the entire deposit on its own. If a proposal's deposit is
strictly inferior to `MinDeposit`, other Atom holders can increase the
proposal's deposit by sending a `TxGovDeposit` transaction. Once the proposal's deposit reaches `MinDeposit`, it enters voting period.
If proposal's deposit does not reach `MinDeposit` before `MaxDepositPeriod`, proposal closes and nobody can deposit on it anymore.
### Deposit refund
There is one instance where Atom holders that deposits can be refunded:
* If the proposal is accepted.
Then, deposits will automatically be refunded to their respective depositer.
### Proposal types
In the initial version of the governance module, there are two types of
proposal:
* `PlainTextProposal` All the proposals that do not involve a modification of
the source code go under this type. For example, an opinion poll would use a
proposal of type `PlainTextProposal`.
* `SoftwareUpgradeProposal`. If accepted, validators are expected to update
their software in accordance with the proposal. They must do so by following
a 2-steps process described in the [Software Upgrade](#software-upgrade)
section below. Software upgrade roadmap may be discussed and agreed on via
`PlainTextProposals`, but actual software upgrades must be performed via
`SoftwareUpgradeProposals`.
## Vote
### Participants
*Participants* are users that have the right to vote on proposals. On the
Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and
other users do not get the right to participate in governance. However, they
can submit and deposit on proposals.
Note that some *participants* can be forbidden to vote on a proposal under a
certain validator if:
* *participant* bonded or unbonded Atoms to said validator after proposal
entered voting period.
* *participant* became validator after proposal entered voting period.
This does not prevent *participant* to vote with Atoms bonded to other
validators. For example, if a *participant* bonded some Atoms to validator A
before a proposal entered voting period and other Atoms to validator B after
proposal entered voting period, only the vote under validator B will be
forbidden.
### Voting period
Once a proposal reaches `MinDeposit`, it immediately enters `Voting period`. We
define `Voting period` as the interval between the moment the vote opens and
the moment the vote closes. `Voting period` should always be shorter than
`Unbonding period` to prevent double voting. The initial value of
`Voting period` is 2 weeks.
### Option set
The option set of a proposal refers to the set of choices a participant can
choose from when casting its vote.
The initial option set includes the following options:
- `Yes`
- `No`
- `NoWithVeto`
- `Abstain`
`NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` option
allows voters to signal that they do not intend to vote in favor or against the
proposal but accept the result of the vote.
*Note: from the UI, for urgent proposals we should maybe add a Not Urgent
option that casts a `NoWithVeto` vote.*
### Quorum
Quorum is defined as the minimum percentage of voting power that needs to be
casted on a proposal for the result to be valid.
In the initial version of the governance module, there will be no quorum
enforced by the protocol. Participation is ensured via the combination of
inheritance and validator's punishment for non-voting.
### Threshold
Threshold is defined as the minimum proportion of `Yes` votes (excluding
`Abstain` votes) for the proposal to be accepted.
Initially, the threshold is set at 50% with a possibility to veto if more than
1/3rd of votes (excluding `Abstain` votes) are `NoWithVeto` votes. This means
that proposals are accepted if the proportion of `Yes` votes (excluding
`Abstain` votes) at the end of the voting period is superior to 50% and if the
proportion of `NoWithVeto` votes is inferior to 1/3 (excluding `Abstain`
votes).
Proposals can be accepted before the end of the voting period if they meet a special condtion. Namely, if the ratio of `Yes` votes to `InitTotalVotingPower`exceeds 2:3, the proposal will be immediately accepted, even if the `Voting period` is not finished. `InitTotalVotingPower` is the total voting power of all bonded Atom holders at the moment when the vote opens.
This condition exists so that the network can react quickly in case of urgency.
### Inheritance
If a delegator does not vote, it will inherit its validator vote.
* If the delegator votes before its validator, it will not inherit from the
validator's vote.
* If the delegator votes after its validator, it will override its validator
vote with its own. If the proposal is urgent, it is possible
that the vote will close before delegators have a chance to react and
override their validator's vote. This is not a problem, as proposals require more than 2/3rd of the total voting power to pass before the end of the voting period. If more than 2/3rd of validators collude, they can censor the votes of delegators anyway.
### Validators punishment for non-voting
Validators are required to vote on all proposals to ensure that results have
legitimacy. Voting is part of validators' directives and failure to do it will
result in a penalty.
If a validators address is not in the list of addresses that voted on a
proposal and the vote is closed (i.e. `MinDeposit` was reached and `Voting
period` is over), then the validator will automatically be partially slashed by
`GovernancePenalty`.
*Note: Need to define values for `GovernancePenalty`*
**Exception:** If a proposal is accepted via the special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` that exceeds 2:3, validators cannot be punished for not having voted on it.
That is because the proposal will close as soon as the ratio exceeds 2:3,
making it mechanically impossible for some validators to vote on it.
### Governance address
Later, we may add permissionned keys that could only sign txs from certain modules. For the MVP, the `Governance address` will be the main validator address generated at account creation. This address corresponds to a different PrivKey than the Tendermint PrivKey which is responsible for signing consensus messages. Validators thus do not have to sign governance transactions with the sensitive Tendermint PrivKey.
## Software Upgrade
If proposals are of type `SoftwareUpgradeProposal`, then nodes need to upgrade
their software to the new version that was voted. This process is divided in
two steps.
### Signal
After a `SoftwareUpgradeProposal` is accepted, validators are expected to
download and install the new version of the software while continuing to run
the previous version. Once a validator has downloaded and installed the
upgrade, it will start signaling to the network that it is ready to switch by
including the proposal's `proposalID` in its *precommits*.(*Note: Confirmation
that we want it in the precommit?*)
Note: There is only one signal slot per *precommit*. If several
`SoftwareUpgradeProposals` are accepted in a short timeframe, a pipeline will
form and they will be implemented one after the other in the order that they
were accepted.
### Switch
Once a block contains more than 2/3rd *precommits* where a common
`SoftwareUpgradeProposal` is signaled, all the nodes (including validator
nodes, non-validating full nodes and light-nodes) are expected to switch to the
new version of the software.
*Note: Not clear how the flip is handled programatically*

View File

@ -0,0 +1,182 @@
# Implementation (1/2)
## State
### Procedures
`Procedures` define the rule according to which votes are run. There can only
be one active procedure at any given time. If governance wants to change a
procedure, either to modify a value or add/remove a parameter, a new procedure
has to be created and the previous one rendered inactive.
```go
type Procedure struct {
VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks
MinDeposit int64 // Minimum deposit for a proposal to enter voting period.
OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain}
ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal}
Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5
Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
GovernancePenalty int64 // Penalty if validator does not vote
IsActive bool // If true, procedure is active. Only one procedure can have isActive true.
}
```
The current active procedure is stored in a global `params` KVStore.
### Deposit
```go
type Deposit struct {
Amount sdk.Coins // sAmount of coins deposited by depositer
Depositer crypto.address // Address of depositer
}
```
### Votes
```go
type Votes struct {
YesVotes int64
NoVote int64
NoWithVetoVotes int64
AbstainVotes int64
}
```
### Proposals
`Proposals` are item to be voted on.
```go
type Proposal struct {
Title string // Title of the proposal
Description string // Description of the proposal
Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
TotalDeposit sdk.Coins // Current deposit on this proposal. Initial value is set at InitialDeposit
Deposits []Deposit // List of deposits on the proposal
SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included
Submitter crypto.address // Address of the submitter
VotingStartBlock int64 // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached
InitTotalVotingPower int64 // Total voting power when proposal enters voting period (default 0)
InitProcedure Procedure // Active Procedure when proposal enters voting period
Votes Votes // Total votes for each option
}
```
We also introduce a type `ValidatorGovInfo`
```go
type ValidatorGovInfo struct {
InitVotingPower int64 // Voting power of validator when proposal enters voting period
Minus int64 // Minus of validator, used to compute validator's voting power
}
```
### Stores
*Stores are KVStores in the multistore. The key to find the store is the first parameter in the list*
* `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their
`proposalID`
* `Options`: A mapping `map[[]byte]string` of options indexed by
`<proposalID>:<voterAddress>:<validatorAddress>` as `[]byte`. Given a
`proposalID`, an `address` and a validator's `address`, returns option chosen by this `address` for this validator (`nil` if `address` has not voted under this validator)
* `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's
governance infos indexed by `<proposalID>:<validatorAddress>`. Returns
`nil` if proposal has not entered voting period or if `address` was not the
address of a validator when proposal entered voting period.
For pseudocode purposes, here are the two function we will use to read or write in stores:
* `load(StoreKey, Key)`: Retrieve item stored at key `Key` in store found at key `StoreKey` in the multistore
* `store(StoreKey, Key, value)`: Write value `Value` at key `Key` in store found at key `StoreKey` in the multistore
### Proposal Processing Queue
**Store:**
* `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the
`ProposalIDs` of proposals that reached `MinDeposit`. Each round, the oldest
element of `ProposalProcessingQueue` is checked during `BeginBlock` to see if
`CurrentBlock == VotingStartBlock + InitProcedure.VotingPeriod`. If it is,
then the application checks if validators in `InitVotingPowerList` have voted
and, if not, applies `GovernancePenalty`. If the proposal is accepted, deposits are refunded.
After that proposal is ejected from `ProposalProcessingQueue` and the next element of the queue is evaluated.
Note that if a proposal is accepted under the special condition,
its `ProposalID` must be ejected from `ProposalProcessingQueue`.
And the pseudocode for the `ProposalProcessingQueue`:
```go
in BeginBlock do
checkProposal() // First call of the recursive function
// Recursive function. First call in BeginBlock
func checkProposal()
if (ProposalProcessingQueue.Peek() == nil)
return
else
proposalID = ProposalProcessingQueue.Peek()
proposal = load(Proposals, proposalID)
if (proposal.Votes.YesVotes/proposal.InitTotalVotingPower >= 2/3)
// proposal was urgent and accepted under the special condition
// no punishment
// refund deposits
ProposalProcessingQueue.pop()
newDeposits = new []Deposits
for each (amount, depositer) in proposal.Deposits
newDeposits.append[{0, depositer}]
depositer.AtomBalance += amount
proposal.Deposits = newDeposits
store(Proposals, <proposalID>, proposal)
checkProposal()
else if (CurrentBlock == proposal.VotingStartBlock + proposal.Procedure.VotingPeriod)
ProposalProcessingQueue.pop()
activeProcedure = load(params, 'ActiveProcedure')
for each validator in CurrentBondedValidators
validatorGovInfo = load(ValidatorGovInfos, <proposalID>:<validator.address>)
if (validatorGovInfo.InitVotingPower != nil)
// validator was bonded when vote started
validatorOption = load(Options, <proposalID>:<validator.address><validator.address>)
if (validatorOption == nil)
// validator did not vote
slash validator by activeProcedure.GovernancePenalty
if((proposal.Votes.YesVotes/(proposal.Votes.YesVotes + proposal.Votes.NoVotes + proposal.Votes.NoWithVetoVotes)) > 0.5 AND (proposal.Votes.NoWithVetoVotes/(proposal.Votes.YesVotes + proposal.Votes.NoVotes + proposal.Votes.NoWithVetoVotes) < 1/3))
// proposal was accepted at the end of the voting period
// refund deposits
newDeposits = new []Deposits
for each (amount, depositer) in proposal.Deposits
newDeposits.append[{0, depositer}]
depositer.AtomBalance += amount
proposal.Deposits = newDeposits
store(Proposals, <proposalID>, proposal)
checkProposal()
```

View File

@ -0,0 +1,327 @@
# Implementation (2/2)
## Transactions
### Proposal Submission
Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal`
transaction.
```go
type TxGovSubmitProposal struct {
Title string // Title of the proposal
Description string // Description of the proposal
Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive.
}
```
**State modifications:**
* Generate new `proposalID`
* Create new `Proposal`
* Initialise `Proposals` attributes
* Decrease balance of sender by `InitialDeposit`
* If `MinDeposit` is reached:
* Push `proposalID` in `ProposalProcessingQueueEnd`
* Store each validator's voting power in `ValidatorGovInfos`
A `TxGovSubmitProposal` transaction can be handled according to the following
pseudocode.
```go
// PSEUDOCODE //
// Check if TxGovSubmitProposal is valid. If it is, create proposal //
upon receiving txGovSubmitProposal from sender do
if !correctlyFormatted(txGovSubmitProposal) then
// check if proposal is correctly formatted. Includes fee payment.
throw
else
if (txGovSubmitProposal.InitialDeposit <= 0) OR (sender.AtomBalance < InitialDeposit) then
// InitialDeposit is negative or null OR sender has insufficient funds
throw
else
sender.AtomBalance -= txGovSubmitProposal.InitialDeposit
proposalID = generate new proposalID
proposal = NewProposal()
proposal.Title = txGovSubmitProposal.Title
proposal.Description = txGovSubmitProposal.Description
proposal.Type = txGovSubmitProposal.Type
proposal.TotalDeposit = txGovSubmitProposal.InitialDeposit
proposal.SubmitBlock = CurrentBlock
proposal.Deposits.append({InitialDeposit, sender})
proposal.Submitter = sender
proposal.Votes.YesVotes = 0
proposal.Votes.NoVotes = 0
proposal.Votes.NoWithVetoVotes = 0
proposal.Votes.AbstainVotes = 0
activeProcedure = load(params, 'ActiveProcedure')
if (txGovSubmitProposal.InitialDeposit < activeProcedure.MinDeposit) then
// MinDeposit is not reached
proposal.VotingStartBlock = -1
proposal.InitTotalVotingPower = 0
else
// MinDeposit is reached
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedure = activeProcedure
for each validator in CurrentBondedValidators
// Store voting power of each bonded validator
validatorGovInfo = new ValidatorGovInfo
validatorGovInfo.InitVotingPower = validator.VotingPower
validatorGovInfo.Minus = 0
store(ValidatorGovInfos, <proposalID>:<validator.Address>, validatorGovInfo)
ProposalProcessingQueue.push(proposalID)
store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping
return proposalID
```
### Deposit
Once a proposal is submitted, if
`Proposal.TotalDeposit < ActiveProcedure.MinDeposit`, Atom holders can send
`TxGovDeposit` transactions to increase the proposal's deposit.
```go
type TxGovDeposit struct {
ProposalID int64 // ID of the proposal
Deposit int64 // Number of Atoms to add to the proposal's deposit
}
```
**State modifications:**
* Decrease balance of sender by `deposit`
* Add `deposit` of sender in `proposal.Deposits`
* Increase `proposal.TotalDeposit` by sender's `deposit`
* If `MinDeposit` is reached:
* Push `proposalID` in `ProposalProcessingQueueEnd`
* Store each validator's voting power in `ValidatorGovInfos`
A `TxGovDeposit` transaction has to go through a number of checks to be valid.
These checks are outlined in the following pseudocode.
```go
// PSEUDOCODE //
// Check if TxGovDeposit is valid. If it is, increase deposit and check if MinDeposit is reached
upon receiving txGovDeposit from sender do
// check if proposal is correctly formatted. Includes fee payment.
if !correctlyFormatted(txGovDeposit) then
throw
else
proposal = load(Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
// There is no proposal for this proposalID
throw
else
if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit)
// deposit is negative or null OR sender has insufficient funds
throw
else
activeProcedure = load(params, 'ActiveProcedure')
if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then
// MinDeposit was reached
throw
else
if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then
// Maximum deposit period reached
throw
else
// sender can deposit
sender.AtomBalance -= txGovDeposit.Deposit
proposal.Deposits.append({txGovVote.Deposit, sender})
proposal.TotalDeposit += txGovDeposit.Deposit
if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then
// MinDeposit is reached, vote opens
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedure = activeProcedure
for each validator in CurrentBondedValidators
// Store voting power of each bonded validator
validatorGovInfo = NewValidatorGovInfo()
validatorGovInfo.InitVotingPower = validator.VotingPower
validatorGovInfo.Minus = 0
store(ValidatorGovInfos, <proposalID>:<validator.Address>, validatorGovInfo)
ProposalProcessingQueue.push(txGovDeposit.ProposalID)
store(Proposals, txGovVote.ProposalID, proposal)
```
### Vote
Once `ActiveProcedure.MinDeposit` is reached, voting period starts. From there,
bonded Atom holders are able to send `TxGovVote` transactions to cast their
vote on the proposal.
```go
type TxGovVote struct {
ProposalID int64 // proposalID of the proposal
Option string // option from OptionSet chosen by the voter
ValidatorAddress crypto.address // Address of the validator voter wants to tie its vote to
}
```
**State modifications:**
* If sender is not a validator and validator has not voted, initialize or
increase minus of validator by sender's `voting power`
* If sender is not a validator and validator has voted, decrease
votes of `validatorOption` by sender's `voting power`
* If sender is not a validator, increase votes of `txGovVote.Option`
by sender's `voting power`
* If sender is a validator, increase votes of `txGovVote.Option` by
validator's `InitVotingPower - minus` (`minus` can be equal to 0)
Votes need to be tied to a validator in order to compute validator's voting
power. If a delegator is bonded to multiple validators, it will have to send
one transaction per validator (the UI should facilitate this so that multiple
transactions can be sent in one "vote flow"). If the sender is the validator
itself, then it will input its own address as `ValidatorAddress`
Next is a pseudocode proposal of the way `TxGovVote` transactions are
handled:
```go
// PSEUDOCODE //
// Check if TxGovVote is valid. If it is, count vote//
upon receiving txGovVote from sender do
// check if proposal is correctly formatted. Includes fee payment.
if !correctlyFormatted(txGovDeposit) then
throw
else
proposal = load(Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
// There is no proposal for this proposalID
throw
else
validator = load(CurrentValidators, txGovVote.ValidatorAddress)
if !proposal.InitProcedure.OptionSet.includes(txGovVote.Option) OR
(validator == nil) then
// Throws if
// Option is not in Option Set of procedure that was active when vote opened OR if
// ValidatorAddress is not the address of a current validator
throw
else
option = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>)
if (option != nil)
// sender has already voted with the Atoms bonded to ValidatorAddress
throw
else
if (proposal.VotingStartBlock < 0) OR
(CurrentBlock > proposal.VotingStartBlock + proposal.InitProcedure.VotingPeriod) OR
(proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorAddress) OR
(proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.Address) OR
(proposal.Votes.YesVotes/proposal.InitTotalVotingPower >= 2/3) then
// Throws if
// Vote has not started OR if
// Vote had ended OR if
// sender bonded Atoms to ValidatorAddress after start of vote OR if
// sender unbonded Atoms from ValidatorAddress after start of vote OR if
// special condition is met, i.e. proposal is accepted and closed
throw
else
validatorGovInfo = load(ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorAddress>)
if (validatorGovInfo == nil)
// validator became validator after proposal entered voting period
throw
else
// sender can vote, check if sender == validator and store sender's option in Options
store(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>, txGovVote.Option)
if (sender != validator.address)
// Here, sender is not the Address of the validator whose Address is txGovVote.ValidatorAddress
if sender does not have bonded Atoms to txGovVote.ValidatorAddress then
// check in Staking module
throw
else
validatorOption = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>)
if (validatorOption == nil)
// Validator has not voted already
validatorGovInfo.Minus += sender.bondedAmounTo(txGovVote.ValidatorAddress)
store(ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorAddress>, validatorGovInfo)
else
// Validator has already voted
// Reduce votes of option chosen by validator by sender's bonded Amount
proposal.Votes.validatorOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress)
// increase votes of option chosen by sender by bonded Amount
senderOption = txGovVote.Option
propoal.Votes.senderOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress)
store(Proposals, txGovVote.ProposalID, proposal)
else
// sender is the address of the validator whose main Address is txGovVote.ValidatorAddress
// i.e. sender == validator
proposal.Votes.validatorOption += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus)
store(Proposals, txGovVote.ProposalID, proposal)
```

31
docs/spec/ibc/ibc.md Normal file
View File

@ -0,0 +1,31 @@
# IBC Specification
IBC(Inter-Blockchain Communication) protocol is used by multiple zones on Cosmos. Using IBC, the zones can send coins or arbitrary data to other zones.
## Terms
How IBC module treats incoming IBC packets is simillar with how BaseApp treats incoming transactions. Therefore, the components of IBC module have their corresponding pair in BaseApp.
| BaseApp Terms | IBC Terms |
| ------------- | ---------- |
| Router | Dispatcher |
| Tx | Packet |
| Msg | Payload |
## MVP Specifications
### [MVP1](./mvp1.md)
MVP1 will contain the basic functionalities, including packet generation and packet receivement. There will be no security check for incoming packets.
### [MVP2](./mvp2.md)
IBC module will be more modular in MVP2. Indivisual modules can register custom handlers to IBC module.
### [MVP3](./mvp3.md)
Light client verification is added to verify the message from the other chain. Registering chains with their ROT(Root Of Trust) is needed.
### [MVP4](./mvp4.md)
ACK verification / timeout handler helper functions and messaging queue are implemented to make it failsafe. Callbacks will be registered to the dispatcher to handle failure when they register handlers.

76
docs/spec/ibc/mvp1.md Normal file
View File

@ -0,0 +1,76 @@
# IBC Spec
*This is a living document and should be edited as the IBC spec and
implementation change*
## MVP1
The initial implementation of IBC will include just enough for simple coin
transfers between chains, with safety features such as ACK messages being added
later.
It is a complete stand-alone module. It includes the commands to send IBC
packets as well as to post them to the destination chain.
### IBC Module
```go
// User facing API
type IBCPacket struct {
SrcAddr sdk.Address
DestAddr sdk.Address
Coins sdk.Coins
SrcChain string
DestChain string
}
// Implements sdk.Msg
type IBCTransferMsg struct {
IBCPacket
}
// Implements sdk.Msg
type IBCReceiveMsg struct {
IBCPacket
Relayer sdk.Address
Sequence int64
}
// Internal API
type IBCMapper struct {
ibcKey sdk.StoreKey // IngressKey / EgressKey => Value
// Ingress: Source Chain ID => last income msg's sequence
// Egress: (Dest chain ID, Msg index) => length / indexed msg
}
type IngressKey struct {
SrcChain string
}
type EgressKey struct {
DestChain string
Index int64
}
```
`egressKey` stores the outgoing `IBCTransfer`s as a list. Its getter takes an
`EgressKey` and returns the length if `egressKey.Index == -1`, an element if
`egressKey.Index > 0`.
`ingressKey` stores the latest income `IBCTransfer`'s sequence. It's getter
takes an `IngressKey`.
## Relayer
**Packets**
- Connect to 2 Tendermint RPC endpoints
- Query for IBC outgoing `IBCOutMsg` queue (can poll on a certain time interval, or check after each new block, etc)
- For any new `IBCOutMsg`, build `IBCInMsg` and post to destination chain
## CLI
- Load relay process
- Execute `IBCOutMsg`

89
docs/spec/ibc/mvp2.md Normal file
View File

@ -0,0 +1,89 @@
# IBC Spec
*This is a living document and should be edited as the IBC spec and implementation change*
## MVP2
IBC module will store its own router for handling custom incoming msgs. `IBCPush` are made for inter-module communication. `IBCRegisterMsg` adds a handler in the router of the module.
### IBC Module
```golang
// User facing API
type Packet struct {
Data Payload
SrcChain string
DestChain string
}
type Payload interface {
Type() string
ValidateBasic() sdk.Error
}
type TransferPayload struct {
SrcAddr sdk.Address
DestAddr sdk.Address
Coins sdk.Coins
}
// Implements sdk.Msg
type IBCTransferMsg struct {
Packet
}
// Implements sdk.Msg
type IBCReceiveMsg struct {
Packet
Relayer sdk.Address
Sequence int64
}
// Internal API
type rule struct {
r string
f func(sdk.Context, IBCPacket) sdk.Result
}
type Dispatcher struct {
rules []rule
}
func NewHandler(dispatcher Dispatcher, ibcm IBCMapper) sdk.Handler
type IBCMapper struct {
ibcKey sdk.StoreKey // IngressKey / EgressKey => Value
// Ingress: Source Chain ID => last income msg's sequence
// Egress: (Dest chain ID, Msg index) => length / indexed msg
}
type IngressKey struct {
SrcChain string
}
type EgressKey struct {
DestChain string
Index int64
}
// Used by other modules
func (ibcm IBCMapper) PushPacket(ctx sdk.Context, dest string, payload Payload)
```
`egressKey` stores the outgoing `IBCTransfer`s as a list. Its getter takes an `EgressKey` and returns the length if `egressKey.Index == -1`, an element if `egressKey.Index > 0`.
`ingressKey` stores the last income `IBCTransfer`'s sequence. Its getter takes an `IngressKey`.
## Relayer
**Packets**
- Connect to 2 Tendermint RPC endpoints
- Query for IBC outgoing `IBCOutMsg` queue (can poll on a certain time interval, or check after each new block, etc)
- For any new `IBCOutMsg`, build `IBCInMsg` and post to destination chain
## CLI
- Load relay process
- Execute `IBCOutMsg`

111
docs/spec/ibc/mvp3.md Normal file
View File

@ -0,0 +1,111 @@
# IBC Spec
## MVP3
`IBCOpenMsg` is added to open the connection between two chains. Also, `IBCUpdateMsg` is added, making it able to prove the header.
### IBC Module
// Implements sdk.Msg
type IBCTransferMsg struct {
Packet
}
// Implements sdk.Msg
type IBCReceiveMsg struct {
Packet
}
// Internal API
```golang
// User facing API
type Packet struct {
Data Payload
SrcChain string
DestChain string
}
type Payload interface {
Type() string
ValidateBasic() sdk.Error
}
type TransferPayload struct {
SrcAddr sdk.Address
DestAddr sdk.Address
Coins sdk.Coins
}
// Implements sdk.Msg
type IBCTransferMsg struct {
Packet
}
// Implements sdk.Msg
type IBCReceiveMsg struct {
Packet
Proof iavl.Proof
FromChainID string
FromChainHeight uint64
}
type RootOfTrust struct {
//
}
// Implements sdk.Msg
type IBCOpenMsg struct {
ROT RootOfTrust
Chain string
}
// Implements sdk.Msg
type IBCUpdateMsg struct {
Header tm.Header
Commit tm.Commit
}
// Internal API
type rule struct {
r string
f func(sdk.Context, IBCPacket) sdk.Result
}
type Dispatcher struct {
rules []rule
}
func NewHandler(dispatcher Dispatcher, ibcm IBCMapper) sdk.Handler
type IBCMapper struct {
ibcKey sdk.StoreKey // IngressKey / EgressKey / HeaderKey => Value
// ChannelID => last income msg's sequence
// (ChannelID, Msg index) => length / indexed msg
// ChannelID => last known header
}
type IngressKey struct {
ChannelID uint64
}
type EgressKey struct {
ChannelID uint64
Index int64
}
type HeaderKey struct {
ChannelID uint64
}
// Used by other modules
func (ibcm IBCMapper) PushPacket(ctx sdk.Context, dest string, payload Payload)
```

View File

@ -2,113 +2,183 @@
## Basic Terms and Definitions
- Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system
- Atom - native token of the Cosmsos Hub
- Atom holder - an entity that holds some amount of Atoms
- Candidate - an Atom holder that is actively involved in Tendermint blockchain protocol (running Tendermint Full Node
TODO: add link to Full Node definition) and is competing with other candidates to be elected as a Validator
(TODO: add link to Validator definition))
- Validator - a candidate that is currently selected among a set of candidates to be able to sign protocol messages
in the Tendermint consensus protocol
- Delegator - an Atom holder that has bonded any of its Atoms by delegating them to a validator (or a candidate)
- Bonding Atoms - a process of locking Atoms in a bond deposit (putting Atoms under protocol control).
Atoms are always bonded through a validator (or candidate) process. Bonded atoms can be slashed (burned) in case a
validator process misbehaves (does not behave according to a protocol specification). Atom holder can regain access
to its bonded Atoms (if they are not slashed in the meantime), i.e., they can be moved to its account,
after Unbonding period has expired.
- Unbonding period - a period of time after which Atom holder gains access to its bonded Atoms (they can be withdrawn
to a user account) or they can re-delegate
- Inflationary provisions - inflation is a process of increasing Atom supply. Atoms are being created in the process of
(Cosmos Hub) blocks creation. Owners of bonded atoms are rewarded for securing network with inflationary provisions
proportional to it's bonded Atom share.
- Transaction fees - transaction fee is a fee that is included in the Cosmsos Hub transactions. The fees are collected
by the current validator set and distributed among validators and delegators in proportion to it's bonded Atom share.
- Commission fee - a fee taken from the transaction fees by a validator for it's service
* Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system
* Atom - native token of the Cosmsos Hub
* Atom holder - an entity that holds some amount of Atoms
* Candidate - an Atom holder that is actively involved in the Tendermint
blockchain protocol (running Tendermint Full Node (TODO: add link to Full
Node definition) and is competing with other candidates to be elected as a
validator (TODO: add link to Validator definition))
* Validator - a candidate that is currently selected among a set of candidates
to be able to sign protocol messages in the Tendermint consensus protocol
* Delegator - an Atom holder that has bonded some of its Atoms by delegating
them to a validator (or a candidate)
* Bonding Atoms - a process of locking Atoms in a bond deposit (putting Atoms
under protocol control). Atoms are always bonded through a validator (or
candidate) process. Bonded atoms can be slashed (burned) in case a validator
process misbehaves (does not behave according to the protocol specification).
Atom holders can regain access to their bonded Atoms if they have not been
slashed by waiting an Unbonding period.
* Unbonding period - a period of time after which Atom holder gains access to
its bonded Atoms (they can be withdrawn to a user account) or they can be
re-delegated.
* Inflationary provisions - inflation is 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.
* 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 collection of Atoms contributed by different
Atom holders. There are two global pools in the Staking module: bonded pool and unbonded pool. Bonded Atoms are part
of the global bonded pool. On the other side, if a candidate or delegator wants to unbond its Atoms, those Atoms are
kept in the unbonding pool for a duration of the unbonding period. In the Staking module, a pool is logical concept,
i.e., there is no pool data structure that would be responsible for managing pool resources. Instead, it is managed
in a distributed way. More precisely, at the global level, for each pool, we track only the total amount of
(bonded or unbonded) Atoms and a current amount of issued shares. A share is a unit of Atom distribution and the
value of the share (share-to-atom exchange rate) is changing during the system execution. The
share-to-atom exchange rate can be computed as:
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 two global
pools in the Staking module: the bonded pool and unbonding pool. Bonded Atoms
are part of the global bonded pool. If a candidate or delegator wants to unbond
its Atoms, those Atoms are moved to the the unbonding pool for the duration of
the unbonding period. In the Staking module, a pool is a logical concept, i.e.,
there is no pool data structure that would be responsible for managing pool
resources. Instead, it is managed in a distributed way. More precisely, at the
global level, for each pool, we track only the total amount of bonded or unbonded
Atoms and the current amount of issued shares. A share is a unit of Atom distribution
and the value of the share (share-to-atom exchange rate) changes during
system execution. The share-to-atom exchange rate can be computed as:
`share-to-atom-ex-rate = size of the pool / ammount of issued shares`
`share-to-atom-exchange-rate = size of the pool / ammount of issued shares`
Then for each candidate (in a per candidate data structure) we keep track of an amount of shares the candidate is owning
in a pool. At any point in time, the exact amount of Atoms a candidate has in the pool
can be computed as the number of shares it owns multiplied with the share-to-atom exchange rate:
Then for each validator candidate (in a per candidate data structure) we keep track of
the amount of shares the candidate owns in a pool. At any point in time,
the exact amount of Atoms a candidate has in the pool can be computed as the
number of shares it owns multiplied with the current share-to-atom exchange rate:
`candidate-coins = candidate.Shares * share-to-atom-ex-rate`
`candidate-coins = candidate.Shares * share-to-atom-exchange-rate`
The benefit of such accounting of the pool resources is the fact that a modification to the pool because of
bonding/unbonding/slashing/provisioning of atoms affects only global data (size of the pool and the number of shares)
and the related validator/candidate data structure, i.e., the data structure of other validators do not need to be
modified. Let's explain this further with several small examples:
The benefit of such accounting of the pool resources is the fact that a
modification to the pool from bonding/unbonding/slashing/provisioning of
Atoms affects only global data (size of the pool and the number of shares) and
not the related validator/candidate 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:
We consider initially 4 validators p1, p2, p3 and p4, and that each validator has bonded 10 Atoms
to a 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.
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 the 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.
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.
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).
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 candidate is, depending on it's status, contributing Atoms to either the bonded or unbonded pool, and in return gets
some amount of (global) pool shares. Note that not all those Atoms (and respective shares) are owned by the candidate
as some Atoms could be delegated to a candidate. The mechanism for distribution of Atoms (and shares) between a
candidate and it's delegators is based on a notion of delegator shares. More precisely, every candidate is issuing
(local) delegator shares (`Candidate.IssuedDelegatorShares`) that represents some portion of global shares
managed by the candidate (`Candidate.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.
A candidate is, depending on it's status, contributing Atoms to either the
bonded or unbonding pool, and in return gets some amount of (global) pool
shares. Note that not all those Atoms (and respective shares) are owned by the
candidate as some Atoms could be delegated to a candidate. The mechanism for
distribution of Atoms (and shares) between a candidate and it's delegators is
based on a notion of delegator shares. More precisely, every candidate is
issuing (local) delegator shares (`Candidate.IssuedDelegatorShares`) that
represents some portion of global shares managed by the candidate
(`Candidate.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.
Lets consider 4 validators p1, p2, p3 and p4, and assume that each validator has bonded 10 Atoms to a bonded pool.
Furthermore, lets assume that we have issued initially 40 global shares, i.e., that `share-to-atom-ex-rate = 1 atom per
share`. So we will `GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the Candidate data structure
of each validator `Candidate.GlobalStakeShares = 10`. Furthermore, each validator issued 10 delegator
shares which are initially owned by itself, i.e., `Candidate.IssuedDelegatorShares = 10`, where
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
Candidate data structure of each validator `Candidate.GlobalStakeShares = 10`.
Furthermore, each validator issued 10 delegator shares which are initially
owned by itself, i.e., `Candidate.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 `Candidate.GlobalStakeShares = 15`, but we also need to issue also additional delegator shares,
i.e., `Candidate.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-ex-rate is now worth 50/45 Atoms per share. Therefore, a delegator d1 now owns
`delegatorCoins = 10 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 100/9 Atoms`
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 `Candidate.GlobalStakeShares = 15`, but we also need to
issue also additional delegator shares, i.e.,
`Candidate.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`
### Inflation provisions
Validator provisions are minted on an hourly basis (the first block of a new
hour). The annual target of between 7% and 20%. The long-term target ratio of
bonded tokens to unbonded tokens is 67%.
The target annual inflation rate is recalculated for each provisions cycle. The
inflation is also subject to a rate change (positive or negative) depending on
the distance from the desired ratio (67%). The maximum rate change possible is
defined to be 13% per year, however the annual inflation is capped as between
7% and 20%.
```go
inflationRateChange(0) = 0
GlobalState.Inflation(0) = 0.07
bondedRatio = GlobalState.BondedPool / GlobalState.TotalSupply
AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13
annualInflation += AnnualInflationRateChange
if annualInflation > 0.20 then GlobalState.Inflation = 0.20
if annualInflation < 0.07 then GlobalState.Inflation = 0.07
provisionTokensHourly = GlobalState.TotalSupply * GlobalState.Inflation / (365.25*24)
```
Because the validators hold a relative bonded share (`GlobalStakeShares`), when
more bonded tokens are added proportionally to all validators, the only term
which needs to be updated is the `GlobalState.BondedPool`. So for each
provisions cycle:
```go
GlobalState.BondedPool += provisionTokensHourly
```

View File

@ -2,54 +2,66 @@
## Overview
The Cosmos Hub is a Tendermint-based Proof of Stake 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 consensus is a
Byzantine fault-tolerant distributed protocol that involves all validators in the process of exchanging protocol
messages in the production of each block. To avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up
coins in a bond deposit. Tendermint protocol messages are signed by the validator's private key, and this is a basis for
Tendermint strict accountability that allows punishing misbehaving validators by slashing (burning) their bonded Atoms.
On the other hand, validators are for it's service of securing blockchain network rewarded by the inflationary
provisions and transactions fees. This incentivizes correct behavior of the validators and provide economic security
of the network.
The Cosmos Hub is a Tendermint-based Proof of Stake 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 consensus is a
Byzantine fault-tolerant distributed protocol that involves all validators in
the process of exchanging protocol messages in the production of each block. To
avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up
coins in a bond deposit. Tendermint protocol messages are signed by the
validator's private key, and this is a basis for Tendermint strict
accountability that allows punishing misbehaving validators 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 incentives correct behavior of the validators and
provides the economic security of the network.
The native token of the Cosmos Hub is called 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 validator candidates (Atom holders that
wants to become a validator). The other option for Atom holder is to delegate
their atoms to validators, i.e., being a delegator. A delegator is an Atom
holder that has bonded its Atoms by delegating it to a validator (or validator
candidate). 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.
The native token of the Cosmos Hub is called 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 validator candidates (Atom holder that wants to
become a validator). The other option for Atom holder is to delegate their atoms to validators, i.e.,
being a delegator. A delegator is an Atom holder that has bonded its Atoms by delegating it to a validator
(or validator candidate). By bonding Atoms to securing network (and taking a risk of being slashed in case the
validator misbehaves), 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.
## State
The staking module persists the following information to the store:
- `GlobalState`, describing the global pools and the inflation related fields
- `map[PubKey]Candidate`, a map of validator candidates (including current validators), indexed by public key
- `map[rational.Rat]Candidate`, an ordered map of validator candidates (including current validators), indexed by
shares in the global pool (bonded or unbonded depending on candidate status)
- `map[[]byte]DelegatorBond`, a map of DelegatorBonds (for each delegation to a candidate by a delegator), indexed by
the delegator address and the candidate public key
- `queue[QueueElemUnbondDelegation]`, a queue of unbonding delegations
- `queue[QueueElemReDelegate]`, a queue of re-delegations
* `GlobalState`, describing the global pools and the inflation related fields
* validator candidates (including current validators), indexed by public key and shares in the global pool
(bonded or unbonded depending on candidate status)
* delegator bonds (for each delegation to a candidate by a delegator), indexed by the delegator address and the candidate
public key
* the queue of unbonding delegations
* the queue of re-delegations
### Global State
GlobalState data structure contains total Atoms supply, amount of Atoms in the bonded pool, sum of all shares
distributed for the bonded pool, amount of Atoms in the unbonded pool, sum of all shares distributed for the
unbonded pool, a timestamp of the last processing of inflation, the current annual inflation rate, a timestamp
for the last comission accounting reset, the global fee pool, a pool of reserve taxes collected for the governance use
and an adjustment factor for calculating global feel accum (?).
``` golang
The GlobalState data structure contains total Atom supply, amount of Atoms in
the bonded pool, sum of all shares distributed for the bonded pool, amount of
Atoms in the unbonded pool, sum of all shares distributed for the unbonded
pool, a timestamp of the last processing of inflation, the current annual
inflation rate, a timestamp for the last comission accounting reset, the global
fee pool, a pool of reserve taxes collected for the governance use and an
adjustment factor for calculating global fee accum. `Params` is global data
structure that stores system parameters and defines overall functioning of the
module.
``` go
type GlobalState struct {
TotalSupply int64 // total supply of Atoms
BondedPool int64 // reserve of bonded tokens
BondedShares rational.Rat // sum of all shares distributed for the BondedPool
UnbondedPool int64 // reserve of unbonded tokens held with candidates
UnbondedPool int64 // reserve of unbonding tokens held with candidates
UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool
InflationLastTime int64 // timestamp of last processing of inflation
Inflation rational.Rat // current annual inflation rate
@ -58,19 +70,40 @@ type GlobalState struct {
ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use
Adjustment rational.Rat // Adjustment factor for calculating global fee accum
}
type Params struct {
HoldBonded Address // account where all bonded coins are held
HoldUnbonding Address // account where all delegated but unbonding coins are held
InflationRateChange rational.Rational // maximum annual change in inflation rate
InflationMax rational.Rational // maximum inflation rate
InflationMin rational.Rational // minimum inflation rate
GoalBonded rational.Rational // Goal of percent bonded atoms
ReserveTax rational.Rational // Tax collected on all fees
MaxVals uint16 // maximum number of validators
AllowedBondDenom string // bondable coin denomination
// gas costs for txs
GasDeclareCandidacy int64
GasEditCandidacy int64
GasDelegate int64
GasRedelegate int64
GasUnbond int64
}
```
### Candidate
The `Candidate` data structure holds the current state and some historical actions of
validators or candidate-validators.
The `Candidate` data structure holds the current state and some historical
actions of validators or candidate-validators.
``` golang
``` go
type Candidate struct {
Status CandidateStatus
PubKey crypto.PubKey
ConsensusPubKey crypto.PubKey
GovernancePubKey crypto.PubKey
Owner Address
Owner crypto.Address
GlobalStakeShares rational.Rat
IssuedDelegatorShares rational.Rat
RedelegatingShares rational.Rat
@ -83,118 +116,115 @@ type Candidate struct {
Adjustment rational.Rat
Description Description
}
```
CandidateStatus can be VyingUnbonded, VyingUnbonding, Bonded, KickUnbonding and KickUnbonded.
``` golang
type Description struct {
Name string
DateBonded string
Identity string
Website string
Details string
Name string
DateBonded string
Identity string
Website string
Details string
}
```
Candidate parameters are described:
- Status: signal that the candidate is either vying for validator status,
either unbonded or unbonding, an active validator, or a kicked validator
either unbonding or unbonded.
- PubKey: separated key from the owner of the candidate as is used strictly
for participating in consensus.
- Owner: Address where coins are bonded from and unbonded to
- GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if
`Candidate.Status` is `Bonded`; or shares of `GlobalState.UnbondedPool` otherwise
- IssuedDelegatorShares: Sum of all shares a candidate issued to delegators (which
includes the candidate's self-bond); a delegator share represents their stake in
the Candidate's `GlobalStakeShares`
- RedelegatingShares: The portion of `IssuedDelegatorShares` which are
currently re-delegating to a new validator
- VotingPower: Proportional to the amount of bonded tokens which the validator
has if the candidate is a validator.
- Commission: The commission rate of fees charged to any delegators
- CommissionMax: The maximum commission rate this candidate can charge
each day from the date `GlobalState.DateLastCommissionReset`
- CommissionChangeRate: The maximum daily increase of the candidate commission
- CommissionChangeToday: Counter for the amount of change to commission rate
which has occurred today, reset on the first block of each day (UTC time)
- ProposerRewardPool: reward pool for extra fees collected when this candidate
is the proposer of a block
- Adjustment factor used to passively calculate each validators entitled fees
from `GlobalState.FeePool`
- Description
- Name: moniker
- DateBonded: date determined which the validator was bonded
- Identity: optional field to provide a signature which verifies the
validators identity (ex. UPort or Keybase)
- Website: optional website link
- Details: optional details
* Status: it can be Bonded (active validator), Unbonding (validator candidate)
or Revoked
* ConsensusPubKey: candidate public key that is used strictly for participating in
consensus
* GovernancePubKey: public key used by the validator for governance voting
* Owner: Address that is allowed to unbond coins.
* GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if
`Candidate.Status` is `Bonded`; or shares of `GlobalState.Unbondingt Pool`
otherwise
* IssuedDelegatorShares: Sum of all shares a candidate issued to delegators
(which includes the candidate's self-bond); a delegator share represents
their stake in the Candidate's `GlobalStakeShares`
* RedelegatingShares: The portion of `IssuedDelegatorShares` which are
currently re-delegating to a new validator
* VotingPower: Proportional to the amount of bonded tokens which the validator
has if `Candidate.Status` is `Bonded`; otherwise it is equal to `0`
* Commission: The commission rate of fees charged to any delegators
* CommissionMax: The maximum commission rate this candidate can charge each
day from the date `GlobalState.DateLastCommissionReset`
* CommissionChangeRate: The maximum daily increase of the candidate commission
* CommissionChangeToday: Counter for the amount of change to commission rate
which has occurred today, reset on the first block of each day (UTC time)
* ProposerRewardPool: reward pool for extra fees collected when this candidate
is the proposer of a block
* Adjustment factor used to passively calculate each validators entitled fees
from `GlobalState.FeePool`
* Description
* Name: moniker
* DateBonded: date determined which the validator was bonded
* Identity: optional field to provide a signature which verifies the
validators identity (ex. UPort or Keybase)
* Website: optional website link
* Details: optional details
### DelegatorBond
Atom holders may delegate coins to validators; under this circumstance their
funds are held in a `DelegatorBond` data structure. It is owned by one delegator, and is
associated with the shares for one validator. The sender of the transaction is
considered the owner of the bond.
Atom holders may delegate coins to candidates; under this circumstance their
funds are held in a `DelegatorBond` data structure. It is owned by one
delegator, and is associated with the shares for one candidate. The sender of
the transaction is the owner of the bond.
``` golang
``` go
type DelegatorBond struct {
Candidate crypto.PubKey
Shares rational.Rat
Candidate crypto.PubKey
Shares rational.Rat
AdjustmentFeePool coin.Coins
AdjustmentRewardPool coin.Coins
}
```
Description:
- Candidate: the public key of the validator candidate: bonding too
- Shares: the number of delegator shares received from the validator candidate
- AdjustmentFeePool: Adjustment factor used to passively calculate each bonds
entitled fees from `GlobalState.FeePool`
- AdjustmentRewardPool: Adjustment factor used to passively calculate each
bonds entitled fees from `Candidate.ProposerRewardPool``
* Candidate: the public key of the validator candidate: bonding too
* Shares: the number of delegator shares received from the validator candidate
* AdjustmentFeePool: Adjustment factor used to passively calculate each bonds
entitled fees from `GlobalState.FeePool`
* AdjustmentRewardPool: Adjustment factor used to passively calculate each
bonds entitled fees from `Candidate.ProposerRewardPool`
### QueueElem
Unbonding and re-delegation process is implemented using the ordered queue data structure.
All queue elements used share a common structure:
The Unbonding and re-delegation process is implemented using the ordered queue
data structure. All queue elements share a common structure:
``` golang
```golang
type QueueElem struct {
Candidate crypto.PubKey
InitHeight int64 // when the queue was initiated
Candidate crypto.PubKey
InitTime int64 // when the element was added to the queue
}
```
The queue is ordered so the next to unbond/re-delegate is at the head. Every
tick the head of the queue is checked and if the unbonding period has passed
since `InitHeight`, the final settlement of the unbonding is started or re-delegation is executed, and the element is
pop from the queue. Each `QueueElem` is persisted in the store until it is popped from the queue.
The queue is ordered so the next element to unbond/re-delegate is at the head.
Every tick the head of the queue is checked and if the unbonding period has
passed since `InitTime`, the final settlement of the unbonding is started or
re-delegation is executed, and the element is popped from the queue. Each
`QueueElem` is persisted in the store until it is popped from the queue.
### QueueElemUnbondDelegation
``` golang
QueueElemUnbondDelegation structure is used in the unbonding queue.
```golang
type QueueElemUnbondDelegation struct {
QueueElem
Payout Address // account to pay out to
Shares rational.Rat // amount of delegator shares which are unbonding
StartSlashRatio rational.Rat // candidate slash ratio at start of re-delegation
QueueElem
Payout Address // account to pay out to
Tokens coin.Coins // the value in Atoms of the amount of delegator shares which are unbonding
StartSlashRatio rational.Rat // candidate slash ratio
}
```
In the unbonding queue - the fraction of all historical slashings on
that validator are recorded (`StartSlashRatio`). When this queue reaches maturity
if that total slashing applied is greater on the validator then the
difference (amount that should have been slashed from the first validator) is
assigned to the amount being paid out.
### QueueElemReDelegate
``` golang
QueueElemReDelegate structure is used in the re-delegation queue.
```golang
type QueueElemReDelegate struct {
QueueElem
Payout Address // account to pay out to
QueueElem
Payout Address // account to pay out to
Shares rational.Rat // amount of shares which are unbonding
NewCandidate crypto.PubKey // validator to bond to after unbond
}
@ -203,31 +233,38 @@ type QueueElemReDelegate struct {
### Transaction Overview
Available Transactions:
- TxDeclareCandidacy
- TxEditCandidacy
- TxLivelinessCheck
- TxProveLive
- TxDelegate
- TxUnbond
- TxRedelegate
* TxDeclareCandidacy
* TxEditCandidacy
* TxDelegate
* TxUnbond
* TxRedelegate
* TxLivelinessCheck
* TxProveLive
## Transaction processing
In this section we describe the processing of the transactions and the corresponding updates to the global state.
For the following text we will use gs to refer to the GlobalState data structure, candidateMap is a reference to the
map[PubKey]Candidate, delegatorBonds is a reference to map[[]byte]DelegatorBond, unbondDelegationQueue is a
reference to the queue[QueueElemUnbondDelegation] and redelegationQueue is the reference for the
queue[QueueElemReDelegate]. We use tx to denote reference to a transaction that is being processed.
In this section we describe the processing of the transactions and the
corresponding updates to the global state. In the following text we will use
`gs` to refer to the `GlobalState` data structure, `unbondDelegationQueue` is a
reference to the queue of unbond delegations, `reDelegationQueue` is the
reference for the queue of redelegations. We use `tx` to denote a
reference to a transaction that is being processed, and `sender` to denote the
address of the sender of the transaction. We use function
`loadCandidate(store, PubKey)` to obtain a Candidate structure from the store,
and `saveCandidate(store, candidate)` to save it. Similarly, we use
`loadDelegatorBond(store, sender, PubKey)` to load a delegator bond with the
key (sender and PubKey) from the store, and
`saveDelegatorBond(store, sender, bond)` to save it.
`removeDelegatorBond(store, sender, bond)` is used to remove the bond from the
store.
### TxDeclareCandidacy
A validator candidacy can be declared using the `TxDeclareCandidacy` transaction.
During this transaction a self-delegation transaction is executed to bond
tokens which are sent in with the transaction (TODO: What does this mean?).
A validator candidacy is declared using the `TxDeclareCandidacy` transaction.
``` golang
```golang
type TxDeclareCandidacy struct {
PubKey crypto.PubKey
ConsensusPubKey crypto.PubKey
Amount coin.Coin
GovernancePubKey crypto.PubKey
Commission rational.Rat
@ -235,28 +272,25 @@ type TxDeclareCandidacy struct {
CommissionMaxChange int64
Description Description
}
```
```
declareCandidacy(tx TxDeclareCandidacy):
// create and save the empty candidate
candidate = loadCandidate(store, tx.PubKey)
if candidate != nil then return
if candidate != nil return // candidate with that public key already exists
candidate = NewCandidate(tx.PubKey)
candidate.Status = Unbonded
candidate.Owner = sender
init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares,RedelegatingShares and Adjustment to rational.Zero
init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares, RedelegatingShares and Adjustment to rational.Zero
init commision related fields based on the values from tx
candidate.ProposerRewardPool = Coin(0)
candidate.Description = tx.Description
saveCandidate(store, candidate)
// move coins from the sender account to a (self-bond) delegator account
// the candidate account and global shares are updated within here
txDelegate = TxDelegate{tx.BondUpdate}
return delegateWithCandidate(txDelegate, candidate)
txDelegate = TxDelegate(tx.PubKey, tx.Amount)
return delegateWithCandidate(txDelegate, candidate)
// see delegateWithCandidate function in [TxDelegate](TxDelegate)
```
### TxEditCandidacy
@ -265,221 +299,326 @@ If either the `Description` (excluding `DateBonded` which is constant),
`Commission`, or the `GovernancePubKey` need to be updated, the
`TxEditCandidacy` transaction should be sent from the owner account:
``` golang
```golang
type TxEditCandidacy struct {
GovernancePubKey crypto.PubKey
Commission int64
Description Description
}
```
```
editCandidacy(tx TxEditCandidacy):
candidate = loadCandidate(store, tx.PubKey)
if candidate == nil or candidate.Status == Unbonded return
if tx.GovernancePubKey != nil then candidate.GovernancePubKey = tx.GovernancePubKey
if tx.Commission >= 0 then candidate.Commission = tx.Commission
if tx.Description != nil then candidate.Description = tx.Description
if candidate == nil or candidate.Status == Revoked return
if tx.GovernancePubKey != nil candidate.GovernancePubKey = tx.GovernancePubKey
if tx.Commission >= 0 candidate.Commission = tx.Commission
if tx.Description != nil candidate.Description = tx.Description
saveCandidate(store, candidate)
return
```
```
### TxDelegate
All bonding, whether self-bonding or delegation, is done via `TxDelegate`.
Delegator bonds are created using the `TxDelegate` transaction. Within this
transaction the delegator provides an amount of coins, and in return receives
some amount of candidate's delegator shares that are assigned to
`DelegatorBond.Shares`.
Delegator bonds are created using the `TxDelegate` transaction. Within this transaction the delegator provides
an amount of coins, and in return receives some amount of candidate's delegator shares that are assigned to
`DelegatorBond.Shares`. The amount of created delegator shares depends on the candidate's
delegator-shares-to-atoms exchange rate and is computed as
`delegator-shares = delegator-coins / delegator-shares-to-atom-ex-rate`.
``` golang
type TxDelegate struct {
PubKey crypto.PubKey
Amount coin.Coin
```golang
type TxDelegate struct {
PubKey crypto.PubKey
Amount coin.Coin
}
```
```
delegate(tx TxDelegate):
candidate = loadCandidate(store, tx.PubKey)
if candidate == nil then return
if candidate == nil return
return delegateWithCandidate(tx, candidate)
delegateWithCandidate(tx TxDelegate, candidate Candidate):
if candidate.Status == Revoked then return
if candidate.Status == Revoked return
if candidate.Status == Bonded then
poolAccount = address of the bonded pool
else
poolAccount = address of the unbonded pool
if candidate.Status == Bonded
poolAccount = params.HoldBonded
else
poolAccount = params.HoldUnbonded
// Move coins from the delegator account to the bonded pool account
err = transfer(sender, poolAccount, tx.Amount)
if err != nil then return
err = transfer(sender, poolAccount, tx.Amount)
if err != nil return
// Get or create the delegator bond
bond = loadDelegatorBond(store, sender, tx.PubKey)
if bond == nil then
bond = DelegatorBond{tx.PubKey,rational.Zero, Coin(0), Coin(0)}
bond = loadDelegatorBond(store, sender, tx.PubKey)
if bond == nil then bond = DelegatorBond(tx.PubKey, rational.Zero, Coin(0), Coin(0))
issuedDelegatorShares = candidate.addTokens(tx.Amount, gs)
bond.Shares = bond.Shares.Add(issuedDelegatorShares)
issuedDelegatorShares = addTokens(tx.Amount, candidate)
bond.Shares += issuedDelegatorShares
saveCandidate(store, candidate)
store.Set(GetDelegatorBondKey(sender, bond.PubKey), bond)
saveGlobalState(store, gs)
return
saveCandidate(store, candidate)
saveDelegatorBond(store, sender, bond)
saveGlobalState(store, gs)
return
addTokens(amount int64, gs GlobalState, candidate Candidate):
// get the exchange rate of global pool shares over delegator shares
if candidate.IssuedDelegatorShares.IsZero() then
addTokens(amount coin.Coin, candidate Candidate):
if candidate.Status == Bonded
gs.BondedPool += amount
issuedShares = amount / exchangeRate(gs.BondedShares, gs.BondedPool)
gs.BondedShares += issuedShares
else
gs.UnbondedPool += amount
issuedShares = amount / exchangeRate(gs.UnbondedShares, gs.UnbondedPool)
gs.UnbondedShares += issuedShares
candidate.GlobalStakeShares += issuedShares
if candidate.IssuedDelegatorShares.IsZero()
exRate = rational.One
else
exRate = candiate.GlobalStakeShares.Quo(candidate.IssuedDelegatorShares)
if candidate.Status == Bonded then
gs.BondedPool += amount
issuedShares = exchangeRate(gs.BondedShares, gs.BondedPool).Inv().Mul(amount) // (tokens/shares)^-1 * tokens
gs.BondedShares = gs.BondedShares.Add(issuedShares)
else
gs.UnbondedPool += amount
issuedShares = exchangeRate(gs.UnbondedShares, gs.UnbondedPool).Inv().Mul(amount) // (tokens/shares)^-1 * tokens
gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares)
exRate = candidate.GlobalStakeShares / candidate.IssuedDelegatorShares
candidate.GlobalStakeShares = candidate.GlobalStakeShares.Add(issuedShares)
issuedDelegatorShares = exRate.Mul(receivedGlobalShares)
candidate.IssuedDelegatorShares = candidate.IssuedDelegatorShares.Add(issuedDelegatorShares)
return
issuedDelegatorShares = issuedShares / exRate
candidate.IssuedDelegatorShares += issuedDelegatorShares
return issuedDelegatorShares
exchangeRate(shares rational.Rat, tokenAmount int64):
if shares.IsZero() then return rational.One
return shares.Inv().Mul(tokenAmount)
return tokenAmount / shares
```
### TxUnbond
Delegator unbonding is defined with the following transaction:
``` golang
type TxUnbond struct {
PubKey crypto.PubKey
Shares rational.Rat
```golang
type TxUnbond struct {
PubKey crypto.PubKey
Shares rational.Rat
}
unbond(tx TxUnbond):
bond = loadDelegatorBond(store, sender, tx.PubKey)
if bond == nil return
if bond.Shares < tx.Shares return
bond.Shares -= tx.Shares
candidate = loadCandidate(store, tx.PubKey)
revokeCandidacy = false
if bond.Shares.IsZero()
if sender == candidate.Owner and candidate.Status != Revoked then revokeCandidacy = true then removeDelegatorBond(store, sender, bond)
else
saveDelegatorBond(store, sender, bond)
if candidate.Status == Bonded
poolAccount = params.HoldBonded
else
poolAccount = params.HoldUnbonded
returnedCoins = removeShares(candidate, shares)
unbondDelegationElem = QueueElemUnbondDelegation(tx.PubKey, currentHeight(), sender, returnedCoins, startSlashRatio)
unbondDelegationQueue.add(unbondDelegationElem)
transfer(poolAccount, unbondingPoolAddress, returnCoins)
if revokeCandidacy
if candidate.Status == Bonded then bondedToUnbondedPool(candidate)
candidate.Status = Revoked
if candidate.IssuedDelegatorShares.IsZero()
removeCandidate(store, tx.PubKey)
else
saveCandidate(store, candidate)
saveGlobalState(store, gs)
return
removeShares(candidate Candidate, shares rational.Rat):
globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares
if candidate.Status == Bonded
gs.BondedShares -= globalPoolSharesToRemove
removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * globalPoolSharesToRemove
gs.BondedPool -= removedTokens
else
gs.UnbondedShares -= globalPoolSharesToRemove
removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * globalPoolSharesToRemove
gs.UnbondedPool -= removedTokens
candidate.GlobalStakeShares -= removedTokens
candidate.IssuedDelegatorShares -= shares
return returnedCoins
delegatorShareExRate(candidate Candidate):
if candidate.IssuedDelegatorShares.IsZero() then return rational.One
return candidate.GlobalStakeShares / candidate.IssuedDelegatorShares
bondedToUnbondedPool(candidate Candidate):
removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * candidate.GlobalStakeShares
gs.BondedShares -= candidate.GlobalStakeShares
gs.BondedPool -= removedTokens
gs.UnbondedPool += removedTokens
issuedShares = removedTokens / exchangeRate(gs.UnbondedShares, gs.UnbondedPool)
gs.UnbondedShares += issuedShares
candidate.GlobalStakeShares = issuedShares
candidate.Status = Unbonded
return transfer(address of the bonded pool, address of the unbonded pool, removedTokens)
```
### TxRedelegate
The re-delegation command allows delegators to switch validators while still
receiving equal reward to as if they had never unbonded.
```golang
type TxRedelegate struct {
PubKeyFrom crypto.PubKey
PubKeyTo crypto.PubKey
Shares rational.Rat
}
redelegate(tx TxRedelegate):
bond = loadDelegatorBond(store, sender, tx.PubKey)
if bond == nil then return
if bond.Shares < tx.Shares return
candidate = loadCandidate(store, tx.PubKeyFrom)
if candidate == nil return
candidate.RedelegatingShares += tx.Shares
reDelegationElem = QueueElemReDelegate(tx.PubKeyFrom, currentHeight(), sender, tx.Shares, tx.PubKeyTo)
redelegationQueue.add(reDelegationElem)
return
```
### TxLivelinessCheck
Liveliness issues are calculated by keeping track of the block precommits in
the block header. A queue is persisted which contains the block headers from
all recent blocks for the duration of the unbonding period. A validator is
defined as having livliness issues if they have not been included in more than
33% of the blocks over:
* The most recent 24 Hours if they have >= 20% of global stake
* The most recent week if they have = 0% of global stake
* Linear interpolation of the above two scenarios
Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is
submitted.
```golang
type TxLivelinessCheck struct {
PubKey crypto.PubKey
RewardAccount Addresss
}
```
If the `TxLivelinessCheck` is successful in kicking a validator, 5% of the
liveliness punishment is provided as a reward to `RewardAccount`.
### TxProveLive
If the validator was kicked for liveliness issues and is able to regain
liveliness then all delegators in the temporary unbonding pool which have not
transacted to move will be bonded back to the now-live validator and begin to
once again collect provisions and rewards. Regaining liveliness is demonstrated
by sending in a `TxProveLive` transaction:
```golang
type TxProveLive struct {
PubKey crypto.PubKey
}
```
unbond(tx TxUnbond):
// get delegator bond
bond = loadDelegatorBond(store, sender, tx.PubKey)
if bond == nil then return
### End of block handling
// subtract bond tokens from delegator bond
if bond.Shares.LT(tx.Shares) return // bond shares < tx shares
bond.Shares = bond.Shares.Sub(ts.Shares)
candidate = loadCandidate(store, tx.PubKey)
if candidate == nil return
revokeCandidacy = false
if bond.Shares.IsZero() {
// if the bond is the owner of the candidate then trigger a revoke candidacy
if sender.Equals(candidate.Owner) and candidate.Status != Revoked then
revokeCandidacy = true
// remove the bond
removeDelegatorBond(store, sender, tx.PubKey)
else
saveDelegatorBond(store, sender, bond)
// transfer coins back to account
if candidate.Status == Bonded then
poolAccount = address of the bonded pool
else
poolAccount = address of the unbonded pool
returnCoins = candidate.removeShares(shares, gs)
// TODO: Shouldn't it be created a queue element in this case?
transfer(poolAccount, sender, returnCoins)
if revokeCandidacy then
// change the share types to unbonded if they were not already
if candidate.Status == Bonded then
// replace bonded shares with unbonded shares
tokens = gs.removeSharesBonded(candidate.GlobalStakeShares)
candidate.GlobalStakeShares = gs.addTokensUnbonded(tokens)
candidate.Status = Unbonded
```golang
tick(ctx Context):
hrsPerYr = 8766 // as defined by a julian year of 365.25 days
time = ctx.Time()
if time > gs.InflationLastTime + ProvisionTimeout
gs.InflationLastTime = time
gs.Inflation = nextInflation(hrsPerYr).Round(1000000000)
transfer(address of the bonded pool, address of the unbonded pool, tokens)
// lastly update the status
candidate.Status = Revoked
provisions = gs.Inflation * (gs.TotalSupply / hrsPerYr)
gs.BondedPool += provisions
gs.TotalSupply += provisions
saveGlobalState(store, gs)
if time > unbondDelegationQueue.head().InitTime + UnbondingPeriod
for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do
transfer(unbondingQueueAddress, elem.Payout, elem.Tokens)
unbondDelegationQueue.remove(elem)
if time > reDelegationQueue.head().InitTime + UnbondingPeriod
for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do
candidate = getCandidate(store, elem.PubKey)
returnedCoins = removeShares(candidate, elem.Shares)
candidate.RedelegatingShares -= elem.Shares
delegateWithCandidate(TxDelegate(elem.NewCandidate, returnedCoins), candidate)
reDelegationQueue.remove(elem)
return UpdateValidatorSet()
// deduct shares from the candidate and save
if candidate.GlobalStakeShares.IsZero() then
removeCandidate(store, tx.PubKey)
else
saveCandidate(store, candidate)
nextInflation(hrsPerYr rational.Rat):
if gs.TotalSupply > 0
bondedRatio = gs.BondedPool / gs.TotalSupply
else
bondedRation = 0
inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange
inflationRateChange = inflationRateChangePerYear / hrsPerYr
saveGlobalState(store, gs)
return
inflation = gs.Inflation + inflationRateChange
if inflation > params.InflationMax then inflation = params.InflationMax
removeDelegatorBond(candidate Candidate):
if inflation < params.InflationMin then inflation = params.InflationMin
return inflation
// first remove from the list of bonds
pks = loadDelegatorCandidates(store, sender)
for i, pk := range pks {
if candidate.Equals(pk) {
pks = append(pks[:i], pks[i+1:]...)
}
}
b := wire.BinaryBytes(pks)
store.Set(GetDelegatorBondsKey(delegator), b)
UpdateValidatorSet():
candidates = loadCandidates(store)
// now remove the actual bond
store.Remove(GetDelegatorBondKey(delegator, candidate))
//updateDelegatorBonds(store, delegator)
}
```
v1 = candidates.Validators()
v2 = updateVotingPower(candidates).Validators()
### Inflation provisions
change = v1.validatorsUpdated(v2) // determine all updated validators between two validator sets
return change
Validator provisions are minted on an hourly basis (the first block of a new
hour). The annual target of between 7% and 20%. The long-term target ratio of
bonded tokens to unbonded tokens is 67%.
The target annual inflation rate is recalculated for each previsions cycle. The
inflation is also subject to a rate change (positive of negative) depending or
the distance from the desired ratio (67%). The maximum rate change possible is
defined to be 13% per year, however the annual inflation is capped as between
7% and 20%.
updateVotingPower(candidates Candidates):
foreach candidate in candidates do
candidate.VotingPower = (candidate.IssuedDelegatorShares - candidate.RedelegatingShares) * delegatorShareExRate(candidate)
candidates.Sort()
foreach candidate in candidates do
if candidate is not in the first params.MaxVals
candidate.VotingPower = rational.Zero
if candidate.Status == Bonded then bondedToUnbondedPool(candidate Candidate)
else if candidate.Status == UnBonded then unbondedToBondedPool(candidate)
saveCandidate(store, c)
return candidates
unbondedToBondedPool(candidate Candidate):
removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * candidate.GlobalStakeShares
gs.UnbondedShares -= candidate.GlobalStakeShares
gs.UnbondedPool -= removedTokens
gs.BondedPool += removedTokens
issuedShares = removedTokens / exchangeRate(gs.BondedShares, gs.BondedPool)
gs.BondedShares += issuedShares
```
inflationRateChange(0) = 0
GlobalState.Inflation(0) = 0.07
bondedRatio = GlobalState.BondedPool / GlobalState.TotalSupply
AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13
annualInflation += AnnualInflationRateChange
if annualInflation > 0.20 then GlobalState.Inflation = 0.20
if annualInflation < 0.07 then GlobalState.Inflation = 0.07
provisionTokensHourly = GlobalState.TotalSupply * GlobalState.Inflation / (365.25*24)
```
Because the validators hold a relative bonded share (`GlobalStakeShares`), when
more bonded tokens are added proportionally to all validators, the only term
which needs to be updated is the `GlobalState.BondedPool`. So for each previsions
cycle:
```
GlobalState.BondedPool += provisionTokensHourly
candidate.GlobalStakeShares = issuedShares
candidate.Status = Bonded
return transfer(address of the unbonded pool, address of the bonded pool, removedTokens)
```

View File

@ -1,12 +0,0 @@
*.swp
*.swo
vendor
build
app/data
### Vagrant ###
.vagrant/
*.box
*.log
vagrant

View File

@ -1,11 +1,18 @@
PACKAGES=$(shell go list ./... | grep -v '/vendor/')
BUILD_FLAGS = -ldflags "-X github.com/cosmos/cosmos-sdk/examples/basecoin/version.GitCommit=`git rev-parse --short HEAD`"
all: build test
all: get_tools get_vendor_deps build test
get_tools:
go get github.com/golang/dep/cmd/dep
build:
go build $(BUILD_FLAGS) -o build/basecoin ./cmd/...
get_vendor_deps:
@rm -rf vendor/
@dep ensure
test:
@go test $(PACKAGES)

View File

@ -1,8 +1,70 @@
This is the "Basecoin" example application built on the Cosmos-SDK. This
# Basecoin
This is the "Basecoin" example application built on the Cosmos-Sdk. This
"Basecoin" is not affiliated with [Coinbase](http://www.getbasecoin.com/), nor
the [stable coin](http://www.getbasecoin.com/).
Assuming you've run `make get_tools && make get_vendor_deps` from the root of this repository,
run `make build` here to build the `basecoind` and `basecli` binaries.
Assuming you've run `make get_tools && make get_vendor_deps` from the root of
this repository, run `make build` here to build the `basecoind` and `basecli`
binaries.
If you want to create a new application, start by copying the Basecoin app.
# Building your own Blockchain
Basecoin is the equivalent of an ERC20 token contract for blockchains. In order
to deploy your own application all you need to do is clone `examples/basecoin`
and run it. Now you are already running your own blockchain. In the following
I will explain how to add functionality to your blockchain. This is akin to
defining your own vesting schedule within a contract or setting a specific
multisig. You are just extending the base layer with extra functionality here
and there.
## Structure of Basecoin
Basecoin is build with the cosmos-sdk. It is a sample application that works
with any engine that implements the ABCI protocol. Basecoin defines multiple
unique modules as well as uses modules directly from the sdk. If you want
to modify Basecoin, you either remove or add modules according to your wishes.
## Modules
A module is a fundamental unit in the cosmos-sdk. A module defines its own
transaction, handles its own state as well as its own state transition logic.
Globally, in the `app/app.go` file you just have to define a key for that
module to access some parts of the state, as well as initialise the module
object and finally add it to the transaction router. The router ensures that
every module only gets its own messages.
## Transactions
A user can send a transaction to the running blockchain application. This
transaction can be of any of the ones that are supported by any of the
registered modules.
### CheckTx
Once a user has submitted their transaction to the engine,
the engine will first run `checkTx` to confirm that it is a valid transaction.
The module has to define a handler that knows how to handle every transaction
type. The corresponding handler gets invoked with the checkTx flag set to true.
This means that the handler shouldn't do any expensive operations, but it can
and should write to the checkTx state.
### DeliverTx
The engine calls `deliverTx` when a new block has been agreed upon in
consensus. Again, the corresponding module will have its handler invoked
and the state and context is passed in. During deliverTx execution the
transaction needs to be processed fully and the results are written to the
application state.
## CLI
The cosmos-sdk contains a number of helper libraries in `clients/` to build cli
and RPC interfaces for your specific application.

View File

@ -4,18 +4,21 @@ import (
"encoding/json"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire"
oldwire "github.com/tendermint/go-wire"
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/ibc"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/examples/basecoin/types"
"github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool"
"github.com/cosmos/cosmos-sdk/examples/basecoin/x/sketchy"
)
@ -29,8 +32,9 @@ type BasecoinApp struct {
cdc *wire.Codec
// keys to access the substores
capKeyMainStore *sdk.KVStoreKey
capKeyIBCStore *sdk.KVStoreKey
capKeyMainStore *sdk.KVStoreKey
capKeyIBCStore *sdk.KVStoreKey
capKeyStakingStore *sdk.KVStoreKey
// Manage getting and setting accounts
accountMapper sdk.AccountMapper
@ -39,10 +43,11 @@ type BasecoinApp struct {
func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
// create your application object
var app = &BasecoinApp{
BaseApp: bam.NewBaseApp(appName, logger, db),
cdc: MakeCodec(),
capKeyMainStore: sdk.NewKVStoreKey("main"),
capKeyIBCStore: sdk.NewKVStoreKey("ibc"),
BaseApp: bam.NewBaseApp(appName, logger, db),
cdc: MakeCodec(),
capKeyMainStore: sdk.NewKVStoreKey("main"),
capKeyIBCStore: sdk.NewKVStoreKey("ibc"),
capKeyStakingStore: sdk.NewKVStoreKey("staking"),
}
// define the accountMapper
@ -53,16 +58,20 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
// add handlers
coinKeeper := bank.NewCoinKeeper(app.accountMapper)
coolMapper := cool.NewMapper(app.capKeyMainStore)
ibcMapper := ibc.NewIBCMapper(app.cdc, app.capKeyIBCStore)
stakingMapper := staking.NewMapper(app.capKeyStakingStore)
app.Router().
AddRoute("bank", bank.NewHandler(coinKeeper)).
AddRoute("sketchy", sketchy.NewHandler())
AddRoute("cool", cool.NewHandler(coinKeeper, coolMapper)).
AddRoute("sketchy", sketchy.NewHandler()).
AddRoute("ibc", ibc.NewHandler(ibcMapper, coinKeeper)).
AddRoute("staking", staking.NewHandler(stakingMapper, coinKeeper))
// initialize BaseApp
app.SetTxDecoder(app.txDecoder)
app.SetInitChainer(app.initChainer)
// TODO: mounting multiple stores is broken
// https://github.com/cosmos/cosmos-sdk/issues/532
app.MountStoresIAVL(app.capKeyMainStore) // , app.capKeyIBCStore)
app.MountStoresIAVL(app.capKeyMainStore, app.capKeyIBCStore, app.capKeyStakingStore)
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper))
err := app.LoadLatestVersion(app.capKeyMainStore)
if err != nil {
@ -73,22 +82,55 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
}
// custom tx codec
// TODO: use new go-wire
func MakeCodec() *wire.Codec {
const msgTypeSend = 0x1
const msgTypeIssue = 0x2
const msgTypeQuiz = 0x3
const msgTypeSetTrend = 0x4
const msgTypeIBCTransferMsg = 0x5
const msgTypeIBCReceiveMsg = 0x6
const msgTypeBondMsg = 0x7
const msgTypeUnbondMsg = 0x8
var _ = oldwire.RegisterInterface(
struct{ sdk.Msg }{},
oldwire.ConcreteType{bank.SendMsg{}, msgTypeSend},
oldwire.ConcreteType{bank.IssueMsg{}, msgTypeIssue},
oldwire.ConcreteType{cool.QuizMsg{}, msgTypeQuiz},
oldwire.ConcreteType{cool.SetTrendMsg{}, msgTypeSetTrend},
oldwire.ConcreteType{ibc.IBCTransferMsg{}, msgTypeIBCTransferMsg},
oldwire.ConcreteType{ibc.IBCReceiveMsg{}, msgTypeIBCReceiveMsg},
oldwire.ConcreteType{staking.BondMsg{}, msgTypeBondMsg},
oldwire.ConcreteType{staking.UnbondMsg{}, msgTypeUnbondMsg},
)
const accTypeApp = 0x1
var _ = oldwire.RegisterInterface(
struct{ sdk.Account }{},
oldwire.ConcreteType{&types.AppAccount{}, accTypeApp},
)
cdc := wire.NewCodec()
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
bank.RegisterWire(cdc) // Register bank.[SendMsg,IssueMsg] types.
crypto.RegisterWire(cdc) // Register crypto.[PubKey,PrivKey,Signature] types.
// cdc.RegisterInterface((*sdk.Msg)(nil), nil)
// bank.RegisterWire(cdc) // Register bank.[SendMsg,IssueMsg] types.
// crypto.RegisterWire(cdc) // Register crypto.[PubKey,PrivKey,Signature] types.
// ibc.RegisterWire(cdc) // Register ibc.[IBCTransferMsg, IBCReceiveMsg] types.
return cdc
}
// custom logic for transaction decoding
func (app *BasecoinApp) txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) {
var tx = sdk.StdTx{}
if len(txBytes) == 0 {
return nil, sdk.ErrTxDecode("txBytes are empty")
}
// StdTx.Msg is an interface. The concrete types
// are registered by MakeTxCodec in bank.RegisterWire.
err := app.cdc.UnmarshalBinary(txBytes, &tx)
if err != nil {
return nil, sdk.ErrTxParse("").TraceCause(err, "")
return nil, sdk.ErrTxDecode("").TraceCause(err, "")
}
return tx, nil
}

View File

@ -10,9 +10,11 @@ import (
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/examples/basecoin/types"
"github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/ibc"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
@ -20,54 +22,93 @@ import (
"github.com/tendermint/tmlibs/log"
)
// Construct some global addrs and txs for tests.
var (
chainID = "" // TODO
priv1 = crypto.GenPrivKeyEd25519()
addr1 = priv1.PubKey().Address()
addr2 = crypto.GenPrivKeyEd25519().PubKey().Address()
coins = sdk.Coins{{"foocoin", 10}}
fee = sdk.StdFee{
sdk.Coins{{"foocoin", 0}},
0,
}
sendMsg = bank.SendMsg{
Inputs: []bank.Input{bank.NewInput(addr1, coins)},
Outputs: []bank.Output{bank.NewOutput(addr2, coins)},
}
quizMsg1 = cool.QuizMsg{
Sender: addr1,
CoolAnswer: "icecold",
}
quizMsg2 = cool.QuizMsg{
Sender: addr1,
CoolAnswer: "badvibesonly",
}
setTrendMsg1 = cool.SetTrendMsg{
Sender: addr1,
Cool: "icecold",
}
setTrendMsg2 = cool.SetTrendMsg{
Sender: addr1,
Cool: "badvibesonly",
}
setTrendMsg3 = cool.SetTrendMsg{
Sender: addr1,
Cool: "warmandkind",
}
)
func newBasecoinApp() *BasecoinApp {
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
db := dbm.NewMemDB()
return NewBasecoinApp(logger, db)
}
func TestSendMsg(t *testing.T) {
//_______________________________________________________________________
func TestMsgs(t *testing.T) {
bapp := newBasecoinApp()
// Construct a SendMsg
var msg = bank.SendMsg{
Inputs: []bank.Input{
{
Address: crypto.Address([]byte("input")),
Coins: sdk.Coins{{"atom", 10}},
Sequence: 1,
},
},
Outputs: []bank.Output{
{
Address: crypto.Address([]byte("output")),
Coins: sdk.Coins{{"atom", 10}},
},
},
msgs := []struct {
msg sdk.Msg
}{
{sendMsg},
{quizMsg1},
{setTrendMsg1},
}
priv := crypto.GenPrivKeyEd25519()
sig := priv.Sign(msg.GetSignBytes())
tx := sdk.NewStdTx(msg, []sdk.StdSignature{{
PubKey: priv.PubKey(),
Signature: sig,
}})
sequences := []int64{0}
for i, m := range msgs {
sig := priv1.Sign(sdk.StdSignBytes(chainID, sequences, fee, m.msg))
tx := sdk.NewStdTx(m.msg, fee, []sdk.StdSignature{{
PubKey: priv1.PubKey(),
Signature: sig,
}})
// just marshal/unmarshal!
cdc := MakeCodec()
txBytes, err := cdc.MarshalBinary(tx)
require.NoError(t, err)
// just marshal/unmarshal!
cdc := MakeCodec()
txBytes, err := cdc.MarshalBinary(tx)
require.NoError(t, err, "i: %v", i)
// Run a Check
cres := bapp.CheckTx(txBytes)
assert.Equal(t, sdk.CodeUnrecognizedAddress,
sdk.CodeType(cres.Code), cres.Log)
// Run a Check
cres := bapp.CheckTx(txBytes)
assert.Equal(t, sdk.CodeUnknownAddress,
sdk.CodeType(cres.Code), "i: %v, log: %v", i, cres.Log)
// Simulate a Block
bapp.BeginBlock(abci.RequestBeginBlock{})
dres := bapp.DeliverTx(txBytes)
assert.Equal(t, sdk.CodeUnrecognizedAddress,
sdk.CodeType(dres.Code), dres.Log)
// Simulate a Block
bapp.BeginBlock(abci.RequestBeginBlock{})
dres := bapp.DeliverTx(txBytes)
assert.Equal(t, sdk.CodeUnknownAddress,
sdk.CodeType(dres.Code), "i: %v, log: %v", i, dres.Log)
}
}
func TestGenesis(t *testing.T) {
@ -101,28 +142,19 @@ func TestGenesis(t *testing.T) {
ctx := bapp.BaseApp.NewContext(true, abci.Header{})
res1 := bapp.accountMapper.GetAccount(ctx, baseAcc.Address)
assert.Equal(t, acc, res1)
// reload app and ensure the account is still there
bapp = NewBasecoinApp(logger, db)
ctx = bapp.BaseApp.NewContext(true, abci.Header{})
res1 = bapp.accountMapper.GetAccount(ctx, baseAcc.Address)
assert.Equal(t, acc, res1)
/*
// reload app and ensure the account is still there
bapp = NewBasecoinApp(logger, db)
ctx = bapp.BaseApp.NewContext(true, abci.Header{})
res1 = bapp.accountMapper.GetAccount(ctx, baseAcc.Address)
assert.Equal(t, acc, res1)
*/
}
func TestSendMsgWithAccounts(t *testing.T) {
bapp := newBasecoinApp()
// Construct some genesis bytes to reflect basecoin/types/AppAccount
// First key goes in genesis, used for sending
priv1 := crypto.GenPrivKeyEd25519()
pk1 := priv1.PubKey()
addr1 := pk1.Address()
// Second key receies
pk2 := crypto.GenPrivKeyEd25519().PubKey()
addr2 := pk2.Address()
// Give 77 foocoin to the first key
coins, err := sdk.ParseCoins("77foocoin")
require.Nil(t, err)
@ -139,6 +171,7 @@ func TestSendMsgWithAccounts(t *testing.T) {
},
}
stateBytes, err := json.MarshalIndent(genesisState, "", "\t")
require.Nil(t, err)
// Initialize the chain
vals := []abci.Validator{}
@ -147,30 +180,13 @@ func TestSendMsgWithAccounts(t *testing.T) {
// A checkTx context (true)
ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{})
res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1)
assert.Equal(t, acc1, res1)
// Construct a SendMsg
var msg = bank.SendMsg{
Inputs: []bank.Input{
{
Address: crypto.Address(addr1),
Coins: sdk.Coins{{"foocoin", 10}},
Sequence: 1,
},
},
Outputs: []bank.Output{
{
Address: crypto.Address(addr2),
Coins: sdk.Coins{{"foocoin", 10}},
},
},
}
// Sign the tx
sig := priv1.Sign(msg.GetSignBytes())
tx := sdk.NewStdTx(msg, []sdk.StdSignature{{
sequences := []int64{0}
sig := priv1.Sign(sdk.StdSignBytes(chainID, sequences, fee, sendMsg))
tx := sdk.NewStdTx(sendMsg, fee, []sdk.StdSignature{{
PubKey: priv1.PubKey(),
Signature: sig,
}})
@ -184,13 +200,160 @@ func TestSendMsgWithAccounts(t *testing.T) {
res = bapp.Deliver(tx)
assert.Equal(t, sdk.CodeOK, res.Code, res.Log)
// A deliverTx context
ctxDeliver := bapp.BaseApp.NewContext(false, abci.Header{})
// Check balances
ctxDeliver := bapp.BaseApp.NewContext(false, abci.Header{})
res2 := bapp.accountMapper.GetAccount(ctxDeliver, addr1)
res3 := bapp.accountMapper.GetAccount(ctxDeliver, addr2)
assert.Equal(t, fmt.Sprintf("%v", res2.GetCoins()), "67foocoin")
assert.Equal(t, fmt.Sprintf("%v", res3.GetCoins()), "10foocoin")
// Delivering again should cause replay error
res = bapp.Deliver(tx)
assert.Equal(t, sdk.CodeInvalidSequence, res.Code, res.Log)
// bumping the txnonce number without resigning should be an auth error
tx.Signatures[0].Sequence = 1
res = bapp.Deliver(tx)
assert.Equal(t, sdk.CodeUnauthorized, res.Code, res.Log)
// resigning the tx with the bumped sequence should work
sequences = []int64{1}
sig = priv1.Sign(sdk.StdSignBytes(chainID, sequences, fee, tx.Msg))
tx.Signatures[0].Signature = sig
res = bapp.Deliver(tx)
assert.Equal(t, sdk.CodeOK, res.Code, res.Log)
}
func TestQuizMsg(t *testing.T) {
bapp := newBasecoinApp()
// Construct genesis state
// Construct some genesis bytes to reflect basecoin/types/AppAccount
coins := sdk.Coins{}
baseAcc := auth.BaseAccount{
Address: addr1,
Coins: coins,
}
acc1 := &types.AppAccount{baseAcc, "foobart"}
// Construct genesis state
genesisState := types.GenesisState{
Accounts: []*types.GenesisAccount{
types.NewGenesisAccount(acc1),
},
}
stateBytes, err := json.MarshalIndent(genesisState, "", "\t")
require.Nil(t, err)
// Initialize the chain (nil)
vals := []abci.Validator{}
bapp.InitChain(abci.RequestInitChain{vals, stateBytes})
bapp.Commit()
// A checkTx context (true)
ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{})
res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1)
assert.Equal(t, acc1, res1)
// Set the trend, submit a really cool quiz and check for reward
SignCheckDeliver(t, bapp, setTrendMsg1, 0, true)
SignCheckDeliver(t, bapp, quizMsg1, 1, true)
CheckBalance(t, bapp, "69icecold")
SignCheckDeliver(t, bapp, quizMsg2, 2, true) // result without reward
CheckBalance(t, bapp, "69icecold")
SignCheckDeliver(t, bapp, quizMsg1, 3, true)
CheckBalance(t, bapp, "138icecold")
SignCheckDeliver(t, bapp, setTrendMsg2, 4, true) // reset the trend
SignCheckDeliver(t, bapp, quizMsg1, 5, true) // the same answer will nolonger do!
CheckBalance(t, bapp, "138icecold")
SignCheckDeliver(t, bapp, quizMsg2, 6, true) // earlier answer now relavent again
CheckBalance(t, bapp, "69badvibesonly,138icecold")
SignCheckDeliver(t, bapp, setTrendMsg3, 7, false) // expect to fail to set the trend to something which is not cool
}
func TestHandler(t *testing.T) {
bapp := newBasecoinApp()
sourceChain := "source-chain"
destChain := "dest-chain"
vals := []abci.Validator{}
baseAcc := auth.BaseAccount{
Address: addr1,
Coins: coins,
}
acc1 := &types.AppAccount{baseAcc, "foobart"}
genesisState := types.GenesisState{
Accounts: []*types.GenesisAccount{
types.NewGenesisAccount(acc1),
},
}
stateBytes, err := json.MarshalIndent(genesisState, "", "\t")
require.Nil(t, err)
bapp.InitChain(abci.RequestInitChain{vals, stateBytes})
bapp.Commit()
// A checkTx context (true)
ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{})
res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1)
assert.Equal(t, acc1, res1)
packet := ibc.IBCPacket{
SrcAddr: addr1,
DestAddr: addr1,
Coins: coins,
SrcChain: sourceChain,
DestChain: destChain,
}
transferMsg := ibc.IBCTransferMsg{
IBCPacket: packet,
}
receiveMsg := ibc.IBCReceiveMsg{
IBCPacket: packet,
Relayer: addr1,
Sequence: 0,
}
SignCheckDeliver(t, bapp, transferMsg, 0, true)
CheckBalance(t, bapp, "")
SignCheckDeliver(t, bapp, transferMsg, 1, false)
SignCheckDeliver(t, bapp, receiveMsg, 2, true)
CheckBalance(t, bapp, "10foocoin")
SignCheckDeliver(t, bapp, receiveMsg, 3, false)
}
func SignCheckDeliver(t *testing.T, bapp *BasecoinApp, msg sdk.Msg, seq int64, expPass bool) {
// Sign the tx
tx := sdk.NewStdTx(msg, fee, []sdk.StdSignature{{
PubKey: priv1.PubKey(),
Signature: priv1.Sign(sdk.StdSignBytes(chainID, []int64{seq}, fee, msg)),
Sequence: seq,
}})
// Run a Check
res := bapp.Check(tx)
if expPass {
require.Equal(t, sdk.CodeOK, res.Code, res.Log)
} else {
require.NotEqual(t, sdk.CodeOK, res.Code, res.Log)
}
// Simulate a Block
bapp.BeginBlock(abci.RequestBeginBlock{})
res = bapp.Deliver(tx)
if expPass {
require.Equal(t, sdk.CodeOK, res.Code, res.Log)
} else {
require.NotEqual(t, sdk.CodeOK, res.Code, res.Log)
}
}
func CheckBalance(t *testing.T, bapp *BasecoinApp, balExpected string) {
ctxDeliver := bapp.BaseApp.NewContext(false, abci.Header{})
res2 := bapp.accountMapper.GetAccount(ctxDeliver, addr1)
assert.Equal(t, balExpected, fmt.Sprintf("%v", res2.GetCoins()))
}

View File

@ -2,9 +2,8 @@ package main
import (
"errors"
"os"
"github.com/spf13/cobra"
"os"
"github.com/tendermint/tmlibs/cli"
@ -13,9 +12,13 @@ import (
"github.com/cosmos/cosmos-sdk/client/lcd"
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/client/tx"
coolcmd "github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool/commands"
"github.com/cosmos/cosmos-sdk/version"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands"
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/commands"
ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/commands"
stakingcmd "github.com/cosmos/cosmos-sdk/x/staking/commands"
"github.com/cosmos/cosmos-sdk/examples/basecoin/app"
"github.com/cosmos/cosmos-sdk/examples/basecoin/types"
@ -40,6 +43,10 @@ func main() {
// get the codec
cdc := app.MakeCodec()
// TODO: setup keybase, viper object, etc. to be passed into
// the below functions and eliminate global vars, like we do
// with the cdc
// add standard rpc, and tx commands
rpc.AddCommands(basecliCmd)
basecliCmd.AddCommand(client.LineBreak)
@ -49,17 +56,38 @@ func main() {
// add query/post commands (custom to binary)
basecliCmd.AddCommand(
client.GetCommands(
authcmd.GetAccountCmd("main", cdc, types.GetParseAccount(cdc)),
authcmd.GetAccountCmd("main", cdc, types.GetAccountDecoder(cdc)),
)...)
basecliCmd.AddCommand(
client.PostCommands(
bankcmd.SendTxCmd(cdc),
)...)
basecliCmd.AddCommand(
client.PostCommands(
coolcmd.QuizTxCmd(cdc),
)...)
basecliCmd.AddCommand(
client.PostCommands(
coolcmd.SetTrendTxCmd(cdc),
)...)
basecliCmd.AddCommand(
client.PostCommands(
ibccmd.IBCTransferCmd(cdc),
)...)
basecliCmd.AddCommand(
client.PostCommands(
ibccmd.IBCRelayCmd(cdc),
stakingcmd.BondTxCmd(cdc),
)...)
basecliCmd.AddCommand(
client.PostCommands(
stakingcmd.UnbondTxCmd(cdc),
)...)
// add proxy, version and key info
basecliCmd.AddCommand(
client.LineBreak,
lcd.ServeCommand(),
lcd.ServeCommand(cdc),
keys.Commands(),
client.LineBreak,
version.VersionCmd,

View File

@ -59,6 +59,7 @@ func generateApp(rootDir string, logger log.Logger) (abci.Application, error) {
}
func main() {
// TODO: set logger through CLI
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).
With("module", "main")
@ -66,6 +67,7 @@ func main() {
server.InitCmd(defaultOptions, logger),
server.StartCmd(generateApp, logger),
server.UnsafeResetAllCmd(logger),
server.ShowNodeIdCmd(logger),
version.VersionCmd,
)

View File

@ -2,9 +2,8 @@ package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
)
var _ sdk.Account = (*AppAccount)(nil)
@ -23,11 +22,17 @@ type AppAccount struct {
func (acc AppAccount) GetName() string { return acc.Name }
func (acc *AppAccount) SetName(name string) { acc.Name = name }
// Get the ParseAccount function for the custom AppAccount
func GetParseAccount(cdc *wire.Codec) sdk.ParseAccount {
// Get the AccountDecoder function for the custom AppAccount
func GetAccountDecoder(cdc *wire.Codec) sdk.AccountDecoder {
return func(accBytes []byte) (res sdk.Account, err error) {
if len(accBytes) == 0 {
return nil, sdk.ErrTxDecode("accBytes are empty")
}
acct := new(AppAccount)
err = cdc.UnmarshalBinary(accBytes, acct)
err = cdc.UnmarshalBinary(accBytes, &acct)
if err != nil {
panic(err)
}
return acct, err
}
}
@ -41,9 +46,9 @@ type GenesisState struct {
// GenesisAccount doesn't need pubkey or sequence
type GenesisAccount struct {
Name string `json:"name"`
Address crypto.Address `json:"address"`
Coins sdk.Coins `json:"coins"`
Name string `json:"name"`
Address sdk.Address `json:"address"`
Coins sdk.Coins `json:"coins"`
}
func NewGenesisAccount(aa *AppAccount) *GenesisAccount {

View File

@ -0,0 +1,99 @@
package commands
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/builder"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool"
)
// take the coolness quiz transaction
func QuizTxCmd(cdc *wire.Codec) *cobra.Command {
return &cobra.Command{
Use: "cool [answer]",
Short: "What's cooler than being cool?",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide an answer")
}
// get the from address from the name flag
from, err := builder.GetFromAddress()
if err != nil {
return err
}
// create the message
msg := cool.NewQuizMsg(from, args[0])
// get account name
name := viper.GetString(client.FlagName)
// get password
buf := client.BufferStdin()
prompt := fmt.Sprintf("Password to sign with '%s':", name)
passphrase, err := client.GetPassword(prompt, buf)
if err != nil {
return err
}
// build and sign the transaction, then broadcast to Tendermint
res, err := builder.SignBuildBroadcast(name, passphrase, msg, cdc)
if err != nil {
return err
}
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
return nil
},
}
}
// set a new cool trend transaction
func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command {
return &cobra.Command{
Use: "setcool [answer]",
Short: "You're so cool, tell us what is cool!",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide an answer")
}
// get the from address from the name flag
from, err := builder.GetFromAddress()
if err != nil {
return err
}
// get account name
name := viper.GetString(client.FlagName)
// get password
buf := client.BufferStdin()
prompt := fmt.Sprintf("Password to sign with '%s':", name)
passphrase, err := client.GetPassword(prompt, buf)
if err != nil {
return err
}
// create the message
msg := cool.NewSetTrendMsg(from, args[0])
// build and sign the transaction, then broadcast to Tendermint
res, err := builder.SignBuildBroadcast(name, passphrase, msg, cdc)
if err != nil {
return err
}
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
return nil
},
}
}

View File

@ -0,0 +1,56 @@
package cool
import (
"fmt"
"reflect"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank"
)
// This is just an example to demonstrate a functional custom module
// with full feature set functionality.
//
// /$$$$$$$ /$$$$$$ /$$$$$$ /$$
// /$$_____/ /$$__ $$ /$$__ $$| $$
//| $$ | $$ \ $$| $$ \ $$| $$
//| $$ | $$ | $$| $$ | $$| $$
//| $$$$$$$| $$$$$$/| $$$$$$/| $$$$$$$
// \_______/ \______/ \______/ |______/
// Handle all "coolmodule" type objects
func NewHandler(ck bank.CoinKeeper, cm Mapper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case SetTrendMsg:
return handleSetTrendMsg(ctx, cm, msg)
case QuizMsg:
return handleQuizMsg(ctx, ck, cm, msg)
default:
errMsg := fmt.Sprintf("Unrecognized cool Msg type: %v", reflect.TypeOf(msg).Name())
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
}
// Handle QuizMsg This is the engine of your module
func handleSetTrendMsg(ctx sdk.Context, cm Mapper, msg SetTrendMsg) sdk.Result {
cm.SetTrend(ctx, msg.Cool)
return sdk.Result{}
}
// Handle QuizMsg This is the engine of your module
func handleQuizMsg(ctx sdk.Context, ck bank.CoinKeeper, cm Mapper, msg QuizMsg) sdk.Result {
currentTrend := cm.GetTrend(ctx)
if msg.CoolAnswer == currentTrend {
bonusCoins := sdk.Coins{{currentTrend, 69}}
_, err := ck.AddCoins(ctx, msg.Sender, bonusCoins)
if err != nil {
return err.Result()
}
}
return sdk.Result{}
}

View File

@ -0,0 +1,30 @@
package cool
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// This Cool Mapper handlers sets/gets of custom variables for your module
type Mapper struct {
key sdk.StoreKey // The (unexposed) key used to access the store from the Context.
}
func NewMapper(key sdk.StoreKey) Mapper {
return Mapper{key}
}
// Key to knowing the trend on the streets!
var trendKey = []byte("TrendKey")
// Implements sdk.AccountMapper.
func (am Mapper) GetTrend(ctx sdk.Context) string {
store := ctx.KVStore(am.key)
bz := store.Get(trendKey)
return string(bz)
}
// Implements sdk.AccountMapper.
func (am Mapper) SetTrend(ctx sdk.Context, newTrend string) {
store := ctx.KVStore(am.key)
store.Set(trendKey, []byte(newTrend))
}

View File

@ -0,0 +1,103 @@
package cool
import (
"encoding/json"
"fmt"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// A really cool msg type, these fields are can be entirely arbitrary and
// custom to your message
type SetTrendMsg struct {
Sender sdk.Address
Cool string
}
// New cool message
func NewSetTrendMsg(sender sdk.Address, cool string) SetTrendMsg {
return SetTrendMsg{
Sender: sender,
Cool: cool,
}
}
// enforce the msg type at compile time
var _ sdk.Msg = SetTrendMsg{}
// nolint
func (msg SetTrendMsg) Type() string { return "cool" }
func (msg SetTrendMsg) Get(key interface{}) (value interface{}) { return nil }
func (msg SetTrendMsg) GetSigners() []sdk.Address { return []sdk.Address{msg.Sender} }
func (msg SetTrendMsg) String() string {
return fmt.Sprintf("SetTrendMsg{Sender: %v, Cool: %v}", msg.Sender, msg.Cool)
}
// Validate Basic is used to quickly disqualify obviously invalid messages quickly
func (msg SetTrendMsg) ValidateBasic() sdk.Error {
if len(msg.Sender) == 0 {
return sdk.ErrUnknownAddress(msg.Sender.String()).Trace("")
}
if strings.Contains(msg.Cool, "hot") {
return sdk.ErrUnauthorized("").Trace("hot is not cool")
}
if strings.Contains(msg.Cool, "warm") {
return sdk.ErrUnauthorized("").Trace("warm is not very cool")
}
return nil
}
// Get the bytes for the message signer to sign on
func (msg SetTrendMsg) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
//_______________________________________________________________________
// A message type to quiz how cool you are. these fields are can be entirely
// arbitrary and custom to your message
type QuizMsg struct {
Sender sdk.Address
CoolAnswer string
}
// New cool message
func NewQuizMsg(sender sdk.Address, coolerthancool string) QuizMsg {
return QuizMsg{
Sender: sender,
CoolAnswer: coolerthancool,
}
}
// enforce the msg type at compile time
var _ sdk.Msg = QuizMsg{}
// nolint
func (msg QuizMsg) Type() string { return "cool" }
func (msg QuizMsg) Get(key interface{}) (value interface{}) { return nil }
func (msg QuizMsg) GetSigners() []sdk.Address { return []sdk.Address{msg.Sender} }
func (msg QuizMsg) String() string {
return fmt.Sprintf("QuizMsg{Sender: %v, CoolAnswer: %v}", msg.Sender, msg.CoolAnswer)
}
// Validate Basic is used to quickly disqualify obviously invalid messages quickly
func (msg QuizMsg) ValidateBasic() sdk.Error {
if len(msg.Sender) == 0 {
return sdk.ErrUnknownAddress(msg.Sender.String()).Trace("")
}
return nil
}
// Get the bytes for the message signer to sign on
func (msg QuizMsg) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}

View File

@ -4,7 +4,6 @@ import (
"bytes"
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
// An sdk.Tx which is its own sdk.Msg.
@ -44,7 +43,7 @@ func (tx kvstoreTx) ValidateBasic() sdk.Error {
return nil
}
func (tx kvstoreTx) GetSigners() []crypto.Address {
func (tx kvstoreTx) GetSigners() []sdk.Address {
return nil
}
@ -52,10 +51,6 @@ func (tx kvstoreTx) GetSignatures() []sdk.StdSignature {
return nil
}
func (tx kvstoreTx) GetFeePayer() crypto.Address {
return nil
}
// takes raw transaction bytes and decodes them into an sdk.Tx. An sdk.Tx has
// all the signatures and can be used to authenticate.
func decodeTx(txBytes []byte) (sdk.Tx, sdk.Error) {
@ -69,7 +64,7 @@ func decodeTx(txBytes []byte) (sdk.Tx, sdk.Error) {
k, v := split[0], split[1]
tx = kvstoreTx{k, v, txBytes}
} else {
return nil, sdk.ErrTxParse("too many =")
return nil, sdk.ErrTxDecode("too many =")
}
return tx, nil

253
glide.lock generated
View File

@ -1,253 +0,0 @@
hash: fa45c8a4f5512ed730f793b93d4876bdc604a1333a5a1f938c98a0f7dd55f22e
updated: 2018-03-01T00:41:12.97082395-05:00
imports:
- name: github.com/bgentry/speakeasy
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
- name: github.com/btcsuite/btcd
version: 50de9da05b50eb15658bb350f6ea24368a111ab7
subpackages:
- btcec
- name: github.com/davecgh/go-spew
version: 346938d642f2ec3594ed81d874461961cd0faa76
subpackages:
- spew
- name: github.com/ebuchman/fail-test
version: 95f809107225be108efcf10a3509e4ea6ceef3c4
- name: github.com/fsnotify/fsnotify
version: c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9
- name: github.com/go-kit/kit
version: 4dc7be5d2d12881735283bcab7352178e190fc71
subpackages:
- log
- log/level
- log/term
- name: github.com/go-logfmt/logfmt
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
- name: github.com/go-stack/stack
version: 259ab82a6cad3992b4e21ff5cac294ccb06474bc
- name: github.com/gogo/protobuf
version: 1adfc126b41513cc696b209667c8656ea7aac67c
subpackages:
- gogoproto
- jsonpb
- proto
- protoc-gen-gogo/descriptor
- sortkeys
- types
- name: github.com/golang/protobuf
version: 925541529c1fa6821df4e44ce2723319eb2be768
subpackages:
- proto
- ptypes
- ptypes/any
- ptypes/duration
- ptypes/timestamp
- name: github.com/golang/snappy
version: 553a641470496b2327abcac10b36396bd98e45c9
- name: github.com/gorilla/websocket
version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b
- name: github.com/hashicorp/hcl
version: 23c074d0eceb2b8a5bfdbb271ab780cde70f05a8
subpackages:
- hcl/ast
- hcl/parser
- hcl/scanner
- hcl/strconv
- hcl/token
- json/parser
- json/scanner
- json/token
- name: github.com/howeyc/crc16
version: 96a97a1abb579c7ff1a8ffa77f2e72d1c314b57f
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/jmhodges/levigo
version: c42d9e0ca023e2198120196f842701bb4c55d7b9
- name: github.com/kr/logfmt
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
- name: github.com/magiconair/properties
version: 49d762b9817ba1c2e9d0c69183c2b4a8b8f1d934
- name: github.com/mattn/go-isatty
version: 0360b2af4f38e8d38c7fce2a9f4e702702d73a39
- name: github.com/mitchellh/mapstructure
version: b4575eea38cca1123ec2dc90c26529b5c5acfcff
- name: github.com/pelletier/go-toml
version: acdc4509485b587f5e675510c4f2c63e90ff68a8
- name: github.com/pkg/errors
version: 645ef00459ed84a119197bfb8d8205042c6df63d
- name: github.com/rcrowley/go-metrics
version: 1f30fe9094a513ce4c700b9a54458bbb0c96996c
- name: github.com/rigelrozanski/common
version: f691f115798593d783b9999b1263c2f4ffecc439
- name: github.com/spf13/afero
version: bb8f1927f2a9d3ab41c9340aa034f6b803f4359c
subpackages:
- mem
- name: github.com/spf13/cast
version: acbeb36b902d72a7a4c18e8f3241075e7ab763e4
- name: github.com/spf13/cobra
version: 7b2c5ac9fc04fc5efafb60700713d4fa609b777b
- name: github.com/spf13/jwalterweatherman
version: 7c0cea34c8ece3fbeb2b27ab9b59511d360fb394
- name: github.com/spf13/pflag
version: e57e3eeb33f795204c1ca35f56c44f83227c6e66
- name: github.com/spf13/viper
version: 25b30aa063fc18e48662b86996252eabdcf2f0c7
- name: github.com/syndtr/goleveldb
version: 34011bf325bce385408353a30b101fe5e923eb6e
subpackages:
- leveldb
- leveldb/cache
- leveldb/comparer
- leveldb/errors
- leveldb/filter
- leveldb/iterator
- leveldb/journal
- leveldb/memdb
- leveldb/opt
- leveldb/storage
- leveldb/table
- leveldb/util
- name: github.com/tendermint/abci
version: 68592f4d8ee34e97db94b7a7976b1309efdb7eb9
subpackages:
- client
- example/code
- example/dummy
- server
- types
- name: github.com/tendermint/ed25519
version: d8387025d2b9d158cf4efb07e7ebf814bcce2057
subpackages:
- edwards25519
- extra25519
- name: github.com/tendermint/go-crypto
version: 4fc3055dbd17aa1203d0abc64b9293f378da22ec
subpackages:
- keys
- keys/bcrypt
- keys/words
- keys/words/wordlist
- name: github.com/tendermint/go-wire
version: 5d7845f24b843c914cf571dad2ca13c91cf70f0d
- name: github.com/tendermint/iavl
version: 1a59ec0c82dc940c25339dd7c834df5cb76a95cb
- name: github.com/tendermint/tendermint
version: c330b9e43c93351a5c3040333d7d0c7c27859a20
subpackages:
- blockchain
- cmd/tendermint/commands
- config
- consensus
- consensus/types
- evidence
- lite
- lite/client
- lite/errors
- lite/files
- lite/proxy
- mempool
- node
- p2p
- p2p/conn
- p2p/pex
- p2p/trust
- p2p/upnp
- proxy
- rpc/client
- rpc/core
- rpc/core/types
- rpc/grpc
- rpc/lib
- rpc/lib/client
- rpc/lib/server
- rpc/lib/types
- state
- state/txindex
- state/txindex/kv
- state/txindex/null
- types
- version
- wire
- name: github.com/tendermint/tmlibs
version: 26f2ab65f82cfc6873c312e8030104c47c05f10e
subpackages:
- autofile
- cli
- cli/flags
- clist
- common
- db
- flowrate
- log
- merkle
- pubsub
- pubsub/query
- name: golang.org/x/crypto
version: 1875d0a70c90e57f11972aefd42276df65e895b9
subpackages:
- blowfish
- curve25519
- nacl/box
- nacl/secretbox
- openpgp/armor
- openpgp/errors
- poly1305
- ripemd160
- salsa20/salsa
- name: golang.org/x/net
version: 2fb46b16b8dda405028c50f7c7f0f9dd1fa6bfb1
subpackages:
- context
- http2
- http2/hpack
- idna
- internal/timeseries
- lex/httplex
- trace
- name: golang.org/x/sys
version: 37707fdb30a5b38865cfb95e5aab41707daec7fd
subpackages:
- unix
- name: golang.org/x/text
version: e19ae1496984b1c655b8044a65c0300a3c878dd3
subpackages:
- secure/bidirule
- transform
- unicode/bidi
- unicode/norm
- name: google.golang.org/genproto
version: 4eb30f4778eed4c258ba66527a0d4f9ec8a36c45
subpackages:
- googleapis/rpc/status
- name: google.golang.org/grpc
version: 401e0e00e4bb830a10496d64cd95e068c5bf50de
subpackages:
- balancer
- codes
- connectivity
- credentials
- grpclb/grpc_lb_v1/messages
- grpclog
- internal
- keepalive
- metadata
- naming
- peer
- resolver
- stats
- status
- tap
- transport
- name: gopkg.in/yaml.v2
version: d670f9405373e636a5a2765eea47fac0c9bc91a4
testImports:
- name: github.com/pmezard/go-difflib
version: 792786c7400a136282c1664665ae0a8db921c6c2
subpackages:
- difflib
- name: github.com/stretchr/testify
version: 12b6f73e6084dad08a7c6e575284b177ecafbc71
subpackages:
- assert
- require

View File

@ -1,54 +0,0 @@
package: github.com/cosmos/cosmos-sdk
import:
- package: github.com/golang/protobuf
version: ^1.0.0
subpackages:
- proto
- package: github.com/bgentry/speakeasy
version: ^0.1.0
- package: github.com/mattn/go-isatty
version: ~0.0.3
- package: github.com/pkg/errors
version: ^0.8.0
- package: github.com/rigelrozanski/common
- package: github.com/tendermint/abci
version: develop
subpackages:
- server
- types
- package: github.com/tendermint/go-crypto
version: develop
- package: github.com/tendermint/go-wire
version: develop
- package: github.com/tendermint/iavl
version: develop
- package: github.com/tendermint/tmlibs
version: develop
subpackages:
- common
- db
- log
- merkle
- package: github.com/tendermint/tendermint
version: breaking/wire-sdk2
subpackages:
- cmd/tendermint/commands
- config
- lite
- rpc/client
- types
- package: golang.org/x/crypto
subpackages:
- ripemd160
- package: github.com/spf13/pflag
version: v1.0.0
- package: github.com/spf13/cobra
version: v0.0.1
- package: github.com/spf13/viper
version: ^1.0.0
testImport:
- package: github.com/stretchr/testify
version: ^1.2.1
subpackages:
- assert
- require

View File

@ -6,7 +6,6 @@ import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
// An sdk.Tx which is its own sdk.Msg.
@ -57,7 +56,7 @@ func (tx kvstoreTx) ValidateBasic() sdk.Error {
return nil
}
func (tx kvstoreTx) GetSigners() []crypto.Address {
func (tx kvstoreTx) GetSigners() []sdk.Address {
return nil
}
@ -65,10 +64,6 @@ func (tx kvstoreTx) GetSignatures() []sdk.StdSignature {
return nil
}
func (tx kvstoreTx) GetFeePayer() crypto.Address {
return nil
}
// takes raw transaction bytes and decodes them into an sdk.Tx. An sdk.Tx has
// all the signatures and can be used to authenticate.
func decodeTx(txBytes []byte) (sdk.Tx, sdk.Error) {
@ -82,7 +77,7 @@ func decodeTx(txBytes []byte) (sdk.Tx, sdk.Error) {
k, v := split[0], split[1]
tx = kvstoreTx{k, v, txBytes}
} else {
return nil, sdk.ErrTxParse("too many =")
return nil, sdk.ErrTxDecode("too many =")
}
return tx, nil

View File

@ -2,16 +2,11 @@ package server
import (
"encoding/json"
"fmt"
"io/ioutil"
"github.com/spf13/cobra"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/words"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
@ -20,14 +15,14 @@ import (
)
// InitCmd will initialize all files for tendermint,
// along with proper app_options.
// along with proper app_state.
// The application can pass in a function to generate
// proper options. And may want to use GenerateCoinKey
// proper state. And may want to use GenerateCoinKey
// to create default account(s).
func InitCmd(gen GenOptions, logger log.Logger) *cobra.Command {
func InitCmd(gen GenAppState, logger log.Logger) *cobra.Command {
cmd := initCmd{
gen: gen,
logger: logger,
genAppState: gen,
logger: logger,
}
return &cobra.Command{
Use: "init",
@ -36,39 +31,14 @@ func InitCmd(gen GenOptions, logger log.Logger) *cobra.Command {
}
}
// GenOptions can parse command-line and flag to
// generate default app_options for the genesis file.
// GenAppState can parse command-line and flag to
// generate default app_state for the genesis file.
// This is application-specific
type GenOptions func(args []string) (json.RawMessage, error)
// GenerateCoinKey returns the address of a public key,
// along with the secret phrase to recover the private key.
// You can give coins to this address and return the recovery
// phrase to the user to access them.
func GenerateCoinKey() (crypto.Address, string, error) {
// construct an in-memory key store
codec, err := words.LoadCodec("english")
if err != nil {
return nil, "", err
}
keybase := keys.New(
dbm.NewMemDB(),
codec,
)
// generate a private key, with recovery phrase
info, secret, err := keybase.Create("name", "pass", keys.AlgoEd25519)
if err != nil {
return nil, "", err
}
addr := info.PubKey.Address()
return addr, secret, nil
}
type GenAppState func(args []string) (json.RawMessage, error)
type initCmd struct {
gen GenOptions
logger log.Logger
genAppState GenAppState
logger log.Logger
}
func (c initCmd) run(cmd *cobra.Command, args []string) error {
@ -84,19 +54,19 @@ func (c initCmd) run(cmd *cobra.Command, args []string) error {
}
// no app_options, leave like tendermint
if c.gen == nil {
if c.genAppState == nil {
return nil
}
// Now, we want to add the custom app_options
options, err := c.gen(args)
// Now, we want to add the custom app_state
appState, err := c.genAppState(args)
if err != nil {
return err
}
// And add them to the genesis file
genFile := config.GenesisFile()
return addGenesisOptions(genFile, options)
return addGenesisState(genFile, appState)
}
// This was copied from tendermint/cmd/tendermint/commands/init.go
@ -140,7 +110,7 @@ func (c initCmd) initTendermintFiles(config *cfg.Config) error {
// so we can add one line.
type GenesisDoc map[string]json.RawMessage
func addGenesisOptions(filename string, options json.RawMessage) error {
func addGenesisState(filename string, appState json.RawMessage) error {
bz, err := ioutil.ReadFile(filename)
if err != nil {
return err
@ -152,7 +122,7 @@ func addGenesisOptions(filename string, options json.RawMessage) error {
return err
}
doc["app_state"] = options
doc["app_state"] = appState
out, err := json.MarshalIndent(doc, "", " ")
if err != nil {
return err
@ -160,23 +130,3 @@ func addGenesisOptions(filename string, options json.RawMessage) error {
return ioutil.WriteFile(filename, out, 0600)
}
// GetGenesisJSON returns a new tendermint genesis with Basecoin app_options
// that grant a large amount of "mycoin" to a single address
// TODO: A better UX for generating genesis files
func GetGenesisJSON(pubkey, chainID, denom, addr string, options string) string {
return fmt.Sprintf(`{
"accounts": [{
"address": "%s",
"coins": [
{
"denom": "%s",
"amount": 9007199254740992
}
]
}],
"plugin_options": [
"coin/issuer", {"app": "sigs", "addr": "%s"}%s
]
}`, addr, denom, addr, options)
}

View File

@ -1,11 +1,8 @@
package server
import (
"io/ioutil"
"os"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/tendermint/tmlibs/log"
@ -13,21 +10,8 @@ import (
"github.com/cosmos/cosmos-sdk/mock"
)
// setupViper creates a homedir to run inside,
// and returns a cleanup function to defer
func setupViper() func() {
rootDir, err := ioutil.TempDir("", "mock-sdk-cmd")
if err != nil {
panic(err) // fuck it!
}
viper.Set("home", rootDir)
return func() {
os.RemoveAll(rootDir)
}
}
func TestInit(t *testing.T) {
defer setupViper()()
defer setupViper(t)()
logger := log.NewNopLogger()
cmd := InitCmd(mock.GenInitOptions, logger)

34
server/key.go Normal file
View File

@ -0,0 +1,34 @@
package server
import (
"github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/words"
dbm "github.com/tendermint/tmlibs/db"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// GenerateCoinKey returns the address of a public key,
// along with the secret phrase to recover the private key.
// You can give coins to this address and return the recovery
// phrase to the user to access them.
func GenerateCoinKey() (sdk.Address, string, error) {
// construct an in-memory key store
codec, err := words.LoadCodec("english")
if err != nil {
return nil, "", err
}
keybase := keys.New(
dbm.NewMemDB(),
codec,
)
// generate a private key, with recovery phrase
info, secret, err := keybase.Create("name", "pass", keys.AlgoEd25519)
if err != nil {
return nil, "", err
}
addr := info.PubKey.Address()
return addr, secret, nil
}

38
server/show_node_id.go Normal file
View File

@ -0,0 +1,38 @@
package server
import (
"fmt"
"github.com/spf13/cobra"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tmlibs/log"
)
// ShowNodeIdCmd - ported from Tendermint, dump node ID to stdout
func ShowNodeIdCmd(logger log.Logger) *cobra.Command {
cmd := showNodeId{logger}
return &cobra.Command{
Use: "show_node_id",
Short: "Show this node's ID",
RunE: cmd.run,
}
}
type showNodeId struct {
logger log.Logger
}
func (s showNodeId) run(cmd *cobra.Command, args []string) error {
cfg, err := tcmd.ParseConfig()
if err != nil {
return err
}
nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile())
if err != nil {
return err
}
fmt.Println(nodeKey.ID())
return nil
}

View File

@ -23,14 +23,14 @@ const (
// appGenerator lets us lazily initialize app, using home dir
// and other flags (?) to start
type appGenerator func(string, log.Logger) (abci.Application, error)
type appCreator func(string, log.Logger) (abci.Application, error)
// StartCmd runs the service passed in, either
// stand-alone, or in-process with tendermint
func StartCmd(app appGenerator, logger log.Logger) *cobra.Command {
func StartCmd(app appCreator, logger log.Logger) *cobra.Command {
start := startCmd{
app: app,
logger: logger,
appCreator: app,
logger: logger,
}
cmd := &cobra.Command{
Use: "start",
@ -48,8 +48,8 @@ func StartCmd(app appGenerator, logger log.Logger) *cobra.Command {
}
type startCmd struct {
app appGenerator
logger log.Logger
appCreator appCreator
logger log.Logger
}
func (s startCmd) run(cmd *cobra.Command, args []string) error {
@ -65,7 +65,7 @@ func (s startCmd) startStandAlone() error {
// Generate the app in the proper dir
addr := viper.GetString(flagAddress)
home := viper.GetString("home")
app, err := s.app(home, s.logger)
app, err := s.appCreator(home, s.logger)
if err != nil {
return err
}
@ -92,7 +92,7 @@ func (s startCmd) startInProcess() error {
}
home := cfg.RootDir
app, err := s.app(home, s.logger)
app, err := s.appCreator(home, s.logger)
if err != nil {
return err
}

View File

@ -1,12 +1,10 @@
package server
import (
"fmt"
"os"
// "os"
"testing"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
@ -15,7 +13,7 @@ import (
)
func TestStartStandAlone(t *testing.T) {
defer setupViper()()
defer setupViper(t)()
logger := log.NewNopLogger()
initCmd := InitCmd(mock.GenInitOptions, logger)
@ -26,14 +24,15 @@ func TestStartStandAlone(t *testing.T) {
viper.Set(flagWithTendermint, false)
viper.Set(flagAddress, "localhost:11122")
startCmd := StartCmd(mock.NewApp, logger)
startCmd.Flags().Set(flagAddress, FreeTCPAddr(t)) // set to a new free address
timeout := time.Duration(3) * time.Second
err = runOrTimeout(startCmd, timeout)
require.NoError(t, err)
RunOrTimeout(startCmd, timeout, t)
}
/*
func TestStartWithTendermint(t *testing.T) {
defer setupViper()()
defer setupViper(t)()
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).
With("module", "mock-cmd")
@ -45,28 +44,12 @@ func TestStartWithTendermint(t *testing.T) {
// set up app and start up
viper.Set(flagWithTendermint, true)
startCmd := StartCmd(mock.NewApp, logger)
startCmd.Flags().Set(flagAddress, FreeTCPAddr(t)) // set to a new free address
timeout := time.Duration(3) * time.Second
err = runOrTimeout(startCmd, timeout)
require.NoError(t, err)
}
//a, _ := startCmd.Flags().GetString(flagAddress)
//panic(a)
func runOrTimeout(cmd *cobra.Command, timeout time.Duration) error {
done := make(chan error)
go func(out chan<- error) {
// this should NOT exit
err := cmd.RunE(nil, nil)
if err != nil {
out <- err
}
out <- fmt.Errorf("start died for unknown reasons")
}(done)
timer := time.NewTimer(timeout)
select {
case err := <-done:
return err
case <-timer.C:
return nil
}
RunOrTimeout(startCmd, timeout, t)
}
*/

82
server/test_helpers.go Normal file
View File

@ -0,0 +1,82 @@
package server
import (
"fmt"
"io/ioutil"
"net"
"os"
"testing"
"time"
"github.com/cosmos/cosmos-sdk/mock"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/tendermint/tmlibs/cli"
"github.com/tendermint/tmlibs/log"
)
// Get a free address for a test tendermint server
// protocol is either tcp, http, etc
func FreeTCPAddr(t *testing.T) string {
l, err := net.Listen("tcp", "0.0.0.0:0")
defer l.Close()
require.Nil(t, err)
port := l.Addr().(*net.TCPAddr).Port
addr := fmt.Sprintf("tcp://0.0.0.0:%d", port)
return addr
}
// setupViper creates a homedir to run inside,
// and returns a cleanup function to defer
func setupViper(t *testing.T) func() {
rootDir, err := ioutil.TempDir("", "mock-sdk-cmd")
require.Nil(t, err)
viper.Set(cli.HomeFlag, rootDir)
return func() {
os.RemoveAll(rootDir)
}
}
// Begin the server pass up the channel to close
// NOTE pass up the channel so it can be closed at the end of the process
func StartServer(t *testing.T) chan error {
defer setupViper(t)()
// init server
initCmd := InitCmd(mock.GenInitOptions, log.NewNopLogger())
err := initCmd.RunE(nil, nil)
require.NoError(t, err)
// start server
viper.Set(flagWithTendermint, true)
startCmd := StartCmd(mock.NewApp, log.NewNopLogger())
startCmd.Flags().Set(flagAddress, FreeTCPAddr(t)) // set to a new free address
startCmd.Flags().Set("rpc.laddr", FreeTCPAddr(t)) // set to a new free address
timeout := time.Duration(3) * time.Second
return RunOrTimeout(startCmd, timeout, t)
}
// Run or Timout RunE of command passed in
func RunOrTimeout(cmd *cobra.Command, timeout time.Duration, t *testing.T) chan error {
done := make(chan error)
go func(out chan<- error) {
// this should NOT exit
err := cmd.RunE(nil, nil)
if err != nil {
out <- err
}
out <- fmt.Errorf("start died for unknown reasons")
}(done)
timer := time.NewTimer(timeout)
select {
case err := <-done:
require.NoError(t, err)
case <-timer.C:
return done
}
return done
}

View File

@ -19,7 +19,7 @@ const (
func LoadIAVLStore(db dbm.DB, id CommitID) (CommitStore, error) {
tree := iavl.NewVersionedTree(db, defaultIAVLCacheSize)
err := tree.LoadVersion(id.Version)
_, err := tree.LoadVersion(id.Version)
if err != nil {
return nil, err
}
@ -119,6 +119,13 @@ func (st *iavlStore) Iterator(start, end []byte) Iterator {
return newIAVLIterator(st.tree.Tree(), start, end, true)
}
func (st *iavlStore) Subspace(prefix []byte) Iterator {
end := make([]byte, len(prefix))
copy(end, prefix)
end[len(end)-1]++
return st.Iterator(prefix, end)
}
// Implements IterKVStore.
func (st *iavlStore) ReverseIterator(start, end []byte) Iterator {
return newIAVLIterator(st.tree.Tree(), start, end, false)
@ -134,7 +141,7 @@ func (st *iavlStore) ReverseIterator(start, end []byte) Iterator {
func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
if len(req.Data) == 0 {
msg := "Query cannot be zero length"
return sdk.ErrTxParse(msg).Result().ToQuery()
return sdk.ErrTxDecode(msg).Result().ToQuery()
}
tree := st.tree

View File

@ -82,6 +82,26 @@ func TestIAVLIterator(t *testing.T) {
}
}
func TestIAVLSubspace(t *testing.T) {
db := dbm.NewMemDB()
tree, _ := newTree(t, db)
iavlStore := newIAVLStore(tree, numHistory)
iavlStore.Set([]byte("test1"), []byte("test1"))
iavlStore.Set([]byte("test2"), []byte("test2"))
iavlStore.Set([]byte("test3"), []byte("test3"))
iter := iavlStore.Subspace([]byte("test"))
expected := []string{"test1", "test2", "test3"}
for i := 0; iter.Valid(); iter.Next() {
expectedKey := expected[i]
key, value := iter.Key(), iter.Value()
assert.EqualValues(t, key, expectedKey)
assert.EqualValues(t, value, expectedKey)
i += 1
}
}
func TestIAVLStoreQuery(t *testing.T) {
db := dbm.NewMemDB()
tree := iavl.NewVersionedTree(db, cacheSize)

View File

@ -1,7 +1,7 @@
package store
import (
"github.com/tendermint/go-wire"
"github.com/cosmos/cosmos-sdk/wire"
)
var cdc = wire.NewCodec()

320
tests/tests.go Normal file
View File

@ -0,0 +1,320 @@
package tests
import (
"bufio"
"bytes"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
//"strings"
"testing"
"time"
"github.com/cosmos/cosmos-sdk/server"
"github.com/stretchr/testify/require"
)
// Tests assume the `basecoind` and `basecli` binaries
// have been built and are located in `./build`
// TODO remove test dirs if tests are successful
//nolint
var (
basecoind = "build/basecoind"
basecli = "build/basecli"
basecoindDir = "./tmp-basecoind-tests"
basecliDir = "./tmp-basecli-tests"
ACCOUNTS = []string{"alice", "bob", "charlie", "igor"}
alice = ACCOUNTS[0]
bob = ACCOUNTS[1]
charlie = ACCOUNTS[2]
igor = ACCOUNTS[3]
)
func gopath() string {
return filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "cosmos", "cosmos-sdk")
}
func whereIsBasecoind() string {
return filepath.Join(gopath(), basecoind)
}
func whereIsBasecli() string {
return filepath.Join(gopath(), basecli)
}
// Init Basecoin Test
func TestInitBasecoin(t *testing.T, home string) string {
var err error
password := "some-random-password"
initBasecoind := exec.Command(whereIsBasecoind(), "init", "--home", home)
cmdWriter, err := initBasecoind.StdinPipe()
require.Nil(t, err)
buf := new(bytes.Buffer)
initBasecoind.Stdout = buf
if err = initBasecoind.Start(); err != nil {
t.Error(err)
}
_, err = cmdWriter.Write([]byte(password))
require.Nil(t, err)
cmdWriter.Close()
if err = initBasecoind.Wait(); err != nil {
t.Error(err)
}
// get seed from initialization
theOutput := strings.Split(buf.String(), "\n")
var seedLine int
for _seedLine, o := range theOutput {
if strings.HasPrefix(string(o), "Secret phrase") {
seedLine = _seedLine + 1
break
}
}
seed := string(theOutput[seedLine])
// enable indexing
err = appendToFile(path.Join(home, "config", "config.toml"), "\n\n[tx_indexing]\nindex_all_tags = true\n")
require.Nil(t, err)
return seed
}
func appendToFile(path string, text string) error {
f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
return err
}
defer f.Close()
if _, err = f.WriteString(text); err != nil {
return err
}
return nil
}
func makeKeys() error {
for _, acc := range ACCOUNTS {
makeKeys := exec.Command(whereIsBasecli(), "keys", "add", acc, "--home", basecliDir)
cmdWriter, err := makeKeys.StdinPipe()
if err != nil {
return err
}
makeKeys.Stdout = os.Stdout
if err := makeKeys.Start(); err != nil {
return err
}
cmdWriter.Write([]byte("1234567890"))
if err != nil {
return err
}
cmdWriter.Close()
if err := makeKeys.Wait(); err != nil {
return err
}
}
return nil
}
func _TestSendCoins(t *testing.T) {
if err := StartServer(); err != nil {
t.Error(err)
}
// send some coins
// [zr] where dafuq do I get a FROM (oh, use --name)
sendTo := fmt.Sprintf("--to=%s", bob)
sendFrom := fmt.Sprintf("--from=%s", alice)
cmdOut, err := exec.Command(whereIsBasecli(), "send", sendTo, "--amount=1000mycoin", sendFrom, "--seq=0").Output()
if err != nil {
t.Error(err)
}
fmt.Printf("sent: %s", string(cmdOut))
}
// expects TestInitBaseCoin to have been run
func StartServer() error {
// straight outta https://nathanleclaire.com/blog/2014/12/29/shelled-out-commands-in-golang/
cmdName := whereIsBasecoind()
cmdArgs := []string{"start", "--home", basecoindDir}
cmd := exec.Command(cmdName, cmdArgs...)
cmdReader, err := cmd.StdoutPipe()
if err != nil {
return err
}
scanner := bufio.NewScanner(cmdReader)
go func() {
for scanner.Scan() {
fmt.Printf("running [basecoind start] %s\n", scanner.Text())
}
}()
err = cmd.Start()
if err != nil {
return err
}
err = cmd.Wait()
if err != nil {
return err
}
time.Sleep(5 * time.Second)
return nil
// TODO return cmd.Process so that we can later do something like:
// cmd.Process.Kill()
// see: https://stackoverflow.com/questions/11886531/terminating-a-process-started-with-os-exec-in-golang
}
// Init Basecoin Test
func InitServerForTest(t *testing.T) {
Clean()
var err error
password := "some-random-password"
usePassword := exec.Command("echo", password)
initBasecoind := exec.Command(whereIsBasecoind(), "init", "--home", basecoindDir)
initBasecoind.Stdin, err = usePassword.StdoutPipe()
require.Nil(t, err)
initBasecoind.Stdout = os.Stdout
err = initBasecoind.Start()
require.Nil(t, err)
err = usePassword.Run()
require.Nil(t, err)
err = initBasecoind.Wait()
require.Nil(t, err)
err = makeKeys()
require.Nil(t, err)
}
// expects TestInitBaseCoin to have been run
func StartNodeServerForTest(t *testing.T, home string) *exec.Cmd {
cmdName := whereIsBasecoind()
cmdArgs := []string{"start", "--home", home}
cmd := exec.Command(cmdName, cmdArgs...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
require.Nil(t, err)
// FIXME: if there is a nondeterministic node start failure,
// we should probably make this read the logs to wait for RPC
time.Sleep(time.Second * 2)
return cmd
}
// expects TestInitBaseCoin to have been run
func StartLCDServerForTest(t *testing.T, home, chainID string) (cmd *exec.Cmd, port string) {
cmdName := whereIsBasecli()
port = strings.Split(server.FreeTCPAddr(t), ":")[2]
cmdArgs := []string{
"rest-server",
"--home",
home,
"--bind",
fmt.Sprintf("localhost:%s", port),
"--chain-id",
chainID,
}
cmd = exec.Command(cmdName, cmdArgs...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
require.Nil(t, err)
time.Sleep(time.Second * 2) // TODO: LOL
return cmd, port
}
// clean the directories
func Clean() {
// ignore errors b/c the dirs may not yet exist
err := os.Remove(basecoindDir)
panic(err)
err = os.Remove(basecliDir)
panic(err)
}
/*
chainID = "staking_test"
testDir = "./tmp_tests"
)
func runTests() {
if err := os.Mkdir(testDir, 0666); err != nil {
panic(err)
}
defer os.Remove(testDir)
// make some keys
//if err := makeKeys(); err != nil {
// panic(err)
//}
if err := initServer(); err != nil {
fmt.Printf("Err: %v", err)
panic(err)
}
}
func initServer() error {
serveDir := filepath.Join(testDir, "server")
//serverLog := filepath.Join(testDir, "gaia-node.log")
// get RICH
keyOut, err := exec.Command(GAIA, CLIENT_EXE, "keys", "get", "alice").Output()
if err != nil {
fmt.Println("one")
return err
}
key := strings.Split(string(keyOut), "\t")
fmt.Printf("wit:%s", key[2])
outByte, err := exec.Command(GAIA, SERVER_EXE, "init", "--static", fmt.Sprintf("--chain-id=%s", chainID), fmt.Sprintf("--home=%s", serveDir), key[2]).Output()
if err != nil {
fmt.Println("teo")
fmt.Printf("Error: %v", err)
return err
}
fmt.Sprintf("OUT: %s", string(outByte))
return nil
}
*/

56
tools/Gopkg.lock generated Normal file
View File

@ -0,0 +1,56 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/alecthomas/gometalinter"
packages = ["."]
revision = "46cc1ea3778b247666c2949669a3333c532fa9c6"
version = "v2.0.5"
[[projects]]
branch = "master"
name = "github.com/alecthomas/units"
packages = ["."]
revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
[[projects]]
branch = "master"
name = "github.com/google/shlex"
packages = ["."]
revision = "6f45313302b9c56850fc17f99e40caebce98c716"
[[projects]]
name = "github.com/nicksnyder/go-i18n"
packages = [
"i18n",
"i18n/bundle",
"i18n/language",
"i18n/translation"
]
revision = "0dc1626d56435e9d605a29875701721c54bc9bbd"
version = "v1.10.0"
[[projects]]
name = "github.com/pelletier/go-toml"
packages = ["."]
revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8"
version = "v1.1.0"
[[projects]]
branch = "v3-unstable"
name = "gopkg.in/alecthomas/kingpin.v3-unstable"
packages = ["."]
revision = "b8d601de6db1f3b56a99ffe9051eb708574bc1cd"
[[projects]]
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "7f97868eec74b32b0982dd158a51a446d1da7eb5"
version = "v2.1.1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "bb8cda576a5c4dda202435f43a46ae50a254181a4bf22c6af6f4d3d03079d509"
solver-name = "gps-cdcl"
solver-version = 1

34
tools/Gopkg.toml Normal file
View File

@ -0,0 +1,34 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/alecthomas/gometalinter"
version = "2.0.5"
[prune]
go-tests = true
unused-packages = true

View File

@ -1,40 +1,40 @@
all: install_glide check get_vendor_deps install
all: install
########################################
### Glide
### DEP
GLIDE = github.com/tendermint/glide
GLIDE_CHECK := $(shell command -v glide 2> /dev/null)
DEP = github.com/golang/dep/cmd/dep
DEP_CHECK := $(shell command -v dep 2> /dev/null)
check:
ifndef GLIDE_CHECK
@echo "No glide in path. Install with 'make install_glide'."
check_tools:
ifndef DEP_CHECK
@echo "No dep in path. Install with 'make get_tools'."
else
@echo "Found glide in path."
@echo "Found dep in path."
endif
install_glide:
ifdef GLIDE_CHECK
@echo "Glide is already installed. Run 'make update_glide' to update."
get_tools:
ifdef DEP_CHECK
@echo "Dep is already installed. Run 'make update_tools' to update."
else
@echo "$(ansi_grn)Installing glide$(ansi_end)"
go get -v $(GLIDE)
@echo "$(ansi_grn)Installing dep$(ansi_end)"
go get -v $(DEP)
endif
update_glide:
@echo "$(ansi_grn)Updating glide$(ansi_end)"
go get -u -v $(GLIDE)
update_tools:
@echo "$(ansi_grn)Updating dep$(ansi_end)"
go get -u -v $(DEP)
########################################
### Install tools
get_vendor_deps: check
get_vendor_deps: check_tools
@rm -rf vendor/
@echo "--> Running glide install"
@glide install
@echo "--> Running dep ensure"
@dep ensure -v
install: get_vendor_deps
@echo "$(ansi_grn)Installing tools$(ansi_end)"
@ -42,10 +42,7 @@ install: get_vendor_deps
go build -o bin/go-vendorinstall go-vendorinstall/*.go
@echo "$(ansi_yel)Install gometalinter.v2$(ansi_end)"
GOBIN=$(CURDIR)/bin ./bin/go-vendorinstall github.com/alecthomas/gometalinter
@echo "$(ansi_yel)Install shelldown$(ansi_end)"
GOBIN=$(CURDIR)/bin ./bin/go-vendorinstall github.com/rigelrozanski/shelldown/cmd/shelldown
GOBIN="$(CURDIR)/bin" ./bin/go-vendorinstall github.com/alecthomas/gometalinter
@echo "$(ansi_grn)Done installing tools$(ansi_end)"
@ -62,4 +59,4 @@ ansi_end=\033[0m
# 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: check install_glide update_glide get_vendor_deps install
.PHONY: check_tools install_tools update_tools get_vendor_deps install

18
tools/glide.lock generated
View File

@ -1,18 +0,0 @@
hash: 934ad5be72c9c240e8555eb6e1b2319840266c04c0fa9e024008cf841c0cee65
updated: 2018-02-23T19:33:08.596187+01:00
imports:
- name: github.com/alecthomas/gometalinter
version: 46cc1ea3778b247666c2949669a3333c532fa9c6
- name: github.com/rigelrozanski/common
version: f691f115798593d783b9999b1263c2f4ffecc439
- name: github.com/rigelrozanski/shelldown
version: 2e18b6eb9bf428aa524e71433296e0b7c73ae0a3
subpackages:
- cmd/shelldown
- name: github.com/spf13/cobra
version: 7b2c5ac9fc04fc5efafb60700713d4fa609b777b
- name: github.com/spf13/pflag
version: e57e3eeb33f795204c1ca35f56c44f83227c6e66
- name: github.com/spf13/viper
version: 25b30aa063fc18e48662b86996252eabdcf2f0c7
testImports: []

View File

@ -1,13 +0,0 @@
package: github.com/cosmos/cosmos-sdk/tools
import:
- package: github.com/alecthomas/gometalinter
version: ^2.0.5
- package: github.com/rigelrozanski/shelldown
subpackages:
- cmd/shelldown
- package: github.com/spf13/pflag
version: v1.0.0
- package: github.com/spf13/cobra
version: v0.0.1
- package: github.com/spf13/viper
version: ^1.0.0

View File

@ -1,12 +1,11 @@
package main
import (
// Include dependencies here so glide picks them up
// and installs sub-dependencies.
// Include dependencies here so dep picks them up
// and installs sub-dependencies.
// TODO: Ideally this gets auto-imported on glide update.
// Any way to make that happen?
_ "github.com/rigelrozanski/common"
)
// TODO: Ideally this gets auto-imported on dep update.
// Any way to make that happen?
// NOTE: problems with this import because its a main not a lib
// _ "github.com/alecthomas/gometalinter"
func main() {}

View File

@ -2,13 +2,17 @@ package types
import (
crypto "github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common"
)
// Address in go-crypto style
type Address = cmn.HexBytes
// Account is a standard account using a sequence number for replay protection
// and a pubkey for authentication.
type Account interface {
GetAddress() crypto.Address
SetAddress(crypto.Address) error // errors if already set.
GetAddress() Address
SetAddress(Address) error // errors if already set.
GetPubKey() crypto.PubKey // can return nil.
SetPubKey(crypto.PubKey) error
@ -26,10 +30,10 @@ type Account interface {
// AccountMapper stores and retrieves accounts from stores
// retrieved from the context.
type AccountMapper interface {
NewAccountWithAddress(ctx Context, addr crypto.Address) Account
GetAccount(ctx Context, addr crypto.Address) Account
NewAccountWithAddress(ctx Context, addr Address) Account
GetAccount(ctx Context, addr Address) Account
SetAccount(ctx Context, acc Account)
}
// Application function variable used to unmarshal account
type ParseAccount func([]byte) (Account, error)
// AccountDecoder unmarshals account bytes
type AccountDecoder func(accountBytes []byte) (Account, error)

View File

@ -139,8 +139,14 @@ func (coins Coins) IsGTE(coinsB Coins) bool {
}
// IsZero returns true if there are no coins
// or all coins are zero.
func (coins Coins) IsZero() bool {
return len(coins) == 0
for _, coin := range coins {
if !coin.IsZero() {
return false
}
}
return true
}
// IsEqual returns true if the two sets of Coins have the same value

View File

@ -3,8 +3,6 @@ package types
import (
"fmt"
"runtime"
"github.com/tendermint/go-crypto"
)
// ABCI Response Code
@ -19,17 +17,20 @@ func (code CodeType) IsOK() bool {
}
// ABCI Response Codes
// Base SDK reserves 0 ~ 99.
// Base SDK reserves 0 - 99.
const (
CodeOK CodeType = 0
CodeInternal CodeType = 1
CodeTxParse CodeType = 2
CodeBadNonce CodeType = 3
CodeUnauthorized CodeType = 4
CodeInsufficientFunds CodeType = 5
CodeUnknownRequest CodeType = 6
CodeUnrecognizedAddress CodeType = 7
CodeInvalidSequence CodeType = 8
CodeOK CodeType = 0
CodeInternal CodeType = 1
CodeTxDecode CodeType = 2
CodeInvalidSequence CodeType = 3
CodeUnauthorized CodeType = 4
CodeInsufficientFunds CodeType = 5
CodeUnknownRequest CodeType = 6
CodeInvalidAddress CodeType = 7
CodeInvalidPubKey CodeType = 8
CodeUnknownAddress CodeType = 9
CodeInsufficientCoins CodeType = 10
CodeInvalidCoins CodeType = 11
CodeGenesisParse CodeType = 0xdead // TODO: remove ?
)
@ -39,22 +40,28 @@ func CodeToDefaultMsg(code CodeType) string {
switch code {
case CodeInternal:
return "Internal error"
case CodeTxParse:
case CodeTxDecode:
return "Tx parse error"
case CodeGenesisParse:
return "Genesis parse error"
case CodeBadNonce:
return "Bad nonce"
case CodeInvalidSequence:
return "Invalid sequence"
case CodeUnauthorized:
return "Unauthorized"
case CodeInsufficientFunds:
return "Insufficent funds"
case CodeUnknownRequest:
return "Unknown request"
case CodeUnrecognizedAddress:
return "Unrecognized address"
case CodeInvalidSequence:
return "Invalid sequence"
case CodeInvalidAddress:
return "Invalid address"
case CodeInvalidPubKey:
return "Invalid pubkey"
case CodeUnknownAddress:
return "Unknown address"
case CodeInsufficientCoins:
return "Insufficient coins"
case CodeInvalidCoins:
return "Invalid coins"
default:
return fmt.Sprintf("Unknown code %d", code)
}
@ -68,14 +75,14 @@ func CodeToDefaultMsg(code CodeType) string {
func ErrInternal(msg string) Error {
return newError(CodeInternal, msg)
}
func ErrTxParse(msg string) Error {
return newError(CodeTxParse, msg)
func ErrTxDecode(msg string) Error {
return newError(CodeTxDecode, msg)
}
func ErrGenesisParse(msg string) Error {
return newError(CodeGenesisParse, msg)
}
func ErrBadNonce(msg string) Error {
return newError(CodeBadNonce, msg)
func ErrInvalidSequence(msg string) Error {
return newError(CodeInvalidSequence, msg)
}
func ErrUnauthorized(msg string) Error {
return newError(CodeUnauthorized, msg)
@ -86,11 +93,20 @@ func ErrInsufficientFunds(msg string) Error {
func ErrUnknownRequest(msg string) Error {
return newError(CodeUnknownRequest, msg)
}
func ErrUnrecognizedAddress(addr crypto.Address) Error {
return newError(CodeUnrecognizedAddress, addr.String())
func ErrInvalidAddress(msg string) Error {
return newError(CodeInvalidAddress, msg)
}
func ErrInvalidSequence(msg string) Error {
return newError(CodeInvalidSequence, msg)
func ErrUnknownAddress(msg string) Error {
return newError(CodeUnknownAddress, msg)
}
func ErrInvalidPubKey(msg string) Error {
return newError(CodeInvalidPubKey, msg)
}
func ErrInsufficientCoins(msg string) Error {
return newError(CodeInsufficientCoins, msg)
}
func ErrInvalidCoins(msg string) Error {
return newError(CodeInvalidCoins, msg)
}
//----------------------------------------

53
types/errors_test.go Normal file
View File

@ -0,0 +1,53 @@
package types
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
var codeTypes = []CodeType{
CodeInternal,
CodeTxDecode,
CodeInvalidSequence,
CodeUnauthorized,
CodeInsufficientFunds,
CodeUnknownRequest,
CodeUnknownAddress,
CodeInvalidPubKey,
CodeGenesisParse,
}
type errFn func(msg string) Error
var errFns = []errFn{
ErrInternal,
ErrTxDecode,
ErrInvalidSequence,
ErrUnauthorized,
ErrInsufficientFunds,
ErrUnknownRequest,
ErrUnknownAddress,
ErrInvalidPubKey,
ErrGenesisParse,
}
func TestCodeType(t *testing.T) {
assert.True(t, CodeOK.IsOK())
for _, c := range codeTypes {
assert.False(t, c.IsOK())
msg := CodeToDefaultMsg(c)
assert.False(t, strings.HasPrefix(msg, "Unknown code"))
}
}
func TestErrFn(t *testing.T) {
for i, errFn := range errFns {
err := errFn("")
codeType := codeTypes[i]
assert.Equal(t, err.ABCICode(), codeType)
assert.Equal(t, err.Result().Code, codeType)
}
}

232
types/stdlib/stdlib.go Normal file
View File

@ -0,0 +1,232 @@
package types
import (
"errors"
sdk "github.com/cosmos/cosmos-sdk/types"
wire "github.com/cosmos/cosmos-sdk/wire"
)
type ListMapper interface { // Solidity list like structure
Len(sdk.Context) int64
Get(sdk.Context, int64, interface{})
Set(sdk.Context, int64, interface{})
Push(sdk.Context, interface{})
Iterate(sdk.Context, interface{}, func(sdk.Context, int64))
}
type listMapper struct {
key sdk.StoreKey
cdc *wire.Codec
lk []byte
}
func NewListMapper(cdc *wire.Codec, key sdk.StoreKey) ListMapper {
lk, err := cdc.MarshalBinary(int64(-1))
if err != nil {
panic(err)
}
return listMapper{
key: key,
cdc: cdc,
lk: lk,
}
}
func (lm listMapper) Len(ctx sdk.Context) int64 {
store := ctx.KVStore(lm.key)
bz := store.Get(lm.lk)
if bz == nil {
zero, err := lm.cdc.MarshalBinary(0)
if err != nil {
panic(err)
}
store.Set(lm.lk, zero)
return 0
}
var res int64
if err := lm.cdc.UnmarshalBinary(bz, &res); err != nil {
panic(err)
}
return res
}
func (lm listMapper) Get(ctx sdk.Context, index int64, ptr interface{}) {
if index < 0 {
panic(errors.New(""))
}
store := ctx.KVStore(lm.key)
bz := store.Get(marshalInt64(lm.cdc, index))
if err := lm.cdc.UnmarshalBinary(bz, ptr); err != nil {
panic(err)
}
}
func (lm listMapper) Set(ctx sdk.Context, index int64, value interface{}) {
if index < 0 {
panic(errors.New(""))
}
store := ctx.KVStore(lm.key)
bz, err := lm.cdc.MarshalBinary(value)
if err != nil {
panic(err)
}
store.Set(marshalInt64(lm.cdc, index), bz)
}
func (lm listMapper) Push(ctx sdk.Context, value interface{}) {
length := lm.Len(ctx)
lm.Set(ctx, length, value)
store := ctx.KVStore(lm.key)
store.Set(lm.lk, marshalInt64(lm.cdc, length+1))
}
func (lm listMapper) Iterate(ctx sdk.Context, ptr interface{}, fn func(sdk.Context, int64)) {
length := lm.Len(ctx)
for i := int64(0); i < length; i++ {
lm.Get(ctx, i, ptr)
fn(ctx, i)
}
}
type QueueMapper interface {
Push(sdk.Context, interface{})
Peek(sdk.Context, interface{})
Pop(sdk.Context)
IsEmpty(sdk.Context) bool
Iterate(sdk.Context, interface{}, func(sdk.Context) bool)
}
type queueMapper struct {
key sdk.StoreKey
cdc *wire.Codec
ik []byte
}
func NewQueueMapper(cdc *wire.Codec, key sdk.StoreKey) QueueMapper {
ik, err := cdc.MarshalBinary(int64(-1))
if err != nil {
panic(err)
}
return queueMapper{
key: key,
cdc: cdc,
ik: ik,
}
}
type queueInfo struct {
// begin <= elems < end
Begin int64
End int64
}
func (info queueInfo) validateBasic() error {
if info.End < info.Begin || info.Begin < 0 || info.End < 0 {
return errors.New("")
}
return nil
}
func (info queueInfo) isEmpty() bool {
return info.Begin == info.End
}
func (qm queueMapper) getQueueInfo(store sdk.KVStore) queueInfo {
bz := store.Get(qm.ik)
if bz == nil {
store.Set(qm.ik, marshalQueueInfo(qm.cdc, queueInfo{0, 0}))
return queueInfo{0, 0}
}
var info queueInfo
if err := qm.cdc.UnmarshalBinary(bz, &info); err != nil {
panic(err)
}
if err := info.validateBasic(); err != nil {
panic(err)
}
return info
}
func (qm queueMapper) setQueueInfo(store sdk.KVStore, info queueInfo) {
bz, err := qm.cdc.MarshalBinary(info)
if err != nil {
panic(err)
}
store.Set(qm.ik, bz)
}
func (qm queueMapper) Push(ctx sdk.Context, value interface{}) {
store := ctx.KVStore(qm.key)
info := qm.getQueueInfo(store)
bz, err := qm.cdc.MarshalBinary(value)
if err != nil {
panic(err)
}
store.Set(marshalInt64(qm.cdc, info.End), bz)
info.End++
qm.setQueueInfo(store, info)
}
func (qm queueMapper) Peek(ctx sdk.Context, ptr interface{}) {
store := ctx.KVStore(qm.key)
info := qm.getQueueInfo(store)
bz := store.Get(marshalInt64(qm.cdc, info.Begin))
if err := qm.cdc.UnmarshalBinary(bz, ptr); err != nil {
panic(err)
}
}
func (qm queueMapper) Pop(ctx sdk.Context) {
store := ctx.KVStore(qm.key)
info := qm.getQueueInfo(store)
store.Delete(marshalInt64(qm.cdc, info.Begin))
info.Begin++
qm.setQueueInfo(store, info)
}
func (qm queueMapper) IsEmpty(ctx sdk.Context) bool {
store := ctx.KVStore(qm.key)
info := qm.getQueueInfo(store)
return info.isEmpty()
}
func (qm queueMapper) Iterate(ctx sdk.Context, ptr interface{}, fn func(sdk.Context) bool) {
store := ctx.KVStore(qm.key)
info := qm.getQueueInfo(store)
var i int64
for i = info.Begin; i < info.End; i++ {
key := marshalInt64(qm.cdc, i)
bz := store.Get(key)
if err := qm.cdc.UnmarshalBinary(bz, ptr); err != nil {
panic(err)
}
store.Delete(key)
if fn(ctx) {
break
}
}
info.Begin = i
qm.setQueueInfo(store, info)
}
func marshalQueueInfo(cdc *wire.Codec, info queueInfo) []byte {
bz, err := cdc.MarshalBinary(info)
if err != nil {
panic(err)
}
return bz
}
func marshalInt64(cdc *wire.Codec, i int64) []byte {
bz, err := cdc.MarshalBinary(i)
if err != nil {
panic(err)
}
return bz
}

View File

@ -0,0 +1,69 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
dbm "github.com/tendermint/tmlibs/db"
abci "github.com/tendermint/abci/types"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
wire "github.com/cosmos/cosmos-sdk/wire"
)
type S struct {
I int64
B bool
}
func defaultComponents(key sdk.StoreKey) (sdk.Context, *wire.Codec) {
db := dbm.NewMemDB()
cms := store.NewCommitMultiStore(db)
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db)
cms.LoadLatestVersion()
ctx := sdk.NewContext(cms, abci.Header{}, false, nil)
cdc := wire.NewCodec()
return ctx, cdc
}
func TestListMapper(t *testing.T) {
key := sdk.NewKVStoreKey("list")
ctx, cdc := defaultComponents(key)
lm := NewListMapper(cdc, key)
val := S{1, true}
var res S
lm.Push(ctx, val)
assert.Equal(t, int64(1), lm.Len(ctx))
lm.Get(ctx, int64(0), &res)
assert.Equal(t, val, res)
val = S{2, false}
lm.Set(ctx, int64(0), val)
lm.Get(ctx, int64(0), &res)
assert.Equal(t, val, res)
}
func TestQueueMapper(t *testing.T) {
key := sdk.NewKVStoreKey("queue")
ctx, cdc := defaultComponents(key)
qm := NewQueueMapper(cdc, key)
val := S{1, true}
var res S
qm.Push(ctx, val)
qm.Peek(ctx, &res)
assert.Equal(t, val, res)
qm.Pop(ctx)
empty := qm.IsEmpty(ctx)
assert.Equal(t, true, empty)
assert.Panics(t, func() { qm.Peek(ctx, &res) })
}

View File

@ -1,7 +1,7 @@
package types
import (
crypto "github.com/tendermint/go-crypto"
"encoding/json"
)
// Transactions messages must fulfill the Msg
@ -24,19 +24,17 @@ type Msg interface {
// 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() []crypto.Address
GetSigners() []Address
}
//__________________________________________________________
// Transactions objects must fulfill the Tx
type Tx interface {
// Gets the Msg.
GetMsg() Msg
// The address that pays the base fee for this message. The fee is
// deducted before the Msg is processed.
GetFeePayer() crypto.Address
// 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
@ -49,26 +47,140 @@ type Tx interface {
var _ Tx = (*StdTx)(nil)
// StdTx is a standard way to wrap a Msg with Signatures.
// 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 {
Msg `json:"msg"`
Fee StdFee `json:"fee"`
Signatures []StdSignature `json:"signatures"`
}
func NewStdTx(msg Msg, sigs []StdSignature) StdTx {
func NewStdTx(msg Msg, fee StdFee, sigs []StdSignature) StdTx {
return StdTx{
Msg: msg,
Fee: fee,
Signatures: sigs,
}
}
//nolint
func (tx StdTx) GetMsg() Msg { return tx.Msg }
func (tx StdTx) GetFeePayer() crypto.Address { return tx.Signatures[0].PubKey.Address() } // XXX but PubKey is optional!
func (tx StdTx) GetSignatures() []StdSignature { return tx.Signatures }
//-------------------------------------
// FeePayer returns the address responsible for paying the fees
// for the transactions. It's the first address returned by msg.GetSigners().
// If GetSigners() is empty, this panics.
func FeePayer(tx Tx) Address {
return tx.GetMsg().GetSigners()[0]
}
// Application function variable used to unmarshal transaction bytes
//__________________________________________________________
// 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 Coins `json"amount"`
Gas int64 `json"gas"`
}
func NewStdFee(gas int64, amount ...Coin) StdFee {
return StdFee{
Amount: amount,
Gas: gas,
}
}
func (fee StdFee) Bytes() []byte {
// normalize. XXX
// this is a sign of something ugly
// (in the lcd_test, client side its null,
// server side its [])
if len(fee.Amount) == 0 {
fee.Amount = Coins{}
}
bz, err := json.Marshal(fee) // TODO
if err != nil {
panic(err)
}
return bz
}
//__________________________________________________________
// StdSignDoc is replay-prevention structure.
// It includes the result of msg.GetSignBytes(),
// as well as the ChainID (prevent cross chain replay)
// and the Sequence numbers for each signature (prevent
// inchain replay and enforce tx ordering per account).
type StdSignDoc struct {
ChainID string `json:"chain_id"`
Sequences []int64 `json:"sequences"`
FeeBytes []byte `json:"fee_bytes"`
MsgBytes []byte `json:"msg_bytes"`
AltBytes []byte `json:"alt_bytes"`
}
// StdSignBytes returns the bytes to sign for a transaction.
// TODO: change the API to just take a chainID and StdTx ?
func StdSignBytes(chainID string, sequences []int64, fee StdFee, msg Msg) []byte {
bz, err := json.Marshal(StdSignDoc{
ChainID: chainID,
Sequences: sequences,
FeeBytes: fee.Bytes(),
MsgBytes: msg.GetSignBytes(),
})
if err != nil {
panic(err)
}
return bz
}
// StdSignMsg is a convenience structure for passing along
// a Msg with the other requirements for a StdSignDoc before
// it is signed. For use in the CLI.
type StdSignMsg struct {
ChainID string
Sequences []int64
Fee StdFee
Msg Msg
// XXX: Alt
}
func (msg StdSignMsg) Bytes() []byte {
return StdSignBytes(msg.ChainID, msg.Sequences, msg.Fee, msg.Msg)
}
//__________________________________________________________
// TxDeocder unmarshals transaction bytes
type TxDecoder func(txBytes []byte) (Tx, Error)
//__________________________________________________________
var _ Msg = (*TestMsg)(nil)
// msg type for testing
type TestMsg struct {
signers []Address
}
func NewTestMsg(addrs ...Address) *TestMsg {
return &TestMsg{
signers: addrs,
}
}
func (msg *TestMsg) Type() string { return "TestMsg" }
func (msg *TestMsg) Get(key interface{}) (value interface{}) { return nil }
func (msg *TestMsg) GetSignBytes() []byte {
bz, err := json.Marshal(msg.signers)
if err != nil {
panic(err)
}
return bz
}
func (msg *TestMsg) ValidateBasic() Error { return nil }
func (msg *TestMsg) GetSigners() []Address {
return msg.signers
}

30
types/tx_msg_test.go Normal file
View File

@ -0,0 +1,30 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
crypto "github.com/tendermint/go-crypto"
)
func newStdFee() StdFee {
return NewStdFee(100,
Coin{"atom", 150},
)
}
func TestStdTx(t *testing.T) {
priv := crypto.GenPrivKeyEd25519()
addr := priv.PubKey().Address()
msg := NewTestMsg(addr)
fee := newStdFee()
sigs := []StdSignature{}
tx := NewStdTx(msg, fee, sigs)
assert.Equal(t, msg, tx.GetMsg())
assert.Equal(t, sigs, tx.GetSignatures())
feePayer := FeePayer(tx)
assert.Equal(t, addr, feePayer)
}

View File

@ -2,6 +2,7 @@ package version
import (
"fmt"
"net/http"
"github.com/spf13/cobra"
)
@ -11,14 +12,28 @@ var (
VersionCmd = &cobra.Command{
Use: "version",
Short: "Print the app version",
Run: doVersionCmd,
Run: printVersion,
}
)
func doVersionCmd(cmd *cobra.Command, args []string) {
func getVersion() string {
v := Version
if GitCommit != "" {
v = v + " " + GitCommit
}
return v
}
// CMD
func printVersion(cmd *cobra.Command, args []string) {
v := getVersion()
fmt.Println(v)
}
// REST
func VersionRequestHandler(w http.ResponseWriter, r *http.Request) {
v := getVersion()
w.Write([]byte(v))
}

View File

@ -6,10 +6,10 @@ package version
// TODO improve
const Maj = "0"
const Min = "11"
const Min = "12"
const Fix = "0"
const Version = "0.11.0"
const Version = "0.12.0"
// GitCommit set by build flags
var GitCommit = ""

55
wire/wire.go Normal file
View File

@ -0,0 +1,55 @@
package wire
import (
"bytes"
"reflect"
"github.com/tendermint/go-wire"
)
type Codec struct{}
func NewCodec() *Codec {
return &Codec{}
}
func (cdc *Codec) MarshalBinary(o interface{}) ([]byte, error) {
w, n, err := new(bytes.Buffer), new(int), new(error)
wire.WriteBinary(o, w, n, err)
return w.Bytes(), *err
}
func (cdc *Codec) UnmarshalBinary(bz []byte, o interface{}) error {
r, n, err := bytes.NewBuffer(bz), new(int), new(error)
rv := reflect.ValueOf(o)
if rv.Kind() == reflect.Ptr {
wire.ReadBinaryPtr(o, r, len(bz), n, err)
} else {
wire.ReadBinary(o, r, len(bz), n, err)
}
return *err
}
func (cdc *Codec) MarshalJSON(o interface{}) ([]byte, error) {
w, n, err := new(bytes.Buffer), new(int), new(error)
wire.WriteJSON(o, w, n, err)
return w.Bytes(), *err
}
func (cdc *Codec) UnmarshalJSON(bz []byte, o interface{}) (err error) {
rv := reflect.ValueOf(o)
if rv.Kind() == reflect.Ptr {
wire.ReadJSONPtr(o, bz, &err)
} else {
wire.ReadJSON(o, bz, &err)
}
return err
}
//----------------------------------------------
func RegisterCrypto(cdc *Codec) {
// TODO
}

View File

@ -1,90 +1,158 @@
package auth
import (
"bytes"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/spf13/viper"
)
// NewAnteHandler returns an AnteHandler that checks
// and increments sequence numbers, checks signatures,
// and deducts fees from the first signer.
func NewAnteHandler(accountMapper sdk.AccountMapper) sdk.AnteHandler {
return func(
ctx sdk.Context, tx sdk.Tx,
) (_ sdk.Context, _ sdk.Result, abort bool) {
// Deduct the fee from the fee payer.
// This is done first because it only
// requires fetching 1 account.
payerAddr := tx.GetFeePayer()
if payerAddr != nil {
payerAcc := accountMapper.GetAccount(ctx, payerAddr)
if payerAcc == nil {
return ctx,
sdk.ErrUnrecognizedAddress(payerAddr).Result(),
true
}
// TODO: Charge fee from payerAcc.
// TODO: accountMapper.SetAccount(ctx, payerAddr)
} else {
// TODO: Ensure that some other spam prevention is used.
}
var sigs = tx.GetSignatures()
// Assert that there are signatures.
var sigs = tx.GetSignatures()
if len(sigs) == 0 {
return ctx,
sdk.ErrUnauthorized("no signers").Result(),
true
}
// Ensure that sigs are correct.
var msg = tx.GetMsg()
var signerAddrs = msg.GetSigners()
var signerAccs = make([]sdk.Account, len(signerAddrs))
// TODO: can tx just implement message?
msg := tx.GetMsg()
// TODO: will this always be a stdtx? should that be used in the function signature?
stdTx, ok := tx.(sdk.StdTx)
if !ok {
return ctx, sdk.ErrInternal("tx must be sdk.StdTx").Result(), true
}
// Assert that number of signatures is correct.
var signerAddrs = msg.GetSigners()
if len(sigs) != len(signerAddrs) {
return ctx,
sdk.ErrUnauthorized("wrong number of signers").Result(),
true
}
// Check each nonce and sig.
// TODO Refactor out.
for i, sig := range sigs {
// Get the sign bytes (requires all sequence numbers and the fee)
sequences := make([]int64, len(signerAddrs))
for i := 0; i < len(signerAddrs); i++ {
sequences[i] = sigs[i].Sequence
}
fee := stdTx.Fee
chainID := ctx.ChainID()
// XXX: major hack; need to get ChainID
// into the app right away (#565)
if chainID == "" {
chainID = viper.GetString("chain-id")
}
signBytes := sdk.StdSignBytes(ctx.ChainID(), sequences, fee, msg)
var signerAcc = accountMapper.GetAccount(ctx, signerAddrs[i])
signerAccs[i] = signerAcc
// Check sig and nonce and collect signer accounts.
var signerAccs = make([]sdk.Account, len(signerAddrs))
for i := 0; i < len(sigs); i++ {
signerAddr, sig := signerAddrs[i], sigs[i]
// If no pubkey, set pubkey.
if signerAcc.GetPubKey() == nil {
err := signerAcc.SetPubKey(sig.PubKey)
if err != nil {
return ctx,
sdk.ErrInternal("setting PubKey on signer").Result(),
true
// check signature, return account with incremented nonce
signerAcc, res := processSig(
ctx, accountMapper,
signerAddr, sig, signBytes,
)
if !res.IsOK() {
return ctx, res, true
}
// first sig pays the fees
if i == 0 {
// TODO: min fee
if !fee.Amount.IsZero() {
signerAcc, res = deductFees(signerAcc, fee)
if !res.IsOK() {
return ctx, res, true
}
}
}
// Check and increment sequence number.
seq := signerAcc.GetSequence()
if seq != sig.Sequence {
return ctx,
sdk.ErrInvalidSequence("").Result(),
true
}
signerAcc.SetSequence(seq + 1)
// Check sig.
if !sig.PubKey.VerifyBytes(msg.GetSignBytes(), sig.Signature) {
return ctx,
sdk.ErrUnauthorized("").Result(),
true
}
// Save the account.
accountMapper.SetAccount(ctx, signerAcc)
signerAccs[i] = signerAcc
}
// cache the signer accounts in the context
ctx = WithSigners(ctx, signerAccs)
// TODO: tx tags (?)
return ctx, sdk.Result{}, false // continue...
}
}
// verify the signature and increment the sequence.
// if the account doesn't have a pubkey, set it.
func processSig(
ctx sdk.Context, am sdk.AccountMapper,
addr sdk.Address, sig sdk.StdSignature, signBytes []byte) (
acc sdk.Account, res sdk.Result) {
// Get the account.
acc = am.GetAccount(ctx, addr)
if acc == nil {
return nil, sdk.ErrUnknownAddress(addr.String()).Result()
}
// Check and increment sequence number.
seq := acc.GetSequence()
if seq != sig.Sequence {
return nil, sdk.ErrInvalidSequence(
fmt.Sprintf("Invalid sequence. Got %d, expected %d", sig.Sequence, seq)).Result()
}
acc.SetSequence(seq + 1)
// If pubkey is not known for account,
// set it from the StdSignature.
pubKey := acc.GetPubKey()
if pubKey.Empty() {
pubKey = sig.PubKey
if pubKey.Empty() {
return nil, sdk.ErrInvalidPubKey("PubKey not found").Result()
}
if !bytes.Equal(pubKey.Address(), addr) {
return nil, sdk.ErrInvalidPubKey(
fmt.Sprintf("PubKey does not match Signer address %v", addr)).Result()
}
err := acc.SetPubKey(pubKey)
if err != nil {
return nil, sdk.ErrInternal("setting PubKey on signer's account").Result()
}
}
// Check sig.
if !pubKey.VerifyBytes(signBytes, sig.Signature) {
return nil, sdk.ErrUnauthorized("signature verification failed").Result()
}
return
}
// Deduct the fee from the account.
// We could use the CoinKeeper (in addition to the AccountMapper,
// because the CoinKeeper doesn't give us accounts), but it seems easier to do this.
func deductFees(acc sdk.Account, fee sdk.StdFee) (sdk.Account, sdk.Result) {
coins := acc.GetCoins()
feeAmount := fee.Amount
newCoins := coins.Minus(feeAmount)
if !newCoins.IsNotNegative() {
errMsg := fmt.Sprintf("%s < %s", coins, feeAmount)
return nil, sdk.ErrInsufficientFunds(errMsg).Result()
}
acc.SetCoins(newCoins)
return acc, sdk.Result{}
}

325
x/auth/ante_test.go Normal file
View File

@ -0,0 +1,325 @@
package auth
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
)
func newTestMsg(addrs ...sdk.Address) *sdk.TestMsg {
return sdk.NewTestMsg(addrs...)
}
func newStdFee() sdk.StdFee {
return sdk.NewStdFee(100,
sdk.Coin{"atom", 150},
)
}
// coins to more than cover the fee
func newCoins() sdk.Coins {
return sdk.Coins{
{"atom", 10000000},
}
}
// generate a priv key and return it with its address
func privAndAddr() (crypto.PrivKey, sdk.Address) {
priv := crypto.GenPrivKeyEd25519()
addr := priv.PubKey().Address()
return priv.Wrap(), addr
}
// run the tx through the anteHandler and ensure its valid
func checkValidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx) {
_, result, abort := anteHandler(ctx, tx)
assert.False(t, abort)
assert.Equal(t, sdk.CodeOK, result.Code)
assert.True(t, result.IsOK())
}
// run the tx through the anteHandler and ensure it fails with the given code
func checkInvalidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, code sdk.CodeType) {
_, result, abort := anteHandler(ctx, tx)
assert.True(t, abort)
assert.Equal(t, code, result.Code)
}
func newTestTx(ctx sdk.Context, msg sdk.Msg, privs []crypto.PrivKey, seqs []int64, fee sdk.StdFee) sdk.Tx {
signBytes := sdk.StdSignBytes(ctx.ChainID(), seqs, fee, msg)
return newTestTxWithSignBytes(msg, privs, seqs, fee, signBytes)
}
func newTestTxWithSignBytes(msg sdk.Msg, privs []crypto.PrivKey, seqs []int64, fee sdk.StdFee, signBytes []byte) sdk.Tx {
sigs := make([]sdk.StdSignature, len(privs))
for i, priv := range privs {
sigs[i] = sdk.StdSignature{PubKey: priv.PubKey(), Signature: priv.Sign(signBytes), Sequence: seqs[i]}
}
tx := sdk.NewStdTx(msg, fee, sigs)
return tx
}
// Test various error cases in the AnteHandler control flow.
func TestAnteHandlerSigErrors(t *testing.T) {
// setup
ms, capKey := setupMultiStore()
mapper := NewAccountMapper(capKey, &BaseAccount{})
anteHandler := NewAnteHandler(mapper)
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil)
// keys and addresses
priv1, addr1 := privAndAddr()
priv2, addr2 := privAndAddr()
// msg and signatures
var tx sdk.Tx
msg := newTestMsg(addr1, addr2)
fee := newStdFee()
// test no signatures
privs, seqs := []crypto.PrivKey{}, []int64{}
tx = newTestTx(ctx, msg, privs, seqs, fee)
checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized)
// test num sigs dont match GetSigners
privs, seqs = []crypto.PrivKey{priv1}, []int64{0}
tx = newTestTx(ctx, msg, privs, seqs, fee)
checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized)
// test an unrecognized account
privs, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 0}
tx = newTestTx(ctx, msg, privs, seqs, fee)
checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnknownAddress)
// save the first account, but second is still unrecognized
acc1 := mapper.NewAccountWithAddress(ctx, addr1)
acc1.SetCoins(fee.Amount)
mapper.SetAccount(ctx, acc1)
checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnknownAddress)
}
// Test logic around sequence checking with one signer and many signers.
func TestAnteHandlerSequences(t *testing.T) {
// setup
ms, capKey := setupMultiStore()
mapper := NewAccountMapper(capKey, &BaseAccount{})
anteHandler := NewAnteHandler(mapper)
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil)
// keys and addresses
priv1, addr1 := privAndAddr()
priv2, addr2 := privAndAddr()
// set the accounts
acc1 := mapper.NewAccountWithAddress(ctx, addr1)
acc1.SetCoins(newCoins())
mapper.SetAccount(ctx, acc1)
acc2 := mapper.NewAccountWithAddress(ctx, addr2)
acc2.SetCoins(newCoins())
mapper.SetAccount(ctx, acc2)
// msg and signatures
var tx sdk.Tx
msg := newTestMsg(addr1)
fee := newStdFee()
// test good tx from one signer
privs, seqs := []crypto.PrivKey{priv1}, []int64{0}
tx = newTestTx(ctx, msg, privs, seqs, fee)
checkValidTx(t, anteHandler, ctx, tx)
// test sending it again fails (replay protection)
checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidSequence)
// fix sequence, should pass
seqs = []int64{1}
tx = newTestTx(ctx, msg, privs, seqs, fee)
checkValidTx(t, anteHandler, ctx, tx)
// new tx with another signer and correct sequences
msg = newTestMsg(addr1, addr2)
privs, seqs = []crypto.PrivKey{priv1, priv2}, []int64{2, 0}
tx = newTestTx(ctx, msg, privs, seqs, fee)
checkValidTx(t, anteHandler, ctx, tx)
// replay fails
checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidSequence)
// tx from just second signer with incorrect sequence fails
msg = newTestMsg(addr2)
privs, seqs = []crypto.PrivKey{priv2}, []int64{0}
tx = newTestTx(ctx, msg, privs, seqs, fee)
checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidSequence)
// fix the sequence and it passes
tx = newTestTx(ctx, msg, []crypto.PrivKey{priv2}, []int64{1}, fee)
checkValidTx(t, anteHandler, ctx, tx)
// another tx from both of them that passes
msg = newTestMsg(addr1, addr2)
privs, seqs = []crypto.PrivKey{priv1, priv2}, []int64{3, 2}
tx = newTestTx(ctx, msg, privs, seqs, fee)
checkValidTx(t, anteHandler, ctx, tx)
}
// Test logic around fee deduction.
func TestAnteHandlerFees(t *testing.T) {
// setup
ms, capKey := setupMultiStore()
mapper := NewAccountMapper(capKey, &BaseAccount{})
anteHandler := NewAnteHandler(mapper)
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil)
// keys and addresses
priv1, addr1 := privAndAddr()
// set the accounts
acc1 := mapper.NewAccountWithAddress(ctx, addr1)
mapper.SetAccount(ctx, acc1)
// msg and signatures
var tx sdk.Tx
msg := newTestMsg(addr1)
privs, seqs := []crypto.PrivKey{priv1}, []int64{0}
fee := sdk.NewStdFee(100,
sdk.Coin{"atom", 150},
)
// signer does not have enough funds to pay the fee
tx = newTestTx(ctx, msg, privs, seqs, fee)
checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInsufficientFunds)
acc1.SetCoins(sdk.Coins{{"atom", 149}})
mapper.SetAccount(ctx, acc1)
checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInsufficientFunds)
acc1.SetCoins(sdk.Coins{{"atom", 150}})
mapper.SetAccount(ctx, acc1)
checkValidTx(t, anteHandler, ctx, tx)
}
func TestAnteHandlerBadSignBytes(t *testing.T) {
// setup
ms, capKey := setupMultiStore()
mapper := NewAccountMapper(capKey, &BaseAccount{})
anteHandler := NewAnteHandler(mapper)
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil)
// keys and addresses
priv1, addr1 := privAndAddr()
priv2, addr2 := privAndAddr()
// set the accounts
acc1 := mapper.NewAccountWithAddress(ctx, addr1)
acc1.SetCoins(newCoins())
mapper.SetAccount(ctx, acc1)
acc2 := mapper.NewAccountWithAddress(ctx, addr2)
acc2.SetCoins(newCoins())
mapper.SetAccount(ctx, acc2)
var tx sdk.Tx
msg := newTestMsg(addr1)
fee := newStdFee()
fee2 := newStdFee()
fee2.Gas += 100
fee3 := newStdFee()
fee3.Amount[0].Amount += 100
// test good tx and signBytes
privs, seqs := []crypto.PrivKey{priv1}, []int64{0}
tx = newTestTx(ctx, msg, privs, seqs, fee)
checkValidTx(t, anteHandler, ctx, tx)
chainID := ctx.ChainID()
chainID2 := chainID + "somemorestuff"
codeUnauth := sdk.CodeUnauthorized
cases := []struct {
chainID string
seqs []int64
fee sdk.StdFee
msg sdk.Msg
code sdk.CodeType
}{
{chainID2, []int64{1}, fee, msg, codeUnauth}, // test wrong chain_id
{chainID, []int64{2}, fee, msg, codeUnauth}, // test wrong seqs
{chainID, []int64{1, 2}, fee, msg, codeUnauth}, // test wrong seqs
{chainID, []int64{1}, fee, newTestMsg(addr2), codeUnauth}, // test wrong msg
{chainID, []int64{1}, fee2, newTestMsg(addr2), codeUnauth}, // test wrong fee
{chainID, []int64{1}, fee3, newTestMsg(addr2), codeUnauth}, // test wrong fee
}
privs, seqs = []crypto.PrivKey{priv1}, []int64{1}
for _, cs := range cases {
tx := newTestTxWithSignBytes(
msg, privs, seqs, fee,
sdk.StdSignBytes(cs.chainID, cs.seqs, cs.fee, cs.msg),
)
checkInvalidTx(t, anteHandler, ctx, tx, cs.code)
}
// test wrong signer if public key exist
privs, seqs = []crypto.PrivKey{priv2}, []int64{1}
tx = newTestTx(ctx, msg, privs, seqs, fee)
checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized)
// test wrong signer if public doesn't exist
msg = newTestMsg(addr2)
privs, seqs = []crypto.PrivKey{priv1}, []int64{0}
tx = newTestTx(ctx, msg, privs, seqs, fee)
checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidPubKey)
}
func TestAnteHandlerSetPubKey(t *testing.T) {
// setup
ms, capKey := setupMultiStore()
mapper := NewAccountMapper(capKey, &BaseAccount{})
anteHandler := NewAnteHandler(mapper)
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil)
// keys and addresses
priv1, addr1 := privAndAddr()
_, addr2 := privAndAddr()
// set the accounts
acc1 := mapper.NewAccountWithAddress(ctx, addr1)
acc1.SetCoins(newCoins())
mapper.SetAccount(ctx, acc1)
acc2 := mapper.NewAccountWithAddress(ctx, addr2)
acc2.SetCoins(newCoins())
mapper.SetAccount(ctx, acc2)
var tx sdk.Tx
// test good tx and set public key
msg := newTestMsg(addr1)
privs, seqs := []crypto.PrivKey{priv1}, []int64{0}
fee := newStdFee()
tx = newTestTx(ctx, msg, privs, seqs, fee)
checkValidTx(t, anteHandler, ctx, tx)
acc1 = mapper.GetAccount(ctx, addr1)
require.Equal(t, acc1.GetPubKey(), priv1.PubKey())
// test public key not found
msg = newTestMsg(addr2)
tx = newTestTx(ctx, msg, privs, seqs, fee)
sigs := tx.GetSignatures()
sigs[0].PubKey = crypto.PubKey{}
checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidPubKey)
acc2 = mapper.GetAccount(ctx, addr2)
assert.True(t, acc2.GetPubKey().Empty())
// test invalid signature and public key
tx = newTestTx(ctx, msg, privs, seqs, fee)
checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidPubKey)
acc2 = mapper.GetAccount(ctx, addr2)
assert.True(t, acc2.GetPubKey().Empty())
}

View File

@ -3,9 +3,10 @@ package auth
import (
"errors"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
//-----------------------------------------------------------
@ -17,13 +18,13 @@ var _ sdk.Account = (*BaseAccount)(nil)
// Extend this by embedding this in your AppAccount.
// See the examples/basecoin/types/account.go for an example.
type BaseAccount struct {
Address crypto.Address `json:"address"`
Coins sdk.Coins `json:"coins"`
PubKey crypto.PubKey `json:"public_key"`
Sequence int64 `json:"sequence"`
Address sdk.Address `json:"address"`
Coins sdk.Coins `json:"coins"`
PubKey crypto.PubKey `json:"public_key"`
Sequence int64 `json:"sequence"`
}
func NewBaseAccountWithAddress(addr crypto.Address) BaseAccount {
func NewBaseAccountWithAddress(addr sdk.Address) BaseAccount {
return BaseAccount{
Address: addr,
}
@ -40,12 +41,12 @@ func (acc *BaseAccount) Set(key interface{}, value interface{}) error {
}
// Implements sdk.Account.
func (acc BaseAccount) GetAddress() crypto.Address {
func (acc BaseAccount) GetAddress() sdk.Address {
return acc.Address
}
// Implements sdk.Account.
func (acc *BaseAccount) SetAddress(addr crypto.Address) error {
func (acc *BaseAccount) SetAddress(addr sdk.Address) error {
if len(acc.Address) != 0 {
return errors.New("cannot override BaseAccount address")
}
@ -60,7 +61,7 @@ func (acc BaseAccount) GetPubKey() crypto.PubKey {
// Implements sdk.Account.
func (acc *BaseAccount) SetPubKey(pubKey crypto.PubKey) error {
if acc.PubKey != nil {
if !acc.PubKey.Empty() {
return errors.New("cannot override BaseAccount pubkey")
}
acc.PubKey = pubKey
@ -94,5 +95,5 @@ func (acc *BaseAccount) SetSequence(seq int64) error {
func RegisterWireBaseAccount(cdc *wire.Codec) {
// Register crypto.[PubKey,PrivKey,Signature] types.
crypto.RegisterWire(cdc)
wire.RegisterCrypto(cdc)
}

Some files were not shown because too many files have changed in this diff Show More