Merge pull request #1011 from cosmos/cwgoes/slashing

Implement slashing (v1)
This commit is contained in:
Rigel 2018-05-31 11:09:44 -07:00 committed by GitHub
commit 0fb2bbdfef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 895 additions and 77 deletions

48
Gopkg.lock generated
View File

@ -11,13 +11,13 @@
branch = "master" branch = "master"
name = "github.com/btcsuite/btcd" name = "github.com/btcsuite/btcd"
packages = ["btcec"] packages = ["btcec"]
revision = "1432d294a5b055c297457c25434efbf13384cc46" revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "github.com/cosmos/bech32cosmos" name = "github.com/cosmos/bech32cosmos"
packages = ["go"] packages = ["go"]
revision = "efca97cd8c0852c44d96dfdcc70565c306eddff0" revision = "c12e4b6ed52acc1d35fea49acec35f980914dc95"
[[projects]] [[projects]]
name = "github.com/davecgh/go-spew" name = "github.com/davecgh/go-spew"
@ -256,7 +256,7 @@
"leveldb/table", "leveldb/table",
"leveldb/util" "leveldb/util"
] ]
revision = "e6d6b529196422703d54ff5c40e79809ec2020b3" revision = "5d6fca44a948d2be89a9702de7717f0168403d3d"
[[projects]] [[projects]]
name = "github.com/tendermint/abci" name = "github.com/tendermint/abci"
@ -267,8 +267,8 @@
"server", "server",
"types" "types"
] ]
revision = "78a8905690ef54f9d57e3b2b0ee7ad3a04ef3f1f" revision = "f9dce537281ffba5d1e047e6729429f7e5fb90c9"
version = "v0.10.3" version = "v0.11.0-rc0"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -298,17 +298,14 @@
revision = "915416979bf70efa4bcbf1c6cd5d64c5fff9fc19" revision = "915416979bf70efa4bcbf1c6cd5d64c5fff9fc19"
version = "v0.6.2" version = "v0.6.2"
[[projects]]
name = "github.com/tendermint/go-wire"
packages = ["."]
revision = "fa721242b042ecd4c6ed1a934ee740db4f74e45c"
version = "v0.7.3"
[[projects]] [[projects]]
name = "github.com/tendermint/iavl" name = "github.com/tendermint/iavl"
packages = ["."] packages = [
revision = "fd37a0fa3a7454423233bc3d5ea828f38e0af787" ".",
version = "v0.7.0" "sha256truncated"
]
revision = "c9206995e8f948e99927f5084a88a7e94ca256da"
version = "v0.8.0-rc0"
[[projects]] [[projects]]
name = "github.com/tendermint/tendermint" name = "github.com/tendermint/tendermint"
@ -319,6 +316,9 @@
"consensus", "consensus",
"consensus/types", "consensus/types",
"evidence", "evidence",
"libs/events",
"libs/pubsub",
"libs/pubsub/query",
"lite", "lite",
"lite/client", "lite/client",
"lite/errors", "lite/errors",
@ -347,8 +347,8 @@
"types/priv_validator", "types/priv_validator",
"version" "version"
] ]
revision = "018e096748bafe1d2d1e69b909e4158f3b26f6b2" revision = "73de99ecab464208f6ea3a96525f4e4b78425e61"
version = "v0.19.5-rc1" version = "v0.20.0-rc0"
[[projects]] [[projects]]
name = "github.com/tendermint/tmlibs" name = "github.com/tendermint/tmlibs"
@ -361,12 +361,10 @@
"db", "db",
"flowrate", "flowrate",
"log", "log",
"merkle", "merkle"
"pubsub",
"pubsub/query"
] ]
revision = "cc5f287c4798ffe88c04d02df219ecb6932080fd" revision = "d970af87248a4e162590300dbb74e102183a417d"
version = "v0.8.3-rc0" version = "v0.8.3"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -382,7 +380,7 @@
"ripemd160", "ripemd160",
"salsa20/salsa" "salsa20/salsa"
] ]
revision = "1a580b3eff7814fc9b40602fd35256c63b50f491" revision = "ab813273cd59e1333f7ae7bff5d027d4aadf528c"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -396,13 +394,13 @@
"internal/timeseries", "internal/timeseries",
"trace" "trace"
] ]
revision = "57065200b4b034a1c8ad54ff77069408c2218ae6" revision = "1e491301e022f8f977054da4c2d852decd59571f"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "golang.org/x/sys" name = "golang.org/x/sys"
packages = ["unix"] packages = ["unix"]
revision = "7c87d13f8e835d2fb3a70a2912c811ed0c1d241b" revision = "c11f84a56e43e20a78cee75a7c034031ecf57d1f"
[[projects]] [[projects]]
name = "golang.org/x/text" name = "golang.org/x/text"
@ -463,6 +461,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "f0c6224dc5f30c1a7dea716d619665831ea0932b0eb9afc6ac897dbc459134fa" inputs-digest = "a6a5d886519fa9ca97a23715faa852ee14ecb5337e03641d19ea3d3d1c392fee"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

@ -54,43 +54,37 @@
[[constraint]] [[constraint]]
name = "github.com/tendermint/abci" name = "github.com/tendermint/abci"
version = "~0.10.3" version = "0.11.0-rc0"
[[constraint]] [[constraint]]
name = "github.com/tendermint/go-crypto" name = "github.com/tendermint/go-crypto"
version = "~0.6.2" version = "~0.6.2"
[[override]]
name = "github.com/tendermint/go-wire"
version = "0.7.3"
[[constraint]] [[constraint]]
name = "github.com/tendermint/go-amino" name = "github.com/tendermint/go-amino"
version = "~0.9.9" version = "=0.9.9"
[[constraint]] [[constraint]]
name = "github.com/tendermint/iavl" name = "github.com/tendermint/iavl"
version = "~0.7.0" version = "0.8.0-rc0"
[[constraint]] [[constraint]]
name = "github.com/tendermint/tendermint" name = "github.com/tendermint/tendermint"
version = "0.19.5-rc1" version = "0.20.0-rc0"
[[override]] [[constraint]]
name = "github.com/tendermint/tmlibs" name = "github.com/tendermint/tmlibs"
version = "~0.8.3-rc0" version = "~0.8.3-rc0"
[[constraint]]
name = "github.com/cosmos/bech32cosmos"
branch = "master"
# this got updated and broke, so locked to an old working commit ... # this got updated and broke, so locked to an old working commit ...
[[override]] [[override]]
name = "google.golang.org/genproto" name = "google.golang.org/genproto"
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200"
[[constraint]]
name = "github.com/cosmos/bech32cosmos"
branch = "master"
[prune] [prune]
go-tests = true go-tests = true
unused-packages = true unused-packages = true

