Skip to content

Commit

Permalink
Only slash after signedBlocksWindow has passed
Browse files Browse the repository at this point in the history
Closes #19
  • Loading branch information
mdyring committed Oct 20, 2020
1 parent 51d610e commit ef3d389
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 34 deletions.
26 changes: 14 additions & 12 deletions x/slashing/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, sk Keeper, batch

blockTimes := sk.getBlockTimes()
blockTimes = append(blockTimes, ctx.BlockTime())
blockTimes = truncateByWindow(ctx.BlockTime(), blockTimes, signedBlocksWindow)
slashable := false
slashable, blockTimes = truncateByWindow(ctx.BlockTime(), blockTimes, signedBlocksWindow)
sk.setBlockTimes(batch, blockTimes)

sk.handlePendingPenalties(ctx, batch, validatorset(req.LastCommitInfo.Votes))
Expand All @@ -33,7 +34,7 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, sk Keeper, batch
// store whether or not they have actually signed it and slash/unbond any
// which have missed too many blocks in a row (downtime slashing)
for _, voteInfo := range req.LastCommitInfo.GetVotes() {
sk.HandleValidatorSignature(ctx, batch, voteInfo.Validator.Address, voteInfo.Validator.Power, voteInfo.SignedLastBlock, int64(len(blockTimes)))
sk.HandleValidatorSignature(ctx, batch, voteInfo.Validator.Address, voteInfo.Validator.Power, voteInfo.SignedLastBlock, int64(len(blockTimes)), slashable)
}

// Iterate through any newly discovered evidence of infraction
Expand Down Expand Up @@ -61,17 +62,18 @@ func validatorset(validators []abci.VoteInfo) func() map[string]bool {
}
}

func truncateByWindow(blockTime time.Time, times []time.Time, signedBlocksWindow time.Duration) []time.Time {
if len(times) == 0 {
return times
}
func truncateByWindow(blockTime time.Time, times []time.Time, signedBlocksWindow time.Duration) (bool, []time.Time) {

if len(times) > 0 && times[0].Add(signedBlocksWindow).Before(blockTime) {
// Remove timestamps outside of the time window we are watching
threshold := blockTime.Add(-signedBlocksWindow)

// Remove timestamps outside of the time window we are watching
threshold := blockTime.Add(-1 * signedBlocksWindow)
index := sort.Search(len(times), func(i int) bool {
return times[i].After(threshold)
})

index := sort.Search(len(times), func(i int) bool {
return times[i].After(threshold)
})
return true, times[index:]
}

return times[index:]
return false, times
}
11 changes: 8 additions & 3 deletions x/slashing/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,21 +161,26 @@ func (k Keeper) HandleDoubleSign(ctx sdk.Context, batch db.Batch, addr crypto.Ad
k.SetValidatorSigningInfo(ctx, consAddr, signInfo)
}

func (k Keeper) HandleValidatorSignature(ctx sdk.Context, batch db.Batch, addr crypto.Address, power int64, signed bool, blockCount int64) {
func (k Keeper) HandleValidatorSignature(ctx sdk.Context, batch db.Batch, addr crypto.Address, power int64, signed bool, blockCount int64, slashable bool) {
logger := k.Logger(ctx)
height := ctx.BlockHeight()
consAddr := sdk.ConsAddress(addr)

missedBlocks := k.getMissingBlocksForValidator(consAddr)
missedBlocks = truncateByWindow(ctx.BlockTime(), missedBlocks, k.SignedBlocksWindowDuration(ctx))
_, missedBlocks = truncateByWindow(ctx.BlockTime(), missedBlocks, k.SignedBlocksWindowDuration(ctx))

if !signed {
missedBlocks = append(missedBlocks, ctx.BlockTime())
}

k.setMissingBlocksForValidator(batch, consAddr, missedBlocks)
missedBlockCount := sdk.NewInt(int64(len(missedBlocks))).ToDec()

// Validator is only slashable if the signed block window is full (was truncated)
if !slashable {
return
}

missedBlockCount := sdk.NewInt(int64(len(missedBlocks))).ToDec()
missedRatio := missedBlockCount.QuoInt64(blockCount)

// TODO Only do this if missing is true?
Expand Down
44 changes: 25 additions & 19 deletions x/slashing/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func TestHandleDoubleSign(t *testing.T) {

// handle a signature to set signing info
batch := database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), amt.Int64(), true, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), amt.Int64(), true, blockWindow, false)
batch.Write()

// Keep track of token supplies before the slashing
Expand Down Expand Up @@ -135,7 +135,7 @@ func TestPastMaxEvidenceAge(t *testing.T) {

// handle a signature to set signing info
batch := database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, true, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, true, blockWindow, false)
batch.Write()

ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(keeper.MaxEvidenceAge(ctx))})
Expand Down Expand Up @@ -185,13 +185,15 @@ func TestHandleAbsentValidator(t *testing.T) {
//require.Equal(t, int64(0), info.MissedBlocksCounter)
require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil)
height := int64(0)
slashable := false
nextBlocktime := blockTimeGenerator(time.Minute)

