Skip to content

Commit

Permalink
fix: introduce VoterSetCounter (#347)
Browse files Browse the repository at this point in the history
* feat: introduce `VoterSetCounter`

Introducing a concept "voter set counter" eases integration
with `ostracon`'s `VoterSet`.

* fix: fix off-by-one error and add a test to catch it

* refactor: change the meaning of `VoteSetCounter`

Now `VoteSetCounter` in HandleValidatorSignature indicates the real
counted number, not a index-like number.

* fix: check liveness threshold from the second round of the window

Check the liveness threshold only provided `VoterSetCounter >
SignedBlocksWindow` meaning `VoterSetCounter >= SignedBlocksWindow +
1`.

* chore: provide more meaningful log

Log `VoterSetCounter` instead of `minVoterSetCount` which is constant.

* style: correct typos

* Revert "fix: check liveness threshold from the second round of ..."

This reverts commit 6856809.
  • Loading branch information
0Tech authored Oct 14, 2021
1 parent 5d8420a commit 5975502
Show file tree
Hide file tree
Showing 21 changed files with 306 additions and 328 deletions.
64 changes: 20 additions & 44 deletions client/docs/swagger-ui/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14283,16 +14283,6 @@ paths:
properties:
address:
type: string
start_height:
type: string
format: int64
title: >-
height at which validator was first a candidate OR was
unjailed
index_offset:
type: string
format: int64
title: index offset into signed block bit array
jailed_until:
type: string
format: date-time
Expand All @@ -14311,6 +14301,10 @@ paths:
title: >-
missed blocks counter (to avoid scanning the array every
time)
voter_set_counter:
type: string
format: int64
title: how many times the validator joined to voter set
description: >-
ValidatorSigningInfo defines a validator's signing info for
monitoring their
Expand Down Expand Up @@ -14436,16 +14430,6 @@ paths:
properties:
address:
type: string
start_height:
type: string
format: int64
title: >-
height at which validator was first a candidate OR was
unjailed
index_offset:
type: string
format: int64
title: index offset into signed block bit array
jailed_until:
type: string
format: date-time
Expand All @@ -14464,6 +14448,10 @@ paths:
title: >-
missed blocks counter (to avoid scanning the array every
time)
voter_set_counter:
type: string
format: int64
title: how many times the validator joined to voter set
description: >-
ValidatorSigningInfo defines a validator's signing info for
monitoring their
Expand Down Expand Up @@ -42581,14 +42569,6 @@ definitions:
properties:
address:
type: string
start_height:
type: string
format: int64
title: height at which validator was first a candidate OR was unjailed
index_offset:
type: string
format: int64
title: index offset into signed block bit array
jailed_until:
type: string
format: date-time
Expand All @@ -42605,6 +42585,10 @@ definitions:
type: string
format: int64
title: missed blocks counter (to avoid scanning the array every time)
voter_set_counter:
type: string
format: int64
title: how many times the validator joined to voter set
description: >-
ValidatorSigningInfo defines a validator's signing info for monitoring
their
Expand All @@ -42626,14 +42610,6 @@ definitions:
properties:
address:
type: string
start_height:
type: string
format: int64
title: height at which validator was first a candidate OR was unjailed
index_offset:
type: string
format: int64
title: index offset into signed block bit array
jailed_until:
type: string
format: date-time
Expand All @@ -42650,6 +42626,10 @@ definitions:
type: string
format: int64
title: missed blocks counter (to avoid scanning the array every time)
voter_set_counter:
type: string
format: int64
title: how many times the validator joined to voter set
description: >-
ValidatorSigningInfo defines a validator's signing info for
monitoring their
Expand Down Expand Up @@ -42691,14 +42671,6 @@ definitions:
properties:
address:
type: string
start_height:
type: string
format: int64
title: height at which validator was first a candidate OR was unjailed
index_offset:
type: string
format: int64
title: index offset into signed block bit array
jailed_until:
type: string
format: date-time
Expand All @@ -42715,6 +42687,10 @@ definitions:
type: string
format: int64
title: missed blocks counter (to avoid scanning the array every time)
voter_set_counter:
type: string
format: int64
title: how many times the validator joined to voter set
description: >-
ValidatorSigningInfo defines a validator's signing info for monitoring
their
Expand Down
3 changes: 1 addition & 2 deletions docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -7941,11 +7941,10 @@ liveness activity.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `address` | [string](#string) | | |
| `start_height` | [int64](#int64) | | height at which validator was first a candidate OR was unjailed |
| `index_offset` | [int64](#int64) | | index offset into signed block bit array |
| `jailed_until` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | timestamp validator cannot be unjailed until |
| `tombstoned` | [bool](#bool) | | whether or not a validator has been tombstoned (killed out of validator set) |
| `missed_blocks_counter` | [int64](#int64) | | missed blocks counter (to avoid scanning the array every time) |
| `voter_set_counter` | [int64](#int64) | | how many times the validator joined to voter set |



Expand Down
12 changes: 5 additions & 7 deletions proto/lbm/slashing/v1/slashing.proto
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,16 @@ message ValidatorSigningInfo {
option (gogoproto.goproto_stringer) = false;

string address = 1;
// height at which validator was first a candidate OR was unjailed
int64 start_height = 2 [(gogoproto.moretags) = "yaml:\"start_height\""];
// index offset into signed block bit array
int64 index_offset = 3 [(gogoproto.moretags) = "yaml:\"index_offset\""];
// timestamp validator cannot be unjailed until
google.protobuf.Timestamp jailed_until = 4
google.protobuf.Timestamp jailed_until = 2
[(gogoproto.moretags) = "yaml:\"jailed_until\"", (gogoproto.stdtime) = true, (gogoproto.nullable) = false];
// whether or not a validator has been tombstoned (killed out of validator
// set)
bool tombstoned = 5;
bool tombstoned = 3;
// missed blocks counter (to avoid scanning the array every time)
int64 missed_blocks_counter = 6 [(gogoproto.moretags) = "yaml:\"missed_blocks_counter\""];
int64 missed_blocks_counter = 4 [(gogoproto.moretags) = "yaml:\"missed_blocks_counter\""];
// how many times the validator joined to voter set
int64 voter_set_counter = 5 [(gogoproto.moretags) = "yaml:\"voter_set_counter\""];
}

// Params represents the parameters used for by the slashing module.
Expand Down
4 changes: 2 additions & 2 deletions simapp/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,11 @@ func (app *SimApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []

/* Handle slashing state. */

// reset start height on signing infos
// reset voter set counter on signing infos
app.SlashingKeeper.IterateValidatorSigningInfos(
ctx,
func(addr sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) (stop bool) {
info.StartHeight = 0
info.VoterSetCounter = 0
app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info)
return false
},
Expand Down
3 changes: 1 addition & 2 deletions x/slashing/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ func TestBeginBlocker(t *testing.T) {

info, found := app.SlashingKeeper.GetValidatorSigningInfo(ctx, sdk.BytesToConsAddress(pk.Address()))
require.True(t, found)
require.Equal(t, ctx.BlockHeight(), info.StartHeight)
require.Equal(t, int64(1), info.IndexOffset)
require.Equal(t, int64(1), info.VoterSetCounter)
require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil)
require.Equal(t, int64(0), info.MissedBlocksCounter)

Expand Down
7 changes: 3 additions & 4 deletions x/slashing/client/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (s *IntegrationTestSuite) TestGetCmdQuerySigningInfo() {
fmt.Sprintf("--%s=1", flags.FlagHeight),
},
false,
fmt.Sprintf("{\"address\":\"%s\",\"start_height\":\"0\",\"index_offset\":\"0\",\"jailed_until\":\"1970-01-01T00:00:00Z\",\"tombstoned\":false,\"missed_blocks_counter\":\"0\"}", sdk.BytesToConsAddress(val.PubKey.Address())),
fmt.Sprintf("{\"address\":\"%s\",\"jailed_until\":\"1970-01-01T00:00:00Z\",\"tombstoned\":false,\"missed_blocks_counter\":\"0\",\"voter_set_counter\":\"0\"}", sdk.BytesToConsAddress(val.PubKey.Address())),
},
{
"valid address (text output)",
Expand All @@ -79,11 +79,10 @@ func (s *IntegrationTestSuite) TestGetCmdQuerySigningInfo() {
},
false,
fmt.Sprintf(`address: %s
index_offset: "0"
jailed_until: "1970-01-01T00:00:00Z"
missed_blocks_counter: "0"
start_height: "0"
tombstoned: false`, sdk.BytesToConsAddress(val.PubKey.Address())),
tombstoned: false
voter_set_counter: "0"`, sdk.BytesToConsAddress(val.PubKey.Address())),
},
}

Expand Down
8 changes: 4 additions & 4 deletions x/slashing/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ func TestExportAndInitGenesis(t *testing.T) {
app.SlashingKeeper.SetParams(ctx, testslashing.TestParams())

addrDels := simapp.AddTestAddrsIncremental(app, ctx, 2, sdk.TokensFromConsensusPower(200))
info1 := types.NewValidatorSigningInfo(addrDels[0].ToConsAddress(), int64(4), int64(3),
time.Now().UTC().Add(100000000000), false, int64(10))
info2 := types.NewValidatorSigningInfo(addrDels[1].ToConsAddress(), int64(5), int64(4),
time.Now().UTC().Add(10000000000), false, int64(10))
info1 := types.NewValidatorSigningInfo(addrDels[0].ToConsAddress(),
time.Now().UTC().Add(100000000000), false, int64(10), int64(3))
info2 := types.NewValidatorSigningInfo(addrDels[1].ToConsAddress(),
time.Now().UTC().Add(10000000000), false, int64(10), int64(4))

app.SlashingKeeper.SetValidatorSigningInfo(ctx, addrDels[0].ToConsAddress(), info1)
app.SlashingKeeper.SetValidatorSigningInfo(ctx, addrDels[1].ToConsAddress(), info2)
Expand Down
19 changes: 9 additions & 10 deletions x/slashing/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func TestJailedValidatorDelegations(t *testing.T) {
staking.EndBlocker(ctx, app.StakingKeeper)

// set dummy signing info
newInfo := types.NewValidatorSigningInfo(consAddr, 0, 0, time.Unix(0, 0), false, 0)
newInfo := types.NewValidatorSigningInfo(consAddr, time.Unix(0, 0), false, 0, 0)
app.SlashingKeeper.SetValidatorSigningInfo(ctx, consAddr, newInfo)

// delegate tokens to the validator
Expand Down Expand Up @@ -139,7 +139,7 @@ func TestInvalidMsg(t *testing.T) {
}

// Test a validator through uptime, downtime, revocation,
// unrevocation, starting height reset, and revocation again
// unrevocation, voter set counter reset, and revocation again
func TestHandleAbsentValidator(t *testing.T) {
// initial setup
app := simapp.Setup(false)
Expand All @@ -165,8 +165,7 @@ func TestHandleAbsentValidator(t *testing.T) {
// will exist since the validator has been bonded
info, found := app.SlashingKeeper.GetValidatorSigningInfo(ctx, sdk.BytesToConsAddress(val.Address()))
require.True(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, int64(0), info.IndexOffset)
require.Equal(t, int64(0), info.VoterSetCounter)
require.Equal(t, int64(0), info.MissedBlocksCounter)
require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil)
height := int64(0)
Expand All @@ -178,7 +177,7 @@ func TestHandleAbsentValidator(t *testing.T) {
}
info, found = app.SlashingKeeper.GetValidatorSigningInfo(ctx, sdk.BytesToConsAddress(val.Address()))
require.True(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, int64(1000), info.VoterSetCounter)
require.Equal(t, int64(0), info.MissedBlocksCounter)

// 500 blocks missed
Expand All @@ -188,7 +187,7 @@ func TestHandleAbsentValidator(t *testing.T) {
}
info, found = app.SlashingKeeper.GetValidatorSigningInfo(ctx, sdk.BytesToConsAddress(val.Address()))
require.True(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, int64(1500), info.VoterSetCounter)
require.Equal(t, app.SlashingKeeper.SignedBlocksWindow(ctx)-app.SlashingKeeper.MinSignedPerWindow(ctx), info.MissedBlocksCounter)

// validator should be bonded still
Expand All @@ -203,7 +202,7 @@ func TestHandleAbsentValidator(t *testing.T) {
app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), power, false)
info, found = app.SlashingKeeper.GetValidatorSigningInfo(ctx, sdk.BytesToConsAddress(val.Address()))
require.True(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, int64(1501), info.VoterSetCounter)
// counter now reset to zero
require.Equal(t, int64(0), info.MissedBlocksCounter)

Expand All @@ -225,7 +224,7 @@ func TestHandleAbsentValidator(t *testing.T) {
app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), power, false)
info, found = app.SlashingKeeper.GetValidatorSigningInfo(ctx, sdk.BytesToConsAddress(val.Address()))
require.True(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, int64(1502), info.VoterSetCounter)
require.Equal(t, int64(1), info.MissedBlocksCounter)

// end block
Expand Down Expand Up @@ -256,10 +255,10 @@ func TestHandleAbsentValidator(t *testing.T) {
// validator should have been slashed
require.True(t, amt.Sub(slashAmt).Equal(app.BankKeeper.GetBalance(ctx, bondPool.GetAddress(), app.StakingKeeper.BondDenom(ctx)).Amount))

// Validator start height should not have been changed
// Validator voter set counter should not have been changed
info, found = app.SlashingKeeper.GetValidatorSigningInfo(ctx, sdk.BytesToConsAddress(val.Address()))
require.True(t, found)
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, int64(1502), info.VoterSetCounter)
// 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)

Expand Down
8 changes: 4 additions & 4 deletions x/slashing/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ func (suite *SlashingTestSuite) SetupTest() {

addrDels := simapp.AddTestAddrsIncremental(app, ctx, 2, sdk.TokensFromConsensusPower(200))

info1 := types.NewValidatorSigningInfo(addrDels[0].ToConsAddress(), int64(4), int64(3),
time.Unix(2, 0), false, int64(10))
info2 := types.NewValidatorSigningInfo(addrDels[1].ToConsAddress(), int64(5), int64(4),
time.Unix(2, 0), false, int64(10))
info1 := types.NewValidatorSigningInfo(addrDels[0].ToConsAddress(),
time.Unix(2, 0), false, int64(10), int64(3))
info2 := types.NewValidatorSigningInfo(addrDels[1].ToConsAddress(),
time.Unix(2, 0), false, int64(10), int64(4))

app.SlashingKeeper.SetValidatorSigningInfo(ctx, addrDels[0].ToConsAddress(), info1)
app.SlashingKeeper.SetValidatorSigningInfo(ctx, addrDels[1].ToConsAddress(), info2)
Expand Down
5 changes: 2 additions & 3 deletions x/slashing/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@ import (
)

func (k Keeper) AfterValidatorBonded(ctx sdk.Context, address sdk.ConsAddress, _ sdk.ValAddress) {
// Update the signing info start height or create a new signing info
// Update the signing info voter set counter or create a new signing info
_, found := k.GetValidatorSigningInfo(ctx, address)
if !found {
signingInfo := types.NewValidatorSigningInfo(
address,
ctx.BlockHeight(),
0,
time.Unix(0, 0),
false,
0,
0,
)
k.SetValidatorSigningInfo(ctx, address, signingInfo)
}
Expand Down
16 changes: 8 additions & 8 deletions x/slashing/keeper/infractions.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre
}

// this is a relative index, so it counts blocks the validator *should* have signed
// will use the 0-value default signing info if not present, except for start height
index := signInfo.IndexOffset % k.SignedBlocksWindow(ctx)
signInfo.IndexOffset++
// will use the 0-value default signing info if not present, except for the beginning
signInfo.VoterSetCounter++
voterSetCounter := signInfo.VoterSetCounter
index := (voterSetCounter - 1) % k.SignedBlocksWindow(ctx)

// Update signed block bit array & counter
// This counter just tracks the sum of the bit array
Expand Down Expand Up @@ -69,11 +70,11 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre
)
}

minHeight := signInfo.StartHeight + k.SignedBlocksWindow(ctx)
minVoterSetCount := k.SignedBlocksWindow(ctx)
maxMissed := k.SignedBlocksWindow(ctx) - minSignedPerWindow

// if we are past the minimum height and the validator has missed too many blocks, punish them
if height > minHeight && signInfo.MissedBlocksCounter > maxMissed {
// if we have joined enough times to voter set and the validator has missed too many blocks, punish them
if voterSetCounter >= minVoterSetCount && signInfo.MissedBlocksCounter > maxMissed {
validator := k.sk.ValidatorByConsAddr(ctx, consAddr)
if validator != nil && !validator.IsJailed() {
// Downtime confirmed: slash and jail the validator
Expand All @@ -100,14 +101,13 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre

// 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)

logger.Info(
"slashing and jailing validator due to liveness fault",
"height", height,
"validator", consAddr.String(),
"min_height", minHeight,
"voter_set_counter", voterSetCounter,
"threshold", minSignedPerWindow,
"slashed", k.SlashFractionDowntime(ctx).String(),
"jailed_until", signInfo.JailedUntil,
Expand Down
Loading

0 comments on commit 5975502

Please sign in to comment.