View File

@ -68,6 +68,7 @@ type BaseApp struct {
checkState *state // for CheckTx checkState *state // for CheckTx
deliverState *state // for DeliverTx deliverState *state // for DeliverTx
valUpdates []abci.Validator // cached validator changes from DeliverTx valUpdates []abci.Validator // cached validator changes from DeliverTx
absentValidators [][]byte // absent validators from begin block
} }
var _ abci.Application = (*BaseApp)(nil) var _ abci.Application = (*BaseApp)(nil)
@ -384,6 +385,8 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg
if app.beginBlocker != nil { if app.beginBlocker != nil {
res = app.beginBlocker(app.deliverState.ctx, req) res = app.beginBlocker(app.deliverState.ctx, req)
} }
// set the absent validators for addition to context in deliverTx
app.absentValidators = req.AbsentValidators
return return
} }
@ -493,6 +496,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
ctx = app.checkState.ctx.WithTxBytes(txBytes) ctx = app.checkState.ctx.WithTxBytes(txBytes)
} else { } else {
ctx = app.deliverState.ctx.WithTxBytes(txBytes) ctx = app.deliverState.ctx.WithTxBytes(txBytes)
ctx = ctx.WithAbsentValidators(app.absentValidators)
} }
// Simulate a DeliverTx for gas calculation // Simulate a DeliverTx for gas calculation

View File

@ -183,7 +183,7 @@ func TestInitChainer(t *testing.T) {
// set initChainer and try again - should see the value // set initChainer and try again - should see the value
app.SetInitChainer(initChainer) app.SetInitChainer(initChainer)
app.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}")}) // must have valid JSON genesis file, even if empty app.InitChain(abci.RequestInitChain{GenesisBytes: []byte("{}")}) // must have valid JSON genesis file, even if empty
app.Commit() app.Commit()
res = app.Query(query) res = app.Query(query)
assert.Equal(t, value, res.Value) assert.Equal(t, value, res.Value)

View File