// 1000 first blocks OK
for ; height < 1000; height++ {
ctx = ctx.WithBlockHeight(height).WithBlockTime(nextBlocktime(1))
batch := database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, true, blockWindow)
slashable = height > blockWindow
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, true, blockWindow, slashable)
batch.Write()
}
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
Expand All @@ -204,7 +206,7 @@ func TestHandleAbsentValidator(t *testing.T) {
for ; height < nextHeight; height++ {
ctx = ctx.WithBlockHeight(height).WithBlockTime(nextBlocktime(1))
batch := database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow, slashable)
batch.Write()
}
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
Expand All @@ -221,7 +223,7 @@ func TestHandleAbsentValidator(t *testing.T) {
// 501st block missed
ctx = ctx.WithBlockHeight(height).WithBlockTime(nextBlocktime(1))
batch := database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow, slashable)
batch.Write()
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
require.True(t, found)
Expand All @@ -244,7 +246,7 @@ func TestHandleAbsentValidator(t *testing.T) {
// 502nd block *also* missed (since the LastCommit would have still included the just-unbonded validator)
ctx = ctx.WithBlockHeight(height).WithBlockTime(nextBlocktime(1))
batch = database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow, slashable)
batch.Write()
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
require.True(t, found)
Expand Down Expand Up @@ -294,7 +296,7 @@ func TestHandleAbsentValidator(t *testing.T) {
height++
ctx = ctx.WithBlockHeight(height).WithBlockTime(nextBlocktime(1))
batch = database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow, slashable)
batch.Write()
validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val))
require.Equal(t, sdk.Bonded, validator.GetStatus())
Expand All @@ -304,7 +306,7 @@ func TestHandleAbsentValidator(t *testing.T) {
for ; height < nextHeight; height++ {
ctx = ctx.WithBlockHeight(height).WithBlockTime(nextBlocktime(1))
batch = database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow, slashable)
batch.Write()
}

Expand All @@ -316,7 +318,7 @@ func TestHandleAbsentValidator(t *testing.T) {
for ; height <= nextHeight; height++ {
ctx = ctx.WithBlockHeight(height).WithBlockTime(nextBlocktime(1))
batch = database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow, slashable)
batch.Write()
}

Expand Down Expand Up @@ -358,11 +360,11 @@ func TestHandleNewValidator(t *testing.T) {

// Now a validator, for two blocks
batch := database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), 100, true, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), 100, true, blockWindow, true)
batch.Write()
ctx = ctx.WithBlockHeight(blockWindow + 2).WithBlockTime(nextBlocktime(2))
batch = database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), 100, false, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), 100, false, blockWindow, true)
batch.Write()

info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
Expand Down Expand Up @@ -401,18 +403,20 @@ func TestHandleAlreadyJailed(t *testing.T) {

// 1000 first blocks OK
height := int64(0)
slashable := false
for ; height < 1000; height++ {
ctx = ctx.WithBlockHeight(height).WithBlockTime(nextBlocktime(1))
batch := database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, true, blockWindow)
slashable = height > blockWindow
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, true, blockWindow, slashable)
batch.Write()
}

// 501 blocks missed
for ; height < 1501; height++ {
ctx = ctx.WithBlockHeight(height).WithBlockTime(nextBlocktime(1))
batch := database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow, slashable)
batch.Write()
}

Expand All @@ -430,7 +434,7 @@ func TestHandleAlreadyJailed(t *testing.T) {
// another block missed
ctx = ctx.WithBlockHeight(height).WithBlockTime(nextBlocktime(1))
batch := database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, false, blockWindow, slashable)
batch.Write()

// validator should not have been slashed twice
Expand Down Expand Up @@ -464,10 +468,12 @@ func TestValidatorDippingInAndOut(t *testing.T) {

// 100 first blocks OK
height := int64(1)
slashable := false
for ; height < int64(100); height++ {
ctx = ctx.WithBlockHeight(height).WithBlockTime(nextBlocktime(1))
batch := database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, true, height)
slashable = height > blockWindow
keeper.HandleValidatorSignature(ctx, batch, val.Address(), power, true, height, slashable)
batch.Write()
}

Expand Down Expand Up @@ -499,7 +505,7 @@ func TestValidatorDippingInAndOut(t *testing.T) {

// validator misses a block
batch := database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), newPower, false, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), newPower, false, blockWindow, slashable)
batch.Write()
height++

Expand All @@ -512,7 +518,7 @@ func TestValidatorDippingInAndOut(t *testing.T) {
for ; height < latest+500; height++ {
ctx = ctx.WithBlockHeight(height).WithBlockTime(nextBlocktime(1))
batch = database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), newPower, false, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), newPower, false, blockWindow, slashable)
batch.Write()
}

Expand Down Expand Up @@ -543,7 +549,7 @@ func TestValidatorDippingInAndOut(t *testing.T) {
sk.Unjail(ctx, consAddr)

batch = database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), newPower, true, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), newPower, true, blockWindow, slashable)
batch.Write()
height++

Expand All @@ -557,7 +563,7 @@ func TestValidatorDippingInAndOut(t *testing.T) {
for ; height < latest+501; height++ {
ctx = ctx.WithBlockHeight(height).WithBlockTime(nextBlocktime(1))
batch = database.NewBatch()
keeper.HandleValidatorSignature(ctx, batch, val.Address(), newPower, false, blockWindow)
keeper.HandleValidatorSignature(ctx, batch, val.Address(), newPower, false, blockWindow, slashable)
batch.Write()
}

Expand Down

0 comments on commit ef3d389

Please sign in to comment.