refactor!: x/slashing missed block window (#15580)

This commit is contained in:
Aleksandr Bezobchuk 2023-04-10 12:58:41 -04:00 committed by GitHub
parent 96662df62f
commit 4684c854d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 597 additions and 162 deletions

View File

@ -63,6 +63,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Improvements
* (x/slashing) [#15580](https://github.com/cosmos/cosmos-sdk/pull/15580) Refactor the validator's missed block signing window to be a chunked bitmap instead of a "logical" bitmap, significantly reducing the storage footprint.
* [#15448](https://github.com/cosmos/cosmos-sdk/pull/15448) Automatically populate the block timestamp for historical queries. In contexts where the block timestamp is needed for previous states, the timestamp will now be set. Note, when querying against a node it must be re-synced in order to be able to automatically populate the block timestamp. Otherwise, the block timestamp will be populated for heights going forward once upgraded.
* (x/gov) [#15554](https://github.com/cosmos/cosmos-sdk/pull/15554) Add proposal result log in `active_proposal` event. When a proposal passes but fails to execute, the proposal result is logged in the `active_proposal` event.
* (mempool) [#15328](https://github.com/cosmos/cosmos-sdk/pull/15328) Improve the `PriorityNonceMempool`
@ -98,6 +99,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### State Machine Breaking
* (x/slashing) [#15580](https://github.com/cosmos/cosmos-sdk/pull/15580) The validator slashing window now stores "chunked" bitmap entries for each validator's signing window instead of a single boolean entry per signing window index.
* (x/feegrant) [#14294](https://github.com/cosmos/cosmos-sdk/pull/14294) Moved the logic of rejecting duplicate grant from `msg_server` to `keeper` method.
* (x/staking) [#14590](https://github.com/cosmos/cosmos-sdk/pull/14590) `MsgUndelegateResponse` now includes undelegated amount. `x/staking` module's `keeper.Undelegate` now returns 3 values (completionTime,undelegateAmount,error) instead of 2.

View File

@ -1416,19 +1416,20 @@ type ValidatorSigningInfo struct {
unknownFields protoimpl.UnknownFields
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
// Height at which validator was first a candidate OR was unjailed
// Height at which validator was first a candidate OR was un-jailed
StartHeight int64 `protobuf:"varint,2,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"`
// 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`.
// 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.
IndexOffset int64 `protobuf:"varint,3,opt,name=index_offset,json=indexOffset,proto3" json:"index_offset,omitempty"`
// Timestamp until which the validator is jailed due to liveness downtime.
JailedUntil *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=jailed_until,json=jailedUntil,proto3" json:"jailed_until,omitempty"`
// Whether or not a validator has been tombstoned (killed out of validator set). It is set
// once the validator commits an equivocation or for any other configured misbehiavor.
// Whether or not a validator has been tombstoned (killed out of validator
// set). It is set once the validator commits an equivocation or for any other
// configured misbehavior.
Tombstoned bool `protobuf:"varint,5,opt,name=tombstoned,proto3" json:"tombstoned,omitempty"`
// A counter kept to avoid unnecessary array reads.
// Note that `Sum(MissedBlocksBitArray)` always equals `MissedBlocksCounter`.
// A counter of missed (unsigned) blocks. It is used to avoid unnecessary
// reads in the missed block bitmap.
MissedBlocksCounter int64 `protobuf:"varint,6,opt,name=missed_blocks_counter,json=missedBlocksCounter,proto3" json:"missed_blocks_counter,omitempty"`
}

1
go.mod
View File

@ -15,6 +15,7 @@ require (
github.com/99designs/keyring v1.2.1
github.com/armon/go-metrics v0.4.1
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816
github.com/bits-and-blooms/bitset v1.5.0
github.com/chzyer/readline v1.5.1
github.com/cockroachdb/apd/v2 v2.0.2
github.com/cockroachdb/errors v1.9.1

2
go.sum
View File

@ -111,6 +111,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2gVpmOtVTJZNodLdLQLn/KsJqFvXwnd/s=
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8=
github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/btcutil v1.1.2 h1:XLMbX8JQEiwMcYft2EGi8zPUkoa0abKIU6/BJSRsjzQ=

View File

@ -16,20 +16,24 @@ message ValidatorSigningInfo {
option (gogoproto.equal) = true;
string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// Height at which validator was first a candidate OR was unjailed
// Height at which validator was first a candidate OR was un-jailed
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`.
// 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;
// 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];
// Whether or not a validator has been tombstoned (killed out of validator set). It is set
// once the validator commits an equivocation or for any other configured misbehiavor.
google.protobuf.Timestamp jailed_until = 4 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true
];
// Whether or not a validator has been tombstoned (killed out of validator
// set). It is set once the validator commits an equivocation or for any other
// configured misbehavior.
bool tombstoned = 5;
// A counter kept to avoid unnecessary array reads.
// Note that `Sum(MissedBlocksBitArray)` always equals `MissedBlocksCounter`.
// A counter of missed (unsigned) blocks. It is used to avoid unnecessary
// reads in the missed block bitmap.
int64 missed_blocks_counter = 6;
}
@ -44,8 +48,11 @@ message Params {
(amino.encoding) = "cosmos_dec_bytes",
(amino.dont_omitempty) = true
];
google.protobuf.Duration downtime_jail_duration = 3
[(gogoproto.nullable) = false, (amino.dont_omitempty) = true, (gogoproto.stdduration) = true];
google.protobuf.Duration downtime_jail_duration = 3 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(gogoproto.stdduration) = true
];
bytes slash_fraction_double_sign = 4 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false,

View File

@ -49,6 +49,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect
github.com/bits-and-blooms/bitset v1.5.0 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cespare/xxhash v1.1.0 // indirect

View File

@ -267,6 +267,8 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2gVpmOtVTJZNodLdLQLn/KsJqFvXwnd/s=
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8=
github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/btcutil v1.1.2 h1:XLMbX8JQEiwMcYft2EGi8zPUkoa0abKIU6/BJSRsjzQ=

View File

@ -49,6 +49,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect
github.com/bits-and-blooms/bitset v1.5.0 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cespare/xxhash v1.1.0 // indirect

View File

@ -267,6 +267,8 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2gVpmOtVTJZNodLdLQLn/KsJqFvXwnd/s=
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8=
github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/btcutil v1.1.2 h1:XLMbX8JQEiwMcYft2EGi8zPUkoa0abKIU6/BJSRsjzQ=

View File

@ -6,8 +6,8 @@ import (
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
// InitGenesis initialize default parameters
// and the keeper's address to pubkey map
// InitGenesis initializes default parameters and the keeper's address to
// pubkey map.
func (keeper Keeper) InitGenesis(ctx sdk.Context, stakingKeeper types.StakingKeeper, data *types.GenesisState) {
stakingKeeper.IterateValidators(ctx,
func(index int64, validator stakingtypes.ValidatorI) bool {
@ -15,6 +15,7 @@ func (keeper Keeper) InitGenesis(ctx sdk.Context, stakingKeeper types.StakingKee
if err != nil {
panic(err)
}
keeper.AddPubkey(ctx, consPk)
return false
},
@ -33,8 +34,11 @@ func (keeper Keeper) InitGenesis(ctx sdk.Context, stakingKeeper types.StakingKee
if err != nil {
panic(err)
}
for _, missed := range array.MissedBlocks {
keeper.SetValidatorMissedBlockBitArray(ctx, address, missed.Index, missed.Missed)
if err := keeper.SetMissedBlockBitmapValue(ctx, address, missed.Index, missed.Missed); err != nil {
panic(err)
}
}
}

View File

@ -3,6 +3,8 @@ package keeper
import (
"fmt"
"github.com/cockroachdb/errors"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/slashing/types"
@ -31,27 +33,42 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre
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 start height
// 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)
// 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 % k.SignedBlocksWindow(ctx)
signInfo.IndexOffset++
// 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)
// determine if the validator signed the previous block
previous, err := k.GetMissedBlockBitmapValue(ctx, consAddr, index)
if err != nil {
panic(errors.Wrap(err, "failed to get the validator's bitmap value"))
}
missed := !signed
switch {
case !previous && missed:
// Array value has changed from not missed to missed, increment counter
k.SetValidatorMissedBlockBitArray(ctx, consAddr, index, true)
// Bitmap value has changed from not missed to missed, so we flip the bit
// and increment the counter.
if err := k.SetMissedBlockBitmapValue(ctx, consAddr, index, true); err != nil {
panic(err)
}
signInfo.MissedBlocksCounter++
case previous && !missed:
// Array value has changed from missed to not missed, decrement counter
k.SetValidatorMissedBlockBitArray(ctx, consAddr, index, false)
// Bitmap value has changed from missed to not missed, so we flip the bit
// and decrement the counter.
if err := k.SetMissedBlockBitmapValue(ctx, consAddr, index, false); err != nil {
panic(err)
}
signInfo.MissedBlocksCounter--
default:
// Array value at this index has not changed, no need to update counter
// bitmap value at this index has not changed, no need to update counter
}
minSignedPerWindow := k.MinSignedPerWindow(ctx)
@ -105,10 +122,11 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre
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 need to reset the counter & bitmap so that the validator won't be
// immediately slashed for downtime upon re-bonding.
signInfo.MissedBlocksCounter = 0
signInfo.IndexOffset = 0
k.clearValidatorMissedBlockBitArray(ctx, consAddr)
k.DeleteMissedBlockBitmap(ctx, consAddr)
logger.Info(
"slashing and jailing validator due to liveness fault",

View File

@ -5,6 +5,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/slashing/exported"
v2 "github.com/cosmos/cosmos-sdk/x/slashing/migrations/v2"
v3 "github.com/cosmos/cosmos-sdk/x/slashing/migrations/v3"
v4 "github.com/cosmos/cosmos-sdk/x/slashing/migrations/v4"
)
// Migrator is a struct for handling in-place store migrations.
@ -30,3 +31,10 @@ func (m Migrator) Migrate1to2(ctx sdk.Context) error {
func (m Migrator) Migrate2to3(ctx sdk.Context) error {
return v3.Migrate(ctx, ctx.KVStore(m.keeper.storeKey), m.legacySubspace, m.keeper.cdc)
}
// Migrate3to4 migrates the x/slashing module state from the consensus
// version 3 to version 4. Specifically, it migrates the validator missed block
// bitmap.
func (m Migrator) Migrate3to4(ctx sdk.Context) error {
return v4.Migrate(ctx, m.keeper.cdc, ctx.KVStore(m.keeper.storeKey), m.keeper.GetParams(ctx))
}

View File

@ -3,9 +3,9 @@ package keeper
import (
"time"
gogotypes "github.com/cosmos/gogoproto/types"
storetypes "cosmossdk.io/store/types"
"github.com/bits-and-blooms/bitset"
"github.com/cockroachdb/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/slashing/types"
@ -13,20 +13,21 @@ import (
// GetValidatorSigningInfo retruns the ValidatorSigningInfo for a specific validator
// ConsAddress
func (k Keeper) GetValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress) (info types.ValidatorSigningInfo, found bool) {
func (k Keeper) GetValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress) (types.ValidatorSigningInfo, bool) {
store := ctx.KVStore(k.storeKey)
var info types.ValidatorSigningInfo
bz := store.Get(types.ValidatorSigningInfoKey(address))
if bz == nil {
found = false
return
return info, false
}
k.cdc.MustUnmarshal(bz, &info)
found = true
return
return info, true
}
// HasValidatorSigningInfo returns if a given validator has signing information
// persited.
// persisted.
func (k Keeper) HasValidatorSigningInfo(ctx sdk.Context, consAddr sdk.ConsAddress) bool {
_, ok := k.GetValidatorSigningInfo(ctx, consAddr)
return ok
@ -56,53 +57,6 @@ func (k Keeper) IterateValidatorSigningInfos(ctx sdk.Context,
}
}
// GetValidatorMissedBlockBitArray gets the bit for the missed blocks array
func (k Keeper) GetValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64) bool {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.ValidatorMissedBlockBitArrayKey(address, index))
var missed gogotypes.BoolValue
if bz == nil {
// lazy: treat empty key as not missed
return false
}
k.cdc.MustUnmarshal(bz, &missed)
return missed.Value
}
// IterateValidatorMissedBlockBitArray iterates over the signed blocks window
// and performs a callback function
func (k Keeper) IterateValidatorMissedBlockBitArray(ctx sdk.Context,
address sdk.ConsAddress, handler func(index int64, missed bool) (stop bool),
) {
store := ctx.KVStore(k.storeKey)
index := int64(0)
// Array may be sparse
for ; index < k.SignedBlocksWindow(ctx); index++ {
var missed gogotypes.BoolValue
bz := store.Get(types.ValidatorMissedBlockBitArrayKey(address, index))
if bz == nil {
continue
}
k.cdc.MustUnmarshal(bz, &missed)
if handler(index, missed.Value) {
break
}
}
}
// GetValidatorMissedBlocks returns array of missed blocks for given validator Cons address
func (k Keeper) GetValidatorMissedBlocks(ctx sdk.Context, address sdk.ConsAddress) []types.MissedBlock {
missedBlocks := []types.MissedBlock{}
k.IterateValidatorMissedBlockBitArray(ctx, address, func(index int64, missed bool) (stop bool) {
missedBlocks = append(missedBlocks, types.NewMissedBlock(index, missed))
return false
})
return missedBlocks
}
// JailUntil attempts to set a validator's JailedUntil attribute in its signing
// info. It will panic if the signing info does not exist for the validator.
func (k Keeper) JailUntil(ctx sdk.Context, consAddr sdk.ConsAddress, jailTime time.Time) {
@ -141,20 +95,135 @@ func (k Keeper) IsTombstoned(ctx sdk.Context, consAddr sdk.ConsAddress) bool {
return signInfo.Tombstoned
}
// SetValidatorMissedBlockBitArray sets the bit that checks if the validator has
// missed a block in the current window
func (k Keeper) SetValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64, missed bool) {
// getMissedBlockBitmapChunk gets the bitmap chunk at the given chunk index for
// a validator's missed block signing window.
func (k Keeper) getMissedBlockBitmapChunk(ctx sdk.Context, addr sdk.ConsAddress, chunkIndex int64) []byte {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshal(&gogotypes.BoolValue{Value: missed})
store.Set(types.ValidatorMissedBlockBitArrayKey(address, index), bz)
chunk := store.Get(types.ValidatorMissedBlockBitmapKey(addr, chunkIndex))
return chunk
}
// clearValidatorMissedBlockBitArray deletes every instance of ValidatorMissedBlockBitArray in the store
func (k Keeper) clearValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress) {
// setMissedBlockBitmapChunk sets the bitmap chunk at the given chunk index for
// a validator's missed block signing window.
func (k Keeper) setMissedBlockBitmapChunk(ctx sdk.Context, addr sdk.ConsAddress, chunkIndex int64, chunk []byte) {
store := ctx.KVStore(k.storeKey)
iter := storetypes.KVStorePrefixIterator(store, types.ValidatorMissedBlockBitArrayPrefixKey(address))
key := types.ValidatorMissedBlockBitmapKey(addr, chunkIndex)
store.Set(key, chunk)
}
// GetMissedBlockBitmapValue returns true if a validator missed signing a block
// at the given index and false otherwise. The index provided is assumed to be
// the index in the range [0, SignedBlocksWindow), which represents the bitmap
// where each bit represents a height, and is determined by the validator's
// IndexOffset modulo SignedBlocksWindow. This index is used to fetch the chunk
// in the bitmap and the relative bit in that chunk.
func (k Keeper) GetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, index int64) (bool, error) {
// get the chunk or "word" in the logical bitmap
chunkIndex := index / types.MissedBlockBitmapChunkSize
bs := bitset.New(uint(types.MissedBlockBitmapChunkSize))
chunk := k.getMissedBlockBitmapChunk(ctx, addr, chunkIndex)
if chunk != nil {
if err := bs.UnmarshalBinary(chunk); err != nil {
return false, errors.Wrapf(err, "failed to decode bitmap chunk; index: %d", index)
}
}
// get the bit position in the chunk of the logical bitmap, where Test()
// checks if the bit is set.
bitIndex := index % types.MissedBlockBitmapChunkSize
return bs.Test(uint(bitIndex)), nil
}
// SetMissedBlockBitmapValue sets, i.e. flips, a bit in the validator's missed
// block bitmap. When missed=true, the bit is set, otherwise it set to zero. The
// index provided is assumed to be the index in the range [0, SignedBlocksWindow),
// which represents the bitmap where each bit represents a height, and is
// determined by the validator's IndexOffset modulo SignedBlocksWindow. This
// index is used to fetch the chunk in the bitmap and the relative bit in that
// chunk.
func (k Keeper) SetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, index int64, missed bool) error {
// get the chunk or "word" in the logical bitmap
chunkIndex := index / types.MissedBlockBitmapChunkSize
bs := bitset.New(uint(types.MissedBlockBitmapChunkSize))
chunk := k.getMissedBlockBitmapChunk(ctx, addr, chunkIndex)
if chunk != nil {
if err := bs.UnmarshalBinary(chunk); err != nil {
return errors.Wrapf(err, "failed to decode bitmap chunk; index: %d", index)
}
}
// get the bit position in the chunk of the logical bitmap
bitIndex := uint(index % types.MissedBlockBitmapChunkSize)
if missed {
bs.Set(bitIndex)
} else {
bs.Clear(bitIndex)
}
updatedChunk, err := bs.MarshalBinary()
if err != nil {
return errors.Wrapf(err, "failed to encode bitmap chunk; index: %d", index)
}
k.setMissedBlockBitmapChunk(ctx, addr, chunkIndex, updatedChunk)
return nil
}
// DeleteMissedBlockBitmap removes a validator's missed block bitmap from state.
func (k Keeper) DeleteMissedBlockBitmap(ctx sdk.Context, addr sdk.ConsAddress) {
store := ctx.KVStore(k.storeKey)
iter := storetypes.KVStorePrefixIterator(store, types.ValidatorMissedBlockBitmapPrefixKey(addr))
defer iter.Close()
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}
}
// IterateMissedBlockBitmap iterates over a validator's signed blocks window
// bitmap and performs a callback function on each index, i.e. block height, in
// the range [0, SignedBlocksWindow).
//
// Note: A callback will only be executed over all bitmap chunks that exist in
// state.
func (k Keeper) IterateMissedBlockBitmap(ctx sdk.Context, addr sdk.ConsAddress, cb func(index int64, missed bool) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iter := storetypes.KVStorePrefixIterator(store, types.ValidatorMissedBlockBitmapPrefixKey(addr))
defer iter.Close()
var index int64
for ; iter.Valid(); iter.Next() {
bs := bitset.New(uint(types.MissedBlockBitmapChunkSize))
if err := bs.UnmarshalBinary(iter.Value()); err != nil {
panic(errors.Wrapf(err, "failed to decode bitmap chunk; index: %v", string(iter.Key())))
}
for i := uint(0); i < types.MissedBlockBitmapChunkSize; i++ {
// execute the callback, where Test() returns true if the bit is set
if cb(index, bs.Test(i)) {
break
}
index++
}
}
}
// GetValidatorMissedBlocks returns array of missed blocks for given validator.
func (k Keeper) GetValidatorMissedBlocks(ctx sdk.Context, addr sdk.ConsAddress) []types.MissedBlock {
missedBlocks := make([]types.MissedBlock, 0, k.SignedBlocksWindow(ctx))
k.IterateMissedBlockBitmap(ctx, addr, func(index int64, missed bool) (stop bool) {
if missed {
missedBlocks = append(missedBlocks, types.NewMissedBlock(index, missed))
}
return false
})
return missedBlocks
}

View File

@ -53,41 +53,44 @@ func (s *KeeperTestSuite) TestValidatorSigningInfo() {
require.Equal(sInfo.JailedUntil, jailTime)
}
func (s *KeeperTestSuite) TestValidatorMissedBlockBitArray() {
func (s *KeeperTestSuite) TestValidatorMissedBlockBitmap_SmallWindow() {
ctx, keeper := s.ctx, s.slashingKeeper
require := s.Require()
params := testutil.TestParams()
params.SignedBlocksWindow = 100
require.NoError(keeper.SetParams(ctx, params))
for _, window := range []int64{100, 32_000} {
params := testutil.TestParams()
params.SignedBlocksWindow = window
require.NoError(keeper.SetParams(ctx, params))
testCases := []struct {
name string
index int64
missed bool
}{
{
name: "missed block with false",
index: 50,
missed: false,
},
{
name: "missed block with true",
index: 51,
missed: true,
},
}
for ind, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
keeper.SetValidatorMissedBlockBitArray(ctx, consAddr, tc.index, tc.missed)
missed := keeper.GetValidatorMissedBlockBitArray(ctx, consAddr, tc.index)
// validator misses all blocks in the window
var valIdxOffset int64
for valIdxOffset < params.SignedBlocksWindow {
idx := valIdxOffset % params.SignedBlocksWindow
err := keeper.SetMissedBlockBitmapValue(ctx, consAddr, idx, true)
require.NoError(err)
require.Equal(missed, tc.missed)
missedBlocks := keeper.GetValidatorMissedBlocks(ctx, consAddr)
require.Equal(len(missedBlocks), ind+1)
require.Equal(missedBlocks[ind].Index, tc.index)
require.Equal(missedBlocks[ind].Missed, tc.missed)
})
missed, err := keeper.GetMissedBlockBitmapValue(ctx, consAddr, idx)
require.NoError(err)
require.True(missed)
valIdxOffset++
}
// validator should have missed all blocks
missedBlocks := keeper.GetValidatorMissedBlocks(ctx, consAddr)
require.Len(missedBlocks, int(params.SignedBlocksWindow))
// sign next block, which rolls the missed block bitmap
idx := valIdxOffset % params.SignedBlocksWindow
err := keeper.SetMissedBlockBitmapValue(ctx, consAddr, idx, false)
require.NoError(err)
missed, err := keeper.GetMissedBlockBitmapValue(ctx, consAddr, idx)
require.NoError(err)
require.False(missed)
// validator should have missed all blocks except the last one
missedBlocks = keeper.GetValidatorMissedBlocks(ctx, consAddr)
require.Len(missedBlocks, int(params.SignedBlocksWindow)-1)
}
}

View File

@ -0,0 +1,21 @@
package v2
import (
"encoding/binary"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
)
var ValidatorMissedBlockBitArrayKeyPrefix = []byte{0x02}
func ValidatorMissedBlockBitArrayPrefixKey(v sdk.ConsAddress) []byte {
return append(ValidatorMissedBlockBitArrayKeyPrefix, address.MustLengthPrefix(v.Bytes())...)
}
func ValidatorMissedBlockBitArrayKey(v sdk.ConsAddress, i int64) []byte {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(i))
return append(ValidatorMissedBlockBitArrayPrefixKey(v), b...)
}

View File

@ -4,9 +4,8 @@ import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
storetypes "cosmossdk.io/store/types"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/testutil"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
@ -39,7 +38,7 @@ func TestStoreMigration(t *testing.T) {
{
"ValidatorMissedBlockBitArrayKey",
v1.ValidatorMissedBlockBitArrayKey(consAddr, 2),
types.ValidatorMissedBlockBitArrayKey(consAddr, 2),
v2.ValidatorMissedBlockBitArrayKey(consAddr, 2),
},
{
"AddrPubkeyRelationKey",

View File

@ -0,0 +1,53 @@
package v4
import (
"encoding/binary"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/cosmos/cosmos-sdk/types/kv"
)
const (
addrLen = 20
MissedBlockBitmapChunkSize = 1024 // 2^10 bits
)
var (
ValidatorSigningInfoKeyPrefix = []byte{0x01}
validatorMissedBlockBitArrayKeyPrefix = []byte{0x02}
)
func ValidatorSigningInfoKey(v sdk.ConsAddress) []byte {
return append(ValidatorSigningInfoKeyPrefix, address.MustLengthPrefix(v.Bytes())...)
}
func ValidatorSigningInfoAddress(key []byte) (v sdk.ConsAddress) {
// Remove prefix and address length.
kv.AssertKeyAtLeastLength(key, 3)
addr := key[2:]
return sdk.ConsAddress(addr)
}
func validatorMissedBlockBitArrayPrefixKey(v sdk.ConsAddress) []byte {
return append(validatorMissedBlockBitArrayKeyPrefix, v.Bytes()...)
}
func ValidatorMissedBlockBitArrayKey(v sdk.ConsAddress, i int64) []byte {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(i))
return append(validatorMissedBlockBitArrayPrefixKey(v), b...)
}
func validatorMissedBlockBitmapPrefixKey(v sdk.ConsAddress) []byte {
return append(validatorMissedBlockBitArrayKeyPrefix, address.MustLengthPrefix(v.Bytes())...)
}
func ValidatorMissedBlockBitmapKey(v sdk.ConsAddress, chunkIndex int64) []byte {
bz := make([]byte, 8)
binary.LittleEndian.PutUint64(bz, uint64(chunkIndex))
return append(validatorMissedBlockBitmapPrefixKey(v), bz...)
}

View File

@ -0,0 +1,153 @@
package v4
import (
"cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"
"github.com/bits-and-blooms/bitset"
gogotypes "github.com/cosmos/gogoproto/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/slashing/types"
)
// Migrate migrates state to consensus version 4. Specifically, the migration
// deletes all existing validator bitmap entries and replaces them with a real
// "chunked" bitmap.
func Migrate(ctx sdk.Context, cdc codec.BinaryCodec, store storetypes.KVStore, params types.Params) error {
// Get all the missed blocks for each validator, based on the existing signing
// info.
var missedBlocks []types.ValidatorMissedBlocks
iterateValidatorSigningInfos(ctx, cdc, store, func(addr sdk.ConsAddress, info types.ValidatorSigningInfo) (stop bool) {
bechAddr := addr.String()
localMissedBlocks := GetValidatorMissedBlocks(ctx, cdc, store, addr, params)
missedBlocks = append(missedBlocks, types.ValidatorMissedBlocks{
Address: bechAddr,
MissedBlocks: localMissedBlocks,
})
return false
})
// For each missed blocks entry, of which there should only be one per validator,
// we clear all the old entries and insert the new chunked entry.
for _, mb := range missedBlocks {
addr, err := sdk.ConsAddressFromBech32(mb.Address)
if err != nil {
return err
}
deleteValidatorMissedBlockBitArray(ctx, store, addr)
for _, b := range mb.MissedBlocks {
// Note: It is not necessary to store entries with missed=false, i.e. where
// the bit is zero, since when the bitmap is initialized, all non-set bits
// are already zero.
if b.Missed {
if err := setMissedBlockBitmapValue(ctx, store, addr, b.Index, true); err != nil {
return err
}
}
}
}
return nil
}
func iterateValidatorSigningInfos(
ctx sdk.Context,
cdc codec.BinaryCodec,
store storetypes.KVStore,
cb func(address sdk.ConsAddress, info types.ValidatorSigningInfo) (stop bool),
) {
iter := storetypes.KVStorePrefixIterator(store, ValidatorSigningInfoKeyPrefix)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
address := ValidatorSigningInfoAddress(iter.Key())
var info types.ValidatorSigningInfo
cdc.MustUnmarshal(iter.Value(), &info)
if cb(address, info) {
break
}
}
}
func iterateValidatorMissedBlockBitArray(
ctx sdk.Context,
cdc codec.BinaryCodec,
store storetypes.KVStore,
addr sdk.ConsAddress,
params types.Params,
cb func(index int64, missed bool) (stop bool),
) {
for i := int64(0); i < params.SignedBlocksWindow; i++ {
var missed gogotypes.BoolValue
bz := store.Get(ValidatorMissedBlockBitArrayKey(addr, i))
if bz == nil {
continue
}
cdc.MustUnmarshal(bz, &missed)
if cb(i, missed.Value) {
break
}
}
}
func GetValidatorMissedBlocks(
ctx sdk.Context,
cdc codec.BinaryCodec,
store storetypes.KVStore,
addr sdk.ConsAddress,
params types.Params,
) []types.MissedBlock {
var missedBlocks []types.MissedBlock
iterateValidatorMissedBlockBitArray(ctx, cdc, store, addr, params, func(index int64, missed bool) (stop bool) {
missedBlocks = append(missedBlocks, types.NewMissedBlock(index, missed))
return false
})
return missedBlocks
}
func deleteValidatorMissedBlockBitArray(ctx sdk.Context, store storetypes.KVStore, addr sdk.ConsAddress) {
iter := storetypes.KVStorePrefixIterator(store, validatorMissedBlockBitArrayPrefixKey(addr))
defer iter.Close()
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}
}
func setMissedBlockBitmapValue(ctx sdk.Context, store storetypes.KVStore, addr sdk.ConsAddress, index int64, missed bool) error {
// get the chunk or "word" in the logical bitmap
chunkIndex := index / MissedBlockBitmapChunkSize
key := ValidatorMissedBlockBitmapKey(addr, chunkIndex)
bs := bitset.New(uint(MissedBlockBitmapChunkSize))
chunk := store.Get(key)
if chunk != nil {
if err := bs.UnmarshalBinary(chunk); err != nil {
return errors.Wrapf(err, "failed to decode bitmap chunk; index: %d", index)
}
}
// get the bit position in the chunk of the logical bitmap
bitIndex := uint(index % MissedBlockBitmapChunkSize)
if missed {
bs.Set(bitIndex)
} else {
bs.Clear(bitIndex)
}
updatedChunk, err := bs.MarshalBinary()
if err != nil {
return errors.Wrapf(err, "failed to encode bitmap chunk; index: %d", index)
}
store.Set(key, updatedChunk)
return nil
}

View File

@ -0,0 +1,64 @@
package v4_test
import (
"testing"
storetypes "cosmossdk.io/store/types"
"github.com/bits-and-blooms/bitset"
gogotypes "github.com/cosmos/gogoproto/types"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
"github.com/cosmos/cosmos-sdk/x/slashing"
v4 "github.com/cosmos/cosmos-sdk/x/slashing/migrations/v4"
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
)
var consAddr = sdk.ConsAddress(sdk.AccAddress([]byte("addr1_______________")))
func TestMigrate(t *testing.T) {
cdc := moduletestutil.MakeTestEncodingConfig(slashing.AppModuleBasic{}).Codec
storeKey := storetypes.NewKVStoreKey(slashingtypes.ModuleName)
tKey := storetypes.NewTransientStoreKey("transient_test")
ctx := testutil.DefaultContext(storeKey, tKey)
store := ctx.KVStore(storeKey)
params := slashingtypes.Params{SignedBlocksWindow: 100}
// store old signing info and bitmap entries
bz := cdc.MustMarshal(&slashingtypes.ValidatorSigningInfo{Address: consAddr.String()})
store.Set(v4.ValidatorSigningInfoKey(consAddr), bz)
for i := int64(0); i < params.SignedBlocksWindow; i++ {
// all even blocks are missed
missed := &gogotypes.BoolValue{Value: i%2 == 0}
bz := cdc.MustMarshal(missed)
store.Set(v4.ValidatorMissedBlockBitArrayKey(consAddr, i), bz)
}
err := v4.Migrate(ctx, cdc, store, params)
require.NoError(t, err)
// ensure old entries no longer exist and new bitmap chunk entries exist
entries := v4.GetValidatorMissedBlocks(ctx, cdc, store, consAddr, params)
require.Empty(t, entries)
for i := int64(0); i < params.SignedBlocksWindow; i++ {
chunkIndex := i / v4.MissedBlockBitmapChunkSize
chunk := store.Get(v4.ValidatorMissedBlockBitmapKey(consAddr, chunkIndex))
require.NotNil(t, chunk)
bs := bitset.New(uint(v4.MissedBlockBitmapChunkSize))
require.NoError(t, bs.UnmarshalBinary(chunk))
// ensure all even blocks are missed
bitIndex := uint(i % v4.MissedBlockBitmapChunkSize)
require.Equal(t, i%2 == 0, bs.Test(bitIndex))
require.Equal(t, i%2 == 1, !bs.Test(bitIndex))
}
// ensure there's only one chunk for a window of size 100
chunk := store.Get(v4.ValidatorMissedBlockBitmapKey(consAddr, 1))
require.Nil(t, chunk)
}

View File

@ -5,15 +5,13 @@ import (
"encoding/json"
"fmt"
abci "github.com/cometbft/cometbft/abci/types"
gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/spf13/cobra"
modulev1 "cosmossdk.io/api/cosmos/slashing/module/v1"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/depinject"
store "cosmossdk.io/store/types"
abci "github.com/cometbft/cometbft/abci/types"
gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
@ -32,7 +30,7 @@ import (
)
// ConsensusVersion defines the current x/slashing module consensus version.
const ConsensusVersion = 3
const ConsensusVersion = 4
var (
_ module.AppModuleBasic = AppModuleBasic{}
@ -148,6 +146,10 @@ func (am AppModule) RegisterServices(cfg module.Configurator) {
if err := cfg.RegisterMigration(types.ModuleName, 2, m.Migrate2to3); err != nil {
panic(fmt.Sprintf("failed to migrate x/%s from version 2 to 3: %v", types.ModuleName, err))
}
if err := cfg.RegisterMigration(types.ModuleName, 3, m.Migrate3to4); err != nil {
panic(fmt.Sprintf("failed to migrate x/%s from version 3 to 4: %v", types.ModuleName, err))
}
}
// InitGenesis performs genesis initialization for the slashing module. It returns

View File

@ -23,7 +23,7 @@ func NewDecodeStore(cdc codec.BinaryCodec) func(kvA, kvB kv.Pair) string {
cdc.MustUnmarshal(kvB.Value, &infoB)
return fmt.Sprintf("%v\n%v", infoA, infoB)
case bytes.Equal(kvA.Key[:1], types.ValidatorMissedBlockBitArrayKeyPrefix):
case bytes.Equal(kvA.Key[:1], types.ValidatorMissedBlockBitmapKeyPrefix):
var missedA, missedB gogotypes.BoolValue
cdc.MustUnmarshal(kvA.Value, &missedA)
cdc.MustUnmarshal(kvB.Value, &missedB)

View File

@ -36,7 +36,7 @@ func TestDecodeStore(t *testing.T) {
kvPairs := kv.Pairs{
Pairs: []kv.Pair{
{Key: types.ValidatorSigningInfoKey(consAddr1), Value: cdc.MustMarshal(&info)},
{Key: types.ValidatorMissedBlockBitArrayKey(consAddr1, 6), Value: cdc.MustMarshal(&missed)},
{Key: types.ValidatorMissedBlockBitmapKey(consAddr1, 6), Value: cdc.MustMarshal(&missed)},
{Key: types.AddrPubkeyRelationKey(delAddr1), Value: bz},
{Key: []byte{0x99}, Value: []byte{0x99}}, // This test should panic
},

View File

@ -17,6 +17,25 @@ const (
// RouterKey is the message route for slashing
RouterKey = ModuleName
// MissedBlockBitmapChunkSize defines the chunk size, in number of bits, of a
// validator missed block bitmap. Chunks are used to reduce the storage and
// write overhead of IAVL nodes. The total size of the bitmap is roughly in
// the range [0, SignedBlocksWindow) where each bit represents a block. A
// validator's IndexOffset modulo the SignedBlocksWindow is used to retrieve
// the chunk in that bitmap range. Once the chunk is retrieved, the same index
// is used to check or flip a bit, where if a bit is set, it indicates the
// validator missed that block.
//
// For a bitmap of N items, i.e. a validator's signed block window, the amount
// of write complexity per write with a factor of f being the overhead of
// IAVL being un-optimized, i.e. 2-4, is as follows:
//
// ChunkSize + (f * 256 <IAVL leaf hash>) + 256 * log_2(N / ChunkSize)
//
// As for the storage overhead, with the same factor f, it is as follows:
// (N - 256) + (N / ChunkSize) * (512 * f)
MissedBlockBitmapChunkSize = 1024 // 2^10 bits
)
// Keys for slashing store
@ -24,15 +43,15 @@ const (
//
// - 0x01<consAddrLen (1 Byte)><consAddress_Bytes>: ValidatorSigningInfo
//
// - 0x02<consAddrLen (1 Byte)><consAddress_Bytes><period_Bytes>: bool
// - 0x02<consAddrLen (1 Byte)><consAddress_Bytes><chunk_index>: bitmap_chunk
//
// - 0x03<accAddrLen (1 Byte)><accAddr_Bytes>: cryptotypes.PubKey
var (
ParamsKey = []byte{0x00} // Prefix for params key
ValidatorSigningInfoKeyPrefix = []byte{0x01} // Prefix for signing info
ValidatorMissedBlockBitArrayKeyPrefix = []byte{0x02} // Prefix for missed block bit array
AddrPubkeyRelationKeyPrefix = []byte{0x03} // Prefix for address-pubkey relation
ParamsKey = []byte{0x00} // Prefix for params key
ValidatorSigningInfoKeyPrefix = []byte{0x01} // Prefix for signing info
ValidatorMissedBlockBitmapKeyPrefix = []byte{0x02} // Prefix for missed block bitmap
AddrPubkeyRelationKeyPrefix = []byte{0x03} // Prefix for address-pubkey relation
)
// ValidatorSigningInfoKey - stored by *Consensus* address (not operator address)
@ -49,17 +68,19 @@ func ValidatorSigningInfoAddress(key []byte) (v sdk.ConsAddress) {
return sdk.ConsAddress(addr)
}
// ValidatorMissedBlockBitArrayPrefixKey - stored by *Consensus* address (not operator address)
func ValidatorMissedBlockBitArrayPrefixKey(v sdk.ConsAddress) []byte {
return append(ValidatorMissedBlockBitArrayKeyPrefix, address.MustLengthPrefix(v.Bytes())...)
// ValidatorMissedBlockBitmapPrefixKey returns the key prefix for a validator's
// missed block bitmap.
func ValidatorMissedBlockBitmapPrefixKey(v sdk.ConsAddress) []byte {
return append(ValidatorMissedBlockBitmapKeyPrefix, address.MustLengthPrefix(v.Bytes())...)
}
// ValidatorMissedBlockBitArrayKey - stored by *Consensus* address (not operator address)
func ValidatorMissedBlockBitArrayKey(v sdk.ConsAddress, i int64) []byte {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(i))
// ValidatorMissedBlockBitmapKey returns the key for a validator's missed block
// bitmap chunk.
func ValidatorMissedBlockBitmapKey(v sdk.ConsAddress, chunkIndex int64) []byte {
bz := make([]byte, 8)
binary.LittleEndian.PutUint64(bz, uint64(chunkIndex))
return append(ValidatorMissedBlockBitArrayPrefixKey(v), b...)
return append(ValidatorMissedBlockBitmapPrefixKey(v), bz...)
}
// AddrPubkeyRelationKey gets pubkey relation key used to get the pubkey from the address

View File

@ -35,19 +35,20 @@ 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
// Height at which validator was first a candidate OR was un-jailed
StartHeight int64 `protobuf:"varint,2,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"`
// 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`.
// 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.
IndexOffset int64 `protobuf:"varint,3,opt,name=index_offset,json=indexOffset,proto3" json:"index_offset,omitempty"`
// Timestamp until which the validator is jailed due to liveness downtime.
JailedUntil time.Time `protobuf:"bytes,4,opt,name=jailed_until,json=jailedUntil,proto3,stdtime" json:"jailed_until"`
// Whether or not a validator has been tombstoned (killed out of validator set). It is set
// once the validator commits an equivocation or for any other configured misbehiavor.
// Whether or not a validator has been tombstoned (killed out of validator
// set). It is set once the validator commits an equivocation or for any other
// configured misbehavior.
Tombstoned bool `protobuf:"varint,5,opt,name=tombstoned,proto3" json:"tombstoned,omitempty"`
// A counter kept to avoid unnecessary array reads.
// Note that `Sum(MissedBlocksBitArray)` always equals `MissedBlocksCounter`.
// A counter of missed (unsigned) blocks. It is used to avoid unnecessary
// reads in the missed block bitmap.
MissedBlocksCounter int64 `protobuf:"varint,6,opt,name=missed_blocks_counter,json=missedBlocksCounter,proto3" json:"missed_blocks_counter,omitempty"`
}