@ -15,6 +15,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/ibc" "github.com/cosmos/cosmos-sdk/x/ibc"
"github.com/cosmos/cosmos-sdk/x/slashing"
"github.com/cosmos/cosmos-sdk/x/stake" "github.com/cosmos/cosmos-sdk/x/stake"
) )
@ -38,6 +39,7 @@ type GaiaApp struct {
keyAccount *sdk.KVStoreKey keyAccount *sdk.KVStoreKey
keyIBC *sdk.KVStoreKey keyIBC *sdk.KVStoreKey
keyStake *sdk.KVStoreKey keyStake *sdk.KVStoreKey
keySlashing *sdk.KVStoreKey
// Manage getting and setting accounts // Manage getting and setting accounts
accountMapper auth.AccountMapper accountMapper auth.AccountMapper
@ -45,6 +47,7 @@ type GaiaApp struct {
coinKeeper bank.Keeper coinKeeper bank.Keeper
ibcMapper ibc.Mapper ibcMapper ibc.Mapper
stakeKeeper stake.Keeper stakeKeeper stake.Keeper
slashingKeeper slashing.Keeper
} }
func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
@ -58,6 +61,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
keyAccount: sdk.NewKVStoreKey("acc"), keyAccount: sdk.NewKVStoreKey("acc"),
keyIBC: sdk.NewKVStoreKey("ibc"), keyIBC: sdk.NewKVStoreKey("ibc"),
keyStake: sdk.NewKVStoreKey("stake"), keyStake: sdk.NewKVStoreKey("stake"),
keySlashing: sdk.NewKVStoreKey("slashing"),
} }
// define the accountMapper // define the accountMapper
@ -71,6 +75,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
app.coinKeeper = bank.NewKeeper(app.accountMapper) app.coinKeeper = bank.NewKeeper(app.accountMapper)
app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace)) app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace))
app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace)) app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace))
app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.RegisterCodespace(slashing.DefaultCodespace))
// register message routes // register message routes
app.Router(). app.Router().
@ -80,9 +85,10 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
// initialize BaseApp // initialize BaseApp
app.SetInitChainer(app.initChainer) app.SetInitChainer(app.initChainer)
app.SetBeginBlocker(slashing.NewBeginBlocker(app.slashingKeeper))
app.SetEndBlocker(stake.NewEndBlocker(app.stakeKeeper)) app.SetEndBlocker(stake.NewEndBlocker(app.stakeKeeper))
app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake)
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper))
app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing)
err := app.LoadLatestVersion(app.keyMain) err := app.LoadLatestVersion(app.keyMain)
if err != nil { if err != nil {
cmn.Exit(err.Error()) cmn.Exit(err.Error())
@ -97,6 +103,7 @@ func MakeCodec() *wire.Codec {
ibc.RegisterWire(cdc) ibc.RegisterWire(cdc)
bank.RegisterWire(cdc) bank.RegisterWire(cdc)
stake.RegisterWire(cdc) stake.RegisterWire(cdc)
slashing.RegisterWire(cdc)
auth.RegisterWire(cdc) auth.RegisterWire(cdc)
sdk.RegisterWire(cdc) sdk.RegisterWire(cdc)
wire.RegisterCrypto(cdc) wire.RegisterCrypto(cdc)
@ -105,7 +112,8 @@ func MakeCodec() *wire.Codec {
// custom logic for gaia initialization // custom logic for gaia initialization
func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
stateJSON := req.AppStateBytes stateJSON := req.GenesisBytes
// TODO is this now the whole genesis file?
var genesisState GenesisState var genesisState GenesisState
err := app.cdc.UnmarshalJSON(stateJSON, &genesisState) err := app.cdc.UnmarshalJSON(stateJSON, &genesisState)

View File

@ -104,7 +104,7 @@ func MakeCodec() *wire.Codec {
// Custom logic for basecoin initialization // Custom logic for basecoin initialization
func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
stateJSON := req.AppStateBytes stateJSON := req.GenesisBytes
genesisState := new(types.GenesisState) genesisState := new(types.GenesisState)
err := app.cdc.UnmarshalJSON(stateJSON, genesisState) err := app.cdc.UnmarshalJSON(stateJSON, genesisState)

View File

@ -118,7 +118,7 @@ func MakeCodec() *wire.Codec {
// custom logic for democoin initialization // custom logic for democoin initialization
func (app *DemocoinApp) initChainerFn(coolKeeper cool.Keeper, powKeeper pow.Keeper) sdk.InitChainer { func (app *DemocoinApp) initChainerFn(coolKeeper cool.Keeper, powKeeper pow.Keeper) sdk.InitChainer {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
stateJSON := req.AppStateBytes stateJSON := req.GenesisBytes
genesisState := new(types.GenesisState) genesisState := new(types.GenesisState)
err := app.cdc.UnmarshalJSON(stateJSON, genesisState) err := app.cdc.UnmarshalJSON(stateJSON, genesisState)

View File

@ -35,9 +35,9 @@ func TestKeeperGetSet(t *testing.T) {
cdc := wire.NewCodec() cdc := wire.NewCodec()
auth.RegisterBaseAccount(cdc) auth.RegisterBaseAccount(cdc)
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{}) accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{})
stakeKeeper := NewKeeper(capKey, bank.NewKeeper(accountMapper), DefaultCodespace) stakeKeeper := NewKeeper(capKey, bank.NewKeeper(accountMapper), DefaultCodespace)
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
addr := sdk.Address([]byte("some-address")) addr := sdk.Address([]byte("some-address"))
bi := stakeKeeper.getBondInfo(ctx, addr) bi := stakeKeeper.getBondInfo(ctx, addr)

View File

@ -88,7 +88,7 @@ type GenesisJSON struct {
// with key/value pairs // with key/value pairs
func InitChainer(key sdk.StoreKey) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain { func InitChainer(key sdk.StoreKey) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
stateJSON := req.AppStateBytes stateJSON := req.GenesisBytes
genesisState := new(GenesisJSON) genesisState := new(GenesisJSON)
err := json.Unmarshal(stateJSON, genesisState) err := json.Unmarshal(stateJSON, genesisState)

View File

@ -26,7 +26,7 @@ func TestInitApp(t *testing.T) {
//TODO test validators in the init chain? //TODO test validators in the init chain?
req := abci.RequestInitChain{ req := abci.RequestInitChain{
AppStateBytes: appState, GenesisBytes: appState,
} }
app.InitChain(req) app.InitChain(req)
app.Commit() app.Commit()

View File

@ -31,6 +31,7 @@ type Context struct {
// create a new context // create a new context
func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byte, logger log.Logger) Context { func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byte, logger log.Logger) Context {
c := Context{ c := Context{
Context: context.Background(), Context: context.Background(),
pst: newThePast(), pst: newThePast(),
@ -43,6 +44,7 @@ func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byt
c = c.WithIsCheckTx(isCheckTx) c = c.WithIsCheckTx(isCheckTx)
c = c.WithTxBytes(txBytes) c = c.WithTxBytes(txBytes)
c = c.WithLogger(logger) c = c.WithLogger(logger)
c = c.WithAbsentValidators(nil)
c = c.WithGasMeter(NewInfiniteGasMeter()) c = c.WithGasMeter(NewInfiniteGasMeter())
return c return c
} }
@ -128,6 +130,7 @@ const (
contextKeyIsCheckTx contextKeyIsCheckTx
contextKeyTxBytes contextKeyTxBytes
contextKeyLogger contextKeyLogger
contextKeyAbsentValidators
contextKeyGasMeter contextKeyGasMeter
) )
@ -157,6 +160,9 @@ func (c Context) TxBytes() []byte {
func (c Context) Logger() log.Logger { func (c Context) Logger() log.Logger {
return c.Value(contextKeyLogger).(log.Logger) return c.Value(contextKeyLogger).(log.Logger)
} }
func (c Context) AbsentValidators() [][]byte {
return c.Value(contextKeyAbsentValidators).([][]byte)
}
func (c Context) GasMeter() GasMeter { func (c Context) GasMeter() GasMeter {
return c.Value(contextKeyGasMeter).(GasMeter) return c.Value(contextKeyGasMeter).(GasMeter)
} }
@ -182,6 +188,9 @@ func (c Context) WithTxBytes(txBytes []byte) Context {
func (c Context) WithLogger(logger log.Logger) Context { func (c Context) WithLogger(logger log.Logger) Context {
return c.withValue(contextKeyLogger, logger) return c.withValue(contextKeyLogger, logger)
} }
func (c Context) WithAbsentValidators(AbsentValidators [][]byte) Context {
return c.withValue(contextKeyAbsentValidators, AbsentValidators)
}
func (c Context) WithGasMeter(meter GasMeter) Context { func (c Context) WithGasMeter(meter GasMeter) Context {
return c.withValue(contextKeyGasMeter, meter) return c.withValue(contextKeyGasMeter, meter)
} }

View File

@ -58,6 +58,9 @@ type ValidatorSet interface {
Validator(Context, Address) Validator // get a particular validator by owner address Validator(Context, Address) Validator // get a particular validator by owner address
TotalPower(Context) Rat // total power of the validator set TotalPower(Context) Rat // total power of the validator set
Slash(Context, crypto.PubKey, int64, Rat) // slash the validator and delegators of the validator, specifying offence height & slash fraction
Revoke(Context, crypto.PubKey) // revoke a validator
Unrevoke(Context, crypto.PubKey) // unrevoke a validator
} }
//_______________________________________________________________________________ //_______________________________________________________________________________

View File

@ -25,6 +25,11 @@ func (t Tags) AppendTags(a Tags) Tags {
return append(t, a...) return append(t, a...)
} }
// Turn tags into KVPair list
func (t Tags) ToKVPairs() []cmn.KVPair {
return []cmn.KVPair(t)
}
// New variadic tags, must be k string, v []byte repeating // New variadic tags, must be k string, v []byte repeating
func NewTags(tags ...interface{}) Tags { func NewTags(tags ...interface{}) Tags {
var ret Tags var ret Tags

52
x/slashing/errors.go Normal file
View File

@ -0,0 +1,52 @@
//nolint
package slashing
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Local code type
type CodeType = sdk.CodeType
const (
// Default slashing codespace
DefaultCodespace sdk.CodespaceType = 10
// Invalid validator
CodeInvalidValidator CodeType = 201
// Validator jailed
CodeValidatorJailed CodeType = 202
)
func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "That address is not associated with any known validator")
}
func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "Validator does not exist for that address")
}
func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeValidatorJailed, "Validator jailed, cannot yet be unrevoked")
}
func codeToDefaultMsg(code CodeType) string {
switch code {
case CodeInvalidValidator:
return "Invalid Validator"
case CodeValidatorJailed:
return "Validator Jailed"
default:
return sdk.CodeToDefaultMsg(code)
}
}
func msgOrDefaultMsg(msg string, code CodeType) string {
if msg != "" {
return msg
}
return codeToDefaultMsg(code)
}
func newError(codespace sdk.CodespaceType, code CodeType, msg string) sdk.Error {
msg = msgOrDefaultMsg(msg, code)
return sdk.NewError(codespace, code, msg)
}

58
x/slashing/handler.go Normal file
View File

@ -0,0 +1,58 @@
package slashing
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
func NewHandler(k Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
// NOTE msg already has validate basic run
switch msg := msg.(type) {
case MsgUnrevoke:
return handleMsgUnrevoke(ctx, msg, k)
default:
return sdk.ErrTxDecode("invalid message parse in staking module").Result()
}
}
}
// Validators must submit a transaction to unrevoke itself after
// having been revoked (and thus unbonded) for downtime
func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.Result {
// Validator must exist
validator := k.stakeKeeper.Validator(ctx, msg.ValidatorAddr)
if validator == nil {
return ErrNoValidatorForAddress(k.codespace).Result()
}
addr := validator.GetPubKey().Address()
// Signing info must exist
info, found := k.getValidatorSigningInfo(ctx, addr)
if !found {
return ErrNoValidatorForAddress(k.codespace).Result()
}
// Cannot be unrevoked until out of jail
if ctx.BlockHeader().Time < info.JailedUntil {
return ErrValidatorJailed(k.codespace).Result()
}
if ctx.IsCheckTx() {
return sdk.Result{}
}
// Update the starting height (so the validator can't be immediately revoked again)
info.StartHeight = ctx.BlockHeight()
k.setValidatorSigningInfo(ctx, addr, info)
// Unrevoke the validator
k.stakeKeeper.Unrevoke(ctx, validator.GetPubKey())
tags := sdk.NewTags("action", []byte("unrevoke"), "validator", msg.ValidatorAddr.Bytes())
return sdk.Result{
Tags: tags,
}
}

91
x/slashing/keeper.go Normal file
View File

@ -0,0 +1,91 @@
package slashing
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/stake"
crypto "github.com/tendermint/go-crypto"
)
// Keeper of the slashing store
type Keeper struct {
storeKey sdk.StoreKey
cdc *wire.Codec
stakeKeeper stake.Keeper
// codespace
codespace sdk.CodespaceType
}
// NewKeeper creates a slashing keeper
func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, sk stake.Keeper, codespace sdk.CodespaceType) Keeper {
keeper := Keeper{
storeKey: key,
cdc: cdc,
stakeKeeper: sk,
codespace: codespace,
}
return keeper
}
// handle a validator signing two blocks at the same height
func (k Keeper) handleDoubleSign(ctx sdk.Context, height int64, timestamp int64, pubkey crypto.PubKey) {
logger := ctx.Logger().With("module", "x/slashing")
age := ctx.BlockHeader().Time - timestamp
// Double sign too old
if age > MaxEvidenceAge {
logger.Info(fmt.Sprintf("Ignored double sign from %s at height %d, age of %d past max age of %d", pubkey.Address(), height, age, MaxEvidenceAge))
return
}
// Double sign confirmed
logger.Info(fmt.Sprintf("Confirmed double sign from %s at height %d, age of %d less than max age of %d", pubkey.Address(), height, age, MaxEvidenceAge))
k.stakeKeeper.Slash(ctx, pubkey, height, SlashFractionDoubleSign)
}
// handle a validator signature, must be called once per validator per block
func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, signed bool) {
logger := ctx.Logger().With("module", "x/slashing")
height := ctx.BlockHeight()
if !signed {
logger.Info(fmt.Sprintf("Absent validator %s at height %d", pubkey.Address(), height))
}
address := pubkey.Address()
// Local index, so counts blocks validator *should* have signed
// Will use the 0-value default signing info if not present
signInfo, _ := k.getValidatorSigningInfo(ctx, address)
index := signInfo.IndexOffset % SignedBlocksWindow
signInfo.IndexOffset++
// Update signed block bit array & counter
// This counter just tracks the sum of the bit array
// That way we avoid needing to read/write the whole array each time
previous := k.getValidatorSigningBitArray(ctx, address, index)
if previous == signed {
// Array value at this index has not changed, no need to update counter
} else if previous && !signed {
// Array value has changed from signed to unsigned, decrement counter
k.setValidatorSigningBitArray(ctx, address, index, false)
signInfo.SignedBlocksCounter--
} else if !previous && signed {
// Array value has changed from unsigned to signed, increment counter
k.setValidatorSigningBitArray(ctx, address, index, true)
signInfo.SignedBlocksCounter++
}
minHeight := signInfo.StartHeight + SignedBlocksWindow
if height > minHeight && signInfo.SignedBlocksCounter < MinSignedPerWindow {
// Downtime confirmed, slash, revoke, and jail the validator
logger.Info(fmt.Sprintf("Validator %s past min height of %d and below signed blocks threshold of %d", pubkey.Address(), minHeight, MinSignedPerWindow))
k.stakeKeeper.Slash(ctx, pubkey, height, SlashFractionDowntime)
k.stakeKeeper.Revoke(ctx, pubkey)
signInfo.JailedUntil = ctx.BlockHeader().Time + DowntimeUnbondDuration
}
// Set the updated signing info
k.setValidatorSigningInfo(ctx, address, signInfo)
}

131
x/slashing/keeper_test.go Normal file
View File

@ -0,0 +1,131 @@
package slashing
import (
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake"
)
func TestHandleDoubleSign(t *testing.T) {
// initial setup
ctx, ck, sk, keeper := createTestInput(t)
addr, val, amt := addrs[0], pks[0], int64(100)
got := stake.NewHandler(sk)(ctx, newTestMsgDeclareCandidacy(addr, val, amt))
require.True(t, got.IsOK())
sk.Tick(ctx)
require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins - amt}})
require.Equal(t, sdk.NewRat(amt), sk.Validator(ctx, addr).GetPower())
// double sign less than max age
keeper.handleDoubleSign(ctx, 0, 0, val)
require.Equal(t, sdk.NewRat(amt).Mul(sdk.NewRat(19).Quo(sdk.NewRat(20))), sk.Validator(ctx, addr).GetPower())
ctx = ctx.WithBlockHeader(abci.Header{Time: 300})
// double sign past max age
keeper.handleDoubleSign(ctx, 0, 0, val)
require.Equal(t, sdk.NewRat(amt).Mul(sdk.NewRat(19).Quo(sdk.NewRat(20))), sk.Validator(ctx, addr).GetPower())
}
func TestHandleAbsentValidator(t *testing.T) {
// initial setup
ctx, ck, sk, keeper := createTestInput(t)
addr, val, amt := addrs[0], pks[0], int64(100)
sh := stake.NewHandler(sk)
slh := NewHandler(keeper)
got := sh(ctx, newTestMsgDeclareCandidacy(addr, val, amt))
require.True(t, got.IsOK())
sk.Tick(ctx)
require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins - amt}})
require.Equal(t, sdk.NewRat(amt), sk.Validator(ctx, addr).GetPower())
info, found := keeper.getValidatorSigningInfo(ctx, val.Address())
require.False(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, int64(0), info.IndexOffset)
require.Equal(t, int64(0), info.SignedBlocksCounter)
require.Equal(t, int64(0), info.JailedUntil)
height := int64(0)
// 1000 first blocks OK
for ; height < 1000; height++ {
ctx = ctx.WithBlockHeight(height)
keeper.handleValidatorSignature(ctx, val, true)
}
info, found = keeper.getValidatorSigningInfo(ctx, val.Address())
require.True(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, SignedBlocksWindow, info.SignedBlocksCounter)
// 50 blocks missed
for ; height < 1050; height++ {
ctx = ctx.WithBlockHeight(height)
keeper.handleValidatorSignature(ctx, val, false)
}
info, found = keeper.getValidatorSigningInfo(ctx, val.Address())
require.True(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, SignedBlocksWindow-50, info.SignedBlocksCounter)
// validator should be bonded still
validator, _ := sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Bonded, validator.GetStatus())
pool := sk.GetPool(ctx)
require.Equal(t, int64(100), pool.BondedTokens)
// 51st block missed
ctx = ctx.WithBlockHeight(height)
keeper.handleValidatorSignature(ctx, val, false)
info, found = keeper.getValidatorSigningInfo(ctx, val.Address())
require.True(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, SignedBlocksWindow-51, info.SignedBlocksCounter)
// validator should have been revoked
validator, _ = sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Unbonded, validator.GetStatus())
// unrevocation should fail prior to jail expiration
got = slh(ctx, NewMsgUnrevoke(addr))
require.False(t, got.IsOK())
// unrevocation should succeed after jail expiration
ctx = ctx.WithBlockHeader(abci.Header{Time: int64(86400 * 2)})
got = slh(ctx, NewMsgUnrevoke(addr))
require.True(t, got.IsOK())
// validator should be rebonded now
validator, _ = sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Bonded, validator.GetStatus())
// validator should have been slashed
pool = sk.GetPool(ctx)
require.Equal(t, int64(99), pool.BondedTokens)
// validator start height should have been changed
info, found = keeper.getValidatorSigningInfo(ctx, val.Address())
require.True(t, found)
require.Equal(t, height, info.StartHeight)
require.Equal(t, SignedBlocksWindow-51, info.SignedBlocksCounter)
// validator should not be immediately revoked again
height++
ctx = ctx.WithBlockHeight(height)
keeper.handleValidatorSignature(ctx, val, false)
validator, _ = sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Bonded, validator.GetStatus())
// validator should be revoked again after 100 unsigned blocks
nextHeight := height + 100
for ; height <= nextHeight; height++ {
ctx = ctx.WithBlockHeight(height)
keeper.handleValidatorSignature(ctx, val, false)
}
validator, _ = sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Unbonded, validator.GetStatus())
}

