ForceUnbond() implementation WIP

This commit is contained in:
Christopher Goes 2018-05-26 00:13:29 +02:00
parent 796948b838
commit e614799d0f
No known key found for this signature in database
GPG Key ID: E828D98232D328D3
8 changed files with 110 additions and 19 deletions

View File

@ -67,10 +67,10 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, height int64, timestamp int64,
logger := ctx.Logger().With("module", "x/slashing")
age := ctx.BlockHeader().Time - timestamp
if age > MaxEvidenceAge {
logger.Info(fmt.Sprintf("Ignored double sign from %v at height %d, age of %d past max age of %d", pubkey.Address(), height, 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
}
logger.Info(fmt.Sprintf("Confirmed double sign from %v at height %d, age of %d less than max age of %d", pubkey.Address(), height, age, MaxEvidenceAge))
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)
}
@ -79,7 +79,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey,
logger := ctx.Logger().With("module", "x/slashing")
height := ctx.BlockHeight()
if !signed {
logger.Info(fmt.Sprintf("Absent validator %v at height %d", pubkey.Address(), height))
logger.Info(fmt.Sprintf("Absent validator %s at height %d", pubkey.Address(), height))
}
index := height % SignedBlocksWindow
address := pubkey.Address()
@ -96,8 +96,9 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey,
}
minHeight := signInfo.StartHeight + SignedBlocksWindow
if height > minHeight && signInfo.SignedBlocksCounter < MinSignedPerWindow {
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.ForceUnbond(ctx, pubkey, DowntimeUnbondDuration)
k.stakeKeeper.ForceUnbond(ctx, pubkey, DowntimeUnbondDuration) // TODO
}
}

View File

@ -58,7 +58,9 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, Keep
accountMapper := auth.NewAccountMapper(cdc, keyAcc, &auth.BaseAccount{})
ck := bank.NewKeeper(accountMapper)
sk := stake.NewKeeper(cdc, keyStake, ck, stake.DefaultCodespace)
stake.InitGenesis(ctx, sk, stake.DefaultGenesisState())
genesis := stake.DefaultGenesisState()
genesis.Pool.BondedTokens = initCoins * int64(len(addrs))
stake.InitGenesis(ctx, sk, genesis)
for _, addr := range addrs {
ck.AddCoins(ctx, addr, sdk.Coins{
{sk.GetParams(ctx).BondDenom, initCoins},
@ -86,7 +88,8 @@ func TestHandleDoubleSign(t *testing.T) {
func TestHandleAbsentValidator(t *testing.T) {
ctx, ck, sk, keeper := createTestInput(t)
addr, val, amt := addrs[0], pks[0], int64(10)
got := stake.NewHandler(sk)(ctx, newTestMsgDeclareCandidacy(addr, val, amt))
sh := stake.NewHandler(sk)
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}})
@ -114,6 +117,9 @@ func TestHandleAbsentValidator(t *testing.T) {
require.True(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, SignedBlocksWindow-50, info.SignedBlocksCounter)
// should be bonded still
validator := sk.ValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Bonded, validator.GetStatus())
// 51st block missed
ctx = ctx.WithBlockHeight(height)
keeper.handleValidatorSignature(ctx, val, false)
@ -122,6 +128,16 @@ func TestHandleAbsentValidator(t *testing.T) {
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, SignedBlocksWindow-51, info.SignedBlocksCounter)
height++
// should have been revoked
validator = sk.ValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Unbonded, validator.GetStatus())
got = sh(ctx, stake.NewMsgUnrevoke(addr))
require.False(t, got.IsOK()) // should fail prior to jail expiration
ctx = ctx.WithBlockHeader(abci.Header{Time: int64(86400 * 2)})
got = sh(ctx, stake.NewMsgUnrevoke(addr))
require.True(t, got.IsOK()) // should succeed after jail expiration
validator = sk.ValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Bonded, validator.GetStatus())
// should have been slashed
require.Equal(t, sdk.NewRat(amt).Mul(sdk.NewRat(99).Quo(sdk.NewRat(100))), sk.Validator(ctx, addr).GetPower())
}

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
@ -30,6 +31,8 @@ func codeToDefaultMsg(code CodeType) string {
return "Invalid Bond"
case CodeInvalidInput:
return "Invalid Input"
case CodeValidatorJailed:
return "Validator Jailed"
case CodeUnauthorized:
return "Unauthorized"
case CodeInternal:
@ -98,6 +101,9 @@ func ErrBadShares(codespace sdk.CodespaceType) sdk.Error {
func ErrBadRemoveValidator(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "Error removing validator")
}
func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeValidatorJailed, "Validator jailed, cannot yet be unrevoked")
}
//----------------------------------------

View File

