From 366dea6d368b645a76e9cecfbb8e30adcbd8107c Mon Sep 17 00:00:00 2001 From: Youngtaek Yoon Date: Wed, 6 Oct 2021 01:45:10 +0000 Subject: [PATCH 1/7] feat: introduce `VoterSetCounter` Introducing a concept "voter set counter" eases integration with `ostracon`'s `VoterSet`. --- client/docs/swagger-ui/swagger.yaml | 64 +++---- docs/core/proto-docs.md | 3 +- proto/lbm/slashing/v1/slashing.proto | 12 +- simapp/export.go | 4 +- x/slashing/abci_test.go | 3 +- x/slashing/client/cli/cli_test.go | 7 +- x/slashing/genesis_test.go | 8 +- x/slashing/handler_test.go | 19 +- x/slashing/keeper/grpc_query_test.go | 8 +- x/slashing/keeper/hooks.go | 5 +- x/slashing/keeper/infractions.go | 16 +- x/slashing/keeper/keeper_test.go | 36 ++-- x/slashing/keeper/signing_info_test.go | 12 +- x/slashing/simulation/decoder_test.go | 2 +- x/slashing/simulation/operations_test.go | 4 +- x/slashing/spec/02_state.md | 20 +-- x/slashing/spec/04_begin_block.md | 130 ++++++++------ x/slashing/spec/05_hooks.md | 7 +- x/slashing/types/signing_info.go | 16 +- x/slashing/types/slashing.pb.go | 214 +++++++++-------------- x/wasm/linkwasmd/app/export.go | 4 +- 21 files changed, 268 insertions(+), 326 deletions(-) diff --git a/client/docs/swagger-ui/swagger.yaml b/client/docs/swagger-ui/swagger.yaml index ba32b52ef8..735a807e0d 100644 --- a/client/docs/swagger-ui/swagger.yaml +++ b/client/docs/swagger-ui/swagger.yaml @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index bd8d898184..3f7f62916b 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -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 | diff --git a/proto/lbm/slashing/v1/slashing.proto b/proto/lbm/slashing/v1/slashing.proto index dd32a5a64f..c6d9863f35 100644 --- a/proto/lbm/slashing/v1/slashing.proto +++ b/proto/lbm/slashing/v1/slashing.proto @@ -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. diff --git a/simapp/export.go b/simapp/export.go index a69e93ae67..82c601a356 100644 --- a/simapp/export.go +++ b/simapp/export.go @@ -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 }, diff --git a/x/slashing/abci_test.go b/x/slashing/abci_test.go index 83c90bf85a..82cb42767c 100644 --- a/x/slashing/abci_test.go +++ b/x/slashing/abci_test.go @@ -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) diff --git a/x/slashing/client/cli/cli_test.go b/x/slashing/client/cli/cli_test.go index 1d79ef41b0..64ed523522 100644 --- a/x/slashing/client/cli/cli_test.go +++ b/x/slashing/client/cli/cli_test.go @@ -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)", @@ -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())), }, } diff --git a/x/slashing/genesis_test.go b/x/slashing/genesis_test.go index 88f284261a..e0a5efc757 100644 --- a/x/slashing/genesis_test.go +++ b/x/slashing/genesis_test.go @@ -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) diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 2482ac09b7..4229e2d185 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -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 @@ -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) @@ -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) @@ -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 @@ -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 @@ -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) @@ -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 @@ -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) diff --git a/x/slashing/keeper/grpc_query_test.go b/x/slashing/keeper/grpc_query_test.go index bf343a7098..b1f70c5cbd 100644 --- a/x/slashing/keeper/grpc_query_test.go +++ b/x/slashing/keeper/grpc_query_test.go @@ -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) diff --git a/x/slashing/keeper/hooks.go b/x/slashing/keeper/hooks.go index 19020ae56b..e6a5c87db1 100644 --- a/x/slashing/keeper/hooks.go +++ b/x/slashing/keeper/hooks.go @@ -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) } diff --git a/x/slashing/keeper/infractions.go b/x/slashing/keeper/infractions.go index d7c694ff11..f4badbff61 100644 --- a/x/slashing/keeper/infractions.go +++ b/x/slashing/keeper/infractions.go @@ -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 + voterSetCounter := signInfo.VoterSetCounter + signInfo.VoterSetCounter++ + index := voterSetCounter % k.SignedBlocksWindow(ctx) // Update signed block bit array & counter // This counter just tracks the sum of the bit array @@ -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 @@ -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, + "min_voter_set_count", minVoterSetCount, "threshold", minSignedPerWindow, "slashed", k.SlashFractionDowntime(ctx).String(), "jailed_until", signInfo.JailedUntil, diff --git a/x/slashing/keeper/keeper_test.go b/x/slashing/keeper/keeper_test.go index 52a9ed00e8..a5b750fce7 100644 --- a/x/slashing/keeper/keeper_test.go +++ b/x/slashing/keeper/keeper_test.go @@ -77,7 +77,7 @@ func TestUnJailNotBonded(t *testing.T) { } // Test a new validator entering the validator set -// Ensure that SigningInfo.StartHeight is set correctly +// Ensure that SigningInfo.VoterSetCounter is set correctly // and that they are not immediately jailed func TestHandleNewValidator(t *testing.T) { app := simapp.Setup(false) @@ -107,8 +107,7 @@ func TestHandleNewValidator(t *testing.T) { info, found := app.SlashingKeeper.GetValidatorSigningInfo(ctx, sdk.BytesToConsAddress(val.Address())) require.True(t, found) - require.Equal(t, app.SlashingKeeper.SignedBlocksWindow(ctx)+1, info.StartHeight) - require.Equal(t, int64(2), info.IndexOffset) + require.Equal(t, int64(2), info.VoterSetCounter) require.Equal(t, int64(1), info.MissedBlocksCounter) require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) @@ -173,7 +172,7 @@ func TestHandleAlreadyJailed(t *testing.T) { // 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 +// the voter set counter of the signing info is reset correctly func TestValidatorDippingInAndOut(t *testing.T) { // initial setup @@ -223,18 +222,29 @@ func TestValidatorDippingInAndOut(t *testing.T) { tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false) newPower := int64(150) - // validator misses a block - app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, false) - height++ + // 300 more blocks happend + latest := height + for ; height < latest+300; height++ { + ctx = ctx.WithBlockHeight(height) + app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true) + } - // shouldn't be jailed/kicked yet + // validator misses 501 blocks exceeding the liveness threshold + latest = height + for ; height < latest+501; height++ { + ctx = ctx.WithBlockHeight(height) + app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, false) + } + + // shouldn't be jailed/kicked yet because it had not joined to vote set 1000 times + // 100 times + (kicked) + 300 times + 501 times = 901 times tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false) - // validator misses 500 more blocks, 501 total - latest := height - for ; height < latest+500; height++ { + // 100 more blocks happend + latest = height + for ; height < latest+100; height++ { ctx = ctx.WithBlockHeight(height) - app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, false) + app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true) } // should now be jailed & kicked @@ -245,7 +255,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { signInfo, found := app.SlashingKeeper.GetValidatorSigningInfo(ctx, consAddr) require.True(t, found) require.Equal(t, int64(0), signInfo.MissedBlocksCounter) - require.Equal(t, int64(0), signInfo.IndexOffset) + require.Equal(t, int64(1001), signInfo.VoterSetCounter) // array should be cleared for offset := int64(0); offset < app.SlashingKeeper.SignedBlocksWindow(ctx); offset++ { missed := app.SlashingKeeper.GetValidatorMissedBlockBitArray(ctx, consAddr, offset) diff --git a/x/slashing/keeper/signing_info_test.go b/x/slashing/keeper/signing_info_test.go index 2318d8e91a..ac8d5ea204 100644 --- a/x/slashing/keeper/signing_info_test.go +++ b/x/slashing/keeper/signing_info_test.go @@ -21,17 +21,15 @@ func TestGetSetValidatorSigningInfo(t *testing.T) { require.False(t, found) newInfo := types.NewValidatorSigningInfo( addrDels[0].ToConsAddress(), - int64(4), - int64(3), time.Unix(2, 0), false, int64(10), + int64(3), ) app.SlashingKeeper.SetValidatorSigningInfo(ctx, addrDels[0].ToConsAddress(), newInfo) info, found = app.SlashingKeeper.GetValidatorSigningInfo(ctx, addrDels[0].ToConsAddress()) require.True(t, found) - require.Equal(t, info.StartHeight, int64(4)) - require.Equal(t, info.IndexOffset, int64(3)) + require.Equal(t, info.VoterSetCounter, int64(3)) require.Equal(t, info.JailedUntil, time.Unix(2, 0).UTC()) require.Equal(t, info.MissedBlocksCounter, int64(10)) } @@ -58,11 +56,10 @@ func TestTombstoned(t *testing.T) { newInfo := types.NewValidatorSigningInfo( addrDels[0].ToConsAddress(), - int64(4), - int64(3), time.Unix(2, 0), false, int64(10), + int64(3), ) app.SlashingKeeper.SetValidatorSigningInfo(ctx, addrDels[0].ToConsAddress(), newInfo) @@ -81,11 +78,10 @@ func TestJailUntil(t *testing.T) { newInfo := types.NewValidatorSigningInfo( addrDels[0].ToConsAddress(), - int64(4), - int64(3), time.Unix(2, 0), false, int64(10), + int64(3), ) app.SlashingKeeper.SetValidatorSigningInfo(ctx, addrDels[0].ToConsAddress(), newInfo) app.SlashingKeeper.JailUntil(ctx, addrDels[0].ToConsAddress(), time.Unix(253402300799, 0).UTC()) diff --git a/x/slashing/simulation/decoder_test.go b/x/slashing/simulation/decoder_test.go index 52c3de462a..b6297d3208 100644 --- a/x/slashing/simulation/decoder_test.go +++ b/x/slashing/simulation/decoder_test.go @@ -28,7 +28,7 @@ func TestDecodeStore(t *testing.T) { cdc, _ := simapp.MakeCodecs() dec := simulation.NewDecodeStore(cdc) - info := types.NewValidatorSigningInfo(consAddr1, 0, 1, time.Now().UTC(), false, 0) + info := types.NewValidatorSigningInfo(consAddr1, time.Now().UTC(), false, 0, 1) bechPK := sdk.MustBech32ifyPubKey(sdk.Bech32PubKeyTypeConsPub, delPk1) missed := gogotypes.BoolValue{Value: true} diff --git a/x/slashing/simulation/operations_test.go b/x/slashing/simulation/operations_test.go index 6ecd2e2679..699e0a76f6 100644 --- a/x/slashing/simulation/operations_test.go +++ b/x/slashing/simulation/operations_test.go @@ -69,8 +69,8 @@ func TestSimulateMsgUnjail(t *testing.T) { app.StakingKeeper.SetValidatorByConsAddr(ctx, validator0) val0ConsAddress, err := validator0.GetConsAddr() require.NoError(t, err) - info := types.NewValidatorSigningInfo(val0ConsAddress, int64(4), int64(3), - time.Unix(2, 0), false, int64(10)) + info := types.NewValidatorSigningInfo(val0ConsAddress, + time.Unix(2, 0), false, int64(10), int64(3)) app.SlashingKeeper.SetValidatorSigningInfo(ctx, val0ConsAddress, info) // put validator0 in jail diff --git a/x/slashing/spec/02_state.md b/x/slashing/spec/02_state.md index 17931ce866..2f2acc6d8d 100644 --- a/x/slashing/spec/02_state.md +++ b/x/slashing/spec/02_state.md @@ -45,30 +45,26 @@ The information stored for tracking validator liveness is as follows: // liveness activity. message ValidatorSigningInfo { string address = 1; - // height at which validator was first a candidate OR was unjailed - int64 start_height = 2; - // index offset into signed block bit array - int64 index_offset = 3; // timestamp validator cannot be unjailed until - google.protobuf.Timestamp jailed_until = 4; + google.protobuf.Timestamp jailed_until = 2; // 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; + int64 missed_blocks_counter = 4; + // how many times the validator joined to voter set + int64 voter_set_counter = 5; } ``` Where: - **Address**: The validator's consensus address. -- **StartHeight**: The height that the candidate became an active validator - (with non-zero voting power). -- **IndexOffset**: Index which is incremented each time the validator was a bonded - in a block and may have signed a precommit or not. This in conjunction with the - `SignedBlocksWindow` param determines the index in the `MissedBlocksBitArray`. - **JailedUntil**: Time for which the validator is jailed until due to liveness downtime. - **Tombstoned**: Desribes if the validator is tombstoned or not. It is set once the validator commits an equivocation or for any other configured misbehiavor. - **MissedBlocksCounter**: A counter kept to avoid unnecessary array reads. Note that `Sum(MissedBlocksBitArray)` equals `MissedBlocksCounter` always. +- **VoterSetCounter**: A counter keeps tracking the number of incidents the validator + joins to the voter set. This in conjunction with the `SignedBlocksWindow` param + determines the index in the `MissedBlocksBitArray`. diff --git a/x/slashing/spec/04_begin_block.md b/x/slashing/spec/04_begin_block.md index 99572c419f..3af642acb7 100644 --- a/x/slashing/spec/04_begin_block.md +++ b/x/slashing/spec/04_begin_block.md @@ -7,92 +7,106 @@ order: 4 ## Liveness Tracking At the beginning of each block, we update the `ValidatorSigningInfo` for each -validator and check if they've crossed below the liveness threshold over a +voter and check if they've crossed below the liveness threshold over a sliding window. This sliding window is defined by `SignedBlocksWindow` and the -index in this window is determined by `IndexOffset` found in the validator's -`ValidatorSigningInfo`. For each block processed, the `IndexOffset` is incremented -regardless if the validator signed or not. Once the index is determined, the +index in this window is determined by `VoterSetCounter` found in the voter's +`ValidatorSigningInfo`. For each vote processed, the `VoterSetCounter` is incremented +regardless if the voter signed or not. Once the index is determined, the `MissedBlocksBitArray` and `MissedBlocksCounter` are updated accordingly. -Finally, in order to determine if a validator crosses below the liveness threshold, +Finally, in order to determine if a voter crosses below the liveness threshold, we fetch the maximum number of blocks missed, `maxMissed`, which is `SignedBlocksWindow - (MinSignedPerWindow * SignedBlocksWindow)` and the minimum -height at which we can determine liveness, `minHeight`. If the current block is -greater than `minHeight` and the validator's `MissedBlocksCounter` is greater than +height at which we can determine liveness, `minVoterSetCount`. If the voter set counter is +greater than `minVoterSetCount` and the voter's `MissedBlocksCounter` is greater than `maxMissed`, they will be slashed by `SlashFractionDowntime`, will be jailed for `DowntimeJailDuration`, and have the following values reset: -`MissedBlocksBitArray`, `MissedBlocksCounter`, and `IndexOffset`. +`MissedBlocksBitArray` and `MissedBlocksCounter`. **Note**: Liveness slashes do **NOT** lead to a tombstombing. ```go -height := block.Height +height := ctx.BlockHeight() -for vote in block.LastCommitInfo.Votes { - signInfo := GetValidatorSigningInfo(vote.Validator.Address) - - // This is a relative index, so we counts blocks the validator SHOULD have - // signed. We use the 0-value default signing info if not present, except for - // start height. - index := signInfo.IndexOffset % SignedBlocksWindow() - signInfo.IndexOffset++ +for _, voteInfo := range req.LastCommitInfo.getVotes() { + // fetch the validator public key + consAddr := sdk.BytesToConsAddress(voteInfo.Validator.Address) + if _, err := k.GetPubkey(ctx, addr); err != nil { + panic(fmt.Sprintf("Validator consensus-address %s not found", consAddr)) + } - // Update MissedBlocksBitArray and MissedBlocksCounter. The MissedBlocksCounter - // just tracks the sum of MissedBlocksBitArray. That way we avoid needing to - // read/write the whole array each time. - missedPrevious := GetValidatorMissedBlockBitArray(vote.Validator.Address, index) - missed := !signed + // fetch signing info + signInfo, found := k.GetValidatorSigningInfo(ctx, consAddr) + if !found { + panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr)) + } + // 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 the beginning + voterSetCounter := signInfo.VoterSetCounter + signInfo.VoterSetCounter++ + index := voterSetCounter % k.SignedBlocksWindow(ctx) + + // 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.GetValidatorMissedBlockBitArray(ctx, consAddr, index) + missed := !voteInfo.SignedLastBlock switch { - case !missedPrevious && missed: - // array index has changed from not missed to missed, increment counter - SetValidatorMissedBlockBitArray(vote.Validator.Address, index, true) + case !previous && missed: + // Array value has changed from not missed to missed, increment counter + k.SetValidatorMissedBlockBitArray(ctx, consAddr, index, true) signInfo.MissedBlocksCounter++ - - case missedPrevious && !missed: - // array index has changed from missed to not missed, decrement counter - SetValidatorMissedBlockBitArray(vote.Validator.Address, index, false) + case previous && !missed: + // Array value has changed from missed to not missed, decrement counter + k.SetValidatorMissedBlockBitArray(ctx, consAddr, index, false) signInfo.MissedBlocksCounter-- - default: - // array index at this index has not changed; no need to update counter + // Array value at this index has not changed, no need to update counter } + minSignedPerWindow := k.MinSignedPerWindow(ctx) + if missed { // emit events... } - minHeight := signInfo.StartHeight + SignedBlocksWindow() - maxMissed := SignedBlocksWindow() - MinSignedPerWindow() + minVoterSetCount := k.SignedBlocksWindow(ctx) + maxMissed := k.SignedBlocksWindow(ctx) - minSignedPerWindow - // If we are past the minimum height and the validator has missed too many - // jail and slash them. - if height > minHeight && signInfo.MissedBlocksCounter > maxMissed { - validator := ValidatorByConsAddr(vote.Validator.Address) + // 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 + // We need to retrieve the stake distribution which signed the block, so we subtract ValidatorUpdateDelay from the evidence height, + // and subtract an additional 1 since this is the LastCommit. + // Note that this *can* result in a negative "distributionHeight" up to -ValidatorUpdateDelay-1, + // i.e. at the end of the pre-genesis block (none) = at the beginning of the genesis block. + // That's fine since this is just used to filter unbonding delegations & redelegations. + distributionHeight := height - sdk.ValidatorUpdateDelay - 1 - // emit events... + // emit events... + + k.sk.Slash(ctx, consAddr, distributionHeight, voteInfo.Validator.Power, k.SlashFractionDowntime(ctx)) + k.sk.Jail(ctx, consAddr) + + signInfo.JailedUntil = ctx.BlockHeader().Time.Add(k.DowntimeJailDuration(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.VoterSetCounter = 0 + k.clearValidatorMissedBlockBitArray(ctx, consAddr) + + // log events... + } else { + // validator was (a) not found or (b) already jailed so we do not slash - // We need to retrieve the stake distribution which signed the block, so we - // subtract ValidatorUpdateDelay from the block height, and subtract an - // additional 1 since this is the LastCommit. - // - // Note, that this CAN result in a negative "distributionHeight" up to - // -ValidatorUpdateDelay-1, i.e. at the end of the pre-genesis block (none) = at the beginning of the genesis block. - // That's fine since this is just used to filter unbonding delegations & redelegations. - distributionHeight := height - sdk.ValidatorUpdateDelay - 1 - - Slash(vote.Validator.Address, distributionHeight, vote.Validator.Power, SlashFractionDowntime()) - Jail(vote.Validator.Address) - - signInfo.JailedUntil = block.Time.Add(DowntimeJailDuration()) - - // 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 - ClearValidatorMissedBlockBitArray(vote.Validator.Address) + // log events... + } } - SetValidatorSigningInfo(vote.Validator.Address, signInfo) + // Set the updated signing info + k.SetValidatorSigningInfo(ctx, consAddr, signInfo) } ``` diff --git a/x/slashing/spec/05_hooks.md b/x/slashing/spec/05_hooks.md index 8f78cdff01..d87f6d289a 100644 --- a/x/slashing/spec/05_hooks.md +++ b/x/slashing/spec/05_hooks.md @@ -9,7 +9,7 @@ In this section we describe the "hooks" - slashing module code that runs when ot ## Validator Bonded Upon successful first-time bonding of a new validator, we create a new `ValidatorSigningInfo` structure for the -now-bonded validator, which `StartHeight` of the current block. +now-bonded validator. ``` onValidatorBonded(address sdk.ValAddress) @@ -17,11 +17,10 @@ onValidatorBonded(address sdk.ValAddress) signingInfo, found = GetValidatorSigningInfo(address) if !found { signingInfo = ValidatorSigningInfo { - StartHeight : CurrentHeight, - IndexOffset : 0, JailedUntil : time.Unix(0, 0), Tombstone : false, - MissedBloskCounter : 0 + MissedBloskCounter : 0, + VoterSetCounter : 0, } setValidatorSigningInfo(signingInfo) } diff --git a/x/slashing/types/signing_info.go b/x/slashing/types/signing_info.go index 5689ebe5e7..53118d0682 100644 --- a/x/slashing/types/signing_info.go +++ b/x/slashing/types/signing_info.go @@ -11,17 +11,16 @@ import ( // NewValidatorSigningInfo creates a new ValidatorSigningInfo instance //nolint:interfacer func NewValidatorSigningInfo( - condAddr sdk.ConsAddress, startHeight, indexOffset int64, - jailedUntil time.Time, tombstoned bool, missedBlocksCounter int64, + condAddr sdk.ConsAddress, jailedUntil time.Time, tombstoned bool, + missedBlocksCounter, voterSetCounter int64, ) ValidatorSigningInfo { return ValidatorSigningInfo{ Address: condAddr.String(), - StartHeight: startHeight, - IndexOffset: indexOffset, JailedUntil: jailedUntil, Tombstoned: tombstoned, MissedBlocksCounter: missedBlocksCounter, + VoterSetCounter: voterSetCounter, } } @@ -29,13 +28,12 @@ func NewValidatorSigningInfo( func (i ValidatorSigningInfo) String() string { return fmt.Sprintf(`Validator Signing Info: Address: %s - Start Height: %d - Index Offset: %d Jailed Until: %v Tombstoned: %t - Missed Blocks Counter: %d`, - i.Address, i.StartHeight, i.IndexOffset, i.JailedUntil, - i.Tombstoned, i.MissedBlocksCounter) + Missed Blocks Counter: %d + Voter Set Counter: %d`, + i.Address, i.JailedUntil, i.Tombstoned, + i.MissedBlocksCounter, i.VoterSetCounter) } // unmarshal a validator signing info from a store value diff --git a/x/slashing/types/slashing.pb.go b/x/slashing/types/slashing.pb.go index 8434c8f66c..f3d2e186e7 100644 --- a/x/slashing/types/slashing.pb.go +++ b/x/slashing/types/slashing.pb.go @@ -33,17 +33,15 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // liveness activity. type ValidatorSigningInfo struct { Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - // height at which validator was first a candidate OR was unjailed - StartHeight int64 `protobuf:"varint,2,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty" yaml:"start_height"` - // index offset into signed block bit array - IndexOffset int64 `protobuf:"varint,3,opt,name=index_offset,json=indexOffset,proto3" json:"index_offset,omitempty" yaml:"index_offset"` // timestamp validator cannot be unjailed until - JailedUntil time.Time `protobuf:"bytes,4,opt,name=jailed_until,json=jailedUntil,proto3,stdtime" json:"jailed_until" yaml:"jailed_until"` + JailedUntil time.Time `protobuf:"bytes,2,opt,name=jailed_until,json=jailedUntil,proto3,stdtime" json:"jailed_until" yaml:"jailed_until"` // whether or not a validator has been tombstoned (killed out of validator // set) - Tombstoned bool `protobuf:"varint,5,opt,name=tombstoned,proto3" json:"tombstoned,omitempty"` + Tombstoned bool `protobuf:"varint,3,opt,name=tombstoned,proto3" json:"tombstoned,omitempty"` // missed blocks counter (to avoid scanning the array every time) - MissedBlocksCounter int64 `protobuf:"varint,6,opt,name=missed_blocks_counter,json=missedBlocksCounter,proto3" json:"missed_blocks_counter,omitempty" yaml:"missed_blocks_counter"` + MissedBlocksCounter int64 `protobuf:"varint,4,opt,name=missed_blocks_counter,json=missedBlocksCounter,proto3" json:"missed_blocks_counter,omitempty" yaml:"missed_blocks_counter"` + // how many times the validator joined to voter set + VoterSetCounter int64 `protobuf:"varint,5,opt,name=voter_set_counter,json=voterSetCounter,proto3" json:"voter_set_counter,omitempty" yaml:"voter_set_counter"` } func (m *ValidatorSigningInfo) Reset() { *m = ValidatorSigningInfo{} } @@ -85,20 +83,6 @@ func (m *ValidatorSigningInfo) GetAddress() string { return "" } -func (m *ValidatorSigningInfo) GetStartHeight() int64 { - if m != nil { - return m.StartHeight - } - return 0 -} - -func (m *ValidatorSigningInfo) GetIndexOffset() int64 { - if m != nil { - return m.IndexOffset - } - return 0 -} - func (m *ValidatorSigningInfo) GetJailedUntil() time.Time { if m != nil { return m.JailedUntil @@ -120,6 +104,13 @@ func (m *ValidatorSigningInfo) GetMissedBlocksCounter() int64 { return 0 } +func (m *ValidatorSigningInfo) GetVoterSetCounter() int64 { + if m != nil { + return m.VoterSetCounter + } + return 0 +} + // Params represents the parameters used for by the slashing module. type Params struct { SignedBlocksWindow int64 `protobuf:"varint,1,opt,name=signed_blocks_window,json=signedBlocksWindow,proto3" json:"signed_blocks_window,omitempty" yaml:"signed_blocks_window"` @@ -184,47 +175,46 @@ func init() { func init() { proto.RegisterFile("lbm/slashing/v1/slashing.proto", fileDescriptor_037930fac4075874) } var fileDescriptor_037930fac4075874 = []byte{ - // 632 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0x3f, 0x6f, 0xd3, 0x4e, - 0x18, 0xce, 0xfd, 0xd2, 0x5f, 0x29, 0x97, 0x48, 0x48, 0x6e, 0x4a, 0x4d, 0x00, 0x3b, 0xf5, 0x14, - 0x06, 0x6c, 0xb5, 0x6c, 0x1d, 0x4d, 0x55, 0x51, 0x90, 0xa0, 0xb8, 0x05, 0x24, 0x06, 0xac, 0x73, - 0x7c, 0x71, 0x8e, 0x9e, 0xef, 0x22, 0xdf, 0xa5, 0x7f, 0x10, 0x1b, 0x0b, 0x12, 0x4b, 0xc7, 0x8e, - 0x1d, 0xf9, 0x28, 0x1d, 0x3b, 0x22, 0x86, 0x80, 0xd2, 0x05, 0xd6, 0x7e, 0x02, 0xe4, 0x3b, 0xbb, - 0x8d, 0xda, 0x64, 0xe8, 0x76, 0xef, 0xf3, 0xbc, 0xef, 0xf3, 0xfe, 0xb5, 0xa1, 0x45, 0xa3, 0xd4, - 0x13, 0x14, 0x89, 0x1e, 0x61, 0x89, 0xb7, 0xbb, 0x7c, 0xf1, 0x76, 0xfb, 0x19, 0x97, 0xdc, 0xb8, - 0x43, 0xa3, 0xd4, 0xbd, 0xc0, 0x76, 0x97, 0x9b, 0x8d, 0x84, 0x27, 0x5c, 0x71, 0x5e, 0xfe, 0xd2, - 0x6e, 0x4d, 0x2b, 0xe1, 0x3c, 0xa1, 0xd8, 0x53, 0x56, 0x34, 0xe8, 0x7a, 0xf1, 0x20, 0x43, 0x92, - 0x70, 0x56, 0xf0, 0xf6, 0x55, 0x5e, 0x92, 0x14, 0x0b, 0x89, 0xd2, 0xbe, 0x76, 0x70, 0xbe, 0x56, - 0x61, 0xe3, 0x2d, 0xa2, 0x24, 0x46, 0x92, 0x67, 0x5b, 0x24, 0x61, 0x84, 0x25, 0x1b, 0xac, 0xcb, - 0x0d, 0x13, 0xde, 0x42, 0x71, 0x9c, 0x61, 0x21, 0x4c, 0xd0, 0x02, 0xed, 0xdb, 0x41, 0x69, 0x1a, - 0xab, 0xb0, 0x2e, 0x24, 0xca, 0x64, 0xd8, 0xc3, 0x24, 0xe9, 0x49, 0xf3, 0xbf, 0x16, 0x68, 0x57, - 0xfd, 0xc5, 0xf3, 0xa1, 0x3d, 0x7f, 0x80, 0x52, 0xba, 0xea, 0x8c, 0xb3, 0x4e, 0x50, 0x53, 0xe6, - 0x33, 0x65, 0xe5, 0xb1, 0x84, 0xc5, 0x78, 0x3f, 0xe4, 0xdd, 0xae, 0xc0, 0xd2, 0xac, 0x5e, 0x8d, - 0x1d, 0x67, 0x9d, 0xa0, 0xa6, 0xcc, 0x57, 0xca, 0x32, 0x3e, 0xc0, 0xfa, 0x47, 0x44, 0x28, 0x8e, - 0xc3, 0x01, 0x93, 0x84, 0x9a, 0x33, 0x2d, 0xd0, 0xae, 0xad, 0x34, 0x5d, 0xdd, 0xa2, 0x5b, 0xb6, - 0xe8, 0x6e, 0x97, 0x2d, 0xfa, 0xf6, 0xc9, 0xd0, 0xae, 0x5c, 0x6a, 0x8f, 0x47, 0x3b, 0x87, 0xbf, - 0x6c, 0x10, 0xd4, 0x34, 0xf4, 0x26, 0x47, 0x0c, 0x0b, 0x42, 0xc9, 0xd3, 0x48, 0x48, 0xce, 0x70, - 0x6c, 0xfe, 0xdf, 0x02, 0xed, 0xb9, 0x60, 0x0c, 0x31, 0xb6, 0xe1, 0x42, 0x4a, 0x84, 0xc0, 0x71, - 0x18, 0x51, 0xde, 0xd9, 0x11, 0x61, 0x87, 0x0f, 0x98, 0xc4, 0x99, 0x39, 0xab, 0x9a, 0x68, 0x9d, - 0x0f, 0xed, 0x07, 0x3a, 0xd1, 0x44, 0x37, 0x27, 0x98, 0xd7, 0xb8, 0xaf, 0xe0, 0xa7, 0x1a, 0x5d, - 0x9d, 0x3b, 0x3a, 0xb6, 0x2b, 0x7f, 0x8e, 0x6d, 0xe0, 0xfc, 0x9d, 0x81, 0xb3, 0x9b, 0x28, 0x43, - 0xa9, 0x30, 0x5e, 0xc3, 0x86, 0x20, 0x09, 0xbb, 0xd4, 0xd8, 0x23, 0x2c, 0xe6, 0x7b, 0x6a, 0x13, - 0x55, 0xdf, 0x3e, 0x1f, 0xda, 0xf7, 0x8b, 0x51, 0x4f, 0xf0, 0x72, 0x02, 0x43, 0xc3, 0x3a, 0xd1, - 0x3b, 0x05, 0x1a, 0x9f, 0xf3, 0xea, 0x59, 0x58, 0x04, 0xf4, 0x71, 0x56, 0x6a, 0xe6, 0xeb, 0xab, - 0xfb, 0x1b, 0xf9, 0xa8, 0x7e, 0x0e, 0xed, 0xa5, 0x84, 0xc8, 0xde, 0x20, 0x72, 0x3b, 0x3c, 0xf5, - 0x28, 0x61, 0xd8, 0xa3, 0x51, 0xfa, 0x58, 0xc4, 0x3b, 0x9e, 0x3c, 0xe8, 0x63, 0xe1, 0xae, 0xe1, - 0xce, 0x78, 0x9b, 0x13, 0xf4, 0x9c, 0xc0, 0x48, 0x09, 0xdb, 0x52, 0xf0, 0x26, 0xce, 0x8a, 0xec, - 0x9f, 0xe0, 0xdd, 0x98, 0xef, 0xb1, 0xfc, 0xfa, 0xc2, 0x7c, 0xe6, 0x61, 0x79, 0xa7, 0xea, 0x02, - 0x6a, 0x2b, 0xf7, 0xae, 0x6d, 0x71, 0xad, 0x70, 0xf0, 0x1f, 0x15, 0x4b, 0x7c, 0xa8, 0x93, 0x4e, - 0x96, 0x71, 0x8e, 0xf2, 0x75, 0x36, 0x4a, 0xf2, 0x39, 0x22, 0xb4, 0x14, 0x30, 0xbe, 0x01, 0xd8, - 0x54, 0x5f, 0x52, 0xd8, 0xcd, 0x50, 0x27, 0x87, 0xc2, 0x98, 0x0f, 0x22, 0x8a, 0x55, 0xf1, 0xea, - 0x8c, 0xea, 0xfe, 0xcb, 0x9b, 0xf4, 0xbf, 0x54, 0x0c, 0x7f, 0xaa, 0xa8, 0x13, 0x2c, 0x2a, 0x72, - 0xbd, 0xe0, 0xd6, 0x14, 0x95, 0x0f, 0xc5, 0xf8, 0x02, 0xe0, 0xe2, 0xb5, 0x40, 0x5d, 0xb5, 0xba, - 0xb9, 0xba, 0xff, 0xe2, 0x26, 0xa5, 0x58, 0x53, 0x4a, 0xd1, 0x8a, 0x4e, 0xb0, 0x70, 0xa5, 0x0e, - 0x8d, 0xfb, 0xeb, 0xdf, 0x47, 0x16, 0x38, 0x19, 0x59, 0xe0, 0x74, 0x64, 0x81, 0xdf, 0x23, 0x0b, - 0x1c, 0x9e, 0x59, 0x95, 0xd3, 0x33, 0xab, 0xf2, 0xe3, 0xcc, 0xaa, 0xbc, 0x6f, 0x4f, 0xcb, 0xbc, - 0x7f, 0xf9, 0xcb, 0x52, 0x45, 0x44, 0xb3, 0x6a, 0x5f, 0x4f, 0xfe, 0x05, 0x00, 0x00, 0xff, 0xff, - 0x41, 0xf4, 0x50, 0x29, 0xcf, 0x04, 0x00, 0x00, + // 613 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0xb1, 0x4f, 0xdc, 0x3e, + 0x18, 0x3d, 0x03, 0x3f, 0x7e, 0xd4, 0x9c, 0x84, 0x1a, 0x8e, 0x72, 0xbd, 0xd2, 0xf8, 0xc8, 0x74, + 0x1d, 0x9a, 0x08, 0xba, 0x31, 0xa6, 0x08, 0x95, 0x56, 0xaa, 0x68, 0xa0, 0xad, 0xd4, 0xa1, 0x91, + 0x73, 0x31, 0xc1, 0xc5, 0xb1, 0x4f, 0xb1, 0x0f, 0x4a, 0xd5, 0xad, 0x63, 0x17, 0x46, 0x46, 0xc6, + 0xfe, 0x29, 0x8c, 0x8c, 0x15, 0x43, 0x5a, 0x1d, 0x4b, 0xbb, 0xde, 0x5f, 0x50, 0xc5, 0x4e, 0x00, + 0xc1, 0x31, 0xb0, 0xd9, 0xef, 0xfb, 0xfc, 0xde, 0xbb, 0xef, 0x7b, 0x17, 0x68, 0xb3, 0x28, 0xf5, + 0x24, 0xc3, 0x72, 0x87, 0xf2, 0xc4, 0xdb, 0x5b, 0xba, 0x38, 0xbb, 0xbd, 0x4c, 0x28, 0x61, 0xcd, + 0xb0, 0x28, 0x75, 0x2f, 0xb0, 0xbd, 0xa5, 0x56, 0x23, 0x11, 0x89, 0xd0, 0x35, 0xaf, 0x38, 0x99, + 0xb6, 0x96, 0x9d, 0x08, 0x91, 0x30, 0xe2, 0xe9, 0x5b, 0xd4, 0xdf, 0xf6, 0xe2, 0x7e, 0x86, 0x15, + 0x15, 0xbc, 0xac, 0xa3, 0xeb, 0x75, 0x45, 0x53, 0x22, 0x15, 0x4e, 0x7b, 0xa6, 0xc1, 0x39, 0x1b, + 0x83, 0x8d, 0x77, 0x98, 0xd1, 0x18, 0x2b, 0x91, 0x6d, 0xd2, 0x84, 0x53, 0x9e, 0xac, 0xf3, 0x6d, + 0x61, 0x35, 0xe1, 0xff, 0x38, 0x8e, 0x33, 0x22, 0x65, 0x13, 0xb4, 0x41, 0xe7, 0x5e, 0x50, 0x5d, + 0xad, 0x8f, 0xb0, 0xfe, 0x09, 0x53, 0x46, 0xe2, 0xb0, 0xcf, 0x15, 0x65, 0xcd, 0xb1, 0x36, 0xe8, + 0x4c, 0x2f, 0xb7, 0x5c, 0x23, 0xe5, 0x56, 0x52, 0xee, 0x56, 0x25, 0xe5, 0xa3, 0x93, 0x1c, 0xd5, + 0x86, 0x39, 0x9a, 0x3d, 0xc0, 0x29, 0x5b, 0x71, 0xae, 0xbe, 0x76, 0x0e, 0x7f, 0x21, 0x10, 0x4c, + 0x1b, 0xe8, 0x6d, 0x81, 0x58, 0x36, 0x84, 0x4a, 0xa4, 0x91, 0x54, 0x82, 0x93, 0xb8, 0x39, 0xde, + 0x06, 0x9d, 0xa9, 0xe0, 0x0a, 0x62, 0x6d, 0xc1, 0xb9, 0x94, 0x4a, 0x49, 0xe2, 0x30, 0x62, 0xa2, + 0xbb, 0x2b, 0xc3, 0xae, 0xe8, 0x73, 0x45, 0xb2, 0xe6, 0x44, 0x1b, 0x74, 0xc6, 0xfd, 0xf6, 0x30, + 0x47, 0x0b, 0x46, 0x68, 0x64, 0x9b, 0x13, 0xcc, 0x1a, 0xdc, 0xd7, 0xf0, 0x73, 0x83, 0x5a, 0x2f, + 0xe0, 0xfd, 0x3d, 0xa1, 0x48, 0x16, 0x4a, 0xa2, 0x2e, 0x18, 0xff, 0xd3, 0x8c, 0x0b, 0xc3, 0x1c, + 0x35, 0x0d, 0xe3, 0x8d, 0x16, 0x27, 0x98, 0xd1, 0xd8, 0x26, 0x51, 0x25, 0xd3, 0xca, 0xd4, 0xd1, + 0x31, 0xaa, 0xfd, 0x39, 0x46, 0xc0, 0xf9, 0x3b, 0x01, 0x27, 0x37, 0x70, 0x86, 0x53, 0x69, 0xbd, + 0x81, 0x0d, 0x49, 0x13, 0x7e, 0xe9, 0x66, 0x9f, 0xf2, 0x58, 0xec, 0xeb, 0xd9, 0x8e, 0xfb, 0x68, + 0x98, 0xa3, 0x47, 0x46, 0x61, 0x54, 0x97, 0x13, 0x58, 0x06, 0x36, 0x96, 0xdf, 0x6b, 0xd0, 0xfa, + 0x5a, 0xcc, 0x81, 0x87, 0xe5, 0x83, 0x1e, 0xc9, 0x2a, 0xce, 0x62, 0x21, 0x75, 0x7f, 0xbd, 0x18, + 0xfa, 0x59, 0x8e, 0x16, 0x13, 0xaa, 0x76, 0xfa, 0x91, 0xdb, 0x15, 0xa9, 0xc7, 0x28, 0x27, 0x1e, + 0x8b, 0xd2, 0xa7, 0x32, 0xde, 0xf5, 0xd4, 0x41, 0x8f, 0x48, 0x77, 0x95, 0x74, 0xaf, 0x0e, 0x6c, + 0x04, 0x9f, 0x13, 0x58, 0x29, 0xe5, 0x9b, 0x1a, 0xde, 0x20, 0x59, 0xa9, 0xfe, 0x05, 0x3e, 0x88, + 0xc5, 0x3e, 0x2f, 0xf2, 0x14, 0x16, 0xdb, 0x0b, 0xab, 0xe4, 0xe9, 0x8d, 0x4d, 0x2f, 0x3f, 0xbc, + 0x91, 0x87, 0xd5, 0xb2, 0xc1, 0x7f, 0x52, 0xc6, 0xe1, 0xb1, 0x11, 0x1d, 0x4d, 0xe3, 0x1c, 0x15, + 0xc1, 0x68, 0x54, 0xc5, 0x97, 0x98, 0xb2, 0x8a, 0xc0, 0xfa, 0x0e, 0x60, 0x4b, 0xff, 0x37, 0xc2, + 0xed, 0x0c, 0x77, 0x0b, 0x28, 0x8c, 0x45, 0x3f, 0x62, 0x44, 0x9b, 0xd7, 0x39, 0xa8, 0xfb, 0xaf, + 0xef, 0xf2, 0xfb, 0x17, 0xcb, 0xe1, 0xdf, 0x4a, 0xea, 0x04, 0xf3, 0xba, 0xb8, 0x56, 0xd6, 0x56, + 0x75, 0xa9, 0x18, 0x8a, 0xf5, 0x0d, 0xc0, 0xf9, 0x1b, 0x0f, 0x8d, 0x6b, 0x1d, 0xa0, 0xba, 0xff, + 0xea, 0x2e, 0x56, 0xec, 0x5b, 0xac, 0x18, 0x46, 0x27, 0x98, 0xbb, 0xe6, 0xc3, 0xe0, 0xfe, 0xda, + 0x8f, 0x81, 0x0d, 0x4e, 0x06, 0x36, 0x38, 0x1d, 0xd8, 0xe0, 0xf7, 0xc0, 0x06, 0x87, 0xe7, 0x76, + 0xed, 0xf4, 0xdc, 0xae, 0xfd, 0x3c, 0xb7, 0x6b, 0x1f, 0x3a, 0xb7, 0x29, 0x7f, 0xbe, 0xfc, 0x08, + 0x69, 0x13, 0xd1, 0xa4, 0xde, 0xd7, 0xb3, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x53, 0x8f, 0x16, + 0x77, 0xa1, 0x04, 0x00, 0x00, } func (this *ValidatorSigningInfo) Equal(that interface{}) bool { @@ -249,12 +239,6 @@ func (this *ValidatorSigningInfo) Equal(that interface{}) bool { if this.Address != that1.Address { return false } - if this.StartHeight != that1.StartHeight { - return false - } - if this.IndexOffset != that1.IndexOffset { - return false - } if !this.JailedUntil.Equal(that1.JailedUntil) { return false } @@ -264,6 +248,9 @@ func (this *ValidatorSigningInfo) Equal(that interface{}) bool { if this.MissedBlocksCounter != that1.MissedBlocksCounter { return false } + if this.VoterSetCounter != that1.VoterSetCounter { + return false + } return true } func (this *Params) Equal(that interface{}) bool { @@ -322,10 +309,15 @@ func (m *ValidatorSigningInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.VoterSetCounter != 0 { + i = encodeVarintSlashing(dAtA, i, uint64(m.VoterSetCounter)) + i-- + dAtA[i] = 0x28 + } if m.MissedBlocksCounter != 0 { i = encodeVarintSlashing(dAtA, i, uint64(m.MissedBlocksCounter)) i-- - dAtA[i] = 0x30 + dAtA[i] = 0x20 } if m.Tombstoned { i-- @@ -335,7 +327,7 @@ func (m *ValidatorSigningInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0 } i-- - dAtA[i] = 0x28 + dAtA[i] = 0x18 } n1, err1 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.JailedUntil, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.JailedUntil):]) if err1 != nil { @@ -344,17 +336,7 @@ func (m *ValidatorSigningInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= n1 i = encodeVarintSlashing(dAtA, i, uint64(n1)) i-- - dAtA[i] = 0x22 - if m.IndexOffset != 0 { - i = encodeVarintSlashing(dAtA, i, uint64(m.IndexOffset)) - i-- - dAtA[i] = 0x18 - } - if m.StartHeight != 0 { - i = encodeVarintSlashing(dAtA, i, uint64(m.StartHeight)) - i-- - dAtA[i] = 0x10 - } + dAtA[i] = 0x12 if len(m.Address) > 0 { i -= len(m.Address) copy(dAtA[i:], m.Address) @@ -452,12 +434,6 @@ func (m *ValidatorSigningInfo) Size() (n int) { if l > 0 { n += 1 + l + sovSlashing(uint64(l)) } - if m.StartHeight != 0 { - n += 1 + sovSlashing(uint64(m.StartHeight)) - } - if m.IndexOffset != 0 { - n += 1 + sovSlashing(uint64(m.IndexOffset)) - } l = github_com_gogo_protobuf_types.SizeOfStdTime(m.JailedUntil) n += 1 + l + sovSlashing(uint64(l)) if m.Tombstoned { @@ -466,6 +442,9 @@ func (m *ValidatorSigningInfo) Size() (n int) { if m.MissedBlocksCounter != 0 { n += 1 + sovSlashing(uint64(m.MissedBlocksCounter)) } + if m.VoterSetCounter != 0 { + n += 1 + sovSlashing(uint64(m.VoterSetCounter)) + } return n } @@ -557,44 +536,6 @@ func (m *ValidatorSigningInfo) Unmarshal(dAtA []byte) error { m.Address = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field StartHeight", wireType) - } - m.StartHeight = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowSlashing - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.StartHeight |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field IndexOffset", wireType) - } - m.IndexOffset = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowSlashing - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.IndexOffset |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field JailedUntil", wireType) } @@ -627,7 +568,7 @@ func (m *ValidatorSigningInfo) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 5: + case 3: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Tombstoned", wireType) } @@ -647,7 +588,7 @@ func (m *ValidatorSigningInfo) Unmarshal(dAtA []byte) error { } } m.Tombstoned = bool(v != 0) - case 6: + case 4: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field MissedBlocksCounter", wireType) } @@ -666,6 +607,25 @@ func (m *ValidatorSigningInfo) Unmarshal(dAtA []byte) error { break } } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field VoterSetCounter", wireType) + } + m.VoterSetCounter = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSlashing + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.VoterSetCounter |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipSlashing(dAtA[iNdEx:]) diff --git a/x/wasm/linkwasmd/app/export.go b/x/wasm/linkwasmd/app/export.go index 5ac6b8a0f0..4f44761566 100644 --- a/x/wasm/linkwasmd/app/export.go +++ b/x/wasm/linkwasmd/app/export.go @@ -176,11 +176,11 @@ func (app *LinkApp) 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 }, From b914b62edd78c9683708c441101b9b55b4a04b69 Mon Sep 17 00:00:00 2001 From: Youngtaek Yoon Date: Wed, 6 Oct 2021 04:57:32 +0000 Subject: [PATCH 2/7] fix: fix off-by-one error and add a test to catch it --- x/slashing/keeper/infractions.go | 2 +- x/slashing/keeper/keeper_test.go | 64 +++++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/x/slashing/keeper/infractions.go b/x/slashing/keeper/infractions.go index f4badbff61..9b803a942d 100644 --- a/x/slashing/keeper/infractions.go +++ b/x/slashing/keeper/infractions.go @@ -70,7 +70,7 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre ) } - minVoterSetCount := k.SignedBlocksWindow(ctx) + minVoterSetCount := k.SignedBlocksWindow(ctx) - 1 maxMissed := k.SignedBlocksWindow(ctx) - minSignedPerWindow // if we have joined enough times to voter set and the validator has missed too many blocks, punish them diff --git a/x/slashing/keeper/keeper_test.go b/x/slashing/keeper/keeper_test.go index a5b750fce7..8d6f60e140 100644 --- a/x/slashing/keeper/keeper_test.go +++ b/x/slashing/keeper/keeper_test.go @@ -222,30 +222,28 @@ func TestValidatorDippingInAndOut(t *testing.T) { tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false) newPower := int64(150) - // 300 more blocks happend + // validator misses 501 blocks exceeding the liveness threshold latest := height - for ; height < latest+300; height++ { + for ; height < latest+501; height++ { ctx = ctx.WithBlockHeight(height) - app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true) + app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, false) } - // validator misses 501 blocks exceeding the liveness threshold + // 398 more blocks happend latest = height - for ; height < latest+501; height++ { + for ; height < latest+398; height++ { ctx = ctx.WithBlockHeight(height) - app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, false) + app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true) } // shouldn't be jailed/kicked yet because it had not joined to vote set 1000 times - // 100 times + (kicked) + 300 times + 501 times = 901 times + // 100 times + (kicked) + 501 times + 398 times = 999 times tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false) - // 100 more blocks happend - latest = height - for ; height < latest+100; height++ { - ctx = ctx.WithBlockHeight(height) - app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true) - } + // another block happend + ctx = ctx.WithBlockHeight(height) + app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true) + height++ // should now be jailed & kicked staking.EndBlocker(ctx, app.StakingKeeper) @@ -255,7 +253,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { signInfo, found := app.SlashingKeeper.GetValidatorSigningInfo(ctx, consAddr) require.True(t, found) require.Equal(t, int64(0), signInfo.MissedBlocksCounter) - require.Equal(t, int64(1001), signInfo.VoterSetCounter) + require.Equal(t, int64(1000), signInfo.VoterSetCounter) // array should be cleared for offset := int64(0); offset < app.SlashingKeeper.SignedBlocksWindow(ctx); offset++ { missed := app.SlashingKeeper.GetValidatorMissedBlockBitArray(ctx, consAddr, offset) @@ -285,4 +283,42 @@ func TestValidatorDippingInAndOut(t *testing.T) { // validator should now be jailed & kicked staking.EndBlocker(ctx, app.StakingKeeper) tstaking.CheckValidator(valAddr, stakingtypes.Unbonding, true) + + // some blocks pass + height = int64(10000) + ctx = ctx.WithBlockHeight(height) + + // validator rejoins and starts signing again + app.StakingKeeper.Unjail(ctx, consAddr) + app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true) + height++ + + // validator should not be kicked since we reset counter/array when it was jailed + staking.EndBlocker(ctx, app.StakingKeeper) + tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false) + + // 1000 blocks happend + latest = height + for ; height < latest+1000; height++ { + ctx = ctx.WithBlockHeight(height) + app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true) + } + + // validator misses 500 blocks + latest = height + for ; height < latest+500; height++ { + ctx = ctx.WithBlockHeight(height) + app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, false) + } + tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false) + + // validator misses another block + ctx = ctx.WithBlockHeight(height) + app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, false) + height++ + + // validator should now be jailed & kicked + staking.EndBlocker(ctx, app.StakingKeeper) + tstaking.CheckValidator(valAddr, stakingtypes.Unbonding, true) + } From 45d50adb7a15ca568c356a409b59181299066458 Mon Sep 17 00:00:00 2001 From: Youngtaek Yoon Date: Wed, 6 Oct 2021 23:53:02 +0000 Subject: [PATCH 3/7] refactor: change the meaning of `VoteSetCounter` Now `VoteSetCounter` in HandleValidatorSignature indicates the real counted number, not a index-like number. --- x/slashing/keeper/infractions.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x/slashing/keeper/infractions.go b/x/slashing/keeper/infractions.go index 9b803a942d..1c41c99be7 100644 --- a/x/slashing/keeper/infractions.go +++ b/x/slashing/keeper/infractions.go @@ -27,9 +27,9 @@ 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 the beginning - voterSetCounter := signInfo.VoterSetCounter signInfo.VoterSetCounter++ - index := voterSetCounter % k.SignedBlocksWindow(ctx) + 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 @@ -70,7 +70,7 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre ) } - minVoterSetCount := k.SignedBlocksWindow(ctx) - 1 + minVoterSetCount := k.SignedBlocksWindow(ctx) maxMissed := k.SignedBlocksWindow(ctx) - minSignedPerWindow // if we have joined enough times to voter set and the validator has missed too many blocks, punish them From 6856809ce5c213245c5ac04055a4d77440bb3c00 Mon Sep 17 00:00:00 2001 From: Youngtaek Yoon Date: Thu, 7 Oct 2021 01:00:21 +0000 Subject: [PATCH 4/7] fix: check liveness threshold from the second round of the window Check the liveness threshold only provided `VoterSetCounter > SignedBlocksWindow` meaning `VoterSetCounter >= SignedBlocksWindow + 1`. --- x/slashing/keeper/infractions.go | 2 +- x/slashing/keeper/keeper_test.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x/slashing/keeper/infractions.go b/x/slashing/keeper/infractions.go index 1c41c99be7..5fc96ae5ab 100644 --- a/x/slashing/keeper/infractions.go +++ b/x/slashing/keeper/infractions.go @@ -74,7 +74,7 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre maxMissed := k.SignedBlocksWindow(ctx) - minSignedPerWindow // 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 { + if voterSetCounter > minVoterSetCount && signInfo.MissedBlocksCounter > maxMissed { validator := k.sk.ValidatorByConsAddr(ctx, consAddr) if validator != nil && !validator.IsJailed() { // Downtime confirmed: slash and jail the validator diff --git a/x/slashing/keeper/keeper_test.go b/x/slashing/keeper/keeper_test.go index 8d6f60e140..6e1d32baf8 100644 --- a/x/slashing/keeper/keeper_test.go +++ b/x/slashing/keeper/keeper_test.go @@ -229,15 +229,15 @@ func TestValidatorDippingInAndOut(t *testing.T) { app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, false) } - // 398 more blocks happend + // 399 more blocks happend latest = height - for ; height < latest+398; height++ { + for ; height < latest+399; height++ { ctx = ctx.WithBlockHeight(height) app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true) } - // shouldn't be jailed/kicked yet because it had not joined to vote set 1000 times - // 100 times + (kicked) + 501 times + 398 times = 999 times + // shouldn't be jailed/kicked yet because it had not joined to vote set 1000 + 1 times + // 100 times + (kicked) + 501 times + 398 times = 1000 times tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false) // another block happend @@ -253,7 +253,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { signInfo, found := app.SlashingKeeper.GetValidatorSigningInfo(ctx, consAddr) require.True(t, found) require.Equal(t, int64(0), signInfo.MissedBlocksCounter) - require.Equal(t, int64(1000), signInfo.VoterSetCounter) + require.Equal(t, int64(1001), signInfo.VoterSetCounter) // array should be cleared for offset := int64(0); offset < app.SlashingKeeper.SignedBlocksWindow(ctx); offset++ { missed := app.SlashingKeeper.GetValidatorMissedBlockBitArray(ctx, consAddr, offset) From 3af603cd4b02797fc3fb7e9d056d74c46f175182 Mon Sep 17 00:00:00 2001 From: Youngtaek Yoon Date: Thu, 7 Oct 2021 01:13:50 +0000 Subject: [PATCH 5/7] chore: provide more meaningful log Log `VoterSetCounter` instead of `minVoterSetCount` which is constant. --- x/slashing/keeper/infractions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/slashing/keeper/infractions.go b/x/slashing/keeper/infractions.go index 5fc96ae5ab..3b6591ca31 100644 --- a/x/slashing/keeper/infractions.go +++ b/x/slashing/keeper/infractions.go @@ -107,7 +107,7 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre "slashing and jailing validator due to liveness fault", "height", height, "validator", consAddr.String(), - "min_voter_set_count", minVoterSetCount, + "voter_set_counter", voterSetCounter, "threshold", minSignedPerWindow, "slashed", k.SlashFractionDowntime(ctx).String(), "jailed_until", signInfo.JailedUntil, From b8fe202a1850b8970651a92981ae50ebbadb1fb1 Mon Sep 17 00:00:00 2001 From: Youngtaek Yoon Date: Thu, 7 Oct 2021 02:11:10 +0000 Subject: [PATCH 6/7] style: correct typos --- x/slashing/keeper/keeper_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/x/slashing/keeper/keeper_test.go b/x/slashing/keeper/keeper_test.go index 6e1d32baf8..8dd2940582 100644 --- a/x/slashing/keeper/keeper_test.go +++ b/x/slashing/keeper/keeper_test.go @@ -211,7 +211,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { tstaking.CheckValidator(valAddr, stakingtypes.Unbonding, false) // 600 more blocks happened - height = 700 + height = int64(700) ctx = ctx.WithBlockHeight(height) // validator added back in @@ -229,18 +229,18 @@ func TestValidatorDippingInAndOut(t *testing.T) { app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, false) } - // 399 more blocks happend + // 399 more blocks happened latest = height for ; height < latest+399; height++ { ctx = ctx.WithBlockHeight(height) app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true) } - // shouldn't be jailed/kicked yet because it had not joined to vote set 1000 + 1 times + // shouldn't be jailed/kicked yet because it have not joined to vote set 1000 + 1 times // 100 times + (kicked) + 501 times + 398 times = 1000 times tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false) - // another block happend + // another block happened ctx = ctx.WithBlockHeight(height) app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true) height++ @@ -297,7 +297,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { staking.EndBlocker(ctx, app.StakingKeeper) tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false) - // 1000 blocks happend + // 1000 blocks happened latest = height for ; height < latest+1000; height++ { ctx = ctx.WithBlockHeight(height) From 43341a0263a82a3f7c48541f548859ced3e17ee0 Mon Sep 17 00:00:00 2001 From: Youngtaek Yoon Date: Thu, 7 Oct 2021 04:05:56 +0000 Subject: [PATCH 7/7] Revert "fix: check liveness threshold from the second round of ..." This reverts commit 6856809ce5c213245c5ac04055a4d77440bb3c00. --- x/slashing/keeper/infractions.go | 2 +- x/slashing/keeper/keeper_test.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x/slashing/keeper/infractions.go b/x/slashing/keeper/infractions.go index 3b6591ca31..683cacdf32 100644 --- a/x/slashing/keeper/infractions.go +++ b/x/slashing/keeper/infractions.go @@ -74,7 +74,7 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre maxMissed := k.SignedBlocksWindow(ctx) - minSignedPerWindow // 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 { + if voterSetCounter >= minVoterSetCount && signInfo.MissedBlocksCounter > maxMissed { validator := k.sk.ValidatorByConsAddr(ctx, consAddr) if validator != nil && !validator.IsJailed() { // Downtime confirmed: slash and jail the validator diff --git a/x/slashing/keeper/keeper_test.go b/x/slashing/keeper/keeper_test.go index 8dd2940582..660d765f0c 100644 --- a/x/slashing/keeper/keeper_test.go +++ b/x/slashing/keeper/keeper_test.go @@ -229,15 +229,15 @@ func TestValidatorDippingInAndOut(t *testing.T) { app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, false) } - // 399 more blocks happened + // 398 more blocks happened latest = height - for ; height < latest+399; height++ { + for ; height < latest+398; height++ { ctx = ctx.WithBlockHeight(height) app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true) } - // shouldn't be jailed/kicked yet because it have not joined to vote set 1000 + 1 times - // 100 times + (kicked) + 501 times + 398 times = 1000 times + // shouldn't be jailed/kicked yet because it have not joined to vote set 1000 times + // 100 times + (kicked) + 501 times + 398 times = 999 times tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false) // another block happened @@ -253,7 +253,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { signInfo, found := app.SlashingKeeper.GetValidatorSigningInfo(ctx, consAddr) require.True(t, found) require.Equal(t, int64(0), signInfo.MissedBlocksCounter) - require.Equal(t, int64(1001), signInfo.VoterSetCounter) + require.Equal(t, int64(1000), signInfo.VoterSetCounter) // array should be cleared for offset := int64(0); offset < app.SlashingKeeper.SignedBlocksWindow(ctx); offset++ { missed := app.SlashingKeeper.GetValidatorMissedBlockBitArray(ctx, consAddr, offset)