46
x/slashing/msg.go Normal file
View File

@ -0,0 +1,46 @@
package slashing
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
var cdc = wire.NewCodec()
// name to identify transaction types
const MsgType = "slashing"
// verify interface at compile time
var _ sdk.Msg = &MsgUnrevoke{}
// MsgUnrevoke - struct for unrevoking revoked validator
type MsgUnrevoke struct {
ValidatorAddr sdk.Address `json:"address"` // address of the validator owner
}
func NewMsgUnrevoke(validatorAddr sdk.Address) MsgUnrevoke {
return MsgUnrevoke{
ValidatorAddr: validatorAddr,
}
}
//nolint
func (msg MsgUnrevoke) Type() string { return MsgType }
func (msg MsgUnrevoke) GetSigners() []sdk.Address { return []sdk.Address{msg.ValidatorAddr} }
// get the bytes for the message signer to sign on
func (msg MsgUnrevoke) GetSignBytes() []byte {
b, err := cdc.MarshalJSON(msg)
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgUnrevoke) ValidateBasic() sdk.Error {
if msg.ValidatorAddr == nil {
return ErrBadValidatorAddr(DefaultCodespace)
}
return nil
}

37
x/slashing/params.go Normal file
View File

@ -0,0 +1,37 @@
package slashing
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
const (
// MaxEvidenceAge - Max age for evidence - 21 days (3 weeks)
// TODO Should this be a governance parameter or just modifiable with SoftwareUpgradeProposals?
// MaxEvidenceAge = 60 * 60 * 24 * 7 * 3
// TODO Temporarily set to 2 minutes for testnets.
MaxEvidenceAge int64 = 60 * 2
// SignedBlocksWindow - sliding window for downtime slashing
// TODO Governance parameter?
// TODO Temporarily set to 100 blocks for testnets
SignedBlocksWindow int64 = 100
// Downtime slashing threshold - 50%
// TODO Governance parameter?
MinSignedPerWindow int64 = SignedBlocksWindow / 2
// Downtime unbond duration
// TODO Governance parameter?
// TODO Temporarily set to 10 minutes for testnets
DowntimeUnbondDuration int64 = 60 * 10
)
var (
// SlashFractionDoubleSign - currently 5%
// TODO Governance parameter?
SlashFractionDoubleSign = sdk.NewRat(1).Quo(sdk.NewRat(20))
// SlashFractionDowntime - currently 1%
// TODO Governance parameter?
SlashFractionDowntime = sdk.NewRat(1).Quo(sdk.NewRat(100))
)

