diff --git a/CHANGELOG.md b/CHANGELOG.md index f21b1ea68b9a..7ced59c5ffb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Gopkg.lock b/Gopkg.lock index 3773902977b9..2de9e117ef10 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -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 diff --git a/Gopkg.toml b/Gopkg.toml index e3df3d6946c7..e9774e1c1512 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -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] diff --git a/Makefile b/Makefile index 023e4f8c627b..b5177621aadf 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index ecafd1e280c6..63a40aaa7f94 100644 --- a/README.md +++ b/README.md @@ -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). \ No newline at end of file diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 8589922cd2c4..dfbb821fe81d 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -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) } diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 526500559da7..1658c591d679 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -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) diff --git a/baseapp/router.go b/baseapp/router.go index 17be883092eb..83efe5dad1ba 100644 --- a/baseapp/router.go +++ b/baseapp/router.go @@ -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 -} diff --git a/client/context/viper.go b/client/context/viper.go new file mode 100644 index 000000000000..750a37c616cc --- /dev/null +++ b/client/context/viper.go @@ -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, + } +} diff --git a/client/core/context.go b/client/core/context.go new file mode 100644 index 000000000000..3d7f400a8e12 --- /dev/null +++ b/client/core/context.go @@ -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 +} diff --git a/client/builder/builder.go b/client/core/core.go similarity index 66% rename from client/builder/builder.go rename to client/core/core.go index ce8ad04958d3..a5c7b340c7ec 100644 --- a/client/builder/builder.go +++ b/client/core/core.go @@ -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 +} diff --git a/client/helpers.go b/client/helpers.go deleted file mode 100644 index f383b95f7ddc..000000000000 --- a/client/helpers.go +++ /dev/null @@ -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 -} diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index f5a2f37be88b..48cc35a8e6c3 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -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 + } + } +} diff --git a/client/rpc/block.go b/client/rpc/block.go index 7f197051a94f..f42a15bc2e98 100644 --- a/client/rpc/block.go +++ b/client/rpc/block.go @@ -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 } diff --git a/client/rpc/status.go b/client/rpc/status.go index e5da94869255..8ea8a5ad6aef 100644 --- a/client/rpc/status.go +++ b/client/rpc/status.go @@ -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 } diff --git a/client/rpc/validators.go b/client/rpc/validators.go index 15c3230e3e16..9bf1505dbf24 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -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 } diff --git a/client/tx/broadcast.go b/client/tx/broadcast.go index b9367645fefa..998e2b0f1700 100644 --- a/client/tx/broadcast.go +++ b/client/tx/broadcast.go @@ -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())) diff --git a/client/tx/query.go b/client/tx/query.go index 7c8c4d124fbe..63ab2ff04b6d 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -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 } diff --git a/client/tx/search.go b/client/tx/search.go index 2790750ebe87..df3be507734c 100644 --- a/client/tx/search.go +++ b/client/tx/search.go @@ -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 } diff --git a/examples/gaia/gaiacli/client.go b/cmd/gaiacli/client.go similarity index 100% rename from examples/gaia/gaiacli/client.go rename to cmd/gaiacli/client.go diff --git a/examples/gaia/gaiacli/key.go b/cmd/gaiacli/key.go similarity index 100% rename from examples/gaia/gaiacli/key.go rename to cmd/gaiacli/key.go diff --git a/examples/gaia/gaiacli/main.go b/cmd/gaiacli/main.go similarity index 83% rename from examples/gaia/gaiacli/main.go rename to cmd/gaiacli/main.go index dce125acbb0d..0eb29c19cedd 100644 --- a/examples/gaia/gaiacli/main.go +++ b/cmd/gaiacli/main.go @@ -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() } diff --git a/cmd/gaiad/main.go b/cmd/gaiad/main.go new file mode 100644 index 000000000000..e44bc73eacbd --- /dev/null +++ b/cmd/gaiad/main.go @@ -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() +} diff --git a/examples/basecoin/LICENSE b/examples/basecoin/LICENSE deleted file mode 100644 index a3811d788812..000000000000 --- a/examples/basecoin/LICENSE +++ /dev/null @@ -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. diff --git a/examples/basecoin/Makefile b/examples/basecoin/Makefile deleted file mode 100644 index e0cf14caa684..000000000000 --- a/examples/basecoin/Makefile +++ /dev/null @@ -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 diff --git a/examples/basecoin/README.md b/examples/basecoin/README.md deleted file mode 100644 index e6de9480db8f..000000000000 --- a/examples/basecoin/README.md +++ /dev/null @@ -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. - diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index 7c737566a7c5..caae1684660c 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -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) diff --git a/examples/basecoin/app/app_test.go b/examples/basecoin/app/app_test.go index 4958240b5f52..65aff3af99fc 100644 --- a/examples/basecoin/app/app_test.go +++ b/examples/basecoin/app/app_test.go @@ -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) diff --git a/examples/basecoin/cmd/basecli/main.go b/examples/basecoin/cmd/basecli/main.go index a0152aee99fa..6271adcecd5d 100644 --- a/examples/basecoin/cmd/basecli/main.go +++ b/examples/basecoin/cmd/basecli/main.go @@ -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() } diff --git a/examples/basecoin/cmd/basecoind/main.go b/examples/basecoin/cmd/basecoind/main.go index 39566767145e..34e45bf312b7 100644 --- a/examples/basecoin/cmd/basecoind/main.go +++ b/examples/basecoin/cmd/basecoind/main.go @@ -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() } diff --git a/examples/basecoin/types/account.go b/examples/basecoin/types/account.go index f34113fc65a8..35b37c7b2f4f 100644 --- a/examples/basecoin/types/account.go +++ b/examples/basecoin/types/account.go @@ -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, diff --git a/examples/democoin/LICENSE b/examples/democoin/LICENSE deleted file mode 100644 index 1697a744365d..000000000000 --- a/examples/democoin/LICENSE +++ /dev/null @@ -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. diff --git a/examples/democoin/Makefile b/examples/democoin/Makefile deleted file mode 100644 index 067d03e9bda0..000000000000 --- a/examples/democoin/Makefile +++ /dev/null @@ -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 diff --git a/examples/democoin/README.md b/examples/democoin/README.md deleted file mode 100644 index fe65abda4b68..000000000000 --- a/examples/democoin/README.md +++ /dev/null @@ -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. - diff --git a/examples/democoin/app/app.go b/examples/democoin/app/app.go index a15bea1dd239..2ee79bd5bf47 100644 --- a/examples/democoin/app/app.go +++ b/examples/democoin/app/app.go @@ -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() + if err != nil { + panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 + // return sdk.ErrGenesisParse("").TraceCause(err, "") + } + app.accountMapper.SetAccount(ctx, acc) + } - for _, gacc := range genesisState.Accounts { - acc, err := gacc.ToAppAccount() + // 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{} } diff --git a/examples/democoin/app/app_test.go b/examples/democoin/app/app_test.go index bf2ddc232f1e..1cc56bd6bf7f 100644 --- a/examples/democoin/app/app_test.go +++ b/examples/democoin/app/app_test.go @@ -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() diff --git a/examples/democoin/cmd/democli/main.go b/examples/democoin/cmd/democli/main.go index 602e5478e94b..2b71db710d6f 100644 --- a/examples/democoin/cmd/democli/main.go +++ b/examples/democoin/cmd/democli/main.go @@ -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() } diff --git a/examples/democoin/cmd/democoind/main.go b/examples/democoin/cmd/democoind/main.go index d9421954c90d..df94e2c323f9 100644 --- a/examples/democoin/cmd/democoind/main.go +++ b/examples/democoin/cmd/democoind/main.go @@ -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() } diff --git a/examples/democoin/types/account.go b/examples/democoin/types/account.go index f34113fc65a8..b5d5a0d0343b 100644 --- a/examples/democoin/types/account.go +++ b/examples/democoin/types/account.go @@ -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, diff --git a/examples/democoin/x/cool/commands/tx.go b/examples/democoin/x/cool/commands/tx.go index ab817309cfab..8deaac405d9a 100644 --- a/examples/democoin/x/cool/commands/tx.go +++ b/examples/democoin/x/cool/commands/tx.go @@ -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 } diff --git a/examples/democoin/x/cool/keeper.go b/examples/democoin/x/cool/keeper.go index 1bf342fdc2a7..0a4fc81e1c98 100644 --- a/examples/democoin/x/cool/keeper.go +++ b/examples/democoin/x/cool/keeper.go @@ -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 } diff --git a/examples/democoin/x/cool/types.go b/examples/democoin/x/cool/types.go index a3fa6ca48e7c..e24c363acec6 100644 --- a/examples/democoin/x/cool/types.go +++ b/examples/democoin/x/cool/types.go @@ -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{ diff --git a/examples/democoin/x/pow/commands/tx.go b/examples/democoin/x/pow/commands/tx.go new file mode 100644 index 000000000000..badbe39092c2 --- /dev/null +++ b/examples/democoin/x/pow/commands/tx.go @@ -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 + }, + } +} diff --git a/examples/democoin/x/pow/errors.go b/examples/democoin/x/pow/errors.go new file mode 100644 index 000000000000..b44eb93d6d54 --- /dev/null +++ b/examples/democoin/x/pow/errors.go @@ -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) +} diff --git a/examples/democoin/x/pow/handler.go b/examples/democoin/x/pow/handler.go new file mode 100644 index 000000000000..d1a691139d43 --- /dev/null +++ b/examples/democoin/x/pow/handler.go @@ -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{} +} diff --git a/examples/democoin/x/pow/handler_test.go b/examples/democoin/x/pow/handler_test.go new file mode 100644 index 000000000000..2de2853713ba --- /dev/null +++ b/examples/democoin/x/pow/handler_test.go @@ -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{}) +} diff --git a/examples/democoin/x/pow/keeper.go b/examples/democoin/x/pow/keeper.go new file mode 100644 index 000000000000..73558632c424 --- /dev/null +++ b/examples/democoin/x/pow/keeper.go @@ -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 +} diff --git a/examples/democoin/x/pow/keeper_test.go b/examples/democoin/x/pow/keeper_test.go new file mode 100644 index 000000000000..6e0d526496e9 --- /dev/null +++ b/examples/democoin/x/pow/keeper_test.go @@ -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)) +} diff --git a/examples/democoin/x/pow/mine.go b/examples/democoin/x/pow/mine.go new file mode 100644 index 000000000000..ff2264aaa775 --- /dev/null +++ b/examples/democoin/x/pow/mine.go @@ -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 + } + } +} diff --git a/examples/democoin/x/pow/types.go b/examples/democoin/x/pow/types.go new file mode 100644 index 000000000000..ea368c306306 --- /dev/null +++ b/examples/democoin/x/pow/types.go @@ -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 +} diff --git a/examples/democoin/x/pow/types_test.go b/examples/democoin/x/pow/types_test.go new file mode 100644 index 000000000000..34ab8914eb85 --- /dev/null +++ b/examples/democoin/x/pow/types_test.go @@ -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]") +} diff --git a/examples/gaia/README.md b/examples/gaia/README.md deleted file mode 100644 index 485af236f051..000000000000 --- a/examples/gaia/README.md +++ /dev/null @@ -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. diff --git a/examples/gaia/gaiad/main.go b/examples/gaia/gaiad/main.go deleted file mode 100644 index 70a44d8cbc11..000000000000 --- a/examples/gaia/gaiad/main.go +++ /dev/null @@ -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() -} diff --git a/examples/kvstore/main.go b/examples/kvstore/main.go index 0cd3d08b898a..0d80826ed17e 100644 --- a/examples/kvstore/main.go +++ b/examples/kvstore/main.go @@ -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 { diff --git a/mock/app.go b/mock/app.go index 20863dd99334..631cc3c314fd 100644 --- a/mock/app.go +++ b/mock/app.go @@ -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 } diff --git a/mock/app_test.go b/mock/app_test.go index 47db93e1c5f6..18449631c626 100644 --- a/mock/app_test.go +++ b/mock/app_test.go @@ -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) diff --git a/publish/print_txs.go b/publish/print_txs.go deleted file mode 100644 index 55cd2b488e0d..000000000000 --- a/publish/print_txs.go +++ /dev/null @@ -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() -} -*/ diff --git a/server/init.go b/server/init.go index ab4ffcb7a472..688bc38e3791 100644 --- a/server/init.go +++ b/server/init.go @@ -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 +} diff --git a/server/init_test.go b/server/init_test.go index 0abb18040082..19e6695193ee 100644 --- a/server/init_test.go +++ b/server/init_test.go @@ -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) } diff --git a/server/key.go b/server/key.go deleted file mode 100644 index aed1f9d1ff32..000000000000 --- a/server/key.go +++ /dev/null @@ -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 -} diff --git a/server/reset.go b/server/reset.go deleted file mode 100644 index 5c70bbdace14..000000000000 --- a/server/reset.go +++ /dev/null @@ -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 -} diff --git a/server/show_node_id.go b/server/show_node_id.go deleted file mode 100644 index f8d9f3f81f6b..000000000000 --- a/server/show_node_id.go +++ /dev/null @@ -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 -} diff --git a/server/show_validator.go b/server/show_validator.go deleted file mode 100644 index d2c7705d6cbd..000000000000 --- a/server/show_validator.go +++ /dev/null @@ -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 -} diff --git a/server/start.go b/server/start.go index c8d9fc4d1d47..6aee5d316cfd 100644 --- a/server/start.go +++ b/server/start.go @@ -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 } diff --git a/server/start_test.go b/server/start_test.go index 2657c5223483..ec6c886b116f 100644 --- a/server/start_test.go +++ b/server/start_test.go @@ -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 - - //a, _ := startCmd.Flags().GetString(flagAddress) - //panic(a) + timeout := time.Duration(5) * time.Second - RunOrTimeout(startCmd, timeout, t) + close(RunOrTimeout(startCmd, timeout, t)) } -*/ diff --git a/server/test_helpers.go b/server/test_helpers.go index 103af7c33194..f226ba1b1fa1 100644 --- a/server/test_helpers.go +++ b/server/test_helpers.go @@ -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 diff --git a/server/tm_cmds.go b/server/tm_cmds.go new file mode 100644 index 000000000000..3e0aefa941b2 --- /dev/null +++ b/server/tm_cmds.go @@ -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 +} diff --git a/server/util.go b/server/util.go new file mode 100644 index 000000000000..95dc4b30d32e --- /dev/null +++ b/server/util.go @@ -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, + ) +} diff --git a/store/iavlstore.go b/store/iavlstore.go index 96110c59c967..e736fbda2ed9 100644 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -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 } diff --git a/store/rootmultistore.go b/store/rootmultistore.go index a39c35813ffb..9be28967c2d8 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -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 diff --git a/types/coin.go b/types/coin.go index d19d4d854526..ab7d863c15f1 100644 --- a/types/coin.go +++ b/types/coin.go @@ -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 diff --git a/types/errors.go b/types/errors.go index 1115d39376ea..4874c436288f 100644 --- a/types/errors.go +++ b/types/errors.go @@ -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(), + } +} diff --git a/types/result.go b/types/result.go index c1afec00ce03..412a9778defc 100644 --- a/types/result.go +++ b/types/result.go @@ -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, - } -} diff --git a/version/command.go b/version/command.go index c21ebb937933..e28cbe57e9f8 100644 --- a/version/command.go +++ b/version/command.go @@ -19,7 +19,7 @@ var ( func getVersion() string { v := Version if GitCommit != "" { - v = v + " " + GitCommit + v = v + "-" + GitCommit } return v } diff --git a/version/version.go b/version/version.go index 426d904ccdf5..324db29dc929 100644 --- a/version/version.go +++ b/version/version.go @@ -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 = "" diff --git a/x/auth/commands/account.go b/x/auth/commands/account.go index 02af73f167ca..b87b957699e8 100644 --- a/x/auth/commands/account.go +++ b/x/auth/commands/account.go @@ -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 } diff --git a/x/auth/rest/query.go b/x/auth/rest/query.go index 22c364ccda0f..5430a77ff249 100644 --- a/x/auth/rest/query.go +++ b/x/auth/rest/query.go @@ -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()))) diff --git a/x/bank/commands/sendtx.go b/x/bank/commands/sendtx.go index 5619b4d0f3a4..ee4625eac292 100644 --- a/x/bank/commands/sendtx.go +++ b/x/bank/commands/sendtx.go @@ -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 } diff --git a/x/bank/msgs.go b/x/bank/msgs.go index 05f824eba572..15822eed75fb 100644 --- a/x/bank/msgs.go +++ b/x/bank/msgs.go @@ -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 diff --git a/x/bank/msgs_test.go b/x/bank/msgs_test.go index bbfdc62ffd39..b158405d96a6 100644 --- a/x/bank/msgs_test.go +++ b/x/bank/msgs_test.go @@ -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) { diff --git a/x/bank/rest/sendtx.go b/x/bank/rest/sendtx.go index 85b9dc4d50a4..b1f8516f335d 100644 --- a/x/bank/rest/sendtx.go +++ b/x/bank/rest/sendtx.go @@ -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())) diff --git a/x/ibc/commands/README.md b/x/ibc/commands/README.md index 11c46c3261d0..ed9652fa341c 100644 --- a/x/ibc/commands/README.md +++ b/x/ibc/commands/README.md @@ -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 -```bash -basecli transfer --name keyname --to address_of_destination --amount 10mycoin --chain test-chain-AAAAAA --chain-id AAAAAA + +> 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": "" +} + +``` + +## 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": "" +} ``` -The id of the chain can be found in `$HOME/.basecoind/config/genesis.json` +## Relay IBC packets -## relay +```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": "" +} -```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 ``` diff --git a/x/ibc/commands/ibctx.go b/x/ibc/commands/ibctx.go index e0186b71755d..689a98318122 100644 --- a/x/ibc/commands/ibctx.go +++ b/x/ibc/commands/ibctx.go @@ -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{ diff --git a/x/ibc/commands/relay.go b/x/ibc/commands/relay.go index 9f6647ba5bb2..d652e306fd1c 100644 --- a/x/ibc/commands/relay.go +++ b/x/ibc/commands/relay.go @@ -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) } diff --git a/x/ibc/rest/transfer.go b/x/ibc/rest/transfer.go index f47159160e84..fceac55677fe 100644 --- a/x/ibc/rest/transfer.go +++ b/x/ibc/rest/transfer.go @@ -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())) diff --git a/x/simplestake/commands/commands.go b/x/simplestake/commands/commands.go index 19d6cddbf992..ce89801d8822 100644 --- a/x/simplestake/commands/commands.go +++ b/x/simplestake/commands/commands.go @@ -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 } diff --git a/x/stake/commands/query.go b/x/stake/commands/query.go index ed436305c3df..7bc6a8aa9411 100644 --- a/x/stake/commands/query.go +++ b/x/stake/commands/query.go @@ -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 } diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go index 90b289de79c3..d1dec6dce990 100644 --- a/x/stake/commands/tx.go +++ b/x/stake/commands/tx.go @@ -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 } diff --git a/x/stake/errors.go b/x/stake/errors.go index bd1992959e2b..e5b3e0cb3c2c 100644 --- a/x/stake/errors.go +++ b/x/stake/errors.go @@ -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") } diff --git a/x/stake/handler.go b/x/stake/handler.go index 7449141aa68a..2f351a608ce5 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -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) - 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}) + pool := k.GetPool(ctx) + _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{bondAmt}) 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 +//} diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index b6953df5c91a..1f0bc6415bea 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -1,248 +1,308 @@ package stake -//import ( -//"strconv" -//"testing" - -//"github.com/stretchr/testify/assert" -//"github.com/stretchr/testify/require" - -//crypto "github.com/tendermint/go-crypto" - -//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 newTestMsgDelegate(amt int64, delegatorAddr, candidateAddr sdk.Address) 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") - -//// 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") - -//// 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") -//} - -//func TestIncrementsMsgDelegate(t *testing.T) { -//_, _, mapper, deliverer := createTestInput(t, addrs[0], false, 1000) - -//// 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, - -//// 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) - -////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 -//} -//} - -//func TestIncrementsMsgUnbond(t *testing.T) { -//_, _, mapper, deliverer := createTestInput(t, addrs[0], false, 0) - -//// 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) - -//// 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) - -////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 - -//assert.Equal(t, expectedBond, gotBonded, "%v, %v", expectedBond, gotBonded) -////assert.Equal(t, expectedSender, gotSender, "%v, %v", expectedSender, gotSender) //XXX fix -//} - -//// 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") -//} - -//leftBonded := initBond - unbondShares*int64(nUnbonds) - -//// 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") - -//// 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") -//} - -//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]} - -//// 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) - -////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) -//} - -//// 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) - -////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)) - -//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) -//} -//} - -//func TestMultipleMsgDelegate(t *testing.T) { -//sender, delegators := addrs[0], addrs[1:] -//_, _, mapper, deliverer := createTestInput(t, addrs[0], false, 1000) - -////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) - -//// 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) - -////Check that the account is bonded -//bond := mapper.getDelegatorBond(delegator, sender) -//assert.NotNil(t, bond, "expected delegatee bond %d to exist", bond) -//} - -//// 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) - -////Check that the account is unbonded -//bond := mapper.getDelegatorBond(delegator, sender) -//assert.Nil(t, bond, "expected delegatee bond %d to be nil", bond) -//} -//} - -//func TestVoidCandidacy(t *testing.T) { -//sender, delegator := addrs[0], addrs[1] -//_, _, _, deliverer := createTestInput(t, addrs[0], false, 1000) - -//// create the candidate -//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) -//got := deliverer.declareCandidacy(msgDeclareCandidacy) -//require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") - -//// bond a delegator -//msgDelegate := newTestMsgDelegate(10, addrs[0]) -//deliverer.sender = delegator -//got = deliverer.delegate(msgDelegate) -//require.NoError(t, got, "expected ok, got %v", got) - -//// 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") - -//// 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) - -//// test that the delegator can still withdraw their bonds -//got = deliverer.unbond(msgUndelegate) -//require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") - -//// verify that the pubkey can now be reused -//got = deliverer.declareCandidacy(msgDeclareCandidacy) -//assert.NoError(t, got, "expected ok, got %v", got) -//} +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + crypto "github.com/tendermint/go-crypto" + + 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 newTestMsgDelegate(delegatorAddr, candidateAddr sdk.Address, amt int64) MsgDelegate { + return MsgDelegate{ + DelegatorAddr: delegatorAddr, + CandidateAddr: candidateAddr, + Bond: sdk.Coin{"fermion", amt}, + } +} + +//______________________________________________________________________ + +func TestDuplicatesMsgDeclareCandidacy(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 1000) + + 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) + + // 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) { + initBond := int64(1000) + ctx, accMapper, keeper := createTestInput(t, false, initBond) + params := keeper.GetParams(ctx) + + bondAmount := int64(10) + candidateAddr, delegatorAddr := addrs[0], addrs[1] + + // 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) + + candidate, found := keeper.GetCandidate(ctx, candidateAddr) + require.True(t, found) + assert.Equal(t, bondAmount, candidate.Liabilities.Evaluate()) + assert.Equal(t, bondAmount, candidate.Assets.Evaluate()) + + // 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) + + //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) + + expBond := int64(i+1) * bondAmount + expLiabilities := int64(i+2) * bondAmount // (1 self delegation) + expDelegatorAcc := initBond - expBond + + gotBond := bond.Shares.Evaluate() + gotLiabilities := candidate.Liabilities.Evaluate() + gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) + + 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 TestIncrementsMsgUnbond(t *testing.T) { + initBond := int64(1000) + ctx, accMapper, keeper := createTestInput(t, false, initBond) + params := keeper.GetParams(ctx) + + // declare candidacy, delegate + candidateAddr, delegatorAddr := addrs[0], addrs[1] + + 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) + + msgDelegate := newTestMsgDelegate(delegatorAddr, candidateAddr, initBond) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + assert.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) + + 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()) + + // 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 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) + + expBond := initBond - int64(i+1)*unbondShares + expLiabilities := 2*initBond - int64(i+1)*unbondShares + expDelegatorAcc := initBond - expBond + + gotBond := bond.Shares.Evaluate() + gotLiabilities := candidate.Liabilities.Evaluate() + gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) + + 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)) + msgUnbond := NewMsgUnbond(delegatorAddr, candidateAddr, unbondShares) + got = handleMsgUnbond(ctx, msgUnbond, keeper) + require.False(t, got.IsOK(), "expected unbond msg to fail") + } + + leftBonded := initBond - unbondShares*int64(numUnbonds) + + // 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) + + // 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) +} + +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]} + + // 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) + + //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) + } + + // 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) + + //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)) + + _, found = keeper.GetCandidate(ctx, candidateAddr) + require.False(t, found) + + 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) + } +} + +func TestMultipleMsgDelegate(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 1000) + candidateAddr, delegatorAddrs := addrs[0], addrs[1:] + + //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) +} diff --git a/x/stake/keeper.go b/x/stake/keeper.go index af2015fe815e..0f29b177bdcb 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -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 + // 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 } - store.Set(GetAccUpdateValidatorKey(validator.Address), bz) - + 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,60 +144,123 @@ 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(maxVal-1) { + if !iterator.Valid() || i > int(maxValidators-1) { iterator.Close() break } bz := iterator.Value() - var val Validator - err := k.cdc.UnmarshalBinary(bz, &val) + var validator Validator + err := k.cdc.UnmarshalBinary(bz, &validator) if err != nil { panic(err) } - validators[i] = val + validators[i] = validator + + // remove from ToKickOut group + store.Delete(GetToKickOutValidatorKey(validator.Address)) // also add to the recent validators group - store.Set(GetRecentValidatorKey(val.Address), bz) + 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 + } + bz := iterator.Value() + var val Validator + err := k.cdc.UnmarshalBinary(bz, &val) + if err != nil { + panic(err) + } + if bytes.Equal(val.Address, address) { + return true + } + iterator.Next() + } + + return false +} + // Is the address provided a part of the most recently saved validator group? func (k Keeper) IsRecentValidator(ctx sdk.Context, address sdk.Address) bool { store := ctx.KVStore(k.storeKey) @@ -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, ¶ms) @@ -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 +} diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go index 051994456e07..45e0570bc144 100644 --- a/x/stake/keeper_keys.go +++ b/x/stake/keeper_keys.go @@ -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()...) diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 6e7478957fbb..088bc5e30cd4 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -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) + + //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 candidate1 to delegate too - keeper.setCandidate(ctx, candidate1) + // 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 +// clear the tracked changes to the validator set +func TestClearAccUpdateValidators(t *testing.T) { + 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) { - //TODO + 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 - // test from something to nothing - // test identical + // 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 - // test validator added at the beginning - // test validator added in the middle - // test validator added at the end - // test multiple validators removed -} + // 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]) -// clear the tracked changes to the validator set -func TestClearAccUpdateValidators(t *testing.T) { - //TODO + // 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()) +} diff --git a/x/stake/pool.go b/x/stake/pool.go index 4c185580e097..1e58fe28eb26 100644 --- a/x/stake/pool.go +++ b/x/stake/pool.go @@ -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 } diff --git a/x/stake/pool_test.go b/x/stake/pool_test.go index 760a89a16b4d..cf1cd7ca2403 100644 --- a/x/stake/pool_test.go +++ b/x/stake/pool_test.go @@ -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 + + // bonded pool / total supply + require.Equal(t, pool.bondedRatio(), sdk.NewRat(2).Quo(sdk.NewRat(3))) + pool.TotalSupply = 0 + + // 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) - //check that the empty keeper loads the default - resPool := keeper.GetPool(ctx) - assert.Equal(t, expPool, resPool) + poolOrig = poolMod + candidatesOrig = candidatesMod - //modify a params, save, and retrieve - expPool.TotalSupply = 777 - keeper.setPool(ctx, expPool) - resPool = keeper.GetPool(ctx) - assert.Equal(t, expPool, resPool) + } + } } diff --git a/x/stake/test_common.go b/x/stake/test_common.go index aef425581330..285364a5d93f 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -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 diff --git a/x/stake/tick.go b/x/stake/tick.go index 6aa2da95db03..129be58f0195 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -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) + + change = k.getAccUpdateValidators(ctx) - // XXX determine change from old validators, set to change - _ = newVals - return change, nil + 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) } diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go index 540ce46999c3..105ee8981f5d 100644 --- a/x/stake/tick_test.go +++ b/x/stake/tick_test.go @@ -1,116 +1,134 @@ package stake -//import ( -//"testing" - -//sdk "github.com/cosmos/cosmos-sdk/types" -//"github.com/stretchr/testify/assert" -//) - -//func TestGetInflation(t *testing.T) { -//ctx, _, keeper := createTestInput(t, nil, false, 0) -//params := defaultParams() -//keeper.setParams(ctx, params) -//gs := keeper.GetPool(ctx) - -//// 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)}, - -//// 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)}, - -//// 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)}, - -//// 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 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)}, - -//// 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 - -//inflation := nextInflation(gs, params) -//diffInflation := inflation.Sub(tc.setInflation) - -//assert.True(t, diffInflation.Equal(tc.expectedChange), -//"%v, %v", 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) - -//// 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 - -//// initial bonded ratio ~ 27% -//assert.True(t, p.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", p.bondedRatio()) - -//// Supplies -//assert.Equal(t, totalSupply, p.TotalSupply) -//assert.Equal(t, bondedShares, p.BondedPool) -//assert.Equal(t, unbondedShares, p.UnbondedPool) - -//// test the value of candidate shares -//assert.True(t, p.bondedShareExRate().Equal(sdk.OneRat), "%v", p.bondedShareExRate()) - -//initialSupply := p.TotalSupply -//initialUnbonded := p.TotalSupply - p.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)) - -//// initial bonded ratio ~ 35% ~ 30% increase for bonded holders -//assert.True(t, p.bondedRatio().Equal(sdk.NewRat(105906511, 305906511)), "%v", p.bondedRatio()) - -//// global supply -//assert.Equal(t, int64(611813022), p.TotalSupply) -//assert.Equal(t, int64(211813022), p.BondedPool) -//assert.Equal(t, unbondedShares, p.UnbondedPool) - -//// test the value of candidate shares -//assert.True(t, p.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(211813022)), "%v", p.bondedShareExRate()) - -//} +import ( + "testing" + + 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, false, 0) + pool := keeper.GetPool(ctx) + params := keeper.GetParams(ctx) + hrsPerYrRat := sdk.NewRat(hrsPerYr) + + // Governing Mechanism: + // bondedRatio = BondedPool / TotalSupply + // inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange + + 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/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 + {"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) + {"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) + {"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 + {"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 := keeper.nextInflation(ctx) + diffInflation := inflation.Sub(tc.setInflation) + + 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, false, 0) + params := defaultParams() + keeper.setParams(ctx, params) + pool := keeper.GetPool(ctx) + + // 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) + + 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) + + // 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, pool.bondedShareExRate().Equal(sdk.OneRat), "%v", pool.bondedShareExRate()) + + initialSupply := pool.TotalSupply + initialUnbonded := pool.TotalSupply - pool.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 ~ 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(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, pool.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(271734723)), "%v", pool.bondedShareExRate()) + +} diff --git a/x/stake/types.go b/x/stake/types.go index 2799e1d76ba4..1154f7962fe3 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -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, + } } -*/ //_________________________________________________________________________