Merge PR #2480: Fix signing info handling bugs & faulty slashing

This commit is contained in:
Christopher Goes 2018-10-16 20:21:24 +02:00 committed by GitHub
commit 55f4f61493
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 250 additions and 100 deletions

View File

@ -69,6 +69,7 @@ BREAKING CHANGES
* [x/staking] \#2244 staking now holds a consensus-address-index instead of a consensus-pubkey-index
* [x/staking] \#2236 more distribution hooks for distribution
* [x/stake] \#2394 Split up UpdateValidator into distinct state transitions applied only in EndBlock
* [x/slashing] \#2480 Fix signing info handling bugs & faulty slashing
* [x/stake] \#2412 Added an unbonding validator queue to EndBlock to automatically update validator.Status when finished Unbonding
* [x/stake] \#2500 Block conflicting redelegations until we add an index
* [x/params] Global Paramstore refactored

View File

@ -43,7 +43,7 @@ func generateSelfSignedCert(host string) (certBytes []byte, priv *ecdsa.PrivateK
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
IsCA: true,
}
hosts := strings.Split(host, ",")
for _, h := range hosts {

View File

@ -713,7 +713,7 @@ func TestUnjail(t *testing.T) {
tests.WaitForHeight(4, port)
require.Equal(t, true, signingInfo.IndexOffset > 0)
require.Equal(t, time.Unix(0, 0).UTC(), signingInfo.JailedUntil)
require.Equal(t, true, signingInfo.SignedBlocksCounter > 0)
require.Equal(t, true, signingInfo.MissedBlocksCounter == 0)
}
func TestProposalsQuery(t *testing.T) {

View File

@ -93,25 +93,27 @@ for val in block.Validators:
index := signInfo.IndexOffset % SIGNED_BLOCKS_WINDOW
signInfo.IndexOffset++
previous = SigningBitArray.Get(val.Address, index)
previous = MissedBlockBitArray.Get(val.Address, index)
// update counter if array has changed
if previous and val in block.AbsentValidators:
SigningBitArray.Set(val.Address, index, false)
signInfo.SignedBlocksCounter--
else if !previous and val not in block.AbsentValidators:
SigningBitArray.Set(val.Address, index, true)
signInfo.SignedBlocksCounter++
if !previous and val in block.AbsentValidators:
MissedBlockBitArray.Set(val.Address, index, true)
signInfo.MissedBlocksCounter++
else if previous and val not in block.AbsentValidators:
MissedBlockBitArray.Set(val.Address, index, false)
signInfo.MissedBlocksCounter--
// else previous == val not in block.AbsentValidators, no change
// validator must be active for at least SIGNED_BLOCKS_WINDOW
// before they can be automatically unbonded for failing to be
// included in 50% of the recent LastCommits
minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW
minSigned = SIGNED_BLOCKS_WINDOW / 2
if height > minHeight AND signInfo.SignedBlocksCounter < minSigned:
maxMissed = SIGNED_BLOCKS_WINDOW / 2
if height > minHeight AND signInfo.MissedBlocksCounter > maxMissed:
signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION
signInfo.IndexOffset = 0
signInfo.MissedBlocksCounter = 0
clearMissedBlockBitArray()
slash & unbond the validator
SigningInfo.Set(val.Address, signInfo)

View File

@ -12,6 +12,17 @@ and `SlashedSoFar` of `0`:
```
onValidatorBonded(address sdk.ValAddress)
signingInfo, found = getValidatorSigningInfo(address)
if !found {
signingInfo = ValidatorSigningInfo {
StartHeight : CurrentHeight,
IndexOffset : 0,
JailedUntil : time.Unix(0, 0),
MissedBloskCounter : 0
}
setValidatorSigningInfo(signingInfo)
}
slashingPeriod = SlashingPeriod{
ValidatorAddr : address,
StartHeight : CurrentHeight,

View File

@ -17,18 +17,18 @@ Information about validator activity is tracked in a `ValidatorSigningInfo`.
It is indexed in the store as follows:
- SigningInfo: ` 0x01 | ValTendermintAddr -> amino(valSigningInfo)`
- SigningBitArray: ` 0x02 | ValTendermintAddr | LittleEndianUint64(signArrayIndex) -> VarInt(didSign)`
- MissedBlocksBitArray: ` 0x02 | ValTendermintAddr | LittleEndianUint64(signArrayIndex) -> VarInt(didMiss)`
The first map allows us to easily lookup the recent signing info for a
validator, according to the Tendermint validator address. The second map acts as
a bit-array of size `SIGNED_BLOCKS_WINDOW` that tells us if the validator signed for a given index in the bit-array.
a bit-array of size `SIGNED_BLOCKS_WINDOW` that tells us if the validator missed the block for a given index in the bit-array.
The index in the bit-array is given as little endian uint64.
The result is a `varint` that takes on `0` or `1`, where `0` indicates the
validator did not sign the corresponding block, and `1` indicates they did.
validator did not miss (did sign) the corresponding block, and `1` indicates they missed the block (did not sign).
Note that the SigningBitArray is not explicitly initialized up-front. Keys are
Note that the MissedBlocksBitArray is not explicitly initialized up-front. Keys are
added as we progress through the first `SIGNED_BLOCKS_WINDOW` blocks for a newly
bonded validator.
@ -40,7 +40,7 @@ type ValidatorSigningInfo struct {
IndexOffset int64 // Offset into the signed block bit array
JailedUntilHeight int64 // Block height until which the validator is jailed,
// or sentinel value of 0 for not jailed
SignedBlocksCounter int64 // Running counter of signed blocks
MissedBlocksCounter int64 // Running counter of missed blocks
}
```
@ -49,7 +49,7 @@ Where:
* `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power).
* `IndexOffset` is incremented each time the candidate was a bonded validator in a block (and may have signed a precommit or not).
* `JailedUntil` is set whenever the candidate is jailed due to downtime
* `SignedBlocksCounter` is a counter kept to avoid unnecessary array reads. `SignedBlocksBitArray.Sum() == SignedBlocksCounter` always.
* `MissedBlocksCounter` is a counter kept to avoid unnecessary array reads. `MissedBlocksBitArray.Sum() == MissedBlocksCounter` always.
## Slashing Period

View File

@ -25,10 +25,6 @@ handleMsgUnjail(tx TxUnjail)
if block time < info.JailedUntil
fail with "Validator still jailed, cannot unjail until period has expired"
// Update the start height so the validator won't be immediately unbonded again
info.StartHeight = BlockHeight
setValidatorSigningInfo(info)
validator.Jailed = false
setValidator(validator)

View File

@ -13,9 +13,9 @@ var (
ProposerKey = []byte{0x04} // key for storing the proposer operator address
// params store
ParamStoreKeyCommunityTax = []byte("community-tax")
ParamStoreKeyBaseProposerReward = []byte("base-proposer-reward")
ParamStoreKeyBonusProposerReward = []byte("bonus-proposer-reward")
ParamStoreKeyCommunityTax = []byte("communitytax")
ParamStoreKeyBaseProposerReward = []byte("baseproposerreward")
ParamStoreKeyBonusProposerReward = []byte("bonusproposerreward")
)
const (

View File

@ -19,9 +19,9 @@ func NewValidatorDistInfo(operatorAddr sdk.ValAddress, currentHeight int64) Vali
return ValidatorDistInfo{
OperatorAddr: operatorAddr,
FeePoolWithdrawalHeight: currentHeight,
Pool: DecCoins{},
PoolCommission: DecCoins{},
DelAccum: NewTotalAccum(currentHeight),
Pool: DecCoins{},
PoolCommission: DecCoins{},
DelAccum: NewTotalAccum(currentHeight),
}
}

View File

@ -46,11 +46,7 @@ func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result {
return ErrValidatorJailed(k.codespace).Result()
}
// update the starting height so the validator can't be immediately jailed
// again
info.StartHeight = ctx.BlockHeight()
k.setValidatorSigningInfo(ctx, consAddr, info)
// unjail the validator
k.validatorSet.Unjail(ctx, consAddr)
tags := sdk.NewTags("action", []byte("unjail"), "validator", []byte(msg.ValidatorAddr.String()))

View File

@ -53,7 +53,7 @@ func TestJailedValidatorDelegations(t *testing.T) {
StartHeight: int64(0),
IndexOffset: int64(0),
JailedUntil: time.Unix(0, 0),
SignedBlocksCounter: int64(0),
MissedBlocksCounter: int64(0),
}
slashingKeeper.setValidatorSigningInfo(ctx, consAddr, newInfo)

View File

@ -1,11 +1,25 @@
package slashing
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Create a new slashing period when a validator is bonded
func (k Keeper) onValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) {
// Update the signing info start height or create a new signing info
_, found := k.getValidatorSigningInfo(ctx, address)
if !found {
signingInfo := ValidatorSigningInfo{
StartHeight: ctx.BlockHeight(),
IndexOffset: 0,
JailedUntil: time.Unix(0, 0),
MissedBlocksCounter: 0,
}
k.setValidatorSigningInfo(ctx, address, signingInfo)
}
// Create a new slashing period when a validator is bonded
slashingPeriod := ValidatorSlashingPeriod{
ValidatorAddr: address,
StartHeight: ctx.BlockHeight(),

View File

@ -102,8 +102,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p
// Will use the 0-value default signing info if not present, except for start height
signInfo, found := k.getValidatorSigningInfo(ctx, consAddr)
if !found {
// If this validator has never been seen before, construct a new SigningInfo with the correct start height
signInfo = NewValidatorSigningInfo(height, 0, time.Unix(0, 0), 0)
panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr))
}
index := signInfo.IndexOffset % k.SignedBlocksWindow(ctx)
signInfo.IndexOffset++
@ -111,24 +110,27 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p
// 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, consAddr, index)
if previous == signed {
previous := k.getValidatorMissedBlockBitArray(ctx, consAddr, index)
missed := !signed
switch {
case !previous && missed:
// Array value has changed from not missed to missed, increment counter
k.setValidatorMissedBlockBitArray(ctx, consAddr, index, true)
signInfo.MissedBlocksCounter++
case previous && !missed:
// Array value has changed from missed to not missed, decrement counter
k.setValidatorMissedBlockBitArray(ctx, consAddr, index, false)
signInfo.MissedBlocksCounter--
default:
// 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, consAddr, index, false)
signInfo.SignedBlocksCounter--
} else if !previous && signed {
// Array value has changed from unsigned to signed, increment counter
k.setValidatorSigningBitArray(ctx, consAddr, index, true)
signInfo.SignedBlocksCounter++
}
if !signed {
logger.Info(fmt.Sprintf("Absent validator %s at height %d, %d signed, threshold %d", addr, height, signInfo.SignedBlocksCounter, k.MinSignedPerWindow(ctx)))
if missed {
logger.Info(fmt.Sprintf("Absent validator %s at height %d, %d missed, threshold %d", addr, height, signInfo.MissedBlocksCounter, k.MinSignedPerWindow(ctx)))
}
minHeight := signInfo.StartHeight + k.SignedBlocksWindow(ctx)
if height > minHeight && signInfo.SignedBlocksCounter < k.MinSignedPerWindow(ctx) {
maxMissed := k.SignedBlocksWindow(ctx) - k.MinSignedPerWindow(ctx)
if height > minHeight && signInfo.MissedBlocksCounter > maxMissed {
validator := k.validatorSet.ValidatorByConsAddr(ctx, consAddr)
if validator != nil && !validator.GetJailed() {
// Downtime confirmed: slash and jail the validator
@ -143,6 +145,10 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p
k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, k.SlashFractionDowntime(ctx))
k.validatorSet.Jail(ctx, consAddr)
signInfo.JailedUntil = ctx.BlockHeader().Time.Add(k.DowntimeUnbondDuration(ctx))
// We need to reset the counter & array so that the validator won't be immediately slashed for downtime upon rebonding.
signInfo.MissedBlocksCounter = 0
signInfo.IndexOffset = 0
k.clearValidatorMissedBlockBitArray(ctx, consAddr)
} else {
// Validator was (a) not found or (b) already jailed, don't slash
logger.Info(fmt.Sprintf("Validator %s would have been slashed for downtime, but was either not found in store or already jailed",

View File

@ -30,7 +30,6 @@ func TestHandleDoubleSign(t *testing.T) {
ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams())
// validator added pre-genesis
ctx = ctx.WithBlockHeight(-1)
sk = sk.WithHooks(keeper.Hooks())
amtInt := int64(100)
operatorAddr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt)
got := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, val, amt))
@ -71,7 +70,6 @@ func TestSlashingPeriodCap(t *testing.T) {
// initial setup
ctx, ck, sk, _, keeper := createTestInput(t, DefaultParams())
sk = sk.WithHooks(keeper.Hooks())
amtInt := int64(100)
operatorAddr, amt := addrs[0], sdk.NewInt(amtInt)
valConsPubKey, valConsAddr := pks[0], pks[0].Address()
@ -137,7 +135,6 @@ func TestHandleAbsentValidator(t *testing.T) {
// initial setup
ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams())
sk = sk.WithHooks(keeper.Hooks())
amtInt := int64(100)
addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt)
sh := stake.NewHandler(sk)
@ -148,14 +145,13 @@ func TestHandleAbsentValidator(t *testing.T) {
keeper.AddValidators(ctx, validatorUpdates)
require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}})
require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower()))
// will exist since the validator has been bonded
info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
require.False(t, found)
require.True(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, int64(0), info.IndexOffset)
require.Equal(t, int64(0), info.SignedBlocksCounter)
// default time.Time value
var blankTime time.Time
require.Equal(t, blankTime, info.JailedUntil)
require.Equal(t, int64(0), info.MissedBlocksCounter)
require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil)
height := int64(0)
// 1000 first blocks OK
@ -166,7 +162,7 @@ func TestHandleAbsentValidator(t *testing.T) {
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
require.True(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, keeper.SignedBlocksWindow(ctx), info.SignedBlocksCounter)
require.Equal(t, int64(0), info.MissedBlocksCounter)
// 500 blocks missed
for ; height < keeper.SignedBlocksWindow(ctx)+(keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)); height++ {
@ -176,7 +172,7 @@ func TestHandleAbsentValidator(t *testing.T) {
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
require.True(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx), info.SignedBlocksCounter)
require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx), info.MissedBlocksCounter)
// validator should be bonded still
validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val))
@ -190,7 +186,8 @@ func TestHandleAbsentValidator(t *testing.T) {
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
require.True(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-1, info.SignedBlocksCounter)
// counter now reset to zero
require.Equal(t, int64(0), info.MissedBlocksCounter)
// end block
stake.EndBlocker(ctx, sk)
@ -211,7 +208,7 @@ func TestHandleAbsentValidator(t *testing.T) {
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
require.True(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-2, info.SignedBlocksCounter)
require.Equal(t, int64(1), info.MissedBlocksCounter)
// end block
stake.EndBlocker(ctx, sk)
@ -248,12 +245,12 @@ func TestHandleAbsentValidator(t *testing.T) {
pool = sk.GetPool(ctx)
require.Equal(t, amtInt-slashAmt-secondSlashAmt, pool.BondedTokens.RoundInt64())
// validator start height should have been changed
// validator start height should not have been changed
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
require.True(t, found)
require.Equal(t, height, info.StartHeight)
// we've missed 2 blocks more than the maximum
require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-2, info.SignedBlocksCounter)
require.Equal(t, int64(0), info.StartHeight)
// we've missed 2 blocks more than the maximum, so the counter was reset to 0 at 1 block more and is now 1
require.Equal(t, int64(1), info.MissedBlocksCounter)
// validator should not be immediately jailed again
height++
@ -294,6 +291,11 @@ func TestHandleNewValidator(t *testing.T) {
ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams())
addr, val, amt := addrs[0], pks[0], int64(100)
sh := stake.NewHandler(sk)
// 1000 first blocks not a validator
ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 1)
// Validator created
got := sh(ctx, NewTestMsgCreateValidator(addr, val, sdk.NewInt(amt)))
require.True(t, got.IsOK())
validatorUpdates := stake.EndBlocker(ctx, sk)
@ -301,9 +303,6 @@ func TestHandleNewValidator(t *testing.T) {
require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.SubRaw(amt)}})
require.Equal(t, sdk.NewDec(amt), sk.Validator(ctx, addr).GetPower())
// 1000 first blocks not a validator
ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 1)
// Now a validator, for two blocks
keeper.handleValidatorSignature(ctx, val.Address(), 100, true)
ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 2)
@ -313,7 +312,7 @@ func TestHandleNewValidator(t *testing.T) {
require.True(t, found)
require.Equal(t, keeper.SignedBlocksWindow(ctx)+1, info.StartHeight)
require.Equal(t, int64(2), info.IndexOffset)
require.Equal(t, int64(1), info.SignedBlocksCounter)
require.Equal(t, int64(1), info.MissedBlocksCounter)
require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil)
// validator should be bonded still, should not have been jailed or slashed
@ -369,3 +368,112 @@ func TestHandleAlreadyJailed(t *testing.T) {
require.Equal(t, amtInt-1, validator.GetTokens().RoundInt64())
}
// Test a validator dipping in and out of the validator set
// Ensure that missed blocks are tracked correctly and that
// the start height of the signing info is reset correctly
func TestValidatorDippingInAndOut(t *testing.T) {
// initial setup
// keeperTestParams set the SignedBlocksWindow to 1000 and MaxMissedBlocksPerWindow to 500
ctx, _, sk, _, keeper := createTestInput(t, keeperTestParams())
params := sk.GetParams(ctx)
params.MaxValidators = 1
sk.SetParams(ctx, params)
amtInt := int64(100)
addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt)
consAddr := sdk.ConsAddress(addr)
sh := stake.NewHandler(sk)
got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt))
require.True(t, got.IsOK())
validatorUpdates := stake.EndBlocker(ctx, sk)
keeper.AddValidators(ctx, validatorUpdates)
// 100 first blocks OK
height := int64(0)
for ; height < int64(100); height++ {
ctx = ctx.WithBlockHeight(height)
keeper.handleValidatorSignature(ctx, val.Address(), amtInt, true)
}
// validator kicked out of validator set
newAmt := int64(101)
got = sh(ctx, NewTestMsgCreateValidator(addrs[1], pks[1], sdk.NewInt(newAmt)))
require.True(t, got.IsOK())
validatorUpdates = stake.EndBlocker(ctx, sk)
require.Equal(t, 2, len(validatorUpdates))
keeper.AddValidators(ctx, validatorUpdates)
validator, _ := sk.GetValidator(ctx, addr)
require.Equal(t, sdk.Unbonding, validator.Status)
// 600 more blocks happened
height = int64(700)
ctx = ctx.WithBlockHeight(height)
// validator added back in
got = sh(ctx, newTestMsgDelegate(sdk.AccAddress(addrs[2]), addrs[0], sdk.NewInt(2)))
require.True(t, got.IsOK())
validatorUpdates = stake.EndBlocker(ctx, sk)
require.Equal(t, 2, len(validatorUpdates))
validator, _ = sk.GetValidator(ctx, addr)
require.Equal(t, sdk.Bonded, validator.Status)
newAmt = int64(102)
// validator misses a block
keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false)
height++
// shouldn't be jailed/kicked yet
validator, _ = sk.GetValidator(ctx, addr)
require.Equal(t, sdk.Bonded, validator.Status)
// validator misses 500 more blocks, 501 total
latest := height
for ; height < latest+500; height++ {
ctx = ctx.WithBlockHeight(height)
keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false)
}
// should now be jailed & kicked
stake.EndBlocker(ctx, sk)
validator, _ = sk.GetValidator(ctx, addr)
require.Equal(t, sdk.Unbonding, validator.Status)
// check all the signing information
signInfo, found := keeper.getValidatorSigningInfo(ctx, consAddr)
require.True(t, found)
require.Equal(t, int64(0), signInfo.MissedBlocksCounter)
require.Equal(t, int64(0), signInfo.IndexOffset)
// array should be cleared
for offset := int64(0); offset < keeper.SignedBlocksWindow(ctx); offset++ {
missed := keeper.getValidatorMissedBlockBitArray(ctx, consAddr, offset)
require.False(t, missed)
}
// some blocks pass
height = int64(5000)
ctx = ctx.WithBlockHeight(height)
// validator rejoins and starts signing again
sk.Unjail(ctx, consAddr)
keeper.handleValidatorSignature(ctx, val.Address(), newAmt, true)
height++
// validator should not be kicked since we reset counter/array when it was jailed
stake.EndBlocker(ctx, sk)
validator, _ = sk.GetValidator(ctx, addr)
require.Equal(t, sdk.Bonded, validator.Status)
// validator misses 501 blocks
latest = height
for ; height < latest+501; height++ {
ctx = ctx.WithBlockHeight(height)
keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false)
}
// validator should now be jailed & kicked
stake.EndBlocker(ctx, sk)
validator, _ = sk.GetValidator(ctx, addr)
require.Equal(t, sdk.Unbonding, validator.Status)
}

View File

@ -9,10 +9,10 @@ import (
// key prefix bytes
var (
ValidatorSigningInfoKey = []byte{0x01} // Prefix for signing info
ValidatorSigningBitArrayKey = []byte{0x02} // Prefix for signature bit array
ValidatorSlashingPeriodKey = []byte{0x03} // Prefix for slashing period
AddrPubkeyRelationKey = []byte{0x04} // Prefix for address-pubkey relation
ValidatorSigningInfoKey = []byte{0x01} // Prefix for signing info
ValidatorMissedBlockBitArrayKey = []byte{0x02} // Prefix for missed block bit array
ValidatorSlashingPeriodKey = []byte{0x03} // Prefix for slashing period
AddrPubkeyRelationKey = []byte{0x04} // Prefix for address-pubkey relation
)
// stored by *Tendermint* address (not operator address)
@ -21,10 +21,15 @@ func GetValidatorSigningInfoKey(v sdk.ConsAddress) []byte {
}
// stored by *Tendermint* address (not operator address)
func GetValidatorSigningBitArrayKey(v sdk.ConsAddress, i int64) []byte {
func GetValidatorMissedBlockBitArrayPrefixKey(v sdk.ConsAddress) []byte {
return append(ValidatorMissedBlockBitArrayKey, v.Bytes()...)
}
// stored by *Tendermint* address (not operator address)
func GetValidatorMissedBlockBitArrayKey(v sdk.ConsAddress, i int64) []byte {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(i))
return append(ValidatorSigningBitArrayKey, append(v.Bytes(), b...)...)
return append(GetValidatorMissedBlockBitArrayPrefixKey(v), b...)
}
// stored by *Tendermint* address (not operator address)

View File

@ -28,32 +28,42 @@ func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress
}
// Stored by *validator* address (not operator address)
func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64) (signed bool) {
func (k Keeper) getValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64) (missed bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(GetValidatorSigningBitArrayKey(address, index))
bz := store.Get(GetValidatorMissedBlockBitArrayKey(address, index))
if bz == nil {
// lazy: treat empty key as unsigned
signed = false
// lazy: treat empty key as not missed
missed = false
return
}
k.cdc.MustUnmarshalBinary(bz, &signed)
k.cdc.MustUnmarshalBinary(bz, &missed)
return
}
// Stored by *validator* address (not operator address)
func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64, signed bool) {
func (k Keeper) setValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64, missed bool) {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinary(signed)
store.Set(GetValidatorSigningBitArrayKey(address, index), bz)
bz := k.cdc.MustMarshalBinary(missed)
store.Set(GetValidatorMissedBlockBitArrayKey(address, index), bz)
}
// Stored by *validator* address (not operator address)
func (k Keeper) clearValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, GetValidatorMissedBlockBitArrayPrefixKey(address))
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}
iter.Close()
}
// Construct a new `ValidatorSigningInfo` struct
func NewValidatorSigningInfo(startHeight int64, indexOffset int64, jailedUntil time.Time, signedBlocksCounter int64) ValidatorSigningInfo {
func NewValidatorSigningInfo(startHeight int64, indexOffset int64, jailedUntil time.Time, missedBlocksCounter int64) ValidatorSigningInfo {
return ValidatorSigningInfo{
StartHeight: startHeight,
IndexOffset: indexOffset,
JailedUntil: jailedUntil,
SignedBlocksCounter: signedBlocksCounter,
MissedBlocksCounter: missedBlocksCounter,
}
}
@ -62,11 +72,11 @@ type ValidatorSigningInfo struct {
StartHeight int64 `json:"start_height"` // height at which validator was first a candidate OR was unjailed
IndexOffset int64 `json:"index_offset"` // index offset into signed block bit array
JailedUntil time.Time `json:"jailed_until"` // timestamp validator cannot be unjailed until
SignedBlocksCounter int64 `json:"signed_blocks_counter"` // signed blocks counter (to avoid scanning the array every time)
MissedBlocksCounter int64 `json:"missed_blocks_counter"` // missed blocks counter (to avoid scanning the array every time)
}
// Return human readable signing info
func (i ValidatorSigningInfo) HumanReadableString() string {
return fmt.Sprintf("Start height: %d, index offset: %d, jailed until: %v, signed blocks counter: %d",
i.StartHeight, i.IndexOffset, i.JailedUntil, i.SignedBlocksCounter)
return fmt.Sprintf("Start height: %d, index offset: %d, jailed until: %v, missed blocks counter: %d",
i.StartHeight, i.IndexOffset, i.JailedUntil, i.MissedBlocksCounter)
}

View File

@ -17,7 +17,7 @@ func TestGetSetValidatorSigningInfo(t *testing.T) {
StartHeight: int64(4),
IndexOffset: int64(3),
JailedUntil: time.Unix(2, 0),
SignedBlocksCounter: int64(10),
MissedBlocksCounter: int64(10),
}
keeper.setValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0]), newInfo)
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0]))
@ -25,14 +25,14 @@ func TestGetSetValidatorSigningInfo(t *testing.T) {
require.Equal(t, info.StartHeight, int64(4))
require.Equal(t, info.IndexOffset, int64(3))
require.Equal(t, info.JailedUntil, time.Unix(2, 0).UTC())
require.Equal(t, info.SignedBlocksCounter, int64(10))
require.Equal(t, info.MissedBlocksCounter, int64(10))
}
func TestGetSetValidatorSigningBitArray(t *testing.T) {
func TestGetSetValidatorMissedBlockBitArray(t *testing.T) {
ctx, _, _, _, keeper := createTestInput(t, DefaultParams())
signed := keeper.getValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0)
require.False(t, signed) // treat empty key as unsigned
keeper.setValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0, true)
signed = keeper.getValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0)
require.True(t, signed) // now should be signed
missed := keeper.getValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0)
require.False(t, missed) // treat empty key as not missed
keeper.setValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0, true)
missed = keeper.getValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0)
require.True(t, missed) // now should be missed
}

View File

@ -88,6 +88,7 @@ func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, s
require.Nil(t, err)
paramstore := paramsKeeper.Subspace(DefaultParamspace)
keeper := NewKeeper(cdc, keySlashing, sk, paramstore, DefaultCodespace)
sk = sk.WithHooks(keeper.Hooks())
require.NotPanics(t, func() {
InitGenesis(ctx, keeper, GenesisState{defaults}, genesis)

View File

@ -45,7 +45,7 @@ func TestBeginBlocker(t *testing.T) {
require.Equal(t, ctx.BlockHeight(), info.StartHeight)
require.Equal(t, int64(1), info.IndexOffset)
require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil)
require.Equal(t, int64(1), info.SignedBlocksCounter)
require.Equal(t, int64(0), info.MissedBlocksCounter)
height := int64(0)

View File

@ -847,11 +847,11 @@ func TestConflictingRedelegation(t *testing.T) {
keeper.SetParams(ctx, params)
// create the validators
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10)
msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10)
msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10)
got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")