View File

@ -0,0 +1,66 @@
package slashing
import (
"encoding/binary"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Stored by *validator* address (not owner address)
func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.Address) (info validatorSigningInfo, found bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(validatorSigningInfoKey(address))
if bz == nil {
found = false
return
}
k.cdc.MustUnmarshalBinary(bz, &info)
found = true
return
}
// Stored by *validator* address (not owner address)
func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.Address, info validatorSigningInfo) {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinary(info)
store.Set(validatorSigningInfoKey(address), bz)
}
// Stored by *validator* address (not owner address)
func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.Address, index int64) (signed bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(validatorSigningBitArrayKey(address, index))
if bz == nil {
// lazy: treat empty key as unsigned
signed = false
return
}
k.cdc.MustUnmarshalBinary(bz, &signed)
return
}
// Stored by *validator* address (not owner address)
func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.Address, index int64, signed bool) {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinary(signed)
store.Set(validatorSigningBitArrayKey(address, index), bz)
}
type validatorSigningInfo struct {
StartHeight int64 `json:"start_height"` // height at which validator was first a candidate OR was unrevoked
IndexOffset int64 `json:"index_offset"` // index offset into signed block bit array
JailedUntil int64 `json:"jailed_until"` // timestamp validator cannot be unrevoked until
SignedBlocksCounter int64 `json:"signed_blocks_counter"` // signed blocks counter (to avoid scanning the array every time)
}
// Stored by *validator* address (not owner address)
func validatorSigningInfoKey(v sdk.Address) []byte {
return append([]byte{0x01}, v.Bytes()...)
}
// Stored by *validator* address (not owner address)
func validatorSigningBitArrayKey(v sdk.Address, i int64) []byte {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(i))
return append([]byte{0x02}, append(v.Bytes(), b...)...)
}

