Skip to content

Commit

Permalink
Implement Simple Staking as a module
Browse files Browse the repository at this point in the history
The simple staking module allows validators to bond and add more stake
to their bond. It doesn't allow partial unbond and has no delegation.
The staking power per validator though is correctly reflected within the
consensus.
  • Loading branch information
Adrian Brink committed Mar 20, 2018
1 parent 4bfa40a commit 75674a9
Show file tree
Hide file tree
Showing 11 changed files with 446 additions and 16 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ docs/_build
coverage.txt
profile.out
.vscode
coverage.txt
profile.out
client/lcd/keys.db/

### Vagrant ###
.vagrant/
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ test_unit:
@go test $(PACKAGES)

test_cover:
@rm -rf examples/basecoin/vendor
@rm -rf examples/basecoin/vendor/
@rm -rf client/lcd/keys.db ~/.tendermint_test
@bash tests/test_cover.sh
@rm -rf client/lcd/keys.db ~/.tendermint_test
Expand Down
28 changes: 17 additions & 11 deletions examples/basecoin/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/ibc"
"github.com/cosmos/cosmos-sdk/x/staking"

"github.com/cosmos/cosmos-sdk/examples/basecoin/types"
"github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool"
Expand All @@ -31,8 +32,9 @@ type BasecoinApp struct {
cdc *wire.Codec

// keys to access the substores
capKeyMainStore *sdk.KVStoreKey
capKeyIBCStore *sdk.KVStoreKey
capKeyMainStore *sdk.KVStoreKey
capKeyIBCStore *sdk.KVStoreKey
capKeyStakingStore *sdk.KVStoreKey

// Manage getting and setting accounts
accountMapper sdk.AccountMapper
Expand All @@ -41,10 +43,11 @@ type BasecoinApp struct {
func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
// create your application object
var app = &BasecoinApp{
BaseApp: bam.NewBaseApp(appName, logger, db),
cdc: MakeCodec(),
capKeyMainStore: sdk.NewKVStoreKey("main"),
capKeyIBCStore: sdk.NewKVStoreKey("ibc"),
BaseApp: bam.NewBaseApp(appName, logger, db),
cdc: MakeCodec(),
capKeyMainStore: sdk.NewKVStoreKey("main"),
capKeyIBCStore: sdk.NewKVStoreKey("ibc"),
capKeyStakingStore: sdk.NewKVStoreKey("staking"),
}

// define the accountMapper
Expand All @@ -57,18 +60,18 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
coinKeeper := bank.NewCoinKeeper(app.accountMapper)
coolMapper := cool.NewMapper(app.capKeyMainStore)
ibcMapper := ibc.NewIBCMapper(app.cdc, app.capKeyIBCStore)
stakingMapper := staking.NewMapper(app.capKeyStakingStore)
app.Router().
AddRoute("bank", bank.NewHandler(coinKeeper)).
AddRoute("cool", cool.NewHandler(coinKeeper, coolMapper)).
AddRoute("sketchy", sketchy.NewHandler()).
AddRoute("ibc", ibc.NewHandler(ibcMapper, coinKeeper))
AddRoute("ibc", ibc.NewHandler(ibcMapper, coinKeeper)).
AddRoute("staking", staking.NewHandler(stakingMapper, coinKeeper))

// initialize BaseApp
app.SetTxDecoder(app.txDecoder)
app.SetInitChainer(app.initChainer)
// TODO: mounting multiple stores is broken
// https://github.com/cosmos/cosmos-sdk/issues/532
app.MountStoresIAVL(app.capKeyMainStore, app.capKeyIBCStore)
app.MountStoresIAVL(app.capKeyMainStore, app.capKeyIBCStore, app.capKeyStakingStore)
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper))
err := app.LoadLatestVersion(app.capKeyMainStore)
if err != nil {
Expand All @@ -81,13 +84,14 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
// custom tx codec
// TODO: use new go-wire
func MakeCodec() *wire.Codec {

const msgTypeSend = 0x1
const msgTypeIssue = 0x2
const msgTypeQuiz = 0x3
const msgTypeSetTrend = 0x4
const msgTypeIBCTransferMsg = 0x5
const msgTypeIBCReceiveMsg = 0x6
const msgTypeBondMsg = 0x7
const msgTypeUnbondMsg = 0x8
var _ = oldwire.RegisterInterface(
struct{ sdk.Msg }{},
oldwire.ConcreteType{bank.SendMsg{}, msgTypeSend},
Expand All @@ -96,6 +100,8 @@ func MakeCodec() *wire.Codec {
oldwire.ConcreteType{cool.SetTrendMsg{}, msgTypeSetTrend},
oldwire.ConcreteType{ibc.IBCTransferMsg{}, msgTypeIBCTransferMsg},
oldwire.ConcreteType{ibc.IBCReceiveMsg{}, msgTypeIBCReceiveMsg},
oldwire.ConcreteType{staking.BondMsg{}, msgTypeBondMsg},
oldwire.ConcreteType{staking.UnbondMsg{}, msgTypeUnbondMsg},
)

const accTypeApp = 0x1
Expand Down
11 changes: 8 additions & 3 deletions examples/basecoin/cmd/basecli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ package main

import (
"errors"
"os"

"github.com/spf13/cobra"
"os"

"github.com/tendermint/tmlibs/cli"

Expand All @@ -14,14 +13,15 @@ import (
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/client/tx"

coolcmd "github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool/commands"
"github.com/cosmos/cosmos-sdk/version"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands"
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/commands"
ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/commands"
stakingcmd "github.com/cosmos/cosmos-sdk/x/staking/commands"

"github.com/cosmos/cosmos-sdk/examples/basecoin/app"
"github.com/cosmos/cosmos-sdk/examples/basecoin/types"
coolcmd "github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool/commands"
)

// gaiacliCmd is the entry point for this binary
Expand Down Expand Up @@ -77,6 +77,11 @@ func main() {
basecliCmd.AddCommand(
client.PostCommands(
ibccmd.IBCRelayCmd(cdc),
stakingcmd.BondTxCmd(cdc),
)...)
basecliCmd.AddCommand(
client.PostCommands(
stakingcmd.UnbondTxCmd(cdc),
)...)

// add proxy, version and key info
Expand Down
2 changes: 1 addition & 1 deletion server/start_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package server

import (
//"os"
// "os"
"testing"
"time"

Expand Down
100 changes: 100 additions & 0 deletions x/staking/commands/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package commands

import (
"encoding/hex"
"fmt"

"github.com/spf13/cobra"
"github.com/spf13/viper"

crypto "github.com/tendermint/go-crypto"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/builder"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/staking"
)

const (
flagStake = "stake"
flagValidator = "validator"
)

func BondTxCmd(cdc *wire.Codec) *cobra.Command {
cmdr := commander{cdc}
cmd := &cobra.Command{
Use: "bond",
Short: "Bond to a validator",
RunE: cmdr.bondTxCmd,
}
cmd.Flags().String(flagStake, "", "Amount of coins to stake")
cmd.Flags().String(flagValidator, "", "Validator address to stake")
return cmd
}

func UnbondTxCmd(cdc *wire.Codec) *cobra.Command {
cmdr := commander{cdc}
cmd := &cobra.Command{
Use: "unbond",
Short: "Unbond from a validator",
RunE: cmdr.unbondTxCmd,
}
return cmd
}

type commander struct {
cdc *wire.Codec
}

func (co commander) bondTxCmd(cmd *cobra.Command, args []string) error {
from, err := builder.GetFromAddress()
if err != nil {
return err
}

stake, err := sdk.ParseCoin(viper.GetString(flagStake))
if err != nil {
return err
}

rawPubKey, err := hex.DecodeString(viper.GetString(flagValidator))
if err != nil {
return err
}
var pubKey crypto.PubKeyEd25519
copy(pubKey[:], rawPubKey)

msg := staking.NewBondMsg(from, stake, pubKey.Wrap())

return co.sendMsg(msg)
}

func (co commander) unbondTxCmd(cmd *cobra.Command, args []string) error {
from, err := builder.GetFromAddress()
if err != nil {
return err
}

msg := staking.NewUnbondMsg(from)

return co.sendMsg(msg)
}

func (co commander) sendMsg(msg sdk.Msg) error {
name := viper.GetString(client.FlagName)
buf := client.BufferStdin()
prompt := fmt.Sprintf("Password to sign with '%s':", name)
passphrase, err := client.GetPassword(prompt, buf)
if err != nil {
return err
}

res, err := builder.SignBuildBroadcast(name, passphrase, msg, co.cdc)
if err != nil {
return err
}

fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
return nil
}
26 changes: 26 additions & 0 deletions x/staking/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package staking

import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

const (
// Staking errors reserve 300 - 399.
CodeEmptyValidator sdk.CodeType = 300
CodeInvalidUnbond sdk.CodeType = 301
)

func ErrEmptyValidator() sdk.Error {
return newError(CodeEmptyValidator, "")
}

func ErrInvalidUnbond() sdk.Error {
return newError(CodeInvalidUnbond, "")
}

// -----------------------------
// Helpers

func newError(code sdk.CodeType, msg string) sdk.Error {
return sdk.NewError(code, msg)
}
69 changes: 69 additions & 0 deletions x/staking/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package staking

import (
abci "github.com/tendermint/abci/types"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank"
)

func NewHandler(sm StakingMapper, ck bank.CoinKeeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case BondMsg:
return handleBondMsg(ctx, sm, ck, msg)
case UnbondMsg:
return handleUnbondMsg(ctx, sm, ck, msg)
default:
return sdk.ErrUnknownRequest("No match for message type.").Result()
}
}
}

func handleBondMsg(ctx sdk.Context, sm StakingMapper, ck bank.CoinKeeper, msg BondMsg) sdk.Result {
_, err := ck.SubtractCoins(ctx, msg.Address, []sdk.Coin{msg.Stake})
if err != nil {
return err.Result()
}

power, err := sm.Bond(ctx, msg.Address, msg.PubKey, msg.Stake.Amount)
if err != nil {
return err.Result()
}

valSet := abci.Validator{
PubKey: msg.PubKey.Bytes(),
Power: power,
}

return sdk.Result{
Code: sdk.CodeOK,
ValidatorUpdates: abci.Validators{valSet},
}
}

func handleUnbondMsg(ctx sdk.Context, sm StakingMapper, ck bank.CoinKeeper, msg UnbondMsg) sdk.Result {
pubKey, power, err := sm.Unbond(ctx, msg.Address)
if err != nil {
return err.Result()
}

stake := sdk.Coin{
Denom: "mycoin",
Amount: power,
}
_, err = ck.AddCoins(ctx, msg.Address, sdk.Coins{stake})
if err != nil {
return err.Result()
}

valSet := abci.Validator{
PubKey: pubKey.Bytes(),
Power: int64(0),
}

return sdk.Result{
Code: sdk.CodeOK,
ValidatorUpdates: abci.Validators{valSet},
}
}
Loading

0 comments on commit 75674a9

Please sign in to comment.