Merge branch 'develop' into fedekunze/README

This commit is contained in:
Federico Kunze 2018-04-09 09:50:22 -03:00 committed by GitHub
commit 56c3ae4a7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 3459 additions and 2074 deletions

View File

@ -1,27 +1,31 @@
# Changelog
## 0.13.0 (TBD)
## 0.13.1 (April 3, 2018)
BUG FIXES
* [x/ibc] Fix CLI and relay for IBC txs
* [x/stake] Various fixes/improvements
## 0.13.0 (April 2, 2018)
BREAKING CHANGES
* [baseapp] `AddRoute` takes an `InitGenesis` function for per-module
initialization
* [basecoin] Remove cool/sketchy modules -> moved to new `democoin`
* [basecoin] NewBasecoinApp takes a `map[string]dbm.DB` as temporary measure
to allow mounting multiple stores with their own DB until they can share one
* [staking] Renamed to `simplestake`
* [x/staking] Renamed to `simplestake`
* [builder] Functions don't take `passphrase` as argument
* [server] GenAppState returns generated seed and address
* [basecoind] `init` command outputs JSON of everything necessary for testnet
* [basecoind] `basecoin.db -> data/basecoin.db`
* [basecli] `data/keys.db -> keys/keys.db`
* [cool] Mapper -> Keeper
FEATURES
* [types] `Coin` supports direct arithmetic operations
* [basecoind] Add `show_validator` and `show_node_id` commands
* [staking] Initial merge of full staking module!
* [x/stake] Initial merge of full staking module!
* [democoin] New example application to demo custom modules
IMPROVEMENTS
@ -45,9 +49,9 @@ BREAKING CHANGES
* [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
* [x/auth] AnteHandler deducts fees
* [x/bank] Move some errors to `types`
* [x/bank] Remove sequence and signature from Input
FEATURES
@ -71,8 +75,8 @@ IMPROVEMENTS
* [specs] Staking
BUG FIXES
* [auth] Fix setting pubkey on new account
* [auth] Require signatures to include the sequences
* [x/auth] Fix setting pubkey on new account
* [x/auth] Require signatures to include the sequences
* [baseapp] Dont panic on nil handler
* [basecoin] Check for empty bytes in account and tx

34
Gopkg.lock generated
View File

@ -117,7 +117,7 @@
"json/scanner",
"json/token"
]
revision = "f40e974e75af4e271d97ce0fc917af5898ae7bda"
revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168"
[[projects]]
branch = "master"
@ -191,8 +191,8 @@
".",
"mem"
]
revision = "bb8f1927f2a9d3ab41c9340aa034f6b803f4359c"
version = "v1.0.2"
revision = "63644898a8da0bc22138abf860edaf5277b6102e"
version = "v1.1.0"
[[projects]]
name = "github.com/spf13/cast"
@ -203,8 +203,8 @@
[[projects]]
name = "github.com/spf13/cobra"
packages = ["."]
revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b"
version = "v0.0.1"
revision = "a1f051bc3eba734da4772d60e2d677f47cf93ef4"
version = "v0.0.2"
[[projects]]
branch = "master"
@ -250,7 +250,7 @@
"leveldb/table",
"leveldb/util"
]
revision = "169b1b37be738edb2813dab48c97a549bcf99bb5"
revision = "714f901b98fdb3aa954b4193d8cbd64a28d80cad"
[[projects]]
name = "github.com/tendermint/abci"
@ -341,8 +341,8 @@
"version",
"wire"
]
revision = "6f9956990c444d53f62f2a3905ed410cfe9afe77"
version = "v0.17.1"
revision = "a1dd329d72e78d4770e602359bad5b7b1e8b72a3"
version = "v0.18.0-rc1"
[[projects]]
name = "github.com/tendermint/tmlibs"
@ -359,8 +359,8 @@
"pubsub",
"pubsub/query"
]
revision = "24da7009c3d8c019b40ba4287495749e3160caca"
version = "v0.7.1"
revision = "2e24b64fc121dcdf1cabceab8dc2f7257675483c"
version = "0.8.1"
[[projects]]
branch = "master"
@ -376,7 +376,7 @@
"ripemd160",
"salsa20/salsa"
]
revision = "88942b9c40a4c9d203b82b3731787b672d6e809b"
revision = "b2aa35443fbc700ab74c586ae79b81c171851023"
[[projects]]
branch = "master"
@ -390,13 +390,13 @@
"lex/httplex",
"trace"
]
revision = "6078986fec03a1dcc236c34816c71b0e05018fda"
revision = "b3c676e531a6dc479fa1b35ac961c13f5e2b4d2e"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "13d03a9a82fba647c21a0ef8fba44a795d0f0835"
revision = "1d206c9fa8975fb4cf00df1dc8bf3283dc24ba0e"
[[projects]]
name = "golang.org/x/text"
@ -423,7 +423,7 @@
branch = "master"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
revision = "ab0870e398d5dd054b868c0db1481ab029b9a9f2"
revision = "35de2414665fc36f56b72d982c5af480d86de5ab"
[[projects]]
name = "google.golang.org/grpc"
@ -452,12 +452,12 @@
[[projects]]
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "86f5ed62f8a0ee96bd888d2efdfd6d4fb100a4eb"
version = "v2.2.0"
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "ed1f3f7f1728cd02945f90ca780e9bdc982573a36a5cc8d7e9f19fb40ba2ca19"
inputs-digest = "67298e1f8058b85f082dbd32123f2779b11bda282616e595141dba41a8675c39"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -70,11 +70,11 @@
name = "github.com/tendermint/iavl"
[[constraint]]
version = "~0.17.1"
version = "~0.18.0-rc1"
name = "github.com/tendermint/tendermint"
[[constraint]]
version = "~0.7.1"
[[override]]
version = "~0.8.1"
name = "github.com/tendermint/tmlibs"
[prune]

View File

@ -2,7 +2,7 @@ PACKAGES=$(shell go list ./... | grep -v '/vendor/')
COMMIT_HASH := $(shell git rev-parse --short HEAD)
BUILD_FLAGS = -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}"
all: check_tools get_vendor_deps build test
all: check_tools get_vendor_deps build build_examples test
########################################
### CI
@ -13,13 +13,16 @@ ci: get_tools get_vendor_deps build test_cover
### Build
# This can be unified later, here for easy demos
gaia:
go build $(BUILD_FLAGS) -o build/gaiad ./examples/gaia/gaiad
go build $(BUILD_FLAGS) -o build/gaiacli ./examples/gaia/gaiacli
build:
@rm -rf $(shell pwd)/examples/basecoin/vendor/
@rm -rf $(shell pwd)/examples/democoin/vendor/
ifeq ($(OS),Windows_NT)
go build $(BUILD_FLAGS) -o build/gaiad.exe ./cmd/gaiad
go build $(BUILD_FLAGS) -o build/gaiacli.exe ./cmd/gaiacli
else
go build $(BUILD_FLAGS) -o build/gaiad ./cmd/gaiad
go build $(BUILD_FLAGS) -o build/gaiacli ./cmd/gaiacli
endif
build_examples:
ifeq ($(OS),Windows_NT)
go build $(BUILD_FLAGS) -o build/basecoind.exe ./examples/basecoin/cmd/basecoind
go build $(BUILD_FLAGS) -o build/basecli.exe ./examples/basecoin/cmd/basecli
@ -33,6 +36,10 @@ else
endif
install:
go install $(BUILD_FLAGS) ./cmd/gaiad
go install $(BUILD_FLAGS) ./cmd/gaiacli
install_examples:
go install $(BUILD_FLAGS) ./examples/basecoin/cmd/basecoind
go install $(BUILD_FLAGS) ./examples/basecoin/cmd/basecli
go install $(BUILD_FLAGS) ./examples/democoin/cmd/democoind
@ -84,12 +91,9 @@ test: test_unit # test_cli
# go test -coverprofile=c.out && go tool cover -html=c.out
test_unit:
@rm -rf examples/basecoin/vendor/
@rm -rf examples/democoin/vendor/
@go test $(PACKAGES)
test_cover:
@rm -rf examples/basecoin/vendor/
@bash tests/test_cover.sh
benchmark:
@ -123,4 +127,4 @@ devdoc_update:
# To avoid unintended conflicts with file names, always add to .PHONY
# unless there is a reason not to.
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
.PHONY: build dist check_tools get_tools get_vendor_deps draw_deps test test_unit test_tutorial benchmark devdoc_init devdoc devdoc_save devdoc_update
.PHONY: build build_examples install install_examples dist check_tools get_tools get_vendor_deps draw_deps test test_unit test_tutorial benchmark devdoc_init devdoc devdoc_save devdoc_update

View File

@ -70,4 +70,4 @@ The key directories of the SDK are:
## Getting Started
See the [documentation](https://cosmos-sdk.readthedocs.io).
See the [documentation](https://cosmos-sdk.readthedocs.io).

View File

@ -248,11 +248,6 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC
// TODO Return something intelligent
panic(err)
}
err = app.Router().InitGenesis(app.deliverState.ctx, *genesisState)
if err != nil {
// TODO Return something intelligent
panic(err)
}
// NOTE: we don't commit, but BeginBlock for block 1
// starts from this deliverState
@ -266,7 +261,7 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
queryable, ok := app.cms.(sdk.Queryable)
if !ok {
msg := "application doesn't support queries"
return sdk.ErrUnknownRequest(msg).Result().ToQuery()
return sdk.ErrUnknownRequest(msg).QueryResult()
}
return queryable.Query(req)
}

View File

@ -249,7 +249,7 @@ func TestDeliverTx(t *testing.T) {
counter += 1
return sdk.Result{}
}, nil)
})
tx := testUpdatePowerTx{} // doesn't matter
header := abci.Header{AppHash: []byte("apphash")}
@ -284,7 +284,7 @@ func TestQuery(t *testing.T) {
store := ctx.KVStore(capKey)
store.Set(key, value)
return sdk.Result{}
}, nil)
})
query := abci.RequestQuery{
Path: "/main/key",
@ -349,7 +349,7 @@ func TestValidatorChange(t *testing.T) {
app.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
// TODO
return sdk.Result{}
}, nil)
})
// Load latest state, which should be empty.
err := app.LoadLatestVersion(capKey)

View File