View File

@ -0,0 +1,35 @@
package slashing
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestGetSetValidatorSigningInfo(t *testing.T) {
ctx, _, _, keeper := createTestInput(t)
info, found := keeper.getValidatorSigningInfo(ctx, addrs[0])
require.False(t, found)
newInfo := validatorSigningInfo{
StartHeight: int64(4),
IndexOffset: int64(3),
JailedUntil: int64(2),
SignedBlocksCounter: int64(10),
}
keeper.setValidatorSigningInfo(ctx, addrs[0], newInfo)
info, found = keeper.getValidatorSigningInfo(ctx, addrs[0])
require.True(t, found)
require.Equal(t, info.StartHeight, int64(4))
require.Equal(t, info.IndexOffset, int64(3))
require.Equal(t, info.JailedUntil, int64(2))
require.Equal(t, info.SignedBlocksCounter, int64(10))
}
func TestGetSetValidatorSigningBitArray(t *testing.T) {
ctx, _, _, keeper := createTestInput(t)
signed := keeper.getValidatorSigningBitArray(ctx, addrs[0], 0)
require.False(t, signed) // treat empty key as unsigned
keeper.setValidatorSigningBitArray(ctx, addrs[0], 0, true)
signed = keeper.getValidatorSigningBitArray(ctx, addrs[0], 0)
require.True(t, signed) // now should be signed
}

95
x/slashing/test_common.go Normal file
View File

@ -0,0 +1,95 @@
package slashing
import (
"encoding/hex"
"os"
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/stake"
)
var (
addrs = []sdk.Address{
testAddr("A58856F0FD53BF058B4909A21AEC019107BA6160"),
testAddr("A58856F0FD53BF058B4909A21AEC019107BA6161"),
}
pks = []crypto.PubKey{
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"),
}
initCoins int64 = 200
)
func createTestCodec() *wire.Codec {
cdc := wire.NewCodec()
sdk.RegisterWire(cdc)
auth.RegisterWire(cdc)
bank.RegisterWire(cdc)
stake.RegisterWire(cdc)
wire.RegisterCrypto(cdc)
return cdc
}
func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, Keeper) {
keyAcc := sdk.NewKVStoreKey("acc")
keyStake := sdk.NewKVStoreKey("stake")
keySlashing := sdk.NewKVStoreKey("slashing")
db := dbm.NewMemDB()
ms := store.NewCommitMultiStore(db)
ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(keySlashing, sdk.StoreTypeIAVL, db)
err := ms.LoadLatestVersion()
require.Nil(t, err)
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewTMLogger(os.Stdout))
cdc := createTestCodec()
accountMapper := auth.NewAccountMapper(cdc, keyAcc, &auth.BaseAccount{})
ck := bank.NewKeeper(accountMapper)
sk := stake.NewKeeper(cdc, keyStake, ck, stake.DefaultCodespace)
genesis := stake.DefaultGenesisState()
genesis.Pool.LooseUnbondedTokens = initCoins * int64(len(addrs))
stake.InitGenesis(ctx, sk, genesis)
for _, addr := range addrs {
ck.AddCoins(ctx, addr, sdk.Coins{
{sk.GetParams(ctx).BondDenom, initCoins},
})
}
keeper := NewKeeper(cdc, keySlashing, sk, DefaultCodespace)
return ctx, ck, sk, keeper
}
func newPubKey(pk string) (res crypto.PubKey) {
pkBytes, err := hex.DecodeString(pk)
if err != nil {
panic(err)
}
var pkEd crypto.PubKeyEd25519
copy(pkEd[:], pkBytes[:])
return pkEd
}
func testAddr(addr string) sdk.Address {
res := []byte(addr)
return res
}
func newTestMsgDeclareCandidacy(address sdk.Address, pubKey crypto.PubKey, amt int64) stake.MsgDeclareCandidacy {
return stake.MsgDeclareCandidacy{
Description: stake.Description{},
ValidatorAddr: address,
PubKey: pubKey,
Bond: sdk.Coin{"steak", amt},
}
}

58
x/slashing/tick.go Normal file
View File

@ -0,0 +1,58 @@
package slashing
import (
"encoding/binary"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
tmtypes "github.com/tendermint/tendermint/types"
)
func NewBeginBlocker(sk Keeper) sdk.BeginBlocker {
return func(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
// Tag the height
heightBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(heightBytes, uint64(req.Header.Height))
tags := sdk.NewTags("height", heightBytes)
// Deal with any equivocation evidence
for _, evidence := range req.ByzantineValidators {
var pk crypto.PubKey
sk.cdc.MustUnmarshalBinary(evidence.PubKey, &pk)
switch string(evidence.Type) {
case tmtypes.DUPLICATE_VOTE:
sk.handleDoubleSign(ctx, evidence.Height, evidence.Time, pk)
default:
ctx.Logger().With("module", "x/slashing").Error(fmt.Sprintf("Ignored unknown evidence type: %s", string(evidence.Type)))
}
}
// Figure out which validators were absent
absent := make(map[crypto.PubKey]struct{})
for _, pubkey := range req.AbsentValidators {
var pk crypto.PubKey
sk.cdc.MustUnmarshalBinary(pubkey, &pk)
absent[pk] = struct{}{}
}
// Iterate over all the validators which *should* have signed this block
sk.stakeKeeper.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) {
pubkey := validator.GetPubKey()
present := true
if _, ok := absent[pubkey]; ok {
present = false
}
sk.handleValidatorSignature(ctx, pubkey, present)
return false
})
// Return the begin block response
// TODO Return something composable, so other modules can also have BeginBlockers
// TODO Add some more tags so clients can track slashing events
return abci.ResponseBeginBlock{
Tags: tags.ToKVPairs(),
}
}
}

12
x/slashing/wire.go Normal file
View File

@ -0,0 +1,12 @@
package slashing
import (
"github.com/cosmos/cosmos-sdk/wire"
)
// Register concrete types on wire codec
func RegisterWire(cdc *wire.Codec) {
cdc.RegisterConcrete(MsgUnrevoke{}, "cosmos-sdk/MsgUnrevoke", nil)
}
var cdcEmpty = wire.NewCodec()

