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
30 changed files with 895 additions and 77 deletions
+52
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
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
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
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
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
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))
)
+66
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...)...)
}
+35
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
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
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
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()
+1
View File
@@ -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
+12 -1
View File
@@ -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
+1
View File
@@ -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(),
+82 -3
View File
@@ -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
}
+16 -10
View File
@@ -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()
+18
View File
@@ -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