@ -1,8 +1,6 @@
package baseapp
import (
"encoding/json"
"fmt"
"regexp"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -10,16 +8,14 @@ import (
// Router provides handlers for each transaction type.
type Router interface {
AddRoute(r string, h sdk.Handler, i sdk.InitGenesis) (rtr Router)
AddRoute(r string, h sdk.Handler) (rtr Router)
Route(path string) (h sdk.Handler)
InitGenesis(ctx sdk.Context, data map[string]json.RawMessage) error
}
// map a transaction type to a handler and an initgenesis function
type route struct {
r string
h sdk.Handler
i sdk.InitGenesis
}
type router struct {
@ -38,11 +34,11 @@ func NewRouter() *router {
var isAlpha = regexp.MustCompile(`^[a-zA-Z]+$`).MatchString
// AddRoute - TODO add description
func (rtr *router) AddRoute(r string, h sdk.Handler, i sdk.InitGenesis) Router {
func (rtr *router) AddRoute(r string, h sdk.Handler) Router {
if !isAlpha(r) {
panic("route expressions can only contain alphanumeric characters")
}
rtr.routes = append(rtr.routes, route{r, h, i})
rtr.routes = append(rtr.routes, route{r, h})
return rtr
}
@ -57,20 +53,3 @@ func (rtr *router) Route(path string) (h sdk.Handler) {
}
return nil
}
// InitGenesis - call `InitGenesis`, where specified, for all routes
// Return the first error if any, otherwise nil
func (rtr *router) InitGenesis(ctx sdk.Context, data map[string]json.RawMessage) error {
for _, route := range rtr.routes {
if route.i != nil {
encoded, found := data[route.r]
if !found {
return sdk.ErrGenesisParse(fmt.Sprintf("Expected module genesis information for module %s but it was not present", route.r))
}
if err := route.i(ctx, encoded); err != nil {
return err
}
}
}
return nil
}

27
client/context/viper.go Normal file
View File

@ -0,0 +1,27 @@
package context
import (
"github.com/spf13/viper"
rpcclient "github.com/tendermint/tendermint/rpc/client"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/core"
)
func NewCoreContextFromViper() core.CoreContext {
nodeURI := viper.GetString(client.FlagNode)
var rpc rpcclient.Client
if nodeURI != "" {
rpc = rpcclient.NewHTTP(nodeURI, "/websocket")
}
return core.CoreContext{
ChainID: viper.GetString(client.FlagChainID),
Height: viper.GetInt64(client.FlagHeight),
TrustNode: viper.GetBool(client.FlagTrustNode),
FromAddressName: viper.GetString(client.FlagName),
NodeURI: nodeURI,
Sequence: viper.GetInt64(client.FlagSequence),
Client: rpc,
}
}

50
client/core/context.go Normal file
View File

@ -0,0 +1,50 @@
package core
import (
rpcclient "github.com/tendermint/tendermint/rpc/client"
)
type CoreContext struct {
ChainID string
Height int64
TrustNode bool
NodeURI string
FromAddressName string
Sequence int64
Client rpcclient.Client
}
func (c CoreContext) WithChainID(chainID string) CoreContext {
c.ChainID = chainID
return c
}
func (c CoreContext) WithHeight(height int64) CoreContext {
c.Height = height
return c
}
func (c CoreContext) WithTrustNode(trustNode bool) CoreContext {
c.TrustNode = trustNode
return c
}
func (c CoreContext) WithNodeURI(nodeURI string) CoreContext {
c.NodeURI = nodeURI
return c
}
func (c CoreContext) WithFromAddressName(fromAddressName string) CoreContext {
c.FromAddressName = fromAddressName
return c
}
func (c CoreContext) WithSequence(sequence int64) CoreContext {
c.Sequence = sequence
return c
}
func (c CoreContext) WithClient(client rpcclient.Client) CoreContext {
c.Client = client
return c
}

View File

@ -1,10 +1,9 @@
package builder
package core
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/wire"
rpcclient "github.com/tendermint/tendermint/rpc/client"
@ -17,9 +16,9 @@ import (
)
// Broadcast the transaction bytes to Tendermint
func BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) {
func (ctx CoreContext) BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) {
node, err := client.GetNode()
node, err := ctx.GetNode()
if err != nil {
return nil, err
}
@ -43,17 +42,17 @@ func BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) {
}
// Query from Tendermint with the provided key and storename
func Query(key cmn.HexBytes, storeName string) (res []byte, err error) {
func (ctx CoreContext) Query(key cmn.HexBytes, storeName string) (res []byte, err error) {
path := fmt.Sprintf("/%s/key", storeName)
node, err := client.GetNode()
node, err := ctx.GetNode()
if err != nil {
return res, err
}
opts := rpcclient.ABCIQueryOptions{
Height: viper.GetInt64(client.FlagHeight),
Trusted: viper.GetBool(client.FlagTrustNode),
Height: ctx.Height,
Trusted: ctx.TrustNode,
}
result, err := node.ABCIQueryWithOptions(path, key, opts)
if err != nil {
@ -67,16 +66,16 @@ func Query(key cmn.HexBytes, storeName string) (res []byte, err error) {
}
// Get the from address from the name flag
func GetFromAddress() (from sdk.Address, err error) {
func (ctx CoreContext) GetFromAddress() (from sdk.Address, err error) {
keybase, err := keys.GetKeyBase()
if err != nil {
return nil, err
}
name := viper.GetString(client.FlagName)
name := ctx.FromAddressName
if name == "" {
return nil, errors.Errorf("must provide a name using --name")
return nil, errors.Errorf("must provide a from address name")
}
info, err := keybase.Get(name)
@ -88,11 +87,11 @@ func GetFromAddress() (from sdk.Address, err error) {
}
// sign and build the transaction from the msg
func SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *wire.Codec) ([]byte, error) {
func (ctx CoreContext) 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))
chainID := ctx.ChainID
sequence := ctx.Sequence
signMsg := sdk.StdSignMsg{
ChainID: chainID,
Sequences: []int64{sequence},
@ -114,7 +113,7 @@ func SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *wire.Codec) ([]byte
sigs := []sdk.StdSignature{{
PubKey: pubkey,
Signature: sig,
Sequence: viper.GetInt64(client.FlagSequence),
Sequence: sequence,
}}
// marshal bytes
@ -124,23 +123,31 @@ func SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *wire.Codec) ([]byte
}
// sign and build the transaction from the msg
func SignBuildBroadcast(name string, msg sdk.Msg, cdc *wire.Codec) (*ctypes.ResultBroadcastTxCommit, error) {
passphrase, err := GetPassphraseFromStdin(name)
func (ctx CoreContext) SignBuildBroadcast(name string, msg sdk.Msg, cdc *wire.Codec) (*ctypes.ResultBroadcastTxCommit, error) {
passphrase, err := ctx.GetPassphraseFromStdin(name)
if err != nil {
return nil, err
}
txBytes, err := SignAndBuild(name, passphrase, msg, cdc)
txBytes, err := ctx.SignAndBuild(name, passphrase, msg, cdc)
if err != nil {
return nil, err
}
return BroadcastTx(txBytes)
return ctx.BroadcastTx(txBytes)
}
// get passphrase from std input
func GetPassphraseFromStdin(name string) (pass string, err error) {
func (ctx CoreContext) GetPassphraseFromStdin(name string) (pass string, err error) {
buf := client.BufferStdin()
prompt := fmt.Sprintf("Password to sign with '%s':", name)
return client.GetPassword(prompt, buf)
}
// GetNode prepares a simple rpc.Client
func (ctx CoreContext) GetNode() (rpcclient.Client, error) {
if ctx.Client == nil {
return nil, errors.New("Must define node URI")
}
return ctx.Client, nil
}

View File

@ -1,17 +0,0 @@
package client
import (
"github.com/pkg/errors"
"github.com/spf13/viper"
rpcclient "github.com/tendermint/tendermint/rpc/client"
)
// GetNode prepares a simple rpc.Client from the flags
func GetNode() (rpcclient.Client, error) {
uri := viper.GetString(FlagNode)
if uri == "" {
return nil, errors.New("Must define node using --node")
}
return rpcclient.NewHTTP(uri, "/websocket"), nil
}

View File

@ -158,7 +158,7 @@ func TestNodeStatus(t *testing.T) {
func TestBlock(t *testing.T) {
time.Sleep(time.Second * 2) // TODO: LOL -> wait for blocks
waitForHeight(2)
var resultBlock ctypes.ResultBlock
@ -222,8 +222,7 @@ func TestCoinSend(t *testing.T) {
// create TX
receiveAddr, resultTx := doSend(t, port, seed)
time.Sleep(time.Second * 2) // T
waitForHeight(resultTx.Height + 1)
// check if tx was commited
assert.Equal(t, uint32(0), resultTx.CheckTx.Code)
@ -258,7 +257,7 @@ func TestIBCTransfer(t *testing.T) {
// create TX
resultTx := doIBCTransfer(t, port, seed)
time.Sleep(time.Second * 2) // T
waitForHeight(resultTx.Height + 1)
// check if tx was commited
assert.Equal(t, uint32(0), resultTx.CheckTx.Code)
@ -296,7 +295,7 @@ func TestTxs(t *testing.T) {
// create TX
_, resultTx := doSend(t, port, seed)
time.Sleep(time.Second * 2) // TO
waitForHeight(resultTx.Height + 1)
// check if tx is findable
res, body := request(t, port, "GET", fmt.Sprintf("/txs/%s", resultTx.Hash), nil)
@ -392,7 +391,7 @@ func startTMAndLCD() (*nm.Node, net.Listener, error) {
return nil, nil, err
}
time.Sleep(time.Second * 2)
waitForStart()
return node, lcd, nil
}
@ -442,6 +441,7 @@ func request(t *testing.T, port, method, path string, payload []byte) (*http.Res
require.Nil(t, err)
output, err := ioutil.ReadAll(res.Body)
res.Body.Close()
require.Nil(t, err)
return res, string(output)
@ -461,8 +461,6 @@ func doSend(t *testing.T, port, seed string) (receiveAddr string, resultTx ctype
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
@ -490,8 +488,6 @@ func doIBCTransfer(t *testing.T, port, seed string) (resultTx ctypes.ResultBroad
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
@ -504,3 +500,72 @@ func doIBCTransfer(t *testing.T, port, seed string) (resultTx ctypes.ResultBroad
return resultTx
}
func waitForHeight(height int64) {
for {
var resultBlock ctypes.ResultBlock
url := fmt.Sprintf("http://localhost:%v%v", port, "/blocks/latest")
res, err := http.Get(url)
if err != nil {
panic(err)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
}
res.Body.Close()
err = json.Unmarshal([]byte(body), &resultBlock)
if err != nil {
fmt.Println("RES", res)
fmt.Println("BODY", string(body))
panic(err)
}
if resultBlock.Block.Height >= height {
return
}
time.Sleep(time.Millisecond * 100)
}
}
// wait for 2 blocks
func waitForStart() {
waitHeight := int64(2)
for {
time.Sleep(time.Second)
var resultBlock ctypes.ResultBlock
url := fmt.Sprintf("http://localhost:%v%v", port, "/blocks/latest")
res, err := http.Get(url)
if err != nil {
panic(err)
}
// waiting for server to start ...
if res.StatusCode != http.StatusOK {
res.Body.Close()
continue
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
}
res.Body.Close()
err = json.Unmarshal([]byte(body), &resultBlock)
if err != nil {
fmt.Println("RES", res)
fmt.Println("BODY", string(body))
panic(err)
}
if resultBlock.Block.Height >= waitHeight {
return
}
}
}

View File

@ -10,6 +10,7 @@ import (
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
)
const (
@ -31,7 +32,8 @@ func blockCommand() *cobra.Command {
func getBlock(height *int64) ([]byte, error) {
// get the node
node, err := client.GetNode()
ctx := context.NewCoreContextFromViper()
node, err := ctx.GetNode()
if err != nil {
return nil, err
}
@ -55,7 +57,7 @@ func getBlock(height *int64) ([]byte, error) {
}
func GetChainHeight() (int64, error) {
node, err := client.GetNode()
node, err := context.NewCoreContextFromViper().GetNode()
if err != nil {
return -1, err
}

View File

@ -10,6 +10,7 @@ import (
wire "github.com/tendermint/go-wire"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
)
@ -25,7 +26,7 @@ func statusCommand() *cobra.Command {
func getNodeStatus() (*ctypes.ResultStatus, error) {
// get the node
node, err := client.GetNode()
node, err := context.NewCoreContextFromViper().GetNode()
if err != nil {
return &ctypes.ResultStatus{}, err
}

View File

@ -10,6 +10,7 @@ import (
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
)
func validatorCommand() *cobra.Command {
@ -26,7 +27,7 @@ func validatorCommand() *cobra.Command {
func GetValidators(height *int64) ([]byte, error) {
// get the node
node, err := client.GetNode()
node, err := context.NewCoreContextFromViper().GetNode()
if err != nil {
return nil, err
}

View File

@ -4,7 +4,7 @@ import (
"encoding/json"
"net/http"
"github.com/cosmos/cosmos-sdk/client/builder"
"github.com/cosmos/cosmos-sdk/client/context"
)
type BroadcastTxBody struct {
@ -22,7 +22,7 @@ func BroadcastTxRequestHandler(w http.ResponseWriter, r *http.Request) {
return
}
res, err := builder.BroadcastTx([]byte(m.TxBytes))
res, err := context.NewCoreContextFromViper().BroadcastTx([]byte(m.TxBytes))
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))

View File

@ -15,6 +15,7 @@ import (
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
@ -39,7 +40,7 @@ func (c commander) queryTx(hashHexStr string, trustNode bool) ([]byte, error) {
}
// get the node
node, err := client.GetNode()
node, err := context.NewCoreContextFromViper().GetNode()
if err != nil {
return nil, err
}

View File

@ -12,6 +12,7 @@ import (
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/wire"
)
@ -43,7 +44,7 @@ func (c commander) searchTx(tags []string) ([]byte, error) {
query := strings.Join(tags, " AND ")
// get the node
node, err := client.GetNode()
node, err := context.NewCoreContextFromViper().GetNode()
if err != nil {
return nil, err
}

View File

@ -17,9 +17,9 @@ const (
flagFee = "fee"
)
// gaiacliCmd is the entry point for this binary
// rootCmd is the entry point for this binary
var (
gaiacliCmd = &cobra.Command{
rootCmd = &cobra.Command{
Use: "gaiacli",
Short: "Gaia light-client",
}
@ -54,16 +54,16 @@ func main() {
cobra.EnableCommandSorting = false
// generic client commands
AddClientCommands(gaiacliCmd)
AddClientCommands(rootCmd)
// query commands (custom to binary)
gaiacliCmd.AddCommand(
rootCmd.AddCommand(
GetCommands(getAccountCmd)...)
// post tx commands (custom to binary)
gaiacliCmd.AddCommand(
rootCmd.AddCommand(
PostCommands(postSendCommand())...)
// add proxy, version and key info
gaiacliCmd.AddCommand(
rootCmd.AddCommand(
lineBreak,
serveCommand(),
KeyCommands(),
@ -72,6 +72,6 @@ func main() {
)
// prepare and add flags
executor := cli.PrepareBaseCmd(gaiacliCmd, "GA", os.ExpandEnv("$HOME/.gaiacli"))
executor := cli.PrepareBaseCmd(rootCmd, "GA", os.ExpandEnv("$HOME/.gaiacli"))
executor.Execute()
}

63
cmd/gaiad/main.go Normal file
View File

@ -0,0 +1,63 @@
package main
import (
"os"
"path/filepath"
"github.com/spf13/cobra"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/cli"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/examples/basecoin/app"
"github.com/cosmos/cosmos-sdk/server"
)
// rootCmd is the entry point for this binary
var (
context = server.NewDefaultContext()
rootCmd = &cobra.Command{
Use: "gaiad",
Short: "Gaia Daemon (server)",
PersistentPreRunE: server.PersistentPreRunEFn(context),
}
)
// TODO: distinguish from basecoin
func generateApp(rootDir string, logger log.Logger) (abci.Application, error) {
dataDir := filepath.Join(rootDir, "data")
dbMain, err := dbm.NewGoLevelDB("gaia", dataDir)
if err != nil {
return nil, err
}
dbAcc, err := dbm.NewGoLevelDB("gaia-acc", dataDir)
if err != nil {
return nil, err
}
dbIBC, err := dbm.NewGoLevelDB("gaia-ibc", dataDir)
if err != nil {
return nil, err
}
dbStaking, err := dbm.NewGoLevelDB("gaia-staking", dataDir)
if err != nil {
return nil, err
}
dbs := map[string]dbm.DB{
"main": dbMain,
"acc": dbAcc,
"ibc": dbIBC,
"staking": dbStaking,
}
bapp := app.NewBasecoinApp(logger, dbs)
return bapp, nil
}
func main() {
server.AddCommands(rootCmd, server.DefaultGenAppState, generateApp, context)
// prepare and add flags
executor := cli.PrepareBaseCmd(rootCmd, "GA", os.ExpandEnv("$HOME/.gaiad"))
executor.Execute()
}

View File

@ -1,204 +0,0 @@
Cosmos-SDK Basecoin (template)
License: Apache2.0
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2018 All in Bits, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,22 +0,0 @@
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: 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)
benchmark:
@go test -bench=. $(PACKAGES)
.PHONY: all build test benchmark

View File

@ -1,70 +0,0 @@
# 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.
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

@ -61,9 +61,9 @@ func NewBasecoinApp(logger log.Logger, dbs map[string]dbm.DB) *BasecoinApp {
ibcMapper := ibc.NewIBCMapper(app.cdc, app.capKeyIBCStore)
stakeKeeper := simplestake.NewKeeper(app.capKeyStakingStore, coinKeeper)
app.Router().
AddRoute("bank", bank.NewHandler(coinKeeper), nil).
AddRoute("ibc", ibc.NewHandler(ibcMapper, coinKeeper), nil).
AddRoute("simplestake", simplestake.NewHandler(stakeKeeper), nil)
AddRoute("bank", bank.NewHandler(coinKeeper)).
AddRoute("ibc", ibc.NewHandler(ibcMapper, coinKeeper)).
AddRoute("simplestake", simplestake.NewHandler(stakeKeeper))
// initialize BaseApp
app.SetTxDecoder(app.txDecoder)

View File

@ -36,6 +36,7 @@ var (
addr4 = priv4.PubKey().Address()
coins = sdk.Coins{{"foocoin", 10}}
halfCoins = sdk.Coins{{"foocoin", 5}}
manyCoins = sdk.Coins{{"foocoin", 1}, {"barcoin", 1}}
fee = sdk.StdFee{
sdk.Coins{{"foocoin", 0}},
0,
@ -73,6 +74,15 @@ var (
bank.NewOutput(addr1, coins),
},
}
sendMsg5 = bank.SendMsg{
Inputs: []bank.Input{
bank.NewInput(addr1, manyCoins),
},
Outputs: []bank.Output{
bank.NewOutput(addr2, manyCoins),
},
}
)
func loggerAndDBs() (log.Logger, map[string]dbm.DB) {
@ -131,6 +141,48 @@ func TestMsgs(t *testing.T) {
}
}
func TestSortGenesis(t *testing.T) {
logger, dbs := loggerAndDBs()
bapp := NewBasecoinApp(logger, dbs)
// Note the order: the coins are unsorted!
coinDenom1, coinDenom2 := "foocoin", "barcoin"
genState := fmt.Sprintf(`{
"accounts": [{
"address": "%s",
"coins": [
{
"denom": "%s",
"amount": 10
},
{
"denom": "%s",
"amount": 20
}
]
}]
}`, addr1.String(), coinDenom1, coinDenom2)
// Initialize the chain
vals := []abci.Validator{}
bapp.InitChain(abci.RequestInitChain{vals, []byte(genState)})
bapp.Commit()
// Unsorted coins means invalid
err := sendMsg5.ValidateBasic()
require.Equal(t, sdk.CodeInvalidCoins, err.ABCICode(), err.ABCILog())
// Sort coins, should be valid
sendMsg5.Inputs[0].Coins.Sort()
sendMsg5.Outputs[0].Coins.Sort()
err = sendMsg5.ValidateBasic()
require.Nil(t, err)
// Ensure we can send
SignCheckDeliver(t, bapp, sendMsg5, []int64{0}, true, priv1)
}
func TestGenesis(t *testing.T) {
logger, dbs := loggerAndDBs()
bapp := NewBasecoinApp(logger, dbs)

View File

@ -1,7 +1,6 @@
package main
import (
"errors"
"os"
"github.com/spf13/cobra"
@ -24,18 +23,14 @@ import (
"github.com/cosmos/cosmos-sdk/examples/basecoin/types"
)
// gaiacliCmd is the entry point for this binary
// rootCmd is the entry point for this binary
var (
basecliCmd = &cobra.Command{
rootCmd = &cobra.Command{
Use: "basecli",
Short: "Basecoin light-client",
}
)
func todoNotImplemented(_ *cobra.Command, _ []string) error {
return errors.New("TODO: Command not yet implemented")
}
func main() {
// disable sorting
cobra.EnableCommandSorting = false
@ -48,36 +43,36 @@ func main() {
// with the cdc
// add standard rpc, and tx commands
rpc.AddCommands(basecliCmd)
basecliCmd.AddCommand(client.LineBreak)
tx.AddCommands(basecliCmd, cdc)
basecliCmd.AddCommand(client.LineBreak)
rpc.AddCommands(rootCmd)
rootCmd.AddCommand(client.LineBreak)
tx.AddCommands(rootCmd, cdc)
rootCmd.AddCommand(client.LineBreak)
// add query/post commands (custom to binary)
basecliCmd.AddCommand(
rootCmd.AddCommand(
client.GetCommands(
authcmd.GetAccountCmd("main", cdc, types.GetAccountDecoder(cdc)),
)...)
basecliCmd.AddCommand(
rootCmd.AddCommand(
client.PostCommands(
bankcmd.SendTxCmd(cdc),
)...)
basecliCmd.AddCommand(
rootCmd.AddCommand(
client.PostCommands(
ibccmd.IBCTransferCmd(cdc),
)...)
basecliCmd.AddCommand(
rootCmd.AddCommand(
client.PostCommands(
ibccmd.IBCRelayCmd(cdc),
simplestakingcmd.BondTxCmd(cdc),
)...)
basecliCmd.AddCommand(
rootCmd.AddCommand(
client.PostCommands(
simplestakingcmd.UnbondTxCmd(cdc),
)...)
// add proxy, version and key info
basecliCmd.AddCommand(
rootCmd.AddCommand(
client.LineBreak,
lcd.ServeCommand(cdc),
keys.Commands(),
@ -86,6 +81,6 @@ func main() {
)
// prepare and add flags
executor := cli.PrepareMainCmd(basecliCmd, "BC", os.ExpandEnv("$HOME/.basecli"))
executor := cli.PrepareMainCmd(rootCmd, "BC", os.ExpandEnv("$HOME/.basecli"))
executor.Execute()
}

View File

@ -1,8 +1,6 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
@ -10,58 +8,38 @@ import (
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/cli"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/examples/basecoin/app"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/version"
)
// basecoindCmd is the entry point for this binary
// rootCmd is the entry point for this binary
var (
basecoindCmd = &cobra.Command{
Use: "gaiad",
Short: "Gaia Daemon (server)",
context = server.NewDefaultContext()
rootCmd = &cobra.Command{
Use: "basecoind",
Short: "Basecoin Daemon (server)",
PersistentPreRunE: server.PersistentPreRunEFn(context),
}
)
// defaultOptions sets up the app_options for the
// default genesis file
func defaultOptions(args []string) (json.RawMessage, string, cmn.HexBytes, error) {
addr, secret, err := server.GenerateCoinKey()
if err != nil {
return nil, "", nil, err
}
opts := fmt.Sprintf(`{
"accounts": [{
"address": "%s",
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
]
}]
}`, addr)
return json.RawMessage(opts), secret, addr, nil
}
func generateApp(rootDir string, logger log.Logger) (abci.Application, error) {
dbMain, err := dbm.NewGoLevelDB("basecoin", filepath.Join(rootDir, "data"))
dataDir := filepath.Join(rootDir, "data")
dbMain, err := dbm.NewGoLevelDB("basecoin", dataDir)
if err != nil {
return nil, err
}
dbAcc, err := dbm.NewGoLevelDB("basecoin-acc", filepath.Join(rootDir, "data"))
dbAcc, err := dbm.NewGoLevelDB("basecoin-acc", dataDir)
if err != nil {
return nil, err
}
dbIBC, err := dbm.NewGoLevelDB("basecoin-ibc", filepath.Join(rootDir, "data"))
dbIBC, err := dbm.NewGoLevelDB("basecoin-ibc", dataDir)
if err != nil {
return nil, err
}
dbStaking, err := dbm.NewGoLevelDB("basecoin-staking", filepath.Join(rootDir, "data"))
dbStaking, err := dbm.NewGoLevelDB("basecoin-staking", dataDir)
if err != nil {
return nil, err
}
@ -76,21 +54,10 @@ 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")
basecoindCmd.AddCommand(
server.InitCmd(defaultOptions, logger),
server.StartCmd(generateApp, logger),
server.UnsafeResetAllCmd(logger),
server.ShowNodeIdCmd(logger),
server.ShowValidatorCmd(logger),
version.VersionCmd,
)
server.AddCommands(rootCmd, server.DefaultGenAppState, generateApp, context)
// prepare and add flags
rootDir := os.ExpandEnv("$HOME/.basecoind")
executor := cli.PrepareBaseCmd(basecoindCmd, "BC", rootDir)
executor := cli.PrepareBaseCmd(rootCmd, "BC", rootDir)
executor.Execute()
}

View File

@ -55,7 +55,7 @@ func NewGenesisAccount(aa *AppAccount) *GenesisAccount {
return &GenesisAccount{
Name: aa.Name,
Address: aa.Address,
Coins: aa.Coins,
Coins: aa.Coins.Sort(),
}
}
@ -63,7 +63,7 @@ func NewGenesisAccount(aa *AppAccount) *GenesisAccount {
func (ga *GenesisAccount) ToAppAccount() (acc *AppAccount, err error) {
baseAcc := auth.BaseAccount{
Address: ga.Address,
Coins: ga.Coins,
Coins: ga.Coins.Sort(),
}
return &AppAccount{
BaseAccount: baseAcc,

View File

@ -1,204 +0,0 @@
Cosmos-SDK Democoin (template)
License: Apache2.0
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2018 All in Bits, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,22 +0,0 @@
PACKAGES=$(shell go list ./... | grep -v '/vendor/')
BUILD_FLAGS = -ldflags "-X github.com/cosmos/cosmos-sdk/examples/democoin/version.GitCommit=`git rev-parse --short HEAD`"
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/democoin ./cmd/...
get_vendor_deps:
@rm -rf vendor/
@dep ensure
test:
@go test $(PACKAGES)
benchmark:
@go test -bench=. $(PACKAGES)
.PHONY: all build test benchmark

View File

@ -1,70 +0,0 @@
# Democoin
This is the "Democoin" example application built on the Cosmos-Sdk. This
"Democoin" is not affiliated with [Coinbase](http://www.getdemocoin.com/), nor
the [stable coin](http://www.getdemocoin.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 `democoind` and `basecli`
binaries.
If you want to create a new application, start by copying the Democoin app.
# Building your own Blockchain
Democoin 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/democoin`
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 Democoin
Democoin is build with the cosmos-sdk. It is a sample application that works
with any engine that implements the ABCI protocol. Democoin defines multiple
unique modules as well as uses modules directly from the sdk. If you want
to modify Democoin, 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

@ -19,6 +19,7 @@ import (
"github.com/cosmos/cosmos-sdk/examples/democoin/types"
"github.com/cosmos/cosmos-sdk/examples/democoin/x/cool"
"github.com/cosmos/cosmos-sdk/examples/democoin/x/pow"
"github.com/cosmos/cosmos-sdk/examples/democoin/x/sketchy"
)
@ -34,6 +35,7 @@ type DemocoinApp struct {
// keys to access the substores
capKeyMainStore *sdk.KVStoreKey
capKeyAccountStore *sdk.KVStoreKey
capKeyPowStore *sdk.KVStoreKey
capKeyIBCStore *sdk.KVStoreKey
capKeyStakingStore *sdk.KVStoreKey
@ -48,6 +50,7 @@ func NewDemocoinApp(logger log.Logger, dbs map[string]dbm.DB) *DemocoinApp {
cdc: MakeCodec(),
capKeyMainStore: sdk.NewKVStoreKey("main"),
capKeyAccountStore: sdk.NewKVStoreKey("acc"),
capKeyPowStore: sdk.NewKVStoreKey("pow"),
capKeyIBCStore: sdk.NewKVStoreKey("ibc"),
capKeyStakingStore: sdk.NewKVStoreKey("stake"),
}
@ -61,20 +64,23 @@ func NewDemocoinApp(logger log.Logger, dbs map[string]dbm.DB) *DemocoinApp {
// add handlers
coinKeeper := bank.NewCoinKeeper(app.accountMapper)
coolKeeper := cool.NewKeeper(app.capKeyMainStore, coinKeeper)
powKeeper := pow.NewKeeper(app.capKeyPowStore, pow.NewPowConfig("pow", int64(1)), coinKeeper)
ibcMapper := ibc.NewIBCMapper(app.cdc, app.capKeyIBCStore)
stakeKeeper := simplestake.NewKeeper(app.capKeyStakingStore, coinKeeper)
app.Router().
AddRoute("bank", bank.NewHandler(coinKeeper), nil).
AddRoute("cool", cool.NewHandler(coolKeeper), coolKeeper.InitGenesis).
AddRoute("sketchy", sketchy.NewHandler(), nil).
AddRoute("ibc", ibc.NewHandler(ibcMapper, coinKeeper), nil).
AddRoute("simplestake", simplestake.NewHandler(stakeKeeper), nil)
AddRoute("bank", bank.NewHandler(coinKeeper)).
AddRoute("cool", cool.NewHandler(coolKeeper)).
AddRoute("pow", powKeeper.Handler).
AddRoute("sketchy", sketchy.NewHandler()).
AddRoute("ibc", ibc.NewHandler(ibcMapper, coinKeeper)).
AddRoute("simplestake", simplestake.NewHandler(stakeKeeper))
// initialize BaseApp
app.SetTxDecoder(app.txDecoder)
app.SetInitChainer(app.initChainer)
app.SetInitChainer(app.initChainerFn(coolKeeper, powKeeper))
app.MountStoreWithDB(app.capKeyMainStore, sdk.StoreTypeIAVL, dbs["main"])
app.MountStoreWithDB(app.capKeyAccountStore, sdk.StoreTypeIAVL, dbs["acc"])
app.MountStoreWithDB(app.capKeyPowStore, sdk.StoreTypeIAVL, dbs["pow"])
app.MountStoreWithDB(app.capKeyIBCStore, sdk.StoreTypeIAVL, dbs["ibc"])
app.MountStoreWithDB(app.capKeyStakingStore, sdk.StoreTypeIAVL, dbs["staking"])
// NOTE: Broken until #532 lands
@ -95,16 +101,18 @@ func MakeCodec() *wire.Codec {
const msgTypeIssue = 0x2
const msgTypeQuiz = 0x3
const msgTypeSetTrend = 0x4
const msgTypeIBCTransferMsg = 0x5
const msgTypeIBCReceiveMsg = 0x6
const msgTypeBondMsg = 0x7
const msgTypeUnbondMsg = 0x8
const msgTypeMine = 0x5
const msgTypeIBCTransferMsg = 0x6
const msgTypeIBCReceiveMsg = 0x7
const msgTypeBondMsg = 0x8
const msgTypeUnbondMsg = 0x9
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{pow.MineMsg{}, msgTypeMine},
oldwire.ConcreteType{ibc.IBCTransferMsg{}, msgTypeIBCTransferMsg},
oldwire.ConcreteType{ibc.IBCReceiveMsg{}, msgTypeIBCReceiveMsg},
oldwire.ConcreteType{simplestake.BondMsg{}, msgTypeBondMsg},
@ -143,23 +151,39 @@ func (app *DemocoinApp) txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) {
}
// custom logic for democoin initialization
func (app *DemocoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
stateJSON := req.AppStateBytes
func (app *DemocoinApp) initChainerFn(coolKeeper cool.Keeper, powKeeper pow.Keeper) sdk.InitChainer {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
stateJSON := req.AppStateBytes
genesisState := new(types.GenesisState)
err := json.Unmarshal(stateJSON, genesisState)
if err != nil {
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(err, "")
}
genesisState := new(types.GenesisState)
err := json.Unmarshal(stateJSON, genesisState)
if err != nil {
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(err, "")
}
for _, gacc := range genesisState.Accounts {
acc, err := gacc.ToAppAccount()
for _, gacc := range genesisState.Accounts {
acc, err := gacc.ToAppAccount()
if err != nil {
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(err, "")
}
app.accountMapper.SetAccount(ctx, acc)
}
// Application specific genesis handling
err = coolKeeper.InitGenesis(ctx, genesisState.CoolGenesis)
if err != nil {
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(err, "")
}
app.accountMapper.SetAccount(ctx, acc)
err = powKeeper.InitGenesis(ctx, genesisState.PowGenesis)
if err != nil {
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(err, "")
}
return abci.ResponseInitChain{}
}
return abci.ResponseInitChain{}
}

View File

@ -11,6 +11,7 @@ import (
"github.com/cosmos/cosmos-sdk/examples/democoin/types"
"github.com/cosmos/cosmos-sdk/examples/democoin/x/cool"
"github.com/cosmos/cosmos-sdk/examples/democoin/x/pow"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
@ -71,6 +72,7 @@ func loggerAndDBs() (log.Logger, map[string]dbm.DB) {
dbs := map[string]dbm.DB{
"main": dbm.NewMemDB(),
"acc": dbm.NewMemDB(),
"pow": dbm.NewMemDB(),
"ibc": dbm.NewMemDB(),
"staking": dbm.NewMemDB(),
}
@ -238,6 +240,58 @@ func TestSendMsgWithAccounts(t *testing.T) {
assert.Equal(t, sdk.CodeOK, res.Code, res.Log)
}
func TestMineMsg(t *testing.T) {
bapp := newDemocoinApp()
// Construct genesis state
// Construct some genesis bytes to reflect democoin/types/AppAccount
coins := sdk.Coins{}
baseAcc := auth.BaseAccount{
Address: addr1,
Coins: coins,
}
acc1 := &types.AppAccount{baseAcc, "foobart"}
// Construct genesis state
genesisState := map[string]interface{}{
"accounts": []*types.GenesisAccount{
types.NewGenesisAccount(acc1),
},
"cool": map[string]string{
"trend": "ice-cold",
},
"pow": map[string]uint64{
"difficulty": 1,
"count": 0,
},
}
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)
// Mine and check for reward
mineMsg1 := pow.GenerateMineMsg(addr1, 1, 2)
SignCheckDeliver(t, bapp, mineMsg1, 0, true)
CheckBalance(t, bapp, "1pow")
// Mine again and check for reward
mineMsg2 := pow.GenerateMineMsg(addr1, 2, 3)
SignCheckDeliver(t, bapp, mineMsg2, 1, true)
CheckBalance(t, bapp, "2pow")
// Mine again - should be invalid
SignCheckDeliver(t, bapp, mineMsg2, 1, false)
CheckBalance(t, bapp, "2pow")
}
func TestQuizMsg(t *testing.T) {
bapp := newDemocoinApp()

View File

@ -1,7 +1,6 @@
package main
import (
"errors"
"os"
"github.com/spf13/cobra"
@ -22,20 +21,18 @@ import (
"github.com/cosmos/cosmos-sdk/examples/democoin/app"
"github.com/cosmos/cosmos-sdk/examples/democoin/types"
coolcmd "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool/commands"
powcmd "github.com/cosmos/cosmos-sdk/examples/democoin/x/pow/commands"
)
// gaiacliCmd is the entry point for this binary
// rootCmd is the entry point for this binary
var (
democliCmd = &cobra.Command{
rootCmd = &cobra.Command{
Use: "democli",
Short: "Democoin light-client",
}
)
func todoNotImplemented(_ *cobra.Command, _ []string) error {
return errors.New("TODO: Command not yet implemented")
}
func main() {
// disable sorting
cobra.EnableCommandSorting = false
@ -48,36 +45,44 @@ func main() {
// with the cdc
// add standard rpc, and tx commands
rpc.AddCommands(democliCmd)
democliCmd.AddCommand(client.LineBreak)
tx.AddCommands(democliCmd, cdc)
democliCmd.AddCommand(client.LineBreak)
rpc.AddCommands(rootCmd)
rootCmd.AddCommand(client.LineBreak)
tx.AddCommands(rootCmd, cdc)
rootCmd.AddCommand(client.LineBreak)
// add query/post commands (custom to binary)
democliCmd.AddCommand(
// start with commands common to basecoin
rootCmd.AddCommand(
client.GetCommands(
authcmd.GetAccountCmd("main", cdc, types.GetAccountDecoder(cdc)),
)...)
democliCmd.AddCommand(
rootCmd.AddCommand(
client.PostCommands(
bankcmd.SendTxCmd(cdc),
)...)
democliCmd.AddCommand(
rootCmd.AddCommand(
client.PostCommands(
ibccmd.IBCTransferCmd(cdc),
)...)
democliCmd.AddCommand(
rootCmd.AddCommand(
client.PostCommands(
ibccmd.IBCRelayCmd(cdc),
simplestakingcmd.BondTxCmd(cdc),
)...)
democliCmd.AddCommand(
rootCmd.AddCommand(
client.PostCommands(
simplestakingcmd.UnbondTxCmd(cdc),
)...)
// and now democoin specific commands
rootCmd.AddCommand(
client.PostCommands(
coolcmd.QuizTxCmd(cdc),
coolcmd.SetTrendTxCmd(cdc),
powcmd.MineCmd(cdc),
)...)
// add proxy, version and key info
democliCmd.AddCommand(
rootCmd.AddCommand(
client.LineBreak,
lcd.ServeCommand(cdc),
keys.Commands(),
@ -86,6 +91,6 @@ func main() {
)
// prepare and add flags
executor := cli.PrepareMainCmd(democliCmd, "BC", os.ExpandEnv("$HOME/.democli"))
executor := cli.PrepareMainCmd(rootCmd, "BC", os.ExpandEnv("$HOME/.democli"))
executor.Execute()
}

View File

@ -2,7 +2,6 @@ package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
@ -10,45 +9,41 @@ import (
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/cli"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/examples/democoin/app"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/version"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// democoindCmd is the entry point for this binary
// rootCmd is the entry point for this binary
var (
democoindCmd = &cobra.Command{
Use: "democoind",
Short: "Gaia Daemon (server)",
context = server.NewDefaultContext()
rootCmd = &cobra.Command{
Use: "democoind",
Short: "Democoin Daemon (server)",
PersistentPreRunE: server.PersistentPreRunEFn(context),
}
)
// defaultOptions sets up the app_options for the
// defaultAppState sets up the app_state for the
// default genesis file
func defaultOptions(args []string) (json.RawMessage, string, cmn.HexBytes, error) {
addr, secret, err := server.GenerateCoinKey()
func defaultAppState(args []string, addr sdk.Address, coinDenom string) (json.RawMessage, error) {
baseJSON, err := server.DefaultGenAppState(args, addr, coinDenom)
if err != nil {
return nil, "", nil, err
return nil, err
}
fmt.Println("Secret phrase to access coins:")
fmt.Println(secret)
opts := fmt.Sprintf(`{
"accounts": [{
"address": "%s",
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
]
}]
}`, addr)
return json.RawMessage(opts), "", nil, nil
var jsonMap map[string]json.RawMessage
err = json.Unmarshal(baseJSON, &jsonMap)
if err != nil {
return nil, err
}
jsonMap["cool"] = json.RawMessage(`{
"trend": "ice-cold"
}`)
bz, err := json.Marshal(jsonMap)
return json.RawMessage(bz), err
}
func generateApp(rootDir string, logger log.Logger) (abci.Application, error) {
@ -60,6 +55,10 @@ func generateApp(rootDir string, logger log.Logger) (abci.Application, error) {
if err != nil {
return nil, err
}
dbPow, err := dbm.NewGoLevelDB("democoin-pow", filepath.Join(rootDir, "data"))
if err != nil {
return nil, err
}
dbIBC, err := dbm.NewGoLevelDB("democoin-ibc", filepath.Join(rootDir, "data"))
if err != nil {
return nil, err
@ -71,6 +70,7 @@ func generateApp(rootDir string, logger log.Logger) (abci.Application, error) {
dbs := map[string]dbm.DB{
"main": dbMain,
"acc": dbAcc,
"pow": dbPow,
"ibc": dbIBC,
"staking": dbStaking,
}
@ -79,21 +79,10 @@ 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")
democoindCmd.AddCommand(
server.InitCmd(defaultOptions, logger),
server.StartCmd(generateApp, logger),
server.UnsafeResetAllCmd(logger),
server.ShowNodeIdCmd(logger),
server.ShowValidatorCmd(logger),
version.VersionCmd,
)
server.AddCommands(rootCmd, defaultAppState, generateApp, context)
// prepare and add flags
rootDir := os.ExpandEnv("$HOME/.democoind")
executor := cli.PrepareBaseCmd(democoindCmd, "BC", rootDir)
executor := cli.PrepareBaseCmd(rootCmd, "BC", rootDir)
executor.Execute()
}

View File

@ -4,6 +4,9 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/examples/democoin/x/cool"
"github.com/cosmos/cosmos-sdk/examples/democoin/x/pow"
)
var _ sdk.Account = (*AppAccount)(nil)
@ -41,7 +44,9 @@ func GetAccountDecoder(cdc *wire.Codec) sdk.AccountDecoder {
// State to Unmarshal
type GenesisState struct {
Accounts []*GenesisAccount `json:"accounts"`
Accounts []*GenesisAccount `json:"accounts"`
PowGenesis pow.PowGenesis `json:"pow"`
CoolGenesis cool.CoolGenesis `json:"cool"`
}
// GenesisAccount doesn't need pubkey or sequence
@ -55,7 +60,7 @@ func NewGenesisAccount(aa *AppAccount) *GenesisAccount {
return &GenesisAccount{
Name: aa.Name,
Address: aa.Address,
Coins: aa.Coins,
Coins: aa.Coins.Sort(),
}
}
@ -63,7 +68,7 @@ func NewGenesisAccount(aa *AppAccount) *GenesisAccount {
func (ga *GenesisAccount) ToAppAccount() (acc *AppAccount, err error) {
baseAcc := auth.BaseAccount{
Address: ga.Address,
Coins: ga.Coins,
Coins: ga.Coins.Sort(),
}
return &AppAccount{
BaseAccount: baseAcc,

View File

@ -8,7 +8,7 @@ import (
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/builder"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/examples/democoin/x/cool"
@ -24,8 +24,10 @@ func QuizTxCmd(cdc *wire.Codec) *cobra.Command {
return errors.New("You must provide an answer")
}
ctx := context.NewCoreContextFromViper()
// get the from address from the name flag
from, err := builder.GetFromAddress()
from, err := ctx.GetFromAddress()
if err != nil {
return err
}
@ -37,7 +39,7 @@ func QuizTxCmd(cdc *wire.Codec) *cobra.Command {
name := viper.GetString(client.FlagName)
// build and sign the transaction, then broadcast to Tendermint
res, err := builder.SignBuildBroadcast(name, msg, cdc)
res, err := ctx.SignBuildBroadcast(name, msg, cdc)
if err != nil {
return err
}
@ -58,8 +60,10 @@ func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command {
return errors.New("You must provide an answer")
}
ctx := context.NewCoreContextFromViper()
// get the from address from the name flag
from, err := builder.GetFromAddress()
from, err := ctx.GetFromAddress()
if err != nil {
return err
}
@ -71,7 +75,7 @@ func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command {
msg := cool.NewSetTrendMsg(from, args[0])
// build and sign the transaction, then broadcast to Tendermint
res, err := builder.SignBuildBroadcast(name, msg, cdc)
res, err := ctx.SignBuildBroadcast(name, msg, cdc)
if err != nil {
return err
}

View File

@ -1,17 +1,10 @@
package cool
import (
"encoding/json"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank"
)
// Cool genesis state, containing the genesis trend
type GenesisState struct {
trend string
}
// Keeper - handlers sets/gets of custom variables for your module
type Keeper struct {
ck bank.CoinKeeper
@ -49,11 +42,7 @@ func (k Keeper) CheckTrend(ctx sdk.Context, guessedTrend string) bool {
}
// InitGenesis - store the genesis trend
func (k Keeper) InitGenesis(ctx sdk.Context, data json.RawMessage) error {
var state GenesisState
if err := json.Unmarshal(data, &state); err != nil {
return err
}
k.setTrend(ctx, state.trend)
func (k Keeper) InitGenesis(ctx sdk.Context, data CoolGenesis) error {
k.setTrend(ctx, data.Trend)
return nil
}

View File

@ -15,6 +15,11 @@ type SetTrendMsg struct {
Cool string
}
// Genesis state - specify genesis trend
type CoolGenesis struct {
Trend string `json:"trend"`
}
// New cool message
func NewSetTrendMsg(sender sdk.Address, cool string) SetTrendMsg {
return SetTrendMsg{

View File

@ -0,0 +1,66 @@
package commands
import (
"fmt"
"strconv"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/examples/democoin/x/pow"
"github.com/cosmos/cosmos-sdk/wire"
)
func MineCmd(cdc *wire.Codec) *cobra.Command {
return &cobra.Command{
Use: "mine [difficulty] [count] [nonce] [solution]",
Short: "Mine some coins with proof-of-work!",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 4 {
return errors.New("You must provide a difficulty, a count, a solution, and a nonce (in that order)")
}
// get from address and parse arguments
ctx := context.NewCoreContextFromViper()
from, err := ctx.GetFromAddress()
if err != nil {
return err
}
difficulty, err := strconv.ParseUint(args[0], 0, 64)
if err != nil {
return err
}
count, err := strconv.ParseUint(args[1], 0, 64)
if err != nil {
return err
}
nonce, err := strconv.ParseUint(args[2], 0, 64)
if err != nil {
return err
}
solution := []byte(args[3])
msg := pow.NewMineMsg(from, difficulty, count, nonce, solution)
// get account name
name := ctx.FromAddressName
// build and sign the transaction, then broadcast to Tendermint
res, err := ctx.SignBuildBroadcast(name, 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,82 @@
package pow
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
type CodeType = sdk.CodeType
const (
CodeInvalidDifficulty CodeType = 201
CodeNonexistentDifficulty CodeType = 202
CodeNonexistentReward CodeType = 203
CodeNonexistentCount CodeType = 204
CodeInvalidProof CodeType = 205
CodeNotBelowTarget CodeType = 206
CodeInvalidCount CodeType = 207
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest
)
func codeToDefaultMsg(code CodeType) string {
switch code {
case CodeInvalidDifficulty:
return "Insuffient difficulty"
case CodeNonexistentDifficulty:
return "Nonexistent difficulty"
case CodeNonexistentReward:
return "Nonexistent reward"
case CodeNonexistentCount:
return "Nonexistent count"
case CodeInvalidProof:
return "Invalid proof"
case CodeNotBelowTarget:
return "Not below target"
case CodeInvalidCount:
return "Invalid count"
case CodeUnknownRequest:
return "Unknown request"
default:
return sdk.CodeToDefaultMsg(code)
}
}
func ErrInvalidDifficulty(msg string) sdk.Error {
return newError(CodeInvalidDifficulty, msg)
}
func ErrNonexistentDifficulty() sdk.Error {
return newError(CodeNonexistentDifficulty, "")
}
func ErrNonexistentReward() sdk.Error {
return newError(CodeNonexistentReward, "")
}
func ErrNonexistentCount() sdk.Error {
return newError(CodeNonexistentCount, "")
}
func ErrInvalidProof(msg string) sdk.Error {
return newError(CodeInvalidProof, msg)
}
func ErrNotBelowTarget(msg string) sdk.Error {
return newError(CodeNotBelowTarget, msg)
}
func ErrInvalidCount(msg string) sdk.Error {
return newError(CodeInvalidCount, msg)
}
func msgOrDefaultMsg(msg string, code CodeType) string {
if msg != "" {
return msg
} else {
return codeToDefaultMsg(code)
}
}
func newError(code CodeType, msg string) sdk.Error {
msg = msgOrDefaultMsg(msg, code)
return sdk.NewError(code, msg)
}

View File

@ -0,0 +1,42 @@
package pow
import (
"reflect"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func (pk Keeper) Handler(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case MineMsg:
return handleMineMsg(ctx, pk, msg)
default:
errMsg := "Unrecognized pow Msg type: " + reflect.TypeOf(msg).Name()
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
func handleMineMsg(ctx sdk.Context, pk Keeper, msg MineMsg) sdk.Result {
// precondition: msg has passed ValidateBasic
newDiff, newCount, err := pk.CheckValid(ctx, msg.Difficulty, msg.Count)
if err != nil {
return err.Result()
}
// commented for now, makes testing difficult
// TODO figure out a better test method that allows early CheckTx return
/*
if ctx.IsCheckTx() {
return sdk.Result{} // TODO
}
*/
err = pk.ApplyValid(ctx, msg.Sender, newDiff, newCount)
if err != nil {
return err.Result()
}
return sdk.Result{}
}

View File

@ -0,0 +1,55 @@
package pow
import (
"testing"
"github.com/stretchr/testify/assert"
abci "github.com/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
auth "github.com/cosmos/cosmos-sdk/x/auth"
bank "github.com/cosmos/cosmos-sdk/x/bank"
)
func TestPowHandler(t *testing.T) {
ms, capKey := setupMultiStore()
am := auth.NewAccountMapper(capKey, &auth.BaseAccount{})
ctx := sdk.NewContext(ms, abci.Header{}, false, nil)
config := NewPowConfig("pow", int64(1))
ck := bank.NewCoinKeeper(am)
keeper := NewKeeper(capKey, config, ck)
handler := keeper.Handler
addr := sdk.Address([]byte("sender"))
count := uint64(1)
difficulty := uint64(2)
err := keeper.InitGenesis(ctx, PowGenesis{uint64(1), uint64(0)})
assert.Nil(t, err)
nonce, proof := mine(addr, count, difficulty)
msg := NewMineMsg(addr, difficulty, count, nonce, proof)
result := handler(ctx, msg)
assert.Equal(t, result, sdk.Result{})
newDiff, err := keeper.GetLastDifficulty(ctx)
assert.Nil(t, err)
assert.Equal(t, newDiff, uint64(2))
newCount, err := keeper.GetLastCount(ctx)
assert.Nil(t, err)
assert.Equal(t, newCount, uint64(1))
// todo assert correct coin change, awaiting https://github.com/cosmos/cosmos-sdk/pull/691
difficulty = uint64(4)
nonce, proof = mine(addr, count, difficulty)
msg = NewMineMsg(addr, difficulty, count, nonce, proof)
result = handler(ctx, msg)
assert.NotEqual(t, result, sdk.Result{})
}

View File

@ -0,0 +1,113 @@
package pow
import (
"fmt"
"strconv"
sdk "github.com/cosmos/cosmos-sdk/types"
bank "github.com/cosmos/cosmos-sdk/x/bank"
)
// module users must specify coin denomination and reward (constant) per PoW solution
type PowConfig struct {
Denomination string
Reward int64
}
// genesis info must specify starting difficulty and starting count
type PowGenesis struct {
Difficulty uint64 `json:"difficulty"`
Count uint64 `json:"count"`
}
type Keeper struct {
key sdk.StoreKey
config PowConfig
ck bank.CoinKeeper
}
func NewPowConfig(denomination string, reward int64) PowConfig {
return PowConfig{denomination, reward}
}
func NewKeeper(key sdk.StoreKey, config PowConfig, ck bank.CoinKeeper) Keeper {
return Keeper{key, config, ck}
}
func (pk Keeper) InitGenesis(ctx sdk.Context, genesis PowGenesis) error {
pk.SetLastDifficulty(ctx, genesis.Difficulty)
pk.SetLastCount(ctx, genesis.Count)
return nil
}
var lastDifficultyKey = []byte("lastDifficultyKey")
func (pk Keeper) GetLastDifficulty(ctx sdk.Context) (uint64, error) {
store := ctx.KVStore(pk.key)
stored := store.Get(lastDifficultyKey)
if stored == nil {
panic("no stored difficulty")
} else {
return strconv.ParseUint(string(stored), 0, 64)
}
}
func (pk Keeper) SetLastDifficulty(ctx sdk.Context, diff uint64) {
store := ctx.KVStore(pk.key)
store.Set(lastDifficultyKey, []byte(strconv.FormatUint(diff, 16)))
}
var countKey = []byte("count")
func (pk Keeper) GetLastCount(ctx sdk.Context) (uint64, error) {
store := ctx.KVStore(pk.key)
stored := store.Get(countKey)
if stored == nil {
panic("no stored count")
} else {
return strconv.ParseUint(string(stored), 0, 64)
}
}
func (pk Keeper) SetLastCount(ctx sdk.Context, count uint64) {
store := ctx.KVStore(pk.key)
store.Set(countKey, []byte(strconv.FormatUint(count, 16)))
}
func (pk Keeper) CheckValid(ctx sdk.Context, difficulty uint64, count uint64) (uint64, uint64, sdk.Error) {
lastDifficulty, err := pk.GetLastDifficulty(ctx)
if err != nil {
return 0, 0, ErrNonexistentDifficulty()
}
newDifficulty := lastDifficulty + 1
lastCount, err := pk.GetLastCount(ctx)
if err != nil {
return 0, 0, ErrNonexistentCount()
}
newCount := lastCount + 1
if count != newCount {
return 0, 0, ErrInvalidCount(fmt.Sprintf("invalid count: was %d, should have been %d", count, newCount))
}
if difficulty != newDifficulty {
return 0, 0, ErrInvalidDifficulty(fmt.Sprintf("invalid difficulty: was %d, should have been %d", difficulty, newDifficulty))
}
return newDifficulty, newCount, nil
}
func (pk Keeper) ApplyValid(ctx sdk.Context, sender sdk.Address, newDifficulty uint64, newCount uint64) sdk.Error {
_, ckErr := pk.ck.AddCoins(ctx, sender, []sdk.Coin{sdk.Coin{pk.config.Denomination, pk.config.Reward}})
if ckErr != nil {
return ckErr
}
pk.SetLastDifficulty(ctx, newDifficulty)
pk.SetLastCount(ctx, newCount)
return nil
}

View File

@ -0,0 +1,49 @@
package pow
import (
"testing"
"github.com/stretchr/testify/assert"
abci "github.com/tendermint/abci/types"
dbm "github.com/tendermint/tmlibs/db"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
auth "github.com/cosmos/cosmos-sdk/x/auth"
bank "github.com/cosmos/cosmos-sdk/x/bank"
)
// possibly share this kind of setup functionality between module testsuites?
func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) {
db := dbm.NewMemDB()
capKey := sdk.NewKVStoreKey("capkey")
ms := store.NewCommitMultiStore(db)
ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db)
ms.LoadLatestVersion()
return ms, capKey
}
func TestPowKeeperGetSet(t *testing.T) {
ms, capKey := setupMultiStore()
am := auth.NewAccountMapper(capKey, &auth.BaseAccount{})
ctx := sdk.NewContext(ms, abci.Header{}, false, nil)
config := NewPowConfig("pow", int64(1))
ck := bank.NewCoinKeeper(am)
keeper := NewKeeper(capKey, config, ck)
err := keeper.InitGenesis(ctx, PowGenesis{uint64(1), uint64(0)})
assert.Nil(t, err)
res, err := keeper.GetLastDifficulty(ctx)
assert.Nil(t, err)
assert.Equal(t, res, uint64(1))
keeper.SetLastDifficulty(ctx, 2)
res, err = keeper.GetLastDifficulty(ctx)
assert.Nil(t, err)
assert.Equal(t, res, uint64(2))
}

View File

@ -0,0 +1,44 @@
package pow
import (
"encoding/hex"
"math"
"strconv"
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
func GenerateMineMsg(sender sdk.Address, count uint64, difficulty uint64) MineMsg {
nonce, hash := mine(sender, count, difficulty)
return NewMineMsg(sender, difficulty, count, nonce, hash)
}
func hash(sender sdk.Address, count uint64, nonce uint64) []byte {
var bytes []byte
bytes = append(bytes, []byte(sender)...)
countBytes := strconv.FormatUint(count, 16)
bytes = append(bytes, countBytes...)
nonceBytes := strconv.FormatUint(nonce, 16)
bytes = append(bytes, nonceBytes...)
hash := crypto.Sha256(bytes)
// uint64, so we just use the first 8 bytes of the hash
// this limits the range of possible difficulty values (as compared to uint256), but fine for proof-of-concept
ret := make([]byte, hex.EncodedLen(len(hash)))
hex.Encode(ret, hash)
return ret[:16]
}
func mine(sender sdk.Address, count uint64, difficulty uint64) (uint64, []byte) {
target := math.MaxUint64 / difficulty
for nonce := uint64(0); ; nonce++ {
hash := hash(sender, count, nonce)
hashuint, err := strconv.ParseUint(string(hash), 16, 64)
if err != nil {
panic(err)
}
if hashuint < target {
return nonce, hash
}
}
}

View File

@ -0,0 +1,78 @@
package pow
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"math"
"strconv"
crypto "github.com/tendermint/go-crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// MineMsg - mine some coins with PoW
type MineMsg struct {
Sender sdk.Address `json:"sender"`
Difficulty uint64 `json:"difficulty"`
Count uint64 `json:"count"`
Nonce uint64 `json:"nonce"`
Proof []byte `json:"proof"`
}
// enforce the msg type at compile time
var _ sdk.Msg = MineMsg{}
// NewMineMsg - construct mine message
func NewMineMsg(sender sdk.Address, difficulty uint64, count uint64, nonce uint64, proof []byte) MineMsg {
return MineMsg{sender, difficulty, count, nonce, proof}
}
func (msg MineMsg) Type() string { return "pow" }
func (msg MineMsg) Get(key interface{}) (value interface{}) { return nil }
func (msg MineMsg) GetSigners() []sdk.Address { return []sdk.Address{msg.Sender} }
func (msg MineMsg) String() string {
return fmt.Sprintf("MineMsg{Sender: %v, Difficulty: %d, Count: %d, Nonce: %d, Proof: %s}", msg.Sender, msg.Difficulty, msg.Count, msg.Nonce, msg.Proof)
}
func (msg MineMsg) ValidateBasic() sdk.Error {
// check hash
var data []byte
// hash must include sender, so no other users can race the tx
data = append(data, []byte(msg.Sender)...)
countBytes := strconv.FormatUint(msg.Count, 16)
// hash must include count so proof-of-work solutions cannot be replayed
data = append(data, countBytes...)
nonceBytes := strconv.FormatUint(msg.Nonce, 16)
data = append(data, nonceBytes...)
hash := crypto.Sha256(data)
hashHex := make([]byte, hex.EncodedLen(len(hash)))
hex.Encode(hashHex, hash)
hashHex = hashHex[:16]
if !bytes.Equal(hashHex, msg.Proof) {
return ErrInvalidProof(fmt.Sprintf("hashHex: %s, proof: %s", hashHex, msg.Proof))
}
// check proof below difficulty
// difficulty is linear - 1 = all hashes, 2 = half of hashes, 3 = third of hashes, etc
target := math.MaxUint64 / msg.Difficulty
hashUint, err := strconv.ParseUint(string(msg.Proof), 16, 64)
if err != nil {
return ErrInvalidProof(fmt.Sprintf("proof: %s", msg.Proof))
}
if hashUint >= target {
return ErrNotBelowTarget(fmt.Sprintf("hashuint: %d, target: %d", hashUint, target))
}
return nil
}
func (msg MineMsg) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}

View File

@ -0,0 +1,78 @@
package pow
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestNewMineMsg(t *testing.T) {
addr := sdk.Address([]byte("sender"))
msg := MineMsg{addr, 0, 0, 0, []byte("")}
equiv := NewMineMsg(addr, 0, 0, 0, []byte(""))
assert.Equal(t, msg, equiv, "%s != %s", msg, equiv)
}
func TestMineMsgType(t *testing.T) {
addr := sdk.Address([]byte("sender"))
msg := MineMsg{addr, 0, 0, 0, []byte("")}
assert.Equal(t, msg.Type(), "pow")
}
func TestMineMsgValidation(t *testing.T) {
addr := sdk.Address([]byte("sender"))
otherAddr := sdk.Address([]byte("another"))
count := uint64(0)
for difficulty := uint64(1); difficulty < 1000; difficulty += 100 {
count += 1
nonce, proof := mine(addr, count, difficulty)
msg := MineMsg{addr, difficulty, count, nonce, proof}
err := msg.ValidateBasic()
assert.Nil(t, err, "error with difficulty %d - %+v", difficulty, err)
msg.Count += 1
err = msg.ValidateBasic()
assert.NotNil(t, err, "count was wrong, should have thrown error with msg %s", msg)
msg.Count -= 1
msg.Nonce += 1
err = msg.ValidateBasic()
assert.NotNil(t, err, "nonce was wrong, should have thrown error with msg %s", msg)
msg.Nonce -= 1
msg.Sender = otherAddr
err = msg.ValidateBasic()
assert.NotNil(t, err, "sender was wrong, should have thrown error with msg %s", msg)
}
}
func TestMineMsgString(t *testing.T) {
addr := sdk.Address([]byte("sender"))
msg := MineMsg{addr, 0, 0, 0, []byte("abc")}
res := msg.String()
assert.Equal(t, res, "MineMsg{Sender: 73656E646572, Difficulty: 0, Count: 0, Nonce: 0, Proof: abc}")
}
func TestMineMsgGet(t *testing.T) {
addr := sdk.Address([]byte("sender"))
msg := MineMsg{addr, 0, 0, 0, []byte("")}
res := msg.Get(nil)
assert.Nil(t, res)
}
func TestMineMsgGetSignBytes(t *testing.T) {
addr := sdk.Address([]byte("sender"))
msg := MineMsg{addr, 1, 1, 1, []byte("abc")}
res := msg.GetSignBytes()
assert.Equal(t, string(res), `{"sender":"73656E646572","difficulty":1,"count":1,"nonce":1,"proof":"YWJj"}`)
}
func TestMineMsgGetSigners(t *testing.T) {
addr := sdk.Address([]byte("sender"))
msg := MineMsg{addr, 1, 1, 1, []byte("abc")}
res := msg.GetSigners()
assert.Equal(t, fmt.Sprintf("%v", res), "[73656E646572]")
}

View File

@ -1,3 +0,0 @@
Gaiad is the abci application, which can be run stand-alone, or in-process with tendermint.
Gaiacli is a client application, which connects to tendermint rpc, and sends transactions and queries the state. It uses light-client proofs to guarantee the results even if it doesn't have 100% trust in the node it connects to.

View File

@ -1,72 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/spf13/cobra"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/cli"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/version"
)
// gaiadCmd is the entry point for this binary
var (
gaiadCmd = &cobra.Command{
Use: "gaiad",
Short: "Gaia Daemon (server)",
}
)
// defaultOptions sets up the app_options for the
// default genesis file
func defaultOptions(args []string) (json.RawMessage, string, cmn.HexBytes, error) {
addr, secret, err := server.GenerateCoinKey()
if err != nil {
return nil, "", nil, err
}
fmt.Println("Secret phrase to access coins:")
fmt.Println(secret)
opts := fmt.Sprintf(`{
"accounts": [{
"address": "%s",
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
]
}]
}`, addr)
return json.RawMessage(opts), secret, addr, nil
}
func generateApp(rootDir string, logger log.Logger) (abci.Application, error) {
// TODO: set this to something real
app := new(baseapp.BaseApp)
return app, nil
}
func main() {
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).
With("module", "main")
gaiadCmd.AddCommand(
server.InitCmd(defaultOptions, logger),
server.StartCmd(generateApp, logger),
server.UnsafeResetAllCmd(logger),
version.VersionCmd,
)
// prepare and add flags
executor := cli.PrepareBaseCmd(gaiadCmd, "GA", os.ExpandEnv("$HOME/.gaiad"))
executor.Execute()
}

View File

@ -41,7 +41,7 @@ func main() {
baseApp.SetTxDecoder(decodeTx)
// Set a handler Route.
baseApp.Router().AddRoute("kvstore", KVStoreHandler(capKeyMainStore), nil)
baseApp.Router().AddRoute("kvstore", KVStoreHandler(capKeyMainStore))
// Load latest version.
if err := baseApp.LoadLatestVersion(capKeyMainStore); err != nil {

View File

@ -6,7 +6,6 @@ import (
"path/filepath"
abci "github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
@ -39,7 +38,7 @@ func NewApp(rootDir string, logger log.Logger) (abci.Application, error) {
baseApp.SetInitChainer(InitChainer(capKeyMainStore))
// Set a handler Route.
baseApp.Router().AddRoute("kvstore", KVStoreHandler(capKeyMainStore), nil)
baseApp.Router().AddRoute("kvstore", KVStoreHandler(capKeyMainStore))
// Load latest version.
if err := baseApp.LoadLatestVersion(capKeyMainStore); err != nil {
@ -107,7 +106,7 @@ func InitChainer(key sdk.StoreKey) func(sdk.Context, abci.RequestInitChain) abci
// GenInitOptions can be passed into InitCmd,
// returns a static string of a few key-values that can be parsed
// by InitChainer
func GenInitOptions(args []string) (json.RawMessage, string, cmn.HexBytes, error) {
func GenInitOptions(args []string, addr sdk.Address, coinDenom string) (json.RawMessage, error) {
opts := []byte(`{
"values": [
{
@ -120,5 +119,5 @@ func GenInitOptions(args []string) (json.RawMessage, string, cmn.HexBytes, error
}
]
}`)
return opts, "", nil, nil
return opts, nil
}

View File

@ -21,7 +21,7 @@ func TestInitApp(t *testing.T) {
require.NoError(t, err)
// initialize it future-way
opts, _, _, err := GenInitOptions(nil)
opts, err := GenInitOptions(nil, nil, "")
require.NoError(t, err)
req := abci.RequestInitChain{AppStateBytes: opts}
app.InitChain(req)

View File

@ -1,62 +0,0 @@
// +build scripts
package main
/*
import (
"encoding/json"
"fmt"
"os"
"time"
"github.com/gorilla/websocket"
"github.com/tendermint/go-wire"
_ "github.com/tendermint/tendermint/rpc/core/types" // Register RPCResponse > Result types
"github.com/tendermint/tendermint/rpc/lib/client"
"github.com/tendermint/tendermint/rpc/lib/types"
cmn "github.com/tendermint/tmlibs/common"
)
func main() {
ws := rpcclient.NewWSClient(os.Args[1]+":46657", "/websocket")
_, err := ws.Start()
if err != nil {
cmn.Exit(err.Error())
}
// Read a bunch of responses
go func() {
for {
res, ok := <-ws.ResultsCh
if !ok {
break
}
//fmt.Println(counter, "res:", Blue(string(res)))
var result []interface{}
err := json.Unmarshal([]byte(string(res)), &result)
if err != nil {
Exit("Error unmarshalling block: " + err.Error())
}
height := result[1].(map[string]interface{})["block"].(map[string]interface{})["header"].(map[string]interface{})["height"]
txs := result[1].(map[string]interface{})["block"].(map[string]interface{})["data"].(map[string]interface{})["txs"]
if len(txs.([]interface{})) > 0 {
fmt.Println(">>", height, txs)
}
}
}()
for i := 0; i < 100000; i++ {
request := rpctypes.NewRPCRequest("fakeid", "block", Arr(i))
reqBytes := wire.JSONBytes(request)
err = ws.WriteMessage(websocket.TextMessage, reqBytes)
if err != nil {
cmn.Exit("writing websocket request: " + err.Error())
}
}
time.Sleep(time.Second * 1000)
ws.Stop()
}
*/

View File

@ -5,33 +5,43 @@ import (
"fmt"
"io/ioutil"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/spf13/cobra"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/words"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/p2p"
tmtypes "github.com/tendermint/tendermint/types"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
)
// testnetInformation contains the info necessary
// to setup a testnet including this account and validator.
type testnetInformation struct {
Secret string `json:"secret"`
Secret string `json:"secret"`
ChainID string `json:"chain_id"`
Account string `json:"account"`
Validator tmtypes.GenesisValidator `json:"validator"`
NodeID p2p.ID `json:"node_id"`
}
type initCmd struct {
genAppState GenAppState
context *Context
}
// InitCmd will initialize all files for tendermint,
// along with proper app_state.
// The application can pass in a function to generate
// proper state. And may want to use GenerateCoinKey
// to create default account(s).
func InitCmd(gen GenAppState, logger log.Logger) *cobra.Command {
func InitCmd(gen GenAppState, ctx *Context) *cobra.Command {
cmd := initCmd{
genAppState: gen,
logger: logger,
context: ctx,
}
cobraCmd := cobra.Command{
Use: "init",
@ -41,28 +51,14 @@ func InitCmd(gen GenAppState, logger log.Logger) *cobra.Command {
return &cobraCmd
}
// GenAppState can parse command-line to
// generate default app_state for the genesis file.
// Also must return generated seed and address
// This is application-specific
type GenAppState func(args []string) (json.RawMessage, string, cmn.HexBytes, error)
type initCmd struct {
genAppState GenAppState
logger log.Logger
}
func (c initCmd) run(cmd *cobra.Command, args []string) error {
// Store testnet information as we go
var testnetInfo testnetInformation
// Run the basic tendermint initialization,
// set up a default genesis with no app_options
config, err := tcmd.ParseConfig()
if err != nil {
return err
}
err = c.initTendermintFiles(config, &testnetInfo)
config := c.context.Config
err := c.initTendermintFiles(config, &testnetInfo)
if err != nil {
return err
}
@ -72,14 +68,22 @@ func (c initCmd) run(cmd *cobra.Command, args []string) error {
return nil
}
// generate secrete and address
addr, secret, err := GenerateCoinKey()
if err != nil {
return err
}
var DEFAULT_DENOM = "mycoin"
// Now, we want to add the custom app_state
appState, secret, address, err := c.genAppState(args)
appState, err := c.genAppState(args, addr, DEFAULT_DENOM)
if err != nil {
return err
}
testnetInfo.Secret = secret
testnetInfo.Account = address.String()
testnetInfo.Account = addr.String()
// And add them to the genesis file
genFile := config.GenesisFile()
@ -108,17 +112,17 @@ func (c initCmd) initTendermintFiles(config *cfg.Config, info *testnetInformatio
var privValidator *tmtypes.PrivValidatorFS
if cmn.FileExists(privValFile) {
privValidator = tmtypes.LoadPrivValidatorFS(privValFile)
c.logger.Info("Found private validator", "path", privValFile)
c.context.Logger.Info("Found private validator", "path", privValFile)
} else {
privValidator = tmtypes.GenPrivValidatorFS(privValFile)
privValidator.Save()
c.logger.Info("Generated private validator", "path", privValFile)
c.context.Logger.Info("Generated private validator", "path", privValFile)
}
// genesis file
genFile := config.GenesisFile()
if cmn.FileExists(genFile) {
c.logger.Info("Found genesis file", "path", genFile)
c.context.Logger.Info("Found genesis file", "path", genFile)
} else {
genDoc := tmtypes.GenesisDoc{
ChainID: cmn.Fmt("test-chain-%v", cmn.RandStr(6)),
@ -131,7 +135,7 @@ func (c initCmd) initTendermintFiles(config *cfg.Config, info *testnetInformatio
if err := genDoc.SaveAs(genFile); err != nil {
return err
}
c.logger.Info("Generated genesis file", "path", genFile)
c.context.Logger.Info("Generated genesis file", "path", genFile)
}
// reload the config file and find our validator info
@ -144,10 +148,39 @@ func (c initCmd) initTendermintFiles(config *cfg.Config, info *testnetInformatio
info.Validator = validator
}
}
info.ChainID = loadedDoc.ChainID
return nil
}
//-------------------------------------------------------------------
// GenAppState takes the command line args, as well
// as an address and coin denomination.
// It returns a default app_state to be included in
// in the genesis file.
// This is application-specific
type GenAppState func(args []string, addr sdk.Address, coinDenom string) (json.RawMessage, error)
// DefaultGenAppState expects two args: an account address
// and a coin denomination, and gives lots of coins to that address.
func DefaultGenAppState(args []string, addr sdk.Address, coinDenom string) (json.RawMessage, error) {
opts := fmt.Sprintf(`{
"accounts": [{
"address": "%s",
"coins": [
{
"denom": "%s",
"amount": 9007199254740992
}
]
}]
}`, addr.String(), coinDenom)
return json.RawMessage(opts), nil
}
//-------------------------------------------------------------------
// GenesisDoc involves some tendermint-specific structures we don't
// want to parse, so we just grab it into a raw object format,
// so we can add one line.
@ -173,3 +206,30 @@ func addGenesisState(filename string, appState json.RawMessage) error {
return ioutil.WriteFile(filename, out, 0600)
}
//-------------------------------------------------------------------
// 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
}

View File

@ -8,13 +8,17 @@ import (
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/mock"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
)
func TestInit(t *testing.T) {
defer setupViper(t)()
logger := log.NewNopLogger()
cmd := InitCmd(mock.GenInitOptions, logger)
err := cmd.RunE(nil, nil)
cfg, err := tcmd.ParseConfig()
require.Nil(t, err)
ctx := NewContext(cfg, logger)
cmd := InitCmd(mock.GenInitOptions, ctx)
err = cmd.RunE(nil, nil)
require.NoError(t, err)
}

View File

@ -1,34 +0,0 @@
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
}

View File

@ -1,31 +0,0 @@
package server
import (
"github.com/spf13/cobra"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tmlibs/log"
)
// UnsafeResetAllCmd - extension of the tendermint command, resets initialization
func UnsafeResetAllCmd(logger log.Logger) *cobra.Command {
cmd := resetAll{logger}
return &cobra.Command{
Use: "unsafe_reset_all",
Short: "Reset all blockchain data",
RunE: cmd.run,
}
}
type resetAll struct {
logger log.Logger
}
func (r resetAll) run(cmd *cobra.Command, args []string) error {
cfg, err := tcmd.ParseConfig()
if err != nil {
return err
}
tcmd.ResetAll(cfg.DBDir(), cfg.PrivValidatorFile(), r.logger)
return nil
}

View File

@ -1,38 +0,0 @@
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

@ -1,40 +0,0 @@
package server
import (
"fmt"
"github.com/spf13/cobra"
"github.com/tendermint/go-wire/data"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/types"
"github.com/tendermint/tmlibs/log"
)
// ShowValidator - ported from Tendermint, show this node's validator info
func ShowValidatorCmd(logger log.Logger) *cobra.Command {
cmd := showValidator{logger}
return &cobra.Command{
Use: "show_validator",
Short: "Show this node's validator info",
RunE: cmd.run,
}
}
type showValidator struct {
logger log.Logger
}
func (s showValidator) run(cmd *cobra.Command, args []string) error {
cfg, err := tcmd.ParseConfig()
if err != nil {
return err
}
privValidator := types.LoadOrGenPrivValidatorFS(cfg.PrivValidatorFile())
pubKeyJSONBytes, err := data.ToJSON(privValidator.PubKey)
if err != nil {
return err
}
fmt.Println(string(pubKeyJSONBytes))
return nil
}

View File

@ -21,16 +21,16 @@ const (
flagAddress = "address"
)
// appGenerator lets us lazily initialize app, using home dir
// AppCreator lets us lazily initialize app, using home dir
// and other flags (?) to start
type appCreator 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 appCreator, logger log.Logger) *cobra.Command {
func StartCmd(app AppCreator, ctx *Context) *cobra.Command {
start := startCmd{
appCreator: app,
logger: logger,
context: ctx,
}
cmd := &cobra.Command{
Use: "start",
@ -48,16 +48,16 @@ func StartCmd(app appCreator, logger log.Logger) *cobra.Command {
}
type startCmd struct {
appCreator appCreator
logger log.Logger
appCreator AppCreator
context *Context
}
func (s startCmd) run(cmd *cobra.Command, args []string) error {
if !viper.GetBool(flagWithTendermint) {
s.logger.Info("Starting ABCI without Tendermint")
s.context.Logger.Info("Starting ABCI without Tendermint")
return s.startStandAlone()
}
s.logger.Info("Starting ABCI with Tendermint")
s.context.Logger.Info("Starting ABCI with Tendermint")
return s.startInProcess()
}
@ -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.appCreator(home, s.logger)
app, err := s.appCreator(home, s.context.Logger)
if err != nil {
return err
}
@ -74,7 +74,7 @@ func (s startCmd) startStandAlone() error {
if err != nil {
return errors.Errorf("Error creating listener: %v\n", err)
}
svr.SetLogger(s.logger.With("module", "abci-server"))
svr.SetLogger(s.context.Logger.With("module", "abci-server"))
svr.Start()
// Wait forever
@ -86,13 +86,9 @@ func (s startCmd) startStandAlone() error {
}
func (s startCmd) startInProcess() error {
cfg, err := tcmd.ParseConfig()
if err != nil {
return err
}
cfg := s.context.Config
home := cfg.RootDir
app, err := s.appCreator(home, s.logger)
app, err := s.appCreator(home, s.context.Logger)
if err != nil {
return err
}
@ -103,7 +99,7 @@ func (s startCmd) startInProcess() error {
proxy.NewLocalClientCreator(app),
node.DefaultGenesisDocProviderFunc(cfg),
node.DefaultDBProvider,
s.logger.With("module", "node"))
s.context.Logger.With("module", "node"))
if err != nil {
return err
}

View File

@ -1,7 +1,8 @@
package server
import (
// "os"
"io/ioutil"
"os"
"testing"
"time"
@ -9,47 +10,56 @@ import (
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/mock"
"github.com/tendermint/abci/server"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tmlibs/log"
)
func TestStartStandAlone(t *testing.T) {
defer setupViper(t)()
home, err := ioutil.TempDir("", "mock-sdk-cmd")
defer func() {
os.RemoveAll(home)
}()
logger := log.NewNopLogger()
initCmd := InitCmd(mock.GenInitOptions, logger)
err := initCmd.RunE(nil, nil)
cfg, err := tcmd.ParseConfig()
require.Nil(t, err)
ctx := NewContext(cfg, logger)
initCmd := InitCmd(mock.GenInitOptions, ctx)
err = initCmd.RunE(nil, nil)
require.NoError(t, err)
// set up app and start up
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
app, err := mock.NewApp(home, logger)
require.Nil(t, err)
svr, err := server.NewServer(FreeTCPAddr(t), "socket", app)
require.Nil(t, err, "Error creating listener")
svr.SetLogger(logger.With("module", "abci-server"))
svr.Start()
RunOrTimeout(startCmd, timeout, t)
timer := time.NewTimer(time.Duration(5) * time.Second)
select {
case <-timer.C:
svr.Stop()
}
}
/*
func TestStartWithTendermint(t *testing.T) {
defer setupViper(t)()
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).
With("module", "mock-cmd")
// logger := log.NewNopLogger()
initCmd := InitCmd(mock.GenInitOptions, logger)
err := initCmd.RunE(nil, nil)
cfg, err := tcmd.ParseConfig()
require.Nil(t, err)
ctx := NewContext(cfg, logger)
initCmd := InitCmd(mock.GenInitOptions, ctx)
err = initCmd.RunE(nil, nil)
require.NoError(t, err)
// set up app and start up
viper.Set(flagWithTendermint, true)
startCmd := StartCmd(mock.NewApp, logger)
startCmd := StartCmd(mock.NewApp, ctx)
startCmd.Flags().Set(flagAddress, FreeTCPAddr(t)) // set to a new free address
timeout := time.Duration(3) * time.Second
timeout := time.Duration(5) * time.Second
//a, _ := startCmd.Flags().GetString(flagAddress)
//panic(a)
RunOrTimeout(startCmd, timeout, t)
close(RunOrTimeout(startCmd, timeout, t))
}
*/

View File

@ -12,6 +12,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tmlibs/cli"
"github.com/tendermint/tmlibs/log"
)
@ -44,14 +45,18 @@ func setupViper(t *testing.T) func() {
func StartServer(t *testing.T) chan error {
defer setupViper(t)()
cfg, err := tcmd.ParseConfig()
require.Nil(t, err)
// init server
initCmd := InitCmd(mock.GenInitOptions, log.NewNopLogger())
err := initCmd.RunE(nil, nil)
ctx := NewContext(cfg, log.NewNopLogger())
initCmd := InitCmd(mock.GenInitOptions, ctx)
err = initCmd.RunE(nil, nil)
require.NoError(t, err)
// start server
viper.Set(flagWithTendermint, true)
startCmd := StartCmd(mock.NewApp, log.NewNopLogger())
startCmd := StartCmd(mock.NewApp, ctx)
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

85
server/tm_cmds.go Normal file
View File

@ -0,0 +1,85 @@
package server
import (
"fmt"
"github.com/spf13/cobra"
"github.com/tendermint/go-wire/data"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/types"
)
// ShowNodeIDCmd - ported from Tendermint, dump node ID to stdout
func ShowNodeIDCmd(ctx *Context) *cobra.Command {
cmd := showNodeId{ctx}
return &cobra.Command{
Use: "show_node_id",
Short: "Show this node's ID",
RunE: cmd.run,
}
}
type showNodeId struct {
context *Context
}
func (s showNodeId) run(cmd *cobra.Command, args []string) error {
cfg := s.context.Config
nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile())
if err != nil {
return err
}
fmt.Println(nodeKey.ID())
return nil
}
//--------------------------------------------------------------------------------
// ShowValidator - ported from Tendermint, show this node's validator info
func ShowValidatorCmd(ctx *Context) *cobra.Command {
cmd := showValidator{ctx}
return &cobra.Command{
Use: "show_validator",
Short: "Show this node's validator info",
RunE: cmd.run,
}
}
type showValidator struct {
context *Context
}
func (s showValidator) run(cmd *cobra.Command, args []string) error {
cfg := s.context.Config
privValidator := types.LoadOrGenPrivValidatorFS(cfg.PrivValidatorFile())
pubKeyJSONBytes, err := data.ToJSON(privValidator.PubKey)
if err != nil {
return err
}
fmt.Println(string(pubKeyJSONBytes))
return nil
}
//------------------------------------------------------------------------------
// UnsafeResetAllCmd - extension of the tendermint command, resets initialization
func UnsafeResetAllCmd(ctx *Context) *cobra.Command {
cmd := resetAll{ctx}
return &cobra.Command{
Use: "unsafe_reset_all",
Short: "Reset all blockchain data",
RunE: cmd.run,
}
}
type resetAll struct {
context *Context
}
func (r resetAll) run(cmd *cobra.Command, args []string) error {
cfg := r.context.Config
tcmd.ResetAll(cfg.DBDir(), cfg.PrivValidatorFile(), r.context.Logger)
return nil
}

77
server/util.go Normal file
View File

@ -0,0 +1,77 @@
package server
import (
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/version"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tmlibs/cli"
tmflags "github.com/tendermint/tmlibs/cli/flags"
"github.com/tendermint/tmlibs/log"
)
type Context struct {
Config *cfg.Config
Logger log.Logger
}
func NewDefaultContext() *Context {
return NewContext(
cfg.DefaultConfig(),
log.NewTMLogger(log.NewSyncWriter(os.Stdout)),
)
}
func NewContext(config *cfg.Config, logger log.Logger) *Context {
return &Context{config, logger}
}
//--------------------------------------------------------------------
// PersistentPreRunEFn returns a PersistentPreRunE function for cobra
// that initailizes the passed in context with a properly configured
// logger and config objecy
func PersistentPreRunEFn(context *Context) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
if cmd.Name() == version.VersionCmd.Name() {
return nil
}
config, err := tcmd.ParseConfig()
if err != nil {
return err
}
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel())
if err != nil {
return err
}
if viper.GetBool(cli.TraceFlag) {
logger = log.NewTracingLogger(logger)
}
logger = logger.With("module", "main")
context.Config = config
context.Logger = logger
return nil
}
}
func AddCommands(
rootCmd *cobra.Command,
appState GenAppState, appCreator AppCreator,
context *Context) {
rootCmd.PersistentFlags().String("log_level", context.Config.LogLevel, "Log level")
rootCmd.AddCommand(
InitCmd(appState, context),
StartCmd(appCreator, context),
UnsafeResetAllCmd(context),
ShowNodeIDCmd(context),
ShowValidatorCmd(context),
version.VersionCmd,
)
}

View File

@ -141,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.ErrTxDecode(msg).Result().ToQuery()
return sdk.ErrTxDecode(msg).QueryResult()
}
tree := st.tree
@ -175,7 +175,7 @@ func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
default:
msg := fmt.Sprintf("Unexpected Query path: %v", req.Path)
return sdk.ErrUnknownRequest(msg).Result().ToQuery()
return sdk.ErrUnknownRequest(msg).QueryResult()
}
return
}

View File

@ -205,18 +205,18 @@ func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery {
path := req.Path
storeName, subpath, err := parsePath(path)
if err != nil {
return err.Result().ToQuery()
return err.QueryResult()
}
store := rs.getStoreByName(storeName)
if store == nil {
msg := fmt.Sprintf("no such store: %s", storeName)
return sdk.ErrUnknownRequest(msg).Result().ToQuery()
return sdk.ErrUnknownRequest(msg).QueryResult()
}
queryable, ok := store.(Queryable)
if !ok {
msg := fmt.Sprintf("store %s doesn't support queries", storeName)
return sdk.ErrUnknownRequest(msg).Result().ToQuery()
return sdk.ErrUnknownRequest(msg).QueryResult()
}
// trim the path and make the query

View File

@ -257,7 +257,10 @@ func (coins Coins) Swap(i, j int) { coins[i], coins[j] = coins[j], coins[i]
var _ sort.Interface = Coins{}
// Sort is a helper function to sort the set of coins inplace
func (coins Coins) Sort() { sort.Sort(coins) }
func (coins Coins) Sort() Coins {
sort.Sort(coins)
return coins
}
//----------------------------------------
// Parsing

View File

@ -3,6 +3,8 @@ package types
import (
"fmt"
"runtime"
abci "github.com/tendermint/abci/types"
)
// ABCI Response Code
@ -121,6 +123,7 @@ type Error interface {
TraceCause(cause error, msg string) Error
Cause() error
Result() Result
QueryResult() abci.ResponseQuery
}
func NewError(code CodeType, msg string) Error {
@ -220,3 +223,11 @@ func (err *sdkError) Result() Result {
Log: err.ABCILog(),
}
}
// QueryResult allows us to return sdk.Error.QueryResult() in query responses
func (err *sdkError) QueryResult() abci.ResponseQuery {
return abci.ResponseQuery{
Code: uint32(err.ABCICode()),
Log: err.ABCILog(),
}
}

View File

@ -38,11 +38,3 @@ type Result struct {
func (res Result) IsOK() bool {
return res.Code.IsOK()
}
// ToQuery allows us to return sdk.Error.Result() in query responses
func (res Result) ToQuery() abci.ResponseQuery {
return abci.ResponseQuery{
Code: uint32(res.Code),
Log: res.Log,
}
}

View File

@ -19,7 +19,7 @@ var (
func getVersion() string {
v := Version
if GitCommit != "" {
v = v + " " + GitCommit
v = v + "-" + GitCommit
}
return v
}

View File

@ -7,9 +7,9 @@ package version
const Maj = "0"
const Min = "13"
const Fix = "0"
const Fix = "2"
const Version = "0.13.0-dev"
const Version = "0.13.2-dev"
// GitCommit set by build flags
var GitCommit = ""

View File

@ -8,7 +8,7 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client/builder"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
@ -64,7 +64,9 @@ func (c commander) getAccountCmd(cmd *cobra.Command, args []string) error {
}
key := sdk.Address(bz)
res, err := builder.Query(key, c.storeName)
ctx := context.NewCoreContextFromViper()
res, err := ctx.Query(key, c.storeName)
if err != nil {
return err
}

View File

@ -8,7 +8,7 @@ import (
"github.com/gorilla/mux"
"github.com/cosmos/cosmos-sdk/client/builder"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
@ -21,6 +21,7 @@ type commander struct {
func QueryAccountRequestHandler(storeName string, cdc *wire.Codec, decoder sdk.AccountDecoder) func(http.ResponseWriter, *http.Request) {
c := commander{storeName, cdc, decoder}
ctx := context.NewCoreContextFromViper()
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
addr := vars["address"]
@ -33,7 +34,7 @@ func QueryAccountRequestHandler(storeName string, cdc *wire.Codec, decoder sdk.A
}
key := sdk.Address(bz)
res, err := builder.Query(key, c.storeName)
res, err := ctx.Query(key, c.storeName)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Could't query account. Error: %s", err.Error())))

View File

@ -8,7 +8,7 @@ import (
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/builder"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/bank"
@ -38,8 +38,10 @@ type Commander struct {
}
func (c Commander) sendTxCmd(cmd *cobra.Command, args []string) error {
ctx := context.NewCoreContextFromViper()
// get the from address
from, err := builder.GetFromAddress()
from, err := ctx.GetFromAddress()
if err != nil {
return err
}
@ -59,14 +61,11 @@ func (c Commander) sendTxCmd(cmd *cobra.Command, args []string) error {
}
to := sdk.Address(bz)
// get account name
name := viper.GetString(client.FlagName)
// build message
msg := BuildMsg(from, to, coins)
// build and sign the transaction, then broadcast to Tendermint
res, err := builder.SignBuildBroadcast(name, msg, c.Cdc)
res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, c.Cdc)
if err != nil {
return err
}

View File

@ -195,7 +195,7 @@ func (out Output) ValidateBasic() sdk.Error {
}
func (out Output) String() string {
return fmt.Sprintf("Output{%X,%v}", out.Address, out.Coins)
return fmt.Sprintf("Output{%v,%v}", out.Address, out.Coins)
}
// NewOutput - create a transaction output, used with SendMsg

View File

@ -179,8 +179,10 @@ func TestSendMsgValidation(t *testing.T) {
func TestSendMsgString(t *testing.T) {
// Construct a SendMsg
addr1 := sdk.Address([]byte("input"))
addr2 := sdk.Address([]byte("output"))
addr1String := "input"
addr2String := "output"
addr1 := sdk.Address([]byte(addr1String))
addr2 := sdk.Address([]byte(addr2String))
coins := sdk.Coins{{"atom", 10}}
var msg = SendMsg{
Inputs: []Input{NewInput(addr1, coins)},
@ -188,8 +190,9 @@ func TestSendMsgString(t *testing.T) {
}
res := msg.String()
expected := fmt.Sprintf("SendMsg{[Input{%X,10atom}]->[Output{%X,10atom}]}", addr1String, addr2String)
// TODO some failures for bad results
assert.Equal(t, res, "SendMsg{[Input{696E707574,10atom}]->[Output{364637353734373037353734,10atom}]}")
assert.Equal(t, expected, res)
}
func TestSendMsgGet(t *testing.T) {
@ -275,16 +278,18 @@ func TestIssueMsgValidation(t *testing.T) {
}
func TestIssueMsgString(t *testing.T) {
addrString := "loan-from-bank"
bankerString := "input"
// Construct a IssueMsg
addr := sdk.Address([]byte("loan-from-bank"))
addr := sdk.Address([]byte(addrString))
coins := sdk.Coins{{"atom", 10}}
var msg = IssueMsg{
Banker: sdk.Address([]byte("input")),
Banker: sdk.Address([]byte(bankerString)),
Outputs: []Output{NewOutput(addr, coins)},
}
res := msg.String()
// TODO: FIX THIS OUTPUT!
assert.Equal(t, res, "IssueMsg{696E707574#[Output{36433646363136453244363637323646364432443632363136453642,10atom}]}")
expected := fmt.Sprintf("IssueMsg{%X#[Output{%X,10atom}]}", bankerString, addrString)
assert.Equal(t, expected, res)
}
func TestIssueMsgGet(t *testing.T) {

View File

@ -7,11 +7,9 @@ import (
"net/http"
"github.com/gorilla/mux"
"github.com/spf13/viper"
"github.com/tendermint/go-crypto/keys"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/builder"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/bank/commands"
@ -30,6 +28,7 @@ type sendBody struct {
// SendRequestHandler - http request handler to send coins to a address
func SendRequestHandler(cdc *wire.Codec, kb keys.Keybase) func(http.ResponseWriter, *http.Request) {
c := commands.Commander{cdc}
ctx := context.NewCoreContextFromViper()
return func(w http.ResponseWriter, r *http.Request) {
// collect data
vars := mux.Vars(r)
@ -73,9 +72,8 @@ func SendRequestHandler(cdc *wire.Codec, kb keys.Keybase) func(http.ResponseWrit
}
// sign
// XXX: OMG
viper.Set(client.FlagSequence, m.Sequence)
txBytes, err := builder.SignAndBuild(m.LocalAccountName, m.Password, msg, c.Cdc)
ctx = ctx.WithSequence(m.Sequence)
txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, msg, c.Cdc)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(err.Error()))
@ -83,7 +81,7 @@ func SendRequestHandler(cdc *wire.Codec, kb keys.Keybase) func(http.ResponseWrit
}
// send
res, err := builder.BroadcastTx(txBytes)
res, err := ctx.BroadcastTx(txBytes)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))

View File

@ -1,25 +1,157 @@
# IBC CLI Usage
# IBC Doubble Hubble
## initialize
## Remove remaining data
```bash
basecoind init # copy the recover key
basecli keys add keyname --recover
basecoind start
```console
> rm -r ~/.chain1
> rm -r ~/.chain2
> rm -r ~/.basecli
```
## transfer
## Initialize both chains
`transfer` sends coins from one chain to another(or itself).
```console
> basecoind init --home ~/.chain1
I[04-02|14:03:33.704] Generated private validator module=main path=/home/mossid/.chain1/config/priv_validator.json
I[04-02|14:03:33.705] Generated genesis file module=main path=/home/mossid/.chain1/config/genesis.json
{
"secret": "crunch ignore trigger neither differ dance cheap brick situate floor luxury citizen husband decline arrow abandon",
"account": "C69FEB398A29AAB1B3C4F07DE22208F35E711BCC",
"validator": {
"pub_key": {
"type": "ed25519",
"data": "8C9917D5E982E221F5A1450103102B44BBFC1E8768126C606246CB37B5794F4D"
},
"power": 10,
"name": ""
},
"node_id": "3ac8e6242315fd62143dc3e52c161edaaa6b1a64",
"chain_id": "test-chain-ZajMfr"
}
> ADDR1=C69FEB398A29AAB1B3C4F07DE22208F35E711BCC
> ID1=test-chain-ZajMfr
> NODE1=tcp://0.0.0.0:36657
> basecli keys add key1 --recover
Enter a passphrase for your key:
Repeat the passphrase:
Enter your recovery seed phrase:
crunch ignore trigger neither differ dance cheap brick situate floor luxury citizen husband decline arrow abandon
key1 C69FEB398A29AAB1B3C4F07DE22208F35E711BCC
> basecoind init --home ~/.chain2
I[04-02|14:09:14.453] Generated private validator module=main path=/home/mossid/.chain2/config/priv_validator.json
I[04-02|14:09:14.453] Generated genesis file module=main path=/home/mossid/.chain2/config/genesis.json
{
"secret": "age guide awesome month female left oxygen soccer define high grocery work desert dinner arena abandon",
"account": "DC26002735D3AA9573707CFA6D77C12349E49868",
"validator": {
"pub_key": {
"type": "ed25519",
"data": "A94FE4B9AD763D301F4DD5A2766009812495FB7A79F1275FB8A5AF09B44FD5F3"
},
"power": 10,
"name": ""
},
"node_id": "ad26831330e1c72b85276d53c20f0680e6fd4cf5"
"chain_id": "test-chain-4XHTPn"
}
> ADDR2=DC26002735D3AA9573707CFA6D77C12349E49868
> ID2=test-chain-4XHTPn
> NODE2=tcp://0.0.0.0:46657
> basecli keys add key2 --recover
Enter a passphrase for your key:
Repeat the passphrase:
Enter your recovery seed phrase:
age guide awesome month female left oxygen soccer define high grocery work desert dinner arena abandon
key2 DC26002735D3AA9573707CFA6D77C12349E49868
> basecoind start --home ~/.chain1 --address tcp://0.0.0.0:36658 --rpc.laddr tcp://0.0.0.0:36657 --p2p.laddr tcp://0.0.0.0:36656
...
> basecoind start --home ~/.chain2 # --address tcp://0.0.0.0:46658 --rpc.laddr tcp://0.0.0.0:46657 --p2p.laddr tcp://0.0.0.0:46656
...
```
## Check balance
```console
> basecli account $ADDR1 --node $NODE1
{
"address": "C69FEB398A29AAB1B3C4F07DE22208F35E711BCC",
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
],
"public_key": null,
"sequence": 0,
"name": ""
}
> basecli account $ADDR2 --node $NODE2
{
"address": "DC26002735D3AA9573707CFA6D77C12349E49868",
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
],
"public_key": null,
"sequence": 0,
"name": ""
}
```bash
basecli transfer --name keyname --to address_of_destination --amount 10mycoin --chain test-chain-AAAAAA --chain-id AAAAAA
```
The id of the chain can be found in `$HOME/.basecoind/config/genesis.json`
## Transfer coins (addr1:chain1 -> addr2:chain2)
```console
> basecli transfer --name key1 --to $ADDR2 --amount 10mycoin --chain $ID2 --chain-id $ID1 --node $NODE1
Password to sign with 'key1':
Committed at block 1022. Hash: E16019DCC4AA08CA70AFCFBC96028ABCC51B6AD0
> basecli account $ADDR1 --node $NODE1
{
"address": "C69FEB398A29AAB1B3C4F07DE22208F35E711BCC",
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740982
}
],
"public_key": {
"type": "ed25519",
"data": "9828FF1780A066A0D93D840737566B697035448D6C880807322BED8919348B2B"
},
"sequence": 1,
"name": ""
}
```
## Relay IBC packets
```console
> basecli relay --name key2 --from-chain-id $ID1 --from-chain-node $NODE1 --to-chain-id $ID2 --to-chain-node $NODE2 --chain-id $ID2
Password to sign with 'key2':
I[04-03|16:18:59.984] Detected IBC packet number=0
I[04-03|16:19:00.869] Relayed IBC packet number=0
> basecli account $ADDR2 --node $NODE2
{
"address": "DC26002735D3AA9573707CFA6D77C12349E49868",
"coins": [
{
"denom": "mycoin",
"amount": 9007199254741002
}
],
"public_key": {
"type": "ed25519",
"data": "F52B4FA545F4E9BFE5D7AF1DD2236899FDEF905F9B3057C38D7C01BF1B8EB52E"
},
"sequence": 1,
"name": ""
}
## relay
```bash
basecli relay --name keyname --from-chain-id test-chain-AAAAAA --from-chain-node=tcp://0.0.0.0:46657 --to-chain-id test-chain-AAAAAA --to-chain-node=tcp://0.0.0.0:46657
```

View File

@ -8,7 +8,7 @@ import (
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/builder"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
wire "github.com/cosmos/cosmos-sdk/wire"
@ -39,8 +39,10 @@ type sendCommander struct {
}
func (c sendCommander) sendIBCTransfer(cmd *cobra.Command, args []string) error {
ctx := context.NewCoreContextFromViper()
// get the from address
from, err := builder.GetFromAddress()
from, err := ctx.GetFromAddress()
if err != nil {
return err
}
@ -52,9 +54,7 @@ func (c sendCommander) sendIBCTransfer(cmd *cobra.Command, args []string) error
}
// get password
name := viper.GetString(client.FlagName)
res, err := builder.SignBuildBroadcast(name, msg, c.cdc)
res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, c.cdc)
if err != nil {
return err
}
@ -77,7 +77,7 @@ func buildMsg(from sdk.Address) (sdk.Msg, error) {
}
to := sdk.Address(bz)
packet := ibc.NewIBCPacket(from, to, coins, client.FlagChainID,
packet := ibc.NewIBCPacket(from, to, coins, viper.GetString(client.FlagChainID),
viper.GetString(flagChain))
msg := ibc.IBCTransferMsg{

View File

@ -1,14 +1,15 @@
package commands
import (
"fmt"
"os"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/builder"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
wire "github.com/cosmos/cosmos-sdk/wire"
@ -30,6 +31,8 @@ type relayCommander struct {
decoder sdk.AccountDecoder
mainStore string
ibcStore string
logger log.Logger
}
func IBCRelayCmd(cdc *wire.Codec) *cobra.Command {
@ -38,6 +41,8 @@ func IBCRelayCmd(cdc *wire.Codec) *cobra.Command {
decoder: authcmd.GetAccountDecoder(cdc),
ibcStore: "ibc",
mainStore: "main",
logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)),
}
cmd := &cobra.Command{
@ -68,7 +73,7 @@ func (c relayCommander) runIBCRelay(cmd *cobra.Command, args []string) {
fromChainNode := viper.GetString(FlagFromChainNode)
toChainID := viper.GetString(FlagToChainID)
toChainNode := viper.GetString(FlagToChainNode)
address, err := builder.GetFromAddress()
address, err := context.NewCoreContextFromViper().GetFromAddress()
if err != nil {
panic(err)
}
@ -78,35 +83,34 @@ func (c relayCommander) runIBCRelay(cmd *cobra.Command, args []string) {
}
func (c relayCommander) loop(fromChainID, fromChainNode, toChainID, toChainNode string) {
ctx := context.NewCoreContextFromViper()
// get password
name := viper.GetString(client.FlagName)
passphrase, err := builder.GetPassphraseFromStdin(name)
passphrase, err := ctx.GetPassphraseFromStdin(ctx.FromAddressName)
if err != nil {
panic(err)
}
ingressKey := ibc.IngressSequenceKey(fromChainID)
processedbz, err := query(toChainNode, ingressKey, c.ibcStore)
if err != nil {
panic(err)
}
var processed int64
if processedbz == nil {
processed = 0
} else if err = c.cdc.UnmarshalBinary(processedbz, &processed); err != nil {
panic(err)
}
OUTER:
for {
time.Sleep(time.Second)
time.Sleep(5 * time.Second)
processedbz, err := query(toChainNode, ingressKey, c.ibcStore)
if err != nil {
panic(err)
}
var processed int64
if processedbz == nil {
processed = 0
} else if err = c.cdc.UnmarshalBinary(processedbz, &processed); err != nil {
panic(err)
}
lengthKey := ibc.EgressLengthKey(toChainID)
egressLengthbz, err := query(fromChainNode, lengthKey, c.ibcStore)
if err != nil {
fmt.Printf("Error querying outgoing packet list length: '%s'\n", err)
c.logger.Error("Error querying outgoing packet list length", "err", err)
continue OUTER
}
var egressLength int64
@ -115,43 +119,37 @@ OUTER:
} else if err = c.cdc.UnmarshalBinary(egressLengthbz, &egressLength); err != nil {
panic(err)
}
fmt.Printf("egressLength queried: %d\n", egressLength)
if egressLength > processed {
c.logger.Info("Detected IBC packet", "number", egressLength-1)
}
seq := c.getSequence(toChainNode)
for i := processed; i < egressLength; i++ {
egressbz, err := query(fromChainNode, ibc.EgressKey(toChainID, i), c.ibcStore)
if err != nil {
fmt.Printf("Error querying egress packet: '%s'\n", err)
c.logger.Error("Error querying egress packet", "err", err)
continue OUTER
}
err = c.broadcastTx(toChainNode, c.refine(egressbz, i, passphrase))
err = c.broadcastTx(seq, toChainNode, c.refine(egressbz, i, passphrase))
seq++
if err != nil {
fmt.Printf("Error broadcasting ingress packet: '%s'\n", err)
c.logger.Error("Error broadcasting ingress packet", "err", err)
continue OUTER
}
fmt.Printf("Relayed packet: %d\n", i)
c.logger.Info("Relayed IBC packet", "number", i)
}
processed = egressLength
}
}
func query(node string, key []byte, storeName string) (res []byte, err error) {
orig := viper.GetString(client.FlagNode)
viper.Set(client.FlagNode, node)
res, err = builder.Query(key, storeName)
viper.Set(client.FlagNode, orig)
return res, err
return context.NewCoreContextFromViper().WithNodeURI(node).Query(key, storeName)
}
func (c relayCommander) broadcastTx(node string, tx []byte) error {
orig := viper.GetString(client.FlagNode)
viper.Set(client.FlagNode, node)
seq := c.getSequence(node) + 1
viper.Set(client.FlagSequence, seq)
_, err := builder.BroadcastTx(tx)
viper.Set(client.FlagNode, orig)
func (c relayCommander) broadcastTx(seq int64, node string, tx []byte) error {
_, err := context.NewCoreContextFromViper().WithNodeURI(node).WithSequence(seq + 1).BroadcastTx(tx)
return err
}
@ -160,6 +158,7 @@ func (c relayCommander) getSequence(node string) int64 {
if err != nil {
panic(err)
}
account, err := c.decoder(res)
if err != nil {
panic(err)
@ -180,8 +179,8 @@ func (c relayCommander) refine(bz []byte, sequence int64, passphrase string) []b
Sequence: sequence,
}
name := viper.GetString(client.FlagName)
res, err := builder.SignAndBuild(name, passphrase, msg, c.cdc)
ctx := context.NewCoreContextFromViper()
res, err := ctx.SignAndBuild(ctx.FromAddressName, passphrase, msg, c.cdc)
if err != nil {
panic(err)
}

View File

@ -7,11 +7,9 @@ import (
"net/http"
"github.com/gorilla/mux"
"github.com/spf13/viper"
"github.com/tendermint/go-crypto/keys"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/builder"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/bank/commands"
@ -31,6 +29,7 @@ type transferBody struct {
// on a different chain via IBC
func TransferRequestHandler(cdc *wire.Codec, kb keys.Keybase) func(http.ResponseWriter, *http.Request) {
c := commands.Commander{cdc}
ctx := context.NewCoreContextFromViper()
return func(w http.ResponseWriter, r *http.Request) {
// collect data
vars := mux.Vars(r)
@ -71,9 +70,8 @@ func TransferRequestHandler(cdc *wire.Codec, kb keys.Keybase) func(http.Response
msg := ibc.IBCTransferMsg{packet}
// sign
// XXX: OMG
viper.Set(client.FlagSequence, m.Sequence)
txBytes, err := builder.SignAndBuild(m.LocalAccountName, m.Password, msg, c.Cdc)
ctx = ctx.WithSequence(m.Sequence)
txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, msg, c.Cdc)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(err.Error()))
@ -81,7 +79,7 @@ func TransferRequestHandler(cdc *wire.Codec, kb keys.Keybase) func(http.Response
}
// send
res, err := builder.BroadcastTx(txBytes)
res, err := ctx.BroadcastTx(txBytes)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))

View File

@ -9,8 +9,7 @@ import (
crypto "github.com/tendermint/go-crypto"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/builder"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/simplestake"
@ -48,7 +47,9 @@ type commander struct {
}
func (co commander) bondTxCmd(cmd *cobra.Command, args []string) error {
from, err := builder.GetFromAddress()
ctx := context.NewCoreContextFromViper()
from, err := ctx.GetFromAddress()
if err != nil {
return err
}
@ -82,7 +83,7 @@ func (co commander) bondTxCmd(cmd *cobra.Command, args []string) error {
}
func (co commander) unbondTxCmd(cmd *cobra.Command, args []string) error {
from, err := builder.GetFromAddress()
from, err := context.NewCoreContextFromViper().GetFromAddress()
if err != nil {
return err
}
@ -93,8 +94,8 @@ func (co commander) unbondTxCmd(cmd *cobra.Command, args []string) error {
}
func (co commander) sendMsg(msg sdk.Msg) error {
name := viper.GetString(client.FlagName)
res, err := builder.SignBuildBroadcast(name, msg, co.cdc)
ctx := context.NewCoreContextFromViper()
res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, co.cdc)
if err != nil {
return err
}

View File

@ -11,7 +11,7 @@ import (
crypto "github.com/tendermint/go-crypto"
"github.com/cosmos/cosmos-sdk/client/builder"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire" // XXX fix
"github.com/cosmos/cosmos-sdk/x/stake"
@ -47,7 +47,8 @@ func GetCmdQueryCandidates(cdc *wire.Codec, storeName string) *cobra.Command {
key := PrefixedKey(stake.MsgType, stake.CandidatesKey)
res, err := builder.Query(key, storeName)
ctx := context.NewCoreContextFromViper()
res, err := ctx.Query(key, storeName)
if err != nil {
return err
}
@ -87,7 +88,9 @@ func GetCmdQueryCandidate(cdc *wire.Codec, storeName string) *cobra.Command {
key := PrefixedKey(stake.MsgType, stake.GetCandidateKey(addr))
res, err := builder.Query(key, storeName)
ctx := context.NewCoreContextFromViper()
res, err := ctx.Query(key, storeName)
if err != nil {
return err
}
@ -133,7 +136,9 @@ func GetCmdQueryDelegatorBond(cdc *wire.Codec, storeName string) *cobra.Command
key := PrefixedKey(stake.MsgType, stake.GetDelegatorBondKey(delegator, addr, cdc))
res, err := builder.Query(key, storeName)
ctx := context.NewCoreContextFromViper()
res, err := ctx.Query(key, storeName)
if err != nil {
return err
}
@ -175,7 +180,9 @@ func GetCmdQueryDelegatorBonds(cdc *wire.Codec, storeName string) *cobra.Command
key := PrefixedKey(stake.MsgType, stake.GetDelegatorBondsKey(delegator, cdc))
res, err := builder.Query(key, storeName)
ctx := context.NewCoreContextFromViper()
res, err := ctx.Query(key, storeName)
if err != nil {
return err
}

View File

@ -11,7 +11,7 @@ import (
crypto "github.com/tendermint/go-crypto"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/builder"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/stake"
@ -92,8 +92,8 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command {
msg := stake.NewMsgDeclareCandidacy(candidateAddr, pk, amount, description)
// build and sign the transaction, then broadcast to Tendermint
name := viper.GetString(client.FlagName)
res, err := builder.SignBuildBroadcast(name, msg, cdc)
ctx := context.NewCoreContextFromViper()
res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, cdc)
if err != nil {
return err
}
@ -129,8 +129,8 @@ func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command {
msg := stake.NewMsgEditCandidacy(candidateAddr, description)
// build and sign the transaction, then broadcast to Tendermint
name := viper.GetString(client.FlagName)
res, err := builder.SignBuildBroadcast(name, msg, cdc)
ctx := context.NewCoreContextFromViper()
res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, cdc)
if err != nil {
return err
}
@ -165,8 +165,8 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command {
msg := stake.NewMsgDelegate(delegatorAddr, candidateAddr, amount)
// build and sign the transaction, then broadcast to Tendermint
name := viper.GetString(client.FlagName)
res, err := builder.SignBuildBroadcast(name, msg, cdc)
ctx := context.NewCoreContextFromViper()
res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, cdc)
if err != nil {
return err
}
@ -212,8 +212,8 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command {
msg := stake.NewMsgUnbond(delegatorAddr, candidateAddr, sharesStr)
// build and sign the transaction, then broadcast to Tendermint
name := viper.GetString(client.FlagName)
res, err := builder.SignBuildBroadcast(name, msg, cdc)
ctx := context.NewCoreContextFromViper()
res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, cdc)
if err != nil {
return err
}

View File

@ -78,6 +78,9 @@ func ErrBadDelegatorAddr() sdk.Error {
func ErrCandidateExistsAddr() sdk.Error {
return newError(CodeInvalidValidator, "Candidate already exist, cannot re-declare candidacy")
}
func ErrCandidateRevoked() sdk.Error {
return newError(CodeInvalidValidator, "Candidacy for this address is currently revoked")
}
func ErrMissingSignature() sdk.Error {
return newError(CodeInvalidValidator, "Missing signature")
}

View File

@ -5,6 +5,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank"
abci "github.com/tendermint/abci/types"
)
//nolint
@ -15,40 +16,6 @@ const (
GasUnbond int64 = 20
)
//XXX fix initstater
// separated for testing
//func InitState(ctx sdk.Context, k Keeper, key, value string) sdk.Error {
//params := k.GetParams(ctx)
//switch key {
//case "allowed_bond_denom":
//params.BondDenom = value
//case "max_vals", "gas_bond", "gas_unbond":
//i, err := strconv.Atoi(value)
//if err != nil {
//return sdk.ErrUnknownRequest(fmt.Sprintf("input must be integer, Error: %v", err.Error()))
//}
//switch key {
//case "max_vals":
//if i < 0 {
//return sdk.ErrUnknownRequest("cannot designate negative max validators")
//}
//params.MaxValidators = uint16(i)
//case "gas_bond":
//GasDelegate = int64(i)
//case "gas_unbound":
//GasUnbond = int64(i)
//}
//default:
//return sdk.ErrUnknownRequest(key)
//}
//k.setParams(params)
//return nil
//}
//_______________________________________________________________________
func NewHandler(k Keeper, ck bank.CoinKeeper) sdk.Handler {
@ -69,16 +36,16 @@ func NewHandler(k Keeper, ck bank.CoinKeeper) sdk.Handler {
}
}
//_____________________________________________________________________
//_______________________________________________
// XXX should be send in the msg (init in CLI)
//func getSender() sdk.Address {
//signers := msg.GetSigners()
//if len(signers) != 1 {
//return sdk.ErrUnauthorized("there can only be one signer for staking transaction").Result()
//}
//sender := signers[0]
//}
// NewEndBlocker generates sdk.EndBlocker
// Performs tick functionality
func NewEndBlocker(k Keeper) sdk.EndBlocker {
return func(ctx sdk.Context, req abci.RequestEndBlock) (res abci.ResponseEndBlock) {
res.ValidatorUpdates = k.Tick(ctx)
return
}
}
//_____________________________________________________________________
@ -106,7 +73,11 @@ func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keepe
// move coins from the msg.Address account to a (self-bond) delegator account
// the candidate account and global shares are updated within here
return delegateWithCandidate(ctx, k, msg.CandidateAddr, msg.Bond, candidate).Result()
err := delegate(ctx, k, msg.CandidateAddr, msg.Bond, candidate)
if err != nil {
return err.Result()
}
return sdk.Result{}
}
func handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy, k Keeper) sdk.Result {
@ -145,25 +116,29 @@ func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result {
if msg.Bond.Denom != k.GetParams(ctx).BondDenom {
return ErrBadBondingDenom().Result()
}
if candidate.Status == Revoked {
return ErrCandidateRevoked().Result()
}
if ctx.IsCheckTx() {
return sdk.Result{
GasUsed: GasDelegate,
}
}
return delegateWithCandidate(ctx, k, msg.DelegatorAddr, msg.Bond, candidate).Result()
err := delegate(ctx, k, msg.DelegatorAddr, msg.Bond, candidate)
if err != nil {
return err.Result()
}
return sdk.Result{}
}
func delegateWithCandidate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address,
// common functionality between handlers
func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address,
bondAmt sdk.Coin, candidate Candidate) sdk.Error {
if candidate.Status == Revoked { //candidate has been withdrawn
return ErrBondNotNominated()
}
// Get or create the delegator bond
existingBond, found := k.getDelegatorBond(ctx, delegatorAddr, candidate.Address)
bond, found := k.getDelegatorBond(ctx, delegatorAddr, candidate.Address)
if !found {
existingBond = DelegatorBond{
bond = DelegatorBond{
DelegatorAddr: delegatorAddr,
CandidateAddr: candidate.Address,
Shares: sdk.ZeroRat,
@ -171,25 +146,17 @@ func delegateWithCandidate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address,
}
// Account new shares, save
err := BondCoins(ctx, k, existingBond, candidate, bondAmt)
pool := k.GetPool(ctx)
_, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{bondAmt})
if err != nil {
return err
}
k.setDelegatorBond(ctx, existingBond)
k.setCandidate(ctx, candidate)
return nil
}
// Perform all the actions required to bond tokens to a delegator bond from their account
func BondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, candidate Candidate, amount sdk.Coin) sdk.Error {
_, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{amount})
if err != nil {
return err
}
newShares := k.candidateAddTokens(ctx, candidate, amount.Amount)
pool, candidate, newShares := pool.candidateAddTokens(candidate, bondAmt.Amount)
bond.Shares = bond.Shares.Add(newShares)
k.setDelegatorBond(ctx, bond)
k.setCandidate(ctx, candidate)
k.setPool(ctx, pool)
return nil
}
@ -216,7 +183,7 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result {
return ErrNotEnoughBondShares(msg.Shares).Result()
}
} else {
if !bond.Shares.GT(shares) {
if bond.Shares.LT(shares) {
return ErrNotEnoughBondShares(msg.Shares).Result()
}
}
@ -258,16 +225,19 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result {
}
// Add the coins
returnAmount := k.candidateRemoveShares(ctx, candidate, shares)
p := k.GetPool(ctx)
p, candidate, returnAmount := p.candidateRemoveShares(candidate, shares)
returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}}
k.coinKeeper.AddCoins(ctx, bond.DelegatorAddr, returnCoins)
/////////////////////////////////////
// revoke candidate if necessary
if revokeCandidacy {
// change the share types to unbonded if they were not already
if candidate.Status == Bonded {
k.bondedToUnbondedPool(ctx, candidate)
p, candidate = p.bondedToUnbondedPool(candidate)
}
// lastly update the status
@ -280,25 +250,43 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result {
} else {
k.setCandidate(ctx, candidate)
}
k.setPool(ctx, p)
return sdk.Result{}
}
// XXX where this used
// Perform all the actions required to bond tokens to a delegator bond from their account
func UnbondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, candidate Candidate, shares sdk.Rat) sdk.Error {
// TODO use or remove
//// Perform all the actions required to bond tokens to a delegator bond from their account
//func BondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond,
//candidate Candidate, amount sdk.Coin) (DelegatorBond, Candidate, Pool, sdk.Error) {
// subtract bond tokens from delegator bond
if bond.Shares.LT(shares) {
return sdk.ErrInsufficientFunds("") //XXX variables inside
}
bond.Shares = bond.Shares.Sub(shares)
//pool := k.GetPool(ctx)
//_, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{amount})
//if err != nil {
//return bond, candidate, pool, err
//}
//pool, candidate, newShares := pool.candidateAddTokens(candidate, amount.Amount)
//bond.Shares = bond.Shares.Add(newShares)
//return bond, candidate, pool, nil
//}
//// Perform all the actions required to bond tokens to a delegator bond from their account
//func UnbondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond,
//candidate Candidate, shares sdk.Rat) (DelegatorBond, Candidate, Pool, sdk.Error) {
returnAmount := k.candidateRemoveShares(ctx, candidate, shares)
returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}}
//pool := k.GetPool(ctx)
_, err := k.coinKeeper.AddCoins(ctx, candidate.Address, returnCoins)
if err != nil {
return err
}
return nil
}
//// subtract bond tokens from delegator bond
//if bond.Shares.LT(shares) {
//errMsg := fmt.Sprintf("cannot unbond %v shares, only have %v shares available", shares, bond.Shares)
//return bond, candidate, pool, sdk.ErrInsufficientFunds(errMsg)
//}
//bond.Shares = bond.Shares.Sub(shares)
//pool, candidate, returnAmount := p.candidateRemoveShares(candidate, shares)
//returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}}
//_, err := k.coinKeeper.AddCoins(ctx, candidate.Address, returnCoins)
//if err != nil {
//return err
//}
//return bond, candidate, pool, nil
//}

View File

@ -1,248 +1,308 @@
package stake
//import (
//"strconv"
//"testing"
import (
"strconv"
"testing"
//"github.com/stretchr/testify/assert"
//"github.com/stretchr/testify/require"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
//crypto "github.com/tendermint/go-crypto"
crypto "github.com/tendermint/go-crypto"
//sdk "github.com/cosmos/cosmos-sdk/types"
//)
sdk "github.com/cosmos/cosmos-sdk/types"
)
////______________________________________________________________________
//______________________________________________________________________
//func newTestMsgDeclareCandidacy(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgDeclareCandidacy {
//return MsgDeclareCandidacy{
//Description: Description{},
//CandidateAddr: address,
//Bond: sdk.Coin{"fermion", amt},
//PubKey: pubKey,
//}
//}
func newTestMsgDeclareCandidacy(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgDeclareCandidacy {
return MsgDeclareCandidacy{
Description: Description{},
CandidateAddr: address,
Bond: sdk.Coin{"fermion", amt},
PubKey: pubKey,
}
}
//func newTestMsgDelegate(amt int64, delegatorAddr, candidateAddr sdk.Address) MsgDelegate {
//return MsgDelegate{
//DelegatorAddr: delegatorAddr,
//CandidateAddr: candidateAddr,
//Bond: sdk.Coin{"fermion", amt},
//}
//}
func newTestMsgDelegate(delegatorAddr, candidateAddr sdk.Address, amt int64) MsgDelegate {
return MsgDelegate{
DelegatorAddr: delegatorAddr,
CandidateAddr: candidateAddr,
Bond: sdk.Coin{"fermion", amt},
}
}
//func TestDuplicatesMsgDeclareCandidacy(t *testing.T) {
//ctxDeliver, _, keeper := createTestInput(t, addrs[0], false, 1000)
//ctxCheck, _, keeper := createTestInput(t, addrs[0], true, 1000)
//______________________________________________________________________
//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10)
//got := deliverer.declareCandidacy(msgDeclareCandidacy)
//assert.NoError(t, got, "expected no error on runMsgDeclareCandidacy")
func TestDuplicatesMsgDeclareCandidacy(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 1000)
//// one sender can bond to two different addresses
//msgDeclareCandidacy.Address = addrs[1]
//err := checker.declareCandidacy(msgDeclareCandidacy)
//assert.Nil(t, err, "didn't expected error on checkTx")
candidateAddr := addrs[0]
pk := pks[0]
msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pk, 10)
got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper)
assert.True(t, got.IsOK(), "%v", got)
candidate, found := keeper.GetCandidate(ctx, candidateAddr)
require.True(t, found)
assert.Equal(t, Unbonded, candidate.Status)
assert.Equal(t, candidateAddr, candidate.Address)
assert.Equal(t, pk, candidate.PubKey)
assert.Equal(t, sdk.NewRat(10), candidate.Assets)
assert.Equal(t, sdk.NewRat(10), candidate.Liabilities)
assert.Equal(t, Description{}, candidate.Description)
//// two addrs cant bond to the same pubkey
//checker.sender = addrs[1]
//msgDeclareCandidacy.Address = addrs[0]
//err = checker.declareCandidacy(msgDeclareCandidacy)
//assert.NotNil(t, err, "expected error on checkTx")
//}
// one candidate cannot bond twice
msgDeclareCandidacy.PubKey = pks[1]
got = handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper)
assert.False(t, got.IsOK(), "%v", got)
}
//func TestIncrementsMsgDelegate(t *testing.T) {
//_, _, mapper, deliverer := createTestInput(t, addrs[0], false, 1000)
func TestIncrementsMsgDelegate(t *testing.T) {
initBond := int64(1000)
ctx, accMapper, keeper := createTestInput(t, false, initBond)
params := keeper.GetParams(ctx)
//// first declare candidacy
//bondAmount := int64(10)
//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], bondAmount)
//got := deliverer.declareCandidacy(msgDeclareCandidacy)
//assert.NoError(t, got, "expected declare candidacy msg to be ok, got %v", got)
//expectedBond := bondAmount // 1 since we send 1 at the start of loop,
bondAmount := int64(10)
candidateAddr, delegatorAddr := addrs[0], addrs[1]
//// just send the same msgbond multiple times
//msgDelegate := newTestMsgDelegate(bondAmount, addrs[0])
//for i := 0; i < 5; i++ {
//got := deliverer.delegate(msgDelegate)
//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
// first declare candidacy
msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pks[0], bondAmount)
got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper)
assert.True(t, got.IsOK(), "expected declare candidacy msg to be ok, got %v", got)
////Check that the accounts and the bond account have the appropriate values
//candidates := mapper.GetCandidates()
//expectedBond += bondAmount
////expectedSender := initSender - expectedBond
//gotBonded := candidates[0].Liabilities.Evaluate()
////gotSender := accStore[string(deliverer.sender)] //XXX use StoreMapper
//assert.Equal(t, expectedBond, gotBonded, "i: %v, %v, %v", i, expectedBond, gotBonded)
////assert.Equal(t, expectedSender, gotSender, "i: %v, %v, %v", i, expectedSender, gotSender) // XXX fix
//}
//}
candidate, found := keeper.GetCandidate(ctx, candidateAddr)
require.True(t, found)
assert.Equal(t, bondAmount, candidate.Liabilities.Evaluate())
assert.Equal(t, bondAmount, candidate.Assets.Evaluate())
//func TestIncrementsMsgUnbond(t *testing.T) {
//_, _, mapper, deliverer := createTestInput(t, addrs[0], false, 0)
// just send the same msgbond multiple times
msgDelegate := newTestMsgDelegate(delegatorAddr, candidateAddr, bondAmount)
for i := 0; i < 5; i++ {
got := handleMsgDelegate(ctx, msgDelegate, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
//// set initial bond
//initBond := int64(1000)
////accStore[string(deliverer.sender)] = initBond //XXX use StoreMapper
//got := deliverer.declareCandidacy(newTestMsgDeclareCandidacy(addrs[0], pks[0], initBond))
//assert.NoError(t, got, "expected initial bond msg to be ok, got %v", got)
//Check that the accounts and the bond account have the appropriate values
candidate, found := keeper.GetCandidate(ctx, candidateAddr)
require.True(t, found)
bond, found := keeper.getDelegatorBond(ctx, delegatorAddr, candidateAddr)
require.True(t, found)
//// just send the same msgunbond multiple times
//// XXX use decimals here
//unbondShares, unbondSharesStr := int64(10), "10"
//msgUndelegate := NewMsgUnbond(addrs[0], unbondSharesStr)
//nUnbonds := 5
//for i := 0; i < nUnbonds; i++ {
//got := deliverer.unbond(msgUndelegate)
//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
expBond := int64(i+1) * bondAmount
expLiabilities := int64(i+2) * bondAmount // (1 self delegation)
expDelegatorAcc := initBond - expBond
////Check that the accounts and the bond account have the appropriate values
//candidates := mapper.GetCandidates()
//expectedBond := initBond - int64(i+1)*unbondShares // +1 since we send 1 at the start of loop
////expectedSender := initSender + (initBond - expectedBond)
//gotBonded := candidates[0].Liabilities.Evaluate()
////gotSender := accStore[string(deliverer.sender)] // XXX use storemapper
gotBond := bond.Shares.Evaluate()
gotLiabilities := candidate.Liabilities.Evaluate()
gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom)
//assert.Equal(t, expectedBond, gotBonded, "%v, %v", expectedBond, gotBonded)
////assert.Equal(t, expectedSender, gotSender, "%v, %v", expectedSender, gotSender) //XXX fix
//}
require.Equal(t, expBond, gotBond,
"i: %v\nexpBond: %v\ngotBond: %v\ncandidate: %v\nbond: %v\n",
i, expBond, gotBond, candidate, bond)
require.Equal(t, expLiabilities, gotLiabilities,
"i: %v\nexpLiabilities: %v\ngotLiabilities: %v\ncandidate: %v\nbond: %v\n",
i, expLiabilities, gotLiabilities, candidate, bond)
require.Equal(t, expDelegatorAcc, gotDelegatorAcc,
"i: %v\nexpDelegatorAcc: %v\ngotDelegatorAcc: %v\ncandidate: %v\nbond: %v\n",
i, expDelegatorAcc, gotDelegatorAcc, candidate, bond)
}
}
//// these are more than we have bonded now
//errorCases := []int64{
////1<<64 - 1, // more than int64
////1<<63 + 1, // more than int64
//1<<63 - 1,
//1 << 31,
//initBond,
//}
//for _, c := range errorCases {
//unbondShares := strconv.Itoa(int(c))
//msgUndelegate := NewMsgUnbond(addrs[0], unbondShares)
//got = deliverer.unbond(msgUndelegate)
//assert.Error(t, got, "expected unbond msg to fail")
//}
func TestIncrementsMsgUnbond(t *testing.T) {
initBond := int64(1000)
ctx, accMapper, keeper := createTestInput(t, false, initBond)
params := keeper.GetParams(ctx)
//leftBonded := initBond - unbondShares*int64(nUnbonds)
// declare candidacy, delegate
candidateAddr, delegatorAddr := addrs[0], addrs[1]
//// should be unable to unbond one more than we have
//msgUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded)+1))
//got = deliverer.unbond(msgUndelegate)
//assert.Error(t, got, "expected unbond msg to fail")
msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pks[0], initBond)
got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper)
assert.True(t, got.IsOK(), "expected declare-candidacy to be ok, got %v", got)
//// should be able to unbond just what we have
//msgUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded)))
//got = deliverer.unbond(msgUndelegate)
//assert.NoError(t, got, "expected unbond msg to pass")
//}
msgDelegate := newTestMsgDelegate(delegatorAddr, candidateAddr, initBond)
got = handleMsgDelegate(ctx, msgDelegate, keeper)
assert.True(t, got.IsOK(), "expected delegation to be ok, got %v", got)
//func TestMultipleMsgDeclareCandidacy(t *testing.T) {
//initSender := int64(1000)
//ctx, accStore, mapper, deliverer := createTestInput(t, addrs[0], false, initSender)
//addrs := []sdk.Address{addrs[0], addrs[1], addrs[2]}
candidate, found := keeper.GetCandidate(ctx, candidateAddr)
require.True(t, found)
assert.Equal(t, initBond*2, candidate.Liabilities.Evaluate())
assert.Equal(t, initBond*2, candidate.Assets.Evaluate())
//// bond them all
//for i, addr := range addrs {
//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[i], pks[i], 10)
//deliverer.sender = addr
//got := deliverer.declareCandidacy(msgDeclareCandidacy)
//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
// just send the same msgUnbond multiple times
// TODO use decimals here
unbondShares, unbondSharesStr := int64(10), "10"
msgUnbond := NewMsgUnbond(delegatorAddr, candidateAddr, unbondSharesStr)
numUnbonds := 5
for i := 0; i < numUnbonds; i++ {
got := handleMsgUnbond(ctx, msgUnbond, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
////Check that the account is bonded
//candidates := mapper.GetCandidates()
//require.Equal(t, i, len(candidates))
//val := candidates[i]
//balanceExpd := initSender - 10
//balanceGot := accStore.GetAccount(ctx, val.Address).GetCoins()
//assert.Equal(t, i+1, len(candidates), "expected %d candidates got %d, candidates: %v", i+1, len(candidates), candidates)
//assert.Equal(t, 10, int(val.Liabilities.Evaluate()), "expected %d shares, got %d", 10, val.Liabilities)
//assert.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot)
//}
//Check that the accounts and the bond account have the appropriate values
candidate, found = keeper.GetCandidate(ctx, candidateAddr)
require.True(t, found)
bond, found := keeper.getDelegatorBond(ctx, delegatorAddr, candidateAddr)
require.True(t, found)
//// unbond them all
//for i, addr := range addrs {
//candidatePre := mapper.GetCandidate(addrs[i])
//msgUndelegate := NewMsgUnbond(addrs[i], "10")
//deliverer.sender = addr
//got := deliverer.unbond(msgUndelegate)
//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
expBond := initBond - int64(i+1)*unbondShares
expLiabilities := 2*initBond - int64(i+1)*unbondShares
expDelegatorAcc := initBond - expBond
////Check that the account is unbonded
//candidates := mapper.GetCandidates()
//assert.Equal(t, len(addrs)-(i+1), len(candidates), "expected %d candidates got %d", len(addrs)-(i+1), len(candidates))
gotBond := bond.Shares.Evaluate()
gotLiabilities := candidate.Liabilities.Evaluate()
gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom)
//candidatePost := mapper.GetCandidate(addrs[i])
//balanceExpd := initSender
//balanceGot := accStore.GetAccount(ctx, candidatePre.Address).GetCoins()
//assert.Nil(t, candidatePost, "expected nil candidate retrieve, got %d", 0, candidatePost)
//assert.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot)
//}
//}
require.Equal(t, expBond, gotBond,
"i: %v\nexpBond: %v\ngotBond: %v\ncandidate: %v\nbond: %v\n",
i, expBond, gotBond, candidate, bond)
require.Equal(t, expLiabilities, gotLiabilities,
"i: %v\nexpLiabilities: %v\ngotLiabilities: %v\ncandidate: %v\nbond: %v\n",
i, expLiabilities, gotLiabilities, candidate, bond)
require.Equal(t, expDelegatorAcc, gotDelegatorAcc,
"i: %v\nexpDelegatorAcc: %v\ngotDelegatorAcc: %v\ncandidate: %v\nbond: %v\n",
i, expDelegatorAcc, gotDelegatorAcc, candidate, bond)
}
//func TestMultipleMsgDelegate(t *testing.T) {
//sender, delegators := addrs[0], addrs[1:]
//_, _, mapper, deliverer := createTestInput(t, addrs[0], false, 1000)
// these are more than we have bonded now
errorCases := []int64{
//1<<64 - 1, // more than int64
//1<<63 + 1, // more than int64
1<<63 - 1,
1 << 31,
initBond,
}
for _, c := range errorCases {
unbondShares := strconv.Itoa(int(c))
msgUnbond := NewMsgUnbond(delegatorAddr, candidateAddr, unbondShares)
got = handleMsgUnbond(ctx, msgUnbond, keeper)
require.False(t, got.IsOK(), "expected unbond msg to fail")
}
////first make a candidate
//msgDeclareCandidacy := newTestMsgDeclareCandidacy(sender, pks[0], 10)
//got := deliverer.declareCandidacy(msgDeclareCandidacy)
//require.NoError(t, got, "expected msg to be ok, got %v", got)
leftBonded := initBond - unbondShares*int64(numUnbonds)
//// delegate multiple parties
//for i, delegator := range delegators {
//msgDelegate := newTestMsgDelegate(10, sender)
//deliverer.sender = delegator
//got := deliverer.delegate(msgDelegate)
//require.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
// should be unable to unbond one more than we have
unbondSharesStr = strconv.Itoa(int(leftBonded) + 1)
msgUnbond = NewMsgUnbond(delegatorAddr, candidateAddr, unbondSharesStr)
got = handleMsgUnbond(ctx, msgUnbond, keeper)
assert.False(t, got.IsOK(),
"got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUnbond, unbondSharesStr, leftBonded)
////Check that the account is bonded
//bond := mapper.getDelegatorBond(delegator, sender)
//assert.NotNil(t, bond, "expected delegatee bond %d to exist", bond)
//}
// should be able to unbond just what we have
unbondSharesStr = strconv.Itoa(int(leftBonded))
msgUnbond = NewMsgUnbond(delegatorAddr, candidateAddr, unbondSharesStr)
got = handleMsgUnbond(ctx, msgUnbond, keeper)
assert.True(t, got.IsOK(),
"got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUnbond, unbondSharesStr, leftBonded)
}
//// unbond them all
//for i, delegator := range delegators {
//msgUndelegate := NewMsgUnbond(sender, "10")
//deliverer.sender = delegator
//got := deliverer.unbond(msgUndelegate)
//require.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
func TestMultipleMsgDeclareCandidacy(t *testing.T) {
initBond := int64(1000)
ctx, accMapper, keeper := createTestInput(t, false, initBond)
params := keeper.GetParams(ctx)
candidateAddrs := []sdk.Address{addrs[0], addrs[1], addrs[2]}
////Check that the account is unbonded
//bond := mapper.getDelegatorBond(delegator, sender)
//assert.Nil(t, bond, "expected delegatee bond %d to be nil", bond)
//}
//}
// bond them all
for i, candidateAddr := range candidateAddrs {
msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pks[i], 10)
got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
//func TestVoidCandidacy(t *testing.T) {
//sender, delegator := addrs[0], addrs[1]
//_, _, _, deliverer := createTestInput(t, addrs[0], false, 1000)
//Check that the account is bonded
candidates := keeper.GetCandidates(ctx, 100)
require.Equal(t, (i + 1), len(candidates))
val := candidates[i]
balanceExpd := initBond - 10
balanceGot := accMapper.GetAccount(ctx, val.Address).GetCoins().AmountOf(params.BondDenom)
require.Equal(t, i+1, len(candidates), "expected %d candidates got %d, candidates: %v", i+1, len(candidates), candidates)
require.Equal(t, 10, int(val.Liabilities.Evaluate()), "expected %d shares, got %d", 10, val.Liabilities)
require.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot)
}
//// create the candidate
//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10)
//got := deliverer.declareCandidacy(msgDeclareCandidacy)
//require.NoError(t, got, "expected no error on runMsgDeclareCandidacy")
// unbond them all
for i, candidateAddr := range candidateAddrs {
candidatePre, found := keeper.GetCandidate(ctx, candidateAddr)
require.True(t, found)
msgUnbond := NewMsgUnbond(candidateAddr, candidateAddr, "10") // self-delegation
got := handleMsgUnbond(ctx, msgUnbond, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
//// bond a delegator
//msgDelegate := newTestMsgDelegate(10, addrs[0])
//deliverer.sender = delegator
//got = deliverer.delegate(msgDelegate)
//require.NoError(t, got, "expected ok, got %v", got)
//Check that the account is unbonded
candidates := keeper.GetCandidates(ctx, 100)
require.Equal(t, len(candidateAddrs)-(i+1), len(candidates),
"expected %d candidates got %d", len(candidateAddrs)-(i+1), len(candidates))
//// unbond the candidates bond portion
//msgUndelegate := NewMsgUnbond(addrs[0], "10")
//deliverer.sender = sender
//got = deliverer.unbond(msgUndelegate)
//require.NoError(t, got, "expected no error on runMsgDeclareCandidacy")
_, found = keeper.GetCandidate(ctx, candidateAddr)
require.False(t, found)
//// test that this pubkey cannot yet be bonded too
//deliverer.sender = delegator
//got = deliverer.delegate(msgDelegate)
//assert.Error(t, got, "expected error, got %v", got)
expBalance := initBond
gotBalance := accMapper.GetAccount(ctx, candidatePre.Address).GetCoins().AmountOf(params.BondDenom)
require.Equal(t, expBalance, gotBalance, "expected account to have %d, got %d", expBalance, gotBalance)
}
}
//// test that the delegator can still withdraw their bonds
//got = deliverer.unbond(msgUndelegate)
//require.NoError(t, got, "expected no error on runMsgDeclareCandidacy")
func TestMultipleMsgDelegate(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 1000)
candidateAddr, delegatorAddrs := addrs[0], addrs[1:]
//// verify that the pubkey can now be reused
//got = deliverer.declareCandidacy(msgDeclareCandidacy)
//assert.NoError(t, got, "expected ok, got %v", got)
//}
//first make a candidate
msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pks[0], 10)
got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
// delegate multiple parties
for i, delegatorAddr := range delegatorAddrs {
msgDelegate := newTestMsgDelegate(delegatorAddr, candidateAddr, 10)
got := handleMsgDelegate(ctx, msgDelegate, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
//Check that the account is bonded
bond, found := keeper.getDelegatorBond(ctx, delegatorAddr, candidateAddr)
require.True(t, found)
require.NotNil(t, bond, "expected delegatee bond %d to exist", bond)
}
// unbond them all
for i, delegatorAddr := range delegatorAddrs {
msgUnbond := NewMsgUnbond(delegatorAddr, candidateAddr, "10")
got := handleMsgUnbond(ctx, msgUnbond, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
//Check that the account is unbonded
_, found := keeper.getDelegatorBond(ctx, delegatorAddr, candidateAddr)
require.False(t, found)
}
}
func TestVoidCandidacy(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 1000)
candidateAddr, delegatorAddr := addrs[0], addrs[1]
// create the candidate
msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pks[0], 10)
got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgDeclareCandidacy")
// bond a delegator
msgDelegate := newTestMsgDelegate(delegatorAddr, candidateAddr, 10)
got = handleMsgDelegate(ctx, msgDelegate, keeper)
require.True(t, got.IsOK(), "expected ok, got %v", got)
// unbond the candidates bond portion
msgUnbondCandidate := NewMsgUnbond(candidateAddr, candidateAddr, "10")
got = handleMsgUnbond(ctx, msgUnbondCandidate, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgDeclareCandidacy")
candidate, found := keeper.GetCandidate(ctx, candidateAddr)
require.True(t, found)
require.Equal(t, Revoked, candidate.Status)
// test that this address cannot yet be bonded too because is revoked
got = handleMsgDelegate(ctx, msgDelegate, keeper)
assert.False(t, got.IsOK(), "expected error, got %v", got)
// test that the delegator can still withdraw their bonds
msgUnbondDelegator := NewMsgUnbond(delegatorAddr, candidateAddr, "10")
got = handleMsgUnbond(ctx, msgUnbondDelegator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgDeclareCandidacy")
// verify that the pubkey can now be reused
got = handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper)
assert.True(t, got.IsOK(), "expected ok, got %v", got)
}

View File

@ -1,9 +1,13 @@
package stake
import (
"bytes"
"encoding/json"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/bank"
abci "github.com/tendermint/abci/types"
)
// keeper of the staking store
@ -26,6 +30,17 @@ func NewKeeper(ctx sdk.Context, cdc *wire.Codec, key sdk.StoreKey, ck bank.CoinK
return keeper
}
// InitGenesis - store genesis parameters
func (k Keeper) InitGenesis(ctx sdk.Context, data json.RawMessage) error {
var state GenesisState
if err := json.Unmarshal(data, &state); err != nil {
return err
}
k.setPool(ctx, state.Pool)
k.setParams(ctx, state.Params)
return nil
}
//_________________________________________________________________________
// get a single candidate
@ -81,30 +96,47 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) {
store.Set(GetCandidateKey(candidate.Address), bz)
// mashal the new validator record
validator := Validator{address, candidate.Assets}
validator := candidate.validator()
bz, err = k.cdc.MarshalBinary(validator)
if err != nil {
panic(err)
}
// if the voting power is the same no need to update any of the other indexes
if oldFound && oldCandidate.Assets.Equal(candidate.Assets) {
return
}
// update the list ordered by voting power
if oldFound {
store.Delete(GetValidatorKey(address, oldCandidate.Assets, k.cdc))
}
store.Set(GetValidatorKey(address, validator.VotingPower, k.cdc), bz)
store.Set(GetValidatorKey(address, validator.Power, k.cdc), bz)
// add to the validators to update list if is already a validator
if store.Get(GetRecentValidatorKey(address)) == nil {
return
}
store.Set(GetAccUpdateValidatorKey(validator.Address), bz)
// or is a new validator
setAcc := false
if store.Get(GetRecentValidatorKey(address)) != nil {
setAcc = true
// want to check in the else statement because inefficient
} else if k.isNewValidator(ctx, store, address) {
setAcc = true
}
if setAcc {
bz, err = k.cdc.MarshalBinary(validator.abciValidator(k.cdc))
if err != nil {
panic(err)
}
store.Set(GetAccUpdateValidatorKey(validator.Address), bz)
}
return
}
func (k Keeper) removeCandidate(ctx sdk.Context, address sdk.Address) {
// first retreive the old candidate record
oldCandidate, found := k.GetCandidate(ctx, address)
candidate, found := k.GetCandidate(ctx, address)
if !found {
return
}
@ -112,39 +144,104 @@ func (k Keeper) removeCandidate(ctx sdk.Context, address sdk.Address) {
// delete the old candidate record
store := ctx.KVStore(k.storeKey)
store.Delete(GetCandidateKey(address))
store.Delete(GetValidatorKey(address, candidate.Assets, k.cdc))
// delete from recent and power weighted validator groups if the validator
// exists and add validator with zero power to the validator updates
if store.Get(GetRecentValidatorKey(address)) == nil {
return
}
bz, err := k.cdc.MarshalBinary(Validator{address, sdk.ZeroRat})
bz, err := k.cdc.MarshalBinary(candidate.validator().abciValidatorZero(k.cdc))
if err != nil {
panic(err)
}
store.Set(GetAccUpdateValidatorKey(address), bz)
store.Delete(GetRecentValidatorKey(address))
store.Delete(GetValidatorKey(address, oldCandidate.Assets, k.cdc))
}
//___________________________________________________________________________
// get the most recent updated validator set from the Candidates. These bonds
// are already sorted by Assets from the UpdateVotingPower function which
// is the only function which is to modify the Assets
// this function also updaates the most recent validators saved in store
// Get the validator set from the candidates. The correct subset is retrieved
// by iterating through an index of the candidates sorted by power, stored
// using the ValidatorsKey. Simultaniously the most recent the validator
// records are updated in store with the RecentValidatorsKey. This store is
// used to determine if a candidate is a validator without needing to iterate
// over the subspace as we do in GetValidators
func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) {
store := ctx.KVStore(k.storeKey)
// clear the recent validators store
k.deleteSubSpace(store, RecentValidatorsKey)
// clear the recent validators store, add to the ToKickOut Temp store
iterator := store.Iterator(subspace(RecentValidatorsKey))
for ; iterator.Valid(); iterator.Next() {
addr := AddrFromKey(iterator.Key())
// iterator.Value is the validator object
store.Set(GetToKickOutValidatorKey(addr), iterator.Value())
store.Delete(iterator.Key())
}
iterator.Close()
// add the actual validator power sorted store
maxVal := k.GetParams(ctx).MaxValidators
iterator := store.ReverseIterator(subspace(ValidatorsKey)) //smallest to largest
validators = make([]Validator, maxVal)
maxValidators := k.GetParams(ctx).MaxValidators
iterator = store.ReverseIterator(subspace(ValidatorsKey)) // largest to smallest
validators = make([]Validator, maxValidators)
i := 0
for ; ; i++ {
if !iterator.Valid() || i > int(maxValidators-1) {
iterator.Close()
break
}
bz := iterator.Value()
var validator Validator
err := k.cdc.UnmarshalBinary(bz, &validator)
if err != nil {
panic(err)
}
validators[i] = validator
// remove from ToKickOut group
store.Delete(GetToKickOutValidatorKey(validator.Address))
// also add to the recent validators group
store.Set(GetRecentValidatorKey(validator.Address), bz)
iterator.Next()
}
// add any kicked out validators to the acc change
iterator = store.Iterator(subspace(ToKickOutValidatorsKey))
for ; iterator.Valid(); iterator.Next() {
key := iterator.Key()
addr := AddrFromKey(key)
// get the zero abci validator from the ToKickOut iterator value
bz := iterator.Value()
var validator Validator
err := k.cdc.UnmarshalBinary(bz, &validator)
if err != nil {
panic(err)
}
bz, err = k.cdc.MarshalBinary(validator.abciValidatorZero(k.cdc))
if err != nil {
panic(err)
}
store.Set(GetAccUpdateValidatorKey(addr), bz)
store.Delete(key)
}
iterator.Close()
return validators[:i] // trim
}
// TODO this is madly inefficient because need to call every time we set a candidate
// Should use something better than an iterator maybe?
// Used to determine if something has just been added to the actual validator set
func (k Keeper) isNewValidator(ctx sdk.Context, store sdk.KVStore, address sdk.Address) bool {
// add the actual validator power sorted store
maxVal := k.GetParams(ctx).MaxValidators
iterator := store.ReverseIterator(subspace(ValidatorsKey)) // largest to smallest
for i := 0; ; i++ {
if !iterator.Valid() || i > int(maxVal-1) {
iterator.Close()
break
@ -155,15 +252,13 @@ func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) {
if err != nil {
panic(err)
}
validators[i] = val
// also add to the recent validators group
store.Set(GetRecentValidatorKey(val.Address), bz)
if bytes.Equal(val.Address, address) {
return true
}
iterator.Next()
}
return validators[:i] // trim
return false
}
// Is the address provided a part of the most recently saved validator group?
@ -179,13 +274,13 @@ func (k Keeper) IsRecentValidator(ctx sdk.Context, address sdk.Address) bool {
// Accumulated updates to the validator set
// get the most recently updated validators
func (k Keeper) getAccUpdateValidators(ctx sdk.Context) (updates []Validator) {
func (k Keeper) getAccUpdateValidators(ctx sdk.Context) (updates []abci.Validator) {
store := ctx.KVStore(k.storeKey)
iterator := store.Iterator(subspace(AccUpdateValidatorsKey)) //smallest to largest
for ; iterator.Valid(); iterator.Next() {
valBytes := iterator.Value()
var val Validator
var val abci.Validator
err := k.cdc.UnmarshalBinary(valBytes, &val)
if err != nil {
panic(err)
@ -199,12 +294,9 @@ func (k Keeper) getAccUpdateValidators(ctx sdk.Context) (updates []Validator) {
// remove all validator update entries
func (k Keeper) clearAccUpdateValidators(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
k.deleteSubSpace(store, AccUpdateValidatorsKey)
}
// TODO move to common functionality somewhere
func (k Keeper) deleteSubSpace(store sdk.KVStore, key []byte) {
iterator := store.Iterator(subspace(key))
// delete subspace
iterator := store.Iterator(subspace(AccUpdateValidatorsKey))
for ; iterator.Valid(); iterator.Next() {
store.Delete(iterator.Key())
}
@ -279,8 +371,7 @@ func (k Keeper) GetParams(ctx sdk.Context) (params Params) {
store := ctx.KVStore(k.storeKey)
b := store.Get(ParamKey)
if b == nil {
k.params = defaultParams()
return k.params
panic("Stored params should not have been nil")
}
err := k.cdc.UnmarshalBinary(b, &params)
@ -298,3 +389,33 @@ func (k Keeper) setParams(ctx sdk.Context, params Params) {
store.Set(ParamKey, b)
k.params = Params{} // clear the cache
}
//_______________________________________________________________________
// load/save the pool
func (k Keeper) GetPool(ctx sdk.Context) (gs Pool) {
// check if cached before anything
if k.gs != (Pool{}) {
return k.gs
}
store := ctx.KVStore(k.storeKey)
b := store.Get(PoolKey)
if b == nil {
panic("Stored pool should not have been nil")
}
err := k.cdc.UnmarshalBinary(b, &gs)
if err != nil {
panic(err) // This error should never occur big problem if does
}
return
}
func (k Keeper) setPool(ctx sdk.Context, p Pool) {
store := ctx.KVStore(k.storeKey)
b, err := k.cdc.MarshalBinary(p)
if err != nil {
panic(err)
}
store.Set(PoolKey, b)
k.gs = Pool{} // clear the cache
}

View File

@ -15,9 +15,11 @@ var (
CandidatesKey = []byte{0x02} // prefix for each key to a candidate
ValidatorsKey = []byte{0x03} // prefix for each key to a validator
AccUpdateValidatorsKey = []byte{0x04} // prefix for each key to a validator which is being updated
RecentValidatorsKey = []byte{0x04} // prefix for each key to the last updated validator group
RecentValidatorsKey = []byte{0x05} // prefix for each key to the last updated validator group
DelegatorBondKeyPrefix = []byte{0x05} // prefix for each key to a delegator's bond
ToKickOutValidatorsKey = []byte{0x06} // prefix for each key to the last updated validator group
DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond
)
const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch
@ -43,6 +45,16 @@ func GetRecentValidatorKey(addr sdk.Address) []byte {
return append(RecentValidatorsKey, addr.Bytes()...)
}
// reverse operation of GetRecentValidatorKey
func AddrFromKey(key []byte) sdk.Address {
return key[1:]
}
// get the key for the accumulated update validators
func GetToKickOutValidatorKey(addr sdk.Address) []byte {
return append(ToKickOutValidatorsKey, addr.Bytes()...)
}
// get the key for delegator bond with candidate
func GetDelegatorBondKey(delegatorAddr, candidateAddr sdk.Address, cdc *wire.Codec) []byte {
return append(GetDelegatorBondsKey(delegatorAddr, cdc), candidateAddr.Bytes()...)

View File

@ -2,6 +2,7 @@ package stake
import (
"bytes"
"encoding/json"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -12,38 +13,34 @@ import (
)
var (
addrDel1 = addrs[0]
addrDel2 = addrs[1]
addrVal1 = addrs[2]
addrVal2 = addrs[3]
addrVal3 = addrs[4]
pk1 = crypto.GenPrivKeyEd25519().PubKey()
pk2 = crypto.GenPrivKeyEd25519().PubKey()
pk3 = crypto.GenPrivKeyEd25519().PubKey()
candidate1 = Candidate{
Address: addrVal1,
PubKey: pk1,
Assets: sdk.NewRat(9),
Liabilities: sdk.NewRat(9),
addrDels = []sdk.Address{
addrs[0],
addrs[1],
}
candidate2 = Candidate{
Address: addrVal2,
PubKey: pk2,
Assets: sdk.NewRat(9),
Liabilities: sdk.NewRat(9),
}
candidate3 = Candidate{
Address: addrVal3,
PubKey: pk3,
Assets: sdk.NewRat(9),
Liabilities: sdk.NewRat(9),
addrVals = []sdk.Address{
addrs[2],
addrs[3],
addrs[4],
addrs[5],
addrs[6],
}
)
// This function tests GetCandidate, GetCandidates, setCandidate, removeCandidate
func TestCandidate(t *testing.T) {
ctx, _, keeper := createTestInput(t, nil, false, 0)
ctx, _, keeper := createTestInput(t, false, 0)
//construct the candidates
var candidates [3]Candidate
amts := []int64{9, 8, 7}
for i, amt := range amts {
candidates[i] = Candidate{
Address: addrVals[i],
PubKey: pks[i],
Assets: sdk.NewRat(amt),
Liabilities: sdk.NewRat(amt),
}
}
candidatesEqual := func(c1, c2 Candidate) bool {
return c1.Status == c2.Status &&
@ -55,60 +52,72 @@ func TestCandidate(t *testing.T) {
}
// check the empty keeper first
_, found := keeper.GetCandidate(ctx, addrVal1)
_, found := keeper.GetCandidate(ctx, addrVals[0])
assert.False(t, found)
resCands := keeper.GetCandidates(ctx, 100)
assert.Zero(t, len(resCands))
// set and retrieve a record
keeper.setCandidate(ctx, candidate1)
resCand, found := keeper.GetCandidate(ctx, addrVal1)
keeper.setCandidate(ctx, candidates[0])
resCand, found := keeper.GetCandidate(ctx, addrVals[0])
require.True(t, found)
assert.True(t, candidatesEqual(candidate1, resCand), "%v \n %v", resCand, candidate1)
assert.True(t, candidatesEqual(candidates[0], resCand), "%v \n %v", resCand, candidates[0])
// modify a records, save, and retrieve
candidate1.Liabilities = sdk.NewRat(99)
keeper.setCandidate(ctx, candidate1)
resCand, found = keeper.GetCandidate(ctx, addrVal1)
candidates[0].Liabilities = sdk.NewRat(99)
keeper.setCandidate(ctx, candidates[0])
resCand, found = keeper.GetCandidate(ctx, addrVals[0])
require.True(t, found)
assert.True(t, candidatesEqual(candidate1, resCand))
assert.True(t, candidatesEqual(candidates[0], resCand))
// also test that the address has been added to address list
resCands = keeper.GetCandidates(ctx, 100)
require.Equal(t, 1, len(resCands))
assert.Equal(t, addrVal1, resCands[0].Address)
assert.Equal(t, addrVals[0], resCands[0].Address)
// add other candidates
keeper.setCandidate(ctx, candidate2)
keeper.setCandidate(ctx, candidate3)
resCand, found = keeper.GetCandidate(ctx, addrVal2)
keeper.setCandidate(ctx, candidates[1])
keeper.setCandidate(ctx, candidates[2])
resCand, found = keeper.GetCandidate(ctx, addrVals[1])
require.True(t, found)
assert.True(t, candidatesEqual(candidate2, resCand), "%v \n %v", resCand, candidate2)
resCand, found = keeper.GetCandidate(ctx, addrVal3)
assert.True(t, candidatesEqual(candidates[1], resCand), "%v \n %v", resCand, candidates[1])
resCand, found = keeper.GetCandidate(ctx, addrVals[2])
require.True(t, found)
assert.True(t, candidatesEqual(candidate3, resCand), "%v \n %v", resCand, candidate3)
assert.True(t, candidatesEqual(candidates[2], resCand), "%v \n %v", resCand, candidates[2])
resCands = keeper.GetCandidates(ctx, 100)
require.Equal(t, 3, len(resCands))
assert.True(t, candidatesEqual(candidate1, resCands[0]), "%v \n %v", resCands[0], candidate1)
assert.True(t, candidatesEqual(candidate2, resCands[1]), "%v \n %v", resCands[1], candidate2)
assert.True(t, candidatesEqual(candidate3, resCands[2]), "%v \n %v", resCands[2], candidate3)
assert.True(t, candidatesEqual(candidates[0], resCands[0]), "%v \n %v", resCands[0], candidates[0])
assert.True(t, candidatesEqual(candidates[1], resCands[1]), "%v \n %v", resCands[1], candidates[1])
assert.True(t, candidatesEqual(candidates[2], resCands[2]), "%v \n %v", resCands[2], candidates[2])
// remove a record
keeper.removeCandidate(ctx, candidate2.Address)
_, found = keeper.GetCandidate(ctx, addrVal2)
keeper.removeCandidate(ctx, candidates[1].Address)
_, found = keeper.GetCandidate(ctx, addrVals[1])
assert.False(t, found)
}
// tests GetDelegatorBond, GetDelegatorBonds, SetDelegatorBond, removeDelegatorBond
func TestBond(t *testing.T) {
ctx, _, keeper := createTestInput(t, nil, false, 0)
ctx, _, keeper := createTestInput(t, false, 0)
// first add a candidate1 to delegate too
keeper.setCandidate(ctx, candidate1)
//construct the candidates
amts := []int64{9, 8, 7}
var candidates [3]Candidate
for i, amt := range amts {
candidates[i] = Candidate{
Address: addrVals[i],
PubKey: pks[i],
Assets: sdk.NewRat(amt),
Liabilities: sdk.NewRat(amt),
}
}
// first add a candidates[0] to delegate too
keeper.setCandidate(ctx, candidates[0])
bond1to1 := DelegatorBond{
DelegatorAddr: addrDel1,
CandidateAddr: addrVal1,
DelegatorAddr: addrDels[0],
CandidateAddr: addrVals[0],
Shares: sdk.NewRat(9),
}
@ -119,30 +128,30 @@ func TestBond(t *testing.T) {
}
// check the empty keeper first
_, found := keeper.getDelegatorBond(ctx, addrDel1, addrVal1)
_, found := keeper.getDelegatorBond(ctx, addrDels[0], addrVals[0])
assert.False(t, found)
// set and retrieve a record
keeper.setDelegatorBond(ctx, bond1to1)
resBond, found := keeper.getDelegatorBond(ctx, addrDel1, addrVal1)
resBond, found := keeper.getDelegatorBond(ctx, addrDels[0], addrVals[0])
assert.True(t, found)
assert.True(t, bondsEqual(bond1to1, resBond))
// modify a records, save, and retrieve
bond1to1.Shares = sdk.NewRat(99)
keeper.setDelegatorBond(ctx, bond1to1)
resBond, found = keeper.getDelegatorBond(ctx, addrDel1, addrVal1)
resBond, found = keeper.getDelegatorBond(ctx, addrDels[0], addrVals[0])
assert.True(t, found)
assert.True(t, bondsEqual(bond1to1, resBond))
// add some more records
keeper.setCandidate(ctx, candidate2)
keeper.setCandidate(ctx, candidate3)
bond1to2 := DelegatorBond{addrDel1, addrVal2, sdk.NewRat(9)}
bond1to3 := DelegatorBond{addrDel1, addrVal3, sdk.NewRat(9)}
bond2to1 := DelegatorBond{addrDel2, addrVal1, sdk.NewRat(9)}
bond2to2 := DelegatorBond{addrDel2, addrVal2, sdk.NewRat(9)}
bond2to3 := DelegatorBond{addrDel2, addrVal3, sdk.NewRat(9)}
keeper.setCandidate(ctx, candidates[1])
keeper.setCandidate(ctx, candidates[2])
bond1to2 := DelegatorBond{addrDels[0], addrVals[1], sdk.NewRat(9)}
bond1to3 := DelegatorBond{addrDels[0], addrVals[2], sdk.NewRat(9)}
bond2to1 := DelegatorBond{addrDels[1], addrVals[0], sdk.NewRat(9)}
bond2to2 := DelegatorBond{addrDels[1], addrVals[1], sdk.NewRat(9)}
bond2to3 := DelegatorBond{addrDels[1], addrVals[2], sdk.NewRat(9)}
keeper.setDelegatorBond(ctx, bond1to2)
keeper.setDelegatorBond(ctx, bond1to3)
keeper.setDelegatorBond(ctx, bond2to1)
@ -150,16 +159,16 @@ func TestBond(t *testing.T) {
keeper.setDelegatorBond(ctx, bond2to3)
// test all bond retrieve capabilities
resBonds := keeper.getDelegatorBonds(ctx, addrDel1, 5)
resBonds := keeper.getDelegatorBonds(ctx, addrDels[0], 5)
require.Equal(t, 3, len(resBonds))
assert.True(t, bondsEqual(bond1to1, resBonds[0]))
assert.True(t, bondsEqual(bond1to2, resBonds[1]))
assert.True(t, bondsEqual(bond1to3, resBonds[2]))
resBonds = keeper.getDelegatorBonds(ctx, addrDel1, 3)
resBonds = keeper.getDelegatorBonds(ctx, addrDels[0], 3)
require.Equal(t, 3, len(resBonds))
resBonds = keeper.getDelegatorBonds(ctx, addrDel1, 2)
resBonds = keeper.getDelegatorBonds(ctx, addrDels[0], 2)
require.Equal(t, 2, len(resBonds))
resBonds = keeper.getDelegatorBonds(ctx, addrDel2, 5)
resBonds = keeper.getDelegatorBonds(ctx, addrDels[1], 5)
require.Equal(t, 3, len(resBonds))
assert.True(t, bondsEqual(bond2to1, resBonds[0]))
assert.True(t, bondsEqual(bond2to2, resBonds[1]))
@ -167,9 +176,9 @@ func TestBond(t *testing.T) {
// delete a record
keeper.removeDelegatorBond(ctx, bond2to3)
_, found = keeper.getDelegatorBond(ctx, addrDel2, addrVal3)
_, found = keeper.getDelegatorBond(ctx, addrDels[1], addrVals[2])
assert.False(t, found)
resBonds = keeper.getDelegatorBonds(ctx, addrDel2, 5)
resBonds = keeper.getDelegatorBonds(ctx, addrDels[1], 5)
require.Equal(t, 2, len(resBonds))
assert.True(t, bondsEqual(bond2to1, resBonds[0]))
assert.True(t, bondsEqual(bond2to2, resBonds[1]))
@ -177,30 +186,30 @@ func TestBond(t *testing.T) {
// delete all the records from delegator 2
keeper.removeDelegatorBond(ctx, bond2to1)
keeper.removeDelegatorBond(ctx, bond2to2)
_, found = keeper.getDelegatorBond(ctx, addrDel2, addrVal1)
_, found = keeper.getDelegatorBond(ctx, addrDels[1], addrVals[0])
assert.False(t, found)
_, found = keeper.getDelegatorBond(ctx, addrDel2, addrVal2)
_, found = keeper.getDelegatorBond(ctx, addrDels[1], addrVals[1])
assert.False(t, found)
resBonds = keeper.getDelegatorBonds(ctx, addrDel2, 5)
resBonds = keeper.getDelegatorBonds(ctx, addrDels[1], 5)
require.Equal(t, 0, len(resBonds))
}
// TODO integrate in testing for equal validators, whichever one was a validator
// first remains the validator https://github.com/cosmos/cosmos-sdk/issues/582
func TestGetValidators(t *testing.T) {
ctx, _, keeper := createTestInput(t, nil, false, 0)
ctx, _, keeper := createTestInput(t, false, 0)
// initialize some candidates into the state
amts := []int64{0, 100, 1, 400, 200}
n := len(amts)
candidates := make([]Candidate, n)
for i := 0; i < n; i++ {
var candidates [5]Candidate
for i, amt := range amts {
c := Candidate{
Status: Unbonded,
PubKey: pks[i],
Address: addrs[i],
Assets: sdk.NewRat(amts[i]),
Liabilities: sdk.NewRat(amts[i]),
Assets: sdk.NewRat(amt),
Liabilities: sdk.NewRat(amt),
}
keeper.setCandidate(ctx, c)
candidates[i] = c
@ -209,11 +218,11 @@ func TestGetValidators(t *testing.T) {
// first make sure everything as normal is ordered
validators := keeper.GetValidators(ctx)
require.Equal(t, len(validators), n)
assert.Equal(t, sdk.NewRat(400), validators[0].VotingPower, "%v", validators)
assert.Equal(t, sdk.NewRat(200), validators[1].VotingPower, "%v", validators)
assert.Equal(t, sdk.NewRat(100), validators[2].VotingPower, "%v", validators)
assert.Equal(t, sdk.NewRat(1), validators[3].VotingPower, "%v", validators)
assert.Equal(t, sdk.NewRat(0), validators[4].VotingPower, "%v", validators)
assert.Equal(t, sdk.NewRat(400), validators[0].Power, "%v", validators)
assert.Equal(t, sdk.NewRat(200), validators[1].Power, "%v", validators)
assert.Equal(t, sdk.NewRat(100), validators[2].Power, "%v", validators)
assert.Equal(t, sdk.NewRat(1), validators[3].Power, "%v", validators)
assert.Equal(t, sdk.NewRat(0), validators[4].Power, "%v", validators)
assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators)
assert.Equal(t, candidates[4].Address, validators[1].Address, "%v", validators)
assert.Equal(t, candidates[1].Address, validators[2].Address, "%v", validators)
@ -225,7 +234,7 @@ func TestGetValidators(t *testing.T) {
keeper.setCandidate(ctx, candidates[3])
validators = keeper.GetValidators(ctx)
require.Equal(t, len(validators), n)
assert.Equal(t, sdk.NewRat(500), validators[0].VotingPower, "%v", validators)
assert.Equal(t, sdk.NewRat(500), validators[0].Power, "%v", validators)
assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators)
// test a decrease in voting power
@ -233,7 +242,7 @@ func TestGetValidators(t *testing.T) {
keeper.setCandidate(ctx, candidates[3])
validators = keeper.GetValidators(ctx)
require.Equal(t, len(validators), n)
assert.Equal(t, sdk.NewRat(300), validators[0].VotingPower, "%v", validators)
assert.Equal(t, sdk.NewRat(300), validators[0].Power, "%v", validators)
assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators)
// test a swap in voting power
@ -241,9 +250,9 @@ func TestGetValidators(t *testing.T) {
keeper.setCandidate(ctx, candidates[0])
validators = keeper.GetValidators(ctx)
require.Equal(t, len(validators), n)
assert.Equal(t, sdk.NewRat(600), validators[0].VotingPower, "%v", validators)
assert.Equal(t, sdk.NewRat(600), validators[0].Power, "%v", validators)
assert.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators)
assert.Equal(t, sdk.NewRat(300), validators[1].VotingPower, "%v", validators)
assert.Equal(t, sdk.NewRat(300), validators[1].Power, "%v", validators)
assert.Equal(t, candidates[3].Address, validators[1].Address, "%v", validators)
// test the max validators term
@ -253,46 +262,301 @@ func TestGetValidators(t *testing.T) {
keeper.setParams(ctx, params)
validators = keeper.GetValidators(ctx)
require.Equal(t, len(validators), n)
assert.Equal(t, sdk.NewRat(600), validators[0].VotingPower, "%v", validators)
assert.Equal(t, sdk.NewRat(600), validators[0].Power, "%v", validators)
assert.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators)
assert.Equal(t, sdk.NewRat(300), validators[1].VotingPower, "%v", validators)
assert.Equal(t, sdk.NewRat(300), validators[1].Power, "%v", validators)
assert.Equal(t, candidates[3].Address, validators[1].Address, "%v", validators)
}
// TODO
// test the mechanism which keeps track of a validator set change
func TestGetAccUpdateValidators(t *testing.T) {
//TODO
// test from nothing to something
// test from something to nothing
// test identical
// test single value change
// test multiple value change
// test validator added at the beginning
// test validator added in the middle
// test validator added at the end
// test multiple validators removed
}
// clear the tracked changes to the validator set
func TestClearAccUpdateValidators(t *testing.T) {
//TODO
ctx, _, keeper := createTestInput(t, false, 0)
amts := []int64{100, 400, 200}
candidates := make([]Candidate, len(amts))
for i, amt := range amts {
c := Candidate{
Status: Unbonded,
PubKey: pks[i],
Address: addrs[i],
Assets: sdk.NewRat(amt),
Liabilities: sdk.NewRat(amt),
}
candidates[i] = c
keeper.setCandidate(ctx, c)
}
acc := keeper.getAccUpdateValidators(ctx)
assert.Equal(t, len(amts), len(acc))
keeper.clearAccUpdateValidators(ctx)
acc = keeper.getAccUpdateValidators(ctx)
assert.Equal(t, 0, len(acc))
}
// test the mechanism which keeps track of a validator set change
func TestGetAccUpdateValidators(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
params := defaultParams()
params.MaxValidators = 4
keeper.setParams(ctx, params)
// TODO eliminate use of candidatesIn here
// tests could be clearer if they just
// created the candidate at time of use
// and were labelled by power in the comments
// outlining in each test
amts := []int64{10, 11, 12, 13, 1}
var candidatesIn [5]Candidate
for i, amt := range amts {
candidatesIn[i] = Candidate{
Address: addrs[i],
PubKey: pks[i],
Assets: sdk.NewRat(amt),
Liabilities: sdk.NewRat(amt),
}
}
// to compare pubkeys between abci pubkey and crypto.PubKey
wirePK := func(pk crypto.PubKey) []byte {
pkBytes, err := keeper.cdc.MarshalBinary(pk)
if err != nil {
panic(err)
}
return pkBytes
}
// test from nothing to something
// candidate set: {} -> {c1, c3}
// validator set: {} -> {c1, c3}
// accUpdate set: {} -> {c1, c3}
assert.Equal(t, 0, len(keeper.GetCandidates(ctx, 5)))
assert.Equal(t, 0, len(keeper.GetValidators(ctx)))
assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx)))
keeper.setCandidate(ctx, candidatesIn[1])
keeper.setCandidate(ctx, candidatesIn[3])
vals := keeper.GetValidators(ctx) // to init recent validator set
require.Equal(t, 2, len(vals))
acc := keeper.getAccUpdateValidators(ctx)
require.Equal(t, 2, len(acc))
candidates := keeper.GetCandidates(ctx, 5)
require.Equal(t, 2, len(candidates))
assert.Equal(t, candidates[0].validator().abciValidator(keeper.cdc), acc[0])
assert.Equal(t, candidates[1].validator().abciValidator(keeper.cdc), acc[1])
assert.Equal(t, candidates[0].validator(), vals[1])
assert.Equal(t, candidates[1].validator(), vals[0])
// test identical,
// candidate set: {c1, c3} -> {c1, c3}
// accUpdate set: {} -> {}
keeper.clearAccUpdateValidators(ctx)
assert.Equal(t, 2, len(keeper.GetCandidates(ctx, 5)))
assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx)))
keeper.setCandidate(ctx, candidates[0])
keeper.setCandidate(ctx, candidates[1])
require.Equal(t, 2, len(keeper.GetCandidates(ctx, 5)))
assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx)))
// test single value change
// candidate set: {c1, c3} -> {c1', c3}
// accUpdate set: {} -> {c1'}
keeper.clearAccUpdateValidators(ctx)
assert.Equal(t, 2, len(keeper.GetCandidates(ctx, 5)))
assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx)))
candidates[0].Assets = sdk.NewRat(600)
keeper.setCandidate(ctx, candidates[0])
candidates = keeper.GetCandidates(ctx, 5)
require.Equal(t, 2, len(candidates))
assert.True(t, candidates[0].Assets.Equal(sdk.NewRat(600)))
acc = keeper.getAccUpdateValidators(ctx)
require.Equal(t, 1, len(acc))
assert.Equal(t, candidates[0].validator().abciValidator(keeper.cdc), acc[0])
// test multiple value change
// candidate set: {c1, c3} -> {c1', c3'}
// accUpdate set: {c1, c3} -> {c1', c3'}
keeper.clearAccUpdateValidators(ctx)
assert.Equal(t, 2, len(keeper.GetCandidates(ctx, 5)))
assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx)))
candidates[0].Assets = sdk.NewRat(200)
candidates[1].Assets = sdk.NewRat(100)
keeper.setCandidate(ctx, candidates[0])
keeper.setCandidate(ctx, candidates[1])
acc = keeper.getAccUpdateValidators(ctx)
require.Equal(t, 2, len(acc))
candidates = keeper.GetCandidates(ctx, 5)
require.Equal(t, 2, len(candidates))
require.Equal(t, candidates[0].validator().abciValidator(keeper.cdc), acc[0])
require.Equal(t, candidates[1].validator().abciValidator(keeper.cdc), acc[1])
// test validtor added at the beginning
// candidate set: {c1, c3} -> {c0, c1, c3}
// accUpdate set: {} -> {c0}
keeper.clearAccUpdateValidators(ctx)
assert.Equal(t, 2, len(keeper.GetCandidates(ctx, 5)))
assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx)))
keeper.setCandidate(ctx, candidatesIn[0])
acc = keeper.getAccUpdateValidators(ctx)
require.Equal(t, 1, len(acc))
candidates = keeper.GetCandidates(ctx, 5)
require.Equal(t, 3, len(candidates))
assert.Equal(t, candidates[0].validator().abciValidator(keeper.cdc), acc[0])
// test validator added at the middle
// candidate set: {c0, c1, c3} -> {c0, c1, c2, c3]
// accUpdate set: {} -> {c2}
keeper.clearAccUpdateValidators(ctx)
assert.Equal(t, 3, len(keeper.GetCandidates(ctx, 5)))
assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx)))
keeper.setCandidate(ctx, candidatesIn[2])
acc = keeper.getAccUpdateValidators(ctx)
require.Equal(t, 1, len(acc))
candidates = keeper.GetCandidates(ctx, 5)
require.Equal(t, 4, len(candidates))
assert.Equal(t, candidates[2].validator().abciValidator(keeper.cdc), acc[0])
// test candidate added at the end but not inserted in the valset
// candidate set: {c0, c1, c2, c3} -> {c0, c1, c2, c3, c4}
// validator set: {c0, c1, c2, c3} -> {c0, c1, c2, c3}
// accUpdate set: {} -> {}
keeper.clearAccUpdateValidators(ctx)
assert.Equal(t, 4, len(keeper.GetCandidates(ctx, 5)))
assert.Equal(t, 4, len(keeper.GetValidators(ctx)))
assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx)))
keeper.setCandidate(ctx, candidatesIn[4])
assert.Equal(t, 5, len(keeper.GetCandidates(ctx, 5)))
assert.Equal(t, 4, len(keeper.GetValidators(ctx)))
require.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) // max validator number is 4
// test candidate change its power but still not in the valset
// candidate set: {c0, c1, c2, c3, c4} -> {c0, c1, c2, c3, c4}
// validator set: {c0, c1, c2, c3} -> {c0, c1, c2, c3}
// accUpdate set: {} -> {}
keeper.clearAccUpdateValidators(ctx)
assert.Equal(t, 5, len(keeper.GetCandidates(ctx, 5)))
assert.Equal(t, 4, len(keeper.GetValidators(ctx)))
assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx)))
candidatesIn[4].Assets = sdk.NewRat(1)
keeper.setCandidate(ctx, candidatesIn[4])
assert.Equal(t, 5, len(keeper.GetCandidates(ctx, 5)))
assert.Equal(t, 4, len(keeper.GetValidators(ctx)))
require.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) // max validator number is 4
// test candidate change its power and become a validator (pushing out an existing)
// candidate set: {c0, c1, c2, c3, c4} -> {c0, c1, c2, c3, c4}
// validator set: {c0, c1, c2, c3} -> {c1, c2, c3, c4}
// accUpdate set: {} -> {c0, c4}
keeper.clearAccUpdateValidators(ctx)
assert.Equal(t, 5, len(keeper.GetCandidates(ctx, 5)))
assert.Equal(t, 4, len(keeper.GetValidators(ctx)))
assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx)))
candidatesIn[4].Assets = sdk.NewRat(1000)
keeper.setCandidate(ctx, candidatesIn[4])
candidates = keeper.GetCandidates(ctx, 5)
require.Equal(t, 5, len(candidates))
vals = keeper.GetValidators(ctx)
require.Equal(t, 4, len(vals))
assert.Equal(t, candidatesIn[1].Address, vals[1].Address)
assert.Equal(t, candidatesIn[2].Address, vals[3].Address)
assert.Equal(t, candidatesIn[3].Address, vals[2].Address)
assert.Equal(t, candidatesIn[4].Address, vals[0].Address)
acc = keeper.getAccUpdateValidators(ctx)
require.Equal(t, 2, len(acc), "%v", acc)
assert.Equal(t, wirePK(candidatesIn[0].PubKey), acc[0].PubKey)
assert.Equal(t, int64(0), acc[0].Power)
assert.Equal(t, vals[0].abciValidator(keeper.cdc), acc[1])
// test from something to nothing
// candidate set: {c0, c1, c2, c3, c4} -> {}
// validator set: {c1, c2, c3, c4} -> {}
// accUpdate set: {} -> {c1, c2, c3, c4}
keeper.clearAccUpdateValidators(ctx)
assert.Equal(t, 5, len(keeper.GetCandidates(ctx, 5)))
assert.Equal(t, 4, len(keeper.GetValidators(ctx)))
assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx)))
keeper.removeCandidate(ctx, candidatesIn[0].Address)
keeper.removeCandidate(ctx, candidatesIn[1].Address)
keeper.removeCandidate(ctx, candidatesIn[2].Address)
keeper.removeCandidate(ctx, candidatesIn[3].Address)
keeper.removeCandidate(ctx, candidatesIn[4].Address)
vals = keeper.GetValidators(ctx)
assert.Equal(t, 0, len(vals), "%v", vals)
candidates = keeper.GetCandidates(ctx, 5)
require.Equal(t, 0, len(candidates))
acc = keeper.getAccUpdateValidators(ctx)
require.Equal(t, 4, len(acc))
assert.Equal(t, wirePK(candidatesIn[1].PubKey), acc[0].PubKey)
assert.Equal(t, wirePK(candidatesIn[2].PubKey), acc[1].PubKey)
assert.Equal(t, wirePK(candidatesIn[3].PubKey), acc[2].PubKey)
assert.Equal(t, wirePK(candidatesIn[4].PubKey), acc[3].PubKey)
assert.Equal(t, int64(0), acc[0].Power)
assert.Equal(t, int64(0), acc[1].Power)
assert.Equal(t, int64(0), acc[2].Power)
assert.Equal(t, int64(0), acc[3].Power)
}
// test if is a validator from the last update
func TestIsRecentValidator(t *testing.T) {
//TODO
ctx, _, keeper := createTestInput(t, false, 0)
amts := []int64{9, 8, 7, 10, 6}
var candidatesIn [5]Candidate
for i, amt := range amts {
candidatesIn[i] = Candidate{
Address: addrVals[i],
PubKey: pks[i],
Assets: sdk.NewRat(amt),
Liabilities: sdk.NewRat(amt),
}
}
// test that an empty validator set doesn't have any validators
validators := keeper.GetValidators(ctx)
assert.Equal(t, 0, len(validators))
// get the validators for the first time
keeper.setCandidate(ctx, candidatesIn[0])
keeper.setCandidate(ctx, candidatesIn[1])
validators = keeper.GetValidators(ctx)
require.Equal(t, 2, len(validators))
assert.Equal(t, candidatesIn[0].validator(), validators[0])
assert.Equal(t, candidatesIn[1].validator(), validators[1])
// test a basic retrieve of something that should be a recent validator
assert.True(t, keeper.IsRecentValidator(ctx, candidatesIn[0].Address))
assert.True(t, keeper.IsRecentValidator(ctx, candidatesIn[1].Address))
// test a basic retrieve of something that should not be a recent validator
assert.False(t, keeper.IsRecentValidator(ctx, candidatesIn[2].Address))
// remove that validator, but don't retrieve the recent validator group
keeper.removeCandidate(ctx, candidatesIn[0].Address)
// test that removed validator is not considered a recent validator
assert.False(t, keeper.IsRecentValidator(ctx, candidatesIn[0].Address))
}
func TestParams(t *testing.T) {
ctx, _, keeper := createTestInput(t, nil, false, 0)
ctx, _, keeper := createTestInput(t, false, 0)
expParams := defaultParams()
//check that the empty keeper loads the default
@ -305,3 +569,46 @@ func TestParams(t *testing.T) {
resParams = keeper.GetParams(ctx)
assert.Equal(t, expParams, resParams)
}
func TestPool(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
expPool := initialPool()
//check that the empty keeper loads the default
resPool := keeper.GetPool(ctx)
assert.Equal(t, expPool, resPool)
//modify a params, save, and retrieve
expPool.TotalSupply = 777
keeper.setPool(ctx, expPool)
resPool = keeper.GetPool(ctx)
assert.Equal(t, expPool, resPool)
}
func TestInitGenesis(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
jsonStr := `{
"params": {
"inflation_rate_change": {"num": 13, "denom": 100},
"inflation_max": {"num": 20, "denom": 100},
"inflation_min": {"num": 7, "denom": 100},
"goal_bonded": {"num": 67, "denom": 100},
"max_validators": 100,
"bond_denom": "fermion"
},
"pool": {
"total_supply": 0,
"bonded_shares": {"num": 0, "denom": 1},
"unbonded_shares": {"num": 0, "denom": 1},
"bonded_pool": 0,
"unbonded_pool": 0,
"inflation_last_time": 0,
"inflation": {"num": 7, "denom": 100}
}
}`
encoded := json.RawMessage(jsonStr)
err := keeper.InitGenesis(ctx, encoded)
require.Nil(t, err)
require.Equal(t, keeper.GetPool(ctx), initialPool())
require.Equal(t, keeper.GetParams(ctx), defaultParams())
}

View File

@ -4,132 +4,115 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// load/save the global staking state
func (k Keeper) GetPool(ctx sdk.Context) (gs Pool) {
// check if cached before anything
if k.gs != (Pool{}) {
return k.gs
// get the bond ratio of the global state
func (p Pool) bondedRatio() sdk.Rat {
if p.TotalSupply > 0 {
return sdk.NewRat(p.BondedPool, p.TotalSupply)
}
store := ctx.KVStore(k.storeKey)
b := store.Get(PoolKey)
if b == nil {
return initialPool()
}
err := k.cdc.UnmarshalBinary(b, &gs)
if err != nil {
panic(err) // This error should never occur big problem if does
}
return
return sdk.ZeroRat
}
func (k Keeper) setPool(ctx sdk.Context, p Pool) {
store := ctx.KVStore(k.storeKey)
b, err := k.cdc.MarshalBinary(p)
if err != nil {
panic(err)
// get the exchange rate of bonded token per issued share
func (p Pool) bondedShareExRate() sdk.Rat {
if p.BondedShares.IsZero() {
return sdk.OneRat
}
store.Set(PoolKey, b)
k.gs = Pool{} // clear the cache
return sdk.NewRat(p.BondedPool).Quo(p.BondedShares)
}
//_______________________________________________________________________
//TODO make these next two functions more efficient should be reading and writting to state ye know
// get the exchange rate of unbonded tokens held in candidates per issued share
func (p Pool) unbondedShareExRate() sdk.Rat {
if p.UnbondedShares.IsZero() {
return sdk.OneRat
}
return sdk.NewRat(p.UnbondedPool).Quo(p.UnbondedShares)
}
// move a candidates asset pool from bonded to unbonded pool
func (k Keeper) bondedToUnbondedPool(ctx sdk.Context, candidate Candidate) {
func (p Pool) bondedToUnbondedPool(candidate Candidate) (Pool, Candidate) {
// replace bonded shares with unbonded shares
tokens := k.removeSharesBonded(ctx, candidate.Assets)
candidate.Assets = k.addTokensUnbonded(ctx, tokens)
p, tokens := p.removeSharesBonded(candidate.Assets)
p, candidate.Assets = p.addTokensUnbonded(tokens)
candidate.Status = Unbonded
k.setCandidate(ctx, candidate)
return p, candidate
}
// move a candidates asset pool from unbonded to bonded pool
func (k Keeper) unbondedToBondedPool(ctx sdk.Context, candidate Candidate) {
func (p Pool) unbondedToBondedPool(candidate Candidate) (Pool, Candidate) {
// replace unbonded shares with bonded shares
tokens := k.removeSharesUnbonded(ctx, candidate.Assets)
candidate.Assets = k.addTokensBonded(ctx, tokens)
p, tokens := p.removeSharesUnbonded(candidate.Assets)
p, candidate.Assets = p.addTokensBonded(tokens)
candidate.Status = Bonded
k.setCandidate(ctx, candidate)
return p, candidate
}
//_______________________________________________________________________
func (k Keeper) addTokensBonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) {
p := k.GetPool(ctx)
issuedShares = p.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens
func (p Pool) addTokensBonded(amount int64) (p2 Pool, issuedShares sdk.Rat) {
issuedShares = sdk.NewRat(amount).Quo(p.bondedShareExRate()) // (tokens/shares)^-1 * tokens
p.BondedPool += amount
p.BondedShares = p.BondedShares.Add(issuedShares)
k.setPool(ctx, p)
return
return p, issuedShares
}
func (k Keeper) removeSharesBonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) {
p := k.GetPool(ctx)
func (p Pool) removeSharesBonded(shares sdk.Rat) (p2 Pool, removedTokens int64) {
removedTokens = p.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares
p.BondedShares = p.BondedShares.Sub(shares)
p.BondedPool -= removedTokens
k.setPool(ctx, p)
return
p.BondedPool = p.BondedPool - removedTokens
return p, removedTokens
}
func (k Keeper) addTokensUnbonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) {
p := k.GetPool(ctx)
func (p Pool) addTokensUnbonded(amount int64) (p2 Pool, issuedShares sdk.Rat) {
issuedShares = p.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens
p.UnbondedShares = p.UnbondedShares.Add(issuedShares)
p.UnbondedPool += amount
k.setPool(ctx, p)
return
return p, issuedShares
}
func (k Keeper) removeSharesUnbonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) {
p := k.GetPool(ctx)
func (p Pool) removeSharesUnbonded(shares sdk.Rat) (p2 Pool, removedTokens int64) {
removedTokens = p.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares
p.UnbondedShares = p.UnbondedShares.Sub(shares)
p.UnbondedPool -= removedTokens
k.setPool(ctx, p)
return
return p, removedTokens
}
//_______________________________________________________________________
// add tokens to a candidate
func (k Keeper) candidateAddTokens(ctx sdk.Context, candidate Candidate, amount int64) (issuedDelegatorShares sdk.Rat) {
func (p Pool) candidateAddTokens(candidate Candidate,
amount int64) (p2 Pool, candidate2 Candidate, issuedDelegatorShares sdk.Rat) {
p := k.GetPool(ctx)
exRate := candidate.delegatorShareExRate()
var receivedGlobalShares sdk.Rat
if candidate.Status == Bonded {
receivedGlobalShares = k.addTokensBonded(ctx, amount)
p, receivedGlobalShares = p.addTokensBonded(amount)
} else {
receivedGlobalShares = k.addTokensUnbonded(ctx, amount)
p, receivedGlobalShares = p.addTokensUnbonded(amount)
}
candidate.Assets = candidate.Assets.Add(receivedGlobalShares)
issuedDelegatorShares = exRate.Mul(receivedGlobalShares)
candidate.Liabilities = candidate.Liabilities.Add(issuedDelegatorShares)
k.setPool(ctx, p) // TODO cache Pool?
return
return p, candidate, issuedDelegatorShares
}
// remove shares from a candidate
func (k Keeper) candidateRemoveShares(ctx sdk.Context, candidate Candidate, shares sdk.Rat) (createdCoins int64) {
func (p Pool) candidateRemoveShares(candidate Candidate,
shares sdk.Rat) (p2 Pool, candidate2 Candidate, createdCoins int64) {
p := k.GetPool(ctx)
//exRate := candidate.delegatorShareExRate() //XXX make sure not used
globalPoolSharesToRemove := candidate.delegatorShareExRate().Mul(shares)
if candidate.Status == Bonded {
createdCoins = k.removeSharesBonded(ctx, globalPoolSharesToRemove)
p, createdCoins = p.removeSharesBonded(globalPoolSharesToRemove)
} else {
createdCoins = k.removeSharesUnbonded(ctx, globalPoolSharesToRemove)
p, createdCoins = p.removeSharesUnbonded(globalPoolSharesToRemove)
}
candidate.Assets = candidate.Assets.Sub(globalPoolSharesToRemove)
candidate.Liabilities = candidate.Liabilities.Sub(shares)
k.setPool(ctx, p) // TODO cache Pool?
return
return p, candidate, createdCoins
}

View File

@ -1,22 +1,533 @@
package stake
import (
"fmt"
"math/rand"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestPool(t *testing.T) {
ctx, _, keeper := createTestInput(t, nil, false, 0)
expPool := initialPool()
func TestBondedRatio(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
pool.TotalSupply = 3
pool.BondedPool = 2
//check that the empty keeper loads the default
resPool := keeper.GetPool(ctx)
assert.Equal(t, expPool, resPool)
// bonded pool / total supply
require.Equal(t, pool.bondedRatio(), sdk.NewRat(2).Quo(sdk.NewRat(3)))
pool.TotalSupply = 0
//modify a params, save, and retrieve
expPool.TotalSupply = 777
keeper.setPool(ctx, expPool)
resPool = keeper.GetPool(ctx)
assert.Equal(t, expPool, resPool)
// avoids divide-by-zero
require.Equal(t, pool.bondedRatio(), sdk.ZeroRat)
}
func TestBondedShareExRate(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
pool.BondedPool = 3
pool.BondedShares = sdk.NewRat(10)
// bonded pool / bonded shares
require.Equal(t, pool.bondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10)))
pool.BondedShares = sdk.ZeroRat
// avoids divide-by-zero
require.Equal(t, pool.bondedShareExRate(), sdk.OneRat)
}
func TestUnbondedShareExRate(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
pool.UnbondedPool = 3
pool.UnbondedShares = sdk.NewRat(10)
// unbonded pool / unbonded shares
require.Equal(t, pool.unbondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10)))
pool.UnbondedShares = sdk.ZeroRat
// avoids divide-by-zero
require.Equal(t, pool.unbondedShareExRate(), sdk.OneRat)
}
func TestBondedToUnbondedPool(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
poolA := keeper.GetPool(ctx)
assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat)
assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat)
candA := Candidate{
Status: Bonded,
Address: addrs[0],
PubKey: pks[0],
Assets: sdk.OneRat,
Liabilities: sdk.OneRat,
}
poolB, candB := poolA.bondedToUnbondedPool(candA)
// status unbonded
assert.Equal(t, candB.Status, Unbonded)
// same exchange rate, assets unchanged
assert.Equal(t, candB.Assets, candA.Assets)
// bonded pool decreased
assert.Equal(t, poolB.BondedPool, poolA.BondedPool-candA.Assets.Evaluate())
// unbonded pool increased
assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool+candA.Assets.Evaluate())
// conservation of tokens
assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool, poolA.BondedPool+poolA.UnbondedPool)
}
func TestUnbonbedtoBondedPool(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
poolA := keeper.GetPool(ctx)
assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat)
assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat)
candA := Candidate{
Status: Bonded,
Address: addrs[0],
PubKey: pks[0],
Assets: sdk.OneRat,
Liabilities: sdk.OneRat,
}
candA.Status = Unbonded
poolB, candB := poolA.unbondedToBondedPool(candA)
// status bonded
assert.Equal(t, candB.Status, Bonded)
// same exchange rate, assets unchanged
assert.Equal(t, candB.Assets, candA.Assets)
// bonded pool increased
assert.Equal(t, poolB.BondedPool, poolA.BondedPool+candA.Assets.Evaluate())
// unbonded pool decreased
assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool-candA.Assets.Evaluate())
// conservation of tokens
assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool, poolA.BondedPool+poolA.UnbondedPool)
}
func TestAddTokensBonded(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
poolA := keeper.GetPool(ctx)
assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat)
poolB, sharesB := poolA.addTokensBonded(10)
assert.Equal(t, poolB.bondedShareExRate(), sdk.OneRat)
// correct changes to bonded shares and bonded pool
assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Add(sharesB))
assert.Equal(t, poolB.BondedPool, poolA.BondedPool+10)
// same number of bonded shares / tokens when exchange rate is one
assert.Equal(t, poolB.BondedShares, sdk.NewRat(poolB.BondedPool))
}
func TestRemoveSharesBonded(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
poolA := keeper.GetPool(ctx)
assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat)
poolB, tokensB := poolA.removeSharesBonded(sdk.NewRat(10))
assert.Equal(t, poolB.bondedShareExRate(), sdk.OneRat)
// correct changes to bonded shares and bonded pool
assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Sub(sdk.NewRat(10)))
assert.Equal(t, poolB.BondedPool, poolA.BondedPool-tokensB)
// same number of bonded shares / tokens when exchange rate is one
assert.Equal(t, poolB.BondedShares, sdk.NewRat(poolB.BondedPool))
}
func TestAddTokensUnbonded(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
poolA := keeper.GetPool(ctx)
assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat)
poolB, sharesB := poolA.addTokensUnbonded(10)
assert.Equal(t, poolB.unbondedShareExRate(), sdk.OneRat)
// correct changes to unbonded shares and unbonded pool
assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Add(sharesB))
assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool+10)
// same number of unbonded shares / tokens when exchange rate is one
assert.Equal(t, poolB.UnbondedShares, sdk.NewRat(poolB.UnbondedPool))
}
func TestRemoveSharesUnbonded(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
poolA := keeper.GetPool(ctx)
assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat)
poolB, tokensB := poolA.removeSharesUnbonded(sdk.NewRat(10))
assert.Equal(t, poolB.unbondedShareExRate(), sdk.OneRat)
// correct changes to unbonded shares and bonded pool
assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Sub(sdk.NewRat(10)))
assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool-tokensB)
// same number of unbonded shares / tokens when exchange rate is one
assert.Equal(t, poolB.UnbondedShares, sdk.NewRat(poolB.UnbondedPool))
}
func TestCandidateAddTokens(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
poolA := keeper.GetPool(ctx)
candA := Candidate{
Status: Bonded,
Address: addrs[0],
PubKey: pks[0],
Assets: sdk.NewRat(9),
Liabilities: sdk.NewRat(9),
}
poolA.BondedPool = candA.Assets.Evaluate()
poolA.BondedShares = candA.Assets
assert.Equal(t, candA.delegatorShareExRate(), sdk.OneRat)
assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat)
assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat)
poolB, candB, sharesB := poolA.candidateAddTokens(candA, 10)
// shares were issued
assert.Equal(t, sdk.NewRat(10).Mul(candA.delegatorShareExRate()), sharesB)
// pool shares were added
assert.Equal(t, candB.Assets, candA.Assets.Add(sdk.NewRat(10)))
// conservation of tokens
assert.Equal(t, poolB.BondedPool, 10+poolA.BondedPool)
}
func TestCandidateRemoveShares(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
poolA := keeper.GetPool(ctx)
candA := Candidate{
Status: Bonded,
Address: addrs[0],
PubKey: pks[0],
Assets: sdk.NewRat(9),
Liabilities: sdk.NewRat(9),
}
poolA.BondedPool = candA.Assets.Evaluate()
poolA.BondedShares = candA.Assets
assert.Equal(t, candA.delegatorShareExRate(), sdk.OneRat)
assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat)
assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat)
poolB, candB, coinsB := poolA.candidateRemoveShares(candA, sdk.NewRat(10))
// coins were created
assert.Equal(t, coinsB, int64(10))
// pool shares were removed
assert.Equal(t, candB.Assets, candA.Assets.Sub(sdk.NewRat(10).Mul(candA.delegatorShareExRate())))
// conservation of tokens
assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool+coinsB, poolA.UnbondedPool+poolA.BondedPool)
// specific case from random tests
assets := sdk.NewRat(5102)
liabilities := sdk.NewRat(115)
cand := Candidate{
Status: Bonded,
Address: addrs[0],
PubKey: pks[0],
Assets: assets,
Liabilities: liabilities,
}
pool := Pool{
TotalSupply: 0,
BondedShares: sdk.NewRat(248305),
UnbondedShares: sdk.NewRat(232147),
BondedPool: 248305,
UnbondedPool: 232147,
InflationLastTime: 0,
Inflation: sdk.NewRat(7, 100),
}
shares := sdk.NewRat(29)
msg := fmt.Sprintf("candidate %s (status: %d, assets: %v, liabilities: %v, delegatorShareExRate: %v)",
cand.Address, cand.Status, cand.Assets, cand.Liabilities, cand.delegatorShareExRate())
msg = fmt.Sprintf("Removed %v shares from %s", shares, msg)
newPool, _, tokens := pool.candidateRemoveShares(cand, shares)
require.Equal(t,
tokens+newPool.UnbondedPool+newPool.BondedPool,
pool.BondedPool+pool.UnbondedPool,
"Tokens were not conserved: %s", msg)
}
/////////////////////////////////////
// TODO Make all random tests less obfuscated!
// generate a random candidate
func randomCandidate(r *rand.Rand) Candidate {
var status CandidateStatus
if r.Float64() < float64(0.5) {
status = Bonded
} else {
status = Unbonded
}
assets := sdk.NewRat(int64(r.Int31n(10000)))
liabilities := sdk.NewRat(int64(r.Int31n(10000)))
return Candidate{
Status: status,
Address: addrs[0],
PubKey: pks[0],
Assets: assets,
Liabilities: liabilities,
}
}
// generate a random staking state
func randomSetup(r *rand.Rand, numCandidates int) (Pool, Candidates) {
pool := Pool{
TotalSupply: 0,
BondedShares: sdk.ZeroRat,
UnbondedShares: sdk.ZeroRat,
BondedPool: 0,
UnbondedPool: 0,
InflationLastTime: 0,
Inflation: sdk.NewRat(7, 100),
}
candidates := make([]Candidate, numCandidates)
for i := 0; i < numCandidates; i++ {
candidate := randomCandidate(r)
if candidate.Status == Bonded {
pool.BondedShares = pool.BondedShares.Add(candidate.Assets)
pool.BondedPool += candidate.Assets.Evaluate()
} else if candidate.Status == Unbonded {
pool.UnbondedShares = pool.UnbondedShares.Add(candidate.Assets)
pool.UnbondedPool += candidate.Assets.Evaluate()
}
candidates[i] = candidate
}
return pool, candidates
}
// any operation that transforms staking state
// takes in RNG instance, pool, candidate
// returns updated pool, updated candidate, delta tokens, descriptive message
type Operation func(r *rand.Rand, p Pool, c Candidate) (Pool, Candidate, int64, string)
// operation: bond or unbond a candidate depending on current status
func OpBondOrUnbond(r *rand.Rand, p Pool, cand Candidate) (Pool, Candidate, int64, string) {
var msg string
if cand.Status == Bonded {
msg = fmt.Sprintf("Unbonded previously bonded candidate %s (assets: %v, liabilities: %v, delegatorShareExRate: %v)",
cand.Address, cand.Assets, cand.Liabilities, cand.delegatorShareExRate())
p, cand = p.bondedToUnbondedPool(cand)
} else if cand.Status == Unbonded {
msg = fmt.Sprintf("Bonded previously unbonded candidate %s (assets: %v, liabilities: %v, delegatorShareExRate: %v)",
cand.Address, cand.Assets, cand.Liabilities, cand.delegatorShareExRate())
p, cand = p.unbondedToBondedPool(cand)
}
return p, cand, 0, msg
}
// operation: add a random number of tokens to a candidate
func OpAddTokens(r *rand.Rand, p Pool, cand Candidate) (Pool, Candidate, int64, string) {
tokens := int64(r.Int31n(1000))
msg := fmt.Sprintf("candidate %s (status: %d, assets: %v, liabilities: %v, delegatorShareExRate: %v)",
cand.Address, cand.Status, cand.Assets, cand.Liabilities, cand.delegatorShareExRate())
p, cand, _ = p.candidateAddTokens(cand, tokens)
msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg)
return p, cand, -1 * tokens, msg // tokens are removed so for accounting must be negative
}
// operation: remove a random number of shares from a candidate
func OpRemoveShares(r *rand.Rand, p Pool, cand Candidate) (Pool, Candidate, int64, string) {
var shares sdk.Rat
for {
shares = sdk.NewRat(int64(r.Int31n(1000)))
if shares.LT(cand.Liabilities) {
break
}
}
msg := fmt.Sprintf("Removed %v shares from candidate %s (status: %d, assets: %v, liabilities: %v, delegatorShareExRate: %v)",
shares, cand.Address, cand.Status, cand.Assets, cand.Liabilities, cand.delegatorShareExRate())
p, cand, tokens := p.candidateRemoveShares(cand, shares)
return p, cand, tokens, msg
}
// pick a random staking operation
func randomOperation(r *rand.Rand) Operation {
operations := []Operation{
OpBondOrUnbond,
OpAddTokens,
OpRemoveShares,
}
r.Shuffle(len(operations), func(i, j int) {
operations[i], operations[j] = operations[j], operations[i]
})
return operations[0]
}
// ensure invariants that should always be true are true
func assertInvariants(t *testing.T, msg string,
pOrig Pool, cOrig Candidates, pMod Pool, cMods Candidates, tokens int64) {
// total tokens conserved
require.Equal(t,
pOrig.UnbondedPool+pOrig.BondedPool,
pMod.UnbondedPool+pMod.BondedPool+tokens,
"Tokens not conserved - msg: %v\n, pOrig.BondedShares: %v, pOrig.UnbondedShares: %v, pMod.BondedShares: %v, pMod.UnbondedShares: %v, pOrig.UnbondedPool: %v, pOrig.BondedPool: %v, pMod.UnbondedPool: %v, pMod.BondedPool: %v, tokens: %v\n",
msg,
pOrig.BondedShares, pOrig.UnbondedShares,
pMod.BondedShares, pMod.UnbondedShares,
pOrig.UnbondedPool, pOrig.BondedPool,
pMod.UnbondedPool, pMod.BondedPool, tokens)
// nonnegative bonded shares
require.False(t, pMod.BondedShares.LT(sdk.ZeroRat),
"Negative bonded shares - msg: %v\npOrig: %#v\npMod: %#v\ntokens: %v\n",
msg, pOrig, pMod, tokens)
// nonnegative unbonded shares
require.False(t, pMod.UnbondedShares.LT(sdk.ZeroRat),
"Negative unbonded shares - msg: %v\npOrig: %#v\npMod: %#v\ntokens: %v\n",
msg, pOrig, pMod, tokens)
// nonnegative bonded ex rate
require.False(t, pMod.bondedShareExRate().LT(sdk.ZeroRat),
"Applying operation \"%s\" resulted in negative bondedShareExRate: %d",
msg, pMod.bondedShareExRate().Evaluate())
// nonnegative unbonded ex rate
require.False(t, pMod.unbondedShareExRate().LT(sdk.ZeroRat),
"Applying operation \"%s\" resulted in negative unbondedShareExRate: %d",
msg, pMod.unbondedShareExRate().Evaluate())
for _, cMod := range cMods {
// nonnegative ex rate
require.False(t, cMod.delegatorShareExRate().LT(sdk.ZeroRat),
"Applying operation \"%s\" resulted in negative candidate.delegatorShareExRate(): %v (candidate.Address: %s)",
msg,
cMod.delegatorShareExRate(),
cMod.Address,
)
// nonnegative assets
require.False(t, cMod.Assets.LT(sdk.ZeroRat),
"Applying operation \"%s\" resulted in negative candidate.Assets: %v (candidate.Liabilities: %v, candidate.delegatorShareExRate: %v, candidate.Address: %s)",
msg,
cMod.Assets,
cMod.Liabilities,
cMod.delegatorShareExRate(),
cMod.Address,
)
// nonnegative liabilities
require.False(t, cMod.Liabilities.LT(sdk.ZeroRat),
"Applying operation \"%s\" resulted in negative candidate.Liabilities: %v (candidate.Assets: %v, candidate.delegatorShareExRate: %v, candidate.Address: %s)",
msg,
cMod.Liabilities,
cMod.Assets,
cMod.delegatorShareExRate(),
cMod.Address,
)
}
}
// TODO Re-enable once the overflow bug is fixed!
// ref https://github.com/cosmos/cosmos-sdk/issues/753
/*
func TestPossibleOverflow(t *testing.T) {
assets := sdk.NewRat(2159)
liabilities := sdk.NewRat(391432570689183511).Quo(sdk.NewRat(40113011844664))
cand := Candidate{
Status: Bonded,
Address: addrs[0],
PubKey: pks[0],
Assets: assets,
Liabilities: liabilities,
}
pool := Pool{
TotalSupply: 0,
BondedShares: assets,
UnbondedShares: sdk.ZeroRat,
BondedPool: assets.Evaluate(),
UnbondedPool: 0,
InflationLastTime: 0,
Inflation: sdk.NewRat(7, 100),
}
tokens := int64(71)
msg := fmt.Sprintf("candidate %s (status: %d, assets: %v, liabilities: %v, delegatorShareExRate: %v)",
cand.Address, cand.Status, cand.Assets, cand.Liabilities, cand.delegatorShareExRate())
_, newCandidate, _ := pool.candidateAddTokens(cand, tokens)
msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg)
require.False(t, newCandidate.delegatorShareExRate().LT(sdk.ZeroRat),
"Applying operation \"%s\" resulted in negative delegatorShareExRate(): %v",
msg, newCandidate.delegatorShareExRate())
}
*/
// run random operations in a random order on a random single-candidate state, assert invariants hold
func TestSingleCandidateIntegrationInvariants(t *testing.T) {
r := rand.New(rand.NewSource(41))
for i := 0; i < 10; i++ {
poolOrig, candidatesOrig := randomSetup(r, 1)
require.Equal(t, 1, len(candidatesOrig))
// sanity check
assertInvariants(t, "no operation",
poolOrig, candidatesOrig,
poolOrig, candidatesOrig, 0)
// TODO Increase iteration count once overflow bug is fixed
// ref https://github.com/cosmos/cosmos-sdk/issues/753
for j := 0; j < 4; j++ {
poolMod, candidateMod, tokens, msg := randomOperation(r)(r, poolOrig, candidatesOrig[0])
candidatesMod := make([]Candidate, len(candidatesOrig))
copy(candidatesMod[:], candidatesOrig[:])
require.Equal(t, 1, len(candidatesOrig), "j %v", j)
require.Equal(t, 1, len(candidatesMod), "j %v", j)
candidatesMod[0] = candidateMod
assertInvariants(t, msg,
poolOrig, candidatesOrig,
poolMod, candidatesMod, tokens)
poolOrig = poolMod
candidatesOrig = candidatesMod
}
}
}
// run random operations in a random order on a random multi-candidate state, assert invariants hold
func TestMultiCandidateIntegrationInvariants(t *testing.T) {
r := rand.New(rand.NewSource(42))
for i := 0; i < 10; i++ {
poolOrig, candidatesOrig := randomSetup(r, 100)
assertInvariants(t, "no operation",
poolOrig, candidatesOrig,
poolOrig, candidatesOrig, 0)
// TODO Increase iteration count once overflow bug is fixed
// ref https://github.com/cosmos/cosmos-sdk/issues/753
for j := 0; j < 3; j++ {
index := int(r.Int31n(int32(len(candidatesOrig))))
poolMod, candidateMod, tokens, msg := randomOperation(r)(r, poolOrig, candidatesOrig[index])
candidatesMod := make([]Candidate, len(candidatesOrig))
copy(candidatesMod[:], candidatesOrig[:])
candidatesMod[index] = candidateMod
assertInvariants(t, msg,
poolOrig, candidatesOrig,
poolMod, candidatesMod, tokens)
poolOrig = poolMod
candidatesOrig = candidatesMod
}
}
}

View File

@ -11,7 +11,6 @@ import (
oldwire "github.com/tendermint/go-wire"
dbm "github.com/tendermint/tmlibs/db"
"github.com/cosmos/cosmos-sdk/examples/basecoin/types"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
@ -52,6 +51,31 @@ var (
emptyPubkey crypto.PubKey
)
// default params for testing
func defaultParams() Params {
return Params{
InflationRateChange: sdk.NewRat(13, 100),
InflationMax: sdk.NewRat(20, 100),
InflationMin: sdk.NewRat(7, 100),
GoalBonded: sdk.NewRat(67, 100),
MaxValidators: 100,
BondDenom: "fermion",
}
}
// initial pool for testing
func initialPool() Pool {
return Pool{
TotalSupply: 0,
BondedShares: sdk.ZeroRat,
UnbondedShares: sdk.ZeroRat,
BondedPool: 0,
UnbondedPool: 0,
InflationLastTime: 0,
Inflation: sdk.NewRat(7, 100),
}
}
// XXX reference the common declaration of this function
func subspace(prefix []byte) (start, end []byte) {
end = make([]byte, len(prefix))
@ -83,7 +107,7 @@ func makeTestCodec() *wire.Codec {
const accTypeApp = 0x1
var _ = oldwire.RegisterInterface(
struct{ sdk.Account }{},
oldwire.ConcreteType{&types.AppAccount{}, accTypeApp},
oldwire.ConcreteType{&auth.BaseAccount{}, accTypeApp},
)
cdc := wire.NewCodec()
@ -105,7 +129,7 @@ func paramsNoInflation() Params {
}
// hogpodge of all sorts of input required for testing
func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool, initCoins int64) (sdk.Context, sdk.AccountMapper, Keeper) {
func createTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, sdk.AccountMapper, Keeper) {
db := dbm.NewMemDB()
keyStake := sdk.NewKVStoreKey("stake")
keyMain := keyStake //sdk.NewKVStoreKey("main") //TODO fix multistore
@ -123,13 +147,14 @@ func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool, initCoins
)
ck := bank.NewCoinKeeper(accountMapper)
keeper := NewKeeper(ctx, cdc, keyStake, ck)
//params := paramsNoInflation()
params := keeper.GetParams(ctx)
keeper.setPool(ctx, initialPool())
keeper.setParams(ctx, defaultParams())
// fill all the addresses with some coins
for _, addr := range addrs {
ck.AddCoins(ctx, addr, sdk.Coins{{params.BondDenom, initCoins}})
ck.AddCoins(ctx, addr, sdk.Coins{
{keeper.GetParams(ctx).BondDenom, initCoins},
})
}
return ctx, accountMapper, keeper

View File

@ -6,38 +6,36 @@ import (
)
const (
hrsPerYear = 8766 // as defined by a julian year of 365.25 days
precision = 1000000000
hrsPerYr = 8766 // as defined by a julian year of 365.25 days
precision = 1000000000
)
var hrsPerYrRat = sdk.NewRat(hrsPerYear) // as defined by a julian year of 365.25 days
var hrsPerYrRat = sdk.NewRat(hrsPerYr) // as defined by a julian year of 365.25 days
// Tick - called at the end of every block
func (k Keeper) Tick(ctx sdk.Context) (change []*abci.Validator, err error) {
// retrieve params
func (k Keeper) Tick(ctx sdk.Context) (change []abci.Validator) {
p := k.GetPool(ctx)
height := ctx.BlockHeight()
// Process Validator Provisions
// XXX right now just process every 5 blocks, in new SDK make hourly
if p.InflationLastTime+5 <= height {
p.InflationLastTime = height
k.processProvisions(ctx)
blockTime := ctx.BlockHeader().Time // XXX assuming in seconds, confirm
if p.InflationLastTime+blockTime >= 3600 {
p.InflationLastTime = blockTime
p = k.processProvisions(ctx)
}
newVals := k.GetValidators(ctx)
// save the params
k.setPool(ctx, p)
// XXX determine change from old validators, set to change
_ = newVals
return change, nil
change = k.getAccUpdateValidators(ctx)
return
}
// process provisions for an hour period
func (k Keeper) processProvisions(ctx sdk.Context) {
func (k Keeper) processProvisions(ctx sdk.Context) Pool {
pool := k.GetPool(ctx)
pool.Inflation = k.nextInflation(ctx).Round(precision)
pool.Inflation = k.nextInflation(ctx)
// Because the validators hold a relative bonded share (`GlobalStakeShare`), when
// more bonded tokens are added proportionally to all validators the only term
@ -46,9 +44,7 @@ func (k Keeper) processProvisions(ctx sdk.Context) {
provisions := pool.Inflation.Mul(sdk.NewRat(pool.TotalSupply)).Quo(hrsPerYrRat).Evaluate()
pool.BondedPool += provisions
pool.TotalSupply += provisions
// save the params
k.setPool(ctx, pool)
return pool
}
// get the next inflation rate for the hour
@ -75,5 +71,5 @@ func (k Keeper) nextInflation(ctx sdk.Context) (inflation sdk.Rat) {
inflation = params.InflationMin
}
return
return inflation.Round(precision)
}

View File

@ -1,116 +1,134 @@
package stake
//import (
//"testing"
import (
"testing"
//sdk "github.com/cosmos/cosmos-sdk/types"
//"github.com/stretchr/testify/assert"
//)
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
//func TestGetInflation(t *testing.T) {
//ctx, _, keeper := createTestInput(t, nil, false, 0)
//params := defaultParams()
//keeper.setParams(ctx, params)
//gs := keeper.GetPool(ctx)
func TestGetInflation(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
params := keeper.GetParams(ctx)
hrsPerYrRat := sdk.NewRat(hrsPerYr)
//// Governing Mechanism:
//// bondedRatio = BondedPool / TotalSupply
//// inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange
// Governing Mechanism:
// bondedRatio = BondedPool / TotalSupply
// inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange
//tests := []struct {
//setBondedPool, setTotalSupply int64
//setInflation, expectedChange sdk.Rat
//}{
//// with 0% bonded atom supply the inflation should increase by InflationRateChange
//{0, 0, sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYr)},
tests := []struct {
name string
setBondedPool, setTotalSupply int64
setInflation, expectedChange sdk.Rat
}{
// with 0% bonded atom supply the inflation should increase by InflationRateChange
{"test 1", 0, 0, sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYrRat).Round(precision)},
//// 100% bonded, starting at 20% inflation and being reduced
//{1, 1, sdk.NewRat(20, 100), sdk.OneRat.Sub(sdk.OneRat.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)},
// 100% bonded, starting at 20% inflation and being reduced
// (1 - (1/0.67))*(0.13/8667)
{"test 2", 1, 1, sdk.NewRat(20, 100),
sdk.OneRat.Sub(sdk.OneRat.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)},
//// 50% bonded, starting at 10% inflation and being increased
//{1, 2, sdk.NewRat(10, 100), sdk.OneRat.Sub(sdk.NewRat(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)},
// 50% bonded, starting at 10% inflation and being increased
{"test 3", 1, 2, sdk.NewRat(10, 100),
sdk.OneRat.Sub(sdk.NewRat(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)},
//// test 7% minimum stop (testing with 100% bonded)
//{1, 1, sdk.NewRat(7, 100), sdk.ZeroRat},
//{1, 1, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000)},
// test 7% minimum stop (testing with 100% bonded)
{"test 4", 1, 1, sdk.NewRat(7, 100), sdk.ZeroRat},
{"test 5", 1, 1, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000).Round(precision)},
//// test 20% maximum stop (testing with 0% bonded)
//{0, 0, sdk.NewRat(20, 100), sdk.ZeroRat},
//{0, 0, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000)},
// test 20% maximum stop (testing with 0% bonded)
{"test 6", 0, 0, sdk.NewRat(20, 100), sdk.ZeroRat},
{"test 7", 0, 0, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000).Round(precision)},
//// perfect balance shouldn't change inflation
//{67, 100, sdk.NewRat(15, 100), sdk.ZeroRat},
//}
//for _, tc := range tests {
//gs.BondedPool, p.TotalSupply = tc.setBondedPool, tc.setTotalSupply
//gs.Inflation = tc.setInflation
// perfect balance shouldn't change inflation
{"test 8", 67, 100, sdk.NewRat(15, 100), sdk.ZeroRat},
}
for _, tc := range tests {
pool.BondedPool, pool.TotalSupply = tc.setBondedPool, tc.setTotalSupply
pool.Inflation = tc.setInflation
keeper.setPool(ctx, pool)
//inflation := nextInflation(gs, params)
//diffInflation := inflation.Sub(tc.setInflation)
inflation := keeper.nextInflation(ctx)
diffInflation := inflation.Sub(tc.setInflation)
//assert.True(t, diffInflation.Equal(tc.expectedChange),
//"%v, %v", diffInflation, tc.expectedChange)
//}
//}
assert.True(t, diffInflation.Equal(tc.expectedChange),
"Name: %v\nDiff: %v\nExpected: %v\n", tc.name, diffInflation, tc.expectedChange)
}
}
//func TestProcessProvisions(t *testing.T) {
//ctx, _, keeper := createTestInput(t, nil, false, 0)
//params := defaultParams()
//keeper.setParams(ctx, params)
//gs := keeper.GetPool(ctx)
func TestProcessProvisions(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 0)
params := defaultParams()
keeper.setParams(ctx, params)
pool := keeper.GetPool(ctx)
//// create some candidates some bonded, some unbonded
//candidates := candidatesFromAddrsEmpty(addrs)
//for i, candidate := range candidates {
//if i < 5 {
//candidate.Status = Bonded
//}
//mintedTokens := int64((i + 1) * 10000000)
//gs.TotalSupply += mintedTokens
//keeper.candidateAddTokens(ctx, candidate, mintedTokens)
//keeper.setCandidate(ctx, candidate)
//}
//var totalSupply int64 = 550000000
//var bondedShares int64 = 150000000
//var unbondedShares int64 = 400000000
// create some candidates some bonded, some unbonded
candidates := make([]Candidate, 10)
for i := 0; i < 10; i++ {
c := Candidate{
Status: Unbonded,
PubKey: pks[i],
Address: addrs[i],
Assets: sdk.NewRat(0),
Liabilities: sdk.NewRat(0),
}
if i < 5 {
c.Status = Bonded
}
mintedTokens := int64((i + 1) * 10000000)
pool.TotalSupply += mintedTokens
pool, c, _ = pool.candidateAddTokens(c, mintedTokens)
//// initial bonded ratio ~ 27%
//assert.True(t, p.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", p.bondedRatio())
keeper.setCandidate(ctx, c)
candidates[i] = c
}
keeper.setPool(ctx, pool)
var totalSupply int64 = 550000000
var bondedShares int64 = 150000000
var unbondedShares int64 = 400000000
assert.Equal(t, totalSupply, pool.TotalSupply)
assert.Equal(t, bondedShares, pool.BondedPool)
assert.Equal(t, unbondedShares, pool.UnbondedPool)
//// Supplies
//assert.Equal(t, totalSupply, p.TotalSupply)
//assert.Equal(t, bondedShares, p.BondedPool)
//assert.Equal(t, unbondedShares, p.UnbondedPool)
// initial bonded ratio ~ 27%
assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", pool.bondedRatio())
//// test the value of candidate shares
//assert.True(t, p.bondedShareExRate().Equal(sdk.OneRat), "%v", p.bondedShareExRate())
// test the value of candidate shares
assert.True(t, pool.bondedShareExRate().Equal(sdk.OneRat), "%v", pool.bondedShareExRate())
//initialSupply := p.TotalSupply
//initialUnbonded := p.TotalSupply - p.BondedPool
initialSupply := pool.TotalSupply
initialUnbonded := pool.TotalSupply - pool.BondedPool
//// process the provisions a year
//for hr := 0; hr < 8766; hr++ {
//expInflation := nextInflation(gs, params).Round(1000000000)
//expProvisions := (expInflation.Mul(sdk.NewRat(gs.TotalSupply)).Quo(hrsPerYr)).Evaluate()
//startBondedPool := p.BondedPool
//startTotalSupply := p.TotalSupply
//processProvisions(ctx, keeper, p, params)
//assert.Equal(t, startBondedPool+expProvisions, p.BondedPool)
//assert.Equal(t, startTotalSupply+expProvisions, p.TotalSupply)
//}
//assert.NotEqual(t, initialSupply, p.TotalSupply)
//assert.Equal(t, initialUnbonded, p.UnbondedPool)
////panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", p.TotalSupply, p.BondedPool, p.TotalSupply-gs.BondedPool))
// process the provisions a year
for hr := 0; hr < 8766; hr++ {
pool := keeper.GetPool(ctx)
expInflation := keeper.nextInflation(ctx).Round(1000000000)
expProvisions := (expInflation.Mul(sdk.NewRat(pool.TotalSupply)).Quo(hrsPerYrRat)).Evaluate()
startBondedPool := pool.BondedPool
startTotalSupply := pool.TotalSupply
pool = keeper.processProvisions(ctx)
keeper.setPool(ctx, pool)
//fmt.Printf("hr %v, startBondedPool %v, expProvisions %v, pool.BondedPool %v\n", hr, startBondedPool, expProvisions, pool.BondedPool)
require.Equal(t, startBondedPool+expProvisions, pool.BondedPool, "hr %v", hr)
require.Equal(t, startTotalSupply+expProvisions, pool.TotalSupply)
}
pool = keeper.GetPool(ctx)
assert.NotEqual(t, initialSupply, pool.TotalSupply)
assert.Equal(t, initialUnbonded, pool.UnbondedPool)
//panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", p.TotalSupply, p.BondedPool, pool.TotalSupply-pool.BondedPool))
//// initial bonded ratio ~ 35% ~ 30% increase for bonded holders
//assert.True(t, p.bondedRatio().Equal(sdk.NewRat(105906511, 305906511)), "%v", p.bondedRatio())
// initial bonded ratio ~ from 27% to 40% increase for bonded holders ownership of total supply
assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(271734723, 671734723)), "%v", pool.bondedRatio())
//// global supply
//assert.Equal(t, int64(611813022), p.TotalSupply)
//assert.Equal(t, int64(211813022), p.BondedPool)
//assert.Equal(t, unbondedShares, p.UnbondedPool)
// global supply
assert.Equal(t, int64(671734723), pool.TotalSupply)
assert.Equal(t, int64(271734723), pool.BondedPool)
assert.Equal(t, unbondedShares, pool.UnbondedPool)
//// test the value of candidate shares
//assert.True(t, p.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(211813022)), "%v", p.bondedShareExRate())
// test the value of candidate shares
assert.True(t, pool.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(271734723)), "%v", pool.bondedShareExRate())
//}
}

View File

@ -2,6 +2,8 @@ package stake
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
)
@ -16,19 +18,6 @@ type Params struct {
BondDenom string `json:"bond_denom"` // bondable coin denomination
}
// XXX do we want to allow for default params even or do we want to enforce that you
// need to be explicit about defining all params in genesis?
func defaultParams() Params {
return Params{
InflationRateChange: sdk.NewRat(13, 100),
InflationMax: sdk.NewRat(20, 100),
InflationMin: sdk.NewRat(7, 100),
GoalBonded: sdk.NewRat(67, 100),
MaxValidators: 100,
BondDenom: "fermion",
}
}
//_________________________________________________________________________
// Pool - dynamic parameters of the current state
@ -42,42 +31,10 @@ type Pool struct {
Inflation sdk.Rat `json:"inflation"` // current annual inflation rate
}
// XXX define globalstate interface?
func initialPool() Pool {
return Pool{
TotalSupply: 0,
BondedShares: sdk.ZeroRat,
UnbondedShares: sdk.ZeroRat,
BondedPool: 0,
UnbondedPool: 0,
InflationLastTime: 0,
Inflation: sdk.NewRat(7, 100),
}
}
// get the bond ratio of the global state
func (p Pool) bondedRatio() sdk.Rat {
if p.TotalSupply > 0 {
return sdk.NewRat(p.BondedPool, p.TotalSupply)
}
return sdk.ZeroRat
}
// get the exchange rate of bonded token per issued share
func (p Pool) bondedShareExRate() sdk.Rat {
if p.BondedShares.IsZero() {
return sdk.OneRat
}
return sdk.NewRat(p.BondedPool).Quo(p.BondedShares)
}
// get the exchange rate of unbonded tokens held in candidates per issued share
func (p Pool) unbondedShareExRate() sdk.Rat {
if p.UnbondedShares.IsZero() {
return sdk.OneRat
}
return sdk.NewRat(p.UnbondedPool).Quo(p.UnbondedShares)
// GenesisState - all staking state that must be provided at genesis
type GenesisState struct {
Pool Pool `json:"pool"`
Params Params `json:"params"`
}
//_______________________________________________________________________________________________________
@ -149,8 +106,9 @@ func (c Candidate) delegatorShareExRate() sdk.Rat {
// Should only be called when the Candidate qualifies as a validator.
func (c Candidate) validator() Validator {
return Validator{
Address: c.Address, // XXX !!!
VotingPower: c.Assets,
Address: c.Address,
PubKey: c.PubKey,
Power: c.Assets,
}
}
@ -161,23 +119,35 @@ func (c Candidate) validator() Validator {
// Validator is one of the top Candidates
type Validator struct {
Address sdk.Address `json:"address"` // Address of validator
VotingPower sdk.Rat `json:"voting_power"` // Voting power if considered a validator
Address sdk.Address `json:"address"`
PubKey crypto.PubKey `json:"pub_key"`
Power sdk.Rat `json:"voting_power"`
}
// ABCIValidator - Get the validator from a bond value
/* TODO
func (v Validator) ABCIValidator() (*abci.Validator, error) {
pkBytes, err := wire.MarshalBinary(v.PubKey)
// abci validator from stake validator type
func (v Validator) abciValidator(cdc *wire.Codec) abci.Validator {
pkBytes, err := cdc.MarshalBinary(v.PubKey)
if err != nil {
return nil, err
panic(err)
}
return &abci.Validator{
return abci.Validator{
PubKey: pkBytes,
Power: v.VotingPower.Evaluate(),
}, nil
Power: v.Power.Evaluate(),
}
}
// abci validator from stake validator type
// with zero power used for validator updates
func (v Validator) abciValidatorZero(cdc *wire.Codec) abci.Validator {
pkBytes, err := cdc.MarshalBinary(v.PubKey)
if err != nil {
panic(err)
}
return abci.Validator{
PubKey: pkBytes,
Power: 0,
}
}
*/
//_________________________________________________________________________