@ -19,6 +19,8 @@ func NewHandler(k Keeper) sdk.Handler {
return handleMsgDelegate(ctx, msg, k)
case MsgUnbond:
return handleMsgUnbond(ctx, msg, k)
case MsgUnrevoke:
return handleMsgUnrevoke(ctx, msg, k)
default:
return sdk.ErrTxDecode("invalid message parse in staking module").Result()
}
@ -247,3 +249,26 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result {
Tags: tags,
}
}
func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.Result {
validator, found := k.GetValidator(ctx, msg.ValidatorAddr)
if !found {
return ErrNoValidatorForAddress(k.codespace).Result()
}
if ctx.BlockHeader().Time < validator.RevokedUntilTime {
return ErrValidatorJailed(k.codespace).Result()
}
if ctx.IsCheckTx() {
return sdk.Result{}
}
validator.Revoked = false
k.updateValidator(ctx, validator)
tags := sdk.NewTags("action", []byte("unrevoke"), "validator", msg.ValidatorAddr.Bytes())
return sdk.Result{
Tags: tags,
}
}

View File

@ -220,7 +220,9 @@ func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) Validator
oldValidator, oldFound := k.GetValidator(ctx, ownerAddr)
if validator.Revoked && oldValidator.Status() == sdk.Bonded {
fmt.Printf("val preupdate: %v\n", validator)
validator, pool = validator.UpdateStatus(pool, sdk.Unbonded)
fmt.Printf("val postupdate: %v\n", validator)
k.setPool(ctx, pool)
}
@ -783,25 +785,29 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, fract
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 pubkey %s", pubkey))
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 %v slashed by fraction %v, removed %v shares and burned %d tokens", pubkey, fraction, sharesToRemove, burned))
logger.Info(fmt.Sprintf("Validator %s slashed by fraction %v, removed %v shares and burned %d tokens", pubkey.Address(), fraction, sharesToRemove, burned))
return
}
// force unbond a validator
func (k Keeper) ForceUnbond(ctx sdk.Context, pubkey crypto.PubKey, jailDuration int64) {
// TODO Implement
/*
val, found := k.GetValidatorByPubKey(ctx, pubkey)
if !found {
ctx.Logger().Info("Validator with pubkey %s not found, cannot force unbond", pubkey)
return
}
*/
logger := ctx.Logger().With("module", "x/stake")
val, found := k.GetValidatorByPubKey(ctx, pubkey)
if !found {
ctx.Logger().Info("Validator with pubkey %s not found, cannot force unbond", pubkey)
return
}
val.Revoked = true
val.RevokedUntilTime = ctx.BlockHeader().Time + jailDuration
k.updateValidator(ctx, val) // update the validator, now revoked
val, _ = k.GetValidatorByPubKey(ctx, pubkey)
logger.Info(fmt.Sprintf("Validator %s revoked for minimum duration %d", pubkey.Address(), jailDuration))
return
}

View File

@ -209,3 +209,36 @@ func (msg MsgUnbond) ValidateBasic() sdk.Error {
}
return nil
}
//______________________________________________________________________
// MsgUnrevoke - struct for unrevoking revoked validator
type MsgUnrevoke struct {
ValidatorAddr sdk.Address `json:"address"`
}
func NewMsgUnrevoke(validatorAddr sdk.Address) MsgUnrevoke {
return MsgUnrevoke{
ValidatorAddr: validatorAddr,
}
}
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 := json.Marshal(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
}

View File

@ -17,9 +17,10 @@ import (
// exchange rate. Voting power can be calculated as total bonds multiplied by
// exchange rate.
type Validator struct {
Owner sdk.Address `json:"owner"` // sender of BondTx - UnbondTx returns here
PubKey crypto.PubKey `json:"pub_key"` // pubkey of validator
Revoked bool `json:"revoked"` // has the validator been revoked from bonded status?
Owner sdk.Address `json:"owner"` // sender of BondTx - UnbondTx returns here
PubKey crypto.PubKey `json:"pub_key"` // pubkey of validator
Revoked bool `json:"revoked"` // has the validator been revoked from bonded status?
RevokedUntilTime int64 `json:"revoked_until_time"` // timestamp before which the validator cannot unrevoke
PoolShares PoolShares `json:"pool_shares"` // total shares for tokens held in the pool
DelegatorShares sdk.Rat `json:"delegator_shares"` // total shares issued to a validator's delegators
@ -46,6 +47,8 @@ func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Descripti
return Validator{
Owner: owner,
PubKey: pubKey,
Revoked: false,
RevokedUntilTime: int64(0),
PoolShares: NewUnbondedShares(sdk.ZeroRat()),
DelegatorShares: sdk.ZeroRat(),
Description: description,

View File

@ -10,6 +10,7 @@ func RegisterWire(cdc *wire.Codec) {
cdc.RegisterConcrete(MsgEditCandidacy{}, "cosmos-sdk/MsgEditCandidacy", nil)
cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil)
cdc.RegisterConcrete(MsgUnbond{}, "cosmos-sdk/MsgUnbond", nil)
cdc.RegisterConcrete(MsgUnrevoke{}, "cosmos-sdk/MsgUnrevoke", nil)
}
var cdcEmpty = wire.NewCodec()