View File

@ -16,6 +16,7 @@ const (
CodeInvalidValidator CodeType = 201 CodeInvalidValidator CodeType = 201
CodeInvalidBond CodeType = 202 CodeInvalidBond CodeType = 202
CodeInvalidInput CodeType = 203 CodeInvalidInput CodeType = 203
CodeValidatorJailed CodeType = 204
CodeUnauthorized CodeType = sdk.CodeUnauthorized CodeUnauthorized CodeType = sdk.CodeUnauthorized
CodeInternal CodeType = sdk.CodeInternal CodeInternal CodeType = sdk.CodeInternal
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest CodeUnknownRequest CodeType = sdk.CodeUnknownRequest

View File

@ -29,14 +29,25 @@ func DefaultGenesisState() GenesisState {
// InitGenesis - store genesis parameters // InitGenesis - store genesis parameters
func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) {
store := ctx.KVStore(k.storeKey)
k.setPool(ctx, data.Pool) k.setPool(ctx, data.Pool)
k.setNewParams(ctx, data.Params) k.setNewParams(ctx, data.Params)
for _, validator := range data.Validators { for _, validator := range data.Validators {
k.updateValidator(ctx, validator)
// set validator
k.setValidator(ctx, validator)
// manually set indexes for the first time
k.setValidatorByPubKeyIndex(ctx, validator)
k.setValidatorByPowerIndex(ctx, validator, data.Pool)
if validator.Status() == sdk.Bonded {
store.Set(GetValidatorsBondedKey(validator.PubKey), validator.Owner)
}
} }
for _, bond := range data.Bonds { for _, bond := range data.Bonds {
k.setDelegation(ctx, bond) k.setDelegation(ctx, bond)
} }
k.updateBondedValidatorsFull(ctx, store)
} }
// WriteGenesis - output genesis parameters // WriteGenesis - output genesis parameters

View File

@ -55,6 +55,7 @@ func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keepe
validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description) validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description)
k.setValidator(ctx, validator) k.setValidator(ctx, validator)
k.setValidatorByPubKeyIndex(ctx, validator)
tags := sdk.NewTags( tags := sdk.NewTags(
"action", []byte("declareCandidacy"), "action", []byte("declareCandidacy"),
"validator", msg.ValidatorAddr.Bytes(), "validator", msg.ValidatorAddr.Bytes(),

View File

@ -8,6 +8,7 @@ import (
"github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/bank"
abci "github.com/tendermint/abci/types" abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
) )
// keeper of the staking store // keeper of the staking store
@ -38,6 +39,16 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.Address) (validator Valid
return k.getValidator(store, addr) return k.getValidator(store, addr)
} }
// get a single validator by pubkey
func (k Keeper) GetValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) (validator Validator, found bool) {
store := ctx.KVStore(k.storeKey)
addr := store.Get(GetValidatorByPubKeyIndexKey(pubkey))
if addr == nil {
return validator, false
}
return k.getValidator(store, addr)
}
// get a single validator (reuse store) // get a single validator (reuse store)
func (k Keeper) getValidator(store sdk.KVStore, addr sdk.Address) (validator Validator, found bool) { func (k Keeper) getValidator(store sdk.KVStore, addr sdk.Address) (validator Validator, found bool) {
b := store.Get(GetValidatorKey(addr)) b := store.Get(GetValidatorKey(addr))
@ -51,10 +62,22 @@ func (k Keeper) getValidator(store sdk.KVStore, addr sdk.Address) (validator Val
// set the main record holding validator details // set the main record holding validator details
func (k Keeper) setValidator(ctx sdk.Context, validator Validator) { func (k Keeper) setValidator(ctx sdk.Context, validator Validator) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
// set main store
bz := k.cdc.MustMarshalBinary(validator) bz := k.cdc.MustMarshalBinary(validator)
store.Set(GetValidatorKey(validator.Owner), bz) store.Set(GetValidatorKey(validator.Owner), bz)
} }
func (k Keeper) setValidatorByPubKeyIndex(ctx sdk.Context, validator Validator) {
store := ctx.KVStore(k.storeKey)
// set pointer by pubkey
store.Set(GetValidatorByPubKeyIndexKey(validator.PubKey), validator.Owner)
}
func (k Keeper) setValidatorByPowerIndex(ctx sdk.Context, validator Validator, pool Pool) {
store := ctx.KVStore(k.storeKey)
store.Set(GetValidatorsByPowerKey(validator, pool), validator.Owner)
}
// Get the set of all validators with no limits, used during genesis dump // Get the set of all validators with no limits, used during genesis dump
func (k Keeper) getAllValidators(ctx sdk.Context) (validators Validators) { func (k Keeper) getAllValidators(ctx sdk.Context) (validators Validators) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
@ -203,8 +226,12 @@ func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) Validator
oldValidator, oldFound := k.GetValidator(ctx, ownerAddr) oldValidator, oldFound := k.GetValidator(ctx, ownerAddr)
if validator.Revoked && oldValidator.Status() == sdk.Bonded { if validator.Revoked && oldValidator.Status() == sdk.Bonded {
validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) validator = k.unbondValidator(ctx, store, validator)
k.setPool(ctx, pool)
// need to also clear the cliff validator spot because the revoke has
// opened up a new spot which will be filled when
// updateValidatorsBonded is called
k.clearCliffValidator(ctx)
} }
powerIncreasing := false powerIncreasing := false
@ -409,7 +436,7 @@ func (k Keeper) updateBondedValidatorsFull(ctx sdk.Context, store sdk.KVStore) {
} }
// perform all the store operations for when a validator status becomes unbonded // perform all the store operations for when a validator status becomes unbonded
func (k Keeper) unbondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator) { func (k Keeper) unbondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator) Validator {
pool := k.GetPool(ctx) pool := k.GetPool(ctx)
// sanity check // sanity check
@ -431,6 +458,7 @@ func (k Keeper) unbondValidator(ctx sdk.Context, store sdk.KVStore, validator Va
// also remove from the Bonded Validators Store // also remove from the Bonded Validators Store
store.Delete(GetValidatorsBondedKey(validator.PubKey)) store.Delete(GetValidatorsBondedKey(validator.PubKey))
return validator
} }
// perform all the store operations for when a validator status becomes bonded // perform all the store operations for when a validator status becomes bonded
@ -470,6 +498,7 @@ func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
pool := k.getPool(store) pool := k.getPool(store)
store.Delete(GetValidatorKey(address)) store.Delete(GetValidatorKey(address))
store.Delete(GetValidatorByPubKeyIndexKey(validator.PubKey))
store.Delete(GetValidatorsByPowerKey(validator, pool)) store.Delete(GetValidatorsByPowerKey(validator, pool))
// delete from the current and power weighted validator groups if the validator // delete from the current and power weighted validator groups if the validator
@ -656,6 +685,13 @@ func (k Keeper) setCliffValidator(ctx sdk.Context, validator Validator, pool Poo
store.Set(ValidatorCliffKey, validator.Owner) store.Set(ValidatorCliffKey, validator.Owner)
} }
// clear the current validator and power of the validator on the cliff
func (k Keeper) clearCliffValidator(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
store.Delete(ValidatorPowerCliffKey)
store.Delete(ValidatorCliffKey)
}
//__________________________________________________________________________ //__________________________________________________________________________
// Implements ValidatorSet // Implements ValidatorSet
@ -749,3 +785,46 @@ func (k Keeper) IterateDelegators(ctx sdk.Context, delAddr sdk.Address, fn func(
} }
iterator.Close() iterator.Close()
} }
// slash a validator
func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, fraction sdk.Rat) {
// TODO height ignored for now, see https://github.com/cosmos/cosmos-sdk/pull/1011#issuecomment-390253957
logger := ctx.Logger().With("module", "x/stake")
val, found := k.GetValidatorByPubKey(ctx, pubkey)
if !found {
panic(fmt.Errorf("Attempted to slash a nonexistent validator with address %s", pubkey.Address()))
}
sharesToRemove := val.PoolShares.Amount.Mul(fraction)
pool := k.GetPool(ctx)
val, pool, burned := val.removePoolShares(pool, sharesToRemove)
k.setPool(ctx, pool) // update the pool
k.updateValidator(ctx, val) // update the validator, possibly kicking it out
logger.Info(fmt.Sprintf("Validator %s slashed by fraction %v, removed %v shares and burned %d tokens", pubkey.Address(), fraction, sharesToRemove, burned))
return
}
// revoke a validator
func (k Keeper) Revoke(ctx sdk.Context, pubkey crypto.PubKey) {
logger := ctx.Logger().With("module", "x/stake")
val, found := k.GetValidatorByPubKey(ctx, pubkey)
if !found {
panic(fmt.Errorf("Validator with pubkey %s not found, cannot revoke", pubkey))
}
val.Revoked = true
k.updateValidator(ctx, val) // update the validator, now revoked
logger.Info(fmt.Sprintf("Validator %s revoked", pubkey.Address()))
return
}
// unrevoke a validator
func (k Keeper) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) {
logger := ctx.Logger().With("module", "x/stake")
val, found := k.GetValidatorByPubKey(ctx, pubkey)
if !found {
panic(fmt.Errorf("Validator with pubkey %s not found, cannot unrevoke", pubkey))
}
val.Revoked = false
k.updateValidator(ctx, val) // update the validator, now unrevoked
logger.Info(fmt.Sprintf("Validator %s unrevoked", pubkey.Address()))
return
}

