Implement Simple Staking as a module
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.
This commit is contained in:
parent
4bfa40adbd
commit
75674a9ec3
|
@ -14,6 +14,9 @@ docs/_build
|
|||
coverage.txt
|
||||
profile.out
|
||||
.vscode
|
||||
coverage.txt
|
||||
profile.out
|
||||
client/lcd/keys.db/
|
||||
|
||||
### Vagrant ###
|
||||
.vagrant/
|
||||
|
|
2
Makefile
2
Makefile
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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},
|
||||
|
@ -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
|
||||
|
|
|
@ -2,9 +2,8 @@ package main
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
//"os"
|
||||
// "os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package staking
|
||||
|
||||
import (
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
wire "github.com/cosmos/cosmos-sdk/wire"
|
||||
)
|
||||
|
||||
type StakingMapper struct {
|
||||
key sdk.StoreKey
|
||||
cdc *wire.Codec
|
||||
}
|
||||
|
||||
func NewMapper(key sdk.StoreKey) StakingMapper {
|
||||
cdc := wire.NewCodec()
|
||||
return StakingMapper{
|
||||
key: key,
|
||||
cdc: cdc,
|
||||
}
|
||||
}
|
||||
|
||||
func (sm StakingMapper) getBondInfo(ctx sdk.Context, addr sdk.Address) *bondInfo {
|
||||
store := ctx.KVStore(sm.key)
|
||||
bz := store.Get(addr)
|
||||
if bz == nil {
|
||||
return nil
|
||||
}
|
||||
var bi bondInfo
|
||||
err := sm.cdc.UnmarshalBinary(bz, &bi)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &bi
|
||||
}
|
||||
|
||||
func (sm StakingMapper) setBondInfo(ctx sdk.Context, addr sdk.Address, bi *bondInfo) {
|
||||
store := ctx.KVStore(sm.key)
|
||||
bz, err := sm.cdc.MarshalBinary(*bi)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
store.Set(addr, bz)
|
||||
}
|
||||
|
||||
func (sm StakingMapper) deleteBondInfo(ctx sdk.Context, addr sdk.Address) {
|
||||
store := ctx.KVStore(sm.key)
|
||||
store.Delete(addr)
|
||||
}
|
||||
|
||||
func (sm StakingMapper) Bond(ctx sdk.Context, addr sdk.Address, pubKey crypto.PubKey, power int64) (int64, sdk.Error) {
|
||||
bi := sm.getBondInfo(ctx, addr)
|
||||
if bi == nil {
|
||||
bi = &bondInfo{
|
||||
PubKey: pubKey,
|
||||
Power: power,
|
||||
}
|
||||
sm.setBondInfo(ctx, addr, bi)
|
||||
return bi.Power, nil
|
||||
}
|
||||
|
||||
newPower := bi.Power + power
|
||||
newBi := &bondInfo{
|
||||
PubKey: bi.PubKey,
|
||||
Power: newPower,
|
||||
}
|
||||
sm.setBondInfo(ctx, addr, newBi)
|
||||
|
||||
return newBi.Power, nil
|
||||
}
|
||||
|
||||
func (sm StakingMapper) Unbond(ctx sdk.Context, addr sdk.Address) (crypto.PubKey, int64, sdk.Error) {
|
||||
bi := sm.getBondInfo(ctx, addr)
|
||||
if bi == nil {
|
||||
return crypto.PubKey{}, 0, ErrInvalidUnbond()
|
||||
}
|
||||
sm.deleteBondInfo(ctx, addr)
|
||||
return bi.PubKey, bi.Power, nil
|
||||
}
|
||||
|
||||
type bondInfo struct {
|
||||
PubKey crypto.PubKey
|
||||
Power int64
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package staking
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
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 TestStakingMapperGetSet(t *testing.T) {
|
||||
ms, capKey := setupMultiStore()
|
||||
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil)
|
||||
stakingMapper := NewMapper(capKey)
|
||||
addr := sdk.Address([]byte("some-address"))
|
||||
|
||||
bi := stakingMapper.getBondInfo(ctx, addr)
|
||||
assert.Nil(t, bi)
|
||||
|
||||
privKey := crypto.GenPrivKeyEd25519()
|
||||
|
||||
bi = &bondInfo{
|
||||
PubKey: privKey.PubKey(),
|
||||
Power: int64(10),
|
||||
}
|
||||
fmt.Printf("Pubkey: %v\n", privKey.PubKey())
|
||||
stakingMapper.setBondInfo(ctx, addr, bi)
|
||||
|
||||
savedBi := stakingMapper.getBondInfo(ctx, addr)
|
||||
assert.NotNil(t, savedBi)
|
||||
fmt.Printf("Bond Info: %v\n", savedBi)
|
||||
assert.Equal(t, int64(10), savedBi.Power)
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package staking
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// -------------------------
|
||||
// BondMsg
|
||||
|
||||
type BondMsg struct {
|
||||
Address sdk.Address `json:"address"`
|
||||
Stake sdk.Coin `json:"coins"`
|
||||
PubKey crypto.PubKey `json:"pub_key"`
|
||||
}
|
||||
|
||||
func NewBondMsg(addr sdk.Address, stake sdk.Coin, pubKey crypto.PubKey) BondMsg {
|
||||
return BondMsg{
|
||||
Address: addr,
|
||||
Stake: stake,
|
||||
PubKey: pubKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (msg BondMsg) Type() string {
|
||||
return "staking"
|
||||
}
|
||||
|
||||
func (msg BondMsg) ValidateBasic() sdk.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (msg BondMsg) Get(key interface{}) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (msg BondMsg) GetSignBytes() []byte {
|
||||
bz, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
func (msg BondMsg) GetSigners() []sdk.Address {
|
||||
return []sdk.Address{msg.Address}
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// UnbondMsg
|
||||
|
||||
type UnbondMsg struct {
|
||||
Address sdk.Address `json:"address"`
|
||||
}
|
||||
|
||||
func NewUnbondMsg(addr sdk.Address) UnbondMsg {
|
||||
return UnbondMsg{
|
||||
Address: addr,
|
||||
}
|
||||
}
|
||||
|
||||
func (msg UnbondMsg) Type() string {
|
||||
return "staking"
|
||||
}
|
||||
|
||||
func (msg UnbondMsg) ValidateBasic() sdk.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (msg UnbondMsg) Get(key interface{}) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (msg UnbondMsg) GetSignBytes() []byte {
|
||||
bz, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
func (msg UnbondMsg) GetSigners() []sdk.Address {
|
||||
return []sdk.Address{msg.Address}
|
||||
}
|
Loading…
Reference in New Issue