Merge pull request #1011 from cosmos/cwgoes/slashing
Implement slashing (v1)
This commit is contained in:
commit
0fb2bbdfef
|
@ -11,13 +11,13 @@
|
|||
branch = "master"
|
||||
name = "github.com/btcsuite/btcd"
|
||||
packages = ["btcec"]
|
||||
revision = "1432d294a5b055c297457c25434efbf13384cc46"
|
||||
revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/cosmos/bech32cosmos"
|
||||
packages = ["go"]
|
||||
revision = "efca97cd8c0852c44d96dfdcc70565c306eddff0"
|
||||
revision = "c12e4b6ed52acc1d35fea49acec35f980914dc95"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
|
@ -256,7 +256,7 @@
|
|||
"leveldb/table",
|
||||
"leveldb/util"
|
||||
]
|
||||
revision = "e6d6b529196422703d54ff5c40e79809ec2020b3"
|
||||
revision = "5d6fca44a948d2be89a9702de7717f0168403d3d"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/abci"
|
||||
|
@ -267,8 +267,8 @@
|
|||
"server",
|
||||
"types"
|
||||
]
|
||||
revision = "78a8905690ef54f9d57e3b2b0ee7ad3a04ef3f1f"
|
||||
version = "v0.10.3"
|
||||
revision = "f9dce537281ffba5d1e047e6729429f7e5fb90c9"
|
||||
version = "v0.11.0-rc0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
@ -298,17 +298,14 @@
|
|||
revision = "915416979bf70efa4bcbf1c6cd5d64c5fff9fc19"
|
||||
version = "v0.6.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/go-wire"
|
||||
packages = ["."]
|
||||
revision = "fa721242b042ecd4c6ed1a934ee740db4f74e45c"
|
||||
version = "v0.7.3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/iavl"
|
||||
packages = ["."]
|
||||
revision = "fd37a0fa3a7454423233bc3d5ea828f38e0af787"
|
||||
version = "v0.7.0"
|
||||
packages = [
|
||||
".",
|
||||
"sha256truncated"
|
||||
]
|
||||
revision = "c9206995e8f948e99927f5084a88a7e94ca256da"
|
||||
version = "v0.8.0-rc0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/tendermint"
|
||||
|
@ -319,6 +316,9 @@
|
|||
"consensus",
|
||||
"consensus/types",
|
||||
"evidence",
|
||||
"libs/events",
|
||||
"libs/pubsub",
|
||||
"libs/pubsub/query",
|
||||
"lite",
|
||||
"lite/client",
|
||||
"lite/errors",
|
||||
|
@ -347,8 +347,8 @@
|
|||
"types/priv_validator",
|
||||
"version"
|
||||
]
|
||||
revision = "018e096748bafe1d2d1e69b909e4158f3b26f6b2"
|
||||
version = "v0.19.5-rc1"
|
||||
revision = "73de99ecab464208f6ea3a96525f4e4b78425e61"
|
||||
version = "v0.20.0-rc0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/tmlibs"
|
||||
|
@ -361,12 +361,10 @@
|
|||
"db",
|
||||
"flowrate",
|
||||
"log",
|
||||
"merkle",
|
||||
"pubsub",
|
||||
"pubsub/query"
|
||||
"merkle"
|
||||
]
|
||||
revision = "cc5f287c4798ffe88c04d02df219ecb6932080fd"
|
||||
version = "v0.8.3-rc0"
|
||||
revision = "d970af87248a4e162590300dbb74e102183a417d"
|
||||
version = "v0.8.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
@ -382,7 +380,7 @@
|
|||
"ripemd160",
|
||||
"salsa20/salsa"
|
||||
]
|
||||
revision = "1a580b3eff7814fc9b40602fd35256c63b50f491"
|
||||
revision = "ab813273cd59e1333f7ae7bff5d027d4aadf528c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
@ -396,13 +394,13 @@
|
|||
"internal/timeseries",
|
||||
"trace"
|
||||
]
|
||||
revision = "57065200b4b034a1c8ad54ff77069408c2218ae6"
|
||||
revision = "1e491301e022f8f977054da4c2d852decd59571f"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
revision = "7c87d13f8e835d2fb3a70a2912c811ed0c1d241b"
|
||||
revision = "c11f84a56e43e20a78cee75a7c034031ecf57d1f"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/text"
|
||||
|
@ -463,6 +461,6 @@
|
|||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "f0c6224dc5f30c1a7dea716d619665831ea0932b0eb9afc6ac897dbc459134fa"
|
||||
inputs-digest = "a6a5d886519fa9ca97a23715faa852ee14ecb5337e03641d19ea3d3d1c392fee"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
24
Gopkg.toml
24
Gopkg.toml
|
@ -54,43 +54,37 @@
|
|||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/abci"
|
||||
version = "~0.10.3"
|
||||
version = "0.11.0-rc0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/go-crypto"
|
||||
version = "~0.6.2"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/tendermint/go-wire"
|
||||
version = "0.7.3"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/go-amino"
|
||||
version = "~0.9.9"
|
||||
version = "=0.9.9"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/iavl"
|
||||
version = "~0.7.0"
|
||||
version = "0.8.0-rc0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/tendermint"
|
||||
version = "0.19.5-rc1"
|
||||
version = "0.20.0-rc0"
|
||||
|
||||
[[override]]
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/tmlibs"
|
||||
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 ...
|
||||
[[override]]
|
||||
name = "google.golang.org/genproto"
|
||||
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/cosmos/bech32cosmos"
|
||||
branch = "master"
|
||||
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
|
|
|
@ -65,9 +65,10 @@ type BaseApp struct {
|
|||
// See methods setCheckState and setDeliverState.
|
||||
// .valUpdates accumulate in DeliverTx and are reset in BeginBlock.
|
||||
// QUESTION: should we put valUpdates in the deliverState.ctx?
|
||||
checkState *state // for CheckTx
|
||||
deliverState *state // for DeliverTx
|
||||
valUpdates []abci.Validator // cached validator changes from DeliverTx
|
||||
checkState *state // for CheckTx
|
||||
deliverState *state // for DeliverTx
|
||||
valUpdates []abci.Validator // cached validator changes from DeliverTx
|
||||
absentValidators [][]byte // absent validators from begin block
|
||||
}
|
||||
|
||||
var _ abci.Application = (*BaseApp)(nil)
|
||||
|
@ -384,6 +385,8 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg
|
|||
if app.beginBlocker != nil {
|
||||
res = app.beginBlocker(app.deliverState.ctx, req)
|
||||
}
|
||||
// set the absent validators for addition to context in deliverTx
|
||||
app.absentValidators = req.AbsentValidators
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -493,6 +496,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
|||
ctx = app.checkState.ctx.WithTxBytes(txBytes)
|
||||
} else {
|
||||
ctx = app.deliverState.ctx.WithTxBytes(txBytes)
|
||||
ctx = ctx.WithAbsentValidators(app.absentValidators)
|
||||
}
|
||||
|
||||
// Simulate a DeliverTx for gas calculation
|
||||
|
|
|
@ -183,7 +183,7 @@ func TestInitChainer(t *testing.T) {
|
|||
|
||||
// set initChainer and try again - should see the value
|
||||
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()
|
||||
res = app.Query(query)
|
||||
assert.Equal(t, value, res.Value)
|
||||
|
|
|
@ -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/slashing"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
)
|
||||
|
||||
|
@ -34,10 +35,11 @@ type GaiaApp struct {
|
|||
cdc *wire.Codec
|
||||
|
||||
// keys to access the substores
|
||||
keyMain *sdk.KVStoreKey
|
||||
keyAccount *sdk.KVStoreKey
|
||||
keyIBC *sdk.KVStoreKey
|
||||
keyStake *sdk.KVStoreKey
|
||||
keyMain *sdk.KVStoreKey
|
||||
keyAccount *sdk.KVStoreKey
|
||||
keyIBC *sdk.KVStoreKey
|
||||
keyStake *sdk.KVStoreKey
|
||||
keySlashing *sdk.KVStoreKey
|
||||
|
||||
// Manage getting and setting accounts
|
||||
accountMapper auth.AccountMapper
|
||||
|
@ -45,6 +47,7 @@ type GaiaApp struct {
|
|||
coinKeeper bank.Keeper
|
||||
ibcMapper ibc.Mapper
|
||||
stakeKeeper stake.Keeper
|
||||
slashingKeeper slashing.Keeper
|
||||
}
|
||||
|
||||
func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
||||
|
@ -52,12 +55,13 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
|||
|
||||
// create your application object
|
||||
var app = &GaiaApp{
|
||||
BaseApp: bam.NewBaseApp(appName, cdc, logger, db),
|
||||
cdc: cdc,
|
||||
keyMain: sdk.NewKVStoreKey("main"),
|
||||
keyAccount: sdk.NewKVStoreKey("acc"),
|
||||
keyIBC: sdk.NewKVStoreKey("ibc"),
|
||||
keyStake: sdk.NewKVStoreKey("stake"),
|
||||
BaseApp: bam.NewBaseApp(appName, cdc, logger, db),
|
||||
cdc: cdc,
|
||||
keyMain: sdk.NewKVStoreKey("main"),
|
||||
keyAccount: sdk.NewKVStoreKey("acc"),
|
||||
keyIBC: sdk.NewKVStoreKey("ibc"),
|
||||
keyStake: sdk.NewKVStoreKey("stake"),
|
||||
keySlashing: sdk.NewKVStoreKey("slashing"),
|
||||
}
|
||||
|
||||
// define the accountMapper
|
||||
|
@ -71,6 +75,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
|||
app.coinKeeper = bank.NewKeeper(app.accountMapper)
|
||||
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.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.RegisterCodespace(slashing.DefaultCodespace))
|
||||
|
||||
// register message routes
|
||||
app.Router().
|
||||
|
@ -80,9 +85,10 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
|||
|
||||
// initialize BaseApp
|
||||
app.SetInitChainer(app.initChainer)
|
||||
app.SetBeginBlocker(slashing.NewBeginBlocker(app.slashingKeeper))
|
||||
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.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing)
|
||||
err := app.LoadLatestVersion(app.keyMain)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
|
@ -97,6 +103,7 @@ func MakeCodec() *wire.Codec {
|
|||
ibc.RegisterWire(cdc)
|
||||
bank.RegisterWire(cdc)
|
||||
stake.RegisterWire(cdc)
|
||||
slashing.RegisterWire(cdc)
|
||||
auth.RegisterWire(cdc)
|
||||
sdk.RegisterWire(cdc)
|
||||
wire.RegisterCrypto(cdc)
|
||||
|
@ -105,7 +112,8 @@ func MakeCodec() *wire.Codec {
|
|||
|
||||
// custom logic for gaia initialization
|
||||
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
|
||||
err := app.cdc.UnmarshalJSON(stateJSON, &genesisState)
|
||||
|
|
|
@ -104,7 +104,7 @@ func MakeCodec() *wire.Codec {
|
|||
|
||||
// Custom logic for basecoin initialization
|
||||
func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
stateJSON := req.AppStateBytes
|
||||
stateJSON := req.GenesisBytes
|
||||
|
||||
genesisState := new(types.GenesisState)
|
||||
err := app.cdc.UnmarshalJSON(stateJSON, genesisState)
|
||||
|
|
|
@ -118,7 +118,7 @@ func MakeCodec() *wire.Codec {
|
|||
// custom logic for democoin initialization
|
||||
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
|
||||
stateJSON := req.GenesisBytes
|
||||
|
||||
genesisState := new(types.GenesisState)
|
||||
err := app.cdc.UnmarshalJSON(stateJSON, genesisState)
|
||||
|
|
|
@ -35,9 +35,9 @@ func TestKeeperGetSet(t *testing.T) {
|
|||
cdc := wire.NewCodec()
|
||||
auth.RegisterBaseAccount(cdc)
|
||||
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
|
||||
accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{})
|
||||
stakeKeeper := NewKeeper(capKey, bank.NewKeeper(accountMapper), DefaultCodespace)
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
|
||||
addr := sdk.Address([]byte("some-address"))
|
||||
|
||||
bi := stakeKeeper.getBondInfo(ctx, addr)
|
||||
|
|
|
@ -88,7 +88,7 @@ type GenesisJSON struct {
|
|||
// with key/value pairs
|
||||
func InitChainer(key sdk.StoreKey) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain {
|
||||
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
stateJSON := req.AppStateBytes
|
||||
stateJSON := req.GenesisBytes
|
||||
|
||||
genesisState := new(GenesisJSON)
|
||||
err := json.Unmarshal(stateJSON, genesisState)
|
||||
|
|
|
@ -26,7 +26,7 @@ func TestInitApp(t *testing.T) {
|
|||
|
||||
//TODO test validators in the init chain?
|
||||
req := abci.RequestInitChain{
|
||||
AppStateBytes: appState,
|
||||
GenesisBytes: appState,
|
||||
}
|
||||
app.InitChain(req)
|
||||
app.Commit()
|
||||
|
|
|
@ -31,6 +31,7 @@ type Context struct {
|
|||
|
||||
// create a new context
|
||||
func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byte, logger log.Logger) Context {
|
||||
|
||||
c := Context{
|
||||
Context: context.Background(),
|
||||
pst: newThePast(),
|
||||
|
@ -43,6 +44,7 @@ func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byt
|
|||
c = c.WithIsCheckTx(isCheckTx)
|
||||
c = c.WithTxBytes(txBytes)
|
||||
c = c.WithLogger(logger)
|
||||
c = c.WithAbsentValidators(nil)
|
||||
c = c.WithGasMeter(NewInfiniteGasMeter())
|
||||
return c
|
||||
}
|
||||
|
@ -128,6 +130,7 @@ const (
|
|||
contextKeyIsCheckTx
|
||||
contextKeyTxBytes
|
||||
contextKeyLogger
|
||||
contextKeyAbsentValidators
|
||||
contextKeyGasMeter
|
||||
)
|
||||
|
||||
|
@ -157,6 +160,9 @@ func (c Context) TxBytes() []byte {
|
|||
func (c Context) Logger() log.Logger {
|
||||
return c.Value(contextKeyLogger).(log.Logger)
|
||||
}
|
||||
func (c Context) AbsentValidators() [][]byte {
|
||||
return c.Value(contextKeyAbsentValidators).([][]byte)
|
||||
}
|
||||
func (c Context) GasMeter() 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 {
|
||||
return c.withValue(contextKeyLogger, logger)
|
||||
}
|
||||
func (c Context) WithAbsentValidators(AbsentValidators [][]byte) Context {
|
||||
return c.withValue(contextKeyAbsentValidators, AbsentValidators)
|
||||
}
|
||||
func (c Context) WithGasMeter(meter GasMeter) Context {
|
||||
return c.withValue(contextKeyGasMeter, meter)
|
||||
}
|
||||
|
|
|
@ -56,8 +56,11 @@ type ValidatorSet interface {
|
|||
IterateValidatorsBonded(Context,
|
||||
func(index int64, validator Validator) (stop bool))
|
||||
|
||||
Validator(Context, Address) Validator // get a particular validator by owner address
|
||||
TotalPower(Context) Rat // total power of the validator set
|
||||
Validator(Context, Address) Validator // get a particular validator by owner address
|
||||
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
|
||||
}
|
||||
|
||||
//_______________________________________________________________________________
|
||||
|
|
|
@ -25,6 +25,11 @@ func (t Tags) AppendTags(a Tags) Tags {
|
|||
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
|
||||
func NewTags(tags ...interface{}) Tags {
|
||||
var ret Tags
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
)
|
|
@ -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...)...)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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},
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
|
@ -16,6 +16,7 @@ const (
|
|||
CodeInvalidValidator CodeType = 201
|
||||
CodeInvalidBond CodeType = 202
|
||||
CodeInvalidInput CodeType = 203
|
||||
CodeValidatorJailed CodeType = 204
|
||||
CodeUnauthorized CodeType = sdk.CodeUnauthorized
|
||||
CodeInternal CodeType = sdk.CodeInternal
|
||||
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest
|
||||
|
|
|
@ -29,14 +29,25 @@ func DefaultGenesisState() GenesisState {
|
|||
|
||||
// InitGenesis - store genesis parameters
|
||||
func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
k.setPool(ctx, data.Pool)
|
||||
k.setNewParams(ctx, data.Params)
|
||||
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 {
|
||||
k.setDelegation(ctx, bond)
|
||||
}
|
||||
k.updateBondedValidatorsFull(ctx, store)
|
||||
}
|
||||
|
||||
// WriteGenesis - output genesis parameters
|
||||
|
|
|
@ -55,6 +55,7 @@ func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keepe
|
|||
|
||||
validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description)
|
||||
k.setValidator(ctx, validator)
|
||||
k.setValidatorByPubKeyIndex(ctx, validator)
|
||||
tags := sdk.NewTags(
|
||||
"action", []byte("declareCandidacy"),
|
||||
"validator", msg.ValidatorAddr.Bytes(),
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
abci "github.com/tendermint/abci/types"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
func (k Keeper) getValidator(store sdk.KVStore, addr sdk.Address) (validator Validator, found bool) {
|
||||
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
|
||||
func (k Keeper) setValidator(ctx sdk.Context, validator Validator) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
// set main store
|
||||
bz := k.cdc.MustMarshalBinary(validator)
|
||||
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
|
||||
func (k Keeper) getAllValidators(ctx sdk.Context) (validators Validators) {
|
||||
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)
|
||||
|
||||
if validator.Revoked && oldValidator.Status() == sdk.Bonded {
|
||||
validator, pool = validator.UpdateStatus(pool, sdk.Unbonded)
|
||||
k.setPool(ctx, pool)
|
||||
validator = k.unbondValidator(ctx, store, validator)
|
||||
|
||||
// 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
|
||||
|
@ -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
|
||||
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)
|
||||
|
||||
// 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
|
||||
store.Delete(GetValidatorsBondedKey(validator.PubKey))
|
||||
return validator
|
||||
}
|
||||
|
||||
// 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)
|
||||
pool := k.getPool(store)
|
||||
store.Delete(GetValidatorKey(address))
|
||||
store.Delete(GetValidatorByPubKeyIndexKey(validator.PubKey))
|
||||
store.Delete(GetValidatorsByPowerKey(validator, pool))
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -749,3 +785,46 @@ func (k Keeper) IterateDelegators(ctx sdk.Context, delAddr sdk.Address, fn func(
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -13,16 +13,17 @@ import (
|
|||
//nolint
|
||||
var (
|
||||
// Keys for store prefixes
|
||||
ParamKey = []byte{0x00} // key for parameters relating to staking
|
||||
PoolKey = []byte{0x01} // key for the staking pools
|
||||
ValidatorsKey = []byte{0x02} // prefix for each key to a validator
|
||||
ValidatorsBondedKey = []byte{0x03} // prefix for each key to bonded/actively validating validators
|
||||
ValidatorsByPowerKey = []byte{0x04} // prefix for each key to a validator sorted by power
|
||||
ValidatorCliffKey = []byte{0x05} // key for block-local tx index
|
||||
ValidatorPowerCliffKey = []byte{0x06} // key for block-local tx index
|
||||
TendermintUpdatesKey = []byte{0x07} // prefix for each key to a validator which is being updated
|
||||
DelegationKey = []byte{0x08} // prefix for each key to a delegator's bond
|
||||
IntraTxCounterKey = []byte{0x09} // key for block-local tx index
|
||||
ParamKey = []byte{0x00} // key for parameters relating to staking
|
||||
PoolKey = []byte{0x01} // key for the staking pools
|
||||
ValidatorsKey = []byte{0x02} // prefix for each key to a validator
|
||||
ValidatorsByPubKeyIndexKey = []byte{0x03} // prefix for each key to a validator by pubkey
|
||||
ValidatorsBondedKey = []byte{0x04} // prefix for each key to bonded/actively validating validators
|
||||
ValidatorsByPowerKey = []byte{0x05} // prefix for each key to a validator sorted by power
|
||||
ValidatorCliffKey = []byte{0x06} // key for block-local tx index
|
||||
ValidatorPowerCliffKey = []byte{0x07} // key for block-local tx index
|
||||
TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated
|
||||
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
|
||||
|
@ -32,6 +33,11 @@ func GetValidatorKey(ownerAddr sdk.Address) []byte {
|
|||
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
|
||||
func GetValidatorsBondedKey(pk crypto.PubKey) []byte {
|
||||
addr := pk.Address()
|
||||
|
|
|
@ -47,6 +47,7 @@ func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Descripti
|
|||
return Validator{
|
||||
Owner: owner,
|
||||
PubKey: pubKey,
|
||||
Revoked: false,
|
||||
PoolShares: NewUnbondedShares(sdk.ZeroRat()),
|
||||
DelegatorShares: sdk.ZeroRat(),
|
||||
Description: description,
|
||||
|
@ -154,6 +155,23 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator,
|
|||
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
|
||||
// get the power or potential power for a validator
|
||||
// if bonded, the power is the BondedShares
|
||||
|
|
Loading…
Reference in New Issue