Skip to content

Commit

Permalink
perf!: Make slashing not write sign info every block (SDK: cosmos#19458
Browse files Browse the repository at this point in the history
  • Loading branch information
ValarDragon committed Feb 24, 2024
1 parent c4a2b32 commit 5114b55
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

## [Unreleased]

* (slashing) [#19458](https://github.com/cosmos/cosmos-sdk/pull/19458) Avoid writing SignInfo's for validator's who did not miss a block. (Every BeginBlock)
* (slashing) [#18959](https://github.com/cosmos/cosmos-sdk/pull/18959) Slight speedup to Slashing Beginblock logic

## [v0.47.5](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.5) - 2023-09-01
Expand Down
8 changes: 4 additions & 4 deletions proto/cosmos/slashing/v1beta1/slashing.proto
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ message ValidatorSigningInfo {
string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// Height at which validator was first a candidate OR was unjailed
int64 start_height = 2;
// 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`.
int64 index_offset = 3;
// DEPRECATED: Index which is incremented every time a validator is bonded in a block and
// _may_ have signed a pre-commit or not. This in conjunction with the
// signed_blocks_window param determines the index in the missed block bitmap.
int64 index_offset = 3 [deprecated = true];
// Timestamp until which the validator is jailed due to liveness downtime.
google.protobuf.Timestamp jailed_until = 4
[(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (amino.dont_omitempty) = true];
Expand Down
10 changes: 4 additions & 6 deletions x/slashing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ provisions and rewards.
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
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
index in this window is determined by (`IndexOffset = height - SignInfo.StartHeight`).
Note that in each block processed, the `IndexOffset` is incremented
regardless if the validator signed or not. Once the index is determined, the
`MissedBlocksBitArray` and `MissedBlocksCounter` are updated accordingly.

Expand All @@ -235,10 +235,8 @@ 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++
// signed. We use the 0-value default signing info if not present.
index := (height - signInfo.StartHeight) % SignedBlocksWindow()

// Update MissedBlocksBitArray and MissedBlocksCounter. The MissedBlocksCounter
// just tracks the sum of MissedBlocksBitArray. That way we avoid needing to
Expand Down
29 changes: 22 additions & 7 deletions x/slashing/keeper/infractions.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,35 @@ func (k Keeper) HandleValidatorSignatureWithParams(ctx sdk.Context, params types
signedBlocksWindow := params.SignedBlocksWindow

// Compute the relative index, so we count the blocks the validator *should*
// have signed. We will use the 0-value default signing info if not present,
// except for start height. The index is in the range [0, SignedBlocksWindow)
// have signed. We will also use the 0-value default signing info if not present.
// The index is in the range [0, SignedBlocksWindow)
// and is used to see if a validator signed a block at the given height, which
// is represented by a bit in the bitmap.
index := signInfo.IndexOffset % signedBlocksWindow
signInfo.IndexOffset++

// The validator start height should get mapped to index 0, so we computed index as:
// (height - startHeight) % signedBlocksWindow
//
// NOTE: There is subtle different behavior between genesis validators and non-genesis validators.
// A genesis validator will start at index 0, whereas a non-genesis validator's startHeight will be the block
// they bonded on, but the first block they vote on will be one later. (And thus their first vote is at index 1)
index := (height - signInfo.StartHeight) % signedBlocksWindow
if signInfo.StartHeight > height {
panic(fmt.Errorf("invalid state, the validator %v has start height %d , which is greater than the current height %d (as parsed from the header)",
signInfo.Address, signInfo.StartHeight, height))

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods Warning

Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt
}
// 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)
modifiedSignInfo := false
missed := !signed
switch {
case !previous && missed:
modifiedSignInfo = true
// Array value has changed from not missed to missed, increment counter
k.SetValidatorMissedBlockBitArray(ctx, consAddr, index, true)
signInfo.MissedBlocksCounter++
case previous && !missed:
modifiedSignInfo = true
// Array value has changed from missed to not missed, decrement counter
k.SetValidatorMissedBlockBitArray(ctx, consAddr, index, false)
signInfo.MissedBlocksCounter--
Expand Down Expand Up @@ -87,6 +98,7 @@ func (k Keeper) HandleValidatorSignatureWithParams(ctx sdk.Context, params types

// if we are past the minimum height and the validator has missed too many blocks, punish them
if height > minHeight && signInfo.MissedBlocksCounter > maxMissed {
modifiedSignInfo = true
validator := k.sk.ValidatorByConsAddr(ctx, consAddr)
if validator != nil && !validator.IsJailed() {
// Downtime confirmed: slash and jail the validator
Expand All @@ -113,8 +125,9 @@ func (k Keeper) HandleValidatorSignatureWithParams(ctx sdk.Context, params types
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.
// We don't set the start height as this will get correctly set
// once they bond again in the AfterValidatorBonded hook!
signInfo.MissedBlocksCounter = 0
signInfo.IndexOffset = 0
k.clearValidatorMissedBlockBitArray(ctx, consAddr)

logger.Info(
Expand All @@ -136,5 +149,7 @@ func (k Keeper) HandleValidatorSignatureWithParams(ctx sdk.Context, params types
}

// Set the updated signing info
k.SetValidatorSigningInfo(ctx, consAddr, signInfo)
if modifiedSignInfo {
k.SetValidatorSigningInfo(ctx, consAddr, signInfo)
}
}

0 comments on commit 5114b55

Please sign in to comment.