View File

@ -16,13 +16,14 @@ var (
ParamKey = []byte{0x00} // key for parameters relating to staking ParamKey = []byte{0x00} // key for parameters relating to staking
PoolKey = []byte{0x01} // key for the staking pools PoolKey = []byte{0x01} // key for the staking pools
ValidatorsKey = []byte{0x02} // prefix for each key to a validator ValidatorsKey = []byte{0x02} // prefix for each key to a validator
ValidatorsBondedKey = []byte{0x03} // prefix for each key to bonded/actively validating validators ValidatorsByPubKeyIndexKey = []byte{0x03} // prefix for each key to a validator by pubkey
ValidatorsByPowerKey = []byte{0x04} // prefix for each key to a validator sorted by power ValidatorsBondedKey = []byte{0x04} // prefix for each key to bonded/actively validating validators
ValidatorCliffKey = []byte{0x05} // key for block-local tx index ValidatorsByPowerKey = []byte{0x05} // prefix for each key to a validator sorted by power
ValidatorPowerCliffKey = []byte{0x06} // key for block-local tx index ValidatorCliffKey = []byte{0x06} // key for block-local tx index
TendermintUpdatesKey = []byte{0x07} // prefix for each key to a validator which is being updated ValidatorPowerCliffKey = []byte{0x07} // key for block-local tx index
DelegationKey = []byte{0x08} // prefix for each key to a delegator's bond TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated
IntraTxCounterKey = []byte{0x09} // key for block-local tx index DelegationKey = []byte{0x09} // prefix for each key to a delegator's bond
IntraTxCounterKey = []byte{0x10} // key for block-local tx index
) )
const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch
@ -32,6 +33,11 @@ func GetValidatorKey(ownerAddr sdk.Address) []byte {
return append(ValidatorsKey, ownerAddr.Bytes()...) return append(ValidatorsKey, ownerAddr.Bytes()...)
} }
// get the key for the validator with pubkey
func GetValidatorByPubKeyIndexKey(pubkey crypto.PubKey) []byte {
return append(ValidatorsByPubKeyIndexKey, pubkey.Bytes()...)
}
// get the key for the current validator group, ordered like tendermint // get the key for the current validator group, ordered like tendermint
func GetValidatorsBondedKey(pk crypto.PubKey) []byte { func GetValidatorsBondedKey(pk crypto.PubKey) []byte {
addr := pk.Address() addr := pk.Address()

View File

@ -47,6 +47,7 @@ func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Descripti
return Validator{ return Validator{
Owner: owner, Owner: owner,
PubKey: pubKey, PubKey: pubKey,
Revoked: false,
PoolShares: NewUnbondedShares(sdk.ZeroRat()), PoolShares: NewUnbondedShares(sdk.ZeroRat()),
DelegatorShares: sdk.ZeroRat(), DelegatorShares: sdk.ZeroRat(),
Description: description, Description: description,
@ -154,6 +155,23 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator,
return v, pool return v, pool
} }
// Remove pool shares
// Returns corresponding tokens, which could be burned (e.g. when slashing
// a validator) or redistributed elsewhere
func (v Validator) removePoolShares(pool Pool, poolShares sdk.Rat) (Validator, Pool, int64) {
var tokens int64
switch v.Status() {
case sdk.Unbonded:
pool, tokens = pool.removeSharesUnbonded(poolShares)
case sdk.Unbonding:
pool, tokens = pool.removeSharesUnbonding(poolShares)
case sdk.Bonded:
pool, tokens = pool.removeSharesBonded(poolShares)
}
v.PoolShares.Amount = v.PoolShares.Amount.Sub(poolShares)
return v, pool, tokens
}
// XXX TEST // XXX TEST
// get the power or potential power for a validator // get the power or potential power for a validator
// if bonded, the power is the BondedShares // if bonded, the